首先让我们明确一点:
正则表达式与HTML解析不兼容并非是一个断言。跟我重复一遍:“不是一个断言”。
这是一个经过科学证实和广为人知的事实。此外,世界也不是在7天内创造出来的,大脚怪也不存在。讨论结束。
我們將此問題限定在以下範圍內:除了正則表達式,我們沒有其他選擇來解析HTML。為什麼?這並不重要。有趣的是,你寫了"這不重要"。事實上,"
為什麼"才是決定你計劃做什麼的部分可能或完全不可能的關鍵。如果有
一件事很重要,那就是"
為什麼"。如果"
為什麼"是"
驗證",那麼答案就是:根據定義,不可能。驗證需要100%的語言覆蓋率。而正則表達式作為上下文無關語法的子集,因此不能覆蓋100%。然而,如果"
為什麼"是"
提取",那麼使用正則表達式可以得到相當好的結果。永遠不會100%可靠,但對於大多數情況而言足夠好了。事實上,現代正則表達式可以做出一些非常強大的事情,即使寫法相當冗長。
这种正则表达式的长度、冗余和复杂性表明,虽然用正则表达式描述有效的电子邮件地址并非不可能,但至少是相对困难的,并且实际上更像是一个暴力字典列表,而不是一个干净的语法。顺便说一下:日期字符串验证更糟糕。首先是闰年。
为了说明我对“验证”和“提取”的区别:
要验证一个简单的电子邮件地址,需要一个巨大的6400多个字符的正则表达式。
然而,从电子邮件地址中“提取”域名只需要简单的
@([^\s]+)
或
(?<=@)[^\s]+
就可以覆盖几乎(如果不是完全)100%。假设字符串被隔离并已知为有效的电子邮件地址。
“生成解析HTML的完美正则表达式是否可能?”你基本上自己回答了这个问题:“不可能”。
那么,这个证明是否具有建设性?我们只知道我们可以吗,还是已经完成了?
这并不是因为“没有人能做到吗?”而是因为“已经被数学证明是不可能的!”。QED
如果不可能,那最准确的是什么?
鉴于根据定义它是不可能的,唯一正确的答案是“没有一个”。
用于解析所有(或尽可能多的)HTML的正则表达式的最佳近似是类似于x | y | z | …的无限长的正则表达式模式,其中x、y、z…是HTML语法的所有(蛮力强制)可能构成,在逻辑上连接在一起,形成了无限长的逻辑OR。它将是一个适当的正则表达式(甚至符合正则表达式的最真实条件),覆盖HTML的所有部分(毕竟它列出并匹配所有可能的字符串),只是理论上可能(或者至少是可行的,就像图灵机一样),但实际上完全无用。
Regex可以描述第三类乔姆斯基语言(正则语言),而HTML(以及大多数其他编程/标记语言)是第二类乔姆斯基语言(上下文无关)。正则语言是上下文无关语言的一个子集。类型n的语法总是可以覆盖类型(n-x)的语言的子集,但永远不可能覆盖全部。因此,正则表达式只能描述HTML的一个子集。大脚怪是一种说法。这是一个事实。
严格的左或右扩展正则表达式没有平衡或嵌套的理解(既不是"S→aSa"也不是混合线性"S→aA,A→Sb,S→ε")。因此,你无法解析HTML。
"S→aSa"(平衡嵌套)的一个快速示例:
<div>
<div>
...
<div>
<div>
没错,HTML/XML 的核心与正则表达式不兼容。从一开始就是个相当糟糕的位置,不是吗?使用正则表达式解析 HTML 实际上是在其核心部分腐败。设计有缺陷,注定会失败。
对于 "S→aA,A→Sb,S→ε"(计数):
无法验证每行正确匹配的 <td>
数量:
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
</tr>
</table>
请注意:一旦您减少对语言“X”的识别范围,您就不再是在识别语言“X”,而是一个新的自包含子集语言“Y”。
在语言领域中,要么全有,要么全无。没有中间状态。
现在有人说PCRE可以做到这一点!是的,那就是无上下文语法。
但是,它不再是正则表达式,因此无法通过测试:
“我们别无选择,只能使用正则表达式。”
而且,仍然是错误的工具。针对这些任务有专门的解析器。使用它们。
邮件匹配的正则表达式(由OP链接)难以阅读,更难以维护。
(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,
?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]
|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)
?[ \t])*(?:[^()<>@,
以下是同一规范的一部分,以适当的无上下文语法形式呈现:
address = mailbox ; one addressee
/ group ; named list
group = phrase ":" [#mailbox] ";"
mailbox = addr-spec ; simple address
/ phrase route-addr ; name & addr-spec
route-addr = "<" [route] addr-spec ">"
route = 1#("@" domain) ":" ; path-relative
addr-spec = local-part "@" domain ; global address
local-part = word *("." word) ; uninterpreted
; case-preserved
domain = sub-domain *("." sub-domain)
sub-domain = domain-ref / domain-literal
domain-ref = atom ; symbolic reference
有些人在面对HTML时,会想到“我知道了,我会使用正则表达式。”现在他们有了两个指标级的问题。
那么告诉我,为什么有人会真的远到决定使用甚至想要做得更像呢?
<div title="blah >" id="tricky">
。 - Nick