只测试或你给的这个字符串,其他的没测试过const template1 = `
1111
::content1
22222
333
::content2
content2::
333333
::content2
sdfsadfda
::content3
666
777
::content4
content4::
content3::
content2::
content1::
::content1
::content2
content2::
::content2
content2::
content1::`
type HeadingElementTagName = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
type TreeNode = {
tagName: HeadingElementTagName
content: string
children: TreeNode[]
textContent: string
}
class Parser {
private index = 0
private REBlank = /\s/
private template: string = ''
private RELiteral = /[a-zA-Z0-9_]/
private skipBlank() {
while (
this.index < this.template.length &&
this.REBlank.test(this.template[this.index])
)
++this.index
}
private parents: TreeNode[] = []
private throwError() {
throw new Error(
`Unexpected token ${this.template[this.index]} at index ${this.index}`
)
}
private consume(chars: string): void {
if (!this.template.startsWith(chars, this.index)) this.throwError()
this.index += chars.length
}
private readTagName(): string {
let tagName = ''
while (
this.index < this.template.length &&
this.template[this.index] !== '>' &&
this.template[this.index] !== '/'
) {
tagName += this.template[this.index]
++this.index
}
if (!tagName) this.throwError()
return tagName
}
private readHeadingElement(): TreeNode {
//读取开始标签比如,
this.consume('
let tagNameStart = this.readTagName()
this.consume('>')
let textContent = ''
while (
this.index < this.template.length &&
this.template[this.index] !== '
) {
textContent += this.template[this.index]
++this.index
}
this.skipBlank()
//读取结束标签比如
this.consume('')
const indexEnd = this.index
const tagNameEnd = this.readTagName()
this.consume('>')
if (tagNameStart !== tagNameEnd)
throw new Error(
`mismatching tag expect ${tagNameStart} but got ${tagNameEnd} at index ${indexEnd}`
)
const headingNode: TreeNode = {
tagName: tagNameStart as HeadingElementTagName,
children: [],
content: '',
textContent,
}
return headingNode
}
readStartContent(): string {
this.consume('::')
let content = ''
while (
this.index < this.template.length &&
this.RELiteral.test(this.template[this.index])
) {
content += this.template[this.index]
++this.index
}
if (!content) this.throwError()
return content
}
readEndContent(): string {
let content = ''
while (
this.index < this.template.length &&
this.RELiteral.test(this.template[this.index])
) {
content += this.template[this.index]
++this.index
}
if (!content) this.throwError()
this.consume('::')
return content
}
/**
* 读取一个TreeNode节点
* @returns 读取的TreeNode节点
*/
generateTree(): TreeNode | null {
let resolvedRoot: TreeNode | null = null
const nodeContent: string[] = []
while (this.index < this.template.length) {
const headingNode = this.readHeadingElement()
const parentNode = this.parents[this.parents.length - 1] ?? null
if (!resolvedRoot) resolvedRoot = headingNode
if (parentNode) parentNode.children.push(headingNode)
this.parents.push(headingNode)
this.skipBlank()
//判断该节点是否有content有则读取,没有则返回
if (this.template.startsWith(':', this.index)) {
const content = (headingNode.content = this.readStartContent())
nodeContent.push(content)
this.skipBlank()
//判断该content下是否有子节点,有的话递归读取
if (this.template.startsWith('
this.generateTree()
}
this.skipBlank()
//没有到达contentEnd据徐读取子节点
if (
this.index >= this.template.length ||
!this.RELiteral.test(this.template[this.index])
)
continue
} else {
/**
* 该节点没有子节点直接返回 */
this.parents.pop()
this.skipBlank()
if (!nodeContent.length) break
}
//循环读取contentEnd
while (
nodeContent.length &&
this.index < this.template.length &&
this.RELiteral.test(this.template[this.index])
) {
const indexEnd = this.index
const contentEnd = this.readEndContent()
if (nodeContent[nodeContent.length - 1] !== contentEnd) {
throw new Error(
`Mismatching content expect ${headingNode.content} but got ${contentEnd} at ${indexEnd}`
)
}
nodeContent.pop()
this.parents.pop()
this.skipBlank()
}
//如果nodeContent为空说明该节点已经处理完毕,结束循环,继续父级的逻辑
if (!nodeContent.length) break
this.skipBlank()
}
return resolvedRoot
}
parse(str: string): null | TreeNode {
this.template = str
this.skipBlank()
if (this.index >= this.template.length) return null
const root: TreeNode = {
tagName: 'DummyRoot' as HeadingElementTagName,
content: '',
children: [],
textContent: '',
}
while (this.index < this.template.length) {
root.children.push(this.generateTree()!)
this.skipBlank()
}
return root
}
}
const parser = new Parser()
console.log(parser.parse(template1)?.children[0])