1

我正在使用 preg_replace_callback 对大量目录产品描述进行一些 SEO,并且在使用正则表达式时遇到了一些困难。

我想替换所有这些单词(帽子,衬衫),除了“男士”+ 0-2 个单词之后的单词,例如“男士漂亮的黑色帽子”,“男士长衬衫”不应该被替换。

这是一个调试代码,在实际应用程序中,我使用回调来为每个单词选择适当的替换:

$str = "men's black hat, and orange shirt!";
preg_match_all('/((\s|\.\s|,\s|\!\s|\?\s)(hat|shirt)(\s|\.|\.\s|,\s|\!|\!\s|\?|\?\s))/i', $str, &$_matches);
print_r($_matches);

谢谢

4

2 回答 2

0

我不认为可变长度的负回溯是可能的。

一个技巧是反转字符串并使用负前瞻。所以,你“理想地”想要做的事情:

preg_match_all('/(?<!\bmen\'s\s+(\w+\s+){0,2})(hat|shirt)\b/i', $str, &$_matches);

你可以做

preg_match_all('/\b(tah|trihs)(?!(\s+\w+){0,2}\s+s\'nem\b)/i', strrev($str), $rev_matches);

然后使用array_map将所有结果反转回来。

顺便说一句,\b被称为单词边界。它们可能是您要使用的,而不是所有(\s|\.|\.\s|,\s|\!|\!\s|\?|\?\s).

于 2013-08-26T16:00:58.633 回答
0

Lookbehind 必须是固定长度的,所以这种解决问题的方法是行不通的。

恕我直言,你试图preg_relace_callback做的太多了。如果您想要执行超出某个级别的复杂操作,那么放弃单个函数调用的便利性是合理的。这是解决问题的另一种方法:

  1. 用于preg_split将文本与标志一起拆分为单词,PREG_SPLIT_OFFSET_CAPTURE以便您知道每个单词在原始文本中出现的位置。
  2. 遍历单词数组。现在可以很容易地对数组进行“消极的后视”,看看帽子或衬衫前面是否有您感兴趣的任何其他术语。
  3. 每当您找到帽子或衬衫的正匹配时,使用正匹配的偏移量preg_split和(已知)长度来substr_replace打开原始文本输入。

例如:

$str = "men's black hat, and orange shirt!";
$targets = array('hat', 'shirt');
$shield = 'men\'s';
$bias = 0;

for ($i = 0; $i < count($words); ++$i) {
    list ($word, $offset) = $words[$i];

    if (!in_array($word, $targets)) {
        continue;
    }

    for ($j = max($i - 2, 0); $j < $i; ++$j) {
        if ($words[$j][0] === $shield) {
            continue 2;
        }
    }

    $replacement = 'FOO';
    $str = substr_replace($str, $replacement, $offset + $bias, strlen($word));
    $bias += strlen($replacement) - strlen($word);
}

echo $str;

看到它在行动

于 2013-08-26T16:10:28.937 回答