【问题标题】:preg_replace not working as expected with wildcardspreg_replace 无法使用通配符按预期工作
【发布时间】:2015-11-10 23:46:21
【问题描述】:

我有以下代码:

//Array filled with data from external file
$patterns = array('!test!', 'stuff1', 'all!!', '');

//Delete empty values in array
$patterns = array_filter($patterns);

foreach($patterns as &$item){
       $item = preg_quote($item);
}

$pattern = '/(\b|^|- |--|-)(?:'.implode('|', $patterns).')(-|--| -|\b|$)/i';

$clid = "I am the !test! stuff1 all!! string";

echo $clid;
$clid = trim(preg_replace($pattern, ' ', $clid));
echo $clid;

输出:

//I am the !test! stuff1 all!! string
//I am the !test! all!! string

我用preg_quote 转义!,那为什么?

我遇到了第二个问题,现在已经解决了,但我不知道为什么会这样。 假设$clid = "I am Jörg Müller with special chars"。如果我删除代码行$patterns = array_filter($patterns);,那么preg_replace 之后的输出是I am J。我不知道为什么,但我用array_filter 解决了这个问题。

谢谢

【问题讨论】:

  • ! 不是单词字符,所以\b 不会在" !" 之间匹配
  • 有问题!我该如何解决这个问题?是否有“更通用”的\b,其中还包括! 和这类字符
  • 可能是前导(?<!\w),以检查它前面没有单词字符,而对于尾随:(?!\w)
  • 您能更好地解释一下您的意思吗?我应该在哪里插入这些模式?

标签: php regex preg-replace preg-match preg-quote


【解决方案1】:

我会这样做的方式:

$clid = "I am the !test! stuff1 all!! string";

$items = ['!test!', 'stuff1', 'all!!', ''];

$pattern = array_reduce($items, function ($c, $i) {
    return empty($i) ? $c : $c . preg_quote($i, '~') . '|';
}, '~[- ]+(?:');

$pattern .= '(*F))(?=[- ])~u';

$result = preg_replace($pattern, '', ' ' . $clid . ' ');
$result = trim($result, "- \t\n\r\0\x0b");

demo

这个想法是用前瞻检查“单词”之后的空格或连字符。这样,这个“分隔符”就不会被消耗掉,并且模式可以处理连续的匹配。

为了避免在模式的开头出现交替(例如很慢的(?:[- ]|^)[- ]*),我在源字符串的开头添加了一个空格,在替换为trim 之后删除了该空格。

(*F)(强制模式失败)仅在此处,因为项目的交替是使用 array_reduce 构建的,它在末尾允许尾随 |

使用 u 修饰符解决了字符超出 ASCII 范围的问题。使用这个修饰符,正则表达式引擎能够处理 UTF-8 编码的字符串。

【讨论】:

    【解决方案2】:

    问题是您使用\bword boundaries 断言。但是,字符 "!" 不是 word character 并且 \b" !" 之间不匹配。

    这些是$clid 中的单词边界:

     I   a m   t h e   ! t e s t !   s t u f f 1   a l l ! !   s t r i n g
    ^ ^ ^   ^ ^     ^   ^       ^   ^           ^ ^     ^     ^           ^
    

    您可以使用lookarounds 断言每个项目是:

    1. (?:-[- ]?| +) 匹配 -[ ]--- 或一个或多个空格。
    2. (?:-[- ]?|(?= )|$) 匹配 -[ ]--- 或断言其后跟一个空格或行尾。

    正则表达式

    $pattern = '/(?:-[- ]?| +)(?:'.implode('|', $patterns).')(?:-[- ]?|(?= )|$)/i';
    

    代码

    //Array filled with data from external file
    $patterns = array('!test!', 'stuff1', 'all!!', '');
    
    //Delete empty values in array
    $patterns = array_filter($patterns);
    
    foreach($patterns as &$item){
           $item = preg_quote($item);
    }
    
    $pattern = '/(?:-[- ]?| +)(?:'.implode('|', $patterns).')(?:-[- ]?|(?= )|$)/i';
    
    
    $clid = "I am the !test! stuff1 all!! string and !test!! not matched";
    $clid = trim(preg_replace($pattern, '', $clid));
    
    echo $clid;
    

    输出

    I am the string and !test!! not matched
    

    ideone demo



    至于您的第二个问题,您的数组中有一个空项目。所以正则表达式会变成:

    (?:option1|option2|option3|)
                               ^
    

    请注意这里有第四个选项:empty 子模式。一个空的子模式总是匹配的。您的正则表达式可以解释为:

    /(\b|^|- |--|-)(-|--| -|\b|$)/i
    

    这就是你得到意想不到的结果的原因

    array_filter() 通过删除空项解决了您的问题。

    【讨论】:

    • 谢谢!但是为什么空子模式总是与特殊字符一起行动?
    • 所以完成新模式(?<!\w)(- |--|-)?(?:' . implode('|', $patterns) . ')(-|--| -)?(?!\w)。这样,当 $patterns 中只有 TEST 时,我也会匹配像 - TEST - 这样的字符串,对吧?
    • TEST$patterns 我想匹配 - TEST -, --TEST--, -TEST-, - TEST--, - TEST-, --TEST -, --TEST-, -TEST--, -TEST -, TEST。第二组和第四组之后的? 表示零次或一次,对吗?所以,它应该像我写的那样工作,我错了吗?
    • - ? 表示-- (后面有空格和没有空格?)。使用?: 或不使用?: 的确切区别是什么?
    • Yes - ? 是一个短划线,后跟一个可选的空格。不同之处在于它不使用内存来捕获该组匹配的文本。您可以在regular-expressions.info/brackets.html 中阅读有关非捕获组的信息
    猜你喜欢
    • 2015-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    • 2011-04-05
    相关资源
    最近更新 更多