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

问题描述:

对于参数的缘故让我们假设一个HTML解析器。解析器(例如,HTML)如何工作?

我读过它标记为首先,然后解析它。

标记化意味着什么?

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

例如,它是否读取<然后开始捕获该元素,然后一旦遇到关闭>(属性外部),它会被推送到某个数组堆栈上?

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

如果我是通过类似HTML Purifier读取源,将是给我的HTML是如何解析的好主意吗?

+0

看http://en.wikipedia.org/wiki/Lexical_parser一个非常简短的介绍;还请查看那里的“解析”文章。而HTML Purifier在某种程度上就是这样做的。 – Piskvor 2010-06-30 14:40:07

+0

HTML Agility Pack是开源的,基于tokanizer。 http://htmlagilitypack.codeplex.com/ – Oded 2010-06-30 14:43:12

+0

如果您可以阅读C(ocaml,lisp),请尝试查看yacc/lex(ocamlyacc/ocamllex,cl-yacc/cl-lex ...)上的一些教程。您将从代码中快速了解基础知识。如果你可以阅读代码。 – Amadan 2010-06-30 14:48:40

首先,你应该知道,解析HTML特别难看 - HTML是被标准化前宽(和发散)的使用。这导致了各种丑陋,例如标准规定某些构造不被允许,但是随后指定这些构造所需的行为。

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

至少在正常情况下,你做而不是在开始解析之前,将所有输入分成标记。相反,解析器调用词法分析器来获取下一个需要的标记。当它被调用时,词法分析器查看足够的输入来查找一个标记,将其提供给解析器,并且直到下一次解析器需要更多输入时才再对输入进行标记。

通常,解析器的工作方式是正确的,但是(至少在典型的解析器中)它在解析语句的过程中使用堆栈,但它通常构建来表示语句一棵树(和抽象语法树,又名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> 

标记生成器可以是字符串转换为显著令牌, 丢弃空格 的平面列表(感谢,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"]), 
    ]), 
    ("<body>", {}, [ 
     ("<p>", {"style": "special"}, ["This paragraph has special style"]), 
     ("<p>", {}, ["This paragraph is not special"]), 
    ]), 
]) 

[("<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>" 
] 

然后解析器转换令牌该列表以形成树或图,它表示的方式是更方便的访问源文本由程序/操纵

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

+7

+1喜欢这个例子 – 2010-06-30 15:58:45

+8

+1实际上显示了什么是标记化 – alex 2010-07-01 01:52:51

+0

+1用于回答(意外)我的关于哪些文本片段应该构成基于HTML/XML/SGML的语言的令牌的长期持久问题! (我在其他主题中询问过这个问题。)谢谢,伙计!非常好的例子,的确如此! :-) – SasQ 2011-09-09 13:30:19

千万不要错过W3C关于parsing HTML5的注释。

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

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

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

一对夫妇解析资源:

+0

+1感谢W3C链接。它看起来像一个内容丰富的(和长)阅读! – alex 2010-07-01 01:53:34

+0

而且,如果将来您的语法不会改变,您可以将转换表“烘焙”到源代码中,然后编译一次。这是可能的,因为你运行程序的机器实际上也是一个状态自动机!所以你可以“在硬件中实现你的自动机”。方法如下:状态可以用代码中的位置(CPU中的指令指针)表示。状态转换只是(不)条件跳转(分支)。您也可以使用程序的堆栈来存储/恢复状态(过程调用和返回)。这会加速很多事情。 – SasQ 2011-09-09 13:36:14

HTML和XML语法(基于SGML等)是非常难以分析和他们不适合早已进入lexing的情况,因为他们不是常规。在解析理论中,正则语法是没有任何递归的那个,即自相似,嵌套模式或圆括号,就像包装必须相互匹配。但基于HTML/XML/SGML的语言确实有嵌套模式:标记可以嵌套。嵌套模式的语法在乔姆斯基分类中的级别较高:它的上下文无关或甚至与上下文相关。

但是,回到你的问题有关词法分析器:
每个语法由两种符号的:非终端符号(那些放松到其他语法规则)和终端符号(那些“原子” - 它们是语法树的叶子,不放松其他任何东西)。终端符号通常只是代币。令牌从词法分析器中逐一抽出并与相应的终端符号匹配。

那些终端符号(标记)通常具有常规语法,这更容易识别(这就是为什么它被分解为词法分析器,这对于常规语法更加专业化,并且可以比使用更一般的方法非常规语法)。因此,要为HTML/XML/SGML类语言编写词法分析器,您需要找到足够原子和规则的语法部分,以便由词法分析器轻松处理。在这里出现问题,因为一开始并不清楚哪些部分是这些。我长期以来一直在努力解决这个问题。

但是列瑞恩上面已经做了很好的认识这些部分。布拉沃为他!令牌类型如下:

  • TagOpener:< lexeme,用于启动标签。
  • TagCloser:> lexeme,用于结束标签。
  • ClosingTagMarker:/ lexeme用于结束标记。
  • 名称:以字母开头的字母数字序列,用于标记名称和属性名称。
  • 值:可以包含各种不同字符,空格等的文本用于属性的值。
  • 等于:= lexeme,用于从属性值中分离属性名称。
  • Quote:' lexeme,用于封闭属性值。
  • DoubleQuote:" lexeme,用于包含属性值。
  • PlainText:任何不包含<字符的文字,不包含在上述类型中。

您也可以为实体引用设置一些标记,如&nbsp;&amp;。大概是:

  • 的EntityReference:由&其次是一些字母数字字符,并与;结束一语义。

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

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

这是HTML 5分析器是如何工作的:

This is how HTML 5 Parser works