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 Flags 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 Node struct {
83 Type NodeType
84 Parent *Node
85 FirstChild *Node
86 LastChild *Node
87 Prev *Node // prev sibling
88 Next *Node // next sibling
89
90 content []byte
91 open bool
92
93 Level uint32 // If Type == Header, this holds the heading level number
94 Literal []byte
95
96 ListData *ListData // If Type == List, this holds list info
97 // TODO: move these fenced code block fields to a substruct
98 IsFenced bool // If Type == CodeBlock, specifies whether it's a fenced code block or an indented one
99 Info []byte // If Type == CodeBlock, this holds the info string
100 FenceChar byte
101 FenceLength uint32
102 FenceOffset uint32
103 LinkData // If Type == Link, this holds link info
104 HeaderID string // If Type == Header, this might hold header ID, if present
105 IsTitleblock bool
106 IsHeader bool // If Type == TableCell, this tells if it's under the header row
107
108 // TODO: convert the int to a proper type
109 Align int // If Type == TableCell, this holds the value for align attribute
110}
111
112func NewNode(typ NodeType) *Node {
113 return &Node{
114 Type: typ,
115 open: true,
116 }
117}
118
119func (n *Node) unlink() {
120 if n.Prev != nil {
121 n.Prev.Next = n.Next
122 } else if n.Parent != nil {
123 n.Parent.FirstChild = n.Next
124 }
125 if n.Next != nil {
126 n.Next.Prev = n.Prev
127 } else if n.Parent != nil {
128 n.Parent.LastChild = n.Prev
129 }
130 n.Parent = nil
131 n.Next = nil
132 n.Prev = nil
133}
134
135func (n *Node) appendChild(child *Node) {
136 child.unlink()
137 child.Parent = n
138 if n.LastChild != nil {
139 n.LastChild.Next = child
140 child.Prev = n.LastChild
141 n.LastChild = child
142 } else {
143 n.FirstChild = child
144 n.LastChild = child
145 }
146}
147
148func (n *Node) isContainer() bool {
149 switch n.Type {
150 case Document:
151 fallthrough
152 case BlockQuote:
153 fallthrough
154 case List:
155 fallthrough
156 case Item:
157 fallthrough
158 case Paragraph:
159 fallthrough
160 case Header:
161 fallthrough
162 case Emph:
163 fallthrough
164 case Strong:
165 fallthrough
166 case Del:
167 fallthrough
168 case Link:
169 fallthrough
170 case Image:
171 fallthrough
172 case Table:
173 fallthrough
174 case TableHead:
175 fallthrough
176 case TableBody:
177 fallthrough
178 case TableRow:
179 fallthrough
180 case TableCell:
181 return true
182 default:
183 return false
184 }
185 return false
186}
187
188func (n *Node) canContain(t NodeType) bool {
189 if n.Type == List {
190 return t == Item
191 }
192 if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
193 return t != Item
194 }
195 if n.Type == Table {
196 return t == TableHead || t == TableBody
197 }
198 if n.Type == TableHead || n.Type == TableBody {
199 return t == TableRow
200 }
201 if n.Type == TableRow {
202 return t == TableCell
203 }
204 return false
205}
206
207type NodeWalker struct {
208 current *Node
209 root *Node
210 entering bool
211}
212
213func NewNodeWalker(root *Node) *NodeWalker {
214 return &NodeWalker{
215 current: root,
216 root: nil,
217 entering: true,
218 }
219}
220
221func (nw *NodeWalker) next() (*Node, bool) {
222 if nw.current == nil {
223 return nil, false
224 }
225 if nw.root == nil {
226 nw.root = nw.current
227 return nw.current, nw.entering
228 }
229 if nw.entering && nw.current.isContainer() {
230 if nw.current.FirstChild != nil {
231 nw.current = nw.current.FirstChild
232 nw.entering = true
233 } else {
234 nw.entering = false
235 }
236 } else if nw.current.Next == nil {
237 nw.current = nw.current.Parent
238 nw.entering = false
239 } else {
240 nw.current = nw.current.Next
241 nw.entering = true
242 }
243 if nw.current == nw.root {
244 return nil, false
245 }
246 return nw.current, nw.entering
247}
248
249func (nw *NodeWalker) resumeAt(node *Node, entering bool) {
250 nw.current = node
251 nw.entering = entering
252}
253
254func ForEachNode(root *Node, f func(node *Node, entering bool)) {
255 walker := NewNodeWalker(root)
256 node, entering := walker.next()
257 for node != nil {
258 f(node, entering)
259 node, entering = walker.next()
260 }
261}
262
263func dump(ast *Node) {
264 fmt.Println(dumpString(ast))
265}
266
267func dump_r(ast *Node, depth int) string {
268 if ast == nil {
269 return ""
270 }
271 indent := bytes.Repeat([]byte("\t"), depth)
272 content := ast.Literal
273 if content == nil {
274 content = ast.content
275 }
276 result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
277 for n := ast.FirstChild; n != nil; n = n.Next {
278 result += dump_r(n, depth+1)
279 }
280 return result
281}
282
283func dumpString(ast *Node) string {
284 return dump_r(ast, 0)
285}