all repos — grayfriday @ f90a576a05b8a97dd6e2936e613eadb48c57112a

blackfriday fork with a few changes

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 int
 87	FenceOffset int
 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        int    // 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
101// Node is a single element in the abstract syntax tree of the parsed document.
102// It holds connections to the structurally neighboring nodes and, for certain
103// types of nodes, additional information that might be needed when rendering.
104type Node struct {
105	Type       NodeType // Determines the type of the node
106	Parent     *Node    // Points to the parent
107	FirstChild *Node    // Points to the first child, if any
108	LastChild  *Node    // Points to the last child, if any
109	Prev       *Node    // Previous sibling; nil if it's the first child
110	Next       *Node    // Next sibling; nil if it's the last child
111
112	Literal []byte // Text contents of the leaf nodes
113
114	HeaderData    // Populated if Type == Header
115	ListData      // Populated if Type == List
116	CodeBlockData // Populated if Type == CodeBlock
117	LinkData      // Populated if Type == Link
118	TableCellData // Populated if Type == TableCell
119
120	content []byte // Markdown content of the block nodes
121	open    bool   // Specifies an open block node that has not been finished to process yet
122}
123
124func NewNode(typ NodeType) *Node {
125	return &Node{
126		Type: typ,
127		open: true,
128	}
129}
130
131func (n *Node) unlink() {
132	if n.Prev != nil {
133		n.Prev.Next = n.Next
134	} else if n.Parent != nil {
135		n.Parent.FirstChild = n.Next
136	}
137	if n.Next != nil {
138		n.Next.Prev = n.Prev
139	} else if n.Parent != nil {
140		n.Parent.LastChild = n.Prev
141	}
142	n.Parent = nil
143	n.Next = nil
144	n.Prev = nil
145}
146
147func (n *Node) appendChild(child *Node) {
148	child.unlink()
149	child.Parent = n
150	if n.LastChild != nil {
151		n.LastChild.Next = child
152		child.Prev = n.LastChild
153		n.LastChild = child
154	} else {
155		n.FirstChild = child
156		n.LastChild = child
157	}
158}
159
160func (n *Node) insertBefore(sibling *Node) {
161	sibling.unlink()
162	sibling.Prev = n.Prev
163	if sibling.Prev != nil {
164		sibling.Prev.Next = sibling
165	}
166	sibling.Next = n
167	n.Prev = sibling
168	sibling.Parent = n.Parent
169	if sibling.Prev == nil {
170		sibling.Parent.FirstChild = sibling
171	}
172}
173
174func (n *Node) isContainer() bool {
175	switch n.Type {
176	case Document:
177		fallthrough
178	case BlockQuote:
179		fallthrough
180	case List:
181		fallthrough
182	case Item:
183		fallthrough
184	case Paragraph:
185		fallthrough
186	case Header:
187		fallthrough
188	case Emph:
189		fallthrough
190	case Strong:
191		fallthrough
192	case Del:
193		fallthrough
194	case Link:
195		fallthrough
196	case Image:
197		fallthrough
198	case Table:
199		fallthrough
200	case TableHead:
201		fallthrough
202	case TableBody:
203		fallthrough
204	case TableRow:
205		fallthrough
206	case TableCell:
207		return true
208	default:
209		return false
210	}
211	return false
212}
213
214func (n *Node) canContain(t NodeType) bool {
215	if n.Type == List {
216		return t == Item
217	}
218	if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
219		return t != Item
220	}
221	if n.Type == Table {
222		return t == TableHead || t == TableBody
223	}
224	if n.Type == TableHead || n.Type == TableBody {
225		return t == TableRow
226	}
227	if n.Type == TableRow {
228		return t == TableCell
229	}
230	return false
231}
232
233// WalkStatus allows NodeVisitor to have some control over the tree traversal.
234// It is returned from NodeVisitor and different values allow Node.Walk to
235// decide which node to go to next.
236type WalkStatus int
237
238const (
239	GoToNext     WalkStatus = iota // The default traversal of every node.
240	SkipChildren                   // Skips all children of current node.
241	Terminate                      // Terminates the traversal.
242)
243
244// NodeVisitor is a callback to be called when traversing the syntax tree.
245// Called twice for every node: once with entering=true when the branch is
246// first visited, then with entering=false after all the children are done.
247type NodeVisitor func(node *Node, entering bool) WalkStatus
248
249func (root *Node) Walk(visitor NodeVisitor) {
250	walker := NewNodeWalker(root)
251	node, entering := walker.next()
252	for node != nil {
253		status := visitor(node, entering)
254		switch status {
255		case GoToNext:
256			node, entering = walker.next()
257		case SkipChildren:
258			node, entering = walker.resumeAt(node, false)
259		case Terminate:
260			return
261		}
262	}
263}
264
265type NodeWalker struct {
266	current  *Node
267	root     *Node
268	entering bool
269}
270
271func NewNodeWalker(root *Node) *NodeWalker {
272	return &NodeWalker{
273		current:  root,
274		root:     nil,
275		entering: true,
276	}
277}
278
279func (nw *NodeWalker) next() (*Node, bool) {
280	if nw.current == nil {
281		return nil, false
282	}
283	if nw.root == nil {
284		nw.root = nw.current
285		return nw.current, nw.entering
286	}
287	if nw.entering && nw.current.isContainer() {
288		if nw.current.FirstChild != nil {
289			nw.current = nw.current.FirstChild
290			nw.entering = true
291		} else {
292			nw.entering = false
293		}
294	} else if nw.current.Next == nil {
295		nw.current = nw.current.Parent
296		nw.entering = false
297	} else {
298		nw.current = nw.current.Next
299		nw.entering = true
300	}
301	if nw.current == nw.root {
302		return nil, false
303	}
304	return nw.current, nw.entering
305}
306
307func (nw *NodeWalker) resumeAt(node *Node, entering bool) (*Node, bool) {
308	nw.current = node
309	nw.entering = entering
310	return nw.next()
311}
312
313func dump(ast *Node) {
314	fmt.Println(dumpString(ast))
315}
316
317func dump_r(ast *Node, depth int) string {
318	if ast == nil {
319		return ""
320	}
321	indent := bytes.Repeat([]byte("\t"), depth)
322	content := ast.Literal
323	if content == nil {
324		content = ast.content
325	}
326	result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
327	for n := ast.FirstChild; n != nil; n = n.Next {
328		result += dump_r(n, depth+1)
329	}
330	return result
331}
332
333func dumpString(ast *Node) string {
334	return dump_r(ast, 0)
335}