code-for-vue-3-book
code-for-vue-3-book copied to clipboard
code-15.4-2.html 代码错误
第一处:parent 会丢失 第二次:因为用了 splice,遍历 children 时,索引应该递减
const children = context.currentNode.children
if (children) {
for (let i = 0; i < children.length; i++) {
context.parent = context.currentNode
context.childIndex = i
traverseNode(children[i], context)
}
}
修正为:
const children = context.currentNode.children
if (children) {
let len = children.length
let parent = context.currentNode
for (let i = len - 1; i >= 0; i--) {
context.parent = parent
context.childIndex = i
traverseNode(children[i], context)
}
}
修正的代码应该是:
const State = {
initial: 1,
tagOpen: 2,
tagName: 3,
text: 4,
tagEnd: 5,
tagEndName: 6
}
function isAlpha(char) {
return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z'
}
function tokenize(str) {
let currentState = State.initial
const chars = []
const tokens = []
while(str) {
const char = str[0]
switch (currentState) {
case State.initial:
if (char === '<') {
currentState = State.tagOpen
str = str.slice(1)
} else if (isAlpha(char)) {
currentState = State.text
chars.push(char)
str = str.slice(1)
}
break
case State.tagOpen:
if (isAlpha(char)) {
currentState = State.tagName
chars.push(char)
str = str.slice(1)
} else if (char === '/') {
currentState = State.tagEnd
str = str.slice(1)
}
break
case State.tagName:
if (isAlpha(char)) {
chars.push(char)
str = str.slice(1)
} else if (char === '>') {
currentState = State.initial
tokens.push({
type: 'tag',
name: chars.join('')
})
chars.length = 0
str = str.slice(1)
}
break
case State.text:
if (isAlpha(char)) {
chars.push(char)
str = str.slice(1)
} else if (char === '<') {
currentState = State.tagOpen
tokens.push({
type: 'text',
content: chars.join('')
})
chars.length = 0
str = str.slice(1)
}
break
case State.tagEnd:
if (isAlpha(char)) {
currentState = State.tagEndName
chars.push(char)
str = str.slice(1)
}
break
case State.tagEndName:
if (isAlpha(char)) {
chars.push(char)
str = str.slice(1)
} else if (char === '>') {
currentState = State.initial
tokens.push({
type: 'tagEnd',
name: chars.join('')
})
chars.length = 0
str = str.slice(1)
}
break
}
}
return tokens
}
function parse(str) {
const tokens = tokenize(str)
const root = {
type: 'Root',
children: []
}
const elementStack = [root]
while (tokens.length) {
const parent = elementStack[elementStack.length - 1]
const t = tokens[0]
switch (t.type) {
case 'tag':
const elementNode = {
type: 'Element',
tag: t.name,
children: []
}
parent.children.push(elementNode)
elementStack.push(elementNode)
break
case 'text':
const textNode = {
type: 'Text',
content: t.content
}
parent.children.push(textNode)
break
case 'tagEnd':
elementStack.pop()
break
}
tokens.shift()
}
return root
}
function dump(node, indent = 0) {
const type = node.type
const desc = node.type === 'Root'
? ''
: node.type === 'Element'
? node.tag
: node.content
console.log(`${'-'.repeat(indent)}${type}: ${desc}`)
if (node.children) {
node.children.forEach(n => dump(n, indent + 2))
}
}
function transformElement(node) {
if (node.type === 'Element' && node.tag === 'p') {
node.tag = 'h1'
}
}
function transformText(node, context) {
if (node.type === 'Text') {
context.removeNode()
}
}
function traverseNode(ast, context) {
context.currentNode = ast
const transforms = context.nodeTransforms
for (let i = 0; i < transforms.length; i++) {
transforms[i](context.currentNode, context)
if (!context.currentNode) return
}
const children = context.currentNode.children
if (children) {
let len = children.length
let parent = context.currentNode
for (let i = len - 1; i >= 0; i--) {
context.parent = parent
context.childIndex = i
traverseNode(children[i], context)
}
}
}
function transform(ast) {
const context = {
currentNode: null,
parent: null,
replaceNode(node) {
context.currentNode = node
context.parent.children[context.childIndex] = node
},
removeNode() {
if (context.parent) {
context.parent.children.splice(context.childIndex, 1)
context.currentNode = null
}
},
nodeTransforms: [
transformElement,
transformText
]
}
// 调用 traverseNode 完成转换
traverseNode(ast, context)
// 打印 AST 信息
dump(ast)
}
const ast = parse(`<div>u<p>Vue</p><p>Template</p>pop</div>`)
transform(ast)