防止后视/前方匹配重叠

And*_*mes 4 regex

我试图匹配引号之间包含的字符串文字的所有部分.

(?<=[\"]).*?(?=(?<=[^\\])[\"]{1})

上面是一个正常工作的正则表达式,除了一个例外,它当然会匹配字符串文字的所有部分,其中左侧和右侧都有引号,无论引用对.

例如(星号表示匹配的字符):

Hello "my" name is "Andy", nice to meet you.`
       ** ********* ****
Run Code Online (Sandbox Code Playgroud)

这里的字符串文字部分" name is "只是因为它的两边都有一个引号字符而匹配.这对我们正在寻找的东西是不正确的.理想的结果是:

Hello "my" name is "Andy", nice to meet you.`
       **           ****
Run Code Online (Sandbox Code Playgroud)

完全理解这是可能的,也许应该通过写一个状态引擎来完成 - 我的问题是 - 用正则表达式术语 - 如果可能的话,我如何防止后面匹配一个先前与外观匹配的字符串文字部分 -先?

ndn*_*kov 5

序幕

我使用Ruby是因为你说你没有偏好,这比个人实际的产品更具个人兴趣.但请注意,虽然这里使用的一些技巧可能不适用于各种正则表达式引擎(如javascript一样)或者对于相同的东西有不同的语法,但这里没有使用Ruby特定的东西.相同的正则表达式将在Perl,Sublime Text和更多地方工作.


但在我们开始之前......

免责声明:这不是办法!不要在生产代码库中使用它!


现在我们已经解决了这个问题......这是一个非常有趣的问题.与任何其他复杂问题一样,分而治之是道路.

我们将要使用的技巧:

  1. 命名组

就像您可以使用创建编号组一样(group_contents),您可以使用定义命名组(?<group_name>group_contents).我们在技术上并不需要这样做,但它会使一切变得更加全面.

  1. 重新执行组模式

您可以使用执行之前定义的相同模式\g<group_name_or_number>.例如:

(?<three_letter_word>\b\w{3}\b) \g<three_letter_word>
Run Code Online (Sandbox Code Playgroud)

会匹配xyz abc.

  1. 重复零次

乍一看,{0}似乎没用.但是,结合上面的两个,它可以像定义函数而不执行它们那样工作.例如:

(?<even>[02468]){0}7\g<even>8\<even>9\<even>0
Run Code Online (Sandbox Code Playgroud)

将匹配7x8y9z0,其中x,yz甚至数字.

  1. 删除匹配的字符

许多正则表达式引擎中的一个常见限制是您无法定义具有可变长度的lookbehinds.即使在你可以使用的那些(比如在java中),你仍然需要定义最大长度.因此,你不能做的事情(?<=x*).

\K来救援.什么\K基本上转化为是降到目前为止这是匹配的一切.换句话说,(?<=x*)y可以改写为x*\Ky.

有了这些技巧,让我们开始吧.


首先,让我们定义一些"函数"(使用技巧#3).

  1. escaped_quote

一个转义报价是"由一个前面奇数编号的反斜杠(\).一个反斜杠具有转义字符,因此,匹配单个的特殊含义反斜杠,我们需要与另一个(又名逃吧\\= 一个反斜杠).

为了匹配偶数反斜杠,我们可以做\\{2}*(也就是两个 反斜杠零次或多次 - 2*n).为了使它变得奇怪,我们只添加一个反斜杠 - \\\\{2}*(2*n + 1).

我们还想说我们想要匹配此序列中的所有反斜杠.这是因为正则表达式引擎会尝试找到偶数反斜杠来阻止我们,除非我们另外说明.\\\"将被解释为非转义引用,因为它可以匹配\\",忽略第一个斜杠.为了不允许这样,我们将添加负面的lookbehind,如下所示:(?<!\\)\\\\{2}*

我们的escaped_quote "函数"的最终定义如下所示:

(?<escaped_quote>(?<!\\)\\\\{2}*"){0}
Run Code Online (Sandbox Code Playgroud)
  1. non_quoting

我们要表达的另一件事是没有有意义的引语.这是一系列字符,它们是转义引号或根本不是引号.

请注意,对于所有部分非引号,我们需要为escaped_quote添加负前瞻.这是为了确保我们不会吃第\一个escapeped_quote,这将留下我们剩余的未转义的报价.

(?<non_quoting>(?:\g<escaped_quote>|(?!\g<escaped_quote>)[^"])*){0}
Run Code Online (Sandbox Code Playgroud)
  1. balanced_quotes

我们将需要的最后一个"函数"是一个序列,它没有不匹配的引号.这可以是没有任何有意义的引用或具有偶数有意义的引号的东西:

(?<balanced_quotes>\g<non_quoting>|(?:\g<non_quoting>"\g<non_quoting>){2}+){0}
Run Code Online (Sandbox Code Playgroud)


完成所有准备工作后,我们随时准备匹配.

我们将从字符串的开头或单引号开始.前者很明显.后者是因为我们的比赛会留下一个引用.(?:^|")

编辑:事实证明这些还不够.因为当我们最后一次匹配的情况下空字符串,\K不会让我们留在相同的位置和匹配空字符串即兴回顾后发一次.要解决这个问题,我们将添加另一个替代方法 - 空字符串.请注意,此处的顺序很重要,因此如果其他两个失败,我们只使用此替代方法:(?:^|"|)

接下来是一个non_quoting序列,所有内容都被删除(使用#4技巧)来实现一个lookbehind:

(?:^|"|)\g<non_quoting>"\K
Run Code Online (Sandbox Code Playgroud)

之后,我们实际匹配的是一个非引用序列:

(?:^|"|)\g<non_quoting>"\K\g<non_quoting>
Run Code Online (Sandbox Code Playgroud)

最后,我们必须确保在关闭当前引用之后,我们将使用balanced_quotes直到字符串的结尾:

(?:^|"|)\g<non_quoting>"\K\g<non_quoting>(?="\g<balanced_quotes>$)
Run Code Online (Sandbox Code Playgroud)


最后!

我们可以将我们的"函数"定义和实际匹配加在一起,以实现最终的正则表达式:

(?<escaped_quote>(?<!\\)\\\\{2}*"){0}(?<non_quoting>(?:\g<escaped_quote>|(?!\g<escaped_quote>)[^"])*){0}(?<balanced_quotes>\g<non_quoting>|(?:\g<non_quoting>"\g<non_quoting>){2}+){0}(?:^|"|)\g<non_quoting>"\K\g<non_quoting>(?="\g<balanced_quotes>$)
Run Code Online (Sandbox Code Playgroud)

看到它在行动


最后的想法

这里需要注意的是,即使您的正则表达式引擎中不支持某些功能,您也可以通过放置函数调用来实现相同的正则表达式.唯一没有在任何地方看到的东西,你将需要的是\K.

我希望每个阅读此内容的人都能获得有趣的学习体验.