all repos — grayfriday @ 8a4d4fa0cdedd8c2584f6fc48bb40ac4c15ebc47

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