【问题标题】:preg_replace when not inside double quotespreg_replace 不在双引号内时
【发布时间】:2014-01-13 01:29:22
【问题描述】:

基本上我想在句子中替换某些单词(例如单词“tree”与单词“pizza”)。限制:当需要替换的单词在双引号之间时,不应该进行替换。

例子:

The tree is green. -> REPLACE tree WITH pizza
"The" tree is "green". -> REPLACE tree WITH pizza
"The tree" is green. -> DONT REPLACE
"The tree is" green. -> DONT REPLACE
The ""tree is green. -> REPLACE tree WITH pizza

可以用正则表达式做到这一点吗?我会计算单词前双引号的数量并检查它是奇数还是偶数。但这可能在 php 中使用 preg_replace 吗?

谢谢!

//编辑:

目前我的代码如下所示:

preg_replace("/tree/", "pizza", $sentence)

但这里的问题是用双引号实现逻辑。我试过这样的事情:

preg_replace("/[^"]tree/", "pizza", $sentence)

但这不起作用,因为它只检查双引号是否在单词前面。但是上面有一些例子,这个检查失败了。 导入是我只想用正则表达式解决这个问题。

【问题讨论】:

  • 这被标记为 php,但您没有显示任何支持它是 php 的内容,您能否显示您尝试过的代码并显示您尝试在 php 中预替换的字符串。我们不是来为你编码的,所以请帮助我们。
  • 请看我的更新。

标签: php regex preg-replace


【解决方案1】:

正则表达式不是一个可以满足你每项工作所需的工具。您可以在一定程度上为此使用正则表达式,但对于嵌套引号中的所有情况,它会继续变得更加复杂。

您可以在此处使用Negative Lookahead

$text = preg_replace('/\btree\b(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/i', 'pizza', $text);

Working demo

正则表达式:

\b               the boundary between a word char (\w) and not a word char
 tree            'tree'
\b               the boundary between a word char (\w) and not a word char
(?!              look ahead to see if there is not:
 [^"]*           any character except: '"' (0 or more times)
  "              '"'
 (?:             group, but do not capture (0 or more times)
  (?:            group, but do not capture (2 times):
   [^"]*         any character except: '"' (0 or more times)
    "            '"'
  ){2}           end of grouping
 )*              end of grouping
 [^"]*           any character except: '"' (0 or more times)
 $               before an optional \n, and the end of the string
)                end of look-ahead

另一个选择是使用受控回溯,因为您可以在 中执行此操作

$text = preg_replace('/"[^"]*"(*SKIP)(*FAIL)|\btree\b/i', 'pizza', $text);

Working demo

这个想法是跳过引用中的内容。我首先匹配引号后跟除" 之外的任何字符,然后是引号,然后使子模式失败并强制正则表达式引擎不使用(*SKIP)(*FAIL) 回溯控制动词的其他替代方法重试子字符串。

【讨论】:

  • 这看起来确实很棒,并且可以按我的预期工作。你能给我解释一下吗?我看到它在寻找这个巨大的语句后面没有出现的“树”这个词。但它到底是什么?
  • 这里的所有答案都不适用于带有汉字的字符串。
  • 我刚刚在 phpstorm/php7 中尝试过这个,并且在 *SKIP 和 *FAIL 星号上出现“悬空元字符”错误。 preg_replace 自 2013 年以来是否发生了变化?
【解决方案2】:

使用一些隐藏的正则表达式功能有一个方便的技巧:

~".*?"(*SKIP)(*FAIL)|\btree\b~s

说明:

~                   # start delimiter (we could have used /, #, @ etc...)
"                   # match a double quote
.*?                 # match anything ungreedy until ...
"                   # match a double quote
(*SKIP)(*FAIL)      # make it fail
|                   # or
\btree\b            # match a tree with wordboundaries
~                   # end delimiter
s                   # setting the s modifier to match newlines with dots .

在实际的 PHP 代码中,您可能希望使用 preg_quote() 来转义正则表达式字符。这是一个小sn-p:

$search = 'tree';
$replace = 'plant';
$input = 'The tree is green.
"The" tree is "green".
"The tree" is green.
"The tree is" green.
The ""tree is green.';

$regex = '~".*?"(*SKIP)(*FAIL)|\b' . preg_quote($search, '~') . '\b~s';
$output = preg_replace($regex, $replace, $input);
echo $output;

Online regex demo     Online PHP demo

【讨论】:

  • 嘿,太好了!我在哪里可以了解更多关于这些隐藏力量的信息?
  • @Jonny5 PHP 手册没有描述 PCRE 库的所有功能。所以你最好阅读pcre manual。当然也太长了。我基本上是通过在 Stackoverflow 上闲逛来学习这些东西的……啊,阅读 perl 手册也可能是个好主意。 See this link.
  • 它是如何工作的?为什么不替换 tree 中的 That's "some tree you" have there.
  • (*SKIP)(*FAIL) 不是秘密,是吗?可以是任何东西,比如(*FOO)(*BAR)?
  • @Rudie 我已经解释过了,好吧,我想这还不够清楚......无论如何,".*?" 将匹配双引号甚至“树”之间的任何内容。我添加了(*SKIP)(*FAIL) 以使其跳过/失败匹配。之后,我添加了一个替换 |\btree\b 以匹配实际的树
【解决方案3】:

这个匹配 tree 使用前瞻:

$pattern = '~\btree\b(?=([^"]|("[^"]*"))*$)~im';

$str = '
The tree is green. -> REPLACE tree WITH pizza
"The" tree is "green". -> REPLACE tree WITH pizza
"The tree" is green. -> DONT REPLACE
"The tree is" green. -> DONT REPLACE
The ""tree is green. -> REPLACE tree WITH pizza';

echo "<pre>".preg_replace($pattern,"pizza",$str)."</pre>";

它查找tree,如果找到,则仅匹配它,如果后跟字符,这些字符不是双引号[^"] 或引用组"[^"]*",直到使用modifiers i (PCRE_CASELESS) and m (PCRE_MULTILINE) 在行尾。

我不想要绿色披萨!圣诞快乐:-)

【讨论】:

    【解决方案4】:

    将此模式tree(?=(?:(?:[^"]*"){2})*[^"]*$)gm 选项一起使用Demo

    这是它从头开始构建的方式:
    tree(?=[^"]*") "tree" 可以看到任意数量的非引号字符后跟引号
    tree(?=([^"]*"){2}) ~ 两次
    tree(?=(([^"]*"){2})*) ~ 尽可能多次
    tree(?=(([^"]*"){2})*[^"]*) ~ 然后可选非引号字符
    tree(?=(([^"]*"){2})*[^"]*$) ~ 到最后
    tree(?=(?:(?:[^"]*"){2})*[^"]*$) 添加非捕获组

    php demo

    【讨论】:

    • 就像我在另一个答案中所说的那样。这太棒了,而且效果很好。你能解释一下单词树背后的部分吗?看起来真的很难。
    • PHP 不支持 g 选项。 (在我看来它应该被授予,因为 preg 应该代表 perl reg ex)
    • @Zarazthuztra 我不知道 PHP,但我知道附加的 Demo 有效,并且 OP 确认了它。
    • @Zarazthuztra PHP 没有 g 修饰符。要匹配单个实例,您可以使用preg_match(),要匹配所有实例,您只需使用不同的函数preg_match_all()。替换 preg_replace() 默认情况下替换所有出现。我们可以通过使用第四个参数来限制它。
    • @HamZa 是的,我已经知道了。试图帮助回答。
    【解决方案5】:

    我正在构建一个 JS 最小化器,这个页面帮助我找到了正确的正则表达式。但是到目前为止,此页面尚未回答的是当引用的字符串包含转义引号时该怎么办。当我找到食谱时,我将此页面添加为书签。

    /*
    Regular expression group 'NotBetween'.
    */
    function rgxgNotBetween($chars, $sep="|")
    {
        $chars = explode($sep, $chars);
    
        $NB = [];
    
        foreach($chars as $CHR){
            //(*PRUNE) steps over $CHR when it is escaped; that is, preceded by a backslash.
            $NB[] = "(?:$CHR(?:\\\\$CHR(*PRUNE)|.)*?$CHR)";
        }
    
        $NB = join("|", $NB);
    
        return "(?:(?:$NB)(*SKIP)(*FAIL))";
    }
    
    function jsIdReplace($search, $replace, $source)
    {
        $search = ""
    
        //SKIP further matching when between...
        //double or single qoutes or js regular expression slashes
        .rgxgNotBetween("\x22|\x27|\/")
    
        //match when NO preceding '.' and no ending ':' (object properties)
        ."|(?:(?<!\.)\b$search\b(?!:))"
    
        //but do match when preceding '?' or ':' AND ending ':' (ternary statements)
        ."|(?:(?<=\?|:)\b$search\b(?=:))";
    
        return preg_replace($search, $replace, $source);
    }
    
    function jsNoComments($source)
    {
        //js comment markers NOT between quotes
        $NBQ = rgxgNotBetween("\x22|\x27");
    
        //block comments
        $source = preg_replace("#$NBQ|/\*.*?\*/#s", "", $source);
    
        //line comments; not preceded by backslash
        $source = preg_replace("#$NBQ|\h*(?<!\\\\)//.*\n?#", "", $source);
    
        return $source;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-17
      • 1970-01-01
      • 2022-11-17
      • 1970-01-01
      相关资源
      最近更新 更多