实现解析 element 标签
1. 测试样例
test('simple element', () => {
const elementStr = '<div></div>'
const ast = baseParse(elementStr)
expect(ast.children[0]).toStrictEqual({
type: NodeType.ELEMENT,
tag: 'div',
})
})
2. 实现
2.1 伪实现
我们先给 NodeType 枚举添加一种 ELEMENT 类型
function parseChildren(context: { source: string }): any {
const nodes: any = []
let node
// 将 context.source 提取出来
const s = context.source
if (s.startsWith('{{')) {
node = parseInterpolation(context)
// 如果第一位是 < 而且第二位是 a-z 的话,就进入到 parseElement
} else if (s.startsWith('<') && /[a-z]/i.test(s[1])) {
node = parseElement(context)
}
nodes.push(node)
return [node]
}
// 为了使测试快速通过,我们可以先写一个伪实现
function parseElement(context: { source: string }): any {
return {
type: NodeType.ELEMENT,
tag: 'div',
}
}
2.2 实现
第一步就是将 <div></div> 中的 div 提取出来,我们可以来使用正则。
^<[a-z]*
通过这个正则可以将 <div 提取出来
function parseElement(context: { source: string }): any {
return parseTag(context)
}
function parseTag(context: { source: string }) {
// i 忽略大小写, ([a-z]*) 作为一个分组
const match = /^<([a-z]*)/i.exec(context.source)
// 其中 tag[1] 就是匹配出来的 div
const tag = match![1]
return {
type: NodeType.ELEMENT,
tag,
}
}
然后别忘了还需要推进
function parseTag(context: { source: string }) {
const match = /^<([a-z]*)/i.exec(context.source)
const tag = match![1]
// 增加推进,同时通过 match[0] 来获取匹配出的 <div 的长度
// +1 意思是需要加上 >
advanceBy(context, match![0].length + 1)
return {
type: NodeType.ELEMENT,
tag,
}
}
最后,推进后我们的 context.source 还剩下 </div>,此时我们发现和我们写好的匹配 <div> 的很像,只需要修改正则就好了
^<\/?[a-z]*
function parseElement(context: { source: string }): any {
// 这里调用两次 parseTag 处理前后标签
const element = parseTag(context)
parseTag(context)
return element
}
function parseTag(context: { source: string }) {
// 修改正则
const match = /^<\/?([a-z]*)/i.exec(context.source)
const tag = match![1]
advanceBy(context, match![0].length + 1)
return {
type: NodeType.ELEMENT,
tag,
}
}
2.3 优化
再处理结束标签的时候我们发现不需要 return 了,所以可以优化一下
// 增加枚举
const enum TagType {
START,
END,
}
function parseElement(context: { source: string }): any {
// 传入类型
const element = parseTag(context, TagType.START)
parseTag(context, TagType.END)
return element
}
function parseTag(context: { source: string }, type: TagType) {
const match = /^<\/?([a-z]*)/i.exec(context.source)
const tag = match![1]
advanceBy(context, match![0].length + 1)
// 如果是 TagType.END 就不需要 return
if (type === TagType.END) return
return {
type: NodeType.ELEMENT,
tag,
}
}
