node.go (view raw)
1package blackfriday
2
3import (
4 "bytes"
5 "fmt"
6)
7
8type NodeType int
9
10const (
11 Document NodeType = iota
12 BlockQuote
13 List
14 Item
15 Paragraph
16 Header
17 HorizontalRule
18 Emph
19 Strong
20 Del
21 Link
22 Image
23 Text
24 HtmlBlock
25 CodeBlock
26 Softbreak
27 Hardbreak
28 Code
29 HtmlSpan
30 Table
31 TableCell
32 TableHead
33 TableBody
34 TableRow
35)
36
37var nodeTypeNames = []string{
38 Document: "Document",
39 BlockQuote: "BlockQuote",
40 List: "List",
41 Item: "Item",
42 Paragraph: "Paragraph",
43 Header: "Header",
44 HorizontalRule: "HorizontalRule",
45 Emph: "Emph",
46 Strong: "Strong",
47 Del: "Del",
48 Link: "Link",
49 Image: "Image",
50 Text: "Text",
51 HtmlBlock: "HtmlBlock",
52 CodeBlock: "CodeBlock",
53 Softbreak: "Softbreak",
54 Hardbreak: "Hardbreak",
55 Code: "Code",
56 HtmlSpan: "HtmlSpan",
57 Table: "Table",
58 TableCell: "TableCell",
59 TableHead: "TableHead",
60 TableBody: "TableBody",
61 TableRow: "TableRow",
62}
63
64func (t NodeType) String() string {
65 return nodeTypeNames[t]
66}
67
68type ListData struct {
69 ListFlags ListType
70 Tight bool // Skip <p>s around list item data if true
71 BulletChar byte // '*', '+' or '-' in bullet lists
72 Delimiter byte // '.' or ')' after the number in ordered lists
73 RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
74}
75
76type LinkData struct {
77 Destination []byte
78 Title []byte
79 NoteID int
80}
81
82type CodeBlockData struct {
83 IsFenced bool // Specifies whether it's a fenced code block or an indented one
84 Info []byte // This holds the info string
85 FenceChar byte
86 FenceLength uint32
87 FenceOffset uint32
88}
89
90type TableCellData struct {
91 IsHeader bool // This tells if it's under the header row
92 Align CellAlignFlags // This holds the value for align attribute
93}
94
95type HeaderData struct {
96 Level uint32 // This holds the heading level number
97 HeaderID string // This might hold header ID, if present
98 IsTitleblock bool // Specifies whether it's a title block
99}
100
101type Node struct {
102 Type NodeType
103 Parent *Node
104 FirstChild *Node
105 LastChild *Node
106 Prev *Node // prev sibling
107 Next *Node // next sibling
108
109 content []byte
110 open bool
111
112 Literal []byte
113
114 HeaderData // If Type == Header, this holds its properties
115 ListData // If Type == List, this holds list info
116 CodeBlockData // If Type == CodeBlock, this holds its properties
117 LinkData // If Type == Link, this holds link info
118 TableCellData // If Type == TableCell, this holds its properties
119}
120
121func NewNode(typ NodeType) *Node {
122 return &Node{
123 Type: typ,
124 open: true,
125 }
126}
127
128func (n *Node) unlink() {
129 if n.Prev != nil {
130 n.Prev.Next = n.Next
131 } else if n.Parent != nil {
132 n.Parent.FirstChild = n.Next
133 }
134 if n.Next != nil {
135 n.Next.Prev = n.Prev
136 } else if n.Parent != nil {
137 n.Parent.LastChild = n.Prev
138 }
139 n.Parent = nil
140 n.Next = nil
141 n.Prev = nil
142}
143
144func (n *Node) appendChild(child *Node) {
145 child.unlink()
146 child.Parent = n
147 if n.LastChild != nil {
148 n.LastChild.Next = child
149 child.Prev = n.LastChild
150 n.LastChild = child
151 } else {
152 n.FirstChild = child
153 n.LastChild = child
154 }
155}
156
157func (n *Node) isContainer() bool {
158 switch n.Type {
159 case Document:
160 fallthrough
161 case BlockQuote:
162 fallthrough
163 case List:
164 fallthrough
165 case Item:
166 fallthrough
167 case Paragraph:
168 fallthrough
169 case Header:
170 fallthrough
171 case Emph:
172 fallthrough
173 case Strong:
174 fallthrough
175 case Del:
176 fallthrough
177 case Link:
178 fallthrough
179 case Image:
180 fallthrough
181 case Table:
182 fallthrough
183 case TableHead:
184 fallthrough
185 case TableBody:
186 fallthrough
187 case TableRow:
188 fallthrough
189 case TableCell:
190 return true
191 default:
192 return false
193 }
194 return false
195}
196
197func (n *Node) canContain(t NodeType) bool {
198 if n.Type == List {
199 return t == Item
200 }
201 if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
202 return t != Item
203 }
204 if n.Type == Table {
205 return t == TableHead || t == TableBody
206 }
207 if n.Type == TableHead || n.Type == TableBody {
208 return t == TableRow
209 }
210 if n.Type == TableRow {
211 return t == TableCell
212 }
213 return false
214}
215
216type NodeWalker struct {
217 current *Node
218 root *Node
219 entering bool
220}
221
222func NewNodeWalker(root *Node) *NodeWalker {
223 return &NodeWalker{
224 current: root,
225 root: nil,
226 entering: true,
227 }
228}
229
230func (nw *NodeWalker) next() (*Node, bool) {
231 if nw.current == nil {
232 return nil, false
233 }
234 if nw.root == nil {
235 nw.root = nw.current
236 return nw.current, nw.entering
237 }
238 if nw.entering && nw.current.isContainer() {
239 if nw.current.FirstChild != nil {
240 nw.current = nw.current.FirstChild
241 nw.entering = true
242 } else {
243 nw.entering = false
244 }
245 } else if nw.current.Next == nil {
246 nw.current = nw.current.Parent
247 nw.entering = false
248 } else {
249 nw.current = nw.current.Next
250 nw.entering = true
251 }
252 if nw.current == nw.root {
253 return nil, false
254 }
255 return nw.current, nw.entering
256}
257
258func (nw *NodeWalker) resumeAt(node *Node, entering bool) {
259 nw.current = node
260 nw.entering = entering
261}
262
263func ForEachNode(root *Node, f func(node *Node, entering bool)) {
264 walker := NewNodeWalker(root)
265 node, entering := walker.next()
266 for node != nil {
267 f(node, entering)
268 node, entering = walker.next()
269 }
270}
271
272func dump(ast *Node) {
273 fmt.Println(dumpString(ast))
274}
275
276func dump_r(ast *Node, depth int) string {
277 if ast == nil {
278 return ""
279 }
280 indent := bytes.Repeat([]byte("\t"), depth)
281 content := ast.Literal
282 if content == nil {
283 content = ast.content
284 }
285 result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
286 for n := ast.FirstChild; n != nil; n = n.Next {
287 result += dump_r(n, depth+1)
288 }
289 return result
290}
291
292func dumpString(ast *Node) string {
293 return dump_r(ast, 0)
294}