为什么不能使用regex解析HTML/XML:一个通俗的形式化解释

2024-05-29 04:03:50 发布

您现在位置:Python中文网/ 问答频道 /正文

没有哪一天,这样就不会有关于用正则表达式解析(X)HTML或XML的问题。

虽然用examples that demonstrates the non-viability of regexes for this taskcollection of expressions来表示这个概念相对来说比较容易,但我还是找不到这样一个形式的解释,解释为什么用外行的话说这是不可能的。

到目前为止,我在这个网站上找到的唯一正式解释可能非常准确,但对自学成才的程序员来说也相当神秘:

the flaw here is that HTML is a Chomsky Type 2 grammar (context free grammar) and RegEx is a Chomsky Type 3 grammar (regular expression)

或:

Regular expressions can only match regular languages but HTML is a context-free language.

或:

A finite automaton (which is the data structure underlying a regular expression) does not have memory apart from the state it's in, and if you have arbitrarily deep nesting, you need an arbitrarily large automaton, which collides with the notion of a finite automaton.

或:

The Pumping lemma for regular languages is the reason why you can't do that.

[公平地说:以上大部分解释链接到维基百科页面,但这些内容并不比答案本身容易理解]。

所以我的问题是:有人能用外行的术语翻译一下上面给出的关于为什么不能使用regex解析(X)HTML/XML的正式解释吗?

编辑:在阅读了第一个答案后,我想我应该澄清:我正在寻找一个“翻译”,它也简要地解释了它试图翻译的概念:在答案的最后,读者应该大致了解-例如-什么是“常规语言”和“上下文无关语法”。。。


Tags: ofthe答案you概念forthatis
3条回答

专注于这一点:

A finite automaton (which is the data structure underlying a regular expression) does not have memory apart from the state it's in, and if you have arbitrarily deep nesting, you need an arbitrarily large automaton, which collides with the notion of a finite automaton.

正则表达式的定义相当于一个有限自动机(每个模式有一个不同的自动机)可以执行字符串是否与模式匹配的测试。一个有限的自动机没有内存-没有堆栈,没有堆,没有无限的磁带涂鸦。它只有有限数量的内部状态,每个状态都可以从被测字符串中读取一个输入单位,并用它来决定下一个要移动到哪个状态。作为特殊情况,它有两个终止状态:“是,匹配”和“否,不匹配”。

另一方面,HTML具有可以任意深度嵌套的结构。要确定文件是否是有效的HTML,需要检查所有结束标记是否与先前的开始标记匹配。要理解它,您需要知道哪个元素正在关闭。没有任何办法“记住”你看到的打开标签,没有机会。

但是请注意,大多数“regex”库实际上允许的不仅仅是正则表达式的严格定义。如果它们能匹配回引用,那么它们已经超越了常规语言。因此,不应该在HTML上使用regex库的原因要比HTML不规则这一简单事实复杂得多。

因为HTML可以有无限制的<tags><inside><tags and="<things><that><look></like></tags>"></inside></each></other>嵌套,而regex无法真正处理这个问题,因为它无法跟踪它从何而来的历史。

一个简单的结构说明了困难:

<body><div id="foo">Hi there!  <div id="bar">Bye!</div></div></body>

99.9%的基于正则表达式的通用提取例程将无法正确地给出ID为foodiv中的所有内容,因为它们无法区分该div的结束标记和bardiv的结束标记,这是因为它们无法说“好的,我现在降到了两个div中的第二个,所以我看到的下一个div close将我带出一个,后面的那个是第一个的close标记。程序员通常通过为特定情况设计特殊的case regex来做出响应,一旦在foo中引入更多的标记,这些regex就会中断,并且必须以巨大的时间和挫折代价取消命名。这就是为什么人们对整件事都很生气。

HTML不代表常规语言这一事实是一个危险的问题。正则表达式和正则语言听起来有点类似,但它们并非同源,但学术上的“正则语言”与当前引擎的匹配能力之间有着显著的差距。事实上,几乎所有的现代正则表达式引擎都支持非正则特性——一个简单的例子是(.*)\1。它使用回溯引用来匹配重复的字符序列-例如123123,或bonbon。递归/平衡结构的匹配使这些更加有趣。

维基百科用Larry Wall的话很好地说明了这一点:

'Regular expressions' [...] are only marginally related to real regular expressions. Nevertheless, the term has grown with the capabilities of our pattern matching engines, so I'm not going to try to fight linguistic necessity here. I will, however, generally call them "regexes" (or "regexen", when I'm in an Anglo-Saxon mood).

正如你所见,“正则表达式只能匹配正则语言”不过是一种常见的谬论。

那么,为什么不呢?

不将HTML与正则表达式匹配的一个很好的原因是“仅仅因为可以,并不意味着应该匹配”。虽然可能-但有更好的工具可用于此项工作。考虑:

  • 有效的HTML比你想象的更难/更复杂。
  • 有许多类型的“有效”HTML—例如,在HTML中有效的内容在XHTML中无效。
  • 在因特网上找到的许多自由格式的HTML都是无效的。HTML库在处理这些问题上也做得很好,并且在许多常见情况下都进行了测试。
  • 通常,如果不将数据作为一个整体进行分析,就不可能匹配其中的一部分。例如,您可能正在查找所有标题,最后在注释或字符串文本中进行匹配。<h1>.*?</h1>可能是一次大胆的尝试,试图找到主标题,但可能会发现:

    <!-- <h1>not the title!</h1> -->
    

    甚至:

    <script>
    var s = "Certainly <h1>not the title!</h1>";
    </script>
    

最后一点是最重要的:

  • 使用专用的HTML解析器比任何正则表达式都好。通常,XPath允许更好的表达方式来查找所需的数据,而使用HTML解析器比大多数人意识到的要容易得多。

Jeff Atwood的博客Parsing Html The Cthulhu Way中可以找到这个主题的一个很好的摘要,以及在混合正则表达式和HTML时的一个重要评论。

什么时候最好使用正则表达式来解析HTML?

在大多数情况下,最好在库可以提供的DOM结构上使用XPath。尽管如此,我还是强烈建议使用regex而不是解析器库,这与流行观点相反:

考虑到以下几个条件:

  • 当你需要一次更新你的HTML文件,你知道结构是一致的。
  • 当你有一个非常小的HTML片段。
  • 当您不处理HTML文件,而是处理类似的模板引擎时(在这种情况下很难找到解析器)。
  • 当您想更改HTML的某些部分,但并非全部时-据我所知,解析器无法回答此请求:它将解析整个文档,并保存整个文档,更改您不想更改的部分。

相关问题 更多 >

    热门问题