all repos — grayfriday @ a1ae674f6fdc9265925c01731dc0fe6532fff546

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	Heading
 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	Heading:        "Heading",
 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	Footnote    *Node  // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
 88}
 89
 90// CodeBlockData contains fields relevant to a CodeBlock node type.
 91type CodeBlockData struct {
 92	IsFenced    bool   // Specifies whether it's a fenced code block or an indented one
 93	Info        []byte // This holds the info string
 94	FenceChar   byte
 95	FenceLength int
 96	FenceOffset int
 97}
 98
 99// TableCellData contains fields relevant to a TableCell node type.
100type TableCellData struct {
101	IsHeader bool           // This tells if it's under the header row
102	Align    CellAlignFlags // This holds the value for align attribute
103}
104
105// HeadingData contains fields relevant to a Heading node type.
106type HeadingData struct {
107	Level        int    // This holds the heading level number
108	HeadingID    string // This might hold heading ID, if present
109	IsTitleblock bool   // Specifies whether it's a title block
110}
111
112// Node is a single element in the abstract syntax tree of the parsed document.
113// It holds connections to the structurally neighboring nodes and, for certain
114// types of nodes, additional information that might be needed when rendering.
115type Node struct {
116	Type       NodeType // Determines the type of the node
117	Parent     *Node    // Points to the parent
118	FirstChild *Node    // Points to the first child, if any
119	LastChild  *Node    // Points to the last child, if any
120	Prev       *Node    // Previous sibling; nil if it's the first child
121	Next       *Node    // Next sibling; nil if it's the last child
122
123	Literal []byte // Text contents of the leaf nodes
124
125	HeadingData   // Populated if Type is Heading
126	ListData      // Populated if Type is List
127	CodeBlockData // Populated if Type is CodeBlock
128	LinkData      // Populated if Type is Link
129	TableCellData // Populated if Type is TableCell
130
131	content []byte // Markdown content of the block nodes
132	open    bool   // Specifies an open block node that has not been finished to process yet
133}
134
135// NewNode allocates a node of a specified type.
136func NewNode(typ NodeType) *Node {
137	return &Node{
138		Type: typ,
139		open: true,
140	}
141}
142
143func (n *Node) String() string {
144	ellipsis := ""
145	snippet := n.Literal
146	if len(snippet) > 16 {
147		snippet = snippet[:16]
148		ellipsis = "..."
149	}
150	return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
151}
152
153// Unlink removes node 'n' from the tree.
154// It panics if the node is nil.
155func (n *Node) Unlink() {
156	if n.Prev != nil {
157		n.Prev.Next = n.Next
158	} else if n.Parent != nil {
159		n.Parent.FirstChild = n.Next
160	}
161	if n.Next != nil {
162		n.Next.Prev = n.Prev
163	} else if n.Parent != nil {
164		n.Parent.LastChild = n.Prev
165	}
166	n.Parent = nil
167	n.Next = nil
168	n.Prev = nil
169}
170
171// AppendChild adds a node 'child' as a child of 'n'.
172// It panics if either node is nil.
173func (n *Node) AppendChild(child *Node) {
174	child.Unlink()
175	child.Parent = n
176	if n.LastChild != nil {
177		n.LastChild.Next = child
178		child.Prev = n.LastChild
179		n.LastChild = child
180	} else {
181		n.FirstChild = child
182		n.LastChild = child
183	}
184}
185
186// InsertBefore inserts 'sibling' immediately before 'n'.
187// It panics if either node is nil.
188func (n *Node) InsertBefore(sibling *Node) {
189	sibling.Unlink()
190	sibling.Prev = n.Prev
191	if sibling.Prev != nil {
192		sibling.Prev.Next = sibling
193	}
194	sibling.Next = n
195	n.Prev = sibling
196	sibling.Parent = n.Parent
197	if sibling.Prev == nil {
198		sibling.Parent.FirstChild = sibling
199	}
200}
201
202// IsContainer returns true if 'n' can contain children.
203func (n *Node) IsContainer() bool {
204	switch n.Type {
205	case Document:
206		fallthrough
207	case BlockQuote:
208		fallthrough
209	case List:
210		fallthrough
211	case Item:
212		fallthrough
213	case Paragraph:
214		fallthrough
215	case Heading:
216		fallthrough
217	case Emph:
218		fallthrough
219	case Strong:
220		fallthrough
221	case Del:
222		fallthrough
223	case Link:
224		fallthrough
225	case Image:
226		fallthrough
227	case Table:
228		fallthrough
229	case TableHead:
230		fallthrough
231	case TableBody:
232		fallthrough
233	case TableRow:
234		fallthrough
235	case TableCell:
236		return true
237	default:
238		return false
239	}
240}
241
242// IsLeaf returns true if 'n' is a leaf node.
243func (n *Node) IsLeaf() bool {
244	return !n.IsContainer()
245}
246
247func (n *Node) canContain(t NodeType) bool {
248	if n.Type == List {
249		return t == Item
250	}
251	if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
252		return t != Item
253	}
254	if n.Type == Table {
255		return t == TableHead || t == TableBody
256	}
257	if n.Type == TableHead || n.Type == TableBody {
258		return t == TableRow
259	}
260	if n.Type == TableRow {
261		return t == TableCell
262	}
263	return false
264}
265
266// WalkStatus allows NodeVisitor to have some control over the tree traversal.
267// It is returned from NodeVisitor and different values allow Node.Walk to
268// decide which node to go to next.
269type WalkStatus int
270
271const (
272	// GoToNext is the default traversal of every node.
273	GoToNext WalkStatus = iota
274	// SkipChildren tells walker to skip all children of current node.
275	SkipChildren
276	// Terminate tells walker to terminate the traversal.
277	Terminate
278)
279
280// NodeVisitor is a callback to be called when traversing the syntax tree.
281// Called twice for every node: once with entering=true when the branch is
282// first visited, then with entering=false after all the children are done.
283type NodeVisitor func(node *Node, entering bool) WalkStatus
284
285// Walk is a convenience method that instantiates a walker and starts a
286// traversal of subtree rooted at n.
287func (n *Node) Walk(visitor NodeVisitor) {
288	w := newNodeWalker(n)
289	for w.current != nil {
290		status := visitor(w.current, w.entering)
291		switch status {
292		case GoToNext:
293			w.next()
294		case SkipChildren:
295			w.entering = false
296			w.next()
297		case Terminate:
298			return
299		}
300	}
301}
302
303type nodeWalker struct {
304	current  *Node
305	root     *Node
306	entering bool
307}
308
309func newNodeWalker(root *Node) *nodeWalker {
310	return &nodeWalker{
311		current:  root,
312		root:     root,
313		entering: true,
314	}
315}
316
317func (nw *nodeWalker) next() {
318	if (!nw.current.IsContainer() || !nw.entering) && nw.current == nw.root {
319		nw.current = nil
320		return
321	}
322	if nw.entering && nw.current.IsContainer() {
323		if nw.current.FirstChild != nil {
324			nw.current = nw.current.FirstChild
325			nw.entering = true
326		} else {
327			nw.entering = false
328		}
329	} else if nw.current.Next == nil {
330		nw.current = nw.current.Parent
331		nw.entering = false
332	} else {
333		nw.current = nw.current.Next
334		nw.entering = true
335	}
336}
337
338func dump(ast *Node) {
339	fmt.Println(dumpString(ast))
340}
341
342func dumpR(ast *Node, depth int) string {
343	if ast == nil {
344		return ""
345	}
346	indent := bytes.Repeat([]byte("\t"), depth)
347	content := ast.Literal
348	if content == nil {
349		content = ast.content
350	}
351	result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
352	for n := ast.FirstChild; n != nil; n = n.Next {
353		result += dumpR(n, depth+1)
354	}
355	return result
356}
357
358func dumpString(ast *Node) string {
359	return dumpR(ast, 0)
360}