您能提供一些关于为什么使用正则表达式解析XML和HTML很困难的例子吗?

415

我看到人们经常犯的一个错误一遍又一遍地尝试使用正则表达式解析XML或HTML。以下是解析XML和HTML困难的原因之一:

人们希望将文件视为一系列行,但这是有效的:

<tag
attr="5"
/>

人们希望将<或<tag视为标签的开始,但是在现实中存在这样的情况:

<img src="imgtag.gif" alt="<img>" />

人们通常希望将开始标签与结束标签匹配,但XML和HTML允许标签包含自身(传统的正则表达式完全无法处理此类情况):

<span id="outer"><span id="inner">foo</span></span> 

人们经常希望匹配文档内容(例如著名的“在给定页面上查找所有电话号码”的问题),但数据可能会被标记(即使在查看时它看起来很正常):
<span class="phonenum">(<span class="area code">703</span>)
<span class="prefix">348</span>-<span class="linenum">3020</span></span>

评论可能包含格式不良或不完整的标签:

<a href="foo">foo</a>
<!-- FIXME:
    <a href="
-->
<a href="bar">bar</a>

你还知道哪些陷阱?


16
Web浏览器每秒都会解析这种混乱的内容,难道就没有人能为我们这些凡人创建一个网页解析器类吗? - Jon Winstanley
25
Jon,他们已经有了。在Perl中有许多HTML解析器,如HTML::Parser、HTML::TreeBuilder等。你的语言几乎肯定也有类似的解析器。 - Chas. Owens
Jon,你在寻找哪种编程语言?你需要解析格式良好的XML还是从网上获得的HTML标记混乱的文本? - Brian Campbell
作为参考,请查看此链接(http://ejohn.org/blog/pure-javascript-html-parser/)以获取Javascript解析器。在大多数其他编程语言中,解析器相对容易找到。 - Félix Saparelli
13
最佳答案是:https://dev59.com/X3I-5IYBdhLWcg3wq6do#1732454 (小心Zalgo)。 - Kelly S. French
4
以下是如何使用模式解析HTML的良好解释,以及为什么您可能不希望这样做的原因:链接 - tchrist
12个回答

270

这里有一些有趣的有效XML:

<!DOCTYPE x [ <!ENTITY y "a]>b"> ]>
<x>
    <a b="&y;>" />
    <![CDATA[[a>b <a>b <a]]>
    <?x <a> <!-- <b> ?> c --> d
</x>

这个小小的 HTML 组合是有效的:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" [
    <!ENTITY % e "href='hello'">
    <!ENTITY e "<a %e;>">
]>
    <title>x</TITLE>
</head>
    <p id  =  a:b center>
    <span / hello </span>
    &amp<br left>
    <!---- >t<!---> < -->
    &e link </a>
</body>

更不用说对于无效结构的所有特定于浏览器的解析了。

希望你好运,去用正则表达式应对这个问题吧!

编辑(Jörg W Mittag):这里还有另一个漂亮的、格式良好、有效的HTML 4.01示例:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd"> 
<HTML/
  <HEAD/
    <TITLE/>/
    <P/>

7
XML的部分?有几个不同的构造,这很麻烦?DTD内部子集?它定义了一个名为“y”的新实体(&entity;),其中包含一个‘]>’序列,如果不在引号中,通常会结束内部子集。 - bobince
17
这表明,即使您不是DTD验证解析器,要正确解析文档,您仍需要对XML中一些更深奥和古老的DTD特性有相当深入的了解。 - bobince
18
HTML 示例中使用了一个很少人知道的特性:短标签。详细内容请参考 http://www.w3.org/QA/2007/10/shorttags.html。 - netvope
29
每当有人按照上述方式编写HTML时, Tim Berners-Lee就会流下一滴泪。 - fgysin
6
我喜欢Stackoverflow的语法高亮器在第一个“]”出现时出错。 - GlassGhost
显示剩余4条评论

73

实际上

<img src="imgtag.gif" alt="<img>" />

这不是有效的HTML,也不是有效的XML。

它不是有效的XML,因为'<'和'>'在属性字符串中不是有效的字符。它们需要使用相应的XML实体&lt;和&gt;进行转义。

它也不是有效的HTML,因为短闭合形式在HTML中不被允许(但在XML和XHTML中是正确的)。根据HTML 4.01规范,“img”标记也是一个隐式关闭的标记。这意味着手动关闭它实际上是错误的,并且等同于两次关闭任何其他标记。

在HTML中的正确版本是

<img src="imgtag.gif" alt="&lt;img&gt;">

正确的XHTML和XML版本是:

<img src="imgtag.gif" alt="&lt;img&gt;"/>

你提供的下面的例子也是无效的。

<
tag
attr="5"
/>

这不是有效的HTML或XML。标签名称必须紧跟在'<'之后,尽管属性和关闭'>'可以放在任何地方。因此,有效的XML实际上是:

<tag
attr="5"
/>

这里还有一个更酷的方法:你可以选择使用 " 或 ' 作为属性引用字符。

<img src="image.gif" alt='This is single quoted AND valid!'>
所有其他已发布的原因都是正确的,但解析HTML最大的问题在于人们通常不了解所有语法规则。你的浏览器将您的标签混乱视为HTML并不意味着您实际上编写了有效的HTML。
编辑:即使是stackoverflow.com也同意我关于有效和无效的定义,您的无效XML / HTML未被突出显示,而我的已更正版本已被突出显示。
基本上,XML不是用于使用regexps解析的。但是也没有理由这样做。每种语言都有许多许多XML解析器可供选择。您可以在SAX解析器、DOM解析器和Pull解析器之间进行选择。所有这些都保证比使用regexp解析要快得多,并且您可以在生成的DOM树上使用XPath或XSLT等酷技术。
因此,我的回答是:仅仅使用regexps解析XML很难,而且这也是一个坏主意。只需使用数百万现有的XML解析器之一,并利用XML的所有高级功能即可。
HTML太难以自己尝试解析了。首先,法定语法具有许多微小的细节,您可能不知道,其次,野生的HTML只是一堆臭大便。有各种松散的解析器库可以很好地处理像tag soup这样的HTML,只需使用这些即可。

9
不需要像大于号一样转义 >。 - Joey
9
好的,s/valid/exists in the wild/g 的翻译是:“在野外存在”。 - Chas. Owens
1
实际上,根据规范,您必须将 > 转义为 >,就像您必须将 < 转义为 < & 和 &,在属性中 " 转义为 ",' 转义为 ',只是许多解析器。 - LordOfThePigs
哎呀,忘记完成我的评论了。只是许多解析器将能够从其错误状态中恢复,如果<正确编码。再次强调,不崩溃您的解析器并不意味着您的XML有效。 - LordOfThePigs
21
规范中没有说必须转义字符‘>’,除了内容中的特殊情况:‘]]>’。因此,总是转义‘>’最简单,但并不要求符合规范。 - bobince
10
">"符号在HTML中是完全有效的。https://dev59.com/x3VD5IYBdhLWcg3wE3bz - jfs

62
我在这个主题上写了整篇博客:正则表达式限制 问题的核心是HTML和XML是递归结构,需要计数机制才能正确解析。 一个真正的正则表达式是不能计数的。你必须有一个上下文无关语法来计数。
前面的段落有一个小小的警告。某些正则表达式实现现在支持递归的想法。然而,一旦你开始在你的正则表达式中添加递归,你就真的在伸展边界,并且应该考虑使用解析器。

22

你清单中没有提到的一个陷阱是属性可以以任意顺序出现,因此如果你的正则表达式要查找具有href为“foo”和class为“bar”的链接,则它们可以以任何顺序出现,并且它们之间可以有任意数量的其他内容。


啊,是的,那正是促使我提出这个问题(第一个链接)的原因。 - Chas. Owens

17

这取决于您对“解析”一词的理解。一般来说,由于XML语法不是正则的,因此无法使用正则表达式解析XML。简单地说,正则表达式无法计数(尽管Perl正则表达式可能实际上能够计数),因此您无法平衡开放-关闭标记。


我猜反向引用可以解决开放和关闭标签的问题。 - Rishul Matta
1
@RishulMatta: 怎么办?你只有有限数量的反向引用,并且需要反转标记... 此外,正则表达式的严格定义不允许反向引用。 - Willem Van Onsem
.NET允许平衡表达式,这些表达式弹出和推入,理论上可以用于匹配层次结构。但这仍然是一个不好的想法。 - Abel

10

人们使用正则表达式是否犯了错误,还是它对他们想要实现的任务足够好?

我完全同意使用正则表达式来解析 html 和 xml 是不可能的,其他人已经回答过了。

然而,如果你的需求并不是解析 html/xml,而只是从“已知良好”的 html / xml 中获取一个小数据片段,那么也许一个正则表达式甚至更简单的“子字符串”就足够了。


8
定义“好够用”。简单的正则表达式不可避免地无法正常工作。未匹配到某些内容或匹配到不应该匹配的内容算是错误吗?如果是,那么使用正则表达式就是一个错误。HTML和XML解析器并不难使用。避免学习它们是一种虚假的节约。 - Chas. Owens
1
好的,定义“足够好”。假设我有一个网页,告诉我客户端的IP地址。这就是它的全部功能。现在,我需要为客户机编写一个应用程序,以告诉我它的IP地址。我访问该网站,查找IP地址并返回它。解析HTML不是必需的! - Robin Day
2
如果您有一个格式完全受您控制的任意字符串,那么该字符串恰好是格式良好的XML并不相关。但实际上几乎没有XML的使用情况属于这种情况。 - Robert Rossney
17
通过我痛苦的经验,我可以告诉你,大多数情况下,使用荒谬而复杂的正则表达式模式是可以得到你想要的结果的。直到网站经历了一个有趣的小改变,你就可以把这个让你哭了两天的正则表达式丢掉,重新开始。 - Thomasz
@Robert:“几乎没有用例”是夸张的说法。根据我的经验,有一些常见的使用情形。YAGNI也适用于这里...有时候。诀窍在于了解你所处理的特定任务需要多么坚不可摧和长寿。Robin提出了一个好观点。他只是说完全解析XML并不总是值得的...即使你知道如何使用它,这也是事实。 - LarsH
这并不是我的夸张。为了在XML上安全地使用正则表达式,你需要处理大量特殊情况(例如属性的任意排序、属性值定界符的正确使用、需要保留和不需要保留空格的位置等等),或者确定这些情况不适用(例如知道你的XML不使用属性,或者无论如何都使用撇号来定界属性值等等)。如果你知道这些情况适用于你的情况,那么你为什么还要使用XML呢? - Robert Rossney

7
我很想说“不要重复造轮子”。但是XML是一个非常,非常复杂的格式。所以也许我应该说“不要重复发明同步辐射器”。
也许正确的陈词滥调是从“当你只有一把锤子时...”开始。你知道如何使用正则表达式,正则表达式擅长解析,那为什么还要学习XML解析库呢?
因为解析XML是困难的。通过不学习使用XML解析库节省的任何努力都将被您需要进行的创造性工作和错误解决所弥补。为了您自己的利益,请搜索“XML库”,利用别人的工作。

3
不过它没有C++那么复杂。 - Cole Tobin
8
我不会使用正则表达式来解析C++。 - Isaac Rabinovitch
4
如果将XML比作一个同步加速器,那么C++就是大型强子对撞机。 - Kevin Kostlan

6
我认为问题归结为以下几点:
  1. 正则表达式几乎总是不正确的。它会无法正确匹配一些合法的输入。你可以花费很多心思让它达到99%或者99.999%的准确率,但要让它达到100%的准确率几乎是不可能的,因为XML使用实体允许了一些奇怪的东西。

  2. 如果正则表达式不正确,即使只有0.00001%的输入无法匹配,你也会面临安全问题,因为有人可能会发现能够破坏你应用程序的那个输入。

  3. 如果正则表达式足够正确以覆盖99.99%的情况,那么它将变得非常难读和难以维护。

  4. 正则表达式很可能在中等大小的输入文件上表现非常糟糕。我第一次接触XML就是要用一个正确的XML解析器替换一个(不正确的)解析传入XML文档的Perl脚本。我们不仅用100行代码取代了300行难以理解的代码,而且将用户响应时间从10秒提高到了约0.1秒。


6

人们通常会默认采用贪婪模式,这往往会导致未经思考的.*吞噬大量文件并将其装入最大可能的<foo>.*</foo>。


2
除了使用 .*?< 使重复变得懒惰之外,您还可以通过使用否定字符类 [^<]*< 来修复它。(免责声明:显然这仍然不是绝对可靠的,这就是问题的关键。) - Rory O'Kane

5
我相信这篇经典文章中包含了你所需要的信息。你可以在其中一条评论中找到答案:

我认为这里的缺陷在于HTML是Chomsky类型2文法(上下文无关文法),而RegEx是Chomsky类型3文法(正则表达式)。由于类型2文法基本上比类型3文法更复杂,因此你不可能指望让它起作用。但是许多人会尝试,有些人会声称成功,而其他人则会发现错误并彻底搞砸。

更多信息请参考维基百科:乔姆斯基层次结构


7
在正式语法讨论中,“正则表达式”的含义与此处不完全相同。大多数现有的正则表达式引擎比 Chomsky Type 3 语法更加强大(例如,非贪婪匹配、反向引用)。一些正则表达式引擎(例如 Perl 的)是图灵完备的。尽管这些工具并不适合解析 HTML,但这经常被引用的论据并非原因所在。 - dubiousjim

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