16.4 解析标签节点
用同一个 parseTag 处理开始标签与结束标签(靠第二个参数区分),并在解析完标签后切换文本模式,决定其子节点怎么解析。
一、统一用 parseTag 处理开始与结束标签
- 通过
parseTag的第二个参数type来指定处理类型:值为'end'时表示解析的是结束标签(默认'start'); - 无论处理的是开始标签还是结束标签,
parseTag都会消费对应的内容; - 两者的正则只有一点不同:结束标签以字符串
</开头。
二、parseTag 的关键步骤
- 用正则匹配并拿到标签名称(
match[1]是第一个捕获组):
// 开始标签
/^<([a-z][^\t\r\n\f />]*)/i
// 结束标签
/^<\/([a-z][^\t\r\n\f />]*)/i
- 调用 advanceBy 消费正则匹配到的全部内容(如
'<div'); - 调用 advanceSpaces 消费标签中无用的空白字符(如
<div >); - 检查剩余内容是否以
/>开头 → 是则说明是自闭合标签,isSelfClosing = true; - 自闭合则消费
/>(2 个字符),否则只消费>(1 个字符); - 返回 Element 节点。
三、advanceBy 与 advanceSpaces
它们挂在上下文对象 context 上,是「消费模板内容」的最小工具:
const context = {
source: str,
mode: TextModes.DATA,
// 消费指定数量的字符
advanceBy(num) {
context.source = context.source.slice(num)
},
// 消费空白字符
advanceSpaces() {
const match = /^[\t\r\n\f ]+/.exec(context.source)
if (match) {
context.advanceBy(match[0].length)
}
}
}
四、parseTag 实现
function parseTag(context, type = 'start') {
const { advanceBy, advanceSpaces } = context
const match = type === 'start'
? /^<([a-z][^\t\r\n\f />]*)/i.exec(context.source)
: /^<\/([a-z][^\t\r\n\f />]*)/i.exec(context.source)
const tag = match[1]
advanceBy(match[0].length) // 消费 '<div'
advanceSpaces() // 消费标签中的空白字符
const isSelfClosing = context.source.startsWith('/>')
// 自闭合消费 '/>',否则消费 '>'
advanceBy(isSelfClosing ? 2 : 1)
return {
type: 'Element',
tag,
props: [],
children: [],
isSelfClosing
}
}
五、解析标签后切换文本模式
parseElement 拿到标签节点后,需要根据标签类型完成文本模式的切换:
if (element.tag === 'textarea' || element.tag === 'title') {
// <textarea> / <title> → RCDATA
context.mode = TextModes.RCDATA
} else if (/style|xmp|iframe|noembed|noframes|noscript/.test(element.tag)) {
// <style>、<xmp>、<iframe>、<noembed>、<noframes>、<noscript> → RAWTEXT
context.mode = TextModes.RAWTEXT
} else {
// 其余标签 → DATA
context.mode = TextModes.DATA
}
完整的 parseElement 流程:
parseTag(context)解析开始标签;- 自闭合标签直接返回,不再递归解析子节点;
- 按上面的规则切换文本模式;
ancestors.push(element)→ 递归parseChildren→ancestors.pop();- 若剩余内容以
</tag开头,调用parseTag(context, 'end');否则报<tag> 标签缺少闭合标签。
易错点:只消费正则匹配到的标签名是不够的,还必须消费标签末尾的
>或/>,否则下一次循环会卡在同一个位置。
相关
- 16.1 文本模式及其对解析器的影响:RCDATA / RAWTEXT 为什么影响
<的解析 - 16.3 状态机的开启与停止:入栈 / 出栈与状态机的起止
- 16.5 解析属性:在标签名之后继续消费属性与指令