package markdown import ( "bytes" "fmt" ) // NodeData represents data field of Node type NodeData interface{} type DocumentData struct { } type BlockQuoteData struct { } // ListData contains fields relevant to a List node type ListData struct { ListFlags ListType Tight bool // Skip
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 } // ItemData contains fields relevant to a Item node type ItemData struct { ListFlags ListType Tight bool // Skip
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 } type ParagraphData struct { } // 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 } type HorizontalRuleData struct { } type EmphData struct { } type StrongData struct { } type DelData struct { } // 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. } type ImageData struct { Destination []byte // Destination is what goes into a href Title []byte // Title is the tooltip thing that goes in a title attribute } type TextData struct { } type HTMLBlockData struct { } // 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 } type SoftbreakData struct { } type HardbreakData struct { } type CodeData struct { } type HTMLSpanData struct { } type TableData struct { } // 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 } type TableHeadData struct { } type TableBodyData struct { } type TableRowData struct { } // 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 { 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 Data NodeData 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(d NodeData) *Node { return &Node{ Data: d, 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) */ return "Node.String() NYI" } // 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 } } func (n *Node) isContainer() bool { switch n.Data.(type) { case *DocumentData, *BlockQuoteData, *ListData, *ItemData, *ParagraphData: return true case *HeadingData, *EmphData, *StrongData, *DelData, *LinkData, *ImageData: return true case *TableData, *TableHeadData, *TableBodyData, *TableRowData, *TableCellData: return true default: return false } } func isListData(d NodeData) bool { _, ok := d.(*ListData) return ok } func isListTight(d NodeData) bool { if listData, ok := d.(*ListData); ok { return listData.Tight } return false } func isItemData(d NodeData) bool { _, ok := d.(*ItemData) return ok } func isItemTerm(node *Node) bool { data, ok := node.Data.(*ItemData) return ok && data.ListFlags&ListTypeTerm != 0 } func isLinkData(d NodeData) bool { _, ok := d.(*LinkData) return ok } func isTableRowData(d NodeData) bool { _, ok := d.(*TableRowData) return ok } func isTableCellData(d NodeData) bool { _, ok := d.(*TableCellData) return ok } func isBlockQuoteData(d NodeData) bool { _, ok := d.(*BlockQuoteData) return ok } func isDocumentData(d NodeData) bool { _, ok := d.(*DocumentData) return ok } func (n *Node) canContain(v NodeData) bool { switch n.Data.(type) { case *ListData: return isItemData(v) case *DocumentData, *BlockQuoteData, *ItemData: return !isItemData(v) case *TableData: switch v.(type) { case *TableHeadData, *TableBodyData: return true default: return false } case *TableHeadData, *TableBodyData: return isTableRowData(v) case *TableRowData: return isTableCellData(v) } return false /* 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%T(%q)\n", indent, ast.Data, 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) }