【问题标题】:PHP PCRE match punctuation but not ++PHP PCRE 匹配标点符号但不匹配 ++
【发布时间】:2016-09-13 19:27:02
【问题描述】:

我试图搜索这个问题的答案一段时间,但找不到。有许多与匹配文本相关的帖子,这些帖子前面没有某些文本,但似乎没有一个适用于这种情况下 + 匹配但只有在单个 + 前面才允许(例如 ++)

我正在尝试从文本中删除标点符号,但让两个连续的 ++ 符号保留,但单个 + 符号消失

$text="Hello World! C+ C++ C#";
print_r(preg_replace('/(?!\+\+)[[:punct:]]/', ' ', $text));

导致(我不确定为什么后面的 + 被删除了?有人可以解释一下吗?):

Hello World C C+ C

如果我尝试:

$text="Hello World! C+ C++ C#";
print_r(preg_replace('/(?!\+)[[:punct:]]/', ' ', $text));

结果是:

Hello World C+ C++ C

但我想要的结果是:

Hello World C C++ C

谢谢

更新:我意识到我可能应该提到我将有其他我想避免的角色。我可能把这个问题简单化了。例如,我可能想避免 # 也因此结果是

Hello World C C++ C#

解决方案应该易于扩展。对于此信息缺失给您带来的不便,我们深表歉意。

【问题讨论】:

  • 问题来自像++++ 这样的场景。那该怎么办呢?
  • 然后只在(*SKIP)(*F)-preg_replace('/(?:[#^]|\*{3}|\+{2})(*SKIP)(*F)|[[:punct:]]+/', ' ', $text);之前枚举非捕获组内的特殊情况
  • preg_replace('\b(\+(?!\+)|[^\P{P}#])', ' ', $text) 你必须在字符类中添加特殊字符。例如。排除![^\P{P}#!]

标签: php regex pcre negative-lookbehind


【解决方案1】:

这里有几个选择,一个是:

(?<!\+)[+#](?!\+)
# with lookarounds making sure no + is after/behind

a demo on regex101.com


PHP:
<?php

$regex = '~(?<!\+)[+#](?!\+)~';

$string = 'Hello World! C+ C++ C#';
$string = preg_replace($regex, '', $string);

echo $string;
?>


另一种方法是使用(*SKIP)(*FAIL) 机制(在本例中要快一些):
\+{2}(*SKIP)(*FAIL)|[+#]
# let two consecutive ++ always fail

regex101.com as well 上查看这个演示。

最后但并非最不重要的: 如果你想添加也应该避免的字符/表达式,你可以将它们放在一个非捕获组中,让这个失败:

(?:\#|\+{2})(*SKIP)(*FAIL)|
[[:punct:]]

又一个demo on the wonderful regex101.com site

【讨论】:

  • 这显然与其余的标点字符不匹配。我可以用 [[:punct:]] 更改 [+#] 并解决该问题。但是我现在看到我应该在问题中告诉我还需要添加更多的标点符号来避免。我怎么做?例如,如果我想避免 # 也?
  • 我真的很喜欢 (*SKIP)(*FAIL)。它解决了我所有的问题。但我有一个问题。 perl 手册说匹配前导直到 (*SKIP) 不能成为任何模式匹配的一部分。它说正则表达式引擎向前跳到这个位置并重试。所以我不明白 (*FAIL) 到底做了什么?因为 perl 手册说它会导致回溯。据我了解,这意味着在匹配的第一个字符之后转到下一个字符。但是,如果 (*SKIP) 已经跳过了 ++ 字符,为什么还需要 (*FAIL)?
  • Evren,主要的一点是,当这些动词一起使用时,会导致当前匹配从匹配对象中完全丢弃,并且 - 非常重要的是 - 在丢弃的匹配之后继续分析字符串。回溯意味着尝试尝试另一个模式变体,因为该模式具有未知的宽度量词或交替。
  • Wiktor,我可以看到它的作用 :) 我的意思是我真的不明白它应该从 perl 手册中执行此操作。这让我觉得它在原始比赛之后又开始了 1 个字符。但是没关系。仅供参考,我的意思是这里的手册 --> perldoc.perl.org/perlre.html#Backtracking --> “它意识到自己的错误,并在它有暂定匹配的地方重新开始一个字符”和 for (*FAIL) “这个模式不匹配并且总是失败。可以用来强制引擎回溯。相当于(?!),但更易读"
【解决方案2】:

您的第一个正则表达式 (?!\+\+)[[:punct:]] 不起作用,因为它会在每个位置查找两个连续的 + 否定符号,然后断言下一个直接字符是标点符号。当它看到C++ ,光标在第一个+ 符号旁边时,匹配成功,因为在第二个+ 之后没有+。所以首先匹配+

Hello World! C+ C+|+ C#
                  ^ Cursor here - (?!\+\+)[[:punct:]] is matched

正则表达式:

[[:punct:]]++((?<=\+)(?<=[^+]\+))

除了条件肯定的后向断言之外的所有格匹配将完成这项工作。

Live demo

解释:

[[:punct:]]++   // Match punctuation marks possessively - won't allow backtrack
((?<=\+)        // Start of a conditional statement, check if last match is a `+`
    (?<=[^+]\+) // If yes, it should not be preceded by another `+`
)               // End of conditional

PHP:

preg_replace('@[[:punct:]]++((?<=\+)(?<=[^+]\+))@', ' ', $text)

更新

如果+ singes 前面总是有一些字母,则有一个更短的解决方案:

\b\+(?!\+)

【讨论】:

    【解决方案3】:

    第一个代码 sn-p 的工作方式如下:找到一个标点符号,如果它不是++ 序列的起点,则匹配并删除它。因此,C++ 中的第二个 + 匹配,并被删除。

    您可以匹配并使用(*SKIP)(*FAIL)动词从匹配中丢弃您想要保留的内容并匹配您想要删除的内容:

    preg_replace('/\+{2}(*SKIP)(*F)|[[:punct:]]+/', ' ', $text);
    

    添加更多字符 - 以防万一:

    preg_replace('/(?:[#^]|\*{3}|\+{2})(*SKIP)(*F)|[[:punct:]]+/', ' ', $text);
                   ^^^                ^
    

    PHP demo

    详情

    • \+{2}(*SKIP)(*FAIL) - 匹配 2 个 + 符号,然后从匹配中丢弃它们
    • | - 或
    • [[:punct:]]+ - 匹配一个或多个标点符号。

    在替换模式中,我们只是用空格替换。

    【讨论】:

    • 我认为它是最干净最容易扩展的方法。
    • 这个很好,但是有问题。我想用空格字符替换找到的字符。这只是删除它们。 A+B 变成 AB
    • 查看更新,可以使用(*SKIP)(*FAIL) 技术来帮助。查看更新的演示 - ideone.com/78HCwZ
    • 现在你复制了一份。
    • @revo \+{2}(*SKIP)(*FAIL)|[+#] != \+{2}(*SKIP)(*F)|[[:punct:]]+
    【解决方案4】:

    我认为这里有三种情况可以匹配加号。
    必须匹配双加号才能越过它。

    注意 - 这遵循关于加号的从左到右的规则。除了这些没有任何规则。

    查找:

    [^\P{P}+]|(\+\+)\+|\+

    替换:'$1 '

    解释

        [^\P{P}+]           # Punctuation but not plus
     |  
        ( \+\+ )            # (1), Plus with leading ++
        \+
     |  
        \+                  # Any old plus sign
    

    可以简化为

       [^\P{P}+]           # Punctuation but not plus
    |  
       ( \+\+ )?           # (1), Plus with optional leading ++
       \+
    

    【讨论】:

    • 你写了\+ 在一个交替的一边。因此它将匹配所有+ 标志,无论它们的存在方式如何。你的第一个正则表达式很好。
    • @revo - 我知道它匹配任何 +。但如果可用,它将被动匹配+++。不幸的是,规则必须从左到右解决。一个独立的++ 似乎就是这样。然后将其与++++++++++++++++ 进行比较,则必须考虑一些规则。
    猜你喜欢
    • 2014-05-03
    • 1970-01-01
    • 2022-01-01
    • 1970-01-01
    • 2016-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-14
    相关资源
    最近更新 更多