mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Performance optimizations
This commit is contained in:
parent
a8a4629dd9
commit
987f65f813
20 changed files with 868 additions and 366 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
107
ast/ast.go
107
ast/ast.go
|
|
@ -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)
|
||||||
|
|
|
||||||
127
ast/block.go
127
ast/block.go
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,3 @@ end:
|
||||||
}
|
}
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *codeSpanParser) CloseBlock(parent ast.Node, pc Context) {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
113
parser/parser.go
113
parser/parser.go
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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("""), nil, nil, nil, []byte("&"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("<"), nil, []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, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 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])
|
||||||
|
|
|
||||||
|
|
@ -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 = ¬Supported{}
|
|
||||||
|
|
||||||
// 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
|
||||||
})
|
})
|
||||||
|
|
|
||||||
366
util/util.go
366
util/util.go
|
|
@ -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("""), nil, nil, nil, []byte("&"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("<"), nil, []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, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 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("<")
|
// EscapeHTMLByte returns HTML escaped bytes if given byte should be escaped,
|
||||||
var entityGt = []byte(">")
|
// otherwise nil.
|
||||||
var entityAmp = []byte("&")
|
func EscapeHTMLByte(b byte) []byte {
|
||||||
var entityQuot = []byte(""")
|
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 'Ӓ" .
|
// ResolveNumericReferences resolve numeric references like 'Ӓ" .
|
||||||
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 'ö" .
|
// ResolveEntityNames resolve entity references like 'ö" .
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue