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