all repos — grayfriday @ 4a7ff562a7e3c58311b4678169a0ae9b0ecc2e59

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