为了论证,我们假设一个HTML解析器。
我已经读过它首先标记化所有内容,然后解析它。
tokenize是什么意思?
解析器是否每个都读取每个字符,构建一个多维数组来存储结构?
例如,它是否读取<
然后开始捕获元素,然后一旦它遇到关闭>
(在属性之外),它就被推送到某个数组堆栈?
我为了知道而感兴趣(我很好奇)。
如果我要阅读HTML Purifier这样的内容,那会不会让我对HTML的解析方式有所了解?
答案 0 :(得分:56)
标记可以由几个步骤组成,例如,如果你有这个HTML代码:
<html>
<head>
<title>My HTML Page</title>
</head>
<body>
<p style="special">
This paragraph has special style
</p>
<p>
This paragraph is not special
</p>
</body>
</html>
标记生成器可以将该字符串转换为重要标记的平面列表,丢弃空格(谢谢,SasQ进行更正):
["<", "html", ">",
"<", "head", ">",
"<", "title", ">", "My HTML Page", "</", "title", ">",
"</", "head", ">",
"<", "body", ">",
"<", "p", "style", "=", "\"", "special", "\"", ">",
"This paragraph has special style",
"</", "p", ">",
"<", "p", ">",
"This paragraph is not special",
"</", "p", ">",
"</", "body", ">",
"</", "html", ">"
]
可能有多个令牌化过程将令牌列表转换为更高级别令牌的列表,如下面的假设HTML解析器可能做的那样(仍然是一个平面列表):
[("<html>", {}),
("<head>", {}),
("<title>", {}), "My HTML Page", "</title>",
"</head>",
("<body>", {}),
("<p>", {"style": "special"}),
"This paragraph has special style",
"</p>",
("<p>", {}),
"This paragraph is not special",
"</p>",
"</body>",
"</html>"
]
然后解析器转换该令牌列表以形成一个树或图形,以便于程序访问/操作的方式表示源文本:
("<html>", {}, [
("<head>", {}, [
("<title>", {}, ["My HTML Page"]),
]),
("<body>", {}, [
("<p>", {"style": "special"}, ["This paragraph has special style"]),
("<p>", {}, ["This paragraph is not special"]),
]),
])
此时,解析完成;然后由用户来解释树,修改它等等。
答案 1 :(得分:30)
首先,您应该意识到解析HTML特别难看 - HTML在标准化之前使用广泛(和不同)。这会导致各种丑陋,例如标准指定不允许某些构造,但随后为这些构造指定所需的行为。
直接提出问题:标记化大致相当于采用英语,并将其分解为单词。在英语中,大多数单词是连续的字母流,可能包括撇号,连字符等。大多数单词被空格包围,但句点,问号,感叹号等也可以表示单词的结尾。同样,对于HTML(或其他),您可以指定一些关于可以构成此语言中的标记(单词)的规则。将输入分解为标记的代码段通常称为词法分析器。
至少在正常情况下,在开始解析之前,不将所有输入分解为令牌。相反,解析器调用词法分析器以在需要时获取下一个标记。当它被调用时,词法分析器会查看足够多的输入以找到一个令牌,将其传递给解析器,并且在下一次解析器需要更多输入之前,不会对所有输入进行标记化。
一般来说,你对解析器的工作原理是正确的,但是(至少在一个典型的解析器中)它在解析语句的过程中使用了一个堆栈,但它构建的表示语句通常是一个树(和抽象语法树,又名AST),不是多维数组。
基于解析HTML的复杂性,我会保留查找解析器,直到您先读完其他几个。如果你做一些环顾四周,你应该能够找到相当数量的解析器/词法分析器,比如数学表达式可能更适合作为介绍(更小,更简单,更容易理解等)。
答案 2 :(得分:9)
不要错过W3C关于parsing HTML5的笔记。
有关扫描/ lexing的有趣介绍,请在网页上搜索高效生成表驱动扫描仪。它显示了扫描最终是如何由自动机理论驱动的。正则表达式的集合被转换为单个NFA。然后将NFA转换为DFA以使状态转换具有确定性。然后,本文描述了将DFA转换为转换表的方法。
关键点:扫描仪使用正则表达式理论,但可能不使用现有的正则表达式库。为了获得更好的性能,状态转换被编码为巨型语句或转换表。
扫描仪保证使用正确的单词(令牌)。解析器保证单词以正确的组合和顺序使用。扫描仪使用正则表达式和自动机理论。解析器使用语法理论,尤其是context-free grammars。
一对解析资源:
答案 3 :(得分:7)
HTML和XML语法(以及其他基于SGML的语法)很难解析,并且它们不适合lexing场景,因为它们不是常规。在解析理论中,常规语法是没有任何递归的那个,即自相似,嵌套模式或类似于括号的包装器,它们必须相互匹配。但是基于HTML / XML / SGML的语言确实具有嵌套模式:标签可以嵌套。嵌套模式的语法在乔姆斯基的分类中更高级别:它是无上下文的,甚至是依赖于上下文的。
但回到你关于lexer的问题:
每种语法都包含两种符号:非终端符号(那些在其他语法规则中展开的符号)和终端符号(那些是“原子”的 - 它们是叶子语法树的一部分,不要放松到其他任何东西)。终端符号通常只是令牌。令牌从词法分析器逐个泵入,并与相应的终端符号匹配。
那些终端符号(标记)通常具有常规语法,这更容易识别(这就是为什么它被排除在词法分析器之外,对于常规语法更加专业,并且可以比使用非常规方法更快地完成它 - 正规语法)。
因此,要为类似HTML / XML / SGML的语言编写词法分析器,您需要找到足够原子且规则的语法部分,以便词法分析器轻松处理。这里出现了问题,因为最初并不清楚这些部分是哪些部分。我很长一段时间都在努力解决这个问题。
但上面的 Lie Ryan 在识别这些部分方面做得非常好。为他而战!令牌类型如下:
<
lexeme,用于开始标记。>
lexeme,用于结束标记。/
lexeme用于结束标记。=
lexeme,用于将属性名称与其值分开。'
lexeme,用于封闭属性值。"
lexeme,用于包含属性值。<
字符但未被上述类型覆盖的文字。您还可以为实体引用添加一些令牌,例如
或&
。大概是:
&
后跟一些字母数字字符并以;
结尾的词汇。为什么我为'
和"
使用了单独的令牌而没有为属性值使用一个令牌?因为常规语法无法识别哪些字符应该结束序列 - 它取决于启动它的字符(结束字符必须与起始字符匹配)。这种“括号”被认为是非常规语法。所以我把它提升到更高的层次 - 对Parser来说。将这些标记(开始和结束)匹配在一起(或者根本不匹配,对于不包含空格的简单属性值)是他的工作。
<强>有感:强> 不幸的是,其中一些令牌可能只出现在其他标记内。因此需要使用词汇上下文,这毕竟是控制状态机识别特定令牌的另一个状态机。这就是为什么我说类似SGML的语言不适合词法分析的模式。
答案 4 :(得分:2)
这就是HTML 5 Parser的工作原理: