解析器(例如,HTML)如何工作?
对于参数的缘故让我们假设一个HTML解析器。解析器(例如,HTML)如何工作?
我读过它标记为首先,然后解析它。
标记化意味着什么?
解析器是否每个都读取每个字符,构建一个多维数组来存储结构?
例如,它是否读取<
然后开始捕获该元素,然后一旦遇到关闭>
(属性外部),它会被推送到某个数组堆栈上?
我很感兴趣,为了知道(我很好奇)。
如果我是通过类似HTML Purifier读取源,将是给我的HTML是如何解析的好主意吗?
首先,你应该知道,解析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>"
]
然后解析器转换令牌该列表以形成树或图,它表示的方式是更方便的访问源文本由程序/操纵
此时,解析完成;然后由用户来解释树,修改它等。
千万不要错过W3C关于parsing HTML5的注释。
有关扫描/乐视的有趣介绍,请在网上搜索桌面驱动扫描仪的高效生成。它显示了扫描如何最终由自动机理论驱动。正则表达式集合被转换为一个单一的NFA。然后将NFA转换为DFA以确定状态转换。本文随后描述了一种将DFA转换为转换表的方法。
一个关键点:扫描仪使用正则表达式理论,但可能不使用现有的正则表达式库。为了获得更好的性能,状态转换被编码为巨大的case语句或转换表。
扫描仪保证使用正确的单词(标记)。解析器保证这些单词以正确的组合和顺序使用。扫描仪使用正则表达式和自动机理论。解析器使用语法理论,尤其是context-free grammars。
一对夫妇解析资源:
HTML和XML语法(基于SGML等)是非常难以分析和他们不适合早已进入lexing的情况,因为他们不是常规。在解析理论中,正则语法是没有任何递归的那个,即自相似,嵌套模式或圆括号,就像包装必须相互匹配。但基于HTML/XML/SGML的语言确实有嵌套模式:标记可以嵌套。嵌套模式的语法在乔姆斯基分类中的级别较高:它的上下文无关或甚至与上下文相关。
但是,回到你的问题有关词法分析器:
每个语法由两种符号的:非终端符号(那些放松到其他语法规则)和终端符号(那些“原子” - 它们是语法树的叶子,不放松其他任何东西)。终端符号通常只是代币。令牌从词法分析器中逐一抽出并与相应的终端符号匹配。
那些终端符号(标记)通常具有常规语法,这更容易识别(这就是为什么它被分解为词法分析器,这对于常规语法更加专业化,并且可以比使用更一般的方法非常规语法)。因此,要为HTML/XML/SGML类语言编写词法分析器,您需要找到足够原子和规则的语法部分,以便由词法分析器轻松处理。在这里出现问题,因为一开始并不清楚哪些部分是这些。我长期以来一直在努力解决这个问题。
但是列瑞恩上面已经做了很好的认识这些部分。布拉沃为他!令牌类型如下:
- TagOpener:
<
lexeme,用于启动标签。 - TagCloser:
>
lexeme,用于结束标签。 - ClosingTagMarker:
/
lexeme用于结束标记。 - 名称:以字母开头的字母数字序列,用于标记名称和属性名称。
- 值:可以包含各种不同字符,空格等的文本用于属性的值。
- 等于:
=
lexeme,用于从属性值中分离属性名称。 - Quote:
'
lexeme,用于封闭属性值。 - DoubleQuote:
"
lexeme,用于包含属性值。 - PlainText:任何不包含
<
字符的文字,不包含在上述类型中。
您也可以为实体引用设置一些标记,如
或&
。大概是:
- 的EntityReference:由
&
其次是一些字母数字字符,并与;
结束一语义。
为什么我对'
和"
使用单独的标记,而不是一个标记用于属性值?由于常规语法无法识别哪些字符应该结束序列 - 它取决于启动它的字符(结尾字符必须与起始字符匹配)。这种“括号”被认为是非常规的语法。所以我把它提升到一个更高的层次 - 对于解析器。将这些令牌(开始和结束)匹配在一起(或者根本没有,对于不包含空格的简单属性值)是他的工作。
事后反思: 不幸的是,其中一些令牌可能只发生在其他标记内。因此需要使用词汇上下文,毕竟这是另一个控制状态机识别特定令牌的状态机。这就是为什么我说SGML类语言不适合词法分析的模式。
这是HTML 5分析器是如何工作的:
看http://en.wikipedia.org/wiki/Lexical_parser一个非常简短的介绍;还请查看那里的“解析”文章。而HTML Purifier在某种程度上就是这样做的。 – Piskvor 2010-06-30 14:40:07
HTML Agility Pack是开源的,基于tokanizer。 http://htmlagilitypack.codeplex.com/ – Oded 2010-06-30 14:43:12
如果您可以阅读C(ocaml,lisp),请尝试查看yacc/lex(ocamlyacc/ocamllex,cl-yacc/cl-lex ...)上的一些教程。您将从代码中快速了解基础知识。如果你可以阅读代码。 – Amadan 2010-06-30 14:48:40