解析器(例如HTML)是如何工作的?

解析器(例如HTML)是如何工作的?,html,browser,parsing,html-parsing,tokenize,Html,Browser,Parsing,Html Parsing,Tokenize,为了论证起见,让我们假设一个HTML解析器 我已经读到它首先标记所有东西,然后解析它 标记化是什么意思 解析器是否读取每个字符,建立多维数组来存储结构 例如,它是否读取(属性外部)后,将其推送到某个数组堆栈上 我感兴趣是为了知道(我很好奇) 如果我通读类似的源代码,这会让我很好地了解HTML是如何解析的吗?首先,你应该知道解析HTML特别难看——HTML在标准化之前被广泛使用(并且有不同的用途)。这导致了各种各样的丑陋,比如标准规定某些构造是不允许的,但却规定了这些构造所需的行为 直截了当地问:

为了论证起见,让我们假设一个HTML解析器

我已经读到它首先标记所有东西,然后解析它

标记化是什么意思

解析器是否读取每个字符,建立多维数组来存储结构

例如,它是否读取
(属性外部)后,将其推送到某个数组堆栈上

我感兴趣是为了知道(我很好奇)


如果我通读类似的源代码,这会让我很好地了解HTML是如何解析的吗?

首先,你应该知道解析HTML特别难看——HTML在标准化之前被广泛使用(并且有不同的用途)。这导致了各种各样的丑陋,比如标准规定某些构造是不允许的,但却规定了这些构造所需的行为

直截了当地问:标记化大致相当于把英语分解成单词。在英语中,大多数单词都是连续的字母流,可能包括撇号、连字符等。大多数单词都被空格包围,但句号、问号、感叹号等也可以表示单词的结尾。同样地,对于HTML(或任何东西),您可以指定一些关于如何用这种语言构成标记(单词)的规则。将输入分解为标记的代码段通常称为lexer

至少在正常情况下,在开始解析之前,不会将所有输入分解为标记。相反,解析器在需要时调用lexer来获取下一个令牌。调用时,lexer查看足够多的输入以找到一个标记,并将其传递给解析器,并且在解析器下次需要更多输入之前,不再对输入进行标记化

一般来说,关于解析器的工作方式,您是对的,但是(至少在典型的解析器中)它在解析语句的过程中使用堆栈,但是它构建来表示语句的通常是树(和抽象语法树,也称AST),而不是多维数组


基于解析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页面

这一段有特殊的风格

这一段并不特别

标记器可以将该字符串转换为重要标记的平面列表,丢弃空格(感谢SasQ的更正):

[“”,
"", 
“,”我的HTML页面“,”,
"",
"",
"",
“本段风格独特”,
"",
"",
“这一段并不特别”,
"",
"",
""
]
可能有多个标记化过程来将标记列表转换为更高级别的标记列表,就像下面假设的HTML解析器可能做的那样(这仍然是一个平面列表):

[(“”,{}),
("", {}), 
(“”,{})、“我的HTML页面”、“”,
"",
("", {}),
(“”,{“风格”:“特殊”}),
“本段风格独特”,
“

”, (“”,{}), “这一段并不特别”, “

”, "", "" ]
然后,解析器将该标记列表转换为一个树或图形,以更方便程序访问/操作的方式表示源文本:

("<html>", {}, [
    ("<head>", {}, [
        ("<title>", {}, ["My HTML Page"]),
    ]), 
    ("<body>", {}, [
        ("<p>", {"style": "special"}, ["This paragraph has special style"]),
        ("<p>", {}, ["This paragraph is not special"]),
    ]),
])
(“”,{}[
("", {}, [
(“”,{},[“我的HTML页面]),
]), 
("", {}, [
(“”,{“风格”:“特殊”},[“本段具有特殊风格”]),
(“”,{},[“本段不特别]),
]),
])

此时,解析已完成;然后由用户解释树、修改树等等。

请不要错过W3C在上的注释

有关扫描/词法分析的有趣介绍,请在web上搜索高效生成的表驱动扫描仪。它显示了扫描最终是如何由自动机理论驱动的。将正则表达式集合转换为单个正则表达式。然后将NFA转换为a,以使状态转换具有确定性。然后描述了一种将DFA转换为转换表的方法

关键一点:扫描程序使用正则表达式理论,但可能不使用现有的正则表达式库。为了获得更好的性能,状态转换被编码为巨型case语句或在转换表中

扫描仪保证使用正确的单词(标记)。语法分析器保证单词以正确的组合和顺序使用。扫描仪使用正则表达式和自动机理论。语法分析器使用语法理论,尤其是

一对解析资源:

HTML和XML语法(以及其他基于SGML的语法)很难解析,而且它们不适合词法分析场景,因为它们不规则。在解析理论中,正则语法是指不具有任何递归的语法,即自相似、嵌套模式或类似于包装器的括号,它们必须相互匹配。但是基于HTML/XML/SGML的语言确实有嵌套模式:标记可以嵌套。具有嵌套模式的语法在乔姆斯基的分类中处于更高的级别:它与上下文无关,甚至与上下文相关

回到你关于lexer的问题:
每种语法都由两种符号组成:非终结符符号(那些展开为其他语法规则的符号)和终结符符号
[("<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"]),
    ]),
])