替换未包含在OPEN和CLOSE字样中的所有特定单词?

时间:2011-09-28 01:42:58

标签: php regex preg-replace preg-replace-callback

我有以下字符串:

  

OPEN 有人打招呼 CLOSE 即时通讯打招呼人 OPEN 有人说   你好 OPEN 他们再次打招呼 CLOSE 我现在必须走了 CLOSE 再次问好!

我正在尝试匹配所有 hello (未包含在 OPEN CLOSE 字词中)并将其替换为另一个词,可能是正则表达式和PHP的preg_replace函数(虽然我对其他方法持开放态度,因为我无法想到)。

因此,从上面的字符串开始,下面的内容将匹配(我已将它们放在带斜体的括号中以帮助您区分):

  

OPEN 有人说你好 CLOSE 我说(你好)人 OPEN 有人说   你好 OPEN 他们再次打招呼 CLOSE 我现在必须再去 CLOSE hello )了!

不太确定如何做到这一点。

编辑或许这将更好地阐明嵌套结构:

OPEN
text
CLOSE

OPEN 
text
  OPEN
   text
  CLOSE
text
CLOSE

正如你从上面所看到的那样,hello没有被注意到,因为它在OPEN ... CLOSE内(所以它们被忽略),而其他的不会被替换。

3 个答案:

答案 0 :(得分:2)

我为hello编号,因此hello2hello5应该被替换。

$s0 = 'OPEN someone said hello1 CLOSE im saying hello2 people OPEN some said hello3 OPEN they said hello4 again CLOSE i have to go now though CLOSE hello5 again!';

$regex='~
hello\d
(?=
  (?:(?!OPEN|CLOSE).)*+
  (?:
    ( 
      OPEN
      (?:
        (?:(?!OPEN|CLOSE).)*+
        |
        (?1)
      )*
      CLOSE
    )
    (?:(?!OPEN|CLOSE).)*+
  )?
  $
)
~x';

$s1=preg_replace($regex, 'goodbye', $s0);
print($s1);

输出:

OPEN someone said hello1 CLOSE im saying goodbye people OPEN some said hello3 OPEN they said hello4 again CLOSE i have to go now though CLOSE goodbye again!

<强> demo

前瞻使用递归子模式构造(?1)来尝试匹配当前匹配的单词和字符串结尾之间的零个或多个完整的嵌套OPEN...CLOSE结构。假设所有OPENCLOSE都得到了适当的平衡,这意味着它匹配的hello\d在这样的结构中而不是

答案 1 :(得分:2)

艾伦的回答很有效。但是,由于我已经花时间来编写它,所以这是另一种使用回调函数和PHP (?R)递归表达式的方法:

function highlightNonNestedHello($str) {
    $re = '/# Two global alternatives. Either...
          (                          # $1: Non-O..C stuff.
            (?:                      # Step through non-O..C chars.
              (?!\b(?:OPEN|CLOSE)\b) # If not start of OPEN or CLOSE,
              .                      # then match next char.
            )+                       # One or more non-O..C chars.
          )                          # End $1:
        |                            # Or...
          (                          # $2: O..C stuff.
            \bOPEN\b                 # Open literal delimiter.
            (?R)+                    # Recurse overall regex.
            \bCLOSE\b                # Close literal delimiter.
          )                          # End $1:
    /sx';
    return preg_replace_callback($re, '_highlightNonNestedHello_cb', $str);
}
function _highlightNonNestedHello_cb($matches) {
    // Case 1: Non-O...C stuff. Highlight all "hello".
    if ($matches[1]) {
        return preg_replace('/\bhello\b/', '(HELLO)', $matches[1]);
    }
    // Case 2: O...C stuff. Preserve as-is.
    return $matches[2];
}

答案 2 :(得分:0)

这是我的尝试,告诉我它是否适合你:

<?php

$str = 'OPEN someone said hello CLOSE im saying hello people OPEN some said hello OPEN they said hello again CLOSE i have to go now though CLOSE hello again!';
echo "<p>$str</p>"; //before

//first replace all of them
$str = str_replace('hello', '(hello)', $str);
//then replace back only those within OPEN CLOSE
function replace_back($match){return str_replace('(hello)', 'hello', $match[0]);}
$str = preg_replace_callback('/OPEN.*?\(hello\).*?CLOSE/', 'replace_back', $str); 

echo "<p>$str</p>"; //after

?>
<style>p{width:500px;background:#F1F1F1;padding:10px;font:13px Arial;}</style>