mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			361 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			361 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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)
 | 
						|
}
 |