| package blackfriday | |
| import ( | |
| "bytes" | |
| "fmt" | |
| ) | |
| // NodeType specifies a type of a single node of a syntax tree. Usually one | |
| // node (and its type) corresponds to a single markdown feature, e.g. emphasis | |
| // or code block. | |
| type NodeType int | |
| // Constants for identifying different types of nodes. See NodeType. | |
| const ( | |
| Document NodeType = iota | |
| BlockQuote | |
| List | |
| Item | |
| Paragraph | |
| Heading | |
| HorizontalRule | |
| Emph | |
| Strong | |
| Del | |
| Link | |
| Image | |
| Text | |
| HTMLBlock | |
| CodeBlock | |
| Softbreak | |
| Hardbreak | |
| Code | |
| HTMLSpan | |
| Table | |
| TableCell | |
| TableHead | |
| TableBody | |
| TableRow | |
| ) | |
| var nodeTypeNames = []string{ | |
| Document: "Document", | |
| BlockQuote: "BlockQuote", | |
| List: "List", | |
| Item: "Item", | |
| Paragraph: "Paragraph", | |
| Heading: "Heading", | |
| HorizontalRule: "HorizontalRule", | |
| Emph: "Emph", | |
| Strong: "Strong", | |
| Del: "Del", | |
| Link: "Link", | |
| Image: "Image", | |
| Text: "Text", | |
| HTMLBlock: "HTMLBlock", | |
| CodeBlock: "CodeBlock", | |
| Softbreak: "Softbreak", | |
| Hardbreak: "Hardbreak", | |
| Code: "Code", | |
| HTMLSpan: "HTMLSpan", | |
| Table: "Table", | |
| TableCell: "TableCell", | |
| TableHead: "TableHead", | |
| TableBody: "TableBody", | |
| TableRow: "TableRow", | |
| } | |
| func (t NodeType) String() string { | |
| return nodeTypeNames[t] | |
| } | |
| // ListData contains fields relevant to a List and Item node type. | |
| type ListData struct { | |
| ListFlags ListType | |
| Tight bool // Skip <p>s around list item data if true | |
| BulletChar byte // '*', '+' or '-' in bullet lists | |
| Delimiter byte // '.' or ')' after the number in ordered lists | |
| RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering | |
| IsFootnotesList bool // This is a list of footnotes | |
| } | |
| // LinkData contains fields relevant to a Link node type. | |
| type LinkData struct { | |
| Destination []byte // Destination is what goes into a href | |
| Title []byte // Title is the tooltip thing that goes in a title attribute | |
| NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote | |
| Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil. | |
| } | |
| // CodeBlockData contains fields relevant to a CodeBlock node type. | |
| type CodeBlockData struct { | |
| IsFenced bool // Specifies whether it's a fenced code block or an indented one | |
| Info []byte // This holds the info string | |
| FenceChar byte | |
| FenceLength int | |
| FenceOffset int | |
| } | |
| // TableCellData contains fields relevant to a TableCell node type. | |
| type TableCellData struct { | |
| IsHeader bool // This tells if it's under the header row | |
| Align CellAlignFlags // This holds the value for align attribute | |
| } | |
| // HeadingData contains fields relevant to a Heading node type. | |
| type HeadingData struct { | |
| Level int // This holds the heading level number | |
| HeadingID string // This might hold heading ID, if present | |
| IsTitleblock bool // Specifies whether it's a title block | |
| } | |
| // Node is a single element in the abstract syntax tree of the parsed document. | |
| // It holds connections to the structurally neighboring nodes and, for certain | |
| // types of nodes, additional information that might be needed when rendering. | |
| type Node struct { | |
| Type NodeType // Determines the type of the node | |
| Parent *Node // Points to the parent | |
| FirstChild *Node // Points to the first child, if any | |
| LastChild *Node // Points to the last child, if any | |
| Prev *Node // Previous sibling; nil if it's the first child | |
| Next *Node // Next sibling; nil if it's the last child | |
| Literal []byte // Text contents of the leaf nodes | |
| HeadingData // Populated if Type is Heading | |
| ListData // Populated if Type is List | |
| CodeBlockData // Populated if Type is CodeBlock | |
| LinkData // Populated if Type is Link | |
| TableCellData // Populated if Type is TableCell | |
| content []byte // Markdown content of the block nodes | |
| open bool // Specifies an open block node that has not been finished to process yet | |
| } | |
| // NewNode allocates a node of a specified type. | |
| func NewNode(typ NodeType) *Node { | |
| return &Node{ | |
| Type: typ, | |
| open: true, | |
| } | |
| } | |
| func (n *Node) String() string { | |
| ellipsis := "" | |
| snippet := n.Literal | |
| if len(snippet) > 16 { | |
| snippet = snippet[:16] | |
| ellipsis = "..." | |
| } | |
| return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis) | |
| } | |
| // Unlink removes node 'n' from the tree. | |
| // It panics if the node is nil. | |
| func (n *Node) Unlink() { | |
| if n.Prev != nil { | |
| n.Prev.Next = n.Next | |
| } else if n.Parent != nil { | |
| n.Parent.FirstChild = n.Next | |
| } | |
| if n.Next != nil { | |
| n.Next.Prev = n.Prev | |
| } else if n.Parent != nil { | |
| n.Parent.LastChild = n.Prev | |
| } | |
| n.Parent = nil | |
| n.Next = nil | |
| n.Prev = nil | |
| } | |
| // AppendChild adds a node 'child' as a child of 'n'. | |
| // It panics if either node is nil. | |
| func (n *Node) AppendChild(child *Node) { | |
| child.Unlink() | |
| child.Parent = n | |
| if n.LastChild != nil { | |
| n.LastChild.Next = child | |
| child.Prev = n.LastChild | |
| n.LastChild = child | |
| } else { | |
| n.FirstChild = child | |
| n.LastChild = child | |
| } | |
| } | |
| // InsertBefore inserts 'sibling' immediately before 'n'. | |
| // It panics if either node is nil. | |
| func (n *Node) InsertBefore(sibling *Node) { | |
| sibling.Unlink() | |
| sibling.Prev = n.Prev | |
| if sibling.Prev != nil { | |
| sibling.Prev.Next = sibling | |
| } | |
| sibling.Next = n | |
| n.Prev = sibling | |
| sibling.Parent = n.Parent | |
| if sibling.Prev == nil { | |
| sibling.Parent.FirstChild = sibling | |
| } | |
| } | |
| // IsContainer returns true if 'n' can contain children. | |
| func (n *Node) IsContainer() bool { | |
| switch n.Type { | |
| case Document: | |
| fallthrough | |
| case BlockQuote: | |
| fallthrough | |
| case List: | |
| fallthrough | |
| case Item: | |
| fallthrough | |
| case Paragraph: | |
| fallthrough | |
| case Heading: | |
| fallthrough | |
| case Emph: | |
| fallthrough | |
| case Strong: | |
| fallthrough | |
| case Del: | |
| fallthrough | |
| case Link: | |
| fallthrough | |
| case Image: | |
| fallthrough | |
| case Table: | |
| fallthrough | |
| case TableHead: | |
| fallthrough | |
| case TableBody: | |
| fallthrough | |
| case TableRow: | |
| fallthrough | |
| case TableCell: | |
| return true | |
| default: | |
| return false | |
| } | |
| } | |
| // IsLeaf returns true if 'n' is a leaf node. | |
| func (n *Node) IsLeaf() bool { | |
| return !n.IsContainer() | |
| } | |
| func (n *Node) canContain(t NodeType) bool { | |
| if n.Type == List { | |
| return t == Item | |
| } | |
| if n.Type == Document || n.Type == BlockQuote || n.Type == Item { | |
| return t != Item | |
| } | |
| if n.Type == Table { | |
| return t == TableHead || t == TableBody | |
| } | |
| if n.Type == TableHead || n.Type == TableBody { | |
| return t == TableRow | |
| } | |
| if n.Type == TableRow { | |
| return t == TableCell | |
| } | |
| return false | |
| } | |
| // WalkStatus allows NodeVisitor to have some control over the tree traversal. | |
| // It is returned from NodeVisitor and different values allow Node.Walk to | |
| // decide which node to go to next. | |
| type WalkStatus int | |
| const ( | |
| // GoToNext is the default traversal of every node. | |
| GoToNext WalkStatus = iota | |
| // SkipChildren tells walker to skip all children of current node. | |
| SkipChildren | |
| // Terminate tells walker to terminate the traversal. | |
| Terminate | |
| ) | |
| // NodeVisitor is a callback to be called when traversing the syntax tree. | |
| // Called twice for every node: once with entering=true when the branch is | |
| // first visited, then with entering=false after all the children are done. | |
| type NodeVisitor func(node *Node, entering bool) WalkStatus | |
| // Walk is a convenience method that instantiates a walker and starts a | |
| // traversal of subtree rooted at n. | |
| func (n *Node) Walk(visitor NodeVisitor) { | |
| w := newNodeWalker(n) | |
| for w.current != nil { | |
| status := visitor(w.current, w.entering) | |
| switch status { | |
| case GoToNext: | |
| w.next() | |
| case SkipChildren: | |
| w.entering = false | |
| w.next() | |
| case Terminate: | |
| return | |
| } | |
| } | |
| } | |
| type nodeWalker struct { | |
| current *Node | |
| root *Node | |
| entering bool | |
| } | |
| func newNodeWalker(root *Node) *nodeWalker { | |
| return &nodeWalker{ | |
| current: root, | |
| root: root, | |
| entering: true, | |
| } | |
| } | |
| func (nw *nodeWalker) next() { | |
| if (!nw.current.IsContainer() || !nw.entering) && nw.current == nw.root { | |
| nw.current = nil | |
| return | |
| } | |
| if nw.entering && nw.current.IsContainer() { | |
| if nw.current.FirstChild != nil { | |
| nw.current = nw.current.FirstChild | |
| nw.entering = true | |
| } else { | |
| nw.entering = false | |
| } | |
| } else if nw.current.Next == nil { | |
| nw.current = nw.current.Parent | |
| nw.entering = false | |
| } else { | |
| nw.current = nw.current.Next | |
| nw.entering = true | |
| } | |
| } | |
| func dump(ast *Node) { | |
| fmt.Println(dumpString(ast)) | |
| } | |
| func dumpR(ast *Node, depth int) string { | |
| if ast == nil { | |
| return "" | |
| } | |
| indent := bytes.Repeat([]byte("\t"), depth) | |
| content := ast.Literal | |
| if content == nil { | |
| content = ast.content | |
| } | |
| result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content) | |
| for n := ast.FirstChild; n != nil; n = n.Next { | |
| result += dumpR(n, depth+1) | |
| } | |
| return result | |
| } | |
| func dumpString(ast *Node) string { | |
| return dumpR(ast, 0) | |
| } |