1 |
yakumo_izuru |
1.1 |
package blackfriday |
2 |
|
|
|
3 |
|
|
import ( |
4 |
|
|
"bytes" |
5 |
|
|
"fmt" |
6 |
|
|
) |
7 |
|
|
|
8 |
|
|
// NodeType specifies a type of a single node of a syntax tree. Usually one |
9 |
|
|
// node (and its type) corresponds to a single markdown feature, e.g. emphasis |
10 |
|
|
// or code block. |
11 |
|
|
type NodeType int |
12 |
|
|
|
13 |
|
|
// Constants for identifying different types of nodes. See NodeType. |
14 |
|
|
const ( |
15 |
|
|
Document NodeType = iota |
16 |
|
|
BlockQuote |
17 |
|
|
List |
18 |
|
|
Item |
19 |
|
|
Paragraph |
20 |
|
|
Heading |
21 |
|
|
HorizontalRule |
22 |
|
|
Emph |
23 |
|
|
Strong |
24 |
|
|
Del |
25 |
|
|
Link |
26 |
|
|
Image |
27 |
|
|
Text |
28 |
|
|
HTMLBlock |
29 |
|
|
CodeBlock |
30 |
|
|
Softbreak |
31 |
|
|
Hardbreak |
32 |
|
|
Code |
33 |
|
|
HTMLSpan |
34 |
|
|
Table |
35 |
|
|
TableCell |
36 |
|
|
TableHead |
37 |
|
|
TableBody |
38 |
|
|
TableRow |
39 |
|
|
) |
40 |
|
|
|
41 |
|
|
var nodeTypeNames = []string{ |
42 |
|
|
Document: "Document", |
43 |
|
|
BlockQuote: "BlockQuote", |
44 |
|
|
List: "List", |
45 |
|
|
Item: "Item", |
46 |
|
|
Paragraph: "Paragraph", |
47 |
|
|
Heading: "Heading", |
48 |
|
|
HorizontalRule: "HorizontalRule", |
49 |
|
|
Emph: "Emph", |
50 |
|
|
Strong: "Strong", |
51 |
|
|
Del: "Del", |
52 |
|
|
Link: "Link", |
53 |
|
|
Image: "Image", |
54 |
|
|
Text: "Text", |
55 |
|
|
HTMLBlock: "HTMLBlock", |
56 |
|
|
CodeBlock: "CodeBlock", |
57 |
|
|
Softbreak: "Softbreak", |
58 |
|
|
Hardbreak: "Hardbreak", |
59 |
|
|
Code: "Code", |
60 |
|
|
HTMLSpan: "HTMLSpan", |
61 |
|
|
Table: "Table", |
62 |
|
|
TableCell: "TableCell", |
63 |
|
|
TableHead: "TableHead", |
64 |
|
|
TableBody: "TableBody", |
65 |
|
|
TableRow: "TableRow", |
66 |
|
|
} |
67 |
|
|
|
68 |
|
|
func (t NodeType) String() string { |
69 |
|
|
return nodeTypeNames[t] |
70 |
|
|
} |
71 |
|
|
|
72 |
|
|
// ListData contains fields relevant to a List and Item node type. |
73 |
|
|
type ListData struct { |
74 |
|
|
ListFlags ListType |
75 |
|
|
Tight bool // Skip <p>s around list item data if true |
76 |
|
|
BulletChar byte // '*', '+' or '-' in bullet lists |
77 |
|
|
Delimiter byte // '.' or ')' after the number in ordered lists |
78 |
|
|
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering |
79 |
|
|
IsFootnotesList bool // This is a list of footnotes |
80 |
|
|
} |
81 |
|
|
|
82 |
|
|
// LinkData contains fields relevant to a Link node type. |
83 |
|
|
type LinkData struct { |
84 |
|
|
Destination []byte // Destination is what goes into a href |
85 |
|
|
Title []byte // Title is the tooltip thing that goes in a title attribute |
86 |
|
|
NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote |
87 |
|
|
Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil. |
88 |
|
|
} |
89 |
|
|
|
90 |
|
|
// CodeBlockData contains fields relevant to a CodeBlock node type. |
91 |
|
|
type CodeBlockData struct { |
92 |
|
|
IsFenced bool // Specifies whether it's a fenced code block or an indented one |
93 |
|
|
Info []byte // This holds the info string |
94 |
|
|
FenceChar byte |
95 |
|
|
FenceLength int |
96 |
|
|
FenceOffset int |
97 |
|
|
} |
98 |
|
|
|
99 |
|
|
// TableCellData contains fields relevant to a TableCell node type. |
100 |
|
|
type TableCellData struct { |
101 |
|
|
IsHeader bool // This tells if it's under the header row |
102 |
|
|
Align CellAlignFlags // This holds the value for align attribute |
103 |
|
|
} |
104 |
|
|
|
105 |
|
|
// HeadingData contains fields relevant to a Heading node type. |
106 |
|
|
type HeadingData struct { |
107 |
|
|
Level int // This holds the heading level number |
108 |
|
|
HeadingID string // This might hold heading ID, if present |
109 |
|
|
IsTitleblock bool // Specifies whether it's a title block |
110 |
|
|
} |
111 |
|
|
|
112 |
|
|
// Node is a single element in the abstract syntax tree of the parsed document. |
113 |
|
|
// It holds connections to the structurally neighboring nodes and, for certain |
114 |
|
|
// types of nodes, additional information that might be needed when rendering. |
115 |
|
|
type Node struct { |
116 |
|
|
Type NodeType // Determines the type of the node |
117 |
|
|
Parent *Node // Points to the parent |
118 |
|
|
FirstChild *Node // Points to the first child, if any |
119 |
|
|
LastChild *Node // Points to the last child, if any |
120 |
|
|
Prev *Node // Previous sibling; nil if it's the first child |
121 |
|
|
Next *Node // Next sibling; nil if it's the last child |
122 |
|
|
|
123 |
|
|
Literal []byte // Text contents of the leaf nodes |
124 |
|
|
|
125 |
|
|
HeadingData // Populated if Type is Heading |
126 |
|
|
ListData // Populated if Type is List |
127 |
|
|
CodeBlockData // Populated if Type is CodeBlock |
128 |
|
|
LinkData // Populated if Type is Link |
129 |
|
|
TableCellData // Populated if Type is TableCell |
130 |
|
|
|
131 |
|
|
content []byte // Markdown content of the block nodes |
132 |
|
|
open bool // Specifies an open block node that has not been finished to process yet |
133 |
|
|
} |
134 |
|
|
|
135 |
|
|
// NewNode allocates a node of a specified type. |
136 |
|
|
func NewNode(typ NodeType) *Node { |
137 |
|
|
return &Node{ |
138 |
|
|
Type: typ, |
139 |
|
|
open: true, |
140 |
|
|
} |
141 |
|
|
} |
142 |
|
|
|
143 |
|
|
func (n *Node) String() string { |
144 |
|
|
ellipsis := "" |
145 |
|
|
snippet := n.Literal |
146 |
|
|
if len(snippet) > 16 { |
147 |
|
|
snippet = snippet[:16] |
148 |
|
|
ellipsis = "..." |
149 |
|
|
} |
150 |
|
|
return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis) |
151 |
|
|
} |
152 |
|
|
|
153 |
|
|
// Unlink removes node 'n' from the tree. |
154 |
|
|
// It panics if the node is nil. |
155 |
|
|
func (n *Node) Unlink() { |
156 |
|
|
if n.Prev != nil { |
157 |
|
|
n.Prev.Next = n.Next |
158 |
|
|
} else if n.Parent != nil { |
159 |
|
|
n.Parent.FirstChild = n.Next |
160 |
|
|
} |
161 |
|
|
if n.Next != nil { |
162 |
|
|
n.Next.Prev = n.Prev |
163 |
|
|
} else if n.Parent != nil { |
164 |
|
|
n.Parent.LastChild = n.Prev |
165 |
|
|
} |
166 |
|
|
n.Parent = nil |
167 |
|
|
n.Next = nil |
168 |
|
|
n.Prev = nil |
169 |
|
|
} |
170 |
|
|
|
171 |
|
|
// AppendChild adds a node 'child' as a child of 'n'. |
172 |
|
|
// It panics if either node is nil. |
173 |
|
|
func (n *Node) AppendChild(child *Node) { |
174 |
|
|
child.Unlink() |
175 |
|
|
child.Parent = n |
176 |
|
|
if n.LastChild != nil { |
177 |
|
|
n.LastChild.Next = child |
178 |
|
|
child.Prev = n.LastChild |
179 |
|
|
n.LastChild = child |
180 |
|
|
} else { |
181 |
|
|
n.FirstChild = child |
182 |
|
|
n.LastChild = child |
183 |
|
|
} |
184 |
|
|
} |
185 |
|
|
|
186 |
|
|
// InsertBefore inserts 'sibling' immediately before 'n'. |
187 |
|
|
// It panics if either node is nil. |
188 |
|
|
func (n *Node) InsertBefore(sibling *Node) { |
189 |
|
|
sibling.Unlink() |
190 |
|
|
sibling.Prev = n.Prev |
191 |
|
|
if sibling.Prev != nil { |
192 |
|
|
sibling.Prev.Next = sibling |
193 |
|
|
} |
194 |
|
|
sibling.Next = n |
195 |
|
|
n.Prev = sibling |
196 |
|
|
sibling.Parent = n.Parent |
197 |
|
|
if sibling.Prev == nil { |
198 |
|
|
sibling.Parent.FirstChild = sibling |
199 |
|
|
} |
200 |
|
|
} |
201 |
|
|
|
202 |
|
|
// IsContainer returns true if 'n' can contain children. |
203 |
|
|
func (n *Node) IsContainer() bool { |
204 |
|
|
switch n.Type { |
205 |
|
|
case Document: |
206 |
|
|
fallthrough |
207 |
|
|
case BlockQuote: |
208 |
|
|
fallthrough |
209 |
|
|
case List: |
210 |
|
|
fallthrough |
211 |
|
|
case Item: |
212 |
|
|
fallthrough |
213 |
|
|
case Paragraph: |
214 |
|
|
fallthrough |
215 |
|
|
case Heading: |
216 |
|
|
fallthrough |
217 |
|
|
case Emph: |
218 |
|
|
fallthrough |
219 |
|
|
case Strong: |
220 |
|
|
fallthrough |
221 |
|
|
case Del: |
222 |
|
|
fallthrough |
223 |
|
|
case Link: |
224 |
|
|
fallthrough |
225 |
|
|
case Image: |
226 |
|
|
fallthrough |
227 |
|
|
case Table: |
228 |
|
|
fallthrough |
229 |
|
|
case TableHead: |
230 |
|
|
fallthrough |
231 |
|
|
case TableBody: |
232 |
|
|
fallthrough |
233 |
|
|
case TableRow: |
234 |
|
|
fallthrough |
235 |
|
|
case TableCell: |
236 |
|
|
return true |
237 |
|
|
default: |
238 |
|
|
return false |
239 |
|
|
} |
240 |
|
|
} |
241 |
|
|
|
242 |
|
|
// IsLeaf returns true if 'n' is a leaf node. |
243 |
|
|
func (n *Node) IsLeaf() bool { |
244 |
|
|
return !n.IsContainer() |
245 |
|
|
} |
246 |
|
|
|
247 |
|
|
func (n *Node) canContain(t NodeType) bool { |
248 |
|
|
if n.Type == List { |
249 |
|
|
return t == Item |
250 |
|
|
} |
251 |
|
|
if n.Type == Document || n.Type == BlockQuote || n.Type == Item { |
252 |
|
|
return t != Item |
253 |
|
|
} |
254 |
|
|
if n.Type == Table { |
255 |
|
|
return t == TableHead || t == TableBody |
256 |
|
|
} |
257 |
|
|
if n.Type == TableHead || n.Type == TableBody { |
258 |
|
|
return t == TableRow |
259 |
|
|
} |
260 |
|
|
if n.Type == TableRow { |
261 |
|
|
return t == TableCell |
262 |
|
|
} |
263 |
|
|
return false |
264 |
|
|
} |
265 |
|
|
|
266 |
|
|
// WalkStatus allows NodeVisitor to have some control over the tree traversal. |
267 |
|
|
// It is returned from NodeVisitor and different values allow Node.Walk to |
268 |
|
|
// decide which node to go to next. |
269 |
|
|
type WalkStatus int |
270 |
|
|
|
271 |
|
|
const ( |
272 |
|
|
// GoToNext is the default traversal of every node. |
273 |
|
|
GoToNext WalkStatus = iota |
274 |
|
|
// SkipChildren tells walker to skip all children of current node. |
275 |
|
|
SkipChildren |
276 |
|
|
// Terminate tells walker to terminate the traversal. |
277 |
|
|
Terminate |
278 |
|
|
) |
279 |
|
|
|
280 |
|
|
// NodeVisitor is a callback to be called when traversing the syntax tree. |
281 |
|
|
// Called twice for every node: once with entering=true when the branch is |
282 |
|
|
// first visited, then with entering=false after all the children are done. |
283 |
|
|
type NodeVisitor func(node *Node, entering bool) WalkStatus |
284 |
|
|
|
285 |
|
|
// Walk is a convenience method that instantiates a walker and starts a |
286 |
|
|
// traversal of subtree rooted at n. |
287 |
|
|
func (n *Node) Walk(visitor NodeVisitor) { |
288 |
|
|
w := newNodeWalker(n) |
289 |
|
|
for w.current != nil { |
290 |
|
|
status := visitor(w.current, w.entering) |
291 |
|
|
switch status { |
292 |
|
|
case GoToNext: |
293 |
|
|
w.next() |
294 |
|
|
case SkipChildren: |
295 |
|
|
w.entering = false |
296 |
|
|
w.next() |
297 |
|
|
case Terminate: |
298 |
|
|
return |
299 |
|
|
} |
300 |
|
|
} |
301 |
|
|
} |
302 |
|
|
|
303 |
|
|
type nodeWalker struct { |
304 |
|
|
current *Node |
305 |
|
|
root *Node |
306 |
|
|
entering bool |
307 |
|
|
} |
308 |
|
|
|
309 |
|
|
func newNodeWalker(root *Node) *nodeWalker { |
310 |
|
|
return &nodeWalker{ |
311 |
|
|
current: root, |
312 |
|
|
root: root, |
313 |
|
|
entering: true, |
314 |
|
|
} |
315 |
|
|
} |
316 |
|
|
|
317 |
|
|
func (nw *nodeWalker) next() { |
318 |
|
|
if (!nw.current.IsContainer() || !nw.entering) && nw.current == nw.root { |
319 |
|
|
nw.current = nil |
320 |
|
|
return |
321 |
|
|
} |
322 |
|
|
if nw.entering && nw.current.IsContainer() { |
323 |
|
|
if nw.current.FirstChild != nil { |
324 |
|
|
nw.current = nw.current.FirstChild |
325 |
|
|
nw.entering = true |
326 |
|
|
} else { |
327 |
|
|
nw.entering = false |
328 |
|
|
} |
329 |
|
|
} else if nw.current.Next == nil { |
330 |
|
|
nw.current = nw.current.Parent |
331 |
|
|
nw.entering = false |
332 |
|
|
} else { |
333 |
|
|
nw.current = nw.current.Next |
334 |
|
|
nw.entering = true |
335 |
|
|
} |
336 |
|
|
} |
337 |
|
|
|
338 |
|
|
func dump(ast *Node) { |
339 |
|
|
fmt.Println(dumpString(ast)) |
340 |
|
|
} |
341 |
|
|
|
342 |
|
|
func dumpR(ast *Node, depth int) string { |
343 |
|
|
if ast == nil { |
344 |
|
|
return "" |
345 |
|
|
} |
346 |
|
|
indent := bytes.Repeat([]byte("\t"), depth) |
347 |
|
|
content := ast.Literal |
348 |
|
|
if content == nil { |
349 |
|
|
content = ast.content |
350 |
|
|
} |
351 |
|
|
result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content) |
352 |
|
|
for n := ast.FirstChild; n != nil; n = n.Next { |
353 |
|
|
result += dumpR(n, depth+1) |
354 |
|
|
} |
355 |
|
|
return result |
356 |
|
|
} |
357 |
|
|
|
358 |
|
|
func dumpString(ast *Node) string { |
359 |
|
|
return dumpR(ast, 0) |
360 |
|
|
} |