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