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

ale*_*lex 37 html browser parsing tokenize html-parsing

为了论证,我们假设一个HTML解析器.

我已经读过它首先将所有内容标记化,然后解析它.

tokenize意味着什么?

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

例如,它是读取a <然后开始捕获元素,然后一旦它遇到关闭>(在属性之外)它被推送到某个地方的数组堆栈?

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

如果我要阅读像HTML Purifier这样的内容,那么这会让我对HTML的解析方式有所了解吗?

Lie*_*yan 57

标记可以由几个步骤组成,例如,如果你有这个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>
Run Code Online (Sandbox Code Playgroud)

tokenizer可以将该字符串转换为重要令牌的平面列表,丢弃空格(谢谢,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", ">"
]
Run Code Online (Sandbox Code Playgroud)

可能有多个标记化过程将标记列表转换为更高级别标记的列表,如下面的假设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>"
]
Run Code Online (Sandbox Code Playgroud)

然后解析器转换该令牌列表以形成一个树或图形,以一种更方便访问/操作程序的方式表示源文本:

("<html>", {}, [
    ("<head>", {}, [
        ("<title>", {}, ["My HTML Page"]),
    ]), 
    ("<body>", {}, [
        ("<p>", {"style": "special"}, ["This paragraph has special style"]),
        ("<p>", {}, ["This paragraph is not special"]),
    ]),
])
Run Code Online (Sandbox Code Playgroud)

此时,解析完成; 然后由用户来解释树,修改它等等.

  • +1实际显示令牌化的功能 (10认同)

Jer*_*fin 30

首先,您应该意识到解析HTML特别难看 - 在标准化之前,HTML在广泛(和不同)中使用.这会导致各种丑陋,例如标准指定不允许某些构造,但随后指定这些构造所需的行为.

直接提出问题:标记化大致相当于采用英语,并将其分解为单词.在英语中,大多数单词是连续的字母流,可能包括撇号,连字符等.大多数单词被空格包围,但句点,问号,感叹号等也可以表示单词的结尾.同样,对于HTML(或其他),您可以指定一些关于可以构成此语言中的标记(单词)的规则.将输入分解为标记的代码段通常称为词法分析器.

至少在正常情况下,你就不是你开始解析之前,打破所有的输入为特征符.相反,解析器调用词法分析器以在需要时获取下一个标记.当它被调用时,词法分析器会查看足够多的输入以找到一个标记,将其传递给解析器,并且在下一次解析器需要更多输入之前,不会对输入进行标记化.

一般来说,你对解析器的工作方式是正确的,但是(至少在一个典型的解析器中)它在解析语句的过程中使用了一个堆栈,但是它构建的表示语句通常是一个树(和抽象语法树,又名AST),不是多维数组.

基于解析HTML的复杂性,我会保留查找解析器,直到您首先阅读其他几个.如果你做一些环顾四周,你应该能够找到相当数量的解析器/词法分析器,比如数学表达式,它们可能更适合作为介绍(更小,更简单,更容易理解等)


Cor*_*rch 9

不要错过W3C关于解析HTML5的注释.

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

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

扫描仪保证使用正确的单词(令牌).解析器保证单词以正确的组合和顺序使用.扫描仪使用正则表达式和自动机理论.解析器使用语法理论,尤其是无上下文语法.

一对解析资源:


Sas*_*asQ 7

HTML和XML语法(以及其他基于SGML的语法)很难解析,并且它们不适合lexing场景,因为它们不是常规的.在解析理论中,常规语法是没有任何递归的语法,即自相似,嵌套模式或类似于括号的包装器,它们必须相互匹配.但是基于HTML/XML/SGML的语言确实有嵌套模式:标签可以嵌套.嵌套模式的语法在Chomsky分类中的级别更高:它是无上下文的,甚至是依赖于上下文的.

但回到你关于词法分析器的问题:
每种语法都包含两种符号:非终端符号(那些解开其他语法规则的符号)和终端符号(那些是"原子"的 - 它们是语法树的叶子并且不要放松其他任何东西).终端符号通常只是令牌.令牌从词法分析器逐个泵送并与其相应的终端符号匹配.

那些终端符号(标记)通常具有常规语法,这更容易识别(这就是为什么它被排除在词法分析器之外,对于常规语法更加专业,并且可以比使用更常规的非常规语法方法更快地完成它).

因此,要为类似HTML/XML/SGML的语言编写词法分析器,您需要找到足够原子且规则的语法部分,以便词法分析器轻松处理.这里出现了问题,因为最初并不清楚这些部分是哪些部分.我很长一段时间都在努力解决这个问题.

但上面的Lie Ryan在识别这些部件方面做得非常好.为他而战!令牌类型如下:

  • TagOpener:<lexeme,用于启动标签.
  • TagCloser:>lexeme,用于结束标签.
  • ClosingTagMarker:/用于关闭标签的lexeme.
  • 名称:以字母开头的字母数字序列,用于标记名称和属性名称.
  • 值:可包​​含各种不同字符,空格等的文本.用于属性值.
  • 等于:=lexeme,用于将属性名称与其值分隔开.
  • 引用:'lexeme,用于包含属性值.
  • DoubleQuote:"lexeme,用于包含属性值.
  • PlainText:任何不<直接包含字符且未被上述类型覆盖的文本.

您还可以为实体引用添加一些令牌,例如&nbsp;&amp;.大概:

  • EntityReference:一个词汇,&后跟一些字母数字字符,以#结尾;.

为什么我用不同的令牌'"而不是属性值一个令牌?因为常规语法无法识别哪些字符应该结束序列 - 它取决于启动它的字符(结束字符必须与起始字符匹配).这种"括号"被认为是非常规语法.所以我把它提升到更高的层次 - 对Parser来说.将这些标记(开始和结束)匹配在一起(或者根本不匹配,对于不包含空格的简单属性值)是他的工作.

事后想法: 不幸的是,其中一些令牌可能只在其他标记内发生.因此需要使用词汇上下文,这毕竟是控制状态机识别特定令牌的另一个状态机.这就是为什么我说类似SGML的语言不适合词法分析的模式.