正则表达式匹配未被另一个char包围的值?

时间:2009-07-28 00:48:37

标签: regex

这是我曾经尝试过的最艰难的事情之一。多年来我一直在寻找,但我找不到办法做到这一点 - 匹配一个不被给定字符包围的字符串,如引号或更大/小于符号。

这样的正则表达式可以匹配不在HTML链接中的URL,不在引号中的SQL table.column值以及许多其他内容。

Example with quotes: 
Match [THIS] and "something with [NOT THIS] followed by" or even [THIS].

Example with <,>, & " 
Match [URL] and <a href="[NOT URL]">or [NOT URL]</a>

Example with single quotes: 
WHERE [THIS] LIKE '%[NOT THIS]'

基本上,如果字符串(THIS)没有被给定的char包围,你如何匹配?

\b(?:[^"'])([^"']+)(?:[^"'])\b

这是一个测试模式:像我想的那样的正则表达式只匹配第一个“引用”。

  

引用,“引用我,以免我引用你!”

6 个答案:

答案 0 :(得分:14)

最佳解决方案取决于您对输入的了解。例如,如果您正在寻找未用双引号括起来的内容,这是否意味着双引号将始终正确平衡?它们可以用反斜杠转义,还是用单引号括起来?

假设最简单的情况 - 没有嵌套,没有转义 - 你可以使用这样的前瞻:

preg_match('/THIS(?=(?:(?:[^"]*+"){2})*+[^"]*+\z)/')

在找到目标(THIS)之后,前瞻基本上计算该点之后的双引号,直到字符串结束。如果它们有奇数,则匹配必须在一对双引号内发生,因此它无效(前瞻失败)。

正如您所发现的,这个问题并不适合正则表达式;这就是为什么所有提出的解决方案都依赖于真正的正则表达式中没有的功能,比如捕获组,外观,不情愿和占有量词。如果没有possessive quantifiersatomic groups,我甚至不会尝试

编辑:要扩展此解决方案以考虑可以使用反斜杠转义的双引号,您只需要替换匹配“任何不是双引号”的正则表达式部分:

[^"]

“任何不是引用或反斜杠,或反斜杠后跟任何东西”:

(?:[^"\\]|\\.)

由于反斜杠转义序列相对较少,因此当您处于正则表达式的那一部分时,尽可能匹配尽可能多的非转义字符是值得的:

(?:[^"\\]++|\\.)

将所有这些放在一起,正则表达式变为:

'/THIS\d+(?=(?:(?:(?:[^"\\]++|\\.)*+"){2})*+(?:[^"\\]++|\\.)*+$)/'

应用于您的测试字符串:

'Match THIS1 and "NOT THIS2" but THIS3 and "NOT "THIS4" or NOT THIS5" ' +
'but \"THIS6\" is good and \\\\"NOT THIS7\\\\".'

...它应与'THIS1''THIS3''THIS4''THIS6'匹配。

答案 1 :(得分:3)

这有点难。有方法,只要你不需要跟踪嵌套。例如,让我们避免引用的东西:

^((?:[^"\\]|\\.|"(?:[^"\\]|\\.)*")*?)THIS

或者,解释:

^     Match from the beginning
(     Store everything from the beginning in group 1, if I want to do replace
    (?:  Non-grouping aggregation, just so I can repeat it
        [^"\\]  Anything but quote or escape character
        |       or...
        \\.     Any escaped character (ie, \", for example)
        |       or...
        "       A quote, followed by...
        (?:     ...another non-grouping aggregation, of...
            [^"\\]  Anything but quote or escape character
            |       or...
            \\.     Any escaped character
        )*      ...as many times as possible, followed by...
        "       A (closing) quote
    )*?  As many as necessary, but as few as possible
)     And this is the end of group 1
THIS  Followed by THIS

现在,还有其他方法可以做到这一点,但也许并不灵活。例如,如果你想找到这个,只要没有前面的“//”或“#”序列 - 换句话说,在评论之外的那个,你可以这样做:

(?<!(?:#|//).*)THIS

在这里,(?<!...)是一个负面的后卫。它不会匹配这些字符,但它会测试它们是否在此之前出现。

对于任何任意嵌套结构 - 例如由n (关闭的n ) - 它们不能用正则表达式表示。 Perl可以做到,但它不是正则表达式。

答案 2 :(得分:1)

嗯,正则表达式只是错误的工具,所以它很难自然。

被其他东西“包围”的东西不是常规语法的有效规则。大多数(或许可以说,一切都很严重)标记和编程语言都不常见。只要不涉及嵌套,您就可以使用正则表达式模拟解析器,但一定要了解您正在做什么。

对于HTML / XML,只需使用HTML resp。 XML解析器;几乎所有语言或网络框架都存在;使用它们通常只涉及几行代码。对于表格,您可能可以使用CSV解析器,或者在压缩时滚动您自己的解析器,该解析器提取引号内/外的部分。在提取出您感兴趣的部分后,您可以使用简单的字符串比较或正则表达式来获得结果。

答案 3 :(得分:1)

请参阅Text::Balanced了解Perl和Perl FAQ

答案 4 :(得分:0)

在考虑嵌套元素(“a”和“this”“)和反向项目”\“这个”“之后,这似乎确实不是正则表达式的工作。但是,只有我能想到的解决这个问题的方法就是像char-by-char解析器这样的正则表达式,它会在找到并输入有效的引用或子引号时标记$ quote_level = ###;这样就可以在你知道你是否在任何给定角色里面的字符串,即使它被斜线或其他东西转义。

我猜这样的char-by-char解析器可以标记开头/结尾引号的字符串位置,这样你就可以按引用段分解字符串,只处理引号之外的字符串。

以下是此解析器如何足够智能以处理嵌套级别的示例。

Match THIS and "NOT THIS" but THIS and "NOT "THIS" or NOT THIS" but \"THIS\" is good.

//Parser "greedy" looking for nested levels
Match THIS and "
            NOT THIS"
                but THIS and "
                        NOT "
                            THIS"
                                or NOT THIS"
                                        but \"THIS\" is good

//Parser "ungreedy" trying to close nested levels
Match THIS and "        " but THIS and "    " THIS "            " but \"THIS\" is good.
                NOT THIS                NOT          or NOT THIS


//Parser closing levels correctly.
Match THIS and "        " but THIS and "                    " but \"THIS\" is good.
                NOT THIS                NOT "   " or NOT THIS
                                            THIS

答案 5 :(得分:0)

正如Alan M指出的那样,你可以使用正则表达式查找奇数,从而通知你在任何给定字符串内部或外部的位置。以引用为例,我们似乎非常接近解决这个问题的方法。唯一剩下的就是处理转义报价。 (我很肯定嵌套引号几乎是不可能的。)

$string = 'Match THIS1 and "NOT THIS2" but THIS3 and "NOT "THIS4" or NOT THIS5" but \"THIS6\" is good and \\\\"NOT THIS7\\\\".';


preg_match_all('/[^"]+(?=(?:(?:(?:[^"\\\]++|\\\.)*+"){2})*+(?:[^"\\\]++|\\\.)*+$)/', $string, $matches);

Array (
        [0] => Match THIS1 and 
        [1] =>  but THIS3 and 
        [2] => THIS4
        [3] =>  but 
        [4] => THIS6
        [5] =>  is good and \\
        [6] => NOT THIS7\
        [7] => .
    )