complete refactor

This commit is contained in:
Krzysztof Kowalczyk 2018-01-28 00:50:21 -08:00
parent 7e13677aaf
commit 242d66580e
9 changed files with 496 additions and 397 deletions

View File

@ -3,6 +3,7 @@ package ast
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"reflect"
) )
// ListType contains bitwise or'ed flags for list and list item objects. // ListType contains bitwise or'ed flags for list and list item objects.
@ -33,19 +34,79 @@ const (
TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight) TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
) )
// NodeData represents data field of Node // Node defines an ast node
type NodeData interface{} type Node interface {
AsTreeNode() *TreeNode
// DocumentData represents top-level document node GetParent() Node
type DocumentData struct { GetChildren() []Node
FirstChild() Node
LastChild() Node
} }
// BlockQuoteData represents data for block quote node // TreeNode is a common part of all nodes, used to represent tree and contain
type BlockQuoteData struct { // data that all nodes have in common
type TreeNode struct {
Parent Node
Children []Node
Literal []byte // Text contents of the leaf nodes
Content []byte // Markdown content of the block nodes
} }
// ListData represents data list node // AsTreeNode returns itself as *TreeNode
type ListData struct { func (n *TreeNode) AsTreeNode() *TreeNode {
res := n
//fmt.Printf("TreeNode.AsTreeNode() called. n: %p, res: %p %v\n", n, res, res)
return res
}
// GetParent returns parent
func (n *TreeNode) GetParent() Node {
return n.Parent
}
// GetChildren returns children
func (n *TreeNode) GetChildren() []Node {
return n.Children
}
// PanicIfTreeNode will panic if node is *TreeNode
func PanicIfTreeNode(node Node) {
if _, ok := node.(*TreeNode); ok {
panic(fmt.Sprintf("%v is TreeNode", node))
}
}
// AddChild adds child node to parent node
func AddChild(parent Node, child Node) {
PanicIfTreeNode(parent)
PanicIfTreeNode(child)
pn := parent.AsTreeNode()
pn.Parent = parent
pn.Children = append(pn.Children, child)
}
func isNilNode(i Node) bool {
if i == nil {
return true
}
return reflect.ValueOf(i).IsNil()
}
// Document represents document
type Document struct {
TreeNode
}
// BlockQuote represents data for block quote node
type BlockQuote struct {
TreeNode
}
// List represents data list node
type List struct {
TreeNode
ListFlags ListType ListFlags ListType
Tight bool // Skip <p>s around list item data if true Tight bool // Skip <p>s around list item data if true
BulletChar byte // '*', '+' or '-' in bullet lists BulletChar byte // '*', '+' or '-' in bullet lists
@ -54,8 +115,10 @@ type ListData struct {
IsFootnotesList bool // This is a list of footnotes IsFootnotesList bool // This is a list of footnotes
} }
// ListItemData represents data for list item node // ListItem represents data for list item node
type ListItemData struct { type ListItem struct {
TreeNode
ListFlags ListType ListFlags ListType
Tight bool // Skip <p>s around list item data if true Tight bool // Skip <p>s around list item data if true
BulletChar byte // '*', '+' or '-' in bullet lists BulletChar byte // '*', '+' or '-' in bullet lists
@ -64,57 +127,72 @@ type ListItemData struct {
IsFootnotesList bool // This is a list of footnotes IsFootnotesList bool // This is a list of footnotes
} }
// ParagraphData represents data for paragraph node // Paragraph represents data for paragraph node
type ParagraphData struct { type Paragraph struct {
TreeNode
} }
// HeadingData contains fields relevant to a Heading node type. // Heading contains fields relevant to a Heading node type.
type HeadingData struct { type Heading struct {
TreeNode
Level int // This holds the heading level number Level int // This holds the heading level number
HeadingID string // This might hold heading ID, if present HeadingID string // This might hold heading ID, if present
IsTitleblock bool // Specifies whether it's a title block IsTitleblock bool // Specifies whether it's a title block
} }
// HorizontalRuleData represents data for horizontal rule node // HorizontalRule represents data for horizontal rule node
type HorizontalRuleData struct { type HorizontalRule struct {
TreeNode
} }
// EmphData represents data for emp node // Emph represents data for emp node
type EmphData struct { type Emph struct {
TreeNode
} }
// StrongData represents data for strong node // Strong represents data for strong node
type StrongData struct { type Strong struct {
TreeNode
} }
// DelData represents data for del node // Del represents data for del node
type DelData struct { type Del struct {
TreeNode
} }
// LinkData represents data for link node // Link represents data for link node
type LinkData struct { type Link struct {
TreeNode
Destination []byte // Destination is what goes into a href Destination []byte // Destination is what goes into a href
Title []byte // Title is the tooltip thing that goes in a title attribute 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 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. Footnote Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
} }
// ImageData represents data for image node // Image represents data for image node
type ImageData struct { type Image struct {
TreeNode
Destination []byte // Destination is what goes into a href Destination []byte // Destination is what goes into a href
Title []byte // Title is the tooltip thing that goes in a title attribute Title []byte // Title is the tooltip thing that goes in a title attribute
} }
// TextData represents data for text node // Text represents data for text node
type TextData struct { type Text struct {
TreeNode
} }
// HTMLBlockData represents data for html node // HTMLBlock represents data for html node
type HTMLBlockData struct { type HTMLBlock struct {
TreeNode
} }
// CodeBlockData contains fields relevant to a CodeBlock node type. // CodeBlock contains fields relevant to a CodeBlock node type.
type CodeBlockData struct { type CodeBlock struct {
TreeNode
IsFenced bool // Specifies whether it's a fenced code block or an indented one IsFenced bool // Specifies whether it's a fenced code block or an indented one
Info []byte // This holds the info string Info []byte // This holds the info string
FenceChar byte FenceChar byte
@ -122,66 +200,56 @@ type CodeBlockData struct {
FenceOffset int FenceOffset int
} }
// SoftbreakData represents data for softbreak node // Softbreak represents data for softbreak node
// Note: not used currently // Note: not used currently
type SoftbreakData struct { type Softbreak struct {
TreeNode
} }
// HardbreakData represents data for hard break node // Hardbreak represents data for hard break node
type HardbreakData struct { type Hardbreak struct {
TreeNode
} }
// CodeData represents data for code node // Code represents data for code node
type CodeData struct { type Code struct {
TreeNode
} }
// HTMLSpanData represents data for html span node // HTMLSpan represents data for html span node
type HTMLSpanData struct { type HTMLSpan struct {
TreeNode
} }
// TableData represents data for table node // Table represents data for table node
type TableData struct { type Table struct {
TreeNode
} }
// TableCellData contains fields relevant to a table cell node type. // TableCell contains fields relevant to a table cell node type.
type TableCellData struct { type TableCell struct {
TreeNode
IsHeader bool // This tells if it's under the header row IsHeader bool // This tells if it's under the header row
Align CellAlignFlags // This holds the value for align attribute Align CellAlignFlags // This holds the value for align attribute
} }
// TableHeadData represents data for a table head node // TableHead represents data for a table head node
type TableHeadData struct { type TableHead struct {
TreeNode
} }
// TableBodyData represents data for a tablef body node // TableBody represents data for a tablef body node
type TableBodyData struct { type TableBody struct {
TreeNode
} }
// TableRowData represents data for a table row node // TableRow represents data for a table row node
type TableRowData struct { type TableRow struct {
} TreeNode
// 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
Children []*Node
Literal []byte // Text contents of the leaf nodes
Data NodeData
Content []byte // Markdown content of the block nodes
}
// NewNode allocates a node of a specified type.
func NewNode(d NodeData) *Node {
return &Node{
Data: d,
}
} }
/*
func (n *Node) String() string { func (n *Node) String() string {
ellipsis := "" ellipsis := ""
snippet := n.Literal snippet := n.Literal
@ -191,8 +259,9 @@ func (n *Node) String() string {
} }
return fmt.Sprintf("%T: '%s%s'", n.Data, snippet, ellipsis) return fmt.Sprintf("%T: '%s%s'", n.Data, snippet, ellipsis)
} }
*/
func removeNodeFromArray(a []*Node, node *Node) []*Node { func removeNodeFromArray(a []Node, node Node) []Node {
n := len(a) n := len(a)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if a[i] == node { if a[i] == node {
@ -203,28 +272,32 @@ func removeNodeFromArray(a []*Node, node *Node) []*Node {
} }
// RemoveFromTree removes this node from tree // RemoveFromTree removes this node from tree
func (n *Node) RemoveFromTree() { func RemoveFromTree(n Node) {
if n.Parent == nil { nt := n.AsTreeNode()
if nt.Parent == nil {
return return
} }
// important: don't clear n.Children if n has no parent // important: don't clear n.Children if n has no parent
// we're called from AppendChild and that might happen on a node // we're called from AppendChild and that might happen on a node
// that accumulated Children but hasn't been inserted into the tree // that accumulated Children but hasn't been inserted into the tree
n.Parent.Children = removeNodeFromArray(n.Parent.Children, n) p := nt.Parent.AsTreeNode()
n.Parent = nil p.Children = removeNodeFromArray(p.Children, n)
n.Children = nil nt.Parent = nil
nt.Children = nil
} }
// AppendChild adds a node 'child' as a child of 'n'. // AppendChild adds a node 'child' as a child of 'n'.
// It panics if either node is nil. // It panics if either node is nil.
func (n *Node) AppendChild(child *Node) { func AppendChild(n Node, child Node) {
child.RemoveFromTree() childTN := child.AsTreeNode()
child.Parent = n RemoveFromTree(child)
n.Children = append(n.Children, child) childTN.Parent = n
nTN := n.AsTreeNode()
nTN.Children = append(nTN.Children, child)
} }
// LastChild returns last child of this node // LastChild returns last child of this node
func (n *Node) LastChild() *Node { func (n *TreeNode) LastChild() Node {
a := n.Children a := n.Children
if len(a) > 0 { if len(a) > 0 {
return a[len(a)-1] return a[len(a)-1]
@ -233,7 +306,7 @@ func (n *Node) LastChild() *Node {
} }
// FirstChild returns first child of this node // FirstChild returns first child of this node
func (n *Node) FirstChild() *Node { func (n *TreeNode) FirstChild() Node {
a := n.Children a := n.Children
if len(a) > 0 { if len(a) > 0 {
return a[0] return a[0]
@ -241,12 +314,12 @@ func (n *Node) FirstChild() *Node {
return nil return nil
} }
// Next returns next sibling of this node // NextNode returns next sibling of this node
func (n *Node) Next() *Node { func NextNode(n Node) Node {
if n.Parent == nil { if isNilNode(n.GetParent()) {
return nil return nil
} }
a := n.Parent.Children a := n.GetParent().AsTreeNode().Children
len := len(a) - 1 len := len(a) - 1
for i := 0; i < len; i++ { for i := 0; i < len; i++ {
if a[i] == n { if a[i] == n {
@ -256,12 +329,12 @@ func (n *Node) Next() *Node {
return nil return nil
} }
// Prev returns previous sibling of this node // PrevNode returns sibling node before n
func (n *Node) Prev() *Node { func PrevNode(n Node) Node {
if n.Parent == nil { if isNilNode(n.GetParent()) {
return nil return nil
} }
a := n.Parent.Children a := n.GetParent().AsTreeNode().Children
len := len(a) len := len(a)
for i := 1; i < len; i++ { for i := 1; i < len; i++ {
if a[i] == n { if a[i] == n {
@ -271,10 +344,10 @@ func (n *Node) Prev() *Node {
return nil return nil
} }
func (n *Node) isContainer() bool { func isContainer(n Node) bool {
// list of non-containers is smaller so we check against that for speed // list of non-containers is smaller so we check against that for speed
switch n.Data.(type) { switch n.(type) {
case *HorizontalRuleData, *TextData, *HTMLBlockData, *CodeBlockData, *SoftbreakData, *HardbreakData, *CodeData, *HTMLSpanData: case *HorizontalRule, *Text, *HTMLBlock, *CodeBlock, *Softbreak, *Hardbreak, *Code, *HTMLSpan:
return false return false
default: default:
return true return true
@ -299,20 +372,20 @@ const (
// Called twice for every node: once with entering=true when the branch is // 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. // first visited, then with entering=false after all the children are done.
type NodeVisitor interface { type NodeVisitor interface {
Visit(node *Node, entering bool) WalkStatus Visit(node Node, entering bool) WalkStatus
} }
// NodeVisitorFunc casts a function to match NodeVisitor interface // NodeVisitorFunc casts a function to match NodeVisitor interface
type NodeVisitorFunc func(node *Node, entering bool) WalkStatus type NodeVisitorFunc func(node Node, entering bool) WalkStatus
// Visit calls visitor function // Visit calls visitor function
func (f NodeVisitorFunc) Visit(node *Node, entering bool) WalkStatus { func (f NodeVisitorFunc) Visit(node Node, entering bool) WalkStatus {
return f(node, entering) return f(node, entering)
} }
// Walk is a convenience method that instantiates a walker and starts a // Walk is a convenience method that instantiates a walker and starts a
// traversal of subtree rooted at n. // traversal of subtree rooted at n.
func (n *Node) Walk(visitor NodeVisitor) { func Walk(n Node, visitor NodeVisitor) {
w := newNodeWalker(n) w := newNodeWalker(n)
for w.current != nil { for w.current != nil {
status := visitor.Visit(w.current, w.entering) status := visitor.Visit(w.current, w.entering)
@ -329,18 +402,18 @@ func (n *Node) Walk(visitor NodeVisitor) {
} }
// WalkFunc is like Walk but accepts just a callback function // WalkFunc is like Walk but accepts just a callback function
func (n *Node) WalkFunc(f NodeVisitorFunc) { func WalkFunc(n Node, f NodeVisitorFunc) {
visitor := NodeVisitorFunc(f) visitor := NodeVisitorFunc(f)
n.Walk(visitor) Walk(n, visitor)
} }
type nodeWalker struct { type nodeWalker struct {
current *Node current Node
root *Node root Node
entering bool entering bool
} }
func newNodeWalker(root *Node) *nodeWalker { func newNodeWalker(root Node) *nodeWalker {
return &nodeWalker{ return &nodeWalker{
current: root, current: root,
root: root, root: root,
@ -349,46 +422,46 @@ func newNodeWalker(root *Node) *nodeWalker {
} }
func (nw *nodeWalker) next() { func (nw *nodeWalker) next() {
if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root { if (!isContainer(nw.current) || !nw.entering) && nw.current == nw.root {
nw.current = nil nw.current = nil
return return
} }
if nw.entering && nw.current.isContainer() { if nw.entering && isContainer(nw.current) {
if nw.current.FirstChild() != nil { if nw.current.FirstChild() != nil {
nw.current = nw.current.FirstChild() nw.current = nw.current.FirstChild()
nw.entering = true nw.entering = true
} else { } else {
nw.entering = false nw.entering = false
} }
} else if nw.current.Next() == nil { } else if NextNode(nw.current) == nil {
nw.current = nw.current.Parent nw.current = nw.current.GetParent()
nw.entering = false nw.entering = false
} else { } else {
nw.current = nw.current.Next() nw.current = NextNode(nw.current)
nw.entering = true nw.entering = true
} }
} }
func dump(ast *Node) { func dump(ast Node) {
fmt.Println(dumpString(ast)) fmt.Println(dumpString(ast))
} }
func dumpR(ast *Node, depth int) string { func dumpR(ast Node, depth int) string {
if ast == nil { if ast == nil {
return "" return ""
} }
indent := bytes.Repeat([]byte("\t"), depth) indent := bytes.Repeat([]byte("\t"), depth)
content := ast.Literal content := ast.AsTreeNode().Literal
if content == nil { if content == nil {
content = ast.Content content = ast.AsTreeNode().Content
} }
result := fmt.Sprintf("%s%T(%q)\n", indent, ast.Data, content) result := fmt.Sprintf("%s%T(%q)\n", indent, ast, content)
for _, n := range ast.Children { for _, n := range ast.GetChildren() {
result += dumpR(n, depth+1) result += dumpR(n, depth+1)
} }
return result return result
} }
func dumpString(ast *Node) string { func dumpString(ast Node) string {
return dumpR(ast, 0) return dumpR(ast, 0)
} }

View File

@ -22,7 +22,7 @@ func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, s
// the integration server. When developing, though, crash dump is often // the integration server. When developing, though, crash dump is often
// preferable, so recovery can be easily turned off with doRecover = false. // preferable, so recovery can be easily turned off with doRecover = false.
var candidate string var candidate string
const doRecover = true const doRecover = false
if doRecover { if doRecover {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {

View File

@ -65,7 +65,7 @@ const (
// rendering of some nodes. If it returns false, Renderer.RenderNode // rendering of some nodes. If it returns false, Renderer.RenderNode
// will execute its logic. If it returns true, Renderer.RenderNode will // will execute its logic. If it returns true, Renderer.RenderNode will
// skip rendering this node and will return WalkStatus // skip rendering this node and will return WalkStatus
type RenderNodeFunc func(w io.Writer, node *ast.Node, entering bool) (ast.WalkStatus, bool) type RenderNodeFunc func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool)
// RendererOptions is a collection of supplementary parameters tweaking // RendererOptions is a collection of supplementary parameters tweaking
// the behavior of various parts of HTML renderer. // the behavior of various parts of HTML renderer.
@ -291,9 +291,9 @@ func needSkipLink(flags Flags, dest []byte) bool {
return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest) return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
} }
func isSmartypantable(node *ast.Node) bool { func isSmartypantable(node ast.Node) bool {
switch node.Parent.Data.(type) { switch node.GetParent().(type) {
case *ast.LinkData, *ast.CodeBlockData, *ast.CodeData: case *ast.Link, *ast.CodeBlock, *ast.Code:
return false return false
} }
return true return true
@ -320,7 +320,7 @@ func (r *Renderer) outTag(w io.Writer, name string, attrs []string) {
r.lastOutputLen = 1 r.lastOutputLen = 1
} }
func footnoteRef(prefix string, node *ast.LinkData) string { func footnoteRef(prefix string, node *ast.Link) string {
urlFrag := prefix + string(slugify(node.Destination)) urlFrag := prefix + string(slugify(node.Destination))
nStr := strconv.Itoa(node.NoteID) nStr := strconv.Itoa(node.NoteID)
anchor := `<a rel="footnote" href="#fn:` + urlFrag + `">` + nStr + `</a>` anchor := `<a rel="footnote" href="#fn:` + urlFrag + `">` + nStr + `</a>`
@ -335,22 +335,22 @@ func footnoteReturnLink(prefix, returnLink string, slug []byte) string {
return ` <a class="footnote-return" href="#fnref:` + prefix + string(slug) + `">` + returnLink + `</a>` return ` <a class="footnote-return" href="#fnref:` + prefix + string(slug) + `">` + returnLink + `</a>`
} }
func itemOpenCR(node *ast.Node) bool { func itemOpenCR(node ast.Node) bool {
if node.Prev() == nil { if ast.PrevNode(node) == nil {
return false return false
} }
ld := node.Parent.Data.(*ast.ListData) ld := node.GetParent().(*ast.List)
return !ld.Tight && ld.ListFlags&ast.ListTypeDefinition == 0 return !ld.Tight && ld.ListFlags&ast.ListTypeDefinition == 0
} }
func skipParagraphTags(node *ast.Node) bool { func skipParagraphTags(node ast.Node) bool {
parent := node.Parent parent := node.GetParent()
grandparent := parent.Parent grandparent := parent.GetParent()
if grandparent == nil || !isListData(grandparent.Data) { if grandparent == nil || !isListData(grandparent) {
return false return false
} }
isParentTerm := isListItemTerm(parent) isParentTerm := isListItemTerm(parent)
grandparentListData := grandparent.Data.(*ast.ListData) grandparentListData := grandparent.(*ast.List)
tightOrTerm := grandparentListData.Tight || isParentTerm tightOrTerm := grandparentListData.Tight || isParentTerm
return tightOrTerm return tightOrTerm
} }
@ -414,13 +414,13 @@ func (r *Renderer) outHRTag(w io.Writer) {
r.outOneOf(w, r.opts.Flags&UseXHTML == 0, "<hr>", "<hr />") r.outOneOf(w, r.opts.Flags&UseXHTML == 0, "<hr>", "<hr />")
} }
func (r *Renderer) text(w io.Writer, node *ast.Node, nodeData *ast.TextData) { func (r *Renderer) text(w io.Writer, node *ast.Text) {
if r.opts.Flags&Smartypants != 0 { if r.opts.Flags&Smartypants != 0 {
var tmp bytes.Buffer var tmp bytes.Buffer
EscapeHTML(&tmp, node.Literal) EscapeHTML(&tmp, node.Literal)
r.sr.Process(w, tmp.Bytes()) r.sr.Process(w, tmp.Bytes())
} else { } else {
if isLinkData(node.Parent.Data) { if isLinkData(node.GetParent()) {
escLink(w, node.Literal) escLink(w, node.Literal)
} else { } else {
EscapeHTML(w, node.Literal) EscapeHTML(w, node.Literal)
@ -428,7 +428,7 @@ func (r *Renderer) text(w io.Writer, node *ast.Node, nodeData *ast.TextData) {
} }
} }
func (r *Renderer) hardBreak(w io.Writer, node *ast.Node, nodeData *ast.HardbreakData) { func (r *Renderer) hardBreak(w io.Writer, node *ast.Hardbreak) {
r.outOneOf(w, r.opts.Flags&UseXHTML == 0, "<br>", "<br />") r.outOneOf(w, r.opts.Flags&UseXHTML == 0, "<br>", "<br />")
r.cr(w) r.cr(w)
} }
@ -451,13 +451,13 @@ func (r *Renderer) outOneOfCr(w io.Writer, outFirst bool, first string, second s
} }
} }
func (r *Renderer) span(w io.Writer, node *ast.Node, nodeData *ast.HTMLSpanData) { func (r *Renderer) span(w io.Writer, node *ast.HTMLSpan) {
if r.opts.Flags&SkipHTML == 0 { if r.opts.Flags&SkipHTML == 0 {
r.out(w, node.Literal) r.out(w, node.Literal)
} }
} }
func (r *Renderer) linkEnter(w io.Writer, node *ast.Node, nodeData *ast.LinkData) { func (r *Renderer) linkEnter(w io.Writer, nodeData *ast.Link) {
var attrs []string var attrs []string
dest := nodeData.Destination dest := nodeData.Destination
dest = r.addAbsPrefix(dest) dest = r.addAbsPrefix(dest)
@ -482,13 +482,13 @@ func (r *Renderer) linkEnter(w io.Writer, node *ast.Node, nodeData *ast.LinkData
r.outTag(w, "<a", attrs) r.outTag(w, "<a", attrs)
} }
func (r *Renderer) linkExit(w io.Writer, node *ast.Node, nodeData *ast.LinkData) { func (r *Renderer) linkExit(w io.Writer, nodeData *ast.Link) {
if nodeData.NoteID == 0 { if nodeData.NoteID == 0 {
r.outs(w, "</a>") r.outs(w, "</a>")
} }
} }
func (r *Renderer) link(w io.Writer, node *ast.Node, nodeData *ast.LinkData, entering bool) { func (r *Renderer) link(w io.Writer, nodeData *ast.Link, entering bool) {
// mark it but don't link it if it is not a safe link: no smartypants // mark it but don't link it if it is not a safe link: no smartypants
if needSkipLink(r.opts.Flags, nodeData.Destination) { if needSkipLink(r.opts.Flags, nodeData.Destination) {
r.outOneOf(w, entering, "<tt>", "</tt>") r.outOneOf(w, entering, "<tt>", "</tt>")
@ -496,13 +496,13 @@ func (r *Renderer) link(w io.Writer, node *ast.Node, nodeData *ast.LinkData, ent
} }
if entering { if entering {
r.linkEnter(w, node, nodeData) r.linkEnter(w, nodeData)
} else { } else {
r.linkExit(w, node, nodeData) r.linkExit(w, nodeData)
} }
} }
func (r *Renderer) imageEnter(w io.Writer, node *ast.Node, nodeData *ast.ImageData) { func (r *Renderer) imageEnter(w io.Writer, nodeData *ast.Image) {
dest := nodeData.Destination dest := nodeData.Destination
dest = r.addAbsPrefix(dest) dest = r.addAbsPrefix(dest)
if r.disableTags == 0 { if r.disableTags == 0 {
@ -517,7 +517,7 @@ func (r *Renderer) imageEnter(w io.Writer, node *ast.Node, nodeData *ast.ImageDa
r.disableTags++ r.disableTags++
} }
func (r *Renderer) imageExit(w io.Writer, node *ast.Node, nodeData *ast.ImageData) { func (r *Renderer) imageExit(w io.Writer, nodeData *ast.Image) {
r.disableTags-- r.disableTags--
if r.disableTags == 0 { if r.disableTags == 0 {
if nodeData.Title != nil { if nodeData.Title != nil {
@ -528,54 +528,54 @@ func (r *Renderer) imageExit(w io.Writer, node *ast.Node, nodeData *ast.ImageDat
} }
} }
func (r *Renderer) paragraphEnter(w io.Writer, node *ast.Node, nodeData *ast.ParagraphData) { func (r *Renderer) paragraphEnter(w io.Writer, nodeData *ast.Paragraph) {
// TODO: untangle this clusterfuck about when the newlines need // TODO: untangle this clusterfuck about when the newlines need
// to be added and when not. // to be added and when not.
prev := node.Prev() prev := ast.PrevNode(nodeData)
if prev != nil { if prev != nil {
switch prev.Data.(type) { switch prev.(type) {
case *ast.HTMLBlockData, *ast.ListData, *ast.ParagraphData, *ast.HeadingData, *ast.CodeBlockData, *ast.BlockQuoteData, *ast.HorizontalRuleData: case *ast.HTMLBlock, *ast.List, *ast.Paragraph, *ast.Heading, *ast.CodeBlock, *ast.BlockQuote, *ast.HorizontalRule:
r.cr(w) r.cr(w)
} }
} }
if isBlockQuoteData(node.Parent.Data) && prev == nil { if isBlockQuoteData(nodeData.Parent) && prev == nil {
r.cr(w) r.cr(w)
} }
r.outs(w, "<p>") r.outs(w, "<p>")
} }
func (r *Renderer) paragraphExit(w io.Writer, node *ast.Node, nodeData *ast.ParagraphData) { func (r *Renderer) paragraphExit(w io.Writer, node *ast.Paragraph) {
r.outs(w, "</p>") r.outs(w, "</p>")
if !(isListItemData(node.Parent.Data) && node.Next() == nil) { if !(isListItemData(node.Parent) && ast.NextNode(node) == nil) {
r.cr(w) r.cr(w)
} }
} }
func (r *Renderer) paragraph(w io.Writer, node *ast.Node, nodeData *ast.ParagraphData, entering bool) { func (r *Renderer) paragraph(w io.Writer, node *ast.Paragraph, entering bool) {
if skipParagraphTags(node) { if skipParagraphTags(node) {
return return
} }
if entering { if entering {
r.paragraphEnter(w, node, nodeData) r.paragraphEnter(w, node)
} else { } else {
r.paragraphExit(w, node, nodeData) r.paragraphExit(w, node)
} }
} }
func (r *Renderer) image(w io.Writer, node *ast.Node, nodeData *ast.ImageData, entering bool) { func (r *Renderer) image(w io.Writer, node *ast.Image, entering bool) {
if entering { if entering {
r.imageEnter(w, node, nodeData) r.imageEnter(w, node)
} else { } else {
r.imageExit(w, node, nodeData) r.imageExit(w, node)
} }
} }
func (r *Renderer) code(w io.Writer, node *ast.Node, nodeData *ast.CodeData) { func (r *Renderer) code(w io.Writer, node *ast.Code) {
r.outs(w, "<code>") r.outs(w, "<code>")
EscapeHTML(w, node.Literal) EscapeHTML(w, node.Literal)
r.outs(w, "</code>") r.outs(w, "</code>")
} }
func (r *Renderer) htmlBlock(w io.Writer, node *ast.Node, nodeData *ast.HTMLBlockData) { func (r *Renderer) htmlBlock(w io.Writer, node *ast.HTMLBlock) {
if r.opts.Flags&SkipHTML != 0 { if r.opts.Flags&SkipHTML != 0 {
return return
} }
@ -584,7 +584,7 @@ func (r *Renderer) htmlBlock(w io.Writer, node *ast.Node, nodeData *ast.HTMLBloc
r.cr(w) r.cr(w)
} }
func (r *Renderer) headingEnter(w io.Writer, node *ast.Node, nodeData *ast.HeadingData) { func (r *Renderer) headingEnter(w io.Writer, nodeData *ast.Heading) {
var attrs []string var attrs []string
if nodeData.IsTitleblock { if nodeData.IsTitleblock {
attrs = append(attrs, `class="title"`) attrs = append(attrs, `class="title"`)
@ -604,18 +604,18 @@ func (r *Renderer) headingEnter(w io.Writer, node *ast.Node, nodeData *ast.Headi
r.outTag(w, headingOpenTagFromLevel(nodeData.Level), attrs) r.outTag(w, headingOpenTagFromLevel(nodeData.Level), attrs)
} }
func (r *Renderer) headingExit(w io.Writer, node *ast.Node, nodeData *ast.HeadingData) { func (r *Renderer) headingExit(w io.Writer, nodeData *ast.Heading) {
r.outs(w, headingCloseTagFromLevel(nodeData.Level)) r.outs(w, headingCloseTagFromLevel(nodeData.Level))
if !(isListItemData(node.Parent.Data) && node.Next() == nil) { if !(isListItemData(nodeData.Parent) && ast.NextNode(nodeData) == nil) {
r.cr(w) r.cr(w)
} }
} }
func (r *Renderer) heading(w io.Writer, node *ast.Node, nodeData *ast.HeadingData, entering bool) { func (r *Renderer) heading(w io.Writer, node *ast.Heading, entering bool) {
if entering { if entering {
r.headingEnter(w, node, nodeData) r.headingEnter(w, node)
} else { } else {
r.headingExit(w, node, nodeData) r.headingExit(w, node)
} }
} }
@ -625,7 +625,7 @@ func (r *Renderer) horizontalRule(w io.Writer) {
r.cr(w) r.cr(w)
} }
func (r *Renderer) listEnter(w io.Writer, node *ast.Node, nodeData *ast.ListData) { func (r *Renderer) listEnter(w io.Writer, nodeData *ast.List) {
// TODO: attrs don't seem to be set // TODO: attrs don't seem to be set
var attrs []string var attrs []string
@ -635,9 +635,9 @@ func (r *Renderer) listEnter(w io.Writer, node *ast.Node, nodeData *ast.ListData
r.cr(w) r.cr(w)
} }
r.cr(w) r.cr(w)
if isListItemData(node.Parent.Data) { if isListItemData(nodeData.Parent) {
grand := node.Parent.Parent grand := nodeData.Parent.GetParent()
if isListTight(grand.Data) { if isListTight(grand) {
r.cr(w) r.cr(w)
} }
} }
@ -653,12 +653,12 @@ func (r *Renderer) listEnter(w io.Writer, node *ast.Node, nodeData *ast.ListData
r.cr(w) r.cr(w)
} }
func (r *Renderer) listExit(w io.Writer, node *ast.Node, nodeData *ast.ListData) { func (r *Renderer) listExit(w io.Writer, node *ast.List) {
closeTag := "</ul>" closeTag := "</ul>"
if nodeData.ListFlags&ast.ListTypeOrdered != 0 { if node.ListFlags&ast.ListTypeOrdered != 0 {
closeTag = "</ol>" closeTag = "</ol>"
} }
if nodeData.ListFlags&ast.ListTypeDefinition != 0 { if node.ListFlags&ast.ListTypeDefinition != 0 {
closeTag = "</dl>" closeTag = "</dl>"
} }
r.outs(w, closeTag) r.outs(w, closeTag)
@ -667,46 +667,46 @@ func (r *Renderer) listExit(w io.Writer, node *ast.Node, nodeData *ast.ListData)
//if node.parent.Type != Item { //if node.parent.Type != Item {
// cr(w) // cr(w)
//} //}
if isListItemData(node.Parent.Data) && node.Next() != nil { if isListItemData(node.Parent) && ast.NextNode(node) != nil {
r.cr(w) r.cr(w)
} }
if isDocumentData(node.Parent.Data) || isBlockQuoteData(node.Parent.Data) { if isDocumentData(node.Parent) || isBlockQuoteData(node.Parent) {
r.cr(w) r.cr(w)
} }
if nodeData.IsFootnotesList { if node.IsFootnotesList {
r.outs(w, "\n</div>\n") r.outs(w, "\n</div>\n")
} }
} }
func (r *Renderer) list(w io.Writer, node *ast.Node, nodeData *ast.ListData, entering bool) { func (r *Renderer) list(w io.Writer, node *ast.List, entering bool) {
if entering { if entering {
r.listEnter(w, node, nodeData) r.listEnter(w, node)
} else { } else {
r.listExit(w, node, nodeData) r.listExit(w, node)
} }
} }
func (r *Renderer) listItemEnter(w io.Writer, node *ast.Node, nodeData *ast.ListItemData) { func (r *Renderer) listItemEnter(w io.Writer, node *ast.ListItem) {
if itemOpenCR(node) { if itemOpenCR(node) {
r.cr(w) r.cr(w)
} }
if nodeData.RefLink != nil { if node.RefLink != nil {
slug := slugify(nodeData.RefLink) slug := slugify(node.RefLink)
r.outs(w, footnoteItem(r.opts.FootnoteAnchorPrefix, slug)) r.outs(w, footnoteItem(r.opts.FootnoteAnchorPrefix, slug))
return return
} }
openTag := "<li>" openTag := "<li>"
if nodeData.ListFlags&ast.ListTypeDefinition != 0 { if node.ListFlags&ast.ListTypeDefinition != 0 {
openTag = "<dd>" openTag = "<dd>"
} }
if nodeData.ListFlags&ast.ListTypeTerm != 0 { if node.ListFlags&ast.ListTypeTerm != 0 {
openTag = "<dt>" openTag = "<dt>"
} }
r.outs(w, openTag) r.outs(w, openTag)
} }
func (r *Renderer) listItemExit(w io.Writer, node *ast.Node, nodeData *ast.ListItemData) { func (r *Renderer) listItemExit(w io.Writer, nodeData *ast.ListItem) {
if nodeData.RefLink != nil && r.opts.Flags&FootnoteReturnLinks != 0 { if nodeData.RefLink != nil && r.opts.Flags&FootnoteReturnLinks != 0 {
slug := slugify(nodeData.RefLink) slug := slugify(nodeData.RefLink)
prefix := r.opts.FootnoteAnchorPrefix prefix := r.opts.FootnoteAnchorPrefix
@ -726,31 +726,31 @@ func (r *Renderer) listItemExit(w io.Writer, node *ast.Node, nodeData *ast.ListI
r.cr(w) r.cr(w)
} }
func (r *Renderer) listItem(w io.Writer, node *ast.Node, nodeData *ast.ListItemData, entering bool) { func (r *Renderer) listItem(w io.Writer, node *ast.ListItem, entering bool) {
if entering { if entering {
r.listItemEnter(w, node, nodeData) r.listItemEnter(w, node)
} else { } else {
r.listItemExit(w, node, nodeData) r.listItemExit(w, node)
} }
} }
func (r *Renderer) codeBlock(w io.Writer, node *ast.Node, nodeData *ast.CodeBlockData) { func (r *Renderer) codeBlock(w io.Writer, node *ast.CodeBlock) {
var attrs []string var attrs []string
attrs = appendLanguageAttr(attrs, nodeData.Info) attrs = appendLanguageAttr(attrs, node.Info)
r.cr(w) r.cr(w)
r.outs(w, "<pre>") r.outs(w, "<pre>")
r.outTag(w, "<code", attrs) r.outTag(w, "<code", attrs)
EscapeHTML(w, node.Literal) EscapeHTML(w, node.Literal)
r.outs(w, "</code>") r.outs(w, "</code>")
r.outs(w, "</pre>") r.outs(w, "</pre>")
if !isListItemData(node.Parent.Data) { if !isListItemData(node.Parent) {
r.cr(w) r.cr(w)
} }
} }
func (r *Renderer) tableCell(w io.Writer, node *ast.Node, nodeData *ast.TableCellData, entering bool) { func (r *Renderer) tableCell(w io.Writer, node *ast.TableCell, entering bool) {
if !entering { if !entering {
r.outOneOf(w, nodeData.IsHeader, "</th>", "</td>") r.outOneOf(w, node.IsHeader, "</th>", "</td>")
r.cr(w) r.cr(w)
return return
} }
@ -758,20 +758,20 @@ func (r *Renderer) tableCell(w io.Writer, node *ast.Node, nodeData *ast.TableCel
// entering // entering
var attrs []string var attrs []string
openTag := "<td" openTag := "<td"
if nodeData.IsHeader { if node.IsHeader {
openTag = "<th" openTag = "<th"
} }
align := cellAlignment(nodeData.Align) align := cellAlignment(node.Align)
if align != "" { if align != "" {
attrs = append(attrs, fmt.Sprintf(`align="%s"`, align)) attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
} }
if node.Prev() == nil { if ast.PrevNode(node) == nil {
r.cr(w) r.cr(w)
} }
r.outTag(w, openTag, attrs) r.outTag(w, openTag, attrs)
} }
func (r *Renderer) tableBody(w io.Writer, node *ast.Node, nodeData *ast.TableBodyData, entering bool) { func (r *Renderer) tableBody(w io.Writer, node *ast.TableBody, entering bool) {
if entering { if entering {
r.cr(w) r.cr(w)
r.outs(w, "<tbody>") r.outs(w, "<tbody>")
@ -795,75 +795,76 @@ func (r *Renderer) tableBody(w io.Writer, node *ast.Node, nodeData *ast.TableBod
// can ask the walker to skip a subtree of this node by returning SkipChildren. // can ask the walker to skip a subtree of this node by returning SkipChildren.
// The typical behavior is to return GoToNext, which asks for the usual // The typical behavior is to return GoToNext, which asks for the usual
// traversal to the next node. // traversal to the next node.
func (r *Renderer) RenderNode(w io.Writer, node *ast.Node, entering bool) ast.WalkStatus { func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.WalkStatus {
if r.opts.RenderNodeHook != nil { if r.opts.RenderNodeHook != nil {
status, didHandle := r.opts.RenderNodeHook(w, node, entering) status, didHandle := r.opts.RenderNodeHook(w, node, entering)
if didHandle { if didHandle {
return status return status
} }
} }
switch nodeData := node.Data.(type) { ast.PanicIfTreeNode(node)
case *ast.TextData: switch node := node.(type) {
r.text(w, node, nodeData) case *ast.Text:
case *ast.SoftbreakData: r.text(w, node)
case *ast.Softbreak:
r.cr(w) r.cr(w)
// TODO: make it configurable via out(renderer.softbreak) // TODO: make it configurable via out(renderer.softbreak)
case *ast.HardbreakData: case *ast.Hardbreak:
r.hardBreak(w, node, nodeData) r.hardBreak(w, node)
case *ast.EmphData: case *ast.Emph:
r.outOneOf(w, entering, "<em>", "</em>") r.outOneOf(w, entering, "<em>", "</em>")
case *ast.StrongData: case *ast.Strong:
r.outOneOf(w, entering, "<strong>", "</strong>") r.outOneOf(w, entering, "<strong>", "</strong>")
case *ast.DelData: case *ast.Del:
r.outOneOf(w, entering, "<del>", "</del>") r.outOneOf(w, entering, "<del>", "</del>")
case *ast.BlockQuoteData: case *ast.BlockQuote:
r.outOneOfCr(w, entering, "<blockquote>", "</blockquote>") r.outOneOfCr(w, entering, "<blockquote>", "</blockquote>")
case *ast.LinkData: case *ast.Link:
r.link(w, node, nodeData, entering) r.link(w, node, entering)
case *ast.ImageData: case *ast.Image:
if r.opts.Flags&SkipImages != 0 { if r.opts.Flags&SkipImages != 0 {
return ast.SkipChildren return ast.SkipChildren
} }
r.image(w, node, nodeData, entering) r.image(w, node, entering)
case *ast.CodeData: case *ast.Code:
r.code(w, node, nodeData) r.code(w, node)
case *ast.CodeBlockData: case *ast.CodeBlock:
r.codeBlock(w, node, nodeData) r.codeBlock(w, node)
case *ast.DocumentData: case *ast.Document:
// do nothing // do nothing
case *ast.ParagraphData: case *ast.Paragraph:
r.paragraph(w, node, nodeData, entering) r.paragraph(w, node, entering)
case *ast.HTMLSpanData: case *ast.HTMLSpan:
r.span(w, node, nodeData) r.span(w, node)
case *ast.HTMLBlockData: case *ast.HTMLBlock:
r.htmlBlock(w, node, nodeData) r.htmlBlock(w, node)
case *ast.HeadingData: case *ast.Heading:
r.heading(w, node, nodeData, entering) r.heading(w, node, entering)
case *ast.HorizontalRuleData: case *ast.HorizontalRule:
r.horizontalRule(w) r.horizontalRule(w)
case *ast.ListData: case *ast.List:
r.list(w, node, nodeData, entering) r.list(w, node, entering)
case *ast.ListItemData: case *ast.ListItem:
r.listItem(w, node, nodeData, entering) r.listItem(w, node, entering)
case *ast.TableData: case *ast.Table:
r.outOneOfCr(w, entering, "<table>", "</table>") r.outOneOfCr(w, entering, "<table>", "</table>")
case *ast.TableCellData: case *ast.TableCell:
r.tableCell(w, node, nodeData, entering) r.tableCell(w, node, entering)
case *ast.TableHeadData: case *ast.TableHead:
r.outOneOfCr(w, entering, "<thead>", "</thead>") r.outOneOfCr(w, entering, "<thead>", "</thead>")
case *ast.TableBodyData: case *ast.TableBody:
r.tableBody(w, node, nodeData, entering) r.tableBody(w, node, entering)
case *ast.TableRowData: case *ast.TableRow:
r.outOneOfCr(w, entering, "<tr>", "</tr>") r.outOneOfCr(w, entering, "<tr>", "</tr>")
default: default:
//panic("Unknown node type " + node.Type.String()) //panic("Unknown node type " + node.Type.String())
panic(fmt.Sprintf("Unknown node type %T", node.Data)) panic(fmt.Sprintf("Unknown node %T", node))
} }
return ast.GoToNext return ast.GoToNext
} }
// RenderHeader writes HTML document preamble and TOC if requested. // RenderHeader writes HTML document preamble and TOC if requested.
func (r *Renderer) RenderHeader(w io.Writer, ast *ast.Node) { func (r *Renderer) RenderHeader(w io.Writer, ast ast.Node) {
r.writeDocumentHeader(w) r.writeDocumentHeader(w)
if r.opts.Flags&TOC != 0 { if r.opts.Flags&TOC != 0 {
r.writeTOC(w, ast) r.writeTOC(w, ast)
@ -871,7 +872,7 @@ func (r *Renderer) RenderHeader(w io.Writer, ast *ast.Node) {
} }
// RenderFooter writes HTML document footer. // RenderFooter writes HTML document footer.
func (r *Renderer) RenderFooter(w io.Writer, ast *ast.Node) { func (r *Renderer) RenderFooter(w io.Writer, ast ast.Node) {
if r.opts.Flags&CompletePage == 0 { if r.opts.Flags&CompletePage == 0 {
return return
} }
@ -925,15 +926,15 @@ func (r *Renderer) writeDocumentHeader(w io.Writer) {
io.WriteString(w, "<body>\n\n") io.WriteString(w, "<body>\n\n")
} }
func (r *Renderer) writeTOC(w io.Writer, doc *ast.Node) { func (r *Renderer) writeTOC(w io.Writer, doc ast.Node) {
buf := bytes.Buffer{} buf := bytes.Buffer{}
inHeading := false inHeading := false
tocLevel := 0 tocLevel := 0
headingCount := 0 headingCount := 0
doc.WalkFunc(func(node *ast.Node, entering bool) ast.WalkStatus { ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
if nodeData, ok := node.Data.(*ast.HeadingData); ok && !nodeData.IsTitleblock { if nodeData, ok := node.(*ast.Heading); ok && !nodeData.IsTitleblock {
inHeading = entering inHeading = entering
if entering { if entering {
nodeData.HeadingID = fmt.Sprintf("toc_%d", headingCount) nodeData.HeadingID = fmt.Sprintf("toc_%d", headingCount)
@ -979,40 +980,40 @@ func (r *Renderer) writeTOC(w io.Writer, doc *ast.Node) {
r.lastOutputLen = buf.Len() r.lastOutputLen = buf.Len()
} }
func isListData(d ast.NodeData) bool { func isListData(n ast.Node) bool {
_, ok := d.(*ast.ListData) _, ok := n.(*ast.List)
return ok return ok
} }
func isListTight(d ast.NodeData) bool { func isListTight(d ast.Node) bool {
if listData, ok := d.(*ast.ListData); ok { if listData, ok := d.(*ast.List); ok {
return listData.Tight return listData.Tight
} }
return false return false
} }
func isListItemData(d ast.NodeData) bool { func isListItemData(d ast.Node) bool {
_, ok := d.(*ast.ListItemData) _, ok := d.(*ast.ListItem)
return ok return ok
} }
func isListItemTerm(node *ast.Node) bool { func isListItemTerm(node ast.Node) bool {
data, ok := node.Data.(*ast.ListItemData) data, ok := node.(*ast.ListItem)
return ok && data.ListFlags&ast.ListTypeTerm != 0 return ok && data.ListFlags&ast.ListTypeTerm != 0
} }
func isLinkData(d ast.NodeData) bool { func isLinkData(d ast.Node) bool {
_, ok := d.(*ast.LinkData) _, ok := d.(*ast.Link)
return ok return ok
} }
func isBlockQuoteData(d ast.NodeData) bool { func isBlockQuoteData(d ast.Node) bool {
_, ok := d.(*ast.BlockQuoteData) _, ok := d.(*ast.BlockQuote)
return ok return ok
} }
func isDocumentData(d ast.NodeData) bool { func isDocumentData(d ast.Node) bool {
_, ok := d.(*ast.DocumentData) _, ok := d.(*ast.Document)
return ok return ok
} }

View File

@ -9,7 +9,7 @@ import (
"github.com/gomarkdown/markdown/parser" "github.com/gomarkdown/markdown/parser"
) )
func renderHookEmpty(w io.Writer, node *ast.Node, entering bool) (ast.WalkStatus, bool) { func renderHookEmpty(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
return ast.GoToNext, true return ast.GoToNext, true
} }
@ -32,8 +32,8 @@ func TestRenderNodeHookEmpty(t *testing.T) {
doTestsParam(t, tests, params) doTestsParam(t, tests, params)
} }
func renderHookCodeBlock(w io.Writer, node *ast.Node, entering bool) (ast.WalkStatus, bool) { func renderHookCodeBlock(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
_, ok := node.Data.(*ast.CodeBlockData) _, ok := node.(*ast.CodeBlock)
if !ok { if !ok {
return ast.GoToNext, false return ast.GoToNext, false
} }

View File

@ -15,7 +15,7 @@ type Renderer interface {
// every leaf node and twice for every non-leaf node (first with // every leaf node and twice for every non-leaf node (first with
// entering=true, then with entering=false). The method should write its // entering=true, then with entering=false). The method should write its
// rendition of the node to writer w. // rendition of the node to writer w.
RenderNode(w io.Writer, node *ast.Node, entering bool) ast.WalkStatus RenderNode(w io.Writer, node ast.Node, entering bool) ast.WalkStatus
// RenderHeader is a method that allows the renderer to produce some // RenderHeader is a method that allows the renderer to produce some
// content preceding the main body of the output document. The header is // content preceding the main body of the output document. The header is
@ -28,18 +28,18 @@ type Renderer interface {
// //
// The output should be written to the supplied writer w. If your // The output should be written to the supplied writer w. If your
// implementation has no header to write, supply an empty implementation. // implementation has no header to write, supply an empty implementation.
RenderHeader(w io.Writer, ast *ast.Node) RenderHeader(w io.Writer, ast ast.Node)
// RenderFooter is a symmetric counterpart of RenderHeader. // RenderFooter is a symmetric counterpart of RenderHeader.
RenderFooter(w io.Writer, ast *ast.Node) RenderFooter(w io.Writer, ast ast.Node)
} }
// Render uses renderer to convert parsed markdown document into a different format. // Render uses renderer to convert parsed markdown document into a different format.
// For example to convert into HTML, use html.Renderer // For example to convert into HTML, use html.Renderer
func Render(doc *ast.Node, renderer Renderer) []byte { func Render(doc ast.Node, renderer Renderer) []byte {
var buf bytes.Buffer var buf bytes.Buffer
renderer.RenderHeader(&buf, doc) renderer.RenderHeader(&buf, doc)
doc.WalkFunc(func(node *ast.Node, entering bool) ast.WalkStatus { ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
return renderer.RenderNode(&buf, node, entering) return renderer.RenderNode(&buf, node, entering)
}) })
renderer.RenderFooter(&buf, doc) renderer.RenderFooter(&buf, doc)

View File

@ -179,7 +179,7 @@ func (p *Parser) block(data []byte) {
// or // or
// ______ // ______
if p.isHRule(data) { if p.isHRule(data) {
p.addBlock(&ast.HorizontalRuleData{}, nil) p.addBlock(&ast.HorizontalRule{}, nil)
i := skipUntilChar(data, 0, '\n') i := skipUntilChar(data, 0, '\n')
data = data[i:] data = data[i:]
continue continue
@ -250,10 +250,10 @@ func (p *Parser) block(data []byte) {
p.nesting-- p.nesting--
} }
func (p *Parser) addBlock(d ast.NodeData, content []byte) *ast.Node { func (p *Parser) addBlock(n ast.Node, content []byte) ast.Node {
p.closeUnmatchedBlocks() p.closeUnmatchedBlocks()
container := p.addChild(d, 0) container := p.addChild(n, 0)
container.Content = content container.AsTreeNode().Content = content
return container return container
} }
@ -307,7 +307,7 @@ func (p *Parser) prefixHeading(data []byte) int {
if id == "" && p.extensions&AutoHeadingIDs != 0 { if id == "" && p.extensions&AutoHeadingIDs != 0 {
id = SanitizeAnchorName(string(data[i:end])) id = SanitizeAnchorName(string(data[i:end]))
} }
d := &ast.HeadingData{ d := &ast.Heading{
HeadingID: id, HeadingID: id,
Level: level, Level: level,
} }
@ -357,7 +357,7 @@ func (p *Parser) titleBlock(data []byte, doRender bool) int {
consumed := len(data) consumed := len(data)
data = bytes.TrimPrefix(data, []byte("% ")) data = bytes.TrimPrefix(data, []byte("% "))
data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1) data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
d := &ast.HeadingData{ d := &ast.Heading{
Level: 1, Level: 1,
IsTitleblock: true, IsTitleblock: true,
} }
@ -455,13 +455,14 @@ func (p *Parser) html(data []byte, doRender bool) int {
if doRender { if doRender {
// trim newlines // trim newlines
end := backChar(data, i, '\n') end := backChar(data, i, '\n')
finalizeHTMLBlock(p.addBlock(&ast.HTMLBlockData{}, data[:end])) finalizeHTMLBlock(p.addBlock(&ast.HTMLBlock{}, data[:end]))
} }
return i return i
} }
func finalizeHTMLBlock(block *ast.Node) { func finalizeHTMLBlock(blockNode ast.Node) {
block := blockNode.AsTreeNode()
block.Literal = block.Content block.Literal = block.Content
block.Content = nil block.Content = nil
} }
@ -475,7 +476,7 @@ func (p *Parser) htmlComment(data []byte, doRender bool) int {
if doRender { if doRender {
// trim trailing newlines // trim trailing newlines
end := backChar(data, size, '\n') end := backChar(data, size, '\n')
block := p.addBlock(&ast.HTMLBlockData{}, data[:end]) block := p.addBlock(&ast.HTMLBlock{}, data[:end])
finalizeHTMLBlock(block) finalizeHTMLBlock(block)
} }
return size return size
@ -506,7 +507,7 @@ func (p *Parser) htmlHr(data []byte, doRender bool) int {
if doRender { if doRender {
// trim newlines // trim newlines
end := backChar(data, size, '\n') end := backChar(data, size, '\n')
finalizeHTMLBlock(p.addBlock(&ast.HTMLBlockData{}, data[:end])) finalizeHTMLBlock(p.addBlock(&ast.HTMLBlock{}, data[:end]))
} }
return size return size
} }
@ -741,7 +742,7 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
} }
if doRender { if doRender {
d := &ast.CodeBlockData{ d := &ast.CodeBlock{
IsFenced: true, IsFenced: true,
} }
block := p.addBlock(d, work.Bytes()) // TODO: get rid of temp buffer block := p.addBlock(d, work.Bytes()) // TODO: get rid of temp buffer
@ -765,7 +766,8 @@ func unescapeString(str []byte) []byte {
return str return str
} }
func finalizeCodeBlock(block *ast.Node, code *ast.CodeBlockData) { func finalizeCodeBlock(blockNode ast.Node, code *ast.CodeBlock) {
block := blockNode.AsTreeNode()
if code.IsFenced { if code.IsFenced {
newlinePos := bytes.IndexByte(block.Content, '\n') newlinePos := bytes.IndexByte(block.Content, '\n')
firstLine := block.Content[:newlinePos] firstLine := block.Content[:newlinePos]
@ -779,15 +781,15 @@ func finalizeCodeBlock(block *ast.Node, code *ast.CodeBlockData) {
} }
func (p *Parser) table(data []byte) int { func (p *Parser) table(data []byte) int {
table := p.addBlock(&ast.TableData{}, nil) table := p.addBlock(&ast.Table{}, nil)
i, columns := p.tableHeader(data) i, columns := p.tableHeader(data)
if i == 0 { if i == 0 {
p.tip = table.Parent p.tip = table.GetParent()
table.RemoveFromTree() ast.RemoveFromTree(table)
return 0 return 0
} }
p.addBlock(&ast.TableBodyData{}, nil) p.addBlock(&ast.TableBody{}, nil)
for i < len(data) { for i < len(data) {
pipes, rowStart := 0, i pipes, rowStart := 0, i
@ -920,14 +922,14 @@ func (p *Parser) tableHeader(data []byte) (size int, columns []ast.CellAlignFlag
return return
} }
p.addBlock(&ast.TableHeadData{}, nil) p.addBlock(&ast.TableHead{}, nil)
p.tableRow(header, columns, true) p.tableRow(header, columns, true)
size = skipCharN(data, i, '\n', 1) size = skipCharN(data, i, '\n', 1)
return return
} }
func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool) { func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool) {
p.addBlock(&ast.TableRowData{}, nil) p.addBlock(&ast.TableRow{}, nil)
i, col := 0, 0 i, col := 0, 0
if data[i] == '|' && !isBackslashEscaped(data, i) { if data[i] == '|' && !isBackslashEscaped(data, i) {
@ -954,7 +956,7 @@ func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool
cellEnd-- cellEnd--
} }
d := &ast.TableCellData{ d := &ast.TableCell{
IsHeader: header, IsHeader: header,
Align: columns[col], Align: columns[col],
} }
@ -963,7 +965,7 @@ func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool
// pad it out with empty columns to get the right number // pad it out with empty columns to get the right number
for ; col < len(columns); col++ { for ; col < len(columns); col++ {
d := &ast.TableCellData{ d := &ast.TableCell{
IsHeader: header, IsHeader: header,
Align: columns[col], Align: columns[col],
} }
@ -1002,7 +1004,7 @@ func (p *Parser) terminateBlockquote(data []byte, beg, end int) bool {
// parse a blockquote fragment // parse a blockquote fragment
func (p *Parser) quote(data []byte) int { func (p *Parser) quote(data []byte) int {
block := p.addBlock(&ast.BlockQuoteData{}, nil) block := p.addBlock(&ast.BlockQuote{}, nil)
var raw bytes.Buffer var raw bytes.Buffer
beg, end := 0, 0 beg, end := 0, 0
for beg < len(data) { for beg < len(data) {
@ -1085,7 +1087,7 @@ func (p *Parser) code(data []byte) int {
work.WriteByte('\n') work.WriteByte('\n')
d := &ast.CodeBlockData{ d := &ast.CodeBlock{
IsFenced: false, IsFenced: false,
} }
block := p.addBlock(d, work.Bytes()) // TODO: get rid of temp buffer block := p.addBlock(d, work.Bytes()) // TODO: get rid of temp buffer
@ -1148,7 +1150,7 @@ func (p *Parser) dliPrefix(data []byte) int {
func (p *Parser) list(data []byte, flags ast.ListType) int { func (p *Parser) list(data []byte, flags ast.ListType) int {
i := 0 i := 0
flags |= ast.ListItemBeginningOfList flags |= ast.ListItemBeginningOfList
d := &ast.ListData{ d := &ast.List{
ListFlags: flags, ListFlags: flags,
Tight: true, Tight: true,
} }
@ -1166,7 +1168,7 @@ func (p *Parser) list(data []byte, flags ast.ListType) int {
flags &= ^ast.ListItemBeginningOfList flags &= ^ast.ListItemBeginningOfList
} }
above := block.Parent above := block.GetParent()
finalizeList(block, d) finalizeList(block, d)
p.tip = above p.tip = above
return i return i
@ -1174,14 +1176,14 @@ func (p *Parser) list(data []byte, flags ast.ListType) int {
// Returns true if block ends with a blank line, descending if needed // Returns true if block ends with a blank line, descending if needed
// into lists and sublists. // into lists and sublists.
func endsWithBlankLine(block *ast.Node) bool { func endsWithBlankLine(block ast.Node) bool {
// TODO: figure this out. Always false now. // TODO: figure this out. Always false now.
for block != nil { for block != nil {
//if block.lastLineBlank { //if block.lastLineBlank {
//return true //return true
//} //}
switch block.Data.(type) { switch block.(type) {
case *ast.ListData, *ast.ListItemData: case *ast.List, *ast.ListItem:
block = block.LastChild() block = block.LastChild()
default: default:
return false return false
@ -1190,8 +1192,8 @@ func endsWithBlankLine(block *ast.Node) bool {
return false return false
} }
func finalizeList(block *ast.Node, listData *ast.ListData) { func finalizeList(block ast.Node, listData *ast.List) {
items := block.Parent.Children items := block.GetParent().GetChildren()
lastItemIdx := len(items) - 1 lastItemIdx := len(items) - 1
for i, item := range items { for i, item := range items {
isLastItem := i == lastItemIdx isLastItem := i == lastItemIdx
@ -1202,7 +1204,7 @@ func finalizeList(block *ast.Node, listData *ast.ListData) {
} }
// recurse into children of list item, to see if there are spaces // recurse into children of list item, to see if there are spaces
// between any of them: // between any of them:
subItems := item.Parent.Children subItems := item.GetParent().GetChildren()
lastSubItemIdx := len(subItems) - 1 lastSubItemIdx := len(subItems) - 1
for i, subItem := range subItems { for i, subItem := range subItems {
isLastSubItem := i == lastSubItemIdx isLastSubItem := i == lastSubItemIdx
@ -1376,7 +1378,7 @@ gatherlines:
rawBytes := raw.Bytes() rawBytes := raw.Bytes()
d := &ast.ListItemData{ d := &ast.ListItem{
ListFlags: *flags, ListFlags: *flags,
Tight: false, Tight: false,
BulletChar: bulletChar, BulletChar: bulletChar,
@ -1395,13 +1397,12 @@ gatherlines:
} }
} else { } else {
// intermediate render of inline item // intermediate render of inline item
child := p.addChild(&ast.Paragraph{}, 0)
if sublist > 0 { if sublist > 0 {
child := p.addChild(&ast.ParagraphData{}, 0) child.AsTreeNode().Content = rawBytes[:sublist]
child.Content = rawBytes[:sublist]
p.block(rawBytes[sublist:]) p.block(rawBytes[sublist:])
} else { } else {
child := p.addChild(&ast.ParagraphData{}, 0) child.AsTreeNode().Content = rawBytes
child.Content = rawBytes
} }
} }
return line return line
@ -1427,7 +1428,7 @@ func (p *Parser) renderParagraph(data []byte) {
end-- end--
} }
p.addBlock(&ast.ParagraphData{}, data[beg:end]) p.addBlock(&ast.Paragraph{}, data[beg:end])
} }
func (p *Parser) paragraph(data []byte) int { func (p *Parser) paragraph(data []byte) int {
@ -1487,7 +1488,7 @@ func (p *Parser) paragraph(data []byte) int {
id = SanitizeAnchorName(string(data[prev:eol])) id = SanitizeAnchorName(string(data[prev:eol]))
} }
d := &ast.HeadingData{ d := &ast.Heading{
Level: level, Level: level,
HeadingID: id, HeadingID: id,
} }

View File

@ -23,7 +23,7 @@ var (
// data is the complete block being rendered // data is the complete block being rendered
// offset is the number of valid chars before the current cursor // offset is the number of valid chars before the current cursor
func (p *Parser) inline(currBlock *ast.Node, data []byte) { func (p *Parser) inline(currBlock ast.Node, data []byte) {
// handlers might call us recursively: enforce a maximum depth // handlers might call us recursively: enforce a maximum depth
if p.nesting >= p.maxNesting || len(data) == 0 { if p.nesting >= p.maxNesting || len(data) == 0 {
return return
@ -38,9 +38,9 @@ func (p *Parser) inline(currBlock *ast.Node, data []byte) {
end++ end++
} else { } else {
// Copy inactive chars into the output. // Copy inactive chars into the output.
currBlock.AppendChild(newTextNode(data[beg:end])) ast.AppendChild(currBlock, newTextNode(data[beg:end]))
if node != nil { if node != nil {
currBlock.AppendChild(node) ast.AppendChild(currBlock, node)
} }
// Skip past whatever the callback used. // Skip past whatever the callback used.
beg = end + consumed beg = end + consumed
@ -54,13 +54,13 @@ func (p *Parser) inline(currBlock *ast.Node, data []byte) {
if data[end-1] == '\n' { if data[end-1] == '\n' {
end-- end--
} }
currBlock.AppendChild(newTextNode(data[beg:end])) ast.AppendChild(currBlock, newTextNode(data[beg:end]))
} }
p.nesting-- p.nesting--
} }
// single and double emphasis parsing // single and double emphasis parsing
func emphasis(p *Parser, data []byte, offset int) (int, *ast.Node) { func emphasis(p *Parser, data []byte, offset int) (int, ast.Node) {
data = data[offset:] data = data[offset:]
c := data[0] c := data[0]
@ -105,7 +105,7 @@ func emphasis(p *Parser, data []byte, offset int) (int, *ast.Node) {
return 0, nil return 0, nil
} }
func codeSpan(p *Parser, data []byte, offset int) (int, *ast.Node) { func codeSpan(p *Parser, data []byte, offset int) (int, ast.Node) {
data = data[offset:] data = data[offset:]
// count the number of backticks in the delimiter // count the number of backticks in the delimiter
@ -139,7 +139,7 @@ func codeSpan(p *Parser, data []byte, offset int) (int, *ast.Node) {
// render the code span // render the code span
if fBegin != fEnd { if fBegin != fEnd {
code := ast.NewNode(&ast.CodeData{}) code := &ast.Code{}
code.Literal = data[fBegin:fEnd] code.Literal = data[fBegin:fEnd]
return end, code return end, code
} }
@ -148,13 +148,13 @@ func codeSpan(p *Parser, data []byte, offset int) (int, *ast.Node) {
} }
// newline preceded by two spaces becomes <br> // newline preceded by two spaces becomes <br>
func maybeLineBreak(p *Parser, data []byte, offset int) (int, *ast.Node) { func maybeLineBreak(p *Parser, data []byte, offset int) (int, ast.Node) {
origOffset := offset origOffset := offset
offset = skipChar(data, offset, ' ') offset = skipChar(data, offset, ' ')
if offset < len(data) && data[offset] == '\n' { if offset < len(data) && data[offset] == '\n' {
if offset-origOffset >= 2 { if offset-origOffset >= 2 {
return offset - origOffset + 1, ast.NewNode(&ast.HardbreakData{}) return offset - origOffset + 1, &ast.Hardbreak{}
} }
return offset - origOffset, nil return offset - origOffset, nil
} }
@ -162,9 +162,9 @@ func maybeLineBreak(p *Parser, data []byte, offset int) (int, *ast.Node) {
} }
// newline without two spaces works when HardLineBreak is enabled // newline without two spaces works when HardLineBreak is enabled
func lineBreak(p *Parser, data []byte, offset int) (int, *ast.Node) { func lineBreak(p *Parser, data []byte, offset int) (int, ast.Node) {
if p.extensions&HardLineBreak != 0 { if p.extensions&HardLineBreak != 0 {
return 1, ast.NewNode(&ast.HardbreakData{}) return 1, &ast.Hardbreak{}
} }
return 0, nil return 0, nil
} }
@ -185,14 +185,14 @@ func isReferenceStyleLink(data []byte, pos int, t linkType) bool {
return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^' return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^'
} }
func maybeImage(p *Parser, data []byte, offset int) (int, *ast.Node) { func maybeImage(p *Parser, data []byte, offset int) (int, ast.Node) {
if offset < len(data)-1 && data[offset+1] == '[' { if offset < len(data)-1 && data[offset+1] == '[' {
return link(p, data, offset) return link(p, data, offset)
} }
return 0, nil return 0, nil
} }
func maybeInlineFootnote(p *Parser, data []byte, offset int) (int, *ast.Node) { func maybeInlineFootnote(p *Parser, data []byte, offset int) (int, ast.Node) {
if offset < len(data)-1 && data[offset+1] == '[' { if offset < len(data)-1 && data[offset+1] == '[' {
return link(p, data, offset) return link(p, data, offset)
} }
@ -200,7 +200,7 @@ func maybeInlineFootnote(p *Parser, data []byte, offset int) (int, *ast.Node) {
} }
// '[': parse a link or an image or a footnote // '[': parse a link or an image or a footnote
func link(p *Parser, data []byte, offset int) (int, *ast.Node) { func link(p *Parser, data []byte, offset int) (int, ast.Node) {
// no links allowed inside regular links, footnote, and deferred footnotes // no links allowed inside regular links, footnote, and deferred footnotes
if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') { if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
return 0, nil return 0, nil
@ -269,7 +269,7 @@ func link(p *Parser, data []byte, offset int) (int, *ast.Node) {
txtE := i txtE := i
i++ i++
var footnoteNode *ast.Node var footnoteNode ast.Node
// skip any amount of whitespace or newline // skip any amount of whitespace or newline
// (this is much more lax than original markdown syntax) // (this is much more lax than original markdown syntax)
@ -444,7 +444,7 @@ func link(p *Parser, data []byte, offset int) (int, *ast.Node) {
} }
} }
footnoteNode = ast.NewNode(&ast.ListItemData{}) footnoteNode = &ast.ListItem{}
if t == linkInlineFootnote { if t == linkInlineFootnote {
// create a new reference // create a new reference
noteID = len(p.notes) + 1 noteID = len(p.notes) + 1
@ -512,16 +512,15 @@ func link(p *Parser, data []byte, offset int) (int, *ast.Node) {
} }
// call the relevant rendering function // call the relevant rendering function
var linkNode *ast.Node var linkNode ast.Node
switch t { switch t {
case linkNormal: case linkNormal:
d := &ast.LinkData{ linkNode = &ast.Link{
Destination: normalizeURI(uLink), Destination: normalizeURI(uLink),
Title: title, Title: title,
} }
linkNode = ast.NewNode(d)
if len(altContent) > 0 { if len(altContent) > 0 {
linkNode.AppendChild(newTextNode(altContent)) ast.AppendChild(linkNode, newTextNode(altContent))
} else { } else {
// links cannot contain other links, so turn off link parsing // links cannot contain other links, so turn off link parsing
// temporarily and recurse // temporarily and recurse
@ -532,22 +531,20 @@ func link(p *Parser, data []byte, offset int) (int, *ast.Node) {
} }
case linkImg: case linkImg:
d := &ast.ImageData{ linkNode = &ast.Image{
Destination: uLink, Destination: uLink,
Title: title, Title: title,
} }
linkNode = ast.NewNode(d) ast.AppendChild(linkNode, newTextNode(data[1:txtE]))
linkNode.AppendChild(newTextNode(data[1:txtE]))
i++ i++
case linkInlineFootnote, linkDeferredFootnote: case linkInlineFootnote, linkDeferredFootnote:
d := &ast.LinkData{ linkNode = &ast.Link{
Destination: link, Destination: link,
Title: title, Title: title,
NoteID: noteID, NoteID: noteID,
Footnote: footnoteNode, Footnote: footnoteNode,
} }
linkNode = ast.NewNode(d)
if t == linkInlineFootnote { if t == linkInlineFootnote {
i++ i++
} }
@ -599,7 +596,7 @@ const (
) )
// '<' when tags or autolinks are allowed // '<' when tags or autolinks are allowed
func leftAngle(p *Parser, data []byte, offset int) (int, *ast.Node) { func leftAngle(p *Parser, data []byte, offset int) (int, ast.Node) {
data = data[offset:] data = data[offset:]
altype, end := tagLength(data) altype, end := tagLength(data)
if size := p.inlineHTMLComment(data); size > 0 { if size := p.inlineHTMLComment(data); size > 0 {
@ -611,18 +608,17 @@ func leftAngle(p *Parser, data []byte, offset int) (int, *ast.Node) {
unescapeText(&uLink, data[1:end+1-2]) unescapeText(&uLink, data[1:end+1-2])
if uLink.Len() > 0 { if uLink.Len() > 0 {
link := uLink.Bytes() link := uLink.Bytes()
d := &ast.LinkData{ node := &ast.Link{
Destination: link, Destination: link,
} }
node := ast.NewNode(d)
if altype == emailAutolink { if altype == emailAutolink {
d.Destination = append([]byte("mailto:"), link...) node.Destination = append([]byte("mailto:"), link...)
} }
node.AppendChild(newTextNode(stripMailto(link))) ast.AppendChild(node, newTextNode(stripMailto(link)))
return end, node return end, node
} }
} else { } else {
htmlTag := ast.NewNode(&ast.HTMLSpanData{}) htmlTag := &ast.HTMLSpan{}
htmlTag.Literal = data[:end] htmlTag.Literal = data[:end]
return end, htmlTag return end, htmlTag
} }
@ -634,12 +630,12 @@ func leftAngle(p *Parser, data []byte, offset int) (int, *ast.Node) {
// '\\' backslash escape // '\\' backslash escape
var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~") var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
func escape(p *Parser, data []byte, offset int) (int, *ast.Node) { func escape(p *Parser, data []byte, offset int) (int, ast.Node) {
data = data[offset:] data = data[offset:]
if len(data) > 1 { if len(data) > 1 {
if p.extensions&BackslashLineBreak != 0 && data[1] == '\n' { if p.extensions&BackslashLineBreak != 0 && data[1] == '\n' {
return 2, ast.NewNode(&ast.HardbreakData{}) return 2, &ast.Hardbreak{}
} }
if bytes.IndexByte(escapeChars, data[1]) < 0 { if bytes.IndexByte(escapeChars, data[1]) < 0 {
return 0, nil return 0, nil
@ -674,7 +670,7 @@ func unescapeText(ob *bytes.Buffer, src []byte) {
// '&' escaped when it doesn't belong to an entity // '&' escaped when it doesn't belong to an entity
// valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; // valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
func entity(p *Parser, data []byte, offset int) (int, *ast.Node) { func entity(p *Parser, data []byte, offset int) (int, ast.Node) {
data = data[offset:] data = data[offset:]
end := skipCharN(data, 1, '#', 1) end := skipCharN(data, 1, '#', 1)
@ -729,7 +725,7 @@ var protocolPrefixes = [][]byte{
const shortestPrefix = 6 // len("ftp://"), the shortest of the above const shortestPrefix = 6 // len("ftp://"), the shortest of the above
func maybeAutoLink(p *Parser, data []byte, offset int) (int, *ast.Node) { func maybeAutoLink(p *Parser, data []byte, offset int) (int, ast.Node) {
// quick check to rule out most false hits // quick check to rule out most false hits
if p.insideLink || len(data) < offset+shortestPrefix { if p.insideLink || len(data) < offset+shortestPrefix {
return 0, nil return 0, nil
@ -746,7 +742,7 @@ func maybeAutoLink(p *Parser, data []byte, offset int) (int, *ast.Node) {
return 0, nil return 0, nil
} }
func autoLink(p *Parser, data []byte, offset int) (int, *ast.Node) { func autoLink(p *Parser, data []byte, offset int) (int, ast.Node) {
// Now a more expensive check to see if we're not inside an anchor element // Now a more expensive check to see if we're not inside an anchor element
anchorStart := offset anchorStart := offset
offsetFromAnchor := 0 offsetFromAnchor := 0
@ -757,7 +753,7 @@ func autoLink(p *Parser, data []byte, offset int) (int, *ast.Node) {
anchorStr := anchorRe.Find(data[anchorStart:]) anchorStr := anchorRe.Find(data[anchorStart:])
if anchorStr != nil { if anchorStr != nil {
anchorClose := ast.NewNode(&ast.HTMLSpanData{}) anchorClose := &ast.HTMLSpan{}
anchorClose.Literal = anchorStr[offsetFromAnchor:] anchorClose.Literal = anchorStr[offsetFromAnchor:]
return len(anchorStr) - offsetFromAnchor, anchorClose return len(anchorStr) - offsetFromAnchor, anchorClose
} }
@ -856,11 +852,10 @@ func autoLink(p *Parser, data []byte, offset int) (int, *ast.Node) {
unescapeText(&uLink, data[:linkEnd]) unescapeText(&uLink, data[:linkEnd])
if uLink.Len() > 0 { if uLink.Len() > 0 {
d := &ast.LinkData{ node := &ast.Link{
Destination: uLink.Bytes(), Destination: uLink.Bytes(),
} }
node := ast.NewNode(d) ast.AppendChild(node, newTextNode(uLink.Bytes()))
node.AppendChild(newTextNode(uLink.Bytes()))
return linkEnd, node return linkEnd, node
} }
@ -1078,7 +1073,7 @@ func helperFindEmphChar(data []byte, c byte) int {
return 0 return 0
} }
func helperEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) { func helperEmphasis(p *Parser, data []byte, c byte) (int, ast.Node) {
i := 0 i := 0
// skip one symbol if coming from emph3 // skip one symbol if coming from emph3
@ -1109,7 +1104,7 @@ func helperEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) {
} }
} }
emph := ast.NewNode(&ast.EmphData{}) emph := &ast.Emph{}
p.inline(emph, data[:i]) p.inline(emph, data[:i])
return i + 1, emph return i + 1, emph
} }
@ -1118,7 +1113,7 @@ func helperEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) {
return 0, nil return 0, nil
} }
func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) { func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, ast.Node) {
i := 0 i := 0
for i < len(data) { for i < len(data) {
@ -1129,11 +1124,10 @@ func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) {
i += length i += length
if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isSpace(data[i-1]) { if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isSpace(data[i-1]) {
var nodeData ast.NodeData = &ast.StrongData{} var node ast.Node = &ast.Strong{}
if c == '~' { if c == '~' {
nodeData = &ast.DelData{} node = &ast.Del{}
} }
node := ast.NewNode(nodeData)
p.inline(node, data[:i]) p.inline(node, data[:i])
return i + 2, node return i + 2, node
} }
@ -1142,7 +1136,7 @@ func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) {
return 0, nil return 0, nil
} }
func helperTripleEmphasis(p *Parser, data []byte, offset int, c byte) (int, *ast.Node) { func helperTripleEmphasis(p *Parser, data []byte, offset int, c byte) (int, ast.Node) {
i := 0 i := 0
origData := data origData := data
data = data[offset:] data = data[offset:]
@ -1162,9 +1156,9 @@ func helperTripleEmphasis(p *Parser, data []byte, offset int, c byte) (int, *ast
switch { switch {
case i+2 < len(data) && data[i+1] == c && data[i+2] == c: case i+2 < len(data) && data[i+1] == c && data[i+2] == c:
// triple symbol found // triple symbol found
strong := ast.NewNode(&ast.StrongData{}) strong := &ast.Strong{}
em := ast.NewNode(&ast.EmphData{}) em := &ast.Emph{}
strong.AppendChild(em) ast.AppendChild(strong, em)
p.inline(em, data[:i]) p.inline(em, data[:i])
return i + 3, strong return i + 3, strong
case i+1 < len(data) && data[i+1] == c: case i+1 < len(data) && data[i+1] == c:
@ -1186,10 +1180,8 @@ func helperTripleEmphasis(p *Parser, data []byte, offset int, c byte) (int, *ast
return 0, nil return 0, nil
} }
func newTextNode(s []byte) *ast.Node { func newTextNode(d []byte) *ast.Text {
node := ast.NewNode(&ast.TextData{}) return &ast.Text{ast.TreeNode{Literal: d}}
node.Literal = s
return node
} }
func normalizeURI(s []byte) []byte { func normalizeURI(s []byte) []byte {

View File

@ -46,7 +46,7 @@ const (
) )
// for each character that triggers a response when parsing inline data. // for each character that triggers a response when parsing inline data.
type inlineParser func(p *Parser, data []byte, offset int) (int, *ast.Node) type inlineParser func(p *Parser, data []byte, offset int) (int, ast.Node)
// ReferenceOverrideFunc is expected to be called with a reference string and // ReferenceOverrideFunc is expected to be called with a reference string and
// return either a valid Reference type that the reference string maps to or // return either a valid Reference type that the reference string maps to or
@ -75,7 +75,7 @@ type Parser struct {
ReferenceOverride ReferenceOverrideFunc ReferenceOverride ReferenceOverrideFunc
// after parsing, this is AST root of parsed markdown text // after parsing, this is AST root of parsed markdown text
Doc *ast.Node Doc ast.Node
extensions Extensions extensions Extensions
@ -90,9 +90,9 @@ type Parser struct {
// in notes. Slice is nil if footnotes not enabled. // in notes. Slice is nil if footnotes not enabled.
notes []*reference notes []*reference
tip *ast.Node // = doc tip ast.Node // = doc
oldTip *ast.Node oldTip ast.Node
lastMatchedContainer *ast.Node // = doc lastMatchedContainer ast.Node // = doc
allClosed bool allClosed bool
} }
@ -109,7 +109,7 @@ func NewWithExtensions(extension Extensions) *Parser {
refs: make(map[string]*reference), refs: make(map[string]*reference),
maxNesting: 16, maxNesting: 16,
insideLink: false, insideLink: false,
Doc: ast.NewNode(&ast.DocumentData{}), Doc: &ast.Document{},
extensions: extension, extensions: extension,
allClosed: true, allClosed: true,
} }
@ -165,41 +165,43 @@ func (p *Parser) getRef(refid string) (ref *reference, found bool) {
return ref, found return ref, found
} }
func (p *Parser) finalize(block *ast.Node) { func (p *Parser) finalize(block ast.Node) {
above := block.Parent p.tip = block.GetParent()
p.tip = above
} }
func (p *Parser) addChild(d ast.NodeData, offset uint32) *ast.Node { func (p *Parser) addChild(n ast.Node, offset uint32) ast.Node {
return p.addExistingChild(ast.NewNode(d), offset) return p.addExistingChild(n, offset)
} }
func canNodeContain(n *ast.Node, v ast.NodeData) bool { func canNodeContain(n ast.Node, v ast.Node) bool {
switch n.Data.(type) { switch n.(type) {
case *ast.ListData: case *ast.List:
return isListItemData(v) return isListItemData(v)
case *ast.DocumentData, *ast.BlockQuoteData, *ast.ListItemData: case *ast.Document, *ast.BlockQuote, *ast.ListItem:
return !isListItemData(v) return !isListItemData(v)
case *ast.TableData: case *ast.Table:
switch v.(type) { switch v.(type) {
case *ast.TableHeadData, *ast.TableBodyData: case *ast.TableHead, *ast.TableBody:
return true return true
default: default:
return false return false
} }
case *ast.TableHeadData, *ast.TableBodyData: case *ast.TableHead, *ast.TableBody:
return isTableRowData(v) return isTableRowData(v)
case *ast.TableRowData: case *ast.TableRow:
return isTableCellData(v) return isTableCellData(v)
} }
return false return false
} }
func (p *Parser) addExistingChild(node *ast.Node, offset uint32) *ast.Node { func (p *Parser) addExistingChild(node ast.Node, offset uint32) ast.Node {
for !canNodeContain(p.tip, node.Data) { if _, ok := node.(*ast.TreeNode); ok {
panic(fmt.Sprintf("adding %v", node))
}
for !canNodeContain(p.tip, node) {
p.finalize(p.tip) p.finalize(p.tip)
} }
p.tip.AppendChild(node) ast.AppendChild(p.tip, node)
p.tip = node p.tip = node
return node return node
} }
@ -207,7 +209,7 @@ func (p *Parser) addExistingChild(node *ast.Node, offset uint32) *ast.Node {
func (p *Parser) closeUnmatchedBlocks() { func (p *Parser) closeUnmatchedBlocks() {
if !p.allClosed { if !p.allClosed {
for p.oldTip != p.lastMatchedContainer { for p.oldTip != p.lastMatchedContainer {
parent := p.oldTip.Parent parent := p.oldTip.GetParent()
p.finalize(p.oldTip) p.finalize(p.oldTip)
p.oldTip = parent p.oldTip = parent
} }
@ -232,18 +234,18 @@ type Reference struct {
// tree can then be rendered with a default or custom renderer, or // tree can then be rendered with a default or custom renderer, or
// analyzed/transformed by the caller to whatever non-standard needs they have. // analyzed/transformed by the caller to whatever non-standard needs they have.
// The return value is the root node of the syntax tree. // The return value is the root node of the syntax tree.
func (p *Parser) Parse(input []byte) *ast.Node { func (p *Parser) Parse(input []byte) ast.Node {
p.block(input) p.block(input)
// Walk the tree and finish up some of unfinished blocks // Walk the tree and finish up some of unfinished blocks
for p.tip != nil { for p.tip != nil {
p.finalize(p.tip) p.finalize(p.tip)
} }
// Walk the tree again and process inline markdown in each block // Walk the tree again and process inline markdown in each block
p.Doc.WalkFunc(func(node *ast.Node, entering bool) ast.WalkStatus { ast.WalkFunc(p.Doc, func(node ast.Node, entering bool) ast.WalkStatus {
switch node.Data.(type) { switch node.(type) {
case *ast.ParagraphData, *ast.HeadingData, *ast.TableCellData: case *ast.Paragraph, *ast.Heading, *ast.TableCell:
p.inline(node, node.Content) p.inline(node, node.AsTreeNode().Content)
node.Content = nil node.AsTreeNode().Content = nil
} }
return ast.GoToNext return ast.GoToNext
}) })
@ -256,7 +258,7 @@ func (p *Parser) parseRefsToAST() {
return return
} }
p.tip = p.Doc p.tip = p.Doc
d := &ast.ListData{ d := &ast.List{
IsFootnotesList: true, IsFootnotesList: true,
ListFlags: ast.ListTypeOrdered, ListFlags: ast.ListTypeOrdered,
} }
@ -270,7 +272,7 @@ func (p *Parser) parseRefsToAST() {
ref := p.notes[i] ref := p.notes[i]
p.addExistingChild(ref.footnote, 0) p.addExistingChild(ref.footnote, 0)
block := ref.footnote block := ref.footnote
blockData := block.Data.(*ast.ListItemData) blockData := block.(*ast.ListItem)
blockData.ListFlags = flags | ast.ListTypeOrdered blockData.ListFlags = flags | ast.ListTypeOrdered
blockData.RefLink = ref.link blockData.RefLink = ref.link
if ref.hasBlock { if ref.hasBlock {
@ -281,14 +283,14 @@ func (p *Parser) parseRefsToAST() {
} }
flags &^= ast.ListItemBeginningOfList | ast.ListItemContainsBlock flags &^= ast.ListItemBeginningOfList | ast.ListItemContainsBlock
} }
above := block.Parent above := block.GetParent()
finalizeList(block, d) finalizeList(block, d)
p.tip = above p.tip = above
block.WalkFunc(func(node *ast.Node, entering bool) ast.WalkStatus { ast.WalkFunc(block, func(node ast.Node, entering bool) ast.WalkStatus {
switch node.Data.(type) { switch node.(type) {
case *ast.ParagraphData, *ast.HeadingData: case *ast.Paragraph, *ast.Heading:
p.inline(node, node.Content) p.inline(node, node.AsTreeNode().Content)
node.Content = nil node.AsTreeNode().Content = nil
} }
return ast.GoToNext return ast.GoToNext
}) })
@ -365,7 +367,7 @@ type reference struct {
title []byte title []byte
noteID int // 0 if not a footnote ref noteID int // 0 if not a footnote ref
hasBlock bool hasBlock bool
footnote *ast.Node // a link to the Item node within a list of footnotes footnote ast.Node // a link to the Item node within a list of footnotes
text []byte // only gets populated by refOverride feature with Reference.Text text []byte // only gets populated by refOverride feature with Reference.Text
} }
@ -770,17 +772,17 @@ func slugify(in []byte) []byte {
return out[a : b+1] return out[a : b+1]
} }
func isTableRowData(d ast.NodeData) bool { func isTableRowData(d ast.Node) bool {
_, ok := d.(*ast.TableRowData) _, ok := d.(*ast.TableRow)
return ok return ok
} }
func isTableCellData(d ast.NodeData) bool { func isTableCellData(d ast.Node) bool {
_, ok := d.(*ast.TableCellData) _, ok := d.(*ast.TableCell)
return ok return ok
} }
func isListItemData(d ast.NodeData) bool { func isListItemData(d ast.Node) bool {
_, ok := d.(*ast.ListItemData) _, ok := d.(*ast.ListItem)
return ok return ok
} }

View File

@ -157,3 +157,33 @@ PASS
ok github.com/gomarkdown/markdown 43.477s ok github.com/gomarkdown/markdown 43.477s
``` ```
It's slower (but opens up possibilities for further improvements). It's slower (but opens up possibilities for further improvements).
After refactoring to make ast.Node a top-level thing.
```
BenchmarkEscapeHTML-8 2000000 829 ns/op 0 B/op 0 allocs/op
BenchmarkSmartDoubleQuotes-8 300000 3998 ns/op 6192 B/op 31 allocs/op
BenchmarkReferenceAmps-8 50000 27389 ns/op 15480 B/op 153 allocs/op
BenchmarkReferenceAutoLinks-8 50000 23106 ns/op 14656 B/op 137 allocs/op
BenchmarkReferenceBackslashEscapes-8 10000 112435 ns/op 36696 B/op 315 allocs/op
BenchmarkReferenceBlockquotesWithCodeBlocks-8 200000 9227 ns/op 7856 B/op 46 allocs/op
BenchmarkReferenceCodeBlocks-8 200000 10469 ns/op 9248 B/op 54 allocs/op
BenchmarkReferenceCodeSpans-8 200000 10522 ns/op 8368 B/op 54 allocs/op
BenchmarkReferenceHardWrappedPara-8 200000 6354 ns/op 6784 B/op 34 allocs/op
BenchmarkReferenceHorizontalRules-8 50000 32393 ns/op 13952 B/op 87 allocs/op
BenchmarkReferenceInlineHTMLAdvances-8 200000 6894 ns/op 7238 B/op 40 allocs/op
BenchmarkReferenceInlineHTMLSimple-8 50000 32942 ns/op 15864 B/op 110 allocs/op
BenchmarkReferenceInlineHTMLComments-8 200000 8181 ns/op 7776 B/op 44 allocs/op
BenchmarkReferenceLinksInline-8 100000 21679 ns/op 14400 B/op 148 allocs/op
BenchmarkReferenceLinksReference-8 20000 83928 ns/op 36688 B/op 473 allocs/op
BenchmarkReferenceLinksShortcut-8 100000 22053 ns/op 13872 B/op 153 allocs/op
BenchmarkReferenceLiterQuotesInTitles-8 100000 10784 ns/op 9296 B/op 81 allocs/op
BenchmarkReferenceMarkdownBasics-8 5000 237097 ns/op 87760 B/op 480 allocs/op
BenchmarkReferenceMarkdownSyntax-8 1000 1465402 ns/op 300769 B/op 1896 allocs/op
BenchmarkReferenceNestedBlockquotes-8 200000 7461 ns/op 7152 B/op 45 allocs/op
BenchmarkReferenceOrderedAndUnorderedLists-8 5000 212256 ns/op 53724 B/op 553 allocs/op
BenchmarkReferenceStrongAndEm-8 100000 13018 ns/op 9264 B/op 72 allocs/op
BenchmarkReferenceTabs-8 100000 15005 ns/op 10752 B/op 71 allocs/op
BenchmarkReferenceTidyness-8 200000 10308 ns/op 8292 B/op 58 allocs/op
PASS
ok github.com/gomarkdown/markdown 42.176s
```