mirror of
https://github.com/status-im/markdown.git
synced 2025-02-21 15:48:13 +00:00
complete refactor
This commit is contained in:
parent
7e13677aaf
commit
242d66580e
307
ast/node.go
307
ast/node.go
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
273
html/renderer.go
273
html/renderer.go
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
10
markdown.go
10
markdown.go
@ -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)
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
100
parser/inline.go
100
parser/inline.go
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
```
|
||||||
|
Loading…
x
Reference in New Issue
Block a user