mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Add a definition list extension, some refactoring
This commit is contained in:
parent
7acda36819
commit
d4d7acb277
26 changed files with 467 additions and 139 deletions
14
README.md
14
README.md
|
|
@ -26,7 +26,11 @@ I need a markdown parser for Go that meets following conditions:
|
||||||
|
|
||||||
[golang-commonmark](https://gitlab.com/golang-commonmark/markdown) may be a good choice, but it seems copy of the [markdown-it](https://github.com/markdown-it) .
|
[golang-commonmark](https://gitlab.com/golang-commonmark/markdown) may be a good choice, but it seems copy of the [markdown-it](https://github.com/markdown-it) .
|
||||||
|
|
||||||
[blackfriday.v2](https://github.com/russross/blackfriday/tree/v2) is a fast and widely used implementation, but it is not CommonMark compliant and can not be extended from outside of the package since it's AST is not interfaces but structs.
|
[blackfriday.v2](https://github.com/russross/blackfriday/tree/v2) is a fast and widely used implementation, but it is not CommonMark compliant and can not be extended from outside of the package since it's AST is not interfaces but structs.
|
||||||
|
|
||||||
|
Furthermore, its behavior differs with other implementations in some cases especially of lists. ([Deep nested lists don't output correctly #329](https://github.com/russross/blackfriday/issues/329), [List block cannot have a second line #244](https://github.com/russross/blackfriday/issues/244), etc).
|
||||||
|
|
||||||
|
This behavior sometimes causes problems. If you migrate your markdown text to blackfriday based wikis from Github, many lists will immediately be broken.
|
||||||
|
|
||||||
As mentioned above, CommonMark is too complicated and hard to implement, So Markdown parsers base on CommonMark barely exist.
|
As mentioned above, CommonMark is too complicated and hard to implement, So Markdown parsers base on CommonMark barely exist.
|
||||||
|
|
||||||
|
|
@ -68,9 +72,9 @@ Parser and Renderer options
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `parser.WithBlockParsers` | List of `util.PrioritizedSlice` whose elements are `parser.BlockParser` | Parsers for parsing block level elements. |
|
| `parser.WithBlockParsers` | A `util.PrioritizedSlice` whose elements are `parser.BlockParser` | Parsers for parsing block level elements. |
|
||||||
| `parser.WithInlineParsers` | List of `util.PrioritizedSlice` whose elements are `parser.InlineParser` | Parsers for parsing inline level elements. |
|
| `parser.WithInlineParsers` | A `util.PrioritizedSlice` whose elements are `parser.InlineParser` | Parsers for parsing inline level elements. |
|
||||||
| `parser.WithParagraphTransformers` | List of `util.PrioritizedSlice` whose elements are `parser.ParagraphTransformer` | Transformers for transforming paragraph nodes. |
|
| `parser.WithParagraphTransformers` | A `util.PrioritizedSlice` whose elements are `parser.ParagraphTransformer` | Transformers for transforming paragraph nodes. |
|
||||||
| `parser.WithHeadingID` | `-` | Enables custom heading ids( `{#custom-id}` ) and auto heading ids. |
|
| `parser.WithHeadingID` | `-` | Enables custom heading ids( `{#custom-id}` ) and auto heading ids. |
|
||||||
| `parser.WithFilterTags` | `...string` | HTML tag names forbidden in HTML blocks and Raw HTMLs. |
|
| `parser.WithFilterTags` | `...string` | HTML tag names forbidden in HTML blocks and Raw HTMLs. |
|
||||||
|
|
||||||
|
|
@ -92,6 +96,8 @@ Parser and Renderer options
|
||||||
- `extension.GFM`
|
- `extension.GFM`
|
||||||
- This extension enables Table, Strikethrough, Linkify and TaskList.
|
- This extension enables Table, Strikethrough, Linkify and TaskList.
|
||||||
In addition, this extension sets some tags to `parser.FilterTags` .
|
In addition, this extension sets some tags to `parser.FilterTags` .
|
||||||
|
- `extension.DefinitionList`
|
||||||
|
- [PHP Markdown Extra Definition lists](https://michelf.ca/projects/php-markdown/extra/#def-list)
|
||||||
|
|
||||||
Create extensions
|
Create extensions
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,11 @@ 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 sets the given value to the attributes.
|
||||||
SetAttribute(name, value []byte)
|
SetAttribute(name, value []byte)
|
||||||
|
|
||||||
// Attribute returns a (attribute value, true) if an attribute
|
// Attribute returns a (attribute value, true) if an attribute
|
||||||
// associated with given name is found, otherwise
|
// associated with the given name is found, otherwise
|
||||||
// (nil, false)
|
// (nil, false)
|
||||||
Attribute(name []byte) ([]byte, bool)
|
Attribute(name []byte) ([]byte, bool)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ func NewParagraph() *Paragraph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsParagraph returns true if given node implements the Paragraph interface,
|
// IsParagraph returns true if the given node implements the Paragraph interface,
|
||||||
// otherwise false.
|
// otherwise false.
|
||||||
func IsParagraph(node Node) bool {
|
func IsParagraph(node Node) bool {
|
||||||
_, ok := node.(*Paragraph)
|
_, ok := node.(*Paragraph)
|
||||||
|
|
@ -305,7 +305,7 @@ func (l *List) IsOrdered() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanContinue returns true if this list can continue with
|
// CanContinue returns true if this list can continue with
|
||||||
// given mark and a list type, otherwise false.
|
// the given mark and a list type, otherwise false.
|
||||||
func (l *List) CanContinue(marker byte, isOrdered bool) bool {
|
func (l *List) CanContinue(marker byte, isOrdered bool) bool {
|
||||||
return marker == l.Marker && isOrdered == l.IsOrdered()
|
return marker == l.Marker && isOrdered == l.IsOrdered()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ func (n *Text) SetHardLineBreak(v bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge merges a Node n into this node.
|
// Merge merges a Node n into this node.
|
||||||
// Merge returns true if given node has been merged, otherwise false.
|
// Merge returns true if the given node has been merged, otherwise false.
|
||||||
func (n *Text) Merge(node Node, source []byte) bool {
|
func (n *Text) Merge(node Node, source []byte) bool {
|
||||||
t, ok := node.(*Text)
|
t, ok := node.(*Text)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -148,7 +148,7 @@ func NewText() *Text {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTextSegment returns a new Text node with given source potision.
|
// NewTextSegment returns a new Text node with the given source potision.
|
||||||
func NewTextSegment(v textm.Segment) *Text {
|
func NewTextSegment(v textm.Segment) *Text {
|
||||||
return &Text{
|
return &Text{
|
||||||
BaseInline: BaseInline{},
|
BaseInline: BaseInline{},
|
||||||
|
|
@ -156,7 +156,7 @@ func NewTextSegment(v textm.Segment) *Text {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRawTextSegment returns a new Text node with given source position.
|
// NewRawTextSegment returns a new Text node with the given source position.
|
||||||
// The new node should be rendered as raw contents.
|
// The new node should be rendered as raw contents.
|
||||||
func NewRawTextSegment(v textm.Segment) *Text {
|
func NewRawTextSegment(v textm.Segment) *Text {
|
||||||
t := &Text{
|
t := &Text{
|
||||||
|
|
@ -256,7 +256,7 @@ func (n *Emphasis) Kind() NodeKind {
|
||||||
return KindEmphasis
|
return KindEmphasis
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEmphasis returns a new Emphasis node with given level.
|
// NewEmphasis returns a new Emphasis node with the given level.
|
||||||
func NewEmphasis(level int) *Emphasis {
|
func NewEmphasis(level int) *Emphasis {
|
||||||
return &Emphasis{
|
return &Emphasis{
|
||||||
BaseInline: BaseInline{},
|
BaseInline: BaseInline{},
|
||||||
|
|
|
||||||
83
extension/ast/definition_list.go
Normal file
83
extension/ast/definition_list.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
gast "github.com/yuin/goldmark/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A DefinitionList struct represents a definition list of Markdown
|
||||||
|
// (PHPMarkdownExtra) text.
|
||||||
|
type DefinitionList struct {
|
||||||
|
gast.BaseBlock
|
||||||
|
Offset int
|
||||||
|
TemporaryParagraph *gast.Paragraph
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump implements Node.Dump.
|
||||||
|
func (n *DefinitionList) Dump(source []byte, level int) {
|
||||||
|
gast.DumpHelper(n, source, level, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KindDefinitionList is a NodeKind of the DefinitionList node.
|
||||||
|
var KindDefinitionList = gast.NewNodeKind("DefinitionList")
|
||||||
|
|
||||||
|
// Kind implements Node.Kind.
|
||||||
|
func (n *DefinitionList) Kind() gast.NodeKind {
|
||||||
|
return KindDefinitionList
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefinitionList returns a new DefinitionList node.
|
||||||
|
func NewDefinitionList(offset int, para *gast.Paragraph) *DefinitionList {
|
||||||
|
return &DefinitionList{
|
||||||
|
Offset: offset,
|
||||||
|
TemporaryParagraph: para,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DefinitionTerm struct represents a definition list term of Markdown
|
||||||
|
// (PHPMarkdownExtra) text.
|
||||||
|
type DefinitionTerm struct {
|
||||||
|
gast.BaseBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump implements Node.Dump.
|
||||||
|
func (n *DefinitionTerm) Dump(source []byte, level int) {
|
||||||
|
gast.DumpHelper(n, source, level, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KindDefinitionTerm is a NodeKind of the DefinitionTerm node.
|
||||||
|
var KindDefinitionTerm = gast.NewNodeKind("DefinitionTerm")
|
||||||
|
|
||||||
|
// Kind implements Node.Kind.
|
||||||
|
func (n *DefinitionTerm) Kind() gast.NodeKind {
|
||||||
|
return KindDefinitionTerm
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefinitionTerm returns a new DefinitionTerm node.
|
||||||
|
func NewDefinitionTerm() *DefinitionTerm {
|
||||||
|
return &DefinitionTerm{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DefinitionDescription struct represents a definition list description of Markdown
|
||||||
|
// (PHPMarkdownExtra) text.
|
||||||
|
type DefinitionDescription struct {
|
||||||
|
gast.BaseBlock
|
||||||
|
IsTight bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump implements Node.Dump.
|
||||||
|
func (n *DefinitionDescription) Dump(source []byte, level int) {
|
||||||
|
gast.DumpHelper(n, source, level, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KindDefinitionDescription is a NodeKind of the DefinitionDescription node.
|
||||||
|
var KindDefinitionDescription = gast.NewNodeKind("DefinitionDescription")
|
||||||
|
|
||||||
|
// Kind implements Node.Kind.
|
||||||
|
func (n *DefinitionDescription) Kind() gast.NodeKind {
|
||||||
|
return KindDefinitionDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefinitionDescription returns a new DefinitionDescription node.
|
||||||
|
func NewDefinitionDescription() *DefinitionDescription {
|
||||||
|
return &DefinitionDescription{}
|
||||||
|
}
|
||||||
230
extension/definition_list.go
Normal file
230
extension/definition_list.go
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
package extension
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
gast "github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/extension/ast"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type definitionListParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultDefinitionListParser = &definitionListParser{}
|
||||||
|
|
||||||
|
// NewDefinitionListParser return a new parser.BlockParser that
|
||||||
|
// can parse PHP Markdown Extra Definition lists.
|
||||||
|
func NewDefinitionListParser() parser.BlockParser {
|
||||||
|
return defaultDefinitionListParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
|
||||||
|
if _, ok := parent.(*ast.DefinitionList); ok {
|
||||||
|
return nil, parser.NoChildren
|
||||||
|
}
|
||||||
|
line, _ := reader.PeekLine()
|
||||||
|
pos := pc.BlockOffset()
|
||||||
|
if line[pos] != ':' {
|
||||||
|
return nil, parser.NoChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
last := parent.LastChild()
|
||||||
|
// need 1 or more spaces after ':'
|
||||||
|
w, _ := util.IndentWidth(line[pos+1:], pos+1)
|
||||||
|
if w < 1 {
|
||||||
|
return nil, parser.NoChildren
|
||||||
|
}
|
||||||
|
if w >= 8 { // starts with indented code
|
||||||
|
w = 5
|
||||||
|
}
|
||||||
|
w += pos + 1 /* 1 = ':' */
|
||||||
|
|
||||||
|
para, lastIsParagraph := last.(*gast.Paragraph)
|
||||||
|
var list *ast.DefinitionList
|
||||||
|
var ok bool
|
||||||
|
if lastIsParagraph {
|
||||||
|
list, ok = last.PreviousSibling().(*ast.DefinitionList)
|
||||||
|
if ok { // is not first item
|
||||||
|
list.Offset = w
|
||||||
|
list.TemporaryParagraph = para
|
||||||
|
} else { // is first item
|
||||||
|
list = ast.NewDefinitionList(w, para)
|
||||||
|
}
|
||||||
|
} else if list, ok = last.(*ast.DefinitionList); ok { // multiple description
|
||||||
|
list.Offset = w
|
||||||
|
list.TemporaryParagraph = nil
|
||||||
|
} else {
|
||||||
|
return nil, parser.NoChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, parser.HasChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionListParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
|
||||||
|
line, _ := reader.PeekLine()
|
||||||
|
if util.IsBlank(line) {
|
||||||
|
return parser.Continue | parser.HasChildren
|
||||||
|
}
|
||||||
|
list, _ := node.(*ast.DefinitionList)
|
||||||
|
w, _ := util.IndentWidth(line, reader.LineOffset())
|
||||||
|
if w < list.Offset {
|
||||||
|
return parser.Close
|
||||||
|
}
|
||||||
|
pos, padding := util.IndentPosition(line, reader.LineOffset(), list.Offset)
|
||||||
|
reader.AdvanceAndSetPadding(pos, padding)
|
||||||
|
return parser.Continue | parser.HasChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionListParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionListParser) CanInterruptParagraph() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionListParser) CanAcceptIndentedLine() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type definitionDescriptionParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultDefinitionDescriptionParser = &definitionDescriptionParser{}
|
||||||
|
|
||||||
|
// NewDefinitionDescriptionParser return a new parser.BlockParser that
|
||||||
|
// can parse definition description starts with ':'.
|
||||||
|
func NewDefinitionDescriptionParser() parser.BlockParser {
|
||||||
|
return defaultDefinitionDescriptionParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
|
||||||
|
line, _ := reader.PeekLine()
|
||||||
|
pos := pc.BlockOffset()
|
||||||
|
if line[pos] != ':' {
|
||||||
|
return nil, parser.NoChildren
|
||||||
|
}
|
||||||
|
list, _ := parent.(*ast.DefinitionList)
|
||||||
|
para := list.TemporaryParagraph
|
||||||
|
list.TemporaryParagraph = nil
|
||||||
|
if para != nil {
|
||||||
|
lines := para.Lines()
|
||||||
|
l := lines.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
term := ast.NewDefinitionTerm()
|
||||||
|
segment := lines.At(i)
|
||||||
|
term.Lines().Append(segment.TrimRightSpace(reader.Source()))
|
||||||
|
list.AppendChild(list, term)
|
||||||
|
}
|
||||||
|
para.Parent().RemoveChild(para.Parent(), para)
|
||||||
|
}
|
||||||
|
cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
|
||||||
|
reader.AdvanceAndSetPadding(cpos, padding)
|
||||||
|
|
||||||
|
return ast.NewDefinitionDescription(), parser.HasChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionDescriptionParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
|
||||||
|
// definitionListParser detects end of the description.
|
||||||
|
// so this method will never be called.
|
||||||
|
return parser.Continue | parser.HasChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionDescriptionParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
|
||||||
|
desc := node.(*ast.DefinitionDescription)
|
||||||
|
desc.IsTight = !desc.HasBlankPreviousLines()
|
||||||
|
if desc.IsTight {
|
||||||
|
for gc := desc.FirstChild(); gc != nil; gc = gc.NextSibling() {
|
||||||
|
paragraph, ok := gc.(*gast.Paragraph)
|
||||||
|
if ok {
|
||||||
|
textBlock := gast.NewTextBlock()
|
||||||
|
textBlock.SetLines(paragraph.Lines())
|
||||||
|
desc.ReplaceChild(desc, paragraph, textBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionDescriptionParser) CanInterruptParagraph() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefinitionListHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||||
|
// renders DefinitionList nodes.
|
||||||
|
type DefinitionListHTMLRenderer struct {
|
||||||
|
html.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefinitionListHTMLRenderer returns a new DefinitionListHTMLRenderer.
|
||||||
|
func NewDefinitionListHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
||||||
|
r := &DefinitionListHTMLRenderer{
|
||||||
|
Config: html.NewConfig(),
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.SetHTMLOption(&r.Config)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||||
|
func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
reg.Register(ast.KindDefinitionList, r.renderDefinitionList)
|
||||||
|
reg.Register(ast.KindDefinitionTerm, r.renderDefinitionTerm)
|
||||||
|
reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
|
if entering {
|
||||||
|
w.WriteString("<dl>\n")
|
||||||
|
} else {
|
||||||
|
w.WriteString("</dl>\n")
|
||||||
|
}
|
||||||
|
return gast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
|
if entering {
|
||||||
|
w.WriteString("<dt>")
|
||||||
|
} else {
|
||||||
|
w.WriteString("</dt>\n")
|
||||||
|
}
|
||||||
|
return gast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
|
if entering {
|
||||||
|
n := node.(*ast.DefinitionDescription)
|
||||||
|
if n.IsTight {
|
||||||
|
w.WriteString("<dd>")
|
||||||
|
} else {
|
||||||
|
w.WriteString("<dd>\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.WriteString("</dd>\n")
|
||||||
|
}
|
||||||
|
return gast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type definitionList struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefinitionList is an extension that allow you to use PHP Markdown Extra Definition lists.
|
||||||
|
var DefinitionList = &definitionList{}
|
||||||
|
|
||||||
|
func (e *definitionList) Extend(m goldmark.Markdown) {
|
||||||
|
m.Parser().AddOption(parser.WithBlockParsers(
|
||||||
|
util.Prioritized(NewDefinitionListParser(), 101),
|
||||||
|
util.Prioritized(NewDefinitionDescriptionParser(), 102),
|
||||||
|
))
|
||||||
|
m.Renderer().AddOption(renderer.WithNodeRenderers(
|
||||||
|
util.Prioritized(NewDefinitionListHTMLRenderer(), 500),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
@ -32,16 +32,16 @@ func NewTableParagraphTransformer() parser.ParagraphTransformer {
|
||||||
return defaultTableParagraphTransformer
|
return defaultTableParagraphTransformer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, pc parser.Context) {
|
func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.Reader, pc parser.Context) {
|
||||||
lines := node.Lines()
|
lines := node.Lines()
|
||||||
if lines.Len() < 2 {
|
if lines.Len() < 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
alignments := b.parseDelimiter(lines.At(1), pc)
|
alignments := b.parseDelimiter(lines.At(1), reader)
|
||||||
if alignments == nil {
|
if alignments == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
header := b.parseRow(lines.At(0), alignments, pc)
|
header := b.parseRow(lines.At(0), alignments, reader)
|
||||||
if header == nil || len(alignments) != header.ChildCount() {
|
if header == nil || len(alignments) != header.ChildCount() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, pc parser.Co
|
||||||
table.AppendChild(table, ast.NewTableHeader(header))
|
table.AppendChild(table, ast.NewTableHeader(header))
|
||||||
if lines.Len() > 2 {
|
if lines.Len() > 2 {
|
||||||
for i := 2; i < lines.Len(); i++ {
|
for i := 2; i < lines.Len(); i++ {
|
||||||
table.AppendChild(table, b.parseRow(lines.At(i), alignments, pc))
|
table.AppendChild(table, b.parseRow(lines.At(i), alignments, reader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.Parent().InsertBefore(node.Parent(), node, table)
|
node.Parent().InsertBefore(node.Parent(), node, table)
|
||||||
|
|
@ -58,8 +58,9 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, pc parser.Co
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, pc parser.Context) *ast.TableRow {
|
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, reader text.Reader) *ast.TableRow {
|
||||||
line := segment.Value(pc.Source())
|
source := reader.Source()
|
||||||
|
line := segment.Value(source)
|
||||||
pos := 0
|
pos := 0
|
||||||
pos += util.TrimLeftSpaceLength(line)
|
pos += util.TrimLeftSpaceLength(line)
|
||||||
limit := len(line)
|
limit := len(line)
|
||||||
|
|
@ -78,8 +79,8 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []
|
||||||
}
|
}
|
||||||
node := ast.NewTableCell()
|
node := ast.NewTableCell()
|
||||||
segment := text.NewSegment(segment.Start+pos, segment.Start+pos+closure)
|
segment := text.NewSegment(segment.Start+pos, segment.Start+pos+closure)
|
||||||
segment = segment.TrimLeftSpace(pc.Source())
|
segment = segment.TrimLeftSpace(source)
|
||||||
segment = segment.TrimRightSpace(pc.Source())
|
segment = segment.TrimRightSpace(source)
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
node.Alignment = alignments[i]
|
node.Alignment = alignments[i]
|
||||||
row.AppendChild(row, node)
|
row.AppendChild(row, node)
|
||||||
|
|
@ -88,8 +89,8 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, pc parser.Context) []ast.Alignment {
|
func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader text.Reader) []ast.Alignment {
|
||||||
line := segment.Value(pc.Source())
|
line := segment.Value(reader.Source())
|
||||||
if !tableDelimRegexp.Match(line) {
|
if !tableDelimRegexp.Match(line) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,11 +103,11 @@ func (b *atxHeadingParser) Continue(node ast.Node, reader text.Reader, pc Contex
|
||||||
return Close
|
return Close
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *atxHeadingParser) Close(node ast.Node, pc Context) {
|
func (b *atxHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
if !b.HeadingID {
|
if !b.HeadingID {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
parseOrGenerateHeadingID(node.(*ast.Heading), pc)
|
parseOrGenerateHeadingID(node.(*ast.Heading), reader, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *atxHeadingParser) CanInterruptParagraph() bool {
|
func (b *atxHeadingParser) CanInterruptParagraph() bool {
|
||||||
|
|
@ -122,7 +122,7 @@ var headingIDRegexp = regexp.MustCompile(`^(.*[^\\])({#([^}]+)}\s*)\n?$`)
|
||||||
var headingIDMap = NewContextKey()
|
var headingIDMap = NewContextKey()
|
||||||
var attrNameID = []byte("id")
|
var attrNameID = []byte("id")
|
||||||
|
|
||||||
func parseOrGenerateHeadingID(node *ast.Heading, pc Context) {
|
func parseOrGenerateHeadingID(node *ast.Heading, reader text.Reader, pc Context) {
|
||||||
existsv := pc.Get(headingIDMap)
|
existsv := pc.Get(headingIDMap)
|
||||||
var exists map[string]bool
|
var exists map[string]bool
|
||||||
if existsv == nil {
|
if existsv == nil {
|
||||||
|
|
@ -133,7 +133,7 @@ func parseOrGenerateHeadingID(node *ast.Heading, pc Context) {
|
||||||
}
|
}
|
||||||
lastIndex := node.Lines().Len() - 1
|
lastIndex := node.Lines().Len() - 1
|
||||||
lastLine := node.Lines().At(lastIndex)
|
lastLine := node.Lines().At(lastIndex)
|
||||||
line := lastLine.Value(pc.Source())
|
line := lastLine.Value(reader.Source())
|
||||||
m := headingIDRegexp.FindSubmatchIndex(line)
|
m := headingIDRegexp.FindSubmatchIndex(line)
|
||||||
var headingID []byte
|
var headingID []byte
|
||||||
if m != nil {
|
if m != nil {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ func (b *blockquoteParser) Continue(node ast.Node, reader text.Reader, pc Contex
|
||||||
return Close
|
return Close
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *blockquoteParser) Close(node ast.Node, pc Context) {
|
func (b *blockquoteParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,11 @@ func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *codeBlockParser) Close(node ast.Node, pc Context) {
|
func (b *codeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
// trim trailing blank lines
|
// trim trailing blank lines
|
||||||
lines := node.Lines()
|
lines := node.Lines()
|
||||||
length := lines.Len() - 1
|
length := lines.Len() - 1
|
||||||
source := pc.Source()
|
source := reader.Source()
|
||||||
for {
|
for {
|
||||||
line := lines.At(length)
|
line := lines.At(length)
|
||||||
if util.IsBlank(line.Value(source)) {
|
if util.IsBlank(line.Value(source)) {
|
||||||
|
|
|
||||||
|
|
@ -58,15 +58,15 @@ func (s *codeSpanParser) Parse(parent ast.Node, block text.Reader, pc Context) a
|
||||||
block.AdvanceLine()
|
block.AdvanceLine()
|
||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
if !node.IsBlank(pc.Source()) {
|
if !node.IsBlank(block.Source()) {
|
||||||
// trim first halfspace and last halfspace
|
// trim first halfspace and last halfspace
|
||||||
segment := node.FirstChild().(*ast.Text).Segment
|
segment := node.FirstChild().(*ast.Text).Segment
|
||||||
shouldTrimmed := true
|
shouldTrimmed := true
|
||||||
if !(!segment.IsEmpty() && pc.Source()[segment.Start] == ' ') {
|
if !(!segment.IsEmpty() && block.Source()[segment.Start] == ' ') {
|
||||||
shouldTrimmed = false
|
shouldTrimmed = false
|
||||||
}
|
}
|
||||||
segment = node.LastChild().(*ast.Text).Segment
|
segment = node.LastChild().(*ast.Text).Segment
|
||||||
if !(!segment.IsEmpty() && pc.Source()[segment.Stop-1] == ' ') {
|
if !(!segment.IsEmpty() && block.Source()[segment.Stop-1] == ' ') {
|
||||||
shouldTrimmed = false
|
shouldTrimmed = false
|
||||||
}
|
}
|
||||||
if shouldTrimmed {
|
if shouldTrimmed {
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc C
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *fencedCodeBlockParser) Close(node ast.Node, pc Context) {
|
func (b *fencedCodeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
pc.Set(fencedCodeBlockInfoKey, nil)
|
pc.Set(fencedCodeBlockInfoKey, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ func (b *htmlBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *htmlBlockParser) Close(node ast.Node, pc Context) {
|
func (b *htmlBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -358,7 +358,7 @@ func parseLinkTitle(block text.Reader) ([]byte, bool) {
|
||||||
return line[1 : pos-1], true
|
return line[1 : pos-1], true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linkParser) CloseBlock(parent ast.Node, pc Context) {
|
func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
|
||||||
tlist := pc.Get(linkLabelStateKey)
|
tlist := pc.Get(linkLabelStateKey)
|
||||||
if tlist == nil {
|
if tlist == nil {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ type linkReferenceParagraphTransformer struct {
|
||||||
// that parses and extracts link reference from paragraphs.
|
// that parses and extracts link reference from paragraphs.
|
||||||
var LinkReferenceParagraphTransformer = &linkReferenceParagraphTransformer{}
|
var LinkReferenceParagraphTransformer = &linkReferenceParagraphTransformer{}
|
||||||
|
|
||||||
func (p *linkReferenceParagraphTransformer) Transform(node *ast.Paragraph, pc Context) {
|
func (p *linkReferenceParagraphTransformer) Transform(node *ast.Paragraph, reader text.Reader, pc Context) {
|
||||||
lines := node.Lines()
|
lines := node.Lines()
|
||||||
block := text.NewBlockReader(pc.Source(), lines)
|
block := text.NewBlockReader(reader.Source(), lines)
|
||||||
removes := [][2]int{}
|
removes := [][2]int{}
|
||||||
for {
|
for {
|
||||||
start, end := parseLinkReferenceDefinition(block, pc)
|
start, end := parseLinkReferenceDefinition(block, pc)
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ func (b *listParser) Continue(node ast.Node, reader text.Reader, pc Context) Sta
|
||||||
return Continue | HasChildren
|
return Continue | HasChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *listParser) Close(node ast.Node, pc Context) {
|
func (b *listParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
list := node.(*ast.List)
|
list := node.(*ast.List)
|
||||||
|
|
||||||
for c := node.FirstChild(); c != nil && list.IsTight; c = c.NextSibling() {
|
for c := node.FirstChild(); c != nil && list.IsTight; c = c.NextSibling() {
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func (b *listItemParser) Continue(node ast.Node, reader text.Reader, pc Context)
|
||||||
return Continue | HasChildren
|
return Continue | HasChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *listItemParser) Close(node ast.Node, pc Context) {
|
func (b *listItemParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,13 @@ func (b *paragraphParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *paragraphParser) Close(node ast.Node, pc Context) {
|
func (b *paragraphParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
lines := node.Lines()
|
lines := node.Lines()
|
||||||
if lines.Len() != 0 {
|
if lines.Len() != 0 {
|
||||||
// trim trailing spaces
|
// trim trailing spaces
|
||||||
length := lines.Len()
|
length := lines.Len()
|
||||||
lastLine := node.Lines().At(length - 1)
|
lastLine := node.Lines().At(length - 1)
|
||||||
node.Lines().Set(length-1, lastLine.TrimRightSpace(pc.Source()))
|
node.Lines().Set(length-1, lastLine.TrimRightSpace(reader.Source()))
|
||||||
}
|
}
|
||||||
if lines.Len() == 0 {
|
if lines.Len() == 0 {
|
||||||
node.Parent().RemoveChild(node.Parent(), node)
|
node.Parent().RemoveChild(node.Parent(), node)
|
||||||
|
|
|
||||||
|
|
@ -71,20 +71,17 @@ type Context interface {
|
||||||
// String implements Stringer.
|
// String implements Stringer.
|
||||||
String() string
|
String() string
|
||||||
|
|
||||||
// Source returns a source of Markdown text.
|
// Get returns a value associated with the given key.
|
||||||
Source() []byte
|
|
||||||
|
|
||||||
// Get returns a value associated with given key.
|
|
||||||
Get(ContextKey) interface{}
|
Get(ContextKey) interface{}
|
||||||
|
|
||||||
// Set sets given value to the context.
|
// Set sets the given value to the context.
|
||||||
Set(ContextKey, interface{})
|
Set(ContextKey, interface{})
|
||||||
|
|
||||||
// AddReference adds given reference to this context.
|
// AddReference adds the given reference to this context.
|
||||||
AddReference(Reference)
|
AddReference(Reference)
|
||||||
|
|
||||||
// Reference returns (a reference, true) if a reference associated with
|
// Reference returns (a reference, true) if a reference associated with
|
||||||
// given label exists, otherwise (nil, false).
|
// the given label exists, otherwise (nil, false).
|
||||||
Reference(label string) (Reference, bool)
|
Reference(label string) (Reference, bool)
|
||||||
|
|
||||||
// References returns a list of references.
|
// References returns a list of references.
|
||||||
|
|
@ -104,11 +101,11 @@ type Context interface {
|
||||||
// LastDelimiter returns a last delimiter of the current delimiter list.
|
// LastDelimiter returns a last delimiter of the current delimiter list.
|
||||||
LastDelimiter() *Delimiter
|
LastDelimiter() *Delimiter
|
||||||
|
|
||||||
// PushDelimiter appends given delimiter to the tail of the current
|
// PushDelimiter appends the given delimiter to the tail of the current
|
||||||
// delimiter list.
|
// delimiter list.
|
||||||
PushDelimiter(delimiter *Delimiter)
|
PushDelimiter(delimiter *Delimiter)
|
||||||
|
|
||||||
// RemoveDelimiter removes given delimiter from the current delimiter list.
|
// RemoveDelimiter removes the given delimiter from the current delimiter list.
|
||||||
RemoveDelimiter(d *Delimiter)
|
RemoveDelimiter(d *Delimiter)
|
||||||
|
|
||||||
// ClearDelimiters clears the current delimiter list.
|
// ClearDelimiters clears the current delimiter list.
|
||||||
|
|
@ -126,7 +123,6 @@ type Context interface {
|
||||||
|
|
||||||
type parseContext struct {
|
type parseContext struct {
|
||||||
store []interface{}
|
store []interface{}
|
||||||
source []byte
|
|
||||||
refs map[string]Reference
|
refs map[string]Reference
|
||||||
blockOffset int
|
blockOffset int
|
||||||
delimiters *Delimiter
|
delimiters *Delimiter
|
||||||
|
|
@ -135,10 +131,9 @@ type parseContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext returns a new Context.
|
// NewContext returns a new Context.
|
||||||
func NewContext(source []byte) Context {
|
func NewContext() Context {
|
||||||
return &parseContext{
|
return &parseContext{
|
||||||
store: make([]interface{}, ContextKeyMax+1),
|
store: make([]interface{}, ContextKeyMax+1),
|
||||||
source: source,
|
|
||||||
refs: map[string]Reference{},
|
refs: map[string]Reference{},
|
||||||
blockOffset: 0,
|
blockOffset: 0,
|
||||||
delimiters: nil,
|
delimiters: nil,
|
||||||
|
|
@ -163,10 +158,6 @@ func (p *parseContext) SetBlockOffset(v int) {
|
||||||
p.blockOffset = v
|
p.blockOffset = v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parseContext) Source() []byte {
|
|
||||||
return p.source
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parseContext) LastDelimiter() *Delimiter {
|
func (p *parseContext) LastDelimiter() *Delimiter {
|
||||||
return p.lastDelimiter
|
return p.lastDelimiter
|
||||||
}
|
}
|
||||||
|
|
@ -322,16 +313,16 @@ type OptionName string
|
||||||
|
|
||||||
// A Parser interface parses Markdown text into AST nodes.
|
// A Parser interface parses Markdown text into AST nodes.
|
||||||
type Parser interface {
|
type Parser interface {
|
||||||
// Parse parses given Markdown text into AST nodes.
|
// Parse parses the given Markdown text into AST nodes.
|
||||||
Parse(reader text.Reader, opts ...ParseOption) ast.Node
|
Parse(reader text.Reader, opts ...ParseOption) ast.Node
|
||||||
|
|
||||||
// AddOption adds given option to thie parser.
|
// AddOption adds the given option to thie parser.
|
||||||
AddOption(Option)
|
AddOption(Option)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A SetOptioner interface sets given option to the object.
|
// A SetOptioner interface sets the given option to the object.
|
||||||
type SetOptioner interface {
|
type SetOptioner interface {
|
||||||
// SetOption sets given option to the object.
|
// SetOption sets the given option to the object.
|
||||||
// Unacceptable options may be passed.
|
// Unacceptable options may be passed.
|
||||||
// Thus implementations must ignore unacceptable options.
|
// Thus implementations must ignore unacceptable options.
|
||||||
SetOption(name OptionName, value interface{})
|
SetOption(name OptionName, value interface{})
|
||||||
|
|
@ -364,14 +355,14 @@ type BlockParser interface {
|
||||||
Continue(node ast.Node, reader text.Reader, pc Context) State
|
Continue(node ast.Node, reader text.Reader, pc Context) State
|
||||||
|
|
||||||
// Close will be called when the parser returns Close.
|
// Close will be called when the parser returns Close.
|
||||||
Close(node ast.Node, pc Context)
|
Close(node ast.Node, reader text.Reader, pc Context)
|
||||||
|
|
||||||
// CanInterruptParagraph returns true if the parser can interrupt pargraphs,
|
// CanInterruptParagraph returns true if the parser can interrupt pargraphs,
|
||||||
// otherwise false.
|
// otherwise false.
|
||||||
CanInterruptParagraph() bool
|
CanInterruptParagraph() bool
|
||||||
|
|
||||||
// CanAcceptIndentedLine returns true if the parser can open new node when
|
// CanAcceptIndentedLine returns true if the parser can open new node when
|
||||||
// given line is being indented more than 3 spaces.
|
// the given line is being indented more than 3 spaces.
|
||||||
CanAcceptIndentedLine() bool
|
CanAcceptIndentedLine() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,7 +375,7 @@ type InlineParser interface {
|
||||||
// a head of line
|
// a head of line
|
||||||
Trigger() []byte
|
Trigger() []byte
|
||||||
|
|
||||||
// Parse parse given block into an inline node.
|
// Parse parse the given block into an inline node.
|
||||||
//
|
//
|
||||||
// Parse can parse beyond the current line.
|
// Parse can parse beyond the current line.
|
||||||
// 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
|
||||||
|
|
@ -396,20 +387,20 @@ type InlineParser interface {
|
||||||
// called when block is closed in the inline parsing.
|
// called when block is closed in the inline parsing.
|
||||||
type CloseBlocker interface {
|
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, block text.Reader, pc Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A ParagraphTransformer transforms parsed Paragraph nodes.
|
// A ParagraphTransformer transforms parsed Paragraph nodes.
|
||||||
// For example, link references are searched in parsed Paragraphs.
|
// For example, link references are searched in parsed Paragraphs.
|
||||||
type ParagraphTransformer interface {
|
type ParagraphTransformer interface {
|
||||||
// Transform transforms given paragraph.
|
// Transform transforms the given paragraph.
|
||||||
Transform(node *ast.Paragraph, pc Context)
|
Transform(node *ast.Paragraph, reader text.Reader, pc Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASTTransformer transforms entire Markdown document AST tree.
|
// ASTTransformer transforms entire Markdown document AST tree.
|
||||||
type ASTTransformer interface {
|
type ASTTransformer interface {
|
||||||
// Transform transforms given AST tree.
|
// Transform transforms the given AST tree.
|
||||||
Transform(node *ast.Document, pc Context)
|
Transform(node *ast.Document, reader text.Reader, pc Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultBlockParsers returns a new list of default BlockParsers.
|
// DefaultBlockParsers returns a new list of default BlockParsers.
|
||||||
|
|
@ -683,7 +674,7 @@ func (p *parser) Parse(reader text.Reader, opts ...ParseOption) ast.Node {
|
||||||
opt(c)
|
opt(c)
|
||||||
}
|
}
|
||||||
if c.Context == nil {
|
if c.Context == nil {
|
||||||
c.Context = NewContext(reader.Source())
|
c.Context = NewContext()
|
||||||
}
|
}
|
||||||
pc := c.Context
|
pc := c.Context
|
||||||
root := ast.NewDocument()
|
root := ast.NewDocument()
|
||||||
|
|
@ -693,29 +684,29 @@ func (p *parser) Parse(reader text.Reader, opts ...ParseOption) ast.Node {
|
||||||
p.parseBlock(blockReader, node, pc)
|
p.parseBlock(blockReader, node, pc)
|
||||||
})
|
})
|
||||||
for _, at := range p.astTransformers {
|
for _, at := range p.astTransformers {
|
||||||
at.Transform(root, pc)
|
at.Transform(root, reader, pc)
|
||||||
}
|
}
|
||||||
//root.Dump(reader.Source(), 0)
|
//root.Dump(reader.Source(), 0)
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) transformParagraph(node *ast.Paragraph, pc Context) {
|
func (p *parser) transformParagraph(node *ast.Paragraph, reader text.Reader, pc Context) {
|
||||||
for _, pt := range p.paragraphTransformers {
|
for _, pt := range p.paragraphTransformers {
|
||||||
pt.Transform(node, pc)
|
pt.Transform(node, reader, pc)
|
||||||
if node.Parent() == nil {
|
if node.Parent() == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) closeBlocks(from, to int, pc Context) {
|
func (p *parser) closeBlocks(from, to int, reader text.Reader, pc Context) {
|
||||||
blocks := pc.OpenedBlocks()
|
blocks := pc.OpenedBlocks()
|
||||||
for i := from; i >= to; i-- {
|
for i := from; i >= to; i-- {
|
||||||
node := blocks[i].Node
|
node := blocks[i].Node
|
||||||
blocks[i].Parser.Close(blocks[i].Node, pc)
|
blocks[i].Parser.Close(blocks[i].Node, reader, 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, reader, pc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if from == len(blocks)-1 {
|
if from == len(blocks)-1 {
|
||||||
|
|
@ -774,7 +765,7 @@ retry:
|
||||||
node.SetBlankPreviousLines(blankLine)
|
node.SetBlankPreviousLines(blankLine)
|
||||||
if last != nil && last.Parent() == nil {
|
if last != nil && last.Parent() == nil {
|
||||||
lastPos := len(pc.OpenedBlocks()) - 1
|
lastPos := len(pc.OpenedBlocks()) - 1
|
||||||
p.closeBlocks(lastPos, lastPos, pc)
|
p.closeBlocks(lastPos, lastPos, reader, pc)
|
||||||
}
|
}
|
||||||
parent.AppendChild(parent, node)
|
parent.AppendChild(parent, node)
|
||||||
result = newBlocksOpened
|
result = newBlocksOpened
|
||||||
|
|
@ -806,15 +797,18 @@ func isBlankLine(lineNum, level int, stats []lineStat) ([]lineStat, bool) {
|
||||||
ret := false
|
ret := false
|
||||||
for i := len(stats) - 1 - level; i >= 0; i-- {
|
for i := len(stats) - 1 - level; i >= 0; i-- {
|
||||||
s := stats[i]
|
s := stats[i]
|
||||||
if s.lineNum == lineNum && s.level == level {
|
if s.lineNum == lineNum {
|
||||||
ret = s.isBlank
|
if s.level < level && s.isBlank {
|
||||||
continue
|
return stats[i:], true
|
||||||
|
} else if s.level == level {
|
||||||
|
return stats[i:], s.isBlank
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if s.lineNum < lineNum {
|
if s.lineNum < lineNum {
|
||||||
return stats[i:], ret
|
return stats[i:], ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stats[0:0], ret
|
return stats, ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
|
func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
|
||||||
|
|
@ -826,14 +820,17 @@ func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// first, we try to open blocks
|
|
||||||
if p.openBlocks(parent, lines != 0, reader, pc) != newBlocksOpened {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lineNum, _ := reader.Position()
|
lineNum, _ := reader.Position()
|
||||||
l := len(pc.OpenedBlocks())
|
if lines != 0 {
|
||||||
for i := 0; i < l; i++ {
|
l := len(pc.OpenedBlocks())
|
||||||
blankLines = append(blankLines, lineStat{lineNum - 1, i, lines != 0})
|
for i := 0; i < l; i++ {
|
||||||
|
blankLines = append(blankLines, lineStat{lineNum - 1, i, lines != 0})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
blankLines, isBlank = isBlankLine(lineNum-1, 0, blankLines)
|
||||||
|
// first, we try to open blocks
|
||||||
|
if p.openBlocks(parent, isBlank, reader, pc) != newBlocksOpened {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
reader.AdvanceLine()
|
reader.AdvanceLine()
|
||||||
for { // process opened blocks line by line
|
for { // process opened blocks line by line
|
||||||
|
|
@ -847,7 +844,7 @@ func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
|
||||||
be := openedBlocks[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, reader, pc)
|
||||||
reader.AdvanceLine()
|
reader.AdvanceLine()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -876,7 +873,7 @@ func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
|
||||||
}
|
}
|
||||||
result := p.openBlocks(thisParent, isBlank, reader, pc)
|
result := p.openBlocks(thisParent, isBlank, reader, pc)
|
||||||
if result != paragraphContinuation {
|
if result != paragraphContinuation {
|
||||||
p.closeBlocks(lastIndex, i, pc)
|
p.closeBlocks(lastIndex, i, reader, pc)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -988,7 +985,7 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
|
||||||
|
|
||||||
ProcessDelimiters(nil, pc)
|
ProcessDelimiters(nil, pc)
|
||||||
for _, ip := range p.closeBlockers {
|
for _, ip := range p.closeBlockers {
|
||||||
ip.CloseBlock(parent, pc)
|
ip.CloseBlock(parent, block, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ func (b *setextHeadingParser) Continue(node ast.Node, reader text.Reader, pc Con
|
||||||
return Close
|
return Close
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *setextHeadingParser) Close(node ast.Node, pc Context) {
|
func (b *setextHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
heading := node.(*ast.Heading)
|
heading := node.(*ast.Heading)
|
||||||
segment := node.Lines().At(0)
|
segment := node.Lines().At(0)
|
||||||
heading.Lines().Clear()
|
heading.Lines().Clear()
|
||||||
|
|
@ -79,7 +79,7 @@ func (b *setextHeadingParser) Close(node ast.Node, pc Context) {
|
||||||
pc.Set(temporaryParagraphKey, nil)
|
pc.Set(temporaryParagraphKey, nil)
|
||||||
if tmp.Lines().Len() == 0 {
|
if tmp.Lines().Len() == 0 {
|
||||||
next := heading.NextSibling()
|
next := heading.NextSibling()
|
||||||
segment = segment.TrimLeftSpace(pc.Source())
|
segment = segment.TrimLeftSpace(reader.Source())
|
||||||
if next == nil || !ast.IsParagraph(next) {
|
if next == nil || !ast.IsParagraph(next) {
|
||||||
para := ast.NewParagraph()
|
para := ast.NewParagraph()
|
||||||
para.Lines().Append(segment)
|
para.Lines().Append(segment)
|
||||||
|
|
@ -97,7 +97,7 @@ func (b *setextHeadingParser) Close(node ast.Node, pc Context) {
|
||||||
if !b.HeadingID {
|
if !b.HeadingID {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
parseOrGenerateHeadingID(heading, pc)
|
parseOrGenerateHeadingID(heading, reader, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *setextHeadingParser) CanInterruptParagraph() bool {
|
func (b *setextHeadingParser) CanInterruptParagraph() bool {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ func (b *themanticBreakParser) Continue(node ast.Node, reader text.Reader, pc Co
|
||||||
return Close
|
return Close
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *themanticBreakParser) Close(node ast.Node, pc Context) {
|
func (b *themanticBreakParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func (o *withWriter) SetHTMLOption(c *Config) {
|
||||||
c.Writer = o.value
|
c.Writer = o.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithWriter is a functional option that allow you to set given writer to
|
// WithWriter is a functional option that allow you to set the given writer to
|
||||||
// the renderer.
|
// the renderer.
|
||||||
func WithWriter(writer Writer) interface {
|
func WithWriter(writer Writer) interface {
|
||||||
renderer.Option
|
renderer.Option
|
||||||
|
|
@ -493,11 +493,11 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
|
|
||||||
// A Writer interface wirtes textual contents to a writer.
|
// A Writer interface wirtes textual contents to a writer.
|
||||||
type Writer interface {
|
type Writer interface {
|
||||||
// Write writes given source to writer with resolving references and unescaping
|
// Write writes the given source to writer with resolving references and unescaping
|
||||||
// backslash escaped characters.
|
// backslash escaped characters.
|
||||||
Write(writer util.BufWriter, source []byte)
|
Write(writer util.BufWriter, source []byte)
|
||||||
|
|
||||||
// RawWrite wirtes given source to writer without resolving references and
|
// RawWrite wirtes the given source to writer without resolving references and
|
||||||
// unescaping backslash escaped characters.
|
// unescaping backslash escaped characters.
|
||||||
RawWrite(writer util.BufWriter, source []byte)
|
RawWrite(writer util.BufWriter, source []byte)
|
||||||
}
|
}
|
||||||
|
|
@ -617,7 +617,7 @@ var bVb = []byte("vbscript:")
|
||||||
var bFile = []byte("file:")
|
var bFile = []byte("file:")
|
||||||
var bData = []byte("data:")
|
var bData = []byte("data:")
|
||||||
|
|
||||||
// IsDangerousURL returns true if given url seems a potentially dangerous url,
|
// IsDangerousURL returns true if the given url seems a potentially dangerous url,
|
||||||
// otherwise false.
|
// otherwise false.
|
||||||
func IsDangerousURL(url []byte) bool {
|
func IsDangerousURL(url []byte) bool {
|
||||||
if bytes.HasPrefix(url, bDataImage) && len(url) >= 11 {
|
if bytes.HasPrefix(url, bDataImage) && len(url) >= 11 {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Package renderer renders given AST to certain formats.
|
// Package renderer renders the given AST to certain formats.
|
||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -130,7 +130,7 @@ func (r *renderer) Register(kind ast.NodeKind, v NodeRendererFunc) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders given AST node to given writer with given Renderer.
|
// Render renders the given AST node to the given writer with the 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
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ type Reader interface {
|
||||||
// Source returns a source of the reader.
|
// Source returns a source of the reader.
|
||||||
Source() []byte
|
Source() []byte
|
||||||
|
|
||||||
|
// ResetPosition resets positions.
|
||||||
|
ResetPosition()
|
||||||
|
|
||||||
// Peek returns a byte at current position without advancing the internal pointer.
|
// Peek returns a byte at current position without advancing the internal pointer.
|
||||||
Peek() byte
|
Peek() byte
|
||||||
|
|
||||||
|
|
@ -28,7 +31,7 @@ type Reader interface {
|
||||||
// PrecendingCharacter returns a character just before current internal pointer.
|
// PrecendingCharacter returns a character just before current internal pointer.
|
||||||
PrecendingCharacter() rune
|
PrecendingCharacter() rune
|
||||||
|
|
||||||
// Value returns a value of given segment.
|
// Value returns a value of the given segment.
|
||||||
Value(Segment) []byte
|
Value(Segment) []byte
|
||||||
|
|
||||||
// LineOffset returns a distance from the line head to current position.
|
// LineOffset returns a distance from the line head to current position.
|
||||||
|
|
@ -82,13 +85,17 @@ func NewReader(source []byte) Reader {
|
||||||
r := &reader{
|
r := &reader{
|
||||||
source: source,
|
source: source,
|
||||||
sourceLength: len(source),
|
sourceLength: len(source),
|
||||||
line: -1,
|
|
||||||
head: 0,
|
|
||||||
}
|
}
|
||||||
r.AdvanceLine()
|
r.ResetPosition()
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *reader) ResetPosition() {
|
||||||
|
r.line = -1
|
||||||
|
r.head = 0
|
||||||
|
r.AdvanceLine()
|
||||||
|
}
|
||||||
|
|
||||||
func (r *reader) Source() []byte {
|
func (r *reader) Source() []byte {
|
||||||
return r.source
|
return r.source
|
||||||
}
|
}
|
||||||
|
|
@ -226,6 +233,7 @@ func (r *reader) FindSubMatch(reg *regexp.Regexp) [][]byte {
|
||||||
// A BlockReader interface is a reader that is optimized for Blocks.
|
// A BlockReader interface is a reader that is optimized for Blocks.
|
||||||
type BlockReader interface {
|
type BlockReader interface {
|
||||||
Reader
|
Reader
|
||||||
|
// Reset resets current state and sets new segments to the reader.
|
||||||
Reset(segment *Segments)
|
Reset(segment *Segments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,10 +258,7 @@ func NewBlockReader(source []byte, segments *Segments) BlockReader {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset resets current state and sets new segments to the reader.
|
func (r *blockReader) ResetPosition() {
|
||||||
func (r *blockReader) Reset(segments *Segments) {
|
|
||||||
r.segments = segments
|
|
||||||
r.segmentsLength = segments.Len()
|
|
||||||
r.line = -1
|
r.line = -1
|
||||||
r.head = 0
|
r.head = 0
|
||||||
r.last = 0
|
r.last = 0
|
||||||
|
|
@ -267,6 +272,12 @@ func (r *blockReader) Reset(segments *Segments) {
|
||||||
r.AdvanceLine()
|
r.AdvanceLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *blockReader) Reset(segments *Segments) {
|
||||||
|
r.segments = segments
|
||||||
|
r.segmentsLength = segments.Len()
|
||||||
|
r.ResetPosition()
|
||||||
|
}
|
||||||
|
|
||||||
func (r *blockReader) Source() []byte {
|
func (r *blockReader) Source() []byte {
|
||||||
return r.source
|
return r.source
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ func NewSegment(start, stop int) Segment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSegmentPadding returns a new Segment with given padding.
|
// NewSegmentPadding returns a new Segment with the given padding.
|
||||||
func NewSegmentPadding(start, stop, n int) Segment {
|
func NewSegmentPadding(start, stop, n int) Segment {
|
||||||
return Segment{
|
return Segment{
|
||||||
Start: start,
|
Start: start,
|
||||||
|
|
@ -53,7 +53,7 @@ func (t *Segment) Len() int {
|
||||||
return t.Stop - t.Start + t.Padding
|
return t.Stop - t.Start + t.Padding
|
||||||
}
|
}
|
||||||
|
|
||||||
// Between returns a segment between this segment and given segment.
|
// Between returns a segment between this segment and the given segment.
|
||||||
func (t *Segment) Between(other Segment) Segment {
|
func (t *Segment) Between(other Segment) Segment {
|
||||||
if t.Stop != other.Stop {
|
if t.Stop != other.Stop {
|
||||||
panic("invalid state")
|
panic("invalid state")
|
||||||
|
|
@ -90,7 +90,7 @@ func (t *Segment) TrimLeftSpace(buffer []byte) Segment {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimLeftSpaceWidth returns a new segment by slicing off leading space
|
// TrimLeftSpaceWidth returns a new segment by slicing off leading space
|
||||||
// characters until given width.
|
// characters until the given width.
|
||||||
func (t *Segment) TrimLeftSpaceWidth(width int, buffer []byte) Segment {
|
func (t *Segment) TrimLeftSpaceWidth(width int, buffer []byte) Segment {
|
||||||
padding := t.Padding
|
padding := t.Padding
|
||||||
for ; width > 0; width-- {
|
for ; width > 0; width-- {
|
||||||
|
|
@ -133,7 +133,7 @@ func (t *Segment) WithStop(v int) Segment {
|
||||||
return NewSegmentPadding(t.Start, v, t.Padding)
|
return NewSegmentPadding(t.Start, v, t.Padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConcatPadding concats the padding to given slice.
|
// ConcatPadding concats the padding to the given slice.
|
||||||
func (t *Segment) ConcatPadding(v []byte) []byte {
|
func (t *Segment) ConcatPadding(v []byte) []byte {
|
||||||
if t.Padding > 0 {
|
if t.Padding > 0 {
|
||||||
return append(v, bytes.Repeat(space, t.Padding)...)
|
return append(v, bytes.Repeat(space, t.Padding)...)
|
||||||
|
|
@ -153,7 +153,7 @@ func NewSegments() *Segments {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append appends given segment after the tail of the collection.
|
// Append appends the given segment after the tail of the collection.
|
||||||
func (s *Segments) Append(t Segment) {
|
func (s *Segments) Append(t Segment) {
|
||||||
if s.values == nil {
|
if s.values == nil {
|
||||||
s.values = make([]Segment, 0, 20)
|
s.values = make([]Segment, 0, 20)
|
||||||
|
|
@ -177,12 +177,12 @@ func (s *Segments) Len() int {
|
||||||
return len(s.values)
|
return len(s.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
// At returns a segment at given index.
|
// At returns a segment at the given index.
|
||||||
func (s *Segments) At(i int) Segment {
|
func (s *Segments) At(i int) Segment {
|
||||||
return s.values[i]
|
return s.values[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets given Segment.
|
// Set sets the given Segment.
|
||||||
func (s *Segments) Set(i int, v Segment) {
|
func (s *Segments) Set(i int, v Segment) {
|
||||||
s.values[i] = v
|
s.values[i] = v
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +202,7 @@ func (s *Segments) Clear() {
|
||||||
s.values = nil
|
s.values = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unshift insert given Segment to head of the collection.
|
// Unshift insert the given Segment to head of the collection.
|
||||||
func (s *Segments) Unshift(v Segment) {
|
func (s *Segments) Unshift(v Segment) {
|
||||||
s.values = append(s.values[0:1], s.values[0:]...)
|
s.values = append(s.values[0:1], s.values[0:]...)
|
||||||
s.values[0] = v
|
s.values[0] = v
|
||||||
|
|
|
||||||
44
util/util.go
44
util/util.go
|
|
@ -36,7 +36,7 @@ func (b *CopyOnWriteBuffer) Write(value []byte) {
|
||||||
b.buffer = append(b.buffer, value...)
|
b.buffer = append(b.buffer, value...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteByte writes given byte to the buffer.
|
// WriteByte writes the given byte to the buffer.
|
||||||
func (b *CopyOnWriteBuffer) WriteByte(c byte) {
|
func (b *CopyOnWriteBuffer) WriteByte(c byte) {
|
||||||
if !b.copied {
|
if !b.copied {
|
||||||
b.buffer = make([]byte, 0, len(b.buffer)+20)
|
b.buffer = make([]byte, 0, len(b.buffer)+20)
|
||||||
|
|
@ -55,7 +55,7 @@ func (b *CopyOnWriteBuffer) IsCopied() bool {
|
||||||
return b.copied
|
return b.copied
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadWhile read given source while pred is true.
|
// ReadWhile read the given source while pred is true.
|
||||||
func ReadWhile(source []byte, index [2]int, pred func(byte) bool) (int, bool) {
|
func ReadWhile(source []byte, index [2]int, pred func(byte) bool) (int, bool) {
|
||||||
j := index[0]
|
j := index[0]
|
||||||
ok := false
|
ok := false
|
||||||
|
|
@ -70,7 +70,7 @@ func ReadWhile(source []byte, index [2]int, pred func(byte) bool) (int, bool) {
|
||||||
return j, ok
|
return j, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsBlank returns true if given string is all space characters.
|
// IsBlank returns true if the given string is all space characters.
|
||||||
func IsBlank(bs []byte) bool {
|
func IsBlank(bs []byte) bool {
|
||||||
for _, b := range bs {
|
for _, b := range bs {
|
||||||
if IsSpace(b) {
|
if IsSpace(b) {
|
||||||
|
|
@ -81,7 +81,7 @@ func IsBlank(bs []byte) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// DedentPosition dedents lines by given width.
|
// DedentPosition dedents lines by the given width.
|
||||||
func DedentPosition(bs []byte, width int) (pos, padding int) {
|
func DedentPosition(bs []byte, width int) (pos, padding int) {
|
||||||
if width == 0 {
|
if width == 0 {
|
||||||
return
|
return
|
||||||
|
|
@ -114,12 +114,12 @@ func VisualizeSpaces(bs []byte) []byte {
|
||||||
return bs
|
return bs
|
||||||
}
|
}
|
||||||
|
|
||||||
// TabWidth calculates actual width of a tab at given position.
|
// TabWidth calculates actual width of a tab at the given position.
|
||||||
func TabWidth(currentPos int) int {
|
func TabWidth(currentPos int) int {
|
||||||
return 4 - currentPos%4
|
return 4 - currentPos%4
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndentPosition searches an indent position with given width for given line.
|
// IndentPosition searches an indent position with the given width for the given line.
|
||||||
// If the line contains tab characters, paddings may be not zero.
|
// If the line contains tab characters, paddings may be not zero.
|
||||||
// currentPos==0 and width==2:
|
// currentPos==0 and width==2:
|
||||||
//
|
//
|
||||||
|
|
@ -148,7 +148,7 @@ func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndentWidth calculate an indent width for given line.
|
// IndentWidth calculate an indent width for the given line.
|
||||||
func IndentWidth(bs []byte, currentPos int) (width, pos int) {
|
func IndentWidth(bs []byte, currentPos int) (width, pos int) {
|
||||||
l := len(bs)
|
l := len(bs)
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
|
|
@ -183,7 +183,7 @@ func FirstNonSpacePosition(bs []byte) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindClosure returns a position that closes given opener.
|
// FindClosure returns a position that closes the given opener.
|
||||||
// If codeSpan is set true, it ignores characters in code spans.
|
// If codeSpan is set true, it ignores characters in code spans.
|
||||||
// If allowNesting is set true, closures correspond to nested opener will be
|
// If allowNesting is set true, closures correspond to nested opener will be
|
||||||
// ignored.
|
// ignored.
|
||||||
|
|
@ -234,7 +234,7 @@ func FindClosure(bs []byte, opener, closure byte, codeSpan, allowNesting bool) i
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimLeft trims characters in given s from head of the source.
|
// TrimLeft trims characters in the given s from head of the source.
|
||||||
// bytes.TrimLeft offers same functionalities, but bytes.TrimLeft
|
// bytes.TrimLeft offers same functionalities, but bytes.TrimLeft
|
||||||
// allocates new buffer for the result.
|
// allocates new buffer for the result.
|
||||||
func TrimLeft(source, b []byte) []byte {
|
func TrimLeft(source, b []byte) []byte {
|
||||||
|
|
@ -255,7 +255,7 @@ func TrimLeft(source, b []byte) []byte {
|
||||||
return source[i:]
|
return source[i:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimRight trims characters in given s from tail of the source.
|
// TrimRight trims characters in the given s from tail of the source.
|
||||||
func TrimRight(source, b []byte) []byte {
|
func TrimRight(source, b []byte) []byte {
|
||||||
i := len(source) - 1
|
i := len(source) - 1
|
||||||
for ; i >= 0; i-- {
|
for ; i >= 0; i-- {
|
||||||
|
|
@ -294,19 +294,19 @@ func TrimRightSpaceLength(source []byte) int {
|
||||||
return TrimRightLength(source, spaces)
|
return TrimRightLength(source, spaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimLeftSpace returns a subslice of given string by slicing off all leading
|
// TrimLeftSpace returns a subslice of the given string by slicing off all leading
|
||||||
// space characters.
|
// space characters.
|
||||||
func TrimLeftSpace(source []byte) []byte {
|
func TrimLeftSpace(source []byte) []byte {
|
||||||
return TrimLeft(source, spaces)
|
return TrimLeft(source, spaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimRightSpace returns a subslice of given string by slicing off all trailing
|
// TrimRightSpace returns a subslice of the given string by slicing off all trailing
|
||||||
// space characters.
|
// space characters.
|
||||||
func TrimRightSpace(source []byte) []byte {
|
func TrimRightSpace(source []byte) []byte {
|
||||||
return TrimRight(source, spaces)
|
return TrimRight(source, spaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplaceSpaces replaces sequence of spaces with given repl.
|
// ReplaceSpaces replaces sequence of spaces with the given repl.
|
||||||
func ReplaceSpaces(source []byte, repl byte) []byte {
|
func ReplaceSpaces(source []byte, repl byte) []byte {
|
||||||
var ret []byte
|
var ret []byte
|
||||||
start := -1
|
start := -1
|
||||||
|
|
@ -350,7 +350,7 @@ func ToRune(source []byte, pos int) rune {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToValidRune returns 0xFFFD if given rune is invalid, otherwise v.
|
// ToValidRune returns 0xFFFD if the given rune is invalid, otherwise v.
|
||||||
func ToValidRune(v rune) rune {
|
func ToValidRune(v rune) rune {
|
||||||
if v == 0 || !utf8.ValidRune(v) {
|
if v == 0 || !utf8.ValidRune(v) {
|
||||||
return rune(0xFFFD)
|
return rune(0xFFFD)
|
||||||
|
|
@ -369,7 +369,7 @@ func ToLinkReference(v []byte) string {
|
||||||
|
|
||||||
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 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}
|
||||||
|
|
||||||
// EscapeHTMLByte returns HTML escaped bytes if given byte should be escaped,
|
// EscapeHTMLByte returns HTML escaped bytes if the given byte should be escaped,
|
||||||
// otherwise nil.
|
// otherwise nil.
|
||||||
func EscapeHTMLByte(b byte) []byte {
|
func EscapeHTMLByte(b byte) []byte {
|
||||||
return htmlEscapeTable[b]
|
return htmlEscapeTable[b]
|
||||||
|
|
@ -500,7 +500,7 @@ func ResolveEntityNames(source []byte) []byte {
|
||||||
|
|
||||||
var htmlSpace = []byte("%20")
|
var htmlSpace = []byte("%20")
|
||||||
|
|
||||||
// URLEscape escape given URL.
|
// URLEscape escape the given URL.
|
||||||
// If resolveReference is set true:
|
// If resolveReference is set true:
|
||||||
// 1. unescape punctuations
|
// 1. unescape punctuations
|
||||||
// 2. resolve numeric references
|
// 2. resolve numeric references
|
||||||
|
|
@ -723,27 +723,27 @@ var urlEscapeTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
|
||||||
var utf8lenTable = [256]int8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 99, 99, 99, 99, 99, 99, 99, 99}
|
var utf8lenTable = [256]int8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 99, 99, 99, 99, 99, 99, 99, 99}
|
||||||
|
|
||||||
// IsPunct returns true if given character is a punctuation, otherwise false.
|
// IsPunct returns true if the given character is a punctuation, otherwise false.
|
||||||
func IsPunct(c byte) bool {
|
func IsPunct(c byte) bool {
|
||||||
return punctTable[c] == 1
|
return punctTable[c] == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSpace returns true if given character is a space, otherwise false.
|
// IsSpace returns true if the given character is a space, otherwise false.
|
||||||
func IsSpace(c byte) bool {
|
func IsSpace(c byte) bool {
|
||||||
return spaceTable[c] == 1
|
return spaceTable[c] == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNumeric returns true if given character is a numeric, otherwise false.
|
// IsNumeric returns true if the given character is a numeric, otherwise false.
|
||||||
func IsNumeric(c byte) bool {
|
func IsNumeric(c byte) bool {
|
||||||
return c >= '0' && c <= '9'
|
return c >= '0' && c <= '9'
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsHexDecimal returns true if given character is a hexdecimal, otherwise false.
|
// IsHexDecimal returns true if the given character is a hexdecimal, otherwise false.
|
||||||
func IsHexDecimal(c byte) bool {
|
func IsHexDecimal(c byte) bool {
|
||||||
return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'
|
return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAlphaNumeric returns true if given character is a alphabet or a numeric, otherwise false.
|
// IsAlphaNumeric returns true if the given character is a alphabet or a numeric, otherwise false.
|
||||||
func IsAlphaNumeric(c byte) bool {
|
func IsAlphaNumeric(c byte) bool {
|
||||||
return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
|
return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
|
||||||
}
|
}
|
||||||
|
|
@ -777,7 +777,7 @@ func (s PrioritizedSlice) Sort() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes given value from this slice.
|
// Remove removes the given value from this slice.
|
||||||
func (s PrioritizedSlice) Remove(v interface{}) PrioritizedSlice {
|
func (s PrioritizedSlice) Remove(v interface{}) PrioritizedSlice {
|
||||||
i := 0
|
i := 0
|
||||||
found := false
|
found := false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue