all repos — grayfriday @ da0664143874c5ec373f1dc400bd412688002f96

blackfriday fork with a few changes

node.go (view raw)

  1package blackfriday
  2
  3import (
  4	"bytes"
  5	"fmt"
  6)
  7
  8// NodeType specifies a type of a single node of a syntax tree. Usually one
  9// node (and its type) corresponds to a single markdown feature, e.g. emphasis
 10// or code block.
 11type NodeType int
 12
 13// Constants for identifying different types of nodes. See NodeType.
 14const (
 15	Document NodeType = iota
 16	BlockQuote
 17	List
 18	Item
 19	Paragraph
 20	Header
 21	HorizontalRule
 22	Emph
 23	Strong
 24	Del
 25	Link
 26	Image
 27	Text
 28	HTMLBlock
 29	CodeBlock
 30	Softbreak
 31	Hardbreak
 32	Code
 33	HTMLSpan
 34	Table
 35	TableCell
 36	TableHead
 37	TableBody
 38	TableRow
 39)
 40
 41var nodeTypeNames = []string{
 42	Document:       "Document",
 43	BlockQuote:     "BlockQuote",
 44	List:           "List",
 45	Item:           "Item",
 46	Paragraph:      "Paragraph",
 47	Header:         "Header",
 48	HorizontalRule: "HorizontalRule",
 49	Emph:           "Emph",
 50	Strong:         "Strong",
 51	Del:            "Del",
 52	Link:           "Link",
 53	Image:          "Image",
 54	Text:           "Text",
 55	HTMLBlock:      "HTMLBlock",
 56	CodeBlock:      "CodeBlock",
 57	Softbreak:      "Softbreak",
 58	Hardbreak:      "Hardbreak",
 59	Code:           "Code",
 60	HTMLSpan:       "HTMLSpan",
 61	Table:          "Table",
 62	TableCell:      "TableCell",
 63	TableHead:      "TableHead",
 64	TableBody:      "TableBody",
 65	TableRow:       "TableRow",
 66}
 67
 68func (t NodeType) String() string {
 69	return nodeTypeNames[t]
 70}
 71
 72// ListData contains fields relevant to a List and Item node type.
 73type ListData struct {
 74	ListFlags       ListType
 75	Tight           bool   // Skip <p>s around list item data if true
 76	BulletChar      byte   // '*', '+' or '-' in bullet lists
 77	Delimiter       byte   // '.' or ')' after the number in ordered lists
 78	RefLink         []byte // If not nil, turns this list item into a footnote item and triggers different rendering
 79	IsFootnotesList bool   // This is a list of footnotes
 80}
 81
 82// LinkData contains fields relevant to a Link node type.
 83type LinkData struct {
 84	Destination []byte // Destination is what goes into a href
 85	Title       []byte // Title is the tooltip thing that goes in a title attribute
 86	NoteID      int    // NoteID contains a serial number of a footnote, zero if it's not a footnote
 87}
 88
 89// CodeBlockData contains fields relevant to a CodeBlock node type.
 90type CodeBlockData struct {
 91	IsFenced    bool   // Specifies whether it's a fenced code block or an indented one
 92	Info        []byte // This holds the info string
 93	FenceChar   byte
 94	FenceLength int
 95	FenceOffset int
 96}
 97
 98// TableCellData contains fields relevant to a TableCell node type.
 99type TableCellData struct {
100	IsHeader bool           // This tells if it's under the header row
101	Align    CellAlignFlags // This holds the value for align attribute
102}
103
104// HeaderData contains fields relevant to a Header node type.
105type HeaderData struct {
106	Level        int    // This holds the heading level number
107	HeaderID     string // This might hold header ID, if present
108	IsTitleblock bool   // Specifies whether it's a title block
109}
110
111// Node is a single element in the abstract syntax tree of the parsed document.
112// It holds connections to the structurally neighboring nodes and, for certain
113// types of nodes, additional information that might be needed when rendering.
114type Node struct {
115	Type       NodeType // Determines the type of the node
116	Parent     *Node    // Points to the parent
117	FirstChild *Node    // Points to the first child, if any
118	LastChild  *Node    // Points to the last child, if any
119	Prev       *Node    // Previous sibling; nil if it's the first child
120	Next       *Node    // Next sibling; nil if it's the last child
121
122	Literal []byte // Text contents of the leaf nodes
123
124	HeaderData    // Populated if Type is Header
125	ListData      // Populated if Type is List
126	CodeBlockData // Populated if Type is CodeBlock
127	LinkData      // Populated if Type is Link
128	TableCellData // Populated if Type is TableCell
129
130	content []byte // Markdown content of the block nodes
131	open    bool   // Specifies an open block node that has not been finished to process yet
132}
133
134// NewNode allocates a node of a specified type.
135func NewNode(typ NodeType) *Node {
136	return &Node{
137		Type: typ,
138		open: true,
139	}
140}
141
142func (n *Node) String() string {
143	ellipsis := ""
144	snippet := n.Literal
145	if len(snippet) > 16 {
146		snippet = snippet[:16]
147		ellipsis = "..."
148	}
149	return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
150}
151
152// Unlink removes node 'n' from the tree.
153// It panics if the node is nil.
154func (n *Node) Unlink() {
155	if n.Prev != nil {
156		n.Prev.Next = n.Next
157	} else if n.Parent != nil {
158		n.Parent.FirstChild = n.Next
159	}
160	if n.Next != nil {
161		n.Next.Prev = n.Prev
162	} else if n.Parent != nil {
163		n.Parent.LastChild = n.Prev
164	}
165	n.Parent = nil
166	n.Next = nil
167	n.Prev = nil
168}
169
170// AppendChild adds a node 'child' as a child of 'n'.
171// It panics if either node is nil.
172func (n *Node) AppendChild(child *Node) {
173	child.Unlink()
174	child.Parent = n
175	if n.LastChild != nil {
176		n.LastChild.Next = child
177		child.Prev = n.LastChild
178		n.LastChild = child
179	} else {
180		n.FirstChild = child
181		n.LastChild = child
182	}
183}
184
185// InsertBefore inserts 'sibling' immediately before 'n'.
186// It panics if either node is nil.
187func (n *Node) InsertBefore(sibling *Node) {
188	sibling.Unlink()
189	sibling.Prev = n.Prev
190	if sibling.Prev != nil {
191		sibling.Prev.Next = sibling
192	}
193	sibling.Next = n
194	n.Prev = sibling
195	sibling.Parent = n.Parent
196	if sibling.Prev == nil {
197		sibling.Parent.FirstChild = sibling
198	}
199}
200
201func (n *Node) isContainer() bool {
202	switch n.Type {
203	case Document:
204		fallthrough
205	case BlockQuote:
206		fallthrough
207	case List:
208		fallthrough
209	case Item:
210		fallthrough
211	case Paragraph:
212		fallthrough
213	case Header:
214		fallthrough
215	case Emph:
216		fallthrough
217	case Strong:
218		fallthrough
219	case Del:
220		fallthrough
221	case Link:
222		fallthrough
223	case Image:
224		fallthrough
225	case Table:
226		fallthrough
227	case TableHead:
228		fallthrough
229	case TableBody:
230		fallthrough
231	case TableRow:
232		fallthrough
233	case TableCell:
234		return true
235	default:
236		return false
237	}
238}
239
240func (n *Node) canContain(t NodeType) bool {
241	if n.Type == List {
242		return t == Item
243	}
244	if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
245		return t != Item
246	}
247	if n.Type == Table {
248		return t == TableHead || t == TableBody
249	}
250	if n.Type == TableHead || n.Type == TableBody {
251		return t == TableRow
252	}
253	if n.Type == TableRow {
254		return t == TableCell
255	}
256	return false
257}
258
259// WalkStatus allows NodeVisitor to have some control over the tree traversal.
260// It is returned from NodeVisitor and different values allow Node.Walk to
261// decide which node to go to next.
262type WalkStatus int
263
264const (
265	// GoToNext is the default traversal of every node.
266	GoToNext WalkStatus = iota
267	// SkipChildren tells walker to skip all children of current node.
268	SkipChildren
269	// Terminate tells walker to terminate the traversal.
270	Terminate
271)
272
273// NodeVisitor is a callback to be called when traversing the syntax tree.
274// Called twice for every node: once with entering=true when the branch is
275// first visited, then with entering=false after all the children are done.
276type NodeVisitor func(node *Node, entering bool) WalkStatus
277
278// Walk is a convenience method that instantiates a walker and starts a
279// traversal of subtree rooted at n.
280func (root *Node) Walk(visitor NodeVisitor) {
281	w := newNodeWalker(root)
282	for w.current != nil {
283		status := visitor(w.current, w.entering)
284		switch status {
285		case GoToNext:
286			w.next()
287		case SkipChildren:
288			w.entering = false
289			w.next()
290		case Terminate:
291			return
292		}
293	}
294}
295
296type nodeWalker struct {
297	current  *Node
298	root     *Node
299	entering bool
300}
301
302func newNodeWalker(root *Node) *nodeWalker {
303	return &nodeWalker{
304		current:  root,
305		root:     root,
306		entering: true,
307	}
308}
309
310func (nw *nodeWalker) next() {
311	if !nw.entering && nw.current == nw.root {
312		nw.current = nil
313		return
314	}
315	if nw.entering && nw.current.isContainer() {
316		if nw.current.FirstChild != nil {
317			nw.current = nw.current.FirstChild
318			nw.entering = true
319		} else {
320			nw.entering = false
321		}
322	} else if nw.current.Next == nil {
323		nw.current = nw.current.Parent
324		nw.entering = false
325	} else {
326		nw.current = nw.current.Next
327		nw.entering = true
328	}
329}
330
331func dump(ast *Node) {
332	fmt.Println(dumpString(ast))
333}
334
335func dumpR(ast *Node, depth int) string {
336	if ast == nil {
337		return ""
338	}
339	indent := bytes.Repeat([]byte("\t"), depth)
340	content := ast.Literal
341	if content == nil {
342		content = ast.content
343	}
344	result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
345	for n := ast.FirstChild; n != nil; n = n.Next {
346		result += dumpR(n, depth+1)
347	}
348	return result
349}
350
351func dumpString(ast *Node) string {
352	return dumpR(ast, 0)
353}