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 |
} |