blackfriday/node.go
Vytautas Šaltenis 2bbed304fb Add AST data structures
The main Node structure that holds every AST node and some supplementary
substructs. A bit of helper code to handle the nodes.
2016-03-30 12:54:12 +03:00

286 lines
5.5 KiB
Go

package blackfriday
import (
"bytes"
"fmt"
)
type NodeType int
const (
Document NodeType = iota
BlockQuote
List
Item
Paragraph
Header
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",
Header: "Header",
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]
}
type ListData struct {
Flags 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
}
type LinkData struct {
Destination []byte
Title []byte
NoteID int
}
type Node struct {
Type NodeType
Parent *Node
FirstChild *Node
LastChild *Node
Prev *Node // prev sibling
Next *Node // next sibling
content []byte
open bool
Level uint32 // If Type == Header, this holds the heading level number
Literal []byte
ListData *ListData // If Type == List, this holds list info
// TODO: move these fenced code block fields to a substruct
IsFenced bool // If Type == CodeBlock, specifies whether it's a fenced code block or an indented one
Info []byte // If Type == CodeBlock, this holds the info string
FenceChar byte
FenceLength uint32
FenceOffset uint32
LinkData // If Type == Link, this holds link info
HeaderID string // If Type == Header, this might hold header ID, if present
IsTitleblock bool
IsHeader bool // If Type == TableCell, this tells if it's under the header row
// TODO: convert the int to a proper type
Align int // If Type == TableCell, this holds the value for align attribute
}
func NewNode(typ NodeType) *Node {
return &Node{
Type: typ,
open: true,
}
}
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
}
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
}
}
func (n *Node) isContainer() bool {
switch n.Type {
case Document:
fallthrough
case BlockQuote:
fallthrough
case List:
fallthrough
case Item:
fallthrough
case Paragraph:
fallthrough
case Header:
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
}
return false
}
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
}
type NodeWalker struct {
current *Node
root *Node
entering bool
}
func NewNodeWalker(root *Node) *NodeWalker {
return &NodeWalker{
current: root,
root: nil,
entering: true,
}
}
func (nw *NodeWalker) next() (*Node, bool) {
if nw.current == nil {
return nil, false
}
if nw.root == nil {
nw.root = nw.current
return nw.current, nw.entering
}
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
}
if nw.current == nw.root {
return nil, false
}
return nw.current, nw.entering
}
func (nw *NodeWalker) resumeAt(node *Node, entering bool) {
nw.current = node
nw.entering = entering
}
func ForEachNode(root *Node, f func(node *Node, entering bool)) {
walker := NewNodeWalker(root)
node, entering := walker.next()
for node != nil {
f(node, entering)
node, entering = walker.next()
}
}
func dump(ast *Node) {
fmt.Println(dumpString(ast))
}
func dump_r(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 += dump_r(n, depth+1)
}
return result
}
func dumpString(ast *Node) string {
return dump_r(ast, 0)
}