quote: '\'' | '"' | void; // 引号类型, 能够是', ", 或许没有
}
Token解析
AST解析首先需求解析原始文本失掉符号列表, 然后再经过上下文语境剖析失掉最终的语法树.
相关于JSON, html虽然看起来复杂, 但是上下文是必需的, 所以虽然JSON可以直接经过token剖析失掉最终的结果, 但是html却不能, token剖析是第一步, 这是必需的. (JSON解析可以参考我的另一篇文章: 徒手写一个JSON解析器(Golang) ).
token解析时, 需求依据以后的形状来剖析token的含义, 然后得出一个token列表.
首先定义token的结构:
export interface IToken {
start: number; // 起始位置
end: number; // 完毕位置
value: string; // token
type: TokenKind; // 类型
}
Token类型一共有以下几种:
export enum TokenKind {
Literal = 'Literal', // 文本
OpenTag = 'OpenTag', // 标签称号
OpenTagEnd = 'OpenTagEnd', // 末尾标签完毕符, 能够是 '/', 或许 '', '--'
CloseTag = 'CloseTag', // 封锁标签
Whitespace = 'Whitespace', // 末尾标签类属性值之间的空白
AttrValueEq = 'AttrValueEq', // 属性中的=
AttrValueNq = 'AttrValueNq', // 属性中没有引号的值
AttrValueSq = 'AttrValueSq', // 被单引号包起来的属性值
AttrValueDq = 'AttrValueDq', // 被双引号包起来的属性值
}
Token剖析时并没有思索属性的键/值关系, 均一致视为属性中的一个片段, 同时, 视 = 为一个
特殊的独立段片段, 然后交给下层的 parser 去剖析键值关系. 这么做的缘由是为了在token剖析
时避免上下文处置, 并简化形状机形状表. 形状列表如下:
enum State {
Literal = 'Literal',
BeforeOpenTag = 'BeforeOpenTag',
OpeningTag = 'OpeningTag',
AfterOpenTag = 'AfterOpenTag',
InValueNq = 'InValueNq',
InValueSq = 'InValueSq',
InValueDq = 'InValueDq',
ClosingOpenTag = 'ClosingOpenTag',
OpeningSpecial = 'OpeningSpecial',
OpeningDoctype = 'OpeningDoctype',
OpeningNormalComment = 'OpeningNormalComment',
InNormalComment = 'InNormalComment',
InShortComment = 'InShortComment',
ClosingNormalComment = 'ClosingNormalComment',
ClosingTag = 'ClosingTag',
}
整个解析采用函数式编程, 没有运用OO, 为了简化在函数间传递形状参数, 由于是一个同步操作,
这里应用了JavaScript的事情模型, 采用全局变量来保存形状. Token剖析时所需求的全局变量列表如下:
let state: State // 以后的形状
let buffer: string // 输入的字符串
let bufSize: number // 输入字符串长度
let sectionStart: number // 正在解析的Token的起始位置
let index: number // 以后解析的字符的位置
let tokens: IToken[] // 已解析的token列表
let char: number // 以后解析的位置的字符的UnicodePoint
在末尾解析前, 需求初始化全局变量:
function init(input: string) {
state = State.Literal
buffer = input
bufSize = input.length
sectionStart = 0
index = 0
tokens = []
}
然后末尾解析, 解析时需求遍历输入字符串中的一切字符, 并依据以后形状停止相应的处置
(改动形状, 输入token等), 解析完成后, 清空全局变量, 前往完毕.
export function tokenize(input: string): IToken[] {
init(input)
while (index < bufSize) {
char = buffer.charCodeAt(index)
switch (state) {
// ...依据不同的形状停止相应的处置
// 文章疏忽了对各个形状的处置, 详细了解可以查看源代码
}
index++
}
const _nodes = nodes
// 清空形状
init('')
return _nodes
}
语法树解析
在获取到token列表之后, 需求依据上下文解析失掉最终的节点树, 方式与tokenize相似,均采用全局变量保存传递形状, 遍历一切的token, 不同之处在于这里没有一个全局的形状机。
由于形状完全可以经过正在解析的节点的类型来判别。
export function parse(input: string): INode[] {
init(input)
while (index < count) {
token = tokens[index]
switch (token.type) {
case TokenKind.Literal:
if (!node) {
node = createLiteral()
pushNode(node)
} else {
appendLiteral(node)
}
break
case TokenKind.OpenTag:
node = void 0
parseOpenTag()
break
case TokenKind.CloseTag:
node = void 0
parseCloseTag()
break
default:
unexpected()
break
}
index++
}
const _nodes = nodes
init()
return _nodes
}
不太多解释, 可以到GitHub查看源代码.
结语
项目已开源, 称号是 html5parser , 可以经过npm/yarn安装:
npm install html5parser -S
# OR
yarn add html5parser
或许到GitHub查看源代码: acrazing/html5parser 。
目前对正常的HTML解析已完全经过测试, 已知的BUG包括对注释的解析, 以及未正常完毕的
输入的解析处置(均在语法剖析层面, token剖析已经过测试).
【编辑引荐】
立足当下共享未来 第四届HTML5移动生态大会浩荡召开
白鹭时代产品线片面退化 携手行业抓住HTML5游戏拐点机遇
HTML5游戏开发难点之效率、功用和加载量
HTML5音频API Web Audio
HTML5中手势原理剖析与数学知识的实际
(责任编辑:admin)