Performance optimizations

This commit is contained in:
yuin 2019-05-01 20:32:41 +09:00
parent a8a4629dd9
commit 987f65f813
20 changed files with 868 additions and 366 deletions

View file

@ -121,9 +121,9 @@ Though goldmark builds clean extensible AST structure and get full compliance wi
Commonmark, it is resonably fast and less memory consumption. Commonmark, it is resonably fast and less memory consumption.
``` ```
BenchmarkGoldMark-4 200 7291402 ns/op 2259603 B/op 16867 allocs/op BenchmarkGoldMark-4 200 7981524 ns/op 2485650 B/op 15716 allocs/op
BenchmarkGolangCommonMark-4 200 7709939 ns/op 3053760 B/op 18682 allocs/op BenchmarkGolangCommonMark-4 200 8609737 ns/op 3053758 B/op 18681 allocs/op
BenchmarkBlackFriday-4 300 5776369 ns/op 3356386 B/op 17480 allocs/op BenchmarkBlackFriday-4 200 6311112 ns/op 3356762 B/op 17481 allocs/op
``` ```
Donation Donation

View file

@ -12,17 +12,50 @@ import (
type NodeType int type NodeType int
const ( const (
// BlockNode indicates that a node is kind of block nodes. // TypeBlock indicates that a node is kind of block nodes.
BlockNode NodeType = iota + 1 TypeBlock NodeType = iota + 1
// InlineNode indicates that a node is kind of inline nodes. // TypeInline indicates that a node is kind of inline nodes.
InlineNode TypeInline
// TypeDocument indicates that a node is kind of document nodes.
TypeDocument
) )
// NodeKind indicates more specific type than NodeType.
type NodeKind int
func (k NodeKind) String() string {
return kindNames[k]
}
var kindMax NodeKind
var kindNames = []string{""}
// NewNodeKind returns a new Kind value.
func NewNodeKind(name string) NodeKind {
kindMax++
kindNames = append(kindNames, name)
return kindMax
}
// An Attribute is an attribute of the Node
type Attribute struct {
Name []byte
Value []byte
}
var attrNameIDS = []byte("#")
var attrNameID = []byte("id")
var attrNameClassS = []byte(".")
var attrNameClass = []byte("class")
// A Node interface defines basic AST node functionalities. // A Node interface defines basic AST node functionalities.
type Node interface { type Node interface {
// Type returns a type of this node. // Type returns a type of this node.
Type() NodeType Type() NodeType
// Kind returns a kind of this node.
Kind() NodeKind
// NextSibling returns a next sibling node of this node. // NextSibling returns a next sibling node of this node.
NextSibling() Node NextSibling() Node
@ -106,6 +139,18 @@ type Node interface {
// IsRaw returns true if contents should be rendered as 'raw' contents. // IsRaw returns true if contents should be rendered as 'raw' contents.
IsRaw() bool IsRaw() bool
// SetAttribute sets given value to the attributes.
SetAttribute(name, value []byte)
// Attribute returns a (attribute value, true) if an attribute
// associated with given name is found, otherwise
// (nil, false)
Attribute(name []byte) ([]byte, bool)
// Attributes returns a list of attributes.
// This may be a nil if there are no attributes.
Attributes() []Attribute
} }
// A BaseNode struct implements the Node interface. // A BaseNode struct implements the Node interface.
@ -115,6 +160,8 @@ type BaseNode struct {
parent Node parent Node
next Node next Node
prev Node prev Node
childCount int
attributes []Attribute
} }
func ensureIsolated(v Node) { func ensureIsolated(v Node) {
@ -153,6 +200,7 @@ func (n *BaseNode) RemoveChild(self, v Node) {
if v.Parent() != self { if v.Parent() != self {
return return
} }
n.childCount--
prev := v.PreviousSibling() prev := v.PreviousSibling()
next := v.NextSibling() next := v.NextSibling()
if prev != nil { if prev != nil {
@ -179,6 +227,7 @@ func (n *BaseNode) RemoveChildren(self Node) {
} }
n.firstChild = nil n.firstChild = nil
n.lastChild = nil n.lastChild = nil
n.childCount = 0
} }
// FirstChild implements Node.FirstChild . // FirstChild implements Node.FirstChild .
@ -193,11 +242,7 @@ func (n *BaseNode) LastChild() Node {
// ChildCount implements Node.ChildCount . // ChildCount implements Node.ChildCount .
func (n *BaseNode) ChildCount() int { func (n *BaseNode) ChildCount() int {
count := 0 return n.childCount
for c := n.firstChild; c != nil; c = c.NextSibling() {
count++
}
return count
} }
// Parent implements Node.Parent . // Parent implements Node.Parent .
@ -224,6 +269,7 @@ func (n *BaseNode) AppendChild(self, v Node) {
} }
v.SetParent(self) v.SetParent(self)
n.lastChild = v n.lastChild = v
n.childCount++
} }
// ReplaceChild implements Node.ReplaceChild . // ReplaceChild implements Node.ReplaceChild .
@ -239,6 +285,7 @@ func (n *BaseNode) InsertAfter(self, v1, insertee Node) {
// InsertBefore implements Node.InsertBefore . // InsertBefore implements Node.InsertBefore .
func (n *BaseNode) InsertBefore(self, v1, insertee Node) { func (n *BaseNode) InsertBefore(self, v1, insertee Node) {
n.childCount++
if v1 == nil { if v1 == nil {
n.AppendChild(self, insertee) n.AppendChild(self, insertee)
return return
@ -269,15 +316,51 @@ func (n *BaseNode) Text(source []byte) []byte {
return buf.Bytes() return buf.Bytes()
} }
// SetAttribute implements Node.SetAttribute.
func (n *BaseNode) SetAttribute(name, value []byte) {
if n.attributes == nil {
n.attributes = make([]Attribute, 0, 10)
n.attributes = append(n.attributes, Attribute{name, value})
return
}
for i, a := range n.attributes {
if bytes.Equal(a.Name, name) {
n.attributes[i].Name = name
n.attributes[i].Value = value
return
}
}
n.attributes = append(n.attributes, Attribute{name, value})
return
}
// Attribute implements Node.Attribute.
func (n *BaseNode) Attribute(name []byte) ([]byte, bool) {
if n.attributes == nil {
return nil, false
}
for i, a := range n.attributes {
if bytes.Equal(a.Name, name) {
return n.attributes[i].Value, true
}
}
return nil, false
}
// Attributes implements Node.Attributes
func (n *BaseNode) Attributes() []Attribute {
return n.attributes
}
// DumpHelper is a helper function to implement Node.Dump. // DumpHelper is a helper function to implement Node.Dump.
// name is a name of the node.
// kv is pairs of an attribute name and an attribute value. // kv is pairs of an attribute name and an attribute value.
// cb is a function called after wrote a name and attributes. // cb is a function called after wrote a name and attributes.
func DumpHelper(v Node, source []byte, level int, name string, kv map[string]string, cb func(int)) { func DumpHelper(v Node, source []byte, level int, kv map[string]string, cb func(int)) {
name := v.Kind().String()
indent := strings.Repeat(" ", level) indent := strings.Repeat(" ", level)
fmt.Printf("%s%s {\n", indent, name) fmt.Printf("%s%s {\n", indent, name)
indent2 := strings.Repeat(" ", level+1) indent2 := strings.Repeat(" ", level+1)
if v.Type() == BlockNode { if v.Type() == TypeBlock {
fmt.Printf("%sRawText: \"", indent2) fmt.Printf("%sRawText: \"", indent2)
for i := 0; i < v.Lines().Len(); i++ { for i := 0; i < v.Lines().Len(); i++ {
line := v.Lines().At(i) line := v.Lines().At(i)

View file

@ -15,7 +15,7 @@ type BaseBlock struct {
// Type implements Node.Type // Type implements Node.Type
func (b *BaseBlock) Type() NodeType { func (b *BaseBlock) Type() NodeType {
return BlockNode return TypeBlock
} }
// IsRaw implements Node.IsRaw // IsRaw implements Node.IsRaw
@ -51,9 +51,22 @@ type Document struct {
BaseBlock BaseBlock
} }
// KindDocument is a NodeKind of the Document node.
var KindDocument = NewNodeKind("Document")
// Dump impelements Node.Dump . // Dump impelements Node.Dump .
func (n *Document) Dump(source []byte, level int) { func (n *Document) Dump(source []byte, level int) {
DumpHelper(n, source, level, "Document", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// Type implements Node.Type .
func (n *Document) Type() NodeType {
return TypeDocument
}
// Kind implements Node.Kind.
func (n *Document) Kind() NodeKind {
return KindDocument
} }
// NewDocument returns a new Document node. // NewDocument returns a new Document node.
@ -71,7 +84,15 @@ type TextBlock struct {
// Dump impelements Node.Dump . // Dump impelements Node.Dump .
func (n *TextBlock) Dump(source []byte, level int) { func (n *TextBlock) Dump(source []byte, level int) {
DumpHelper(n, source, level, "TextBlock", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// KindTextBlock is a NodeKind of the TextBlock node.
var KindTextBlock = NewNodeKind("TextBlock")
// Kind implements Node.Kind.
func (n *TextBlock) Kind() NodeKind {
return KindTextBlock
} }
// NewTextBlock returns a new TextBlock node. // NewTextBlock returns a new TextBlock node.
@ -88,7 +109,15 @@ type Paragraph struct {
// Dump impelements Node.Dump . // Dump impelements Node.Dump .
func (n *Paragraph) Dump(source []byte, level int) { func (n *Paragraph) Dump(source []byte, level int) {
DumpHelper(n, source, level, "Paragraph", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// KindParagraph is a NodeKind of the Paragraph node.
var KindParagraph = NewNodeKind("Paragraph")
// Kind implements Node.Kind.
func (n *Paragraph) Kind() NodeKind {
return KindParagraph
} }
// NewParagraph returns a new Paragraph node. // NewParagraph returns a new Paragraph node.
@ -111,9 +140,6 @@ type Heading struct {
// Level returns a level of this heading. // Level returns a level of this heading.
// This value is between 1 and 6. // This value is between 1 and 6.
Level int Level int
// ID returns an ID of this heading.
ID []byte
} }
// Dump impelements Node.Dump . // Dump impelements Node.Dump .
@ -121,7 +147,15 @@ func (n *Heading) Dump(source []byte, level int) {
m := map[string]string{ m := map[string]string{
"Level": fmt.Sprintf("%d", n.Level), "Level": fmt.Sprintf("%d", n.Level),
} }
DumpHelper(n, source, level, "Heading", m, nil) DumpHelper(n, source, level, m, nil)
}
// KindHeading is a NodeKind of the Heading node.
var KindHeading = NewNodeKind("Heading")
// Kind implements Node.Kind.
func (n *Heading) Kind() NodeKind {
return KindHeading
} }
// NewHeading returns a new Heading node. // NewHeading returns a new Heading node.
@ -139,7 +173,15 @@ type ThemanticBreak struct {
// Dump impelements Node.Dump . // Dump impelements Node.Dump .
func (n *ThemanticBreak) Dump(source []byte, level int) { func (n *ThemanticBreak) Dump(source []byte, level int) {
DumpHelper(n, source, level, "ThemanticBreak", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// KindThemanticBreak is a NodeKind of the ThemanticBreak node.
var KindThemanticBreak = NewNodeKind("ThemanticBreak")
// Kind implements Node.Kind.
func (n *ThemanticBreak) Kind() NodeKind {
return KindThemanticBreak
} }
// NewThemanticBreak returns a new ThemanticBreak node. // NewThemanticBreak returns a new ThemanticBreak node.
@ -161,7 +203,15 @@ func (n *CodeBlock) IsRaw() bool {
// Dump impelements Node.Dump . // Dump impelements Node.Dump .
func (n *CodeBlock) Dump(source []byte, level int) { func (n *CodeBlock) Dump(source []byte, level int) {
DumpHelper(n, source, level, "CodeBlock", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// KindCodeBlock is a NodeKind of the CodeBlock node.
var KindCodeBlock = NewNodeKind("CodeBlock")
// Kind implements Node.Kind.
func (n *CodeBlock) Kind() NodeKind {
return KindCodeBlock
} }
// NewCodeBlock returns a new CodeBlock node. // NewCodeBlock returns a new CodeBlock node.
@ -189,7 +239,15 @@ func (n *FencedCodeBlock) Dump(source []byte, level int) {
if n.Info != nil { if n.Info != nil {
m["Info"] = fmt.Sprintf("\"%s\"", n.Info.Text(source)) m["Info"] = fmt.Sprintf("\"%s\"", n.Info.Text(source))
} }
DumpHelper(n, source, level, "FencedCodeBlock", m, nil) DumpHelper(n, source, level, m, nil)
}
// KindFencedCodeBlock is a NodeKind of the FencedCodeBlock node.
var KindFencedCodeBlock = NewNodeKind("FencedCodeBlock")
// Kind implements Node.Kind.
func (n *FencedCodeBlock) Kind() NodeKind {
return KindFencedCodeBlock
} }
// NewFencedCodeBlock return a new FencedCodeBlock node. // NewFencedCodeBlock return a new FencedCodeBlock node.
@ -207,7 +265,15 @@ type Blockquote struct {
// Dump impelements Node.Dump . // Dump impelements Node.Dump .
func (n *Blockquote) Dump(source []byte, level int) { func (n *Blockquote) Dump(source []byte, level int) {
DumpHelper(n, source, level, "Blockquote", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// KindBlockquote is a NodeKind of the Blockquote node.
var KindBlockquote = NewNodeKind("Blockquote")
// Kind implements Node.Kind.
func (n *Blockquote) Kind() NodeKind {
return KindBlockquote
} }
// NewBlockquote returns a new Blockquote node. // NewBlockquote returns a new Blockquote node.
@ -246,18 +312,23 @@ func (l *List) CanContinue(marker byte, isOrdered bool) bool {
// Dump implements Node.Dump. // Dump implements Node.Dump.
func (l *List) Dump(source []byte, level int) { func (l *List) Dump(source []byte, level int) {
name := "List"
if l.IsOrdered() {
name = "OrderedList"
}
m := map[string]string{ m := map[string]string{
"Marker": fmt.Sprintf("%c", l.Marker), "Ordered": fmt.Sprintf("%v", l.IsOrdered()),
"Tight": fmt.Sprintf("%v", l.IsTight), "Marker": fmt.Sprintf("%c", l.Marker),
"Tight": fmt.Sprintf("%v", l.IsTight),
} }
if l.IsOrdered() { if l.IsOrdered() {
m["Start"] = fmt.Sprintf("%d", l.Start) m["Start"] = fmt.Sprintf("%d", l.Start)
} }
DumpHelper(l, source, level, name, m, nil) DumpHelper(l, source, level, m, nil)
}
// KindList is a NodeKind of the List node.
var KindList = NewNodeKind("List")
// Kind implements Node.Kind.
func (l *List) Kind() NodeKind {
return KindList
} }
// NewList returns a new List node. // NewList returns a new List node.
@ -279,7 +350,15 @@ type ListItem struct {
// Dump implements Node.Dump. // Dump implements Node.Dump.
func (n *ListItem) Dump(source []byte, level int) { func (n *ListItem) Dump(source []byte, level int) {
DumpHelper(n, source, level, "ListItem", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// KindListItem is a NodeKind of the ListItem node.
var KindListItem = NewNodeKind("ListItem")
// Kind implements Node.Kind.
func (n *ListItem) Kind() NodeKind {
return KindListItem
} }
// NewListItem returns a new ListItem node. // NewListItem returns a new ListItem node.
@ -354,6 +433,14 @@ func (n *HTMLBlock) Dump(source []byte, level int) {
fmt.Printf("%s}\n", indent) fmt.Printf("%s}\n", indent)
} }
// KindHTMLBlock is a NodeKind of the HTMLBlock node.
var KindHTMLBlock = NewNodeKind("HTMLBlock")
// Kind implements Node.Kind.
func (n *HTMLBlock) Kind() NodeKind {
return KindHTMLBlock
}
// NewHTMLBlock returns a new HTMLBlock node. // NewHTMLBlock returns a new HTMLBlock node.
func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock { func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock {
return &HTMLBlock{ return &HTMLBlock{

View file

@ -15,7 +15,7 @@ type BaseInline struct {
// Type implements Node.Type // Type implements Node.Type
func (b *BaseInline) Type() NodeType { func (b *BaseInline) Type() NodeType {
return InlineNode return TypeInline
} }
// IsRaw implements Node.IsRaw // IsRaw implements Node.IsRaw
@ -133,6 +133,14 @@ func (n *Text) Dump(source []byte, level int) {
fmt.Printf("%sText: \"%s\"\n", strings.Repeat(" ", level), strings.TrimRight(string(n.Text(source)), "\n")) fmt.Printf("%sText: \"%s\"\n", strings.Repeat(" ", level), strings.TrimRight(string(n.Text(source)), "\n"))
} }
// KindText is a NodeKind of the Text node.
var KindText = NewNodeKind("Text")
// Kind implements Node.Kind.
func (n *Text) Kind() NodeKind {
return KindText
}
// NewText returns a new Text node. // NewText returns a new Text node.
func NewText() *Text { func NewText() *Text {
return &Text{ return &Text{
@ -166,8 +174,7 @@ func MergeOrAppendTextSegment(parent Node, s textm.Segment) {
last := parent.LastChild() last := parent.LastChild()
t, ok := last.(*Text) t, ok := last.(*Text)
if ok && t.Segment.Stop == s.Start && !t.SoftLineBreak() { if ok && t.Segment.Stop == s.Start && !t.SoftLineBreak() {
ts := t.Segment t.Segment = t.Segment.WithStop(s.Stop)
t.Segment = ts.WithStop(s.Stop)
} else { } else {
parent.AppendChild(parent, NewTextSegment(s)) parent.AppendChild(parent, NewTextSegment(s))
} }
@ -207,7 +214,15 @@ func (n *CodeSpan) IsBlank(source []byte) bool {
// Dump implements Node.Dump // Dump implements Node.Dump
func (n *CodeSpan) Dump(source []byte, level int) { func (n *CodeSpan) Dump(source []byte, level int) {
DumpHelper(n, source, level, "CodeSpan", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// KindCodeSpan is a NodeKind of the CodeSpan node.
var KindCodeSpan = NewNodeKind("CodeSpan")
// Kind implements Node.Kind.
func (n *CodeSpan) Kind() NodeKind {
return KindCodeSpan
} }
// NewCodeSpan returns a new CodeSpan node. // NewCodeSpan returns a new CodeSpan node.
@ -225,13 +240,20 @@ type Emphasis struct {
Level int Level int
} }
// Inline implements Inline.Inline.
func (n *Emphasis) Inline() {
}
// Dump implements Node.Dump. // Dump implements Node.Dump.
func (n *Emphasis) Dump(source []byte, level int) { func (n *Emphasis) Dump(source []byte, level int) {
DumpHelper(n, source, level, fmt.Sprintf("Emphasis(%d)", n.Level), nil, nil) m := map[string]string{
"Level": fmt.Sprintf("%v", n.Level),
}
DumpHelper(n, source, level, m, nil)
}
// KindEmphasis is a NodeKind of the Emphasis node.
var KindEmphasis = NewNodeKind("Emphasis")
// Kind implements Node.Kind.
func (n *Emphasis) Kind() NodeKind {
return KindEmphasis
} }
// NewEmphasis returns a new Emphasis node with given level. // NewEmphasis returns a new Emphasis node with given level.
@ -256,18 +278,27 @@ type baseLink struct {
func (n *baseLink) Inline() { func (n *baseLink) Inline() {
} }
func (n *baseLink) Dump(source []byte, level int) {
m := map[string]string{}
m["Destination"] = string(n.Destination)
m["Title"] = string(n.Title)
DumpHelper(n, source, level, "Link", m, nil)
}
// A Link struct represents a link of the Markdown text. // A Link struct represents a link of the Markdown text.
type Link struct { type Link struct {
baseLink baseLink
} }
// Dump implements Node.Dump.
func (n *Link) Dump(source []byte, level int) {
m := map[string]string{}
m["Destination"] = string(n.Destination)
m["Title"] = string(n.Title)
DumpHelper(n, source, level, m, nil)
}
// KindLink is a NodeKind of the Link node.
var KindLink = NewNodeKind("Link")
// Kind implements Node.Kind.
func (n *Link) Kind() NodeKind {
return KindLink
}
// NewLink returns a new Link node. // NewLink returns a new Link node.
func NewLink() *Link { func NewLink() *Link {
c := &Link{ c := &Link{
@ -288,7 +319,15 @@ func (n *Image) Dump(source []byte, level int) {
m := map[string]string{} m := map[string]string{}
m["Destination"] = string(n.Destination) m["Destination"] = string(n.Destination)
m["Title"] = string(n.Title) m["Title"] = string(n.Title)
DumpHelper(n, source, level, "Image", m, nil) DumpHelper(n, source, level, m, nil)
}
// KindImage is a NodeKind of the Image node.
var KindImage = NewNodeKind("Image")
// Kind implements Node.Kind.
func (n *Image) Kind() NodeKind {
return KindImage
} }
// NewImage returns a new Image node. // NewImage returns a new Image node.
@ -338,7 +377,15 @@ func (n *AutoLink) Dump(source []byte, level int) {
m := map[string]string{ m := map[string]string{
"Value": string(segment.Value(source)), "Value": string(segment.Value(source)),
} }
DumpHelper(n, source, level, "AutoLink", m, nil) DumpHelper(n, source, level, m, nil)
}
// KindAutoLink is a NodeKind of the AutoLink node.
var KindAutoLink = NewNodeKind("AutoLink")
// Kind implements Node.Kind.
func (n *AutoLink) Kind() NodeKind {
return KindAutoLink
} }
// NewAutoLink returns a new AutoLink node. // NewAutoLink returns a new AutoLink node.
@ -360,7 +407,15 @@ func (n *RawHTML) Inline() {}
// Dump implements Node.Dump. // Dump implements Node.Dump.
func (n *RawHTML) Dump(source []byte, level int) { func (n *RawHTML) Dump(source []byte, level int) {
DumpHelper(n, source, level, "RawHTML", nil, nil) DumpHelper(n, source, level, nil, nil)
}
// KindRawHTML is a NodeKind of the RawHTML node.
var KindRawHTML = NewNodeKind("RawHTML")
// Kind implements Node.Kind.
func (n *RawHTML) Kind() NodeKind {
return KindRawHTML
} }
// NewRawHTML returns a new RawHTML node. // NewRawHTML returns a new RawHTML node.

View file

@ -10,11 +10,17 @@ type Strikethrough struct {
gast.BaseInline gast.BaseInline
} }
func (n *Strikethrough) Inline() { // Dump implements Node.Dump.
func (n *Strikethrough) Dump(source []byte, level int) {
gast.DumpHelper(n, source, level, nil, nil)
} }
func (n *Strikethrough) Dump(source []byte, level int) { // KindStrikethrough is a NodeKind of the Strikethrough node.
gast.DumpHelper(n, source, level, "Strikethrough", nil, nil) var KindStrikethrough = gast.NewNodeKind("Strikethrough")
// Kind implements Node.Kind.
func (n *Strikethrough) Kind() gast.NodeKind {
return KindStrikethrough
} }
// NewStrikethrough returns a new Strikethrough node. // NewStrikethrough returns a new Strikethrough node.

View file

@ -47,7 +47,7 @@ type Table struct {
// Dump implements Node.Dump // Dump implements Node.Dump
func (n *Table) Dump(source []byte, level int) { func (n *Table) Dump(source []byte, level int) {
gast.DumpHelper(n, source, level, "Table", nil, func(level int) { gast.DumpHelper(n, source, level, nil, func(level int) {
indent := strings.Repeat(" ", level) indent := strings.Repeat(" ", level)
fmt.Printf("%sAlignments {\n", indent) fmt.Printf("%sAlignments {\n", indent)
for i, alignment := range n.Alignments { for i, alignment := range n.Alignments {
@ -61,6 +61,14 @@ func (n *Table) Dump(source []byte, level int) {
}) })
} }
// KindTable is a NodeKind of the Table node.
var KindTable = gast.NewNodeKind("Table")
// Kind implements Node.Kind.
func (n *Table) Kind() gast.NodeKind {
return KindTable
}
// NewTable returns a new Table node. // NewTable returns a new Table node.
func NewTable() *Table { func NewTable() *Table {
return &Table{ return &Table{
@ -76,7 +84,15 @@ type TableRow struct {
// Dump implements Node.Dump. // Dump implements Node.Dump.
func (n *TableRow) Dump(source []byte, level int) { func (n *TableRow) Dump(source []byte, level int) {
gast.DumpHelper(n, source, level, "TableRow", nil, nil) gast.DumpHelper(n, source, level, nil, nil)
}
// KindTableRow is a NodeKind of the TableRow node.
var KindTableRow = gast.NewNodeKind("TableRow")
// Kind implements Node.Kind.
func (n *TableRow) Kind() gast.NodeKind {
return KindTableRow
} }
// NewTableRow returns a new TableRow node. // NewTableRow returns a new TableRow node.
@ -89,6 +105,14 @@ type TableHeader struct {
*TableRow *TableRow
} }
// KindTableHeader is a NodeKind of the TableHeader node.
var KindTableHeader = gast.NewNodeKind("TableHeader")
// Kind implements Node.Kind.
func (n *TableHeader) Kind() gast.NodeKind {
return KindTableHeader
}
// NewTableHeader returns a new TableHeader node. // NewTableHeader returns a new TableHeader node.
func NewTableHeader(row *TableRow) *TableHeader { func NewTableHeader(row *TableRow) *TableHeader {
return &TableHeader{row} return &TableHeader{row}
@ -102,7 +126,15 @@ type TableCell struct {
// Dump implements Node.Dump. // Dump implements Node.Dump.
func (n *TableCell) Dump(source []byte, level int) { func (n *TableCell) Dump(source []byte, level int) {
gast.DumpHelper(n, source, level, "TableCell", nil, nil) gast.DumpHelper(n, source, level, nil, nil)
}
// KindTableCell is a NodeKind of the TableCell node.
var KindTableCell = gast.NewNodeKind("TableCell")
// Kind implements Node.Kind.
func (n *TableCell) Kind() gast.NodeKind {
return KindTableCell
} }
// NewTableCell returns a new TableCell node. // NewTableCell returns a new TableCell node.

View file

@ -16,7 +16,15 @@ func (n *TaskCheckBox) Dump(source []byte, level int) {
m := map[string]string{ m := map[string]string{
"Checked": fmt.Sprintf("%v", n.IsChecked), "Checked": fmt.Sprintf("%v", n.IsChecked),
} }
gast.DumpHelper(n, source, level, "TaskCheckBox", m, nil) gast.DumpHelper(n, source, level, m, nil)
}
// KindTaskCheckBox is a NodeKind of the TaskCheckBox node.
var KindTaskCheckBox = gast.NewNodeKind("TaskCheckBox")
// Kind implements Node.Kind.
func (n *TaskCheckBox) Kind() gast.NodeKind {
return KindTaskCheckBox
} }
// NewTaskCheckBox returns a new TaskCheckBox node. // NewTaskCheckBox returns a new TaskCheckBox node.

View file

@ -77,22 +77,18 @@ func NewStrikethroughHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
return r return r
} }
// Render implements renderer.NodeRenderer.Render. // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
func (r *StrikethroughHTMLRenderer) Render(writer util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *StrikethroughHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
switch node := n.(type) { reg.Register(ast.KindStrikethrough, r.renderStrikethrough)
case *ast.Strikethrough:
return r.renderStrikethrough(writer, source, node, entering), nil
}
return gast.WalkContinue, renderer.NotSupported
} }
func (r *StrikethroughHTMLRenderer) renderStrikethrough(w util.BufWriter, source []byte, n *ast.Strikethrough, entering bool) gast.WalkStatus { func (r *StrikethroughHTMLRenderer) renderStrikethrough(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<del>") w.WriteString("<del>")
} else { } else {
w.WriteString("</del>") w.WriteString("</del>")
} }
return gast.WalkContinue return gast.WalkContinue, nil
} }
type strikethrough struct { type strikethrough struct {

View file

@ -147,31 +147,24 @@ func NewTableHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
return r return r
} }
// Render implements renderer.Renderer.Render. // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
func (r *TableHTMLRenderer) Render(writer util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *TableHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
switch node := n.(type) { reg.Register(ast.KindTable, r.renderTable)
case *ast.Table: reg.Register(ast.KindTableHeader, r.renderTableHeader)
return r.renderTable(writer, source, node, entering), nil reg.Register(ast.KindTableRow, r.renderTableRow)
case *ast.TableHeader: reg.Register(ast.KindTableCell, r.renderTableCell)
return r.renderTableHeader(writer, source, node, entering), nil
case *ast.TableRow:
return r.renderTableRow(writer, source, node, entering), nil
case *ast.TableCell:
return r.renderTableCell(writer, source, node, entering), nil
}
return gast.WalkContinue, renderer.NotSupported
} }
func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n *ast.Table, entering bool) gast.WalkStatus { func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<table>\n") w.WriteString("<table>\n")
} else { } else {
w.WriteString("</table>\n") w.WriteString("</table>\n")
} }
return gast.WalkContinue return gast.WalkContinue, nil
} }
func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n *ast.TableHeader, entering bool) gast.WalkStatus { func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<thead>\n") w.WriteString("<thead>\n")
w.WriteString("<tr>\n") w.WriteString("<tr>\n")
@ -185,10 +178,10 @@ func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n
w.WriteString("</tbody>\n") w.WriteString("</tbody>\n")
} }
} }
return gast.WalkContinue return gast.WalkContinue, nil
} }
func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n *ast.TableRow, entering bool) gast.WalkStatus { func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<tr>\n") w.WriteString("<tr>\n")
} else { } else {
@ -197,10 +190,11 @@ func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n *a
w.WriteString("</tbody>\n") w.WriteString("</tbody>\n")
} }
} }
return gast.WalkContinue return gast.WalkContinue, nil
} }
func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, n *ast.TableCell, entering bool) gast.WalkStatus { func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
n := node.(*ast.TableCell)
tag := "td" tag := "td"
if n.Parent().Parent().FirstChild() == n.Parent() { if n.Parent().Parent().FirstChild() == n.Parent() {
tag = "th" tag = "th"
@ -214,7 +208,7 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, n *
} else { } else {
fmt.Fprintf(w, "</%s>\n", tag) fmt.Fprintf(w, "</%s>\n", tag)
} }
return gast.WalkContinue return gast.WalkContinue, nil
} }
type table struct { type table struct {

View file

@ -75,19 +75,16 @@ func NewTaskCheckBoxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
return r return r
} }
// Render implements renderer.NodeRenderer.Render. // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
func (r *TaskCheckBoxHTMLRenderer) Render(writer util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
switch node := n.(type) { reg.Register(ast.KindTaskCheckBox, r.renderTaskCheckBox)
case *ast.TaskCheckBox:
return r.renderTaskCheckBox(writer, source, node, entering), nil
}
return gast.WalkContinue, renderer.NotSupported
} }
func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, n *ast.TaskCheckBox, entering bool) gast.WalkStatus { func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
if !entering { if !entering {
return gast.WalkContinue return gast.WalkContinue, nil
} }
n := node.(*ast.TaskCheckBox)
if n.IsChecked { if n.IsChecked {
w.WriteString(`<input checked="" disabled="" type="checkbox"`) w.WriteString(`<input checked="" disabled="" type="checkbox"`)
@ -99,7 +96,7 @@ func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source [
} else { } else {
w.WriteString(">") w.WriteString(">")
} }
return gast.WalkContinue return gast.WalkContinue, nil
} }
type taskList struct { type taskList struct {

View file

@ -120,6 +120,7 @@ func (b *atxHeadingParser) CanAcceptIndentedLine() bool {
var headingIDRegexp = regexp.MustCompile(`^(.*[^\\])({#([^}]+)}\s*)\n?$`) var headingIDRegexp = regexp.MustCompile(`^(.*[^\\])({#([^}]+)}\s*)\n?$`)
var headingIDMap = NewContextKey() var headingIDMap = NewContextKey()
var attrNameID = []byte("id")
func parseOrGenerateHeadingID(node *ast.Heading, pc Context) { func parseOrGenerateHeadingID(node *ast.Heading, pc Context) {
existsv := pc.Get(headingIDMap) existsv := pc.Get(headingIDMap)
@ -142,5 +143,5 @@ func parseOrGenerateHeadingID(node *ast.Heading, pc Context) {
} else { } else {
headingID = util.GenerateLinkID(line, exists) headingID = util.GenerateLinkID(line, exists)
} }
node.ID = headingID node.SetAttribute(attrNameID, headingID)
} }

View file

@ -40,7 +40,3 @@ func (s *autoLinkParser) Parse(parent ast.Node, block text.Reader, pc Context) a
block.Advance(match[1]) block.Advance(match[1])
return ast.NewAutoLink(typ, value) return ast.NewAutoLink(typ, value)
} }
func (s *autoLinkParser) CloseBlock(parent ast.Node, pc Context) {
// nothing to do
}

View file

@ -81,7 +81,3 @@ end:
} }
return node return node
} }
func (s *codeSpanParser) CloseBlock(parent ast.Node, pc Context) {
// nothing to do
}

View file

@ -65,6 +65,13 @@ func (d *Delimiter) Dump(source []byte, level int) {
fmt.Printf("%sDelimiter: \"%s\"\n", strings.Repeat(" ", level), string(d.Text(source))) fmt.Printf("%sDelimiter: \"%s\"\n", strings.Repeat(" ", level), string(d.Text(source)))
} }
var kindDelimiter = ast.NewNodeKind("Delimiter")
// Kind implements Node.Kind
func (d *Delimiter) Kind() ast.NodeKind {
return kindDelimiter
}
// Text implements Node.Text // Text implements Node.Text
func (d *Delimiter) Text(source []byte) []byte { func (d *Delimiter) Text(source []byte) []byte {
return d.Segment.Value(source) return d.Segment.Value(source)

View file

@ -48,7 +48,3 @@ func (s *emphasisParser) Parse(parent ast.Node, block text.Reader, pc Context) a
pc.PushDelimiter(node) pc.PushDelimiter(node)
return node return node
} }
func (s *emphasisParser) CloseBlock(parent ast.Node, pc Context) {
// nothing to do
}

View file

@ -43,6 +43,12 @@ func (s *linkLabelState) Dump(source []byte, level int) {
fmt.Printf("%slinkLabelState: \"%s\"\n", strings.Repeat(" ", level), s.Text(source)) fmt.Printf("%slinkLabelState: \"%s\"\n", strings.Repeat(" ", level), s.Text(source))
} }
var kindLinkLabelState = ast.NewNodeKind("LinkLabelState")
func (s *linkLabelState) Kind() ast.NodeKind {
return kindLinkLabelState
}
func pushLinkLabelState(pc Context, v *linkLabelState) { func pushLinkLabelState(pc Context, v *linkLabelState) {
tlist := pc.Get(linkLabelStateKey) tlist := pc.Get(linkLabelStateKey)
var list *linkLabelState var list *linkLabelState

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text" "github.com/yuin/goldmark/text"
@ -55,19 +54,15 @@ func (r *reference) String() string {
} }
// ContextKey is a key that is used to set arbitary values to the context. // ContextKey is a key that is used to set arbitary values to the context.
type ContextKey int32 type ContextKey int
// New returns a new ContextKey value.
func (c *ContextKey) New() ContextKey {
return ContextKey(atomic.AddInt32((*int32)(c), 1))
}
// ContextKeyMax is a maximum value of the ContextKey. // ContextKeyMax is a maximum value of the ContextKey.
var ContextKeyMax ContextKey var ContextKeyMax ContextKey
// NewContextKey return a new ContextKey value. // NewContextKey return a new ContextKey value.
func NewContextKey() ContextKey { func NewContextKey() ContextKey {
return ContextKeyMax.New() ContextKeyMax++
return ContextKeyMax
} }
// A Context interface holds a information that are necessary to parse // A Context interface holds a information that are necessary to parse
@ -127,33 +122,28 @@ type Context interface {
// LastOpenedBlock returns a last node that is currently in parsing. // LastOpenedBlock returns a last node that is currently in parsing.
LastOpenedBlock() Block LastOpenedBlock() Block
// SetLastOpenedBlock sets a last node that is currently in parsing.
SetLastOpenedBlock(Block)
} }
type parseContext struct { type parseContext struct {
store []interface{} store []interface{}
source []byte source []byte
refs map[string]Reference refs map[string]Reference
blockOffset int blockOffset int
delimiters *Delimiter delimiters *Delimiter
lastDelimiter *Delimiter lastDelimiter *Delimiter
openedBlocks []Block openedBlocks []Block
lastOpenedBlock Block
} }
// NewContext returns a new Context. // NewContext returns a new Context.
func NewContext(source []byte) Context { func NewContext(source []byte) Context {
return &parseContext{ return &parseContext{
store: make([]interface{}, ContextKeyMax+1), store: make([]interface{}, ContextKeyMax+1),
source: source, source: source,
refs: map[string]Reference{}, refs: map[string]Reference{},
blockOffset: 0, blockOffset: 0,
delimiters: nil, delimiters: nil,
lastDelimiter: nil, lastDelimiter: nil,
openedBlocks: []Block{}, openedBlocks: []Block{},
lastOpenedBlock: Block{},
} }
} }
@ -276,11 +266,10 @@ func (p *parseContext) SetOpenedBlocks(v []Block) {
} }
func (p *parseContext) LastOpenedBlock() Block { func (p *parseContext) LastOpenedBlock() Block {
return p.lastOpenedBlock if l := len(p.openedBlocks); l != 0 {
} return p.openedBlocks[l-1]
}
func (p *parseContext) SetLastOpenedBlock(v Block) { return Block{}
p.lastOpenedBlock = v
} }
// State represents parser's state. // State represents parser's state.
@ -401,7 +390,11 @@ type InlineParser interface {
// If Parse has been able to parse the current line, it must advance a reader // If Parse has been able to parse the current line, it must advance a reader
// position by consumed byte length. // position by consumed byte length.
Parse(parent ast.Node, block text.Reader, pc Context) ast.Node Parse(parent ast.Node, block text.Reader, pc Context) ast.Node
}
// A CloseBlocker interface is a callback function that will be
// called when block is closed in the inline parsing.
type CloseBlocker interface {
// CloseBlock will be called when a block is closed. // CloseBlock will be called when a block is closed.
CloseBlock(parent ast.Node, pc Context) CloseBlock(parent ast.Node, pc Context)
} }
@ -487,7 +480,7 @@ type parser struct {
options map[OptionName]interface{} options map[OptionName]interface{}
blockParsers []BlockParser blockParsers []BlockParser
inlineParsers [256][]InlineParser inlineParsers [256][]InlineParser
inlineParsersList []InlineParser closeBlockers []CloseBlocker
paragraphTransformers []ParagraphTransformer paragraphTransformers []ParagraphTransformer
astTransformers []ASTTransformer astTransformers []ASTTransformer
config *Config config *Config
@ -610,7 +603,9 @@ func (p *parser) addInlineParser(v util.PrioritizedValue, options map[OptionName
so.SetOption(oname, ovalue) so.SetOption(oname, ovalue)
} }
} }
p.inlineParsersList = append(p.inlineParsersList, ip) if cb, ok := ip.(CloseBlocker); ok {
p.closeBlockers = append(p.closeBlockers, cb)
}
for _, tc := range tcs { for _, tc := range tcs {
if p.inlineParsers[tc] == nil { if p.inlineParsers[tc] == nil {
p.inlineParsers[tc] = []InlineParser{} p.inlineParsers[tc] = []InlineParser{}
@ -715,15 +710,12 @@ func (p *parser) transformParagraph(node *ast.Paragraph, pc Context) {
func (p *parser) closeBlocks(from, to int, pc Context) { func (p *parser) closeBlocks(from, to int, pc Context) {
blocks := pc.OpenedBlocks() blocks := pc.OpenedBlocks()
last := pc.LastOpenedBlock()
for i := from; i >= to; i-- { for i := from; i >= to; i-- {
node := blocks[i].Node node := blocks[i].Node
if node.Parent() != nil { blocks[i].Parser.Close(blocks[i].Node, pc)
blocks[i].Parser.Close(blocks[i].Node, pc) paragraph, ok := node.(*ast.Paragraph)
paragraph, ok := node.(*ast.Paragraph) if ok && node.Parent() != nil {
if ok && node.Parent() != nil { p.transformParagraph(paragraph, pc)
p.transformParagraph(paragraph, pc)
}
} }
} }
if from == len(blocks)-1 { if from == len(blocks)-1 {
@ -731,14 +723,7 @@ func (p *parser) closeBlocks(from, to int, pc Context) {
} else { } else {
blocks = append(blocks[0:to], blocks[from+1:]...) blocks = append(blocks[0:to], blocks[from+1:]...)
} }
l := len(blocks)
if l == 0 {
last.Node = nil
} else {
last = blocks[l-1]
}
pc.SetOpenedBlocks(blocks) pc.SetOpenedBlocks(blocks)
pc.SetLastOpenedBlock(last)
} }
type blockOpenResult int type blockOpenResult int
@ -758,13 +743,13 @@ func (p *parser) openBlocks(parent ast.Node, blankLine bool, reader text.Reader,
} }
retry: retry:
shouldPeek := true shouldPeek := true
var currentLineNum int //var currentLineNum int
var w int var w int
var pos int var pos int
var line []byte var line []byte
for _, bp := range p.blockParsers { for _, bp := range p.blockParsers {
if shouldPeek { if shouldPeek {
currentLineNum, _ = reader.Position() //currentLineNum, _ = reader.Position()
line, _ = reader.PeekLine() line, _ = reader.PeekLine()
w, pos = util.IndentWidth(line, 0) w, pos = util.IndentWidth(line, 0)
pc.SetBlockOffset(pos) pc.SetBlockOffset(pos)
@ -781,9 +766,9 @@ retry:
} }
last := pc.LastOpenedBlock().Node last := pc.LastOpenedBlock().Node
node, state := bp.Open(parent, reader, pc) node, state := bp.Open(parent, reader, pc)
if l, _ := reader.Position(); l != currentLineNum { // if l, _ := reader.Position(); l != currentLineNum {
panic("BlockParser.Open must not advance position beyond the current line") // panic("BlockParser.Open must not advance position beyond the current line")
} // }
if node != nil { if node != nil {
shouldPeek = true shouldPeek = true
node.SetBlankPreviousLines(blankLine) node.SetBlankPreviousLines(blankLine)
@ -795,7 +780,6 @@ retry:
result = newBlocksOpened result = newBlocksOpened
be := Block{node, bp} be := Block{node, bp}
pc.SetOpenedBlocks(append(pc.OpenedBlocks(), be)) pc.SetOpenedBlocks(append(pc.OpenedBlocks(), be))
pc.SetLastOpenedBlock(be)
if state == HasChildren { if state == HasChildren {
parent = node parent = node
goto retry // try child block goto retry // try child block
@ -834,7 +818,6 @@ func isBlankLine(lineNum, level int, stats []lineStat) ([]lineStat, bool) {
} }
func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) { func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
pc.SetLastOpenedBlock(Block{})
pc.SetOpenedBlocks([]Block{}) pc.SetOpenedBlocks([]Block{})
blankLines := make([]lineStat, 0, 64) blankLines := make([]lineStat, 0, 64)
isBlank := false isBlank := false
@ -848,14 +831,20 @@ func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
return return
} }
lineNum, _ := reader.Position() lineNum, _ := reader.Position()
for i := 0; i < len(pc.OpenedBlocks()); i++ { l := len(pc.OpenedBlocks())
for i := 0; i < l; i++ {
blankLines = append(blankLines, lineStat{lineNum - 1, i, lines != 0}) blankLines = append(blankLines, lineStat{lineNum - 1, i, lines != 0})
} }
reader.AdvanceLine() reader.AdvanceLine()
for len(pc.OpenedBlocks()) != 0 { // process opened blocks line by line for { // process opened blocks line by line
lastIndex := len(pc.OpenedBlocks()) - 1 openedBlocks := pc.OpenedBlocks()
for i := 0; i < len(pc.OpenedBlocks()); i++ { l := len(openedBlocks)
be := pc.OpenedBlocks()[i] if l == 0 {
break
}
lastIndex := l - 1
for i := 0; i < l; i++ {
be := openedBlocks[i]
line, _ := reader.PeekLine() line, _ := reader.PeekLine()
if line == nil { if line == nil {
p.closeBlocks(lastIndex, 0, pc) p.closeBlocks(lastIndex, 0, pc)
@ -883,7 +872,7 @@ func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
blankLines, isBlank = isBlankLine(lineNum-1, i, blankLines) blankLines, isBlank = isBlankLine(lineNum-1, i, blankLines)
thisParent := parent thisParent := parent
if i != 0 { if i != 0 {
thisParent = pc.OpenedBlocks()[i-1].Node thisParent = openedBlocks[i-1].Node
} }
result := p.openBlocks(thisParent, isBlank, reader, pc) result := p.openBlocks(thisParent, isBlank, reader, pc)
if result != paragraphContinuation { if result != paragraphContinuation {
@ -998,7 +987,7 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
} }
ProcessDelimiters(nil, pc) ProcessDelimiters(nil, pc)
for _, ip := range p.inlineParsersList { for _, ip := range p.closeBlockers {
ip.CloseBlock(parent, pc) ip.CloseBlock(parent, pc)
} }

View file

@ -158,53 +158,33 @@ func NewRenderer(opts ...Option) renderer.NodeRenderer {
return r return r
} }
// Render implements renderer.NodeRenderer.Render. // RegisterFuncs implements NodeRenderer.RegisterFuncs .
func (r *Renderer) Render(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
switch node := n.(type) {
// blocks // blocks
case *ast.Document: reg.Register(ast.KindDocument, r.renderDocument)
return r.renderDocument(writer, source, node, entering), nil reg.Register(ast.KindHeading, r.renderHeading)
case *ast.Heading: reg.Register(ast.KindBlockquote, r.renderBlockquote)
return r.renderHeading(writer, source, node, entering), nil reg.Register(ast.KindCodeBlock, r.renderCodeBlock)
case *ast.Blockquote: reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock)
return r.renderBlockquote(writer, source, node, entering), nil reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
case *ast.CodeBlock: reg.Register(ast.KindList, r.renderList)
return r.renderCodeBlock(writer, source, node, entering), nil reg.Register(ast.KindListItem, r.renderListItem)
case *ast.FencedCodeBlock: reg.Register(ast.KindParagraph, r.renderParagraph)
return r.renderFencedCodeBlock(writer, source, node, entering), nil reg.Register(ast.KindTextBlock, r.renderTextBlock)
case *ast.HTMLBlock: reg.Register(ast.KindThemanticBreak, r.renderThemanticBreak)
return r.renderHTMLBlock(writer, source, node, entering), nil
case *ast.List:
return r.renderList(writer, source, node, entering), nil
case *ast.ListItem:
return r.renderListItem(writer, source, node, entering), nil
case *ast.Paragraph:
return r.renderParagraph(writer, source, node, entering), nil
case *ast.TextBlock:
return r.renderTextBlock(writer, source, node, entering), nil
case *ast.ThemanticBreak:
return r.renderThemanticBreak(writer, source, node, entering), nil
// inlines // inlines
case *ast.AutoLink: reg.Register(ast.KindAutoLink, r.renderAutoLink)
return r.renderAutoLink(writer, source, node, entering), nil reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
case *ast.CodeSpan: reg.Register(ast.KindEmphasis, r.renderEmphasis)
return r.renderCodeSpan(writer, source, node, entering), nil reg.Register(ast.KindImage, r.renderImage)
case *ast.Emphasis: reg.Register(ast.KindLink, r.renderLink)
return r.renderEmphasis(writer, source, node, entering), nil reg.Register(ast.KindRawHTML, r.renderRawHTML)
case *ast.Image: reg.Register(ast.KindText, r.renderText)
return r.renderImage(writer, source, node, entering), nil
case *ast.Link:
return r.renderLink(writer, source, node, entering), nil
case *ast.RawHTML:
return r.renderRawHTML(writer, source, node, entering), nil
case *ast.Text:
return r.renderText(writer, source, node, entering), nil
}
return ast.WalkContinue, renderer.NotSupported
} }
func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) { func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) {
l := n.Lines().Len() l := n.Lines().Len()
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
@ -213,19 +193,25 @@ func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) {
} }
} }
func (r *Renderer) renderDocument(w util.BufWriter, source []byte, n *ast.Document, entering bool) ast.WalkStatus { func (r *Renderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
// nothing to do // nothing to do
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderHeading(w util.BufWriter, source []byte, n *ast.Heading, entering bool) ast.WalkStatus { var attrNameID = []byte("id")
func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Heading)
if entering { if entering {
w.WriteString("<h") w.WriteString("<h")
w.WriteByte("0123456"[n.Level]) w.WriteByte("0123456"[n.Level])
if n.ID != nil { if n.Attributes() != nil {
w.WriteString(` id="`) id, ok := n.Attribute(attrNameID)
w.Write(n.ID) if ok {
w.WriteByte('"') w.WriteString(` id="`)
w.Write(id)
w.WriteByte('"')
}
} }
w.WriteByte('>') w.WriteByte('>')
} else { } else {
@ -233,29 +219,30 @@ func (r *Renderer) renderHeading(w util.BufWriter, source []byte, n *ast.Heading
w.WriteByte("0123456"[n.Level]) w.WriteByte("0123456"[n.Level])
w.WriteString(">\n") w.WriteString(">\n")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n *ast.Blockquote, entering bool) ast.WalkStatus { func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<blockquote>\n") w.WriteString("<blockquote>\n")
} else { } else {
w.WriteString("</blockquote>\n") w.WriteString("</blockquote>\n")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n *ast.CodeBlock, entering bool) ast.WalkStatus { func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<pre><code>") w.WriteString("<pre><code>")
r.writeLines(w, source, n) r.writeLines(w, source, n)
} else { } else {
w.WriteString("</code></pre>\n") w.WriteString("</code></pre>\n")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, n *ast.FencedCodeBlock, entering bool) ast.WalkStatus { func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.FencedCodeBlock)
if entering { if entering {
w.WriteString("<pre><code") w.WriteString("<pre><code")
if n.Info != nil { if n.Info != nil {
@ -277,10 +264,11 @@ func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, n *ast
} else { } else {
w.WriteString("</code></pre>\n") w.WriteString("</code></pre>\n")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, n *ast.HTMLBlock, entering bool) ast.WalkStatus { func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.HTMLBlock)
if entering { if entering {
if r.Unsafe { if r.Unsafe {
l := n.Lines().Len() l := n.Lines().Len()
@ -301,10 +289,11 @@ func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, n *ast.HTMLB
} }
} }
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderList(w util.BufWriter, source []byte, n *ast.List, entering bool) ast.WalkStatus { func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.List)
tag := "ul" tag := "ul"
if n.IsOrdered() { if n.IsOrdered() {
tag = "ol" tag = "ol"
@ -322,10 +311,10 @@ func (r *Renderer) renderList(w util.BufWriter, source []byte, n *ast.List, ente
w.WriteString(tag) w.WriteString(tag)
w.WriteString(">\n") w.WriteString(">\n")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n *ast.ListItem, entering bool) ast.WalkStatus { func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<li>") w.WriteString("<li>")
fc := n.FirstChild() fc := n.FirstChild()
@ -337,43 +326,43 @@ func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n *ast.ListIt
} else { } else {
w.WriteString("</li>\n") w.WriteString("</li>\n")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n *ast.Paragraph, entering bool) ast.WalkStatus { func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<p>") w.WriteString("<p>")
} else { } else {
w.WriteString("</p>\n") w.WriteString("</p>\n")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderTextBlock(w util.BufWriter, source []byte, n *ast.TextBlock, entering bool) ast.WalkStatus { func (r *Renderer) renderTextBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering { if !entering {
if _, ok := n.NextSibling().(ast.Node); ok && n.FirstChild() != nil { if _, ok := n.NextSibling().(ast.Node); ok && n.FirstChild() != nil {
w.WriteByte('\n') w.WriteByte('\n')
} }
return ast.WalkContinue
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderThemanticBreak(w util.BufWriter, source []byte, n *ast.ThemanticBreak, entering bool) ast.WalkStatus { func (r *Renderer) renderThemanticBreak(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering { if !entering {
return ast.WalkContinue return ast.WalkContinue, nil
} }
if r.XHTML { if r.XHTML {
w.WriteString("<hr />\n") w.WriteString("<hr />\n")
} else { } else {
w.WriteString("<hr>\n") w.WriteString("<hr>\n")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, n *ast.AutoLink, entering bool) ast.WalkStatus { func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.AutoLink)
if !entering { if !entering {
return ast.WalkContinue return ast.WalkContinue, nil
} }
w.WriteString(`<a href="`) w.WriteString(`<a href="`)
segment := n.Value.Segment segment := n.Value.Segment
@ -385,10 +374,10 @@ func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, n *ast.AutoLi
w.WriteString(`">`) w.WriteString(`">`)
w.Write(util.EscapeHTML(value)) w.Write(util.EscapeHTML(value))
w.WriteString(`</a>`) w.WriteString(`</a>`)
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n *ast.CodeSpan, entering bool) ast.WalkStatus { func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<code>") w.WriteString("<code>")
for c := n.FirstChild(); c != nil; c = c.NextSibling() { for c := n.FirstChild(); c != nil; c = c.NextSibling() {
@ -403,13 +392,14 @@ func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n *ast.CodeSp
r.Writer.RawWrite(w, value) r.Writer.RawWrite(w, value)
} }
} }
return ast.WalkSkipChildren return ast.WalkSkipChildren, nil
} }
w.WriteString("</code>") w.WriteString("</code>")
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, n *ast.Emphasis, entering bool) ast.WalkStatus { func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Emphasis)
tag := "em" tag := "em"
if n.Level == 2 { if n.Level == 2 {
tag = "strong" tag = "strong"
@ -423,10 +413,11 @@ func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, n *ast.Emphas
w.WriteString(tag) w.WriteString(tag)
w.WriteByte('>') w.WriteByte('>')
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderLink(w util.BufWriter, source []byte, n *ast.Link, entering bool) ast.WalkStatus { func (r *Renderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Link)
if entering { if entering {
w.WriteString("<a href=\"") w.WriteString("<a href=\"")
if r.Unsafe || !IsDangerousURL(n.Destination) { if r.Unsafe || !IsDangerousURL(n.Destination) {
@ -442,12 +433,13 @@ func (r *Renderer) renderLink(w util.BufWriter, source []byte, n *ast.Link, ente
} else { } else {
w.WriteString("</a>") w.WriteString("</a>")
} }
return ast.WalkContinue return ast.WalkContinue, nil
} }
func (r *Renderer) renderImage(w util.BufWriter, source []byte, n *ast.Image, entering bool) ast.WalkStatus { func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering { if !entering {
return ast.WalkContinue return ast.WalkContinue, nil
} }
n := node.(*ast.Image)
w.WriteString("<img src=\"") w.WriteString("<img src=\"")
if r.Unsafe || !IsDangerousURL(n.Destination) { if r.Unsafe || !IsDangerousURL(n.Destination) {
w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true))) w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
@ -465,21 +457,22 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, n *ast.Image, en
} else { } else {
w.WriteString(">") w.WriteString(">")
} }
return ast.WalkSkipChildren return ast.WalkSkipChildren, nil
} }
func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, n *ast.RawHTML, entering bool) ast.WalkStatus { func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if r.Unsafe { if r.Unsafe {
return ast.WalkContinue return ast.WalkContinue, nil
} }
w.WriteString("<!-- raw HTML omitted -->") w.WriteString("<!-- raw HTML omitted -->")
return ast.WalkSkipChildren return ast.WalkSkipChildren, nil
} }
func (r *Renderer) renderText(w util.BufWriter, source []byte, n *ast.Text, entering bool) ast.WalkStatus { func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering { if !entering {
return ast.WalkContinue return ast.WalkContinue, nil
} }
n := node.(*ast.Text)
segment := n.Segment segment := n.Segment
if n.IsRaw() { if n.IsRaw() {
w.Write(segment.Value(source)) w.Write(segment.Value(source))
@ -495,21 +488,7 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, n *ast.Text, ente
w.WriteByte('\n') w.WriteByte('\n')
} }
} }
return ast.WalkContinue return ast.WalkContinue, nil
}
func readWhile(source []byte, index [2]int, pred func(byte) bool) (int, bool) {
j := index[0]
ok := false
for ; j < index[1]; j++ {
c1 := source[j]
if pred(c1) {
ok = true
continue
}
break
}
return j, ok
} }
// A Writer interface wirtes textual contents to a writer. // A Writer interface wirtes textual contents to a writer.
@ -526,11 +505,9 @@ type Writer interface {
type defaultWriter struct { type defaultWriter struct {
} }
var htmlEscaleTable = [256][]byte{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("&quot;"), nil, nil, nil, []byte("&amp;"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("&lt;"), nil, []byte("&gt;"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}
func escapeRune(writer util.BufWriter, r rune) { func escapeRune(writer util.BufWriter, r rune) {
if r < 256 { if r < 256 {
v := htmlEscaleTable[byte(r)] v := util.EscapeHTMLByte(byte(r))
if v != nil { if v != nil {
writer.Write(v) writer.Write(v)
return return
@ -543,7 +520,7 @@ func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) {
n := 0 n := 0
l := len(source) l := len(source)
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
v := htmlEscaleTable[source[i]] v := util.EscapeHTMLByte(source[i])
if v != nil { if v != nil {
writer.Write(source[i-n : i]) writer.Write(source[i-n : i])
n = 0 n = 0
@ -581,7 +558,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
// code point like #x22; // code point like #x22;
if nnext < limit && nc == 'x' || nc == 'X' { if nnext < limit && nc == 'x' || nc == 'X' {
start := nnext + 1 start := nnext + 1
i, ok = readWhile(source, [2]int{start, limit}, util.IsHexDecimal) i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsHexDecimal)
if ok && i < limit && source[i] == ';' { if ok && i < limit && source[i] == ';' {
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32) v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32)
d.RawWrite(writer, source[n:pos]) d.RawWrite(writer, source[n:pos])
@ -592,7 +569,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
// code point like #1234; // code point like #1234;
} else if nc >= '0' && nc <= '9' { } else if nc >= '0' && nc <= '9' {
start := nnext start := nnext
i, ok = readWhile(source, [2]int{start, limit}, util.IsNumeric) i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsNumeric)
if ok && i < limit && i-start < 8 && source[i] == ';' { if ok && i < limit && i-start < 8 && source[i] == ';' {
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 0, 32) v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 0, 32)
d.RawWrite(writer, source[n:pos]) d.RawWrite(writer, source[n:pos])
@ -603,7 +580,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
} }
} else { } else {
start := next start := next
i, ok = readWhile(source, [2]int{start, limit}, util.IsAlphaNumeric) i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsAlphaNumeric)
// entity reference // entity reference
if ok && i < limit && source[i] == ';' { if ok && i < limit && source[i] == ';' {
name := util.BytesToReadOnlyString(source[start:i]) name := util.BytesToReadOnlyString(source[start:i])

View file

@ -25,16 +25,6 @@ func NewConfig() *Config {
} }
} }
type notSupported struct {
}
func (e *notSupported) Error() string {
return "not supported by this parser"
}
// NotSupported indicates given node can not be rendered by this NodeRenderer.
var NotSupported = &notSupported{}
// An OptionName is a name of the option. // An OptionName is a name of the option.
type OptionName string type OptionName string
@ -80,10 +70,19 @@ type SetOptioner interface {
SetOption(name OptionName, value interface{}) SetOption(name OptionName, value interface{})
} }
// A NodeRenderer interface renders given AST node to given writer. // NodeRendererFunc is a function that renders a given node.
type NodeRendererFunc func(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error)
// A NodeRenderer interface offers NodeRendererFuncs.
type NodeRenderer interface { type NodeRenderer interface {
// Render renders given AST node to given writer. // RendererFuncs registers NodeRendererFuncs to given NodeRendererFuncRegisterer.
Render(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) RegisterFuncs(NodeRendererFuncRegisterer)
}
// A NodeRendererFuncRegisterer registers
type NodeRendererFuncRegisterer interface {
// Register registers given NodeRendererFunc to this object.
Register(ast.NodeKind, NodeRendererFunc)
} }
// A Renderer interface renders given AST node to given // A Renderer interface renders given AST node to given
@ -96,10 +95,12 @@ type Renderer interface {
} }
type renderer struct { type renderer struct {
config *Config config *Config
options map[OptionName]interface{} options map[OptionName]interface{}
nodeRenderers []NodeRenderer nodeRendererFuncsTmp map[ast.NodeKind]NodeRendererFunc
initSync sync.Once maxKind int
nodeRendererFuncs []NodeRendererFunc
initSync sync.Once
} }
// NewRenderer returns a new Renderer with given options. // NewRenderer returns a new Renderer with given options.
@ -110,8 +111,9 @@ func NewRenderer(options ...Option) Renderer {
} }
r := &renderer{ r := &renderer{
options: map[OptionName]interface{}{}, options: map[OptionName]interface{}{},
config: config, config: config,
nodeRendererFuncsTmp: map[ast.NodeKind]NodeRendererFunc{},
} }
return r return r
@ -121,36 +123,46 @@ func (r *renderer) AddOption(o Option) {
o.SetConfig(r.config) o.SetConfig(r.config)
} }
func (r *renderer) Register(kind ast.NodeKind, v NodeRendererFunc) {
r.nodeRendererFuncsTmp[kind] = v
if int(kind) > r.maxKind {
r.maxKind = int(kind)
}
}
// Render renders given AST node to given writer with given Renderer. // Render renders given AST node to given writer with given Renderer.
func (r *renderer) Render(w io.Writer, source []byte, n ast.Node) error { func (r *renderer) Render(w io.Writer, source []byte, n ast.Node) error {
r.initSync.Do(func() { r.initSync.Do(func() {
r.options = r.config.Options r.options = r.config.Options
r.config.NodeRenderers.Sort() r.config.NodeRenderers.Sort()
r.nodeRenderers = make([]NodeRenderer, 0, len(r.config.NodeRenderers)) l := len(r.config.NodeRenderers)
for _, v := range r.config.NodeRenderers { for i := l - 1; i >= 0; i-- {
v := r.config.NodeRenderers[i]
nr, _ := v.Value.(NodeRenderer) nr, _ := v.Value.(NodeRenderer)
if se, ok := v.Value.(SetOptioner); ok { if se, ok := v.Value.(SetOptioner); ok {
for oname, ovalue := range r.options { for oname, ovalue := range r.options {
se.SetOption(oname, ovalue) se.SetOption(oname, ovalue)
} }
} }
r.nodeRenderers = append(r.nodeRenderers, nr) nr.RegisterFuncs(r)
}
r.nodeRendererFuncs = make([]NodeRendererFunc, r.maxKind+1)
for kind, nr := range r.nodeRendererFuncsTmp {
r.nodeRendererFuncs[kind] = nr
} }
r.config = nil r.config = nil
r.nodeRendererFuncsTmp = nil
}) })
writer, ok := w.(util.BufWriter) writer, ok := w.(util.BufWriter)
if !ok { if !ok {
writer = bufio.NewWriter(w) writer = bufio.NewWriter(w)
} }
err := ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) { err := ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
var s ast.WalkStatus s := ast.WalkStatus(ast.WalkContinue)
var err error var err error
for _, nr := range r.nodeRenderers { f := r.nodeRendererFuncs[n.Kind()]
s, err = nr.Render(writer, source, n, entering) if f != nil {
if err == NotSupported { s, err = f(writer, source, n, entering)
continue
}
break
} }
return s, err return s, err
}) })

View file

@ -6,13 +6,70 @@ import (
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
"regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
// A CopyOnWriteBuffer is a byte buffer that copies buffer when
// it need to be changed.
type CopyOnWriteBuffer struct {
buffer []byte
copied bool
}
// NewCopyOnWriteBuffer returns a new CopyOnWriteBuffer.
func NewCopyOnWriteBuffer(buffer []byte) CopyOnWriteBuffer {
return CopyOnWriteBuffer{
buffer: buffer,
copied: false,
}
}
// Write writes given bytes to the buffer.
func (b *CopyOnWriteBuffer) Write(value []byte) {
if !b.copied {
b.buffer = make([]byte, 0, len(b.buffer)+20)
b.copied = true
}
b.buffer = append(b.buffer, value...)
}
// WriteByte writes given byte to the buffer.
func (b *CopyOnWriteBuffer) WriteByte(c byte) {
if !b.copied {
b.buffer = make([]byte, 0, len(b.buffer)+20)
b.copied = true
}
b.buffer = append(b.buffer, c)
}
// Bytes returns bytes of this buffer.
func (b *CopyOnWriteBuffer) Bytes() []byte {
return b.buffer
}
// IsCopied returns true if buffer has been copied, otherwise false.
func (b *CopyOnWriteBuffer) IsCopied() bool {
return b.copied
}
// ReadWhile read given source while pred is true.
func ReadWhile(source []byte, index [2]int, pred func(byte) bool) (int, bool) {
j := index[0]
ok := false
for ; j < index[1]; j++ {
c1 := source[j]
if pred(c1) {
ok = true
continue
}
break
}
return j, ok
}
// IsBlank returns true if given string is all space characters. // IsBlank returns true if given string is all space characters.
func IsBlank(bs []byte) bool { func IsBlank(bs []byte) bool {
for _, b := range bs { for _, b := range bs {
@ -26,6 +83,9 @@ func IsBlank(bs []byte) bool {
// DedentPosition dedents lines by given width. // DedentPosition dedents lines by given width.
func DedentPosition(bs []byte, width int) (pos, padding int) { func DedentPosition(bs []byte, width int) (pos, padding int) {
if width == 0 {
return
}
i := 0 i := 0
l := len(bs) l := len(bs)
w := 0 w := 0
@ -307,30 +367,22 @@ func ToLinkReference(v []byte) string {
return strings.ToLower(string(ReplaceSpaces(v, ' '))) return strings.ToLower(string(ReplaceSpaces(v, ' ')))
} }
var escapeRegex = regexp.MustCompile(`\\.`) var htmlEscapeTable = [256][]byte{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("&quot;"), nil, nil, nil, []byte("&amp;"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("&lt;"), nil, []byte("&gt;"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}
var hexRefRegex = regexp.MustCompile(`#[xX][\da-fA-F]+;`)
var numRefRegex = regexp.MustCompile(`#\d{1,7};`)
var entityRefRegex = regexp.MustCompile(`&([a-zA-Z\d]+);`)
var entityLt = []byte("&lt;") // EscapeHTMLByte returns HTML escaped bytes if given byte should be escaped,
var entityGt = []byte("&gt;") // otherwise nil.
var entityAmp = []byte("&amp;") func EscapeHTMLByte(b byte) []byte {
var entityQuot = []byte("&quot;") return htmlEscapeTable[b]
}
// EscapeHTML escapes characters that should be escaped in HTML text. // EscapeHTML escapes characters that should be escaped in HTML text.
func EscapeHTML(v []byte) []byte { func EscapeHTML(v []byte) []byte {
result := make([]byte, 0, len(v)+10) result := make([]byte, 0, len(v)+10)
for _, c := range v { for _, c := range v {
switch c { escaped := htmlEscapeTable[c]
case '<': if escaped != nil {
result = append(result, entityLt...) result = append(result, escaped...)
case '>': } else {
result = append(result, entityGt...)
case '&':
result = append(result, entityAmp...)
case '"':
result = append(result, entityQuot...)
default:
result = append(result, c) result = append(result, c)
} }
} }
@ -338,41 +390,111 @@ func EscapeHTML(v []byte) []byte {
} }
// UnescapePunctuations unescapes blackslash escaped punctuations. // UnescapePunctuations unescapes blackslash escaped punctuations.
func UnescapePunctuations(v []byte) []byte { func UnescapePunctuations(source []byte) []byte {
return escapeRegex.ReplaceAllFunc(v, func(match []byte) []byte { cob := NewCopyOnWriteBuffer(source)
if IsPunct(match[1]) { limit := len(source)
return []byte{match[1]} n := 0
for i := 0; i < limit; {
c := source[i]
if i < limit-1 && c == '\\' && IsPunct(source[i+1]) {
cob.Write(source[n:i])
cob.WriteByte(source[i+1])
i += 2
n = i
continue
} }
return match i++
}) }
if cob.IsCopied() {
cob.Write(source[n:len(source)])
}
return cob.Bytes()
} }
// ResolveNumericReferences resolve numeric references like '&#1234;" . // ResolveNumericReferences resolve numeric references like '&#1234;" .
func ResolveNumericReferences(v []byte) []byte { func ResolveNumericReferences(source []byte) []byte {
cob := NewCopyOnWriteBuffer(source)
buf := make([]byte, 6, 6) buf := make([]byte, 6, 6)
v = hexRefRegex.ReplaceAllFunc(v, func(match []byte) []byte { limit := len(source)
v, _ := strconv.ParseUint(string(match[2:len(match)-1]), 16, 32) ok := false
n := utf8.EncodeRune(buf, ToValidRune(rune(v))) n := 0
return buf[:n] for i := 0; i < limit; i++ {
}) if source[i] == '&' {
return numRefRegex.ReplaceAllFunc(v, func(match []byte) []byte { pos := i
v, _ := strconv.ParseUint(string(match[1:len(match)-1]), 0, 32) next := i + 1
n := utf8.EncodeRune(buf, ToValidRune(rune(v))) if next < limit && source[next] == '#' {
return buf[:n] nnext := next + 1
}) nc := source[nnext]
// code point like #x22;
if nnext < limit && nc == 'x' || nc == 'X' {
start := nnext + 1
i, ok = ReadWhile(source, [2]int{start, limit}, IsHexDecimal)
if ok && i < limit && source[i] == ';' {
v, _ := strconv.ParseUint(BytesToReadOnlyString(source[start:i]), 16, 32)
cob.Write(source[n:pos])
n = i + 1
runeSize := utf8.EncodeRune(buf, ToValidRune(rune(v)))
cob.Write(buf[:runeSize])
continue
}
// code point like #1234;
} else if nc >= '0' && nc <= '9' {
start := nnext
i, ok = ReadWhile(source, [2]int{start, limit}, IsNumeric)
if ok && i < limit && i-start < 8 && source[i] == ';' {
v, _ := strconv.ParseUint(BytesToReadOnlyString(source[start:i]), 0, 32)
cob.Write(source[n:pos])
n = i + 1
runeSize := utf8.EncodeRune(buf, ToValidRune(rune(v)))
cob.Write(buf[:runeSize])
continue
}
}
}
i = next - 1
}
}
if cob.IsCopied() {
cob.Write(source[n:len(source)])
}
return cob.Bytes()
} }
// ResolveEntityNames resolve entity references like '&ouml;" . // ResolveEntityNames resolve entity references like '&ouml;" .
func ResolveEntityNames(v []byte) []byte { func ResolveEntityNames(source []byte) []byte {
return entityRefRegex.ReplaceAllFunc(v, func(match []byte) []byte { cob := NewCopyOnWriteBuffer(source)
entity, ok := LookUpHTML5EntityByName(string(match[1 : len(match)-1])) limit := len(source)
if ok { ok := false
return entity.Characters n := 0
for i := 0; i < limit; i++ {
if source[i] == '&' {
pos := i
next := i + 1
if !(next < limit && source[next] == '#') {
start := next
i, ok = ReadWhile(source, [2]int{start, limit}, IsAlphaNumeric)
if ok && i < limit && source[i] == ';' {
name := BytesToReadOnlyString(source[start:i])
entity, ok := LookUpHTML5EntityByName(name)
if ok {
cob.Write(source[n:pos])
n = i + 1
cob.Write(entity.Characters)
continue
}
}
}
i = next - 1
} }
return match }
}) if cob.IsCopied() {
cob.Write(source[n:len(source)])
}
return cob.Bytes()
} }
var htmlSpace = []byte("%20")
// URLEscape escape given URL. // URLEscape escape given URL.
// If resolveReference is set true: // If resolveReference is set true:
// 1. unescape punctuations // 1. unescape punctuations
@ -386,32 +508,174 @@ func URLEscape(v []byte, resolveReference bool) []byte {
v = ResolveNumericReferences(v) v = ResolveNumericReferences(v)
v = ResolveEntityNames(v) v = ResolveEntityNames(v)
} }
result := make([]byte, 0, len(v)+10) ret := v
for i := 0; i < len(v); { changed := false
limit := len(v)
n := 0
add := func(b []byte) {
if !changed {
ret = make([]byte, 0, len(v)+20)
changed = true
}
ret = append(ret, b...)
}
for i := 0; i < limit; {
c := v[i] c := v[i]
if urlEscapeTable[c] == 1 { if urlEscapeTable[c] == 1 {
result = append(result, c)
i++ i++
continue continue
} }
if c == '%' && i+2 < len(v) && IsHexDecimal(v[i+1]) && IsHexDecimal(v[i+1]) { if c == '%' && i+2 < limit && IsHexDecimal(v[i+1]) && IsHexDecimal(v[i+1]) {
result = append(result, c, v[i+1], v[i+2])
i += 3 i += 3
continue continue
} }
u8len := utf8lenTable[c] u8len := utf8lenTable[c]
if u8len == 99 { // invalid utf8 leading byte, skip it if u8len == 99 { // invalid utf8 leading byte, skip it
result = append(result, c)
i++ i++
continue continue
} }
if c == ' ' { if c == ' ' {
result = append(result, '%', '2', '0') add(v[n:i])
add(htmlSpace)
i++ i++
n = i
continue continue
} }
result = append(result, []byte(url.QueryEscape(string(v[i:i+int(u8len)])))...) add(v[n:i])
add([]byte(url.QueryEscape(string(v[i : i+int(u8len)]))))
i += int(u8len) i += int(u8len)
n = i
}
if changed {
add(v[n:len(v)])
}
return ret
}
// FindAttributeIndex searchs
// - #id
// - .class
// - attr=value
// in given bytes.
// FindHTMLAttributeIndex returns an int array that elements are
// [name_start, name_stop, value_start, value_stop].
// value_start and value_stop does not include " or '.
// If no attributes found, it returns [4]int{-1, -1, -1, -1}.
func FindAttributeIndex(b []byte, canEscapeQuotes bool) [4]int {
result := [4]int{-1, -1, -1, -1}
i := 0
l := len(b)
for ; i < l && IsSpace(b[i]); i++ {
}
if i >= l {
return result
}
c := b[i]
if c == '#' || c == '.' {
result[0] = i
i++
result[1] = i
result[2] = i
for ; i < l && !IsSpace(b[i]); i++ {
}
result[3] = i
return result
}
return FindHTMLAttributeIndex(b, canEscapeQuotes)
}
// FindHTMLAttributeIndex searches HTML attributes in given bytes.
// FindHTMLAttributeIndex returns an int array that elements are
// [name_start, name_stop, value_start, value_stop].
// value_start and value_stop does not include " or '.
// If no attributes found, it returns [4]int{-1, -1, -1, -1}.
func FindHTMLAttributeIndex(b []byte, canEscapeQuotes bool) [4]int {
result := [4]int{-1, -1, -1, -1}
i := 0
l := len(b)
for ; i < l && IsSpace(b[i]); i++ {
}
if i >= l {
return result
}
c := b[i]
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
c == '_' || c == ':') {
return result
}
result[0] = i
for ; i < l; i++ {
c := b[i]
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' || c == ':' || c == '.' || c == '-') {
break
}
}
result[1] = i
for ; i < l && IsSpace(b[i]); i++ {
}
if i >= l {
return result // empty attribute
}
if b[i] != '=' {
return result // empty attribute
}
i++
for ; i < l && IsSpace(b[i]); i++ {
}
if i >= l {
return [4]int{-1, -1, -1, -1}
}
if b[i] == '"' {
i++
result[2] = i
if canEscapeQuotes {
pos := FindClosure(b[i:], '"', '"', false, false)
if pos < 0 {
return [4]int{-1, -1, -1, -1}
}
result[3] = pos + i
} else {
for ; i < l && b[i] != '"'; i++ {
}
result[3] = i
if result[2] == result[3] || i == l && b[l-1] != '"' {
return [4]int{-1, -1, -1, -1}
}
}
} else if b[i] == '\'' {
i++
result[2] = i
if canEscapeQuotes {
pos := FindClosure(b[i:], '\'', '\'', false, false)
if pos < 0 {
return [4]int{-1, -1, -1, -1}
}
result[3] = pos + i
} else {
for ; i < l && b[i] != '\''; i++ {
}
result[3] = i
if result[2] == result[3] || i == l && b[l-1] != '\'' {
return [4]int{-1, -1, -1, -1}
}
}
} else {
result[2] = i
for ; i < l; i++ {
c = b[i]
if c == '\\' || c == '"' || c == '\'' ||
c == '=' || c == '<' || c == '>' || c == '`' ||
(c >= 0 && c <= 0x20) {
break
}
}
result[3] = i
if result[2] == result[3] {
return [4]int{-1, -1, -1, -1}
}
} }
return result return result
} }