-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.go
171 lines (139 loc) · 4.05 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package parser
import (
"bufio"
"fmt"
"io"
"github.com/apocelipes/autotoc/internal/stack"
)
// ContentEncoder 对URL进行编码的函数,传入URL,传出编码后的结果
type ContentEncoder func(string) string
// Parser 进行整个markdown的解析
type Parser struct {
// 解析title
innerParser TitleParser
// 开始解析的标题层次
topTag string
// 待扫描的标题行的类型
scanType string
// 从tocMark后的位置开始解析标题
tocMark string
// 根据规则或标题内容过滤title
filter TitleFilter
// 对标题的URL进行encoding
encoder ContentEncoder
}
// TitleParser 解析文件中的标题结构
type TitleParser interface {
// content如果不是标题就返回nil,否则返回TitleNode
Parse(content string) *TitleNode
}
// Creator 创建parser的函数类型,接受一个string作为顶层标题结构,被SetParser调用
type Creator func(string) TitleParser
// 存储所有TitleParser的creator
var parserCreators = make(map[string]Creator)
// SetInnerParser 注册新的标题解析器
func SetInnerParser(name string, creator Creator) {
if name == "" || creator == nil {
panic("name or creator should not be empty")
}
parserCreators[name] = creator
}
// GetInnerParser 获取标题解析器。topTag是可标题的最大级别(例如h1-h5中的h1)
func GetInnerParser(name, topTag string) (TitleParser, error) {
creator, ok := parserCreators[name]
if !ok {
return nil, fmt.Errorf("scanType: %s not support", name)
}
return creator(topTag), nil
}
// GetParser 根据name创建markdown文档解析器
func GetParser(options ...Option) *Parser {
parser := &Parser{}
for _, o := range options {
o(parser)
}
var err error
parser.innerParser, err = GetInnerParser(parser.scanType, parser.topTag)
if err != nil {
panic(err)
}
return parser
}
type Option func(*Parser)
// WithTopTag 设置开始解析的标题层级,值为h1-h5
func WithTopTag(tag string) Option {
return func(p *Parser) {
p.topTag = tag
}
}
// WithScanType 设置需要解析的标题类型(html, markdown)
func WithScanType(t string) Option {
return func(p *Parser) {
p.scanType = t
}
}
// TocMark 设置文档中的toc标记
func WithTOCMark(mark string) Option {
return func(p *Parser) {
p.tocMark = mark
}
}
// WithFilter 根据标题内容确定是否需要被解析
func WithFilter(filter TitleFilter) Option {
return func(p *Parser) {
p.filter = filter
}
}
// WithURLEncoder 设置标题内容的编码器,非ascii字符作为URL时可能需要编码
func WithURLEncoder(encoder ContentEncoder) Option {
return func(p *Parser) {
p.encoder = encoder
}
}
func (p *Parser) encode(url string) string {
if p.encoder == nil {
return url
}
return p.encoder(url)
}
func (p *Parser) isFiltered(content string) bool {
if p.filter == nil {
return false
}
return p.filter.FilterTitleContent(content)
}
// Parse 根据topTag和scanType解析文件中的标题行
// 默认从tocMark之后的位置开始解析,如果没有找到tocMark则解析整个markdown文件
func (p *Parser) Parse(file io.Reader) []*TitleNode {
scanner := bufio.NewScanner(file)
ret := make([]*TitleNode, 0)
parents := stack.NewNodeStack[*TitleNode]()
for scanner.Scan() {
line := scanner.Text()
// 如果遇到tocMark就重新构建节点树
if line == p.tocMark {
ret = make([]*TitleNode, 0)
parents.Clear()
continue
}
node := p.innerParser.Parse(line)
if node == nil || p.isFiltered(node.content) {
continue
}
node.id = p.encode(node.id)
parent, _ := parents.Top()
// 找到自己的父节点,排除所有同级或下级标题
for parent != nil && !parent.IsChildNode(node) {
parents.Pop()
parent, _ = parents.Top()
}
// 因为不知道是否存在子节点,所以都当做拥有子节点并入栈
parents.Push(node)
if parent == nil {
ret = append(ret, node)
continue
}
parent.AddChild(node)
}
return ret
}