提问人:VirtuosiMedia 提问时间:3/31/2009 最后编辑:Andy LesterVirtuosiMedia 更新时间:8/13/2013 访问量:3792
可变顺序正则表达式语法
Variable order regex syntax
问:
有没有办法指示两个或多个正则表达式短语可以按任何顺序出现?例如,XML 属性可以按任何顺序编写。假设我有以下 XML:
<a href="home.php" class="link" title="Home">Home</a>
<a href="home.php" title="Home" class="link">Home</a>
我将如何编写一个检查类和标题并适用于这两种情况的匹配项?我主要在寻找允许我按任何顺序检查的语法,而不仅仅是匹配类和标题,因为我可以做到这一点。除了包含这两种组合并用“|”连接它们之外,还有什么方法吗?
编辑:我更喜欢在单个正则表达式中执行此操作,因为我正在以编程方式构建它并对其进行单元测试。
答:
最简单的方法是编写一个拾取部分的正则表达式,然后再编写两个正则表达式来提取类和标题。虽然你可以用一个正则表达式来做到这一点,但它会非常复杂,而且可能更容易出错。<a .... >
使用单个正则表达式,您将需要类似
<a[^>]*((class="([^"]*)")|(title="([^"]*)"))?((title="([^"]*)")|(class="([^"]*)"))?[^>]*>
这只是第一手的猜测,没有检查它是否有效。分而治之要容易得多。
评论
第一个临时解决方案可能是执行以下操作。
((class|title)="[^"]*?" *)+
这远非完美,因为它允许每个属性多次出现。我可以想象这可以通过断言来解决。但是,如果您只想提取属性,这可能已经足够了。
不,我相信使用单个 RE 做到这一点的最佳方法与您描述的完全一样。不幸的是,当你的 XML 可以有 5 个不同的属性时,它会变得非常混乱,给你大量不同的 RE 来检查。
另一方面,我根本不会用 RE 来做这件事,因为它们不是编程语言。使用 XML 处理库的老式方法有什么问题?
如果你被要求使用 RE,这个答案可能没有多大帮助,但我相信使用正确的工具来完成这项工作。
评论
您可以使用命名组将属性从标记中提取出来。运行正则表达式,然后循环访问执行所需任何测试的组。
像这样的东西(未经测试,使用 .net 正则表达式语法,其中 \w 表示单词字符,\s 表示空格):
<a ((?<key>\w+)\s?=\s?['"](?<value>\w+)['"])+ />
评论
如果要匹配一组元素的排列,则可以使用反向引用和零宽度的组合 负正向匹配。
假设您要匹配以下六行中的任何一条:
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>/
您可以为每个属性创建一个前瞻,并将它们插入到整个标签的正则表达式中。例如,标记的正则表达式可以是
<a\b[^<>]*>
如果你在XML上使用它,你可能需要一些更复杂的东西。就其本身而言,此基本正则表达式将与具有零个或多个属性的标签匹配。然后,为要匹配的每个属性添加一个 lookhead:
(?=[^<>]*\s+class="link")
(?=[^<>]*\s+title="Home")
允许它向前扫描属性,但不会让它超出右尖括号。在前瞻中匹配前导空格有两个目的:它比在基本正则表达式中匹配它更灵活,并且它确保我们匹配整个属性名称。将它们结合起来,我们得到:[^<>]*
<a\b(?=[^<>]*\s+class="link")(?=[^<>]*\s+title="Home")[^<>]+>[^<>]+</a>
当然,为了清楚起见,我做了一些简化的假设。我不允许在等号周围使用空格,不允许在属性值周围使用单引号或不使用引号,也不允许在属性值中使用尖括号(我听说这是合法的,但我从未见过这样做)。堵住这些泄漏(如果需要)会使正则表达式更丑陋,但不需要更改基本结构。
你考虑过xpath吗?(其中属性顺序无关紧要)
//a[@class and @title]
将选择两个节点作为有效匹配项。唯一需要注意的是,输入必须是 xhtml(格式正确的 xml)。<a>
评论