在正则表达式中否定一个反向引用

56

如果一个字符串的格式符合预测的格式:

value = "hello and good morning"

我想匹配引号之间的字符串,其中引号可能是双引号 " ,也可能是单引号 ' ,闭合引号(' 或 ")与开头的引号相同。

\bvalue\s*=\s*(["'])([^\1]*)\1

第一个“捕获组”(在第一对括号内) - 应该匹配开头的引号,可以是单引号或双引号,然后 - 我应该允许任何不是第一个组中捕获的字符,并且接着期望被捕获的字符(包含引号)。

需要捕获的字符串应该在第二个捕获组中。
虽然这并不起作用。

这个可以:

\bvalue\s*=\s*(['"])([^"']*)["']

但我希望确保开头和结尾的引号(无论是双引号还是单引号)相同。


编辑
目标基本上是获取具有特定类名的锚点的开放标签,并且我想覆盖包括(')或(")的类属性的罕见情况。

在这里遵循所有建议,我使用了模式:

<\s*\ba\b[^<>]+\bclass\s*=\s*("|'|\\"|\\')(?:(?!\1).)*\s*classname\s*(?:(?!\1).)*\1[^>]*>
< p >意义:
找到标签的开头符号。
允许任何空格。
找到单词a。
允许任何非闭合标签。
找到"class (任何空格) =(任何空格)"
获取引号,可以是以下之一:(" 或 ' 或 \" 或 ')。
根据Alan Moore的回答:允许任何不是开头引号的字符。
找到类名。
允许任何不是引号的字符。
找到与开头引号相同的闭合引号。
允许任何未闭合的标签字符。
找到闭合标签字符。


1
引号可以转义吗?即 value = 'O\'Neill' - Bart Kiers
6个回答

81

你需要使用负向前瞻(negative lookahead),而不是否定字符类(negated character class):

\bvalue\s*=\s*(["'])(?:(?!\1).)*\1

(?:(?!\1).)* 每次只匹配一个字符,且此字符必须满足先行断言不匹配捕获组 (["'']) 中已经匹配的字符。字符类,无论是否取反,每次只能匹配一个字符。对于正则表达式引擎而言,\1 可以代表任意数量的字符,没有办法让它仅在本例中匹配单引号或双引号。因此只能采用更通用但不太易读的解决方案。


1
所以,这部分:(?!\1)的意思是:匹配后面的任何内容,但一定要确保不是 \1 中的内容吗?这正是我需要的,谢谢。 - Yuval A.
我看到它只有在非捕获组内才能正常工作,就像你所做的那样。我只能半懂为什么它必须在非捕获组内... - Yuval A.
3
负向预查 (?!\1) 实际上并不匹配任何字符,它只是断言在当前位置不可能匹配 \1。实际执行匹配(即占用下一个字符)的是点号 . - Alan Moore
4
关于非捕获组,那只是个规定;我使用它是因为在那里我并不需要使用捕获组。我发布的正则表达式应该适用于任何一种方式,虽然((?!\1).)*会过于低效。更重要的是,组是根据它们在正则表达式中的位置进行编号的,因此尽可能使用非捕获组可以更轻松地跟踪捕获组的编号。 - Alan Moore
你需要使用负向先行断言(negative lookahead)来完成。 - Code Jockey

3

不知道您需要这些信息的用途(或者甚至不知道您正在使用此正则表达式的语言或工具),我可以提出许多建议。

使用这些字符串:

value = "hello and good morning"
value = 'hola y buenos dias'
value = 'how can I say "goodbye" so soon?'
value = 'why didn\'t you say "hello" to me this morning?'
value = "Goodbye! Please don't forget to write!"
value = 'Goodbye! Please don\'t forget to write!'

这个表达式:
"((\\"|[^"])*)"|'((\\'|[^'])*)'

将匹配以下字符串:

"hello and good morning"
'hola y buenos dias'
'how can I say "goodbye" so soon?'
'why didn\'t you say "hello" to me this morning?'
"Goodbye! Please don't forget to write!"
'Goodbye! Please don\'t forget to write!'

它可以允许使用“其他”类型的引号或与单个前置\转义的相同类型的引号。引用字符串的内容在组1或3中。您可以通过获取第一个(或最后一个)字符来确定使用哪种引号。如果需要特定的匹配组中的某些内容,请提供更具体的示例(并包括不应该起作用但看起来可能接近的内容)。如果您想选择这条路并需要更多帮助,请询问。

2
我不能代表那个点踩者说话,但是:'this shouldn\'t match - Coleoid

3

您可以使用:

\bvalue\s*=\s*(['"])(.*?)\1

See it


@YuvalA:你是正确的。我们不能在字符类中使用反向引用。 - codaddict
我建议您删除这个答案——这是一种不幸的情况,看起来它应该能够工作并且是最直接的解决方案,但实际上并不能正常工作。 - Trey
@Trey 这是什么意思? - xehpuk
@xehpuk “what”是什么意思?它不起作用。但因为它适用于一个子集,并且看起来应该工作,所以这是一个有点危险的答案,如果留在这里,可能会误导一些人。 如果你怀疑:http://regviz.org/?state=%7B%22regex%22%3A%22%5C%5Cbvalue%5C%5Cs*%3D%5C%5Cs*(%5B%27%5C%22%5D)(.*%3F)%5C%5C1%22%2C%22modifier%22%3A%22gi%22%2C%22text%22%3A%22value%C2%A0%3D%C2%A0%5C%22good%C2%A0morning%5C%22%5Cn%22%2C%22tests%22%3A%5B%5D%7D - Trey
@Trey 当你没有一个失败的案例时,你不能说“实际上在实践中不起作用”。答案包含了一个可行的测试。你可能想向RegViz创建者提交问题(我不理解它的输出,但它确实找到了1个匹配项)。它在Rubular、regex101、RegExr、JavaScript上都可以工作... - xehpuk

0

当我们为CMS Effcore编写Markdown解析器时,我们尝试了不同的变体,以确保速度尽可能快。这些变体如下:

替换示例:

"markdown *text*"

至:

"markdown <em>text</em>"

用于字符“*”和“_”(贪婪模式)的PHP代码#1:

preg_replace('%'.'([*_])'.'(?<phrase>.+?)'.'\\1'.'%sS', '<em>$2<em>', $text);

关于字符“*”和“_”(反向引用的否定)的PHP代码#2:

preg_replace('%'.'([*_])'.'(?<phrase>(?:(?!\\1).){1,})'.'\\1'.'%sS', '<em>$2<em>', $text);

PHP代码#3,用于单个字符“*”(字符类中的否定):

preg_replace('%'.'([*])'.'(?<phrase>[^*]{1,})'.'[*]'.'%sS', '<em>$2<em>', $text);

情况#1(“贪婪模式”)比情况#2(“反向引用中的否定”)更快。

在1000000次迭代中进行测试:

  1. 0.0245740413665秒。
  2. 3.3793921470642秒。

0
我在寻求有关模式匹配的帮助时遇到了这篇文章: < p > < code > value="long text with \"quoted values\" and more"

Alan Moore 的当前最佳答案在这里非常好,但没有考虑引号的转义。因此,所有的信用都归于 Alan,当允许使用 < code > \ 转义引号时,您可以使用此模式:
\bvalue\s*=\s*(["'])(?:(?!(?<!\\)\1).)*\1

额外信息

也许你在这里寻找的模式与我的目的相同,因此我也会分享我的最终解决方案。我必须匹配几个键值对,格式与通常在节点中列出的html属性相同,如:one="first" two="second"

以下正则表达式将匹配此内容,并将捕获组命名为keyvalue

\b(?P<key>[^=\s]*)\s*=\s*(["'])(?P<value>(?:(?!(?<!\\)\2).)*)\2

0

回答这个问题如何在忽略的集合中使用数字引用?

这里是因为它被标记为与此问题完全重复。

不能在类内指定捕获组。
可以做的是在负断言中指定字符,像这样

(["'])((?:(?!\1)[\S\s])*)(\1)

扩展

 ( ["'] )                      # (1)
 (                             # (2 start)
      (?:
           (?! \1 )
           [\S\s] 
      )*
 )                             # (2 end)
 ( \1 )                        # (3)

请注意,在原始帖子中,[^char] 通常也匹配换行符,但由于这是 JavaScript(旧版 JS),因此不能使用点号。
请改用 [\S\s],它可以匹配任何字符。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接