可变顺序正则表达式语法

Variable order regex syntax

提问人:VirtuosiMedia 提问时间:3/31/2009 最后编辑:Andy LesterVirtuosiMedia 更新时间:8/13/2013 访问量:3792

问:

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

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

我将如何编写一个检查类和标题并适用于这两种情况的匹配项?我主要在寻找允许我按任何顺序检查的语法,而不仅仅是匹配类和标题,因为我可以做到这一点。除了包含这两种组合并用“|”连接它们之外,还有什么方法吗?

编辑:我更喜欢在单个正则表达式中执行此操作,因为我正在以编程方式构建它并对其进行单元测试。

正则表达 HTML 解析

评论

0赞 Rick 8/19/2010
我非常喜欢下面布什@Josh回答,因为现在我遇到了这个问题,这对我有用
0赞 Andy Lester 8/13/2013
不,你不能这样做。这是您不使用正则表达式来解析 HTML(或 XML)的原因之一。使用适当的 HTML 解析模块。你无法可靠地使用正则表达式解析 HTML,并且你将面临悲伤和沮丧。一旦 HTML 从您的期望中改变,您的代码就会被破坏。请参阅 htmlparsing.com/php,了解如何使用已经编写、测试和调试的 PHP 模块正确解析 HTML。
0赞 Chas. Owens 3/31/2009
这是正则表达式不适合解析 XML 或 HTML 的众多原因之一。
0赞 Rick 8/19/2010
正则表达式不是一种编程语言,你必须有像布什上面说@Josh的东西。它不应该是一个神奇的工具,可以在没有任何编程来控制它的情况下为您解析事物
0赞 Chas. Owens 8/19/2010
@Rick 当你最终得到一组正则表达式和控制代码,使其能够正确处理HTML或XML时,你将拥有一个解析器。当我们已经有这么多好的解析器时,为什么要编写一个新的解析器?

答:

1赞 Kibbee 3/31/2009 #1

最简单的方法是编写一个拾取部分的正则表达式,然后再编写两个正则表达式来提取类和标题。虽然你可以用一个正则表达式来做到这一点,但它会非常复杂,而且可能更容易出错。<a .... >

使用单个正则表达式,您将需要类似

<a[^>]*((class="([^"]*)")|(title="([^"]*)"))?((title="([^"]*)")|(class="([^"]*)"))?[^>]*>

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

评论

0赞 Daniel Brückner 3/31/2009
枚举所有排列对于 to 可能是可行的,对于三个属性可能是可行的,但是由于排列的数量呈指数级增长,因此这种解决方案很快就会成为一个大问题。
0赞 Daniel Brückner 3/31/2009 #2

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

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

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

8赞 paxdiablo 3/31/2009 #3

不,我相信使用单个 RE 做到这一点的最佳方法与您描述的完全一样。不幸的是,当你的 XML 可以有 5 个不同的属性时,它会变得非常混乱,给你大量不同的 RE 来检查。

另一方面,我根本不会用 RE 来做这件事,因为它们不是编程语言。使用 XML 处理库的老式方法有什么问题?

如果你被要求使用 RE,这个答案可能没有多大帮助,但我相信使用正确的工具来完成这项工作。

评论

2赞 Kibbee 3/31/2009
大多数 HTML 都不是有效的 XML。所以你实际上需要一个 HTML 解析库。根据您尝试提取此信息的原因,它可能不需要围绕某些库编写应用程序。也许这只是一件一次性的事情,你想得到一些粗略的信息。
0赞 VirtuosiMedia 3/31/2009
不幸的是,我认为我必须权衡能够解析无效 XML 的价值与荒谬的排列数量。在某种程度上,正则表达式不会那么微不足道。这不仅仅是一个一次性的项目,但我认为我最终将不得不使用一个库。
1赞 Chris Lutz 3/31/2009
几个正则表达式可能不是一个糟糕的主意,但最好不要在一个中完成所有事情。首先,使用正则表达式将正则表达式<括号>内的内容,然后使用另一个正则表达式提取元素等,并相应地处理它们。它更具可读性,也更容易编写。
0赞 bobince 4/1/2009
+1 尝试使用正则表达式解析 XML 是愚蠢的游戏。适当的 XML 解析器广泛适用于所有平台;使用它们。
0赞 Rick 8/19/2010
仅解析特定属性的 XML 并不总是“傻瓜游戏”,对于某些事情,如果您使用适当的过程(首先标记化等),它真的没有那么复杂,也许它不是效率的最佳选择,但如果您只是想获得一些特定的东西,它不是一项艰巨的任务,因为您已经做到了,并且可能比找到一个像样的解析器并学习其语法只是为了做一些简单的事情更快
2赞 Josh Bush 3/31/2009 #4

您可以使用命名组将属性从标记中提取出来。运行正则表达式,然后循环访问执行所需任何测试的组。

像这样的东西(未经测试,使用 .net 正则表达式语法,其中 \w 表示单词字符,\s 表示空格):

<a ((?<key>\w+)\s?=\s?['"](?<value>\w+)['"])+ />

评论

0赞 Rick 8/19/2010
这可能是最明智的解决方案,因为只使用正则表达式(而不是预先构建的CSS解析器)
0赞 rampion 3/31/2009 #5

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

假设您要匹配以下六行中的任何一条:

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(?!...)

因此,例如,在红宝石中:

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>/
4赞 Alan Moore 3/31/2009 #6

您可以为每个属性创建一个前瞻,并将它们插入到整个标签的正则表达式中。例如,标记的正则表达式可以是

<a\b[^<>]*>

如果你在XML上使用它,你可能需要一些更复杂的东西。就其本身而言,此基本正则表达式将与具有零个或多个属性的标签匹配。然后,为要匹配的每个属性添加一个 lookhead:

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

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

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

当然,为了清楚起见,我做了一些简化的假设。我不允许在等号周围使用空格,不允许在属性值周围使用单引号或不使用引号,也不允许在属性值中使用尖括号(我听说这是合法的,但我从未见过这样做)。堵住这些泄漏(如果需要)会使正则表达式更丑陋,但不需要更改基本结构。

5赞 user74455 3/31/2009 #7

你考虑过xpath吗?(其中属性顺序无关紧要)

//a[@class and @title]

将选择两个节点作为有效匹配项。唯一需要注意的是,输入必须是 xhtml(格式正确的 xml)。<a>