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 (
"bytes"
"fmt"
"reflect"
)
// ListType contains bitwise or'ed flags for list and list item objects.
@ -33,19 +34,79 @@ const (
TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
)
// NodeData represents data field of Node
type NodeData interface{}
// DocumentData represents top-level document node
type DocumentData struct {
// Node defines an ast node
type Node interface {
AsTreeNode() *TreeNode
GetParent() Node
GetChildren() []Node
FirstChild() Node
LastChild() Node
}
// BlockQuoteData represents data for block quote node
type BlockQuoteData struct {
// TreeNode is a common part of all nodes, used to represent tree and contain
// 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
type ListData struct {
// AsTreeNode returns itself as *TreeNode
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
Tight bool // Skip <p>s around list item data if true
BulletChar byte // '*', '+' or '-' in bullet lists
@ -54,8 +115,10 @@ type ListData struct {
IsFootnotesList bool // This is a list of footnotes
}
// ListItemData represents data for list item node
type ListItemData struct {
// ListItem represents data for list item node
type ListItem struct {
TreeNode
ListFlags ListType
Tight bool // Skip <p>s around list item data if true
BulletChar byte // '*', '+' or '-' in bullet lists
@ -64,57 +127,72 @@ type ListItemData struct {
IsFootnotesList bool // This is a list of footnotes
}
// ParagraphData represents data for paragraph node
type ParagraphData struct {
// Paragraph represents data for paragraph node
type Paragraph struct {
TreeNode
}
// HeadingData contains fields relevant to a Heading node type.
type HeadingData struct {
// Heading contains fields relevant to a Heading node type.
type Heading struct {
TreeNode
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
}
// HorizontalRuleData represents data for horizontal rule node
type HorizontalRuleData struct {
// HorizontalRule represents data for horizontal rule node
type HorizontalRule struct {
TreeNode
}
// EmphData represents data for emp node
type EmphData struct {
// Emph represents data for emp node
type Emph struct {
TreeNode
}
// StrongData represents data for strong node
type StrongData struct {
// Strong represents data for strong node
type Strong struct {
TreeNode
}
// DelData represents data for del node
type DelData struct {
// Del represents data for del node
type Del struct {
TreeNode
}
// LinkData represents data for link node
type LinkData struct {
// Link represents data for link node
type Link struct {
TreeNode
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.
Footnote Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
}
// ImageData represents data for image node
type ImageData struct {
// Image represents data for image node
type Image struct {
TreeNode
Destination []byte // Destination is what goes into a href
Title []byte // Title is the tooltip thing that goes in a title attribute
}
// TextData represents data for text node
type TextData struct {
// Text represents data for text node
type Text struct {
TreeNode
}
// HTMLBlockData represents data for html node
type HTMLBlockData struct {
// HTMLBlock represents data for html node
type HTMLBlock struct {
TreeNode
}
// CodeBlockData contains fields relevant to a CodeBlock node type.
type CodeBlockData struct {
// CodeBlock contains fields relevant to a CodeBlock node type.
type CodeBlock struct {
TreeNode
IsFenced bool // Specifies whether it's a fenced code block or an indented one
Info []byte // This holds the info string
FenceChar byte
@ -122,66 +200,56 @@ type CodeBlockData struct {
FenceOffset int
}
// SoftbreakData represents data for softbreak node
// Softbreak represents data for softbreak node
// Note: not used currently
type SoftbreakData struct {
type Softbreak struct {
TreeNode
}
// HardbreakData represents data for hard break node
type HardbreakData struct {
// Hardbreak represents data for hard break node
type Hardbreak struct {
TreeNode
}
// CodeData represents data for code node
type CodeData struct {
// Code represents data for code node
type Code struct {
TreeNode
}
// HTMLSpanData represents data for html span node
type HTMLSpanData struct {
// HTMLSpan represents data for html span node
type HTMLSpan struct {
TreeNode
}
// TableData represents data for table node
type TableData struct {
// Table represents data for table node
type Table struct {
TreeNode
}
// TableCellData contains fields relevant to a table cell node type.
type TableCellData struct {
// TableCell contains fields relevant to a table cell node type.
type TableCell struct {
TreeNode
IsHeader bool // This tells if it's under the header row
Align CellAlignFlags // This holds the value for align attribute
}
// TableHeadData represents data for a table head node
type TableHeadData struct {
// TableHead represents data for a table head node
type TableHead struct {
TreeNode
}
// TableBodyData represents data for a tablef body node
type TableBodyData struct {
// TableBody represents data for a tablef body node
type TableBody struct {
TreeNode
}
// TableRowData represents data for a table row node
type TableRowData struct {
}
// Node is a single element in the abstract syntax tree of the parsed document.
// It holds connections to the structurally neighboring nodes and, for certain
// types of nodes, additional information that might be needed when rendering.
type Node struct {
Parent *Node // Points to the parent
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,
}
// TableRow represents data for a table row node
type TableRow struct {
TreeNode
}
/*
func (n *Node) String() string {
ellipsis := ""
snippet := n.Literal
@ -191,8 +259,9 @@ func (n *Node) String() string {
}
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)
for i := 0; i < n; i++ {
if a[i] == node {
@ -203,28 +272,32 @@ func removeNodeFromArray(a []*Node, node *Node) []*Node {
}
// RemoveFromTree removes this node from tree
func (n *Node) RemoveFromTree() {
if n.Parent == nil {
func RemoveFromTree(n Node) {
nt := n.AsTreeNode()
if nt.Parent == nil {
return
}
// important: don't clear n.Children if n has no parent
// we're called from AppendChild and that might happen on a node
// that accumulated Children but hasn't been inserted into the tree
n.Parent.Children = removeNodeFromArray(n.Parent.Children, n)
n.Parent = nil
n.Children = nil
p := nt.Parent.AsTreeNode()
p.Children = removeNodeFromArray(p.Children, n)
nt.Parent = nil
nt.Children = 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.RemoveFromTree()
child.Parent = n
n.Children = append(n.Children, child)
func AppendChild(n Node, child Node) {
childTN := child.AsTreeNode()
RemoveFromTree(child)
childTN.Parent = n
nTN := n.AsTreeNode()
nTN.Children = append(nTN.Children, child)
}
// LastChild returns last child of this node
func (n *Node) LastChild() *Node {
func (n *TreeNode) LastChild() Node {
a := n.Children
if len(a) > 0 {
return a[len(a)-1]
@ -233,7 +306,7 @@ func (n *Node) LastChild() *Node {
}
// FirstChild returns first child of this node
func (n *Node) FirstChild() *Node {
func (n *TreeNode) FirstChild() Node {
a := n.Children
if len(a) > 0 {
return a[0]
@ -241,12 +314,12 @@ func (n *Node) FirstChild() *Node {
return nil
}
// Next returns next sibling of this node
func (n *Node) Next() *Node {
if n.Parent == nil {
// NextNode returns next sibling of this node
func NextNode(n Node) Node {
if isNilNode(n.GetParent()) {
return nil
}
a := n.Parent.Children
a := n.GetParent().AsTreeNode().Children
len := len(a) - 1
for i := 0; i < len; i++ {
if a[i] == n {
@ -256,12 +329,12 @@ func (n *Node) Next() *Node {
return nil
}
// Prev returns previous sibling of this node
func (n *Node) Prev() *Node {
if n.Parent == nil {
// PrevNode returns sibling node before n
func PrevNode(n Node) Node {
if isNilNode(n.GetParent()) {
return nil
}
a := n.Parent.Children
a := n.GetParent().AsTreeNode().Children
len := len(a)
for i := 1; i < len; i++ {
if a[i] == n {
@ -271,10 +344,10 @@ func (n *Node) Prev() *Node {
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
switch n.Data.(type) {
case *HorizontalRuleData, *TextData, *HTMLBlockData, *CodeBlockData, *SoftbreakData, *HardbreakData, *CodeData, *HTMLSpanData:
switch n.(type) {
case *HorizontalRule, *Text, *HTMLBlock, *CodeBlock, *Softbreak, *Hardbreak, *Code, *HTMLSpan:
return false
default:
return true
@ -299,20 +372,20 @@ const (
// 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 interface {
Visit(node *Node, entering bool) WalkStatus
Visit(node Node, entering bool) WalkStatus
}
// 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
func (f NodeVisitorFunc) Visit(node *Node, entering bool) WalkStatus {
func (f NodeVisitorFunc) Visit(node Node, entering bool) WalkStatus {
return f(node, entering)
}
// Walk is a convenience method that instantiates a walker and starts a
// traversal of subtree rooted at n.
func (n *Node) Walk(visitor NodeVisitor) {
func Walk(n Node, visitor NodeVisitor) {
w := newNodeWalker(n)
for w.current != nil {
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
func (n *Node) WalkFunc(f NodeVisitorFunc) {
func WalkFunc(n Node, f NodeVisitorFunc) {
visitor := NodeVisitorFunc(f)
n.Walk(visitor)
Walk(n, visitor)
}
type nodeWalker struct {
current *Node
root *Node
current Node
root Node
entering bool
}
func newNodeWalker(root *Node) *nodeWalker {
func newNodeWalker(root Node) *nodeWalker {
return &nodeWalker{
current: root,
root: root,
@ -349,46 +422,46 @@ func newNodeWalker(root *Node) *nodeWalker {
}
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
return
}
if nw.entering && nw.current.isContainer() {
if nw.entering && isContainer(nw.current) {
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
} else if NextNode(nw.current) == nil {
nw.current = nw.current.GetParent()
nw.entering = false
} else {
nw.current = nw.current.Next()
nw.current = NextNode(nw.current)
nw.entering = true
}
}
func dump(ast *Node) {
func dump(ast Node) {
fmt.Println(dumpString(ast))
}
func dumpR(ast *Node, depth int) string {
func dumpR(ast Node, depth int) string {
if ast == nil {
return ""
}
indent := bytes.Repeat([]byte("\t"), depth)
content := ast.Literal
content := ast.AsTreeNode().Literal
if content == nil {
content = ast.Content
content = ast.AsTreeNode().Content
}
result := fmt.Sprintf("%s%T(%q)\n", indent, ast.Data, content)
for _, n := range ast.Children {
result := fmt.Sprintf("%s%T(%q)\n", indent, ast, content)
for _, n := range ast.GetChildren() {
result += dumpR(n, depth+1)
}
return result
}
func dumpString(ast *Node) string {
func dumpString(ast Node) string {
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
// preferable, so recovery can be easily turned off with doRecover = false.
var candidate string
const doRecover = true
const doRecover = false
if doRecover {
defer func() {
if err := recover(); err != nil {

View File

@ -65,7 +65,7 @@ const (
// rendering of some nodes. If it returns false, Renderer.RenderNode
// will execute its logic. If it returns true, Renderer.RenderNode will
// 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
// 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)
}
func isSmartypantable(node *ast.Node) bool {
switch node.Parent.Data.(type) {
case *ast.LinkData, *ast.CodeBlockData, *ast.CodeData:
func isSmartypantable(node ast.Node) bool {
switch node.GetParent().(type) {
case *ast.Link, *ast.CodeBlock, *ast.Code:
return false
}
return true
@ -320,7 +320,7 @@ func (r *Renderer) outTag(w io.Writer, name string, attrs []string) {
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))
nStr := strconv.Itoa(node.NoteID)
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>`
}
func itemOpenCR(node *ast.Node) bool {
if node.Prev() == nil {
func itemOpenCR(node ast.Node) bool {
if ast.PrevNode(node) == nil {
return false
}
ld := node.Parent.Data.(*ast.ListData)
ld := node.GetParent().(*ast.List)
return !ld.Tight && ld.ListFlags&ast.ListTypeDefinition == 0
}
func skipParagraphTags(node *ast.Node) bool {
parent := node.Parent
grandparent := parent.Parent
if grandparent == nil || !isListData(grandparent.Data) {
func skipParagraphTags(node ast.Node) bool {
parent := node.GetParent()
grandparent := parent.GetParent()
if grandparent == nil || !isListData(grandparent) {
return false
}
isParentTerm := isListItemTerm(parent)
grandparentListData := grandparent.Data.(*ast.ListData)
grandparentListData := grandparent.(*ast.List)
tightOrTerm := grandparentListData.Tight || isParentTerm
return tightOrTerm
}
@ -414,13 +414,13 @@ func (r *Renderer) outHRTag(w io.Writer) {
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 {
var tmp bytes.Buffer
EscapeHTML(&tmp, node.Literal)
r.sr.Process(w, tmp.Bytes())
} else {
if isLinkData(node.Parent.Data) {
if isLinkData(node.GetParent()) {
escLink(w, node.Literal)
} else {
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.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 {
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
dest := nodeData.Destination
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)
}
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 {
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
if needSkipLink(r.opts.Flags, nodeData.Destination) {
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 {
r.linkEnter(w, node, nodeData)
r.linkEnter(w, nodeData)
} 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 = r.addAbsPrefix(dest)
if r.disableTags == 0 {
@ -517,7 +517,7 @@ func (r *Renderer) imageEnter(w io.Writer, node *ast.Node, nodeData *ast.ImageDa
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--
if r.disableTags == 0 {
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
// to be added and when not.
prev := node.Prev()
prev := ast.PrevNode(nodeData)
if prev != nil {
switch prev.Data.(type) {
case *ast.HTMLBlockData, *ast.ListData, *ast.ParagraphData, *ast.HeadingData, *ast.CodeBlockData, *ast.BlockQuoteData, *ast.HorizontalRuleData:
switch prev.(type) {
case *ast.HTMLBlock, *ast.List, *ast.Paragraph, *ast.Heading, *ast.CodeBlock, *ast.BlockQuote, *ast.HorizontalRule:
r.cr(w)
}
}
if isBlockQuoteData(node.Parent.Data) && prev == nil {
if isBlockQuoteData(nodeData.Parent) && prev == nil {
r.cr(w)
}
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>")
if !(isListItemData(node.Parent.Data) && node.Next() == nil) {
if !(isListItemData(node.Parent) && ast.NextNode(node) == nil) {
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) {
return
}
if entering {
r.paragraphEnter(w, node, nodeData)
r.paragraphEnter(w, node)
} 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 {
r.imageEnter(w, node, nodeData)
r.imageEnter(w, node)
} 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>")
EscapeHTML(w, node.Literal)
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 {
return
}
@ -584,7 +584,7 @@ func (r *Renderer) htmlBlock(w io.Writer, node *ast.Node, nodeData *ast.HTMLBloc
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
if nodeData.IsTitleblock {
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)
}
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))
if !(isListItemData(node.Parent.Data) && node.Next() == nil) {
if !(isListItemData(nodeData.Parent) && ast.NextNode(nodeData) == nil) {
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 {
r.headingEnter(w, node, nodeData)
r.headingEnter(w, node)
} else {
r.headingExit(w, node, nodeData)
r.headingExit(w, node)
}
}
@ -625,7 +625,7 @@ func (r *Renderer) horizontalRule(w io.Writer) {
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
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)
if isListItemData(node.Parent.Data) {
grand := node.Parent.Parent
if isListTight(grand.Data) {
if isListItemData(nodeData.Parent) {
grand := nodeData.Parent.GetParent()
if isListTight(grand) {
r.cr(w)
}
}
@ -653,12 +653,12 @@ func (r *Renderer) listEnter(w io.Writer, node *ast.Node, nodeData *ast.ListData
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>"
if nodeData.ListFlags&ast.ListTypeOrdered != 0 {
if node.ListFlags&ast.ListTypeOrdered != 0 {
closeTag = "</ol>"
}
if nodeData.ListFlags&ast.ListTypeDefinition != 0 {
if node.ListFlags&ast.ListTypeDefinition != 0 {
closeTag = "</dl>"
}
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 {
// cr(w)
//}
if isListItemData(node.Parent.Data) && node.Next() != nil {
if isListItemData(node.Parent) && ast.NextNode(node) != nil {
r.cr(w)
}
if isDocumentData(node.Parent.Data) || isBlockQuoteData(node.Parent.Data) {
if isDocumentData(node.Parent) || isBlockQuoteData(node.Parent) {
r.cr(w)
}
if nodeData.IsFootnotesList {
if node.IsFootnotesList {
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 {
r.listEnter(w, node, nodeData)
r.listEnter(w, node)
} 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) {
r.cr(w)
}
if nodeData.RefLink != nil {
slug := slugify(nodeData.RefLink)
if node.RefLink != nil {
slug := slugify(node.RefLink)
r.outs(w, footnoteItem(r.opts.FootnoteAnchorPrefix, slug))
return
}
openTag := "<li>"
if nodeData.ListFlags&ast.ListTypeDefinition != 0 {
if node.ListFlags&ast.ListTypeDefinition != 0 {
openTag = "<dd>"
}
if nodeData.ListFlags&ast.ListTypeTerm != 0 {
if node.ListFlags&ast.ListTypeTerm != 0 {
openTag = "<dt>"
}
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 {
slug := slugify(nodeData.RefLink)
prefix := r.opts.FootnoteAnchorPrefix
@ -726,31 +726,31 @@ func (r *Renderer) listItemExit(w io.Writer, node *ast.Node, nodeData *ast.ListI
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 {
r.listItemEnter(w, node, nodeData)
r.listItemEnter(w, node)
} 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
attrs = appendLanguageAttr(attrs, nodeData.Info)
attrs = appendLanguageAttr(attrs, node.Info)
r.cr(w)
r.outs(w, "<pre>")
r.outTag(w, "<code", attrs)
EscapeHTML(w, node.Literal)
r.outs(w, "</code>")
r.outs(w, "</pre>")
if !isListItemData(node.Parent.Data) {
if !isListItemData(node.Parent) {
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 {
r.outOneOf(w, nodeData.IsHeader, "</th>", "</td>")
r.outOneOf(w, node.IsHeader, "</th>", "</td>")
r.cr(w)
return
}
@ -758,20 +758,20 @@ func (r *Renderer) tableCell(w io.Writer, node *ast.Node, nodeData *ast.TableCel
// entering
var attrs []string
openTag := "<td"
if nodeData.IsHeader {
if node.IsHeader {
openTag = "<th"
}
align := cellAlignment(nodeData.Align)
align := cellAlignment(node.Align)
if align != "" {
attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
}
if node.Prev() == nil {
if ast.PrevNode(node) == nil {
r.cr(w)
}
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 {
r.cr(w)
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.
// The typical behavior is to return GoToNext, which asks for the usual
// 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 {
status, didHandle := r.opts.RenderNodeHook(w, node, entering)
if didHandle {
return status
}
}
switch nodeData := node.Data.(type) {
case *ast.TextData:
r.text(w, node, nodeData)
case *ast.SoftbreakData:
ast.PanicIfTreeNode(node)
switch node := node.(type) {
case *ast.Text:
r.text(w, node)
case *ast.Softbreak:
r.cr(w)
// TODO: make it configurable via out(renderer.softbreak)
case *ast.HardbreakData:
r.hardBreak(w, node, nodeData)
case *ast.EmphData:
case *ast.Hardbreak:
r.hardBreak(w, node)
case *ast.Emph:
r.outOneOf(w, entering, "<em>", "</em>")
case *ast.StrongData:
case *ast.Strong:
r.outOneOf(w, entering, "<strong>", "</strong>")
case *ast.DelData:
case *ast.Del:
r.outOneOf(w, entering, "<del>", "</del>")
case *ast.BlockQuoteData:
case *ast.BlockQuote:
r.outOneOfCr(w, entering, "<blockquote>", "</blockquote>")
case *ast.LinkData:
r.link(w, node, nodeData, entering)
case *ast.ImageData:
case *ast.Link:
r.link(w, node, entering)
case *ast.Image:
if r.opts.Flags&SkipImages != 0 {
return ast.SkipChildren
}
r.image(w, node, nodeData, entering)
case *ast.CodeData:
r.code(w, node, nodeData)
case *ast.CodeBlockData:
r.codeBlock(w, node, nodeData)
case *ast.DocumentData:
r.image(w, node, entering)
case *ast.Code:
r.code(w, node)
case *ast.CodeBlock:
r.codeBlock(w, node)
case *ast.Document:
// do nothing
case *ast.ParagraphData:
r.paragraph(w, node, nodeData, entering)
case *ast.HTMLSpanData:
r.span(w, node, nodeData)
case *ast.HTMLBlockData:
r.htmlBlock(w, node, nodeData)
case *ast.HeadingData:
r.heading(w, node, nodeData, entering)
case *ast.HorizontalRuleData:
case *ast.Paragraph:
r.paragraph(w, node, entering)
case *ast.HTMLSpan:
r.span(w, node)
case *ast.HTMLBlock:
r.htmlBlock(w, node)
case *ast.Heading:
r.heading(w, node, entering)
case *ast.HorizontalRule:
r.horizontalRule(w)
case *ast.ListData:
r.list(w, node, nodeData, entering)
case *ast.ListItemData:
r.listItem(w, node, nodeData, entering)
case *ast.TableData:
case *ast.List:
r.list(w, node, entering)
case *ast.ListItem:
r.listItem(w, node, entering)
case *ast.Table:
r.outOneOfCr(w, entering, "<table>", "</table>")
case *ast.TableCellData:
r.tableCell(w, node, nodeData, entering)
case *ast.TableHeadData:
case *ast.TableCell:
r.tableCell(w, node, entering)
case *ast.TableHead:
r.outOneOfCr(w, entering, "<thead>", "</thead>")
case *ast.TableBodyData:
r.tableBody(w, node, nodeData, entering)
case *ast.TableRowData:
case *ast.TableBody:
r.tableBody(w, node, entering)
case *ast.TableRow:
r.outOneOfCr(w, entering, "<tr>", "</tr>")
default:
//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
}
// 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)
if r.opts.Flags&TOC != 0 {
r.writeTOC(w, ast)
@ -871,7 +872,7 @@ func (r *Renderer) RenderHeader(w io.Writer, ast *ast.Node) {
}
// 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 {
return
}
@ -925,15 +926,15 @@ func (r *Renderer) writeDocumentHeader(w io.Writer) {
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{}
inHeading := false
tocLevel := 0
headingCount := 0
doc.WalkFunc(func(node *ast.Node, entering bool) ast.WalkStatus {
if nodeData, ok := node.Data.(*ast.HeadingData); ok && !nodeData.IsTitleblock {
ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
if nodeData, ok := node.(*ast.Heading); ok && !nodeData.IsTitleblock {
inHeading = entering
if entering {
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()
}
func isListData(d ast.NodeData) bool {
_, ok := d.(*ast.ListData)
func isListData(n ast.Node) bool {
_, ok := n.(*ast.List)
return ok
}
func isListTight(d ast.NodeData) bool {
if listData, ok := d.(*ast.ListData); ok {
func isListTight(d ast.Node) bool {
if listData, ok := d.(*ast.List); ok {
return listData.Tight
}
return false
}
func isListItemData(d ast.NodeData) bool {
_, ok := d.(*ast.ListItemData)
func isListItemData(d ast.Node) bool {
_, ok := d.(*ast.ListItem)
return ok
}
func isListItemTerm(node *ast.Node) bool {
data, ok := node.Data.(*ast.ListItemData)
func isListItemTerm(node ast.Node) bool {
data, ok := node.(*ast.ListItem)
return ok && data.ListFlags&ast.ListTypeTerm != 0
}
func isLinkData(d ast.NodeData) bool {
_, ok := d.(*ast.LinkData)
func isLinkData(d ast.Node) bool {
_, ok := d.(*ast.Link)
return ok
}
func isBlockQuoteData(d ast.NodeData) bool {
_, ok := d.(*ast.BlockQuoteData)
func isBlockQuoteData(d ast.Node) bool {
_, ok := d.(*ast.BlockQuote)
return ok
}
func isDocumentData(d ast.NodeData) bool {
_, ok := d.(*ast.DocumentData)
func isDocumentData(d ast.Node) bool {
_, ok := d.(*ast.Document)
return ok
}

View File

@ -9,7 +9,7 @@ import (
"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
}
@ -32,8 +32,8 @@ func TestRenderNodeHookEmpty(t *testing.T) {
doTestsParam(t, tests, params)
}
func renderHookCodeBlock(w io.Writer, node *ast.Node, entering bool) (ast.WalkStatus, bool) {
_, ok := node.Data.(*ast.CodeBlockData)
func renderHookCodeBlock(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
_, ok := node.(*ast.CodeBlock)
if !ok {
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
// entering=true, then with entering=false). The method should write its
// 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
// 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
// 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(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.
// 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
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)
})
renderer.RenderFooter(&buf, doc)

View File

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

View File

@ -23,7 +23,7 @@ var (
// data is the complete block being rendered
// 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
if p.nesting >= p.maxNesting || len(data) == 0 {
return
@ -38,9 +38,9 @@ func (p *Parser) inline(currBlock *ast.Node, data []byte) {
end++
} else {
// Copy inactive chars into the output.
currBlock.AppendChild(newTextNode(data[beg:end]))
ast.AppendChild(currBlock, newTextNode(data[beg:end]))
if node != nil {
currBlock.AppendChild(node)
ast.AppendChild(currBlock, node)
}
// Skip past whatever the callback used.
beg = end + consumed
@ -54,13 +54,13 @@ func (p *Parser) inline(currBlock *ast.Node, data []byte) {
if data[end-1] == '\n' {
end--
}
currBlock.AppendChild(newTextNode(data[beg:end]))
ast.AppendChild(currBlock, newTextNode(data[beg:end]))
}
p.nesting--
}
// 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:]
c := data[0]
@ -105,7 +105,7 @@ func emphasis(p *Parser, data []byte, offset int) (int, *ast.Node) {
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:]
// 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
if fBegin != fEnd {
code := ast.NewNode(&ast.CodeData{})
code := &ast.Code{}
code.Literal = data[fBegin:fEnd]
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>
func maybeLineBreak(p *Parser, data []byte, offset int) (int, *ast.Node) {
func maybeLineBreak(p *Parser, data []byte, offset int) (int, ast.Node) {
origOffset := offset
offset = skipChar(data, offset, ' ')
if offset < len(data) && data[offset] == '\n' {
if offset-origOffset >= 2 {
return offset - origOffset + 1, ast.NewNode(&ast.HardbreakData{})
return offset - origOffset + 1, &ast.Hardbreak{}
}
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
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 {
return 1, ast.NewNode(&ast.HardbreakData{})
return 1, &ast.Hardbreak{}
}
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] != '^'
}
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] == '[' {
return link(p, data, offset)
}
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] == '[' {
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
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
if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
return 0, nil
@ -269,7 +269,7 @@ func link(p *Parser, data []byte, offset int) (int, *ast.Node) {
txtE := i
i++
var footnoteNode *ast.Node
var footnoteNode ast.Node
// skip any amount of whitespace or newline
// (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 {
// create a new reference
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
var linkNode *ast.Node
var linkNode ast.Node
switch t {
case linkNormal:
d := &ast.LinkData{
linkNode = &ast.Link{
Destination: normalizeURI(uLink),
Title: title,
}
linkNode = ast.NewNode(d)
if len(altContent) > 0 {
linkNode.AppendChild(newTextNode(altContent))
ast.AppendChild(linkNode, newTextNode(altContent))
} else {
// links cannot contain other links, so turn off link parsing
// temporarily and recurse
@ -532,22 +531,20 @@ func link(p *Parser, data []byte, offset int) (int, *ast.Node) {
}
case linkImg:
d := &ast.ImageData{
linkNode = &ast.Image{
Destination: uLink,
Title: title,
}
linkNode = ast.NewNode(d)
linkNode.AppendChild(newTextNode(data[1:txtE]))
ast.AppendChild(linkNode, newTextNode(data[1:txtE]))
i++
case linkInlineFootnote, linkDeferredFootnote:
d := &ast.LinkData{
linkNode = &ast.Link{
Destination: link,
Title: title,
NoteID: noteID,
Footnote: footnoteNode,
}
linkNode = ast.NewNode(d)
if t == linkInlineFootnote {
i++
}
@ -599,7 +596,7 @@ const (
)
// '<' 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:]
altype, end := tagLength(data)
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])
if uLink.Len() > 0 {
link := uLink.Bytes()
d := &ast.LinkData{
node := &ast.Link{
Destination: link,
}
node := ast.NewNode(d)
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
}
} else {
htmlTag := ast.NewNode(&ast.HTMLSpanData{})
htmlTag := &ast.HTMLSpan{}
htmlTag.Literal = data[:end]
return end, htmlTag
}
@ -634,12 +630,12 @@ func leftAngle(p *Parser, data []byte, offset int) (int, *ast.Node) {
// '\\' backslash escape
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:]
if len(data) > 1 {
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 {
return 0, nil
@ -674,7 +670,7 @@ func unescapeText(ob *bytes.Buffer, src []byte) {
// '&' escaped when it doesn't belong to an entity
// 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:]
end := skipCharN(data, 1, '#', 1)
@ -729,7 +725,7 @@ var protocolPrefixes = [][]byte{
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
if p.insideLink || len(data) < offset+shortestPrefix {
return 0, nil
@ -746,7 +742,7 @@ func maybeAutoLink(p *Parser, data []byte, offset int) (int, *ast.Node) {
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
anchorStart := offset
offsetFromAnchor := 0
@ -757,7 +753,7 @@ func autoLink(p *Parser, data []byte, offset int) (int, *ast.Node) {
anchorStr := anchorRe.Find(data[anchorStart:])
if anchorStr != nil {
anchorClose := ast.NewNode(&ast.HTMLSpanData{})
anchorClose := &ast.HTMLSpan{}
anchorClose.Literal = anchorStr[offsetFromAnchor:]
return len(anchorStr) - offsetFromAnchor, anchorClose
}
@ -856,11 +852,10 @@ func autoLink(p *Parser, data []byte, offset int) (int, *ast.Node) {
unescapeText(&uLink, data[:linkEnd])
if uLink.Len() > 0 {
d := &ast.LinkData{
node := &ast.Link{
Destination: uLink.Bytes(),
}
node := ast.NewNode(d)
node.AppendChild(newTextNode(uLink.Bytes()))
ast.AppendChild(node, newTextNode(uLink.Bytes()))
return linkEnd, node
}
@ -1078,7 +1073,7 @@ func helperFindEmphChar(data []byte, c byte) int {
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
// 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])
return i + 1, emph
}
@ -1118,7 +1113,7 @@ func helperEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) {
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
for i < len(data) {
@ -1129,11 +1124,10 @@ func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) {
i += length
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 == '~' {
nodeData = &ast.DelData{}
node = &ast.Del{}
}
node := ast.NewNode(nodeData)
p.inline(node, data[:i])
return i + 2, node
}
@ -1142,7 +1136,7 @@ func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, *ast.Node) {
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
origData := data
data = data[offset:]
@ -1162,9 +1156,9 @@ func helperTripleEmphasis(p *Parser, data []byte, offset int, c byte) (int, *ast
switch {
case i+2 < len(data) && data[i+1] == c && data[i+2] == c:
// triple symbol found
strong := ast.NewNode(&ast.StrongData{})
em := ast.NewNode(&ast.EmphData{})
strong.AppendChild(em)
strong := &ast.Strong{}
em := &ast.Emph{}
ast.AppendChild(strong, em)
p.inline(em, data[:i])
return i + 3, strong
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
}
func newTextNode(s []byte) *ast.Node {
node := ast.NewNode(&ast.TextData{})
node.Literal = s
return node
func newTextNode(d []byte) *ast.Text {
return &ast.Text{ast.TreeNode{Literal: d}}
}
func normalizeURI(s []byte) []byte {

View File

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

View File

@ -157,3 +157,33 @@ PASS
ok github.com/gomarkdown/markdown 43.477s
```
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
```