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.
|
||||
|
||||
```
|
||||
BenchmarkGoldMark-4 200 7291402 ns/op 2259603 B/op 16867 allocs/op
|
||||
BenchmarkGolangCommonMark-4 200 7709939 ns/op 3053760 B/op 18682 allocs/op
|
||||
BenchmarkBlackFriday-4 300 5776369 ns/op 3356386 B/op 17480 allocs/op
|
||||
BenchmarkGoldMark-4 200 7981524 ns/op 2485650 B/op 15716 allocs/op
|
||||
BenchmarkGolangCommonMark-4 200 8609737 ns/op 3053758 B/op 18681 allocs/op
|
||||
BenchmarkBlackFriday-4 200 6311112 ns/op 3356762 B/op 17481 allocs/op
|
||||
```
|
||||
|
||||
Donation
|
||||
|
|
|
|||
107
ast/ast.go
107
ast/ast.go
|
|
@ -12,17 +12,50 @@ import (
|
|||
type NodeType int
|
||||
|
||||
const (
|
||||
// BlockNode indicates that a node is kind of block nodes.
|
||||
BlockNode NodeType = iota + 1
|
||||
// InlineNode indicates that a node is kind of inline nodes.
|
||||
InlineNode
|
||||
// TypeBlock indicates that a node is kind of block nodes.
|
||||
TypeBlock NodeType = iota + 1
|
||||
// TypeInline indicates that a node is kind of inline nodes.
|
||||
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.
|
||||
type Node interface {
|
||||
// Type returns a type of this node.
|
||||
Type() NodeType
|
||||
|
||||
// Kind returns a kind of this node.
|
||||
Kind() NodeKind
|
||||
|
||||
// NextSibling returns a next sibling node of this node.
|
||||
NextSibling() Node
|
||||
|
||||
|
|
@ -106,6 +139,18 @@ type Node interface {
|
|||
|
||||
// IsRaw returns true if contents should be rendered as 'raw' contents.
|
||||
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.
|
||||
|
|
@ -115,6 +160,8 @@ type BaseNode struct {
|
|||
parent Node
|
||||
next Node
|
||||
prev Node
|
||||
childCount int
|
||||
attributes []Attribute
|
||||
}
|
||||
|
||||
func ensureIsolated(v Node) {
|
||||
|
|
@ -153,6 +200,7 @@ func (n *BaseNode) RemoveChild(self, v Node) {
|
|||
if v.Parent() != self {
|
||||
return
|
||||
}
|
||||
n.childCount--
|
||||
prev := v.PreviousSibling()
|
||||
next := v.NextSibling()
|
||||
if prev != nil {
|
||||
|
|
@ -179,6 +227,7 @@ func (n *BaseNode) RemoveChildren(self Node) {
|
|||
}
|
||||
n.firstChild = nil
|
||||
n.lastChild = nil
|
||||
n.childCount = 0
|
||||
}
|
||||
|
||||
// FirstChild implements Node.FirstChild .
|
||||
|
|
@ -193,11 +242,7 @@ func (n *BaseNode) LastChild() Node {
|
|||
|
||||
// ChildCount implements Node.ChildCount .
|
||||
func (n *BaseNode) ChildCount() int {
|
||||
count := 0
|
||||
for c := n.firstChild; c != nil; c = c.NextSibling() {
|
||||
count++
|
||||
}
|
||||
return count
|
||||
return n.childCount
|
||||
}
|
||||
|
||||
// Parent implements Node.Parent .
|
||||
|
|
@ -224,6 +269,7 @@ func (n *BaseNode) AppendChild(self, v Node) {
|
|||
}
|
||||
v.SetParent(self)
|
||||
n.lastChild = v
|
||||
n.childCount++
|
||||
}
|
||||
|
||||
// ReplaceChild implements Node.ReplaceChild .
|
||||
|
|
@ -239,6 +285,7 @@ func (n *BaseNode) InsertAfter(self, v1, insertee Node) {
|
|||
|
||||
// InsertBefore implements Node.InsertBefore .
|
||||
func (n *BaseNode) InsertBefore(self, v1, insertee Node) {
|
||||
n.childCount++
|
||||
if v1 == nil {
|
||||
n.AppendChild(self, insertee)
|
||||
return
|
||||
|
|
@ -269,15 +316,51 @@ func (n *BaseNode) Text(source []byte) []byte {
|
|||
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.
|
||||
// name is a name of the node.
|
||||
// kv is pairs of an attribute name and an attribute value.
|
||||
// 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)
|
||||
fmt.Printf("%s%s {\n", indent, name)
|
||||
indent2 := strings.Repeat(" ", level+1)
|
||||
if v.Type() == BlockNode {
|
||||
if v.Type() == TypeBlock {
|
||||
fmt.Printf("%sRawText: \"", indent2)
|
||||
for i := 0; i < v.Lines().Len(); i++ {
|
||||
line := v.Lines().At(i)
|
||||
|
|
|
|||
123
ast/block.go
123
ast/block.go
|
|
@ -15,7 +15,7 @@ type BaseBlock struct {
|
|||
|
||||
// Type implements Node.Type
|
||||
func (b *BaseBlock) Type() NodeType {
|
||||
return BlockNode
|
||||
return TypeBlock
|
||||
}
|
||||
|
||||
// IsRaw implements Node.IsRaw
|
||||
|
|
@ -51,9 +51,22 @@ type Document struct {
|
|||
BaseBlock
|
||||
}
|
||||
|
||||
// KindDocument is a NodeKind of the Document node.
|
||||
var KindDocument = NewNodeKind("Document")
|
||||
|
||||
// Dump impelements Node.Dump .
|
||||
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.
|
||||
|
|
@ -71,7 +84,15 @@ type TextBlock struct {
|
|||
|
||||
// Dump impelements Node.Dump .
|
||||
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.
|
||||
|
|
@ -88,7 +109,15 @@ type Paragraph struct {
|
|||
|
||||
// Dump impelements Node.Dump .
|
||||
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.
|
||||
|
|
@ -111,9 +140,6 @@ type Heading struct {
|
|||
// Level returns a level of this heading.
|
||||
// This value is between 1 and 6.
|
||||
Level int
|
||||
|
||||
// ID returns an ID of this heading.
|
||||
ID []byte
|
||||
}
|
||||
|
||||
// Dump impelements Node.Dump .
|
||||
|
|
@ -121,7 +147,15 @@ func (n *Heading) Dump(source []byte, level int) {
|
|||
m := map[string]string{
|
||||
"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.
|
||||
|
|
@ -139,7 +173,15 @@ type ThemanticBreak struct {
|
|||
|
||||
// Dump impelements Node.Dump .
|
||||
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.
|
||||
|
|
@ -161,7 +203,15 @@ func (n *CodeBlock) IsRaw() bool {
|
|||
|
||||
// Dump impelements Node.Dump .
|
||||
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.
|
||||
|
|
@ -189,7 +239,15 @@ func (n *FencedCodeBlock) Dump(source []byte, level int) {
|
|||
if n.Info != nil {
|
||||
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.
|
||||
|
|
@ -207,7 +265,15 @@ type Blockquote struct {
|
|||
|
||||
// Dump impelements Node.Dump .
|
||||
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.
|
||||
|
|
@ -246,18 +312,23 @@ func (l *List) CanContinue(marker byte, isOrdered bool) bool {
|
|||
|
||||
// Dump implements Node.Dump.
|
||||
func (l *List) Dump(source []byte, level int) {
|
||||
name := "List"
|
||||
if l.IsOrdered() {
|
||||
name = "OrderedList"
|
||||
}
|
||||
m := map[string]string{
|
||||
"Ordered": fmt.Sprintf("%v", l.IsOrdered()),
|
||||
"Marker": fmt.Sprintf("%c", l.Marker),
|
||||
"Tight": fmt.Sprintf("%v", l.IsTight),
|
||||
}
|
||||
if l.IsOrdered() {
|
||||
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.
|
||||
|
|
@ -279,7 +350,15 @@ type ListItem struct {
|
|||
|
||||
// Dump implements Node.Dump.
|
||||
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.
|
||||
|
|
@ -354,6 +433,14 @@ func (n *HTMLBlock) Dump(source []byte, level int) {
|
|||
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.
|
||||
func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock {
|
||||
return &HTMLBlock{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ type BaseInline struct {
|
|||
|
||||
// Type implements Node.Type
|
||||
func (b *BaseInline) Type() NodeType {
|
||||
return InlineNode
|
||||
return TypeInline
|
||||
}
|
||||
|
||||
// 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"))
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewText() *Text {
|
||||
return &Text{
|
||||
|
|
@ -166,8 +174,7 @@ func MergeOrAppendTextSegment(parent Node, s textm.Segment) {
|
|||
last := parent.LastChild()
|
||||
t, ok := last.(*Text)
|
||||
if ok && t.Segment.Stop == s.Start && !t.SoftLineBreak() {
|
||||
ts := t.Segment
|
||||
t.Segment = ts.WithStop(s.Stop)
|
||||
t.Segment = t.Segment.WithStop(s.Stop)
|
||||
} else {
|
||||
parent.AppendChild(parent, NewTextSegment(s))
|
||||
}
|
||||
|
|
@ -207,7 +214,15 @@ func (n *CodeSpan) IsBlank(source []byte) bool {
|
|||
|
||||
// Dump implements Node.Dump
|
||||
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.
|
||||
|
|
@ -225,13 +240,20 @@ type Emphasis struct {
|
|||
Level int
|
||||
}
|
||||
|
||||
// Inline implements Inline.Inline.
|
||||
func (n *Emphasis) Inline() {
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
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.
|
||||
|
|
@ -256,18 +278,27 @@ type baseLink struct {
|
|||
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.
|
||||
type Link struct {
|
||||
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.
|
||||
func NewLink() *Link {
|
||||
c := &Link{
|
||||
|
|
@ -288,7 +319,15 @@ func (n *Image) Dump(source []byte, level int) {
|
|||
m := map[string]string{}
|
||||
m["Destination"] = string(n.Destination)
|
||||
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.
|
||||
|
|
@ -338,7 +377,15 @@ func (n *AutoLink) Dump(source []byte, level int) {
|
|||
m := map[string]string{
|
||||
"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.
|
||||
|
|
@ -360,7 +407,15 @@ func (n *RawHTML) Inline() {}
|
|||
|
||||
// Dump implements Node.Dump.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -10,11 +10,17 @@ type Strikethrough struct {
|
|||
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) {
|
||||
gast.DumpHelper(n, source, level, "Strikethrough", nil, nil)
|
||||
// KindStrikethrough is a NodeKind of the Strikethrough node.
|
||||
var KindStrikethrough = gast.NewNodeKind("Strikethrough")
|
||||
|
||||
// Kind implements Node.Kind.
|
||||
func (n *Strikethrough) Kind() gast.NodeKind {
|
||||
return KindStrikethrough
|
||||
}
|
||||
|
||||
// NewStrikethrough returns a new Strikethrough node.
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ type Table struct {
|
|||
|
||||
// Dump implements Node.Dump
|
||||
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)
|
||||
fmt.Printf("%sAlignments {\n", indent)
|
||||
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.
|
||||
func NewTable() *Table {
|
||||
return &Table{
|
||||
|
|
@ -76,7 +84,15 @@ type TableRow struct {
|
|||
|
||||
// Dump implements Node.Dump.
|
||||
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.
|
||||
|
|
@ -89,6 +105,14 @@ type TableHeader struct {
|
|||
*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.
|
||||
func NewTableHeader(row *TableRow) *TableHeader {
|
||||
return &TableHeader{row}
|
||||
|
|
@ -102,7 +126,15 @@ type TableCell struct {
|
|||
|
||||
// Dump implements Node.Dump.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,15 @@ func (n *TaskCheckBox) Dump(source []byte, level int) {
|
|||
m := map[string]string{
|
||||
"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.
|
||||
|
|
|
|||
|
|
@ -77,22 +77,18 @@ func NewStrikethroughHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
|||
return r
|
||||
}
|
||||
|
||||
// Render implements renderer.NodeRenderer.Render.
|
||||
func (r *StrikethroughHTMLRenderer) Render(writer util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
switch node := n.(type) {
|
||||
case *ast.Strikethrough:
|
||||
return r.renderStrikethrough(writer, source, node, entering), nil
|
||||
}
|
||||
return gast.WalkContinue, renderer.NotSupported
|
||||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||
func (r *StrikethroughHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(ast.KindStrikethrough, r.renderStrikethrough)
|
||||
}
|
||||
|
||||
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 {
|
||||
w.WriteString("<del>")
|
||||
} else {
|
||||
w.WriteString("</del>")
|
||||
}
|
||||
return gast.WalkContinue
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type strikethrough struct {
|
||||
|
|
|
|||
|
|
@ -147,31 +147,24 @@ func NewTableHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
|||
return r
|
||||
}
|
||||
|
||||
// Render implements renderer.Renderer.Render.
|
||||
func (r *TableHTMLRenderer) Render(writer util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
switch node := n.(type) {
|
||||
case *ast.Table:
|
||||
return r.renderTable(writer, source, node, entering), nil
|
||||
case *ast.TableHeader:
|
||||
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
|
||||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||
func (r *TableHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(ast.KindTable, r.renderTable)
|
||||
reg.Register(ast.KindTableHeader, r.renderTableHeader)
|
||||
reg.Register(ast.KindTableRow, r.renderTableRow)
|
||||
reg.Register(ast.KindTableCell, r.renderTableCell)
|
||||
}
|
||||
|
||||
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 {
|
||||
w.WriteString("<table>\n")
|
||||
} else {
|
||||
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 {
|
||||
w.WriteString("<thead>\n")
|
||||
w.WriteString("<tr>\n")
|
||||
|
|
@ -185,10 +178,10 @@ func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, 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 {
|
||||
w.WriteString("<tr>\n")
|
||||
} else {
|
||||
|
|
@ -197,10 +190,11 @@ func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n *a
|
|||
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"
|
||||
if n.Parent().Parent().FirstChild() == n.Parent() {
|
||||
tag = "th"
|
||||
|
|
@ -214,7 +208,7 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, n *
|
|||
} else {
|
||||
fmt.Fprintf(w, "</%s>\n", tag)
|
||||
}
|
||||
return gast.WalkContinue
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type table struct {
|
||||
|
|
|
|||
|
|
@ -75,19 +75,16 @@ func NewTaskCheckBoxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
|||
return r
|
||||
}
|
||||
|
||||
// Render implements renderer.NodeRenderer.Render.
|
||||
func (r *TaskCheckBoxHTMLRenderer) Render(writer util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
switch node := n.(type) {
|
||||
case *ast.TaskCheckBox:
|
||||
return r.renderTaskCheckBox(writer, source, node, entering), nil
|
||||
}
|
||||
return gast.WalkContinue, renderer.NotSupported
|
||||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||
func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(ast.KindTaskCheckBox, r.renderTaskCheckBox)
|
||||
}
|
||||
|
||||
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 {
|
||||
return gast.WalkContinue
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
n := node.(*ast.TaskCheckBox)
|
||||
|
||||
if n.IsChecked {
|
||||
w.WriteString(`<input checked="" disabled="" type="checkbox"`)
|
||||
|
|
@ -99,7 +96,7 @@ func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source [
|
|||
} else {
|
||||
w.WriteString(">")
|
||||
}
|
||||
return gast.WalkContinue
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type taskList struct {
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ func (b *atxHeadingParser) CanAcceptIndentedLine() bool {
|
|||
|
||||
var headingIDRegexp = regexp.MustCompile(`^(.*[^\\])({#([^}]+)}\s*)\n?$`)
|
||||
var headingIDMap = NewContextKey()
|
||||
var attrNameID = []byte("id")
|
||||
|
||||
func parseOrGenerateHeadingID(node *ast.Heading, pc Context) {
|
||||
existsv := pc.Get(headingIDMap)
|
||||
|
|
@ -142,5 +143,5 @@ func parseOrGenerateHeadingID(node *ast.Heading, pc Context) {
|
|||
} else {
|
||||
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])
|
||||
return ast.NewAutoLink(typ, value)
|
||||
}
|
||||
|
||||
func (s *autoLinkParser) CloseBlock(parent ast.Node, pc Context) {
|
||||
// nothing to do
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,3 @@ end:
|
|||
}
|
||||
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)))
|
||||
}
|
||||
|
||||
var kindDelimiter = ast.NewNodeKind("Delimiter")
|
||||
|
||||
// Kind implements Node.Kind
|
||||
func (d *Delimiter) Kind() ast.NodeKind {
|
||||
return kindDelimiter
|
||||
}
|
||||
|
||||
// Text implements Node.Text
|
||||
func (d *Delimiter) Text(source []byte) []byte {
|
||||
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)
|
||||
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))
|
||||
}
|
||||
|
||||
var kindLinkLabelState = ast.NewNodeKind("LinkLabelState")
|
||||
|
||||
func (s *linkLabelState) Kind() ast.NodeKind {
|
||||
return kindLinkLabelState
|
||||
}
|
||||
|
||||
func pushLinkLabelState(pc Context, v *linkLabelState) {
|
||||
tlist := pc.Get(linkLabelStateKey)
|
||||
var list *linkLabelState
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"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.
|
||||
type ContextKey int32
|
||||
|
||||
// New returns a new ContextKey value.
|
||||
func (c *ContextKey) New() ContextKey {
|
||||
return ContextKey(atomic.AddInt32((*int32)(c), 1))
|
||||
}
|
||||
type ContextKey int
|
||||
|
||||
// ContextKeyMax is a maximum value of the ContextKey.
|
||||
var ContextKeyMax ContextKey
|
||||
|
||||
// NewContextKey return a new ContextKey value.
|
||||
func NewContextKey() ContextKey {
|
||||
return ContextKeyMax.New()
|
||||
ContextKeyMax++
|
||||
return ContextKeyMax
|
||||
}
|
||||
|
||||
// A Context interface holds a information that are necessary to parse
|
||||
|
|
@ -127,9 +122,6 @@ type Context interface {
|
|||
|
||||
// LastOpenedBlock returns a last node that is currently in parsing.
|
||||
LastOpenedBlock() Block
|
||||
|
||||
// SetLastOpenedBlock sets a last node that is currently in parsing.
|
||||
SetLastOpenedBlock(Block)
|
||||
}
|
||||
|
||||
type parseContext struct {
|
||||
|
|
@ -140,7 +132,6 @@ type parseContext struct {
|
|||
delimiters *Delimiter
|
||||
lastDelimiter *Delimiter
|
||||
openedBlocks []Block
|
||||
lastOpenedBlock Block
|
||||
}
|
||||
|
||||
// NewContext returns a new Context.
|
||||
|
|
@ -153,7 +144,6 @@ func NewContext(source []byte) Context {
|
|||
delimiters: nil,
|
||||
lastDelimiter: nil,
|
||||
openedBlocks: []Block{},
|
||||
lastOpenedBlock: Block{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,11 +266,10 @@ func (p *parseContext) SetOpenedBlocks(v []Block) {
|
|||
}
|
||||
|
||||
func (p *parseContext) LastOpenedBlock() Block {
|
||||
return p.lastOpenedBlock
|
||||
}
|
||||
|
||||
func (p *parseContext) SetLastOpenedBlock(v Block) {
|
||||
p.lastOpenedBlock = v
|
||||
if l := len(p.openedBlocks); l != 0 {
|
||||
return p.openedBlocks[l-1]
|
||||
}
|
||||
return Block{}
|
||||
}
|
||||
|
||||
// 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
|
||||
// position by consumed byte length.
|
||||
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(parent ast.Node, pc Context)
|
||||
}
|
||||
|
|
@ -487,7 +480,7 @@ type parser struct {
|
|||
options map[OptionName]interface{}
|
||||
blockParsers []BlockParser
|
||||
inlineParsers [256][]InlineParser
|
||||
inlineParsersList []InlineParser
|
||||
closeBlockers []CloseBlocker
|
||||
paragraphTransformers []ParagraphTransformer
|
||||
astTransformers []ASTTransformer
|
||||
config *Config
|
||||
|
|
@ -610,7 +603,9 @@ func (p *parser) addInlineParser(v util.PrioritizedValue, options map[OptionName
|
|||
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 {
|
||||
if p.inlineParsers[tc] == nil {
|
||||
p.inlineParsers[tc] = []InlineParser{}
|
||||
|
|
@ -715,30 +710,20 @@ func (p *parser) transformParagraph(node *ast.Paragraph, pc Context) {
|
|||
|
||||
func (p *parser) closeBlocks(from, to int, pc Context) {
|
||||
blocks := pc.OpenedBlocks()
|
||||
last := pc.LastOpenedBlock()
|
||||
for i := from; i >= to; i-- {
|
||||
node := blocks[i].Node
|
||||
if node.Parent() != nil {
|
||||
blocks[i].Parser.Close(blocks[i].Node, pc)
|
||||
paragraph, ok := node.(*ast.Paragraph)
|
||||
if ok && node.Parent() != nil {
|
||||
p.transformParagraph(paragraph, pc)
|
||||
}
|
||||
}
|
||||
}
|
||||
if from == len(blocks)-1 {
|
||||
blocks = blocks[0:to]
|
||||
} else {
|
||||
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.SetLastOpenedBlock(last)
|
||||
}
|
||||
|
||||
type blockOpenResult int
|
||||
|
|
@ -758,13 +743,13 @@ func (p *parser) openBlocks(parent ast.Node, blankLine bool, reader text.Reader,
|
|||
}
|
||||
retry:
|
||||
shouldPeek := true
|
||||
var currentLineNum int
|
||||
//var currentLineNum int
|
||||
var w int
|
||||
var pos int
|
||||
var line []byte
|
||||
for _, bp := range p.blockParsers {
|
||||
if shouldPeek {
|
||||
currentLineNum, _ = reader.Position()
|
||||
//currentLineNum, _ = reader.Position()
|
||||
line, _ = reader.PeekLine()
|
||||
w, pos = util.IndentWidth(line, 0)
|
||||
pc.SetBlockOffset(pos)
|
||||
|
|
@ -781,9 +766,9 @@ retry:
|
|||
}
|
||||
last := pc.LastOpenedBlock().Node
|
||||
node, state := bp.Open(parent, reader, pc)
|
||||
if l, _ := reader.Position(); l != currentLineNum {
|
||||
panic("BlockParser.Open must not advance position beyond the current line")
|
||||
}
|
||||
// if l, _ := reader.Position(); l != currentLineNum {
|
||||
// panic("BlockParser.Open must not advance position beyond the current line")
|
||||
// }
|
||||
if node != nil {
|
||||
shouldPeek = true
|
||||
node.SetBlankPreviousLines(blankLine)
|
||||
|
|
@ -795,7 +780,6 @@ retry:
|
|||
result = newBlocksOpened
|
||||
be := Block{node, bp}
|
||||
pc.SetOpenedBlocks(append(pc.OpenedBlocks(), be))
|
||||
pc.SetLastOpenedBlock(be)
|
||||
if state == HasChildren {
|
||||
parent = node
|
||||
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) {
|
||||
pc.SetLastOpenedBlock(Block{})
|
||||
pc.SetOpenedBlocks([]Block{})
|
||||
blankLines := make([]lineStat, 0, 64)
|
||||
isBlank := false
|
||||
|
|
@ -848,14 +831,20 @@ func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
|
|||
return
|
||||
}
|
||||
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})
|
||||
}
|
||||
reader.AdvanceLine()
|
||||
for len(pc.OpenedBlocks()) != 0 { // process opened blocks line by line
|
||||
lastIndex := len(pc.OpenedBlocks()) - 1
|
||||
for i := 0; i < len(pc.OpenedBlocks()); i++ {
|
||||
be := pc.OpenedBlocks()[i]
|
||||
for { // process opened blocks line by line
|
||||
openedBlocks := pc.OpenedBlocks()
|
||||
l := len(openedBlocks)
|
||||
if l == 0 {
|
||||
break
|
||||
}
|
||||
lastIndex := l - 1
|
||||
for i := 0; i < l; i++ {
|
||||
be := openedBlocks[i]
|
||||
line, _ := reader.PeekLine()
|
||||
if line == nil {
|
||||
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)
|
||||
thisParent := parent
|
||||
if i != 0 {
|
||||
thisParent = pc.OpenedBlocks()[i-1].Node
|
||||
thisParent = openedBlocks[i-1].Node
|
||||
}
|
||||
result := p.openBlocks(thisParent, isBlank, reader, pc)
|
||||
if result != paragraphContinuation {
|
||||
|
|
@ -998,7 +987,7 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
|
|||
}
|
||||
|
||||
ProcessDelimiters(nil, pc)
|
||||
for _, ip := range p.inlineParsersList {
|
||||
for _, ip := range p.closeBlockers {
|
||||
ip.CloseBlock(parent, pc)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -158,53 +158,33 @@ func NewRenderer(opts ...Option) renderer.NodeRenderer {
|
|||
return r
|
||||
}
|
||||
|
||||
// Render implements renderer.NodeRenderer.Render.
|
||||
func (r *Renderer) Render(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
switch node := n.(type) {
|
||||
|
||||
// RegisterFuncs implements NodeRenderer.RegisterFuncs .
|
||||
func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
// blocks
|
||||
|
||||
case *ast.Document:
|
||||
return r.renderDocument(writer, source, node, entering), nil
|
||||
case *ast.Heading:
|
||||
return r.renderHeading(writer, source, node, entering), nil
|
||||
case *ast.Blockquote:
|
||||
return r.renderBlockquote(writer, source, node, entering), nil
|
||||
case *ast.CodeBlock:
|
||||
return r.renderCodeBlock(writer, source, node, entering), nil
|
||||
case *ast.FencedCodeBlock:
|
||||
return r.renderFencedCodeBlock(writer, source, node, entering), nil
|
||||
case *ast.HTMLBlock:
|
||||
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
|
||||
reg.Register(ast.KindDocument, r.renderDocument)
|
||||
reg.Register(ast.KindHeading, r.renderHeading)
|
||||
reg.Register(ast.KindBlockquote, r.renderBlockquote)
|
||||
reg.Register(ast.KindCodeBlock, r.renderCodeBlock)
|
||||
reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock)
|
||||
reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
|
||||
reg.Register(ast.KindList, r.renderList)
|
||||
reg.Register(ast.KindListItem, r.renderListItem)
|
||||
reg.Register(ast.KindParagraph, r.renderParagraph)
|
||||
reg.Register(ast.KindTextBlock, r.renderTextBlock)
|
||||
reg.Register(ast.KindThemanticBreak, r.renderThemanticBreak)
|
||||
|
||||
// inlines
|
||||
|
||||
case *ast.AutoLink:
|
||||
return r.renderAutoLink(writer, source, node, entering), nil
|
||||
case *ast.CodeSpan:
|
||||
return r.renderCodeSpan(writer, source, node, entering), nil
|
||||
case *ast.Emphasis:
|
||||
return r.renderEmphasis(writer, source, node, entering), nil
|
||||
case *ast.Image:
|
||||
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
|
||||
reg.Register(ast.KindAutoLink, r.renderAutoLink)
|
||||
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
|
||||
reg.Register(ast.KindEmphasis, r.renderEmphasis)
|
||||
reg.Register(ast.KindImage, r.renderImage)
|
||||
reg.Register(ast.KindLink, r.renderLink)
|
||||
reg.Register(ast.KindRawHTML, r.renderRawHTML)
|
||||
reg.Register(ast.KindText, r.renderText)
|
||||
}
|
||||
|
||||
func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) {
|
||||
l := n.Lines().Len()
|
||||
for i := 0; i < l; i++ {
|
||||
|
|
@ -213,49 +193,56 @@ 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
|
||||
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 {
|
||||
w.WriteString("<h")
|
||||
w.WriteByte("0123456"[n.Level])
|
||||
if n.ID != nil {
|
||||
if n.Attributes() != nil {
|
||||
id, ok := n.Attribute(attrNameID)
|
||||
if ok {
|
||||
w.WriteString(` id="`)
|
||||
w.Write(n.ID)
|
||||
w.Write(id)
|
||||
w.WriteByte('"')
|
||||
}
|
||||
}
|
||||
w.WriteByte('>')
|
||||
} else {
|
||||
w.WriteString("</h")
|
||||
w.WriteByte("0123456"[n.Level])
|
||||
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 {
|
||||
w.WriteString("<blockquote>\n")
|
||||
} else {
|
||||
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 {
|
||||
w.WriteString("<pre><code>")
|
||||
r.writeLines(w, source, n)
|
||||
} else {
|
||||
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 {
|
||||
w.WriteString("<pre><code")
|
||||
if n.Info != nil {
|
||||
|
|
@ -277,10 +264,11 @@ func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, n *ast
|
|||
} else {
|
||||
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 r.Unsafe {
|
||||
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"
|
||||
if n.IsOrdered() {
|
||||
tag = "ol"
|
||||
|
|
@ -322,10 +311,10 @@ func (r *Renderer) renderList(w util.BufWriter, source []byte, n *ast.List, ente
|
|||
w.WriteString(tag)
|
||||
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 {
|
||||
w.WriteString("<li>")
|
||||
fc := n.FirstChild()
|
||||
|
|
@ -337,43 +326,43 @@ func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n *ast.ListIt
|
|||
} else {
|
||||
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 {
|
||||
w.WriteString("<p>")
|
||||
} else {
|
||||
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 _, ok := n.NextSibling().(ast.Node); ok && n.FirstChild() != nil {
|
||||
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 {
|
||||
return ast.WalkContinue
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
if r.XHTML {
|
||||
w.WriteString("<hr />\n")
|
||||
} else {
|
||||
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 {
|
||||
return ast.WalkContinue
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
w.WriteString(`<a href="`)
|
||||
segment := n.Value.Segment
|
||||
|
|
@ -385,10 +374,10 @@ func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, n *ast.AutoLi
|
|||
w.WriteString(`">`)
|
||||
w.Write(util.EscapeHTML(value))
|
||||
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 {
|
||||
w.WriteString("<code>")
|
||||
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)
|
||||
}
|
||||
}
|
||||
return ast.WalkSkipChildren
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
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"
|
||||
if n.Level == 2 {
|
||||
tag = "strong"
|
||||
|
|
@ -423,10 +413,11 @@ func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, n *ast.Emphas
|
|||
w.WriteString(tag)
|
||||
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 {
|
||||
w.WriteString("<a href=\"")
|
||||
if r.Unsafe || !IsDangerousURL(n.Destination) {
|
||||
|
|
@ -442,12 +433,13 @@ func (r *Renderer) renderLink(w util.BufWriter, source []byte, n *ast.Link, ente
|
|||
} else {
|
||||
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 {
|
||||
return ast.WalkContinue
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
n := node.(*ast.Image)
|
||||
w.WriteString("<img src=\"")
|
||||
if r.Unsafe || !IsDangerousURL(n.Destination) {
|
||||
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 {
|
||||
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 {
|
||||
return ast.WalkContinue
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
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 {
|
||||
return ast.WalkContinue
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
n := node.(*ast.Text)
|
||||
segment := n.Segment
|
||||
if n.IsRaw() {
|
||||
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')
|
||||
}
|
||||
}
|
||||
return ast.WalkContinue
|
||||
}
|
||||
|
||||
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
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
// A Writer interface wirtes textual contents to a writer.
|
||||
|
|
@ -526,11 +505,9 @@ type Writer interface {
|
|||
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) {
|
||||
if r < 256 {
|
||||
v := htmlEscaleTable[byte(r)]
|
||||
v := util.EscapeHTMLByte(byte(r))
|
||||
if v != nil {
|
||||
writer.Write(v)
|
||||
return
|
||||
|
|
@ -543,7 +520,7 @@ func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) {
|
|||
n := 0
|
||||
l := len(source)
|
||||
for i := 0; i < l; i++ {
|
||||
v := htmlEscaleTable[source[i]]
|
||||
v := util.EscapeHTMLByte(source[i])
|
||||
if v != nil {
|
||||
writer.Write(source[i-n : i])
|
||||
n = 0
|
||||
|
|
@ -581,7 +558,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
|
|||
// code point like #x22;
|
||||
if nnext < limit && nc == 'x' || nc == 'X' {
|
||||
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] == ';' {
|
||||
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32)
|
||||
d.RawWrite(writer, source[n:pos])
|
||||
|
|
@ -592,7 +569,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
|
|||
// code point like #1234;
|
||||
} else if nc >= '0' && nc <= '9' {
|
||||
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] == ';' {
|
||||
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 0, 32)
|
||||
d.RawWrite(writer, source[n:pos])
|
||||
|
|
@ -603,7 +580,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
|
|||
}
|
||||
} else {
|
||||
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
|
||||
if ok && i < limit && source[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.
|
||||
type OptionName string
|
||||
|
||||
|
|
@ -80,10 +70,19 @@ type SetOptioner 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 {
|
||||
// Render renders given AST node to given writer.
|
||||
Render(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error)
|
||||
// RendererFuncs registers NodeRendererFuncs to given NodeRendererFuncRegisterer.
|
||||
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
|
||||
|
|
@ -98,7 +97,9 @@ type Renderer interface {
|
|||
type renderer struct {
|
||||
config *Config
|
||||
options map[OptionName]interface{}
|
||||
nodeRenderers []NodeRenderer
|
||||
nodeRendererFuncsTmp map[ast.NodeKind]NodeRendererFunc
|
||||
maxKind int
|
||||
nodeRendererFuncs []NodeRendererFunc
|
||||
initSync sync.Once
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +113,7 @@ func NewRenderer(options ...Option) Renderer {
|
|||
r := &renderer{
|
||||
options: map[OptionName]interface{}{},
|
||||
config: config,
|
||||
nodeRendererFuncsTmp: map[ast.NodeKind]NodeRendererFunc{},
|
||||
}
|
||||
|
||||
return r
|
||||
|
|
@ -121,36 +123,46 @@ func (r *renderer) AddOption(o Option) {
|
|||
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.
|
||||
func (r *renderer) Render(w io.Writer, source []byte, n ast.Node) error {
|
||||
r.initSync.Do(func() {
|
||||
r.options = r.config.Options
|
||||
r.config.NodeRenderers.Sort()
|
||||
r.nodeRenderers = make([]NodeRenderer, 0, len(r.config.NodeRenderers))
|
||||
for _, v := range r.config.NodeRenderers {
|
||||
l := len(r.config.NodeRenderers)
|
||||
for i := l - 1; i >= 0; i-- {
|
||||
v := r.config.NodeRenderers[i]
|
||||
nr, _ := v.Value.(NodeRenderer)
|
||||
if se, ok := v.Value.(SetOptioner); ok {
|
||||
for oname, ovalue := range r.options {
|
||||
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.nodeRendererFuncsTmp = nil
|
||||
})
|
||||
writer, ok := w.(util.BufWriter)
|
||||
if !ok {
|
||||
writer = bufio.NewWriter(w)
|
||||
}
|
||||
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
|
||||
for _, nr := range r.nodeRenderers {
|
||||
s, err = nr.Render(writer, source, n, entering)
|
||||
if err == NotSupported {
|
||||
continue
|
||||
}
|
||||
break
|
||||
f := r.nodeRendererFuncs[n.Kind()]
|
||||
if f != nil {
|
||||
s, err = f(writer, source, n, entering)
|
||||
}
|
||||
return s, err
|
||||
})
|
||||
|
|
|
|||
364
util/util.go
364
util/util.go
|
|
@ -6,13 +6,70 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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.
|
||||
func IsBlank(bs []byte) bool {
|
||||
for _, b := range bs {
|
||||
|
|
@ -26,6 +83,9 @@ func IsBlank(bs []byte) bool {
|
|||
|
||||
// DedentPosition dedents lines by given width.
|
||||
func DedentPosition(bs []byte, width int) (pos, padding int) {
|
||||
if width == 0 {
|
||||
return
|
||||
}
|
||||
i := 0
|
||||
l := len(bs)
|
||||
w := 0
|
||||
|
|
@ -307,30 +367,22 @@ func ToLinkReference(v []byte) string {
|
|||
return strings.ToLower(string(ReplaceSpaces(v, ' ')))
|
||||
}
|
||||
|
||||
var escapeRegex = regexp.MustCompile(`\\.`)
|
||||
var hexRefRegex = regexp.MustCompile(`#[xX][\da-fA-F]+;`)
|
||||
var numRefRegex = regexp.MustCompile(`#\d{1,7};`)
|
||||
var entityRefRegex = regexp.MustCompile(`&([a-zA-Z\d]+);`)
|
||||
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 entityLt = []byte("<")
|
||||
var entityGt = []byte(">")
|
||||
var entityAmp = []byte("&")
|
||||
var entityQuot = []byte(""")
|
||||
// EscapeHTMLByte returns HTML escaped bytes if given byte should be escaped,
|
||||
// otherwise nil.
|
||||
func EscapeHTMLByte(b byte) []byte {
|
||||
return htmlEscapeTable[b]
|
||||
}
|
||||
|
||||
// EscapeHTML escapes characters that should be escaped in HTML text.
|
||||
func EscapeHTML(v []byte) []byte {
|
||||
result := make([]byte, 0, len(v)+10)
|
||||
for _, c := range v {
|
||||
switch c {
|
||||
case '<':
|
||||
result = append(result, entityLt...)
|
||||
case '>':
|
||||
result = append(result, entityGt...)
|
||||
case '&':
|
||||
result = append(result, entityAmp...)
|
||||
case '"':
|
||||
result = append(result, entityQuot...)
|
||||
default:
|
||||
escaped := htmlEscapeTable[c]
|
||||
if escaped != nil {
|
||||
result = append(result, escaped...)
|
||||
} else {
|
||||
result = append(result, c)
|
||||
}
|
||||
}
|
||||
|
|
@ -338,41 +390,111 @@ func EscapeHTML(v []byte) []byte {
|
|||
}
|
||||
|
||||
// UnescapePunctuations unescapes blackslash escaped punctuations.
|
||||
func UnescapePunctuations(v []byte) []byte {
|
||||
return escapeRegex.ReplaceAllFunc(v, func(match []byte) []byte {
|
||||
if IsPunct(match[1]) {
|
||||
return []byte{match[1]}
|
||||
func UnescapePunctuations(source []byte) []byte {
|
||||
cob := NewCopyOnWriteBuffer(source)
|
||||
limit := len(source)
|
||||
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 'Ӓ" .
|
||||
func ResolveNumericReferences(v []byte) []byte {
|
||||
func ResolveNumericReferences(source []byte) []byte {
|
||||
cob := NewCopyOnWriteBuffer(source)
|
||||
buf := make([]byte, 6, 6)
|
||||
v = hexRefRegex.ReplaceAllFunc(v, func(match []byte) []byte {
|
||||
v, _ := strconv.ParseUint(string(match[2:len(match)-1]), 16, 32)
|
||||
n := utf8.EncodeRune(buf, ToValidRune(rune(v)))
|
||||
return buf[:n]
|
||||
})
|
||||
return numRefRegex.ReplaceAllFunc(v, func(match []byte) []byte {
|
||||
v, _ := strconv.ParseUint(string(match[1:len(match)-1]), 0, 32)
|
||||
n := utf8.EncodeRune(buf, ToValidRune(rune(v)))
|
||||
return buf[:n]
|
||||
})
|
||||
limit := len(source)
|
||||
ok := false
|
||||
n := 0
|
||||
for i := 0; i < limit; i++ {
|
||||
if source[i] == '&' {
|
||||
pos := i
|
||||
next := i + 1
|
||||
if next < limit && source[next] == '#' {
|
||||
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 'ö" .
|
||||
func ResolveEntityNames(v []byte) []byte {
|
||||
return entityRefRegex.ReplaceAllFunc(v, func(match []byte) []byte {
|
||||
entity, ok := LookUpHTML5EntityByName(string(match[1 : len(match)-1]))
|
||||
func ResolveEntityNames(source []byte) []byte {
|
||||
cob := NewCopyOnWriteBuffer(source)
|
||||
limit := len(source)
|
||||
ok := false
|
||||
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 {
|
||||
return entity.Characters
|
||||
cob.Write(source[n:pos])
|
||||
n = i + 1
|
||||
cob.Write(entity.Characters)
|
||||
continue
|
||||
}
|
||||
return match
|
||||
})
|
||||
}
|
||||
}
|
||||
i = next - 1
|
||||
}
|
||||
}
|
||||
if cob.IsCopied() {
|
||||
cob.Write(source[n:len(source)])
|
||||
}
|
||||
return cob.Bytes()
|
||||
}
|
||||
|
||||
var htmlSpace = []byte("%20")
|
||||
|
||||
// URLEscape escape given URL.
|
||||
// If resolveReference is set true:
|
||||
// 1. unescape punctuations
|
||||
|
|
@ -386,32 +508,174 @@ func URLEscape(v []byte, resolveReference bool) []byte {
|
|||
v = ResolveNumericReferences(v)
|
||||
v = ResolveEntityNames(v)
|
||||
}
|
||||
result := make([]byte, 0, len(v)+10)
|
||||
for i := 0; i < len(v); {
|
||||
ret := 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]
|
||||
if urlEscapeTable[c] == 1 {
|
||||
result = append(result, c)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if c == '%' && i+2 < len(v) && IsHexDecimal(v[i+1]) && IsHexDecimal(v[i+1]) {
|
||||
result = append(result, c, v[i+1], v[i+2])
|
||||
if c == '%' && i+2 < limit && IsHexDecimal(v[i+1]) && IsHexDecimal(v[i+1]) {
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
u8len := utf8lenTable[c]
|
||||
if u8len == 99 { // invalid utf8 leading byte, skip it
|
||||
result = append(result, c)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if c == ' ' {
|
||||
result = append(result, '%', '2', '0')
|
||||
add(v[n:i])
|
||||
add(htmlSpace)
|
||||
i++
|
||||
n = i
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue