变量顺序正则表达式语法

7

有没有办法表明两个或更多正则表达式短语可以以任何顺序出现?例如,XML属性可以以任何顺序编写。假设我有以下XML:

<a href="home.php" class="link" title="Home">Home</a>
<a href="home.php" title="Home" class="link">Home</a>

我应该如何编写一个匹配项,以检查类和标题并适用于两种情况?我主要寻找允许我以任何顺序检查的语法,而不仅仅是匹配类和标题。除了只包含两个组合并将它们用 '|' 连接之外,还有其他方法吗?

编辑:我的首选是在单个正则表达式中完成,因为我正在通过程序构建它,也对其进行单元测试。


这是正则表达式不适合解析XML或HTML的许多原因之一。 - Chas. Owens
正则表达式并不是一种编程语言,就像@Josh Bush上面说的一样,你必须要有其他的东西。它本身并不是一个神奇的工具,可以在没有任何编程控制的情况下解析所有东西。 - Rick
我非常喜欢@Josh Bush在下面的回答,因为它是目前对我有用的,正是我在遇到这个问题时所需要的。 - Rick
@Rick 当你最终获得了一组正则表达式和控制代码,以至于它可以正确地处理 HTML 或 XML,那么你就拥有了一个解析器。既然我们已经有了这么多好的解析器,为什么要编写一个新的解析器呢? - Chas. Owens
不,你不能这样做。这就是为什么你不应该使用正则表达式来解析HTML(或XML)的原因之一。使用适当的HTML解析模块。你无法可靠地使用正则表达式解析HTML,而且你将在未来面临悲伤和挫折。一旦HTML与你的期望不同,你的代码就会出错。请参阅http://htmlparsing.com/php,了解如何使用已经编写、测试和调试的PHP模块正确解析HTML的示例。 - Andy Lester
7个回答

8
不需要,我认为用一个正则表达式来完成它是最好的方法,就像你描述的那样。不幸的是,当你的XML有5个不同的属性时,它将变得非常杂乱,需要检查许多不同的正则表达式。
另一方面,我根本不会使用正则表达式,因为它们不是编程语言。使用XML处理库的旧方法有什么问题呢?
如果您必须使用正则表达式,那么这个答案可能帮不上忙,但我相信使用正确的工具来完成工作。

2
大多数HTML不是有效的XML。因此,您实际上需要一个HTML解析库。并且根据您尝试提取此信息的原因,可能不值得围绕某个库编写应用程序。也许这只是一次性的事情,您想获取一些粗略的信息。 - Kibbee
不幸的是,我认为我必须权衡能够解析非有效XML的价值和大量排列组合。在某个时候,正则表达式将不再那么简单。这不仅仅是一个一次性的项目,但我认为最终我将不得不使用一个库。 - VirtuosiMedia
1
一些正则表达式可能不是一个坏主意,但最好不要把所有东西都放在一个里面。首先,使用一个正则表达式来获取<括号>内的内容,然后再使用另一个来提取元素等,并相应地处理它们。这样更易读,也更容易编写。 - Chris Lutz
+1 尝试使用正则表达式解析 XML 是愚蠢的行为。适当的 XML 解析器在所有平台上都广泛可用;请使用它们。 - bobince
解析XML仅针对特定属性并不总是“愚人的游戏”,对于某些事情,如果您使用正确的程序(首先进行标记化等),它实际上并不那么复杂。也许这不是效率最高的选择,但如果您只是想获取特定内容,那么它并不像您所说的那样困难,并且可能比查找一个体面的解析器并学习其语法来执行简单操作更快。 - Rick

5
你考虑过使用xpath吗?(其中属性顺序不重要)
//a[@class and @title]

将选择<a>节点作为有效匹配。唯一的注意事项是输入必须是xhtml(格式良好的xml)。


4
您可以为每个属性创建一个前瞻,并将它们插入到整个标签的正则表达式中。例如,标签的正则表达式可以是:
<a\b[^<>]*>

如果您在使用XML时,可能需要更复杂的内容。这个基本的正则表达式只匹配带有零个或多个属性的标签。然后,每添加一个lookhead就可以匹配您想要的属性:

(?=[^<>]*\s+class="link")
(?=[^<>]*\s+title="Home")

[^<>]* 允许它向前扫描属性,但不允许它查看超出关闭尖括号的内容。在这个预查中匹配前导空格有两个目的:它比在基本正则表达式中匹配更灵活,并确保我们匹配整个属性名称。将它们结合起来,我们得到:

<a\b(?=[^<>]*\s+class="link")(?=[^<>]*\s+title="Home")[^<>]+>[^<>]+</a>

当然了,为了更好地表述,我做了一些简化的假设。我没有考虑等号周围的空格,也没有考虑属性值周围是否有单引号或无引号,还没有考虑属性值中包含尖括号(听说是合法的,但我从未见过)。修复这些问题(如果需要)会让正则表达式变得更加丑陋,但不需要对基本结构进行修改。

2
您可以使用命名组从标记中提取属性。 运行正则表达式,然后循环遍历组,进行所需的任何测试。
类似这样(未经测试,使用 .net 正则表达式语法,\w 表示单词字符,\s 表示空格):
<a ((?<key>\w+)\s?=\s?['"](?<value>\w+)['"])+ />

这可能是最明智的解决方案,只使用正则表达式(而不是预先构建的CSS解析器)。 - Rick

1
最简单的方法是编写一个正则表达式来捕获<a .... >部分,然后编写另外两个正则表达式来提取类和标题。虽然你可能可以用一个正则表达式完成它,但这会非常复杂,而且可能会出现更多错误。
使用单个正则表达式,你需要像这样:
<a[^>]*((class="([^"]*)")|(title="([^"]*)"))?((title="([^"]*)")|(class="([^"]*)"))?[^>]*>

这只是一个第一手的猜测,没有检查它是否有效。分而治之问题要容易得多。


枚举所有排列可能对于两个属性是可行的,对于三个属性也许可以,但由于排列数呈指数级增长,所以这种解决方案很快就会变成一个巨大的问题。 - Daniel Brückner

0

一个首先的临时解决方案可能是执行以下操作。

((class|title)="[^"]*?" *)+

这远非完美,因为它允许每个属性出现多次。我可以想象这可能可以通过断言来解决。但如果你只想提取属性,这可能已经足够了。


0

如果您想匹配一组元素的排列,可以使用反向引用和零宽度负向前向匹配的组合。

假设您想匹配这六行中的任意一行:

123-abc-456-def-789-ghi-0AB
123-abc-456-ghi-789-def-0AB
123-def-456-abc-789-ghi-0AB
123-def-456-ghi-789-abc-0AB
123-ghi-456-abc-789-def-0AB
123-ghi-456-def-789-abc-0AB

您可以使用以下正则表达式来实现此操作:
/123-(abc|def|ghi)-456-(?!\1)(abc|def|ghi)-789-(?!\1|\2)(abc|def|ghi)-0AB/

回溯引用(\1\2)允许您引用先前的匹配,而零宽度正向匹配((?!...))允许您否定位置匹配,即如果包含的匹配在此位置,则不匹配。将两者结合起来可以确保您的匹配是给定元素的合法排列,并且每个可能性仅出现一次。
例如,在Ruby中:
input = <<LINES
123-abc-456-abc-789-abc-0AB
123-abc-456-abc-789-def-0AB
123-abc-456-abc-789-ghi-0AB
123-abc-456-def-789-abc-0AB
123-abc-456-def-789-def-0AB
123-abc-456-def-789-ghi-0AB
123-abc-456-ghi-789-abc-0AB
123-abc-456-ghi-789-def-0AB
123-abc-456-ghi-789-ghi-0AB
123-def-456-abc-789-abc-0AB
123-def-456-abc-789-def-0AB
123-def-456-abc-789-ghi-0AB
123-def-456-def-789-abc-0AB
123-def-456-def-789-def-0AB
123-def-456-def-789-ghi-0AB
123-def-456-ghi-789-abc-0AB
123-def-456-ghi-789-def-0AB
123-def-456-ghi-789-ghi-0AB
123-ghi-456-abc-789-abc-0AB
123-ghi-456-abc-789-def-0AB
123-ghi-456-abc-789-ghi-0AB
123-ghi-456-def-789-abc-0AB
123-ghi-456-def-789-def-0AB
123-ghi-456-def-789-ghi-0AB
123-ghi-456-ghi-789-abc-0AB
123-ghi-456-ghi-789-def-0AB
123-ghi-456-ghi-789-ghi-0AB
LINES

# outputs only the permutations
puts input.grep(/123-(abc|def|ghi)-456-(?!\1)(abc|def|ghi)-789-(?!\1|\2)(abc|def|ghi)-0AB/)

对于五个元素的排列,它将是:

/1-(abc|def|ghi|jkl|mno)-
 2-(?!\1)(abc|def|ghi|jkl|mno)-
 3-(?!\1|\2)(abc|def|ghi|jkl|mno)-
 4-(?!\1|\2|\3)(abc|def|ghi|jkl|mno)-
 5-(?!\1|\2|\3|\4)(abc|def|ghi|jkl|mno)-6/x

对于你的例子,正则表达式应该是

/<a href="home.php" (class="link"|title="Home") (?!\1)(class="link"|title="Home")>Home<\/a>/

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