Backport #15059 Signed-off-by: Andrew Thornton <art27@cantab.net>pull/15084/head
parent
a461d90415
commit
7a85e228d8
2
go.mod
2
go.mod
|
@ -99,7 +99,7 @@ require (
|
||||||
github.com/urfave/cli v1.20.0
|
github.com/urfave/cli v1.20.0
|
||||||
github.com/xanzy/go-gitlab v0.37.0
|
github.com/xanzy/go-gitlab v0.37.0
|
||||||
github.com/yohcop/openid-go v1.0.0
|
github.com/yohcop/openid-go v1.0.0
|
||||||
github.com/yuin/goldmark v1.2.1
|
github.com/yuin/goldmark v1.3.3
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
||||||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60
|
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60
|
||||||
go.jolheiser.com/hcaptcha v0.0.4
|
go.jolheiser.com/hcaptcha v0.0.4
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -887,6 +887,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.3.3 h1:37BdQwPx8VOSic8eDSWee6QL9mRpZRm9VJp/QugNrW0=
|
||||||
|
github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
|
||||||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo=
|
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo=
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
goldmark
|
goldmark
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
[![http://godoc.org/github.com/yuin/goldmark](https://godoc.org/github.com/yuin/goldmark?status.svg)](http://godoc.org/github.com/yuin/goldmark)
|
[![https://pkg.go.dev/github.com/yuin/goldmark](https://pkg.go.dev/badge/github.com/yuin/goldmark.svg)](https://pkg.go.dev/github.com/yuin/goldmark)
|
||||||
[![https://github.com/yuin/goldmark/actions?query=workflow:test](https://github.com/yuin/goldmark/workflows/test/badge.svg?branch=master&event=push)](https://github.com/yuin/goldmark/actions?query=workflow:test)
|
[![https://github.com/yuin/goldmark/actions?query=workflow:test](https://github.com/yuin/goldmark/workflows/test/badge.svg?branch=master&event=push)](https://github.com/yuin/goldmark/actions?query=workflow:test)
|
||||||
[![https://coveralls.io/github/yuin/goldmark](https://coveralls.io/repos/github/yuin/goldmark/badge.svg?branch=master)](https://coveralls.io/github/yuin/goldmark)
|
[![https://coveralls.io/github/yuin/goldmark](https://coveralls.io/repos/github/yuin/goldmark/badge.svg?branch=master)](https://coveralls.io/github/yuin/goldmark)
|
||||||
[![https://goreportcard.com/report/github.com/yuin/goldmark](https://goreportcard.com/badge/github.com/yuin/goldmark)](https://goreportcard.com/report/github.com/yuin/goldmark)
|
[![https://goreportcard.com/report/github.com/yuin/goldmark](https://goreportcard.com/badge/github.com/yuin/goldmark)](https://goreportcard.com/report/github.com/yuin/goldmark)
|
||||||
|
@ -173,6 +173,7 @@ Parser and Renderer options
|
||||||
- This extension enables Table, Strikethrough, Linkify and TaskList.
|
- This extension enables Table, Strikethrough, Linkify and TaskList.
|
||||||
- This extension does not filter tags defined in [6.11: Disallowed Raw HTML (extension)](https://github.github.com/gfm/#disallowed-raw-html-extension-).
|
- This extension does not filter tags defined in [6.11: Disallowed Raw HTML (extension)](https://github.github.com/gfm/#disallowed-raw-html-extension-).
|
||||||
If you need to filter HTML tags, see [Security](#security).
|
If you need to filter HTML tags, see [Security](#security).
|
||||||
|
- If you need to parse github emojis, you can use [goldmark-emoji](https://github.com/yuin/goldmark-emoji) extension.
|
||||||
- `extension.DefinitionList`
|
- `extension.DefinitionList`
|
||||||
- [PHP Markdown Extra: Definition lists](https://michelf.ca/projects/php-markdown/extra/#def-list)
|
- [PHP Markdown Extra: Definition lists](https://michelf.ca/projects/php-markdown/extra/#def-list)
|
||||||
- `extension.Footnote`
|
- `extension.Footnote`
|
||||||
|
@ -279,13 +280,96 @@ markdown := goldmark.New(
|
||||||
[]byte("https:"),
|
[]byte("https:"),
|
||||||
}),
|
}),
|
||||||
extension.WithLinkifyURLRegexp(
|
extension.WithLinkifyURLRegexp(
|
||||||
xurls.Strict(),
|
xurls.Strict,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Footnotes extension
|
||||||
|
|
||||||
|
The Footnote extension implements [PHP Markdown Extra: Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes).
|
||||||
|
|
||||||
|
This extension has some options:
|
||||||
|
|
||||||
|
| Functional option | Type | Description |
|
||||||
|
| ----------------- | ---- | ----------- |
|
||||||
|
| `extension.WithFootnoteIDPrefix` | `[]byte` | a prefix for the id attributes.|
|
||||||
|
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
|
||||||
|
| `extension.WithFootnoteLinkTitle` | `[]byte` | an optional title attribute for footnote links.|
|
||||||
|
| `extension.WithFootnoteBacklinkTitle` | `[]byte` | an optional title attribute for footnote backlinks. |
|
||||||
|
| `extension.WithFootnoteLinkClass` | `[]byte` | a class for footnote links. This defaults to `footnote-ref`. |
|
||||||
|
| `extension.WithFootnoteBacklinkClass` | `[]byte` | a class for footnote backlinks. This defaults to `footnote-backref`. |
|
||||||
|
| `extension.WithFootnoteBacklinkHTML` | `[]byte` | a class for footnote backlinks. This defaults to `↩︎`. |
|
||||||
|
|
||||||
|
Some options can have special substitutions. Occurances of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurances of “%%” will be replaced by a number for the reference (footnotes can have multiple references).
|
||||||
|
|
||||||
|
`extension.WithFootnoteIDPrefix` and `extension.WithFootnoteIDPrefixFunction` are useful if you have multiple Markdown documents displayed inside one HTML document to avoid footnote ids to clash each other.
|
||||||
|
|
||||||
|
`extension.WithFootnoteIDPrefix` sets fixed id prefix, so you may write codes like the following:
|
||||||
|
|
||||||
|
```go
|
||||||
|
for _, path := range files {
|
||||||
|
source := readAll(path)
|
||||||
|
prefix := getPrefix(path)
|
||||||
|
|
||||||
|
markdown := goldmark.New(
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
NewFootnote(
|
||||||
|
WithFootnoteIDPrefix([]byte(path)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := markdown.Convert(source, &b)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`extension.WithFootnoteIDPrefixFunction` determines an id prefix by calling given function, so you may write codes like the following:
|
||||||
|
|
||||||
|
```go
|
||||||
|
markdown := goldmark.New(
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
NewFootnote(
|
||||||
|
WithFootnoteIDPrefixFunction(func(n gast.Node) []byte {
|
||||||
|
v, ok := n.OwnerDocument().Meta()["footnote-prefix"]
|
||||||
|
if ok {
|
||||||
|
return util.StringToReadOnlyBytes(v.(string))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, path := range files {
|
||||||
|
source := readAll(path)
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
doc := markdown.Parser().Parse(text.NewReader(source))
|
||||||
|
doc.Meta()["footnote-prefix"] = getPrefix(path)
|
||||||
|
err := markdown.Renderer().Render(&b, source, doc)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use [goldmark-meta](https://github.com/yuin/goldmark-meta) to define a id prefix in the markdown document:
|
||||||
|
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: document title
|
||||||
|
slug: article1
|
||||||
|
footnote-prefix: article1
|
||||||
|
---
|
||||||
|
|
||||||
|
# My article
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
Security
|
Security
|
||||||
--------------------
|
--------------------
|
||||||
By default, goldmark does not render raw HTML or potentially-dangerous URLs.
|
By default, goldmark does not render raw HTML or potentially-dangerous URLs.
|
||||||
|
@ -336,6 +420,8 @@ Extensions
|
||||||
extension for the goldmark Markdown parser.
|
extension for the goldmark Markdown parser.
|
||||||
- [goldmark-highlighting](https://github.com/yuin/goldmark-highlighting): A syntax-highlighting extension
|
- [goldmark-highlighting](https://github.com/yuin/goldmark-highlighting): A syntax-highlighting extension
|
||||||
for the goldmark markdown parser.
|
for the goldmark markdown parser.
|
||||||
|
- [goldmark-emoji](https://github.com/yuin/goldmark-emoji): An emoji
|
||||||
|
extension for the goldmark Markdown parser.
|
||||||
- [goldmark-mathjax](https://github.com/litao91/goldmark-mathjax): Mathjax support for the goldmark markdown parser
|
- [goldmark-mathjax](https://github.com/litao91/goldmark-mathjax): Mathjax support for the goldmark markdown parser
|
||||||
|
|
||||||
goldmark internal(for extension developers)
|
goldmark internal(for extension developers)
|
||||||
|
|
|
@ -45,11 +45,6 @@ type Attribute struct {
|
||||||
Value interface{}
|
Value interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var attrNameIDS = []byte("#")
|
|
||||||
var attrNameID = []byte("id")
|
|
||||||
var attrNameClassS = []byte(".")
|
|
||||||
var attrNameClass = []byte("class")
|
|
||||||
|
|
||||||
// A Node interface defines basic AST node functionalities.
|
// A Node interface defines basic AST node functionalities.
|
||||||
type Node interface {
|
type Node interface {
|
||||||
// Type returns a type of this node.
|
// Type returns a type of this node.
|
||||||
|
@ -116,6 +111,11 @@ type Node interface {
|
||||||
// tail of the children.
|
// tail of the children.
|
||||||
InsertAfter(self, v1, insertee Node)
|
InsertAfter(self, v1, insertee Node)
|
||||||
|
|
||||||
|
// OwnerDocument returns this node's owner document.
|
||||||
|
// If this node is not a child of the Document node, OwnerDocument
|
||||||
|
// returns nil.
|
||||||
|
OwnerDocument() *Document
|
||||||
|
|
||||||
// Dump dumps an AST tree structure to stdout.
|
// Dump dumps an AST tree structure to stdout.
|
||||||
// This function completely aimed for debugging.
|
// This function completely aimed for debugging.
|
||||||
// level is a indent level. Implementer should indent informations with
|
// level is a indent level. Implementer should indent informations with
|
||||||
|
@ -169,7 +169,7 @@ type Node interface {
|
||||||
RemoveAttributes()
|
RemoveAttributes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// A BaseNode struct implements the Node interface.
|
// A BaseNode struct implements the Node interface partialliy.
|
||||||
type BaseNode struct {
|
type BaseNode struct {
|
||||||
firstChild Node
|
firstChild Node
|
||||||
lastChild Node
|
lastChild Node
|
||||||
|
@ -358,6 +358,22 @@ func (n *BaseNode) InsertBefore(self, v1, insertee Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OwnerDocument implements Node.OwnerDocument
|
||||||
|
func (n *BaseNode) OwnerDocument() *Document {
|
||||||
|
d := n.Parent()
|
||||||
|
for {
|
||||||
|
p := d.Parent()
|
||||||
|
if p == nil {
|
||||||
|
if v, ok := d.(*Document); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d = p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Text implements Node.Text .
|
// Text implements Node.Text .
|
||||||
func (n *BaseNode) Text(source []byte) []byte {
|
func (n *BaseNode) Text(source []byte) []byte {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
textm "github.com/yuin/goldmark/text"
|
textm "github.com/yuin/goldmark/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A BaseBlock struct implements the Node interface.
|
// A BaseBlock struct implements the Node interface partialliy.
|
||||||
type BaseBlock struct {
|
type BaseBlock struct {
|
||||||
BaseNode
|
BaseNode
|
||||||
blankPreviousLines bool
|
blankPreviousLines bool
|
||||||
|
@ -50,6 +50,8 @@ func (b *BaseBlock) SetLines(v *textm.Segments) {
|
||||||
// A Document struct is a root node of Markdown text.
|
// A Document struct is a root node of Markdown text.
|
||||||
type Document struct {
|
type Document struct {
|
||||||
BaseBlock
|
BaseBlock
|
||||||
|
|
||||||
|
meta map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// KindDocument is a NodeKind of the Document node.
|
// KindDocument is a NodeKind of the Document node.
|
||||||
|
@ -70,10 +72,29 @@ func (n *Document) Kind() NodeKind {
|
||||||
return KindDocument
|
return KindDocument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OwnerDocument implements Node.OwnerDocument
|
||||||
|
func (n *Document) OwnerDocument() *Document {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta returns metadata of this document.
|
||||||
|
func (n *Document) Meta() map[string]interface{} {
|
||||||
|
if n.meta == nil {
|
||||||
|
n.meta = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
return n.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMeta sets given metadata to this document.
|
||||||
|
func (n *Document) SetMeta(meta map[string]interface{}) {
|
||||||
|
n.meta = meta
|
||||||
|
}
|
||||||
|
|
||||||
// NewDocument returns a new Document node.
|
// NewDocument returns a new Document node.
|
||||||
func NewDocument() *Document {
|
func NewDocument() *Document {
|
||||||
return &Document{
|
return &Document{
|
||||||
BaseBlock: BaseBlock{},
|
BaseBlock: BaseBlock{},
|
||||||
|
meta: nil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A BaseInline struct implements the Node interface.
|
// A BaseInline struct implements the Node interface partialliy.
|
||||||
type BaseInline struct {
|
type BaseInline struct {
|
||||||
BaseNode
|
BaseNode
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
gast "github.com/yuin/goldmark/ast"
|
gast "github.com/yuin/goldmark/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,13 +10,15 @@ import (
|
||||||
// (PHP Markdown Extra) text.
|
// (PHP Markdown Extra) text.
|
||||||
type FootnoteLink struct {
|
type FootnoteLink struct {
|
||||||
gast.BaseInline
|
gast.BaseInline
|
||||||
Index int
|
Index int
|
||||||
|
RefCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump.
|
// Dump implements Node.Dump.
|
||||||
func (n *FootnoteLink) Dump(source []byte, level int) {
|
func (n *FootnoteLink) Dump(source []byte, level int) {
|
||||||
m := map[string]string{}
|
m := map[string]string{}
|
||||||
m["Index"] = fmt.Sprintf("%v", n.Index)
|
m["Index"] = fmt.Sprintf("%v", n.Index)
|
||||||
|
m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
|
||||||
gast.DumpHelper(n, source, level, m, nil)
|
gast.DumpHelper(n, source, level, m, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,36 +33,40 @@ func (n *FootnoteLink) Kind() gast.NodeKind {
|
||||||
// NewFootnoteLink returns a new FootnoteLink node.
|
// NewFootnoteLink returns a new FootnoteLink node.
|
||||||
func NewFootnoteLink(index int) *FootnoteLink {
|
func NewFootnoteLink(index int) *FootnoteLink {
|
||||||
return &FootnoteLink{
|
return &FootnoteLink{
|
||||||
Index: index,
|
Index: index,
|
||||||
|
RefCount: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A FootnoteBackLink struct represents a link to a footnote of Markdown
|
// A FootnoteBacklink struct represents a link to a footnote of Markdown
|
||||||
// (PHP Markdown Extra) text.
|
// (PHP Markdown Extra) text.
|
||||||
type FootnoteBackLink struct {
|
type FootnoteBacklink struct {
|
||||||
gast.BaseInline
|
gast.BaseInline
|
||||||
Index int
|
Index int
|
||||||
|
RefCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump.
|
// Dump implements Node.Dump.
|
||||||
func (n *FootnoteBackLink) Dump(source []byte, level int) {
|
func (n *FootnoteBacklink) Dump(source []byte, level int) {
|
||||||
m := map[string]string{}
|
m := map[string]string{}
|
||||||
m["Index"] = fmt.Sprintf("%v", n.Index)
|
m["Index"] = fmt.Sprintf("%v", n.Index)
|
||||||
|
m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
|
||||||
gast.DumpHelper(n, source, level, m, nil)
|
gast.DumpHelper(n, source, level, m, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KindFootnoteBackLink is a NodeKind of the FootnoteBackLink node.
|
// KindFootnoteBacklink is a NodeKind of the FootnoteBacklink node.
|
||||||
var KindFootnoteBackLink = gast.NewNodeKind("FootnoteBackLink")
|
var KindFootnoteBacklink = gast.NewNodeKind("FootnoteBacklink")
|
||||||
|
|
||||||
// Kind implements Node.Kind.
|
// Kind implements Node.Kind.
|
||||||
func (n *FootnoteBackLink) Kind() gast.NodeKind {
|
func (n *FootnoteBacklink) Kind() gast.NodeKind {
|
||||||
return KindFootnoteBackLink
|
return KindFootnoteBacklink
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFootnoteBackLink returns a new FootnoteBackLink node.
|
// NewFootnoteBacklink returns a new FootnoteBacklink node.
|
||||||
func NewFootnoteBackLink(index int) *FootnoteBackLink {
|
func NewFootnoteBacklink(index int) *FootnoteBacklink {
|
||||||
return &FootnoteBackLink{
|
return &FootnoteBacklink{
|
||||||
Index: index,
|
Index: index,
|
||||||
|
RefCount: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ package extension
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
gast "github.com/yuin/goldmark/ast"
|
gast "github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/extension/ast"
|
"github.com/yuin/goldmark/extension/ast"
|
||||||
|
@ -10,10 +12,10 @@ import (
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var footnoteListKey = parser.NewContextKey()
|
var footnoteListKey = parser.NewContextKey()
|
||||||
|
var footnoteLinkListKey = parser.NewContextKey()
|
||||||
|
|
||||||
type footnoteBlockParser struct {
|
type footnoteBlockParser struct {
|
||||||
}
|
}
|
||||||
|
@ -164,7 +166,20 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ast.NewFootnoteLink(index)
|
fnlink := ast.NewFootnoteLink(index)
|
||||||
|
var fnlist []*ast.FootnoteLink
|
||||||
|
if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
|
||||||
|
fnlist = tmp.([]*ast.FootnoteLink)
|
||||||
|
} else {
|
||||||
|
fnlist = []*ast.FootnoteLink{}
|
||||||
|
pc.Set(footnoteLinkListKey, fnlist)
|
||||||
|
}
|
||||||
|
pc.Set(footnoteLinkListKey, append(fnlist, fnlink))
|
||||||
|
if line[0] == '!' {
|
||||||
|
parent.AppendChild(parent, gast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fnlink
|
||||||
}
|
}
|
||||||
|
|
||||||
type footnoteASTTransformer struct {
|
type footnoteASTTransformer struct {
|
||||||
|
@ -180,23 +195,46 @@ func NewFootnoteASTTransformer() parser.ASTTransformer {
|
||||||
|
|
||||||
func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
|
func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
|
||||||
var list *ast.FootnoteList
|
var list *ast.FootnoteList
|
||||||
if tlist := pc.Get(footnoteListKey); tlist != nil {
|
var fnlist []*ast.FootnoteLink
|
||||||
list = tlist.(*ast.FootnoteList)
|
if tmp := pc.Get(footnoteListKey); tmp != nil {
|
||||||
} else {
|
list = tmp.(*ast.FootnoteList)
|
||||||
|
}
|
||||||
|
if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
|
||||||
|
fnlist = tmp.([]*ast.FootnoteLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.Set(footnoteListKey, nil)
|
||||||
|
pc.Set(footnoteLinkListKey, nil)
|
||||||
|
|
||||||
|
if list == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pc.Set(footnoteListKey, nil)
|
|
||||||
|
counter := map[int]int{}
|
||||||
|
if fnlist != nil {
|
||||||
|
for _, fnlink := range fnlist {
|
||||||
|
if fnlink.Index >= 0 {
|
||||||
|
counter[fnlink.Index]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, fnlink := range fnlist {
|
||||||
|
fnlink.RefCount = counter[fnlink.Index]
|
||||||
|
}
|
||||||
|
}
|
||||||
for footnote := list.FirstChild(); footnote != nil; {
|
for footnote := list.FirstChild(); footnote != nil; {
|
||||||
var container gast.Node = footnote
|
var container gast.Node = footnote
|
||||||
next := footnote.NextSibling()
|
next := footnote.NextSibling()
|
||||||
if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
|
if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
|
||||||
container = fc
|
container = fc
|
||||||
}
|
}
|
||||||
index := footnote.(*ast.Footnote).Index
|
fn := footnote.(*ast.Footnote)
|
||||||
|
index := fn.Index
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
list.RemoveChild(list, footnote)
|
list.RemoveChild(list, footnote)
|
||||||
} else {
|
} else {
|
||||||
container.AppendChild(container, ast.NewFootnoteBackLink(index))
|
backLink := ast.NewFootnoteBacklink(index)
|
||||||
|
backLink.RefCount = counter[index]
|
||||||
|
container.AppendChild(container, backLink)
|
||||||
}
|
}
|
||||||
footnote = next
|
footnote = next
|
||||||
}
|
}
|
||||||
|
@ -214,19 +252,250 @@ func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Read
|
||||||
node.AppendChild(node, list)
|
node.AppendChild(node, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FootnoteConfig holds configuration values for the footnote extension.
|
||||||
|
//
|
||||||
|
// Link* and Backlink* configurations have some variables:
|
||||||
|
// Occurrances of “^^” in the string will be replaced by the
|
||||||
|
// corresponding footnote number in the HTML output.
|
||||||
|
// Occurrances of “%%” will be replaced by a number for the
|
||||||
|
// reference (footnotes can have multiple references).
|
||||||
|
type FootnoteConfig struct {
|
||||||
|
html.Config
|
||||||
|
|
||||||
|
// IDPrefix is a prefix for the id attributes generated by footnotes.
|
||||||
|
IDPrefix []byte
|
||||||
|
|
||||||
|
// IDPrefix is a function that determines the id attribute for given Node.
|
||||||
|
IDPrefixFunction func(gast.Node) []byte
|
||||||
|
|
||||||
|
// LinkTitle is an optional title attribute for footnote links.
|
||||||
|
LinkTitle []byte
|
||||||
|
|
||||||
|
// BacklinkTitle is an optional title attribute for footnote backlinks.
|
||||||
|
BacklinkTitle []byte
|
||||||
|
|
||||||
|
// LinkClass is a class for footnote links.
|
||||||
|
LinkClass []byte
|
||||||
|
|
||||||
|
// BacklinkClass is a class for footnote backlinks.
|
||||||
|
BacklinkClass []byte
|
||||||
|
|
||||||
|
// BacklinkHTML is an HTML content for footnote backlinks.
|
||||||
|
BacklinkHTML []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// FootnoteOption interface is a functional option interface for the extension.
|
||||||
|
type FootnoteOption interface {
|
||||||
|
renderer.Option
|
||||||
|
// SetFootnoteOption sets given option to the extension.
|
||||||
|
SetFootnoteOption(*FootnoteConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFootnoteConfig returns a new Config with defaults.
|
||||||
|
func NewFootnoteConfig() FootnoteConfig {
|
||||||
|
return FootnoteConfig{
|
||||||
|
Config: html.NewConfig(),
|
||||||
|
LinkTitle: []byte(""),
|
||||||
|
BacklinkTitle: []byte(""),
|
||||||
|
LinkClass: []byte("footnote-ref"),
|
||||||
|
BacklinkClass: []byte("footnote-backref"),
|
||||||
|
BacklinkHTML: []byte("↩︎"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOption implements renderer.SetOptioner.
|
||||||
|
func (c *FootnoteConfig) SetOption(name renderer.OptionName, value interface{}) {
|
||||||
|
switch name {
|
||||||
|
case optFootnoteIDPrefixFunction:
|
||||||
|
c.IDPrefixFunction = value.(func(gast.Node) []byte)
|
||||||
|
case optFootnoteIDPrefix:
|
||||||
|
c.IDPrefix = value.([]byte)
|
||||||
|
case optFootnoteLinkTitle:
|
||||||
|
c.LinkTitle = value.([]byte)
|
||||||
|
case optFootnoteBacklinkTitle:
|
||||||
|
c.BacklinkTitle = value.([]byte)
|
||||||
|
case optFootnoteLinkClass:
|
||||||
|
c.LinkClass = value.([]byte)
|
||||||
|
case optFootnoteBacklinkClass:
|
||||||
|
c.BacklinkClass = value.([]byte)
|
||||||
|
case optFootnoteBacklinkHTML:
|
||||||
|
c.BacklinkHTML = value.([]byte)
|
||||||
|
default:
|
||||||
|
c.Config.SetOption(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withFootnoteHTMLOptions struct {
|
||||||
|
value []html.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteHTMLOptions) SetConfig(c *renderer.Config) {
|
||||||
|
if o.value != nil {
|
||||||
|
for _, v := range o.value {
|
||||||
|
v.(renderer.Option).SetConfig(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteHTMLOptions) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
|
if o.value != nil {
|
||||||
|
for _, v := range o.value {
|
||||||
|
v.SetHTMLOption(&c.Config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
|
||||||
|
func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption {
|
||||||
|
return &withFootnoteHTMLOptions{opts}
|
||||||
|
}
|
||||||
|
|
||||||
|
const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"
|
||||||
|
|
||||||
|
type withFootnoteIDPrefix struct {
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[optFootnoteIDPrefix] = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
|
c.IDPrefix = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
|
||||||
|
func WithFootnoteIDPrefix(a []byte) FootnoteOption {
|
||||||
|
return &withFootnoteIDPrefix{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
|
||||||
|
|
||||||
|
type withFootnoteIDPrefixFunction struct {
|
||||||
|
value func(gast.Node) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[optFootnoteIDPrefixFunction] = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteIDPrefixFunction) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
|
c.IDPrefixFunction = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
|
||||||
|
func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption {
|
||||||
|
return &withFootnoteIDPrefixFunction{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"
|
||||||
|
|
||||||
|
type withFootnoteLinkTitle struct {
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[optFootnoteLinkTitle] = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
|
c.LinkTitle = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
|
||||||
|
func WithFootnoteLinkTitle(a []byte) FootnoteOption {
|
||||||
|
return &withFootnoteLinkTitle{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
|
||||||
|
|
||||||
|
type withFootnoteBacklinkTitle struct {
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[optFootnoteBacklinkTitle] = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
|
c.BacklinkTitle = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
|
||||||
|
func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
|
||||||
|
return &withFootnoteBacklinkTitle{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
|
||||||
|
|
||||||
|
type withFootnoteLinkClass struct {
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[optFootnoteLinkClass] = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
|
c.LinkClass = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFootnoteLinkClass is a functional option that is a class for footnote links.
|
||||||
|
func WithFootnoteLinkClass(a []byte) FootnoteOption {
|
||||||
|
return &withFootnoteLinkClass{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
|
||||||
|
|
||||||
|
type withFootnoteBacklinkClass struct {
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[optFootnoteBacklinkClass] = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
|
c.BacklinkClass = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
|
||||||
|
func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
|
||||||
|
return &withFootnoteBacklinkClass{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
|
||||||
|
|
||||||
|
type withFootnoteBacklinkHTML struct {
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[optFootnoteBacklinkHTML] = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
|
c.BacklinkHTML = o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
|
||||||
|
func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
|
||||||
|
return &withFootnoteBacklinkHTML{a}
|
||||||
|
}
|
||||||
|
|
||||||
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||||
// renders FootnoteLink nodes.
|
// renders FootnoteLink nodes.
|
||||||
type FootnoteHTMLRenderer struct {
|
type FootnoteHTMLRenderer struct {
|
||||||
html.Config
|
FootnoteConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
|
// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
|
||||||
func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer {
|
||||||
r := &FootnoteHTMLRenderer{
|
r := &FootnoteHTMLRenderer{
|
||||||
Config: html.NewConfig(),
|
FootnoteConfig: NewFootnoteConfig(),
|
||||||
}
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt.SetHTMLOption(&r.Config)
|
opt.SetFootnoteOption(&r.FootnoteConfig)
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -234,7 +503,7 @@ func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
||||||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||||
func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
|
reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
|
||||||
reg.Register(ast.KindFootnoteBackLink, r.renderFootnoteBackLink)
|
reg.Register(ast.KindFootnoteBacklink, r.renderFootnoteBacklink)
|
||||||
reg.Register(ast.KindFootnote, r.renderFootnote)
|
reg.Register(ast.KindFootnote, r.renderFootnote)
|
||||||
reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
|
reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
|
||||||
}
|
}
|
||||||
|
@ -243,25 +512,45 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.FootnoteLink)
|
n := node.(*ast.FootnoteLink)
|
||||||
is := strconv.Itoa(n.Index)
|
is := strconv.Itoa(n.Index)
|
||||||
_, _ = w.WriteString(`<sup id="fnref:`)
|
_, _ = w.WriteString(`<sup id="`)
|
||||||
|
_, _ = w.Write(r.idPrefix(node))
|
||||||
|
_, _ = w.WriteString(`fnref:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
_, _ = w.WriteString(`"><a href="#fn:`)
|
_, _ = w.WriteString(`"><a href="#`)
|
||||||
|
_, _ = w.Write(r.idPrefix(node))
|
||||||
|
_, _ = w.WriteString(`fn:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
_, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
|
_, _ = w.WriteString(`" class="`)
|
||||||
|
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
|
||||||
|
n.Index, n.RefCount))
|
||||||
|
if len(r.FootnoteConfig.LinkTitle) > 0 {
|
||||||
|
_, _ = w.WriteString(`" title="`)
|
||||||
|
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
|
||||||
|
}
|
||||||
|
_, _ = w.WriteString(`" role="doc-noteref">`)
|
||||||
|
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
_, _ = w.WriteString(`</a></sup>`)
|
_, _ = w.WriteString(`</a></sup>`)
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FootnoteHTMLRenderer) renderFootnoteBackLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.FootnoteBackLink)
|
n := node.(*ast.FootnoteBacklink)
|
||||||
is := strconv.Itoa(n.Index)
|
is := strconv.Itoa(n.Index)
|
||||||
_, _ = w.WriteString(` <a href="#fnref:`)
|
_, _ = w.WriteString(` <a href="#`)
|
||||||
|
_, _ = w.Write(r.idPrefix(node))
|
||||||
|
_, _ = w.WriteString(`fnref:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
_, _ = w.WriteString(`" class="footnote-backref" role="doc-backlink">`)
|
_, _ = w.WriteString(`" class="`)
|
||||||
_, _ = w.WriteString("↩︎")
|
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
|
||||||
|
if len(r.FootnoteConfig.BacklinkTitle) > 0 {
|
||||||
|
_, _ = w.WriteString(`" title="`)
|
||||||
|
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
|
||||||
|
}
|
||||||
|
_, _ = w.WriteString(`" role="doc-backlink">`)
|
||||||
|
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
|
||||||
_, _ = w.WriteString(`</a>`)
|
_, _ = w.WriteString(`</a>`)
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
|
@ -271,7 +560,9 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n
|
||||||
n := node.(*ast.Footnote)
|
n := node.(*ast.Footnote)
|
||||||
is := strconv.Itoa(n.Index)
|
is := strconv.Itoa(n.Index)
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString(`<li id="fn:`)
|
_, _ = w.WriteString(`<li id="`)
|
||||||
|
_, _ = w.Write(r.idPrefix(node))
|
||||||
|
_, _ = w.WriteString(`fn:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
_, _ = w.WriteString(`" role="doc-endnote"`)
|
_, _ = w.WriteString(`" role="doc-endnote"`)
|
||||||
if node.Attributes() != nil {
|
if node.Attributes() != nil {
|
||||||
|
@ -312,11 +603,54 @@ func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byt
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
|
||||||
|
if r.FootnoteConfig.IDPrefix != nil {
|
||||||
|
return r.FootnoteConfig.IDPrefix
|
||||||
|
}
|
||||||
|
if r.FootnoteConfig.IDPrefixFunction != nil {
|
||||||
|
return r.FootnoteConfig.IDPrefixFunction(node)
|
||||||
|
}
|
||||||
|
return []byte("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
|
||||||
|
fast := true
|
||||||
|
for i, c := range b {
|
||||||
|
if i != 0 {
|
||||||
|
if b[i-1] == '^' && c == '^' {
|
||||||
|
fast = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if b[i-1] == '%' && c == '%' {
|
||||||
|
fast = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fast {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
is := []byte(strconv.Itoa(index))
|
||||||
|
rs := []byte(strconv.Itoa(refCount))
|
||||||
|
ret := bytes.Replace(b, []byte("^^"), is, -1)
|
||||||
|
return bytes.Replace(ret, []byte("%%"), rs, -1)
|
||||||
|
}
|
||||||
|
|
||||||
type footnote struct {
|
type footnote struct {
|
||||||
|
options []FootnoteOption
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
|
// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
|
||||||
var Footnote = &footnote{}
|
var Footnote = &footnote{
|
||||||
|
options: []FootnoteOption{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFootnote returns a new extension with given options.
|
||||||
|
func NewFootnote(opts ...FootnoteOption) goldmark.Extender {
|
||||||
|
return &footnote{
|
||||||
|
options: opts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *footnote) Extend(m goldmark.Markdown) {
|
func (e *footnote) Extend(m goldmark.Markdown) {
|
||||||
m.Parser().AddOptions(
|
m.Parser().AddOptions(
|
||||||
|
@ -331,6 +665,6 @@ func (e *footnote) Extend(m goldmark.Markdown) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
util.Prioritized(NewFootnoteHTMLRenderer(), 500),
|
util.Prioritized(NewFootnoteHTMLRenderer(e.options...), 500),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ import (
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||||
|
|
||||||
var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp):\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-a-zA-Z0-9@:%_+.~#$!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||||
|
|
||||||
// An LinkifyConfig struct is a data structure that holds configuration of the
|
// An LinkifyConfig struct is a data structure that holds configuration of the
|
||||||
// Linkify extension.
|
// Linkify extension.
|
||||||
|
@ -24,10 +24,12 @@ type LinkifyConfig struct {
|
||||||
EmailRegexp *regexp.Regexp
|
EmailRegexp *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
const optLinkifyAllowedProtocols parser.OptionName = "LinkifyAllowedProtocols"
|
const (
|
||||||
const optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp"
|
optLinkifyAllowedProtocols parser.OptionName = "LinkifyAllowedProtocols"
|
||||||
const optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp"
|
optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp"
|
||||||
const optLinkifyEmailRegexp parser.OptionName = "LinkifyEmailRegexp"
|
optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp"
|
||||||
|
optLinkifyEmailRegexp parser.OptionName = "LinkifyEmailRegexp"
|
||||||
|
)
|
||||||
|
|
||||||
// SetOption implements SetOptioner.
|
// SetOption implements SetOptioner.
|
||||||
func (c *LinkifyConfig) SetOption(name parser.OptionName, value interface{}) {
|
func (c *LinkifyConfig) SetOption(name parser.OptionName, value interface{}) {
|
||||||
|
@ -156,10 +158,12 @@ func (s *linkifyParser) Trigger() []byte {
|
||||||
return []byte{' ', '*', '_', '~', '('}
|
return []byte{' ', '*', '_', '~', '('}
|
||||||
}
|
}
|
||||||
|
|
||||||
var protoHTTP = []byte("http:")
|
var (
|
||||||
var protoHTTPS = []byte("https:")
|
protoHTTP = []byte("http:")
|
||||||
var protoFTP = []byte("ftp:")
|
protoHTTPS = []byte("https:")
|
||||||
var domainWWW = []byte("www.")
|
protoFTP = []byte("ftp:")
|
||||||
|
domainWWW = []byte("www.")
|
||||||
|
)
|
||||||
|
|
||||||
func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||||
if pc.IsInLinkLabel() {
|
if pc.IsInLinkLabel() {
|
||||||
|
|
|
@ -15,6 +15,14 @@ import (
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var escapedPipeCellListKey = parser.NewContextKey()
|
||||||
|
|
||||||
|
type escapedPipeCell struct {
|
||||||
|
Cell *ast.TableCell
|
||||||
|
Pos []int
|
||||||
|
Transformed bool
|
||||||
|
}
|
||||||
|
|
||||||
// TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format.
|
// TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format.
|
||||||
type TableCellAlignMethod int
|
type TableCellAlignMethod int
|
||||||
|
|
||||||
|
@ -148,7 +156,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
||||||
if alignments == nil {
|
if alignments == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
header := b.parseRow(lines.At(i-1), alignments, true, reader)
|
header := b.parseRow(lines.At(i-1), alignments, true, reader, pc)
|
||||||
if header == nil || len(alignments) != header.ChildCount() {
|
if header == nil || len(alignments) != header.ChildCount() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -156,7 +164,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
||||||
table.Alignments = alignments
|
table.Alignments = alignments
|
||||||
table.AppendChild(table, ast.NewTableHeader(header))
|
table.AppendChild(table, ast.NewTableHeader(header))
|
||||||
for j := i + 1; j < lines.Len(); j++ {
|
for j := i + 1; j < lines.Len(); j++ {
|
||||||
table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader))
|
table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader, pc))
|
||||||
}
|
}
|
||||||
node.Lines().SetSliced(0, i-1)
|
node.Lines().SetSliced(0, i-1)
|
||||||
node.Parent().InsertAfter(node.Parent(), node, table)
|
node.Parent().InsertAfter(node.Parent(), node, table)
|
||||||
|
@ -170,7 +178,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader) *ast.TableRow {
|
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
|
||||||
source := reader.Source()
|
source := reader.Source()
|
||||||
line := segment.Value(source)
|
line := segment.Value(source)
|
||||||
pos := 0
|
pos := 0
|
||||||
|
@ -194,18 +202,39 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []
|
||||||
} else {
|
} else {
|
||||||
alignment = alignments[i]
|
alignment = alignments[i]
|
||||||
}
|
}
|
||||||
closure := util.FindClosure(line[pos:], byte(0), '|', true, false)
|
|
||||||
if closure < 0 {
|
var escapedCell *escapedPipeCell
|
||||||
closure = len(line[pos:])
|
|
||||||
}
|
|
||||||
node := ast.NewTableCell()
|
node := ast.NewTableCell()
|
||||||
seg := text.NewSegment(segment.Start+pos, segment.Start+pos+closure)
|
node.Alignment = alignment
|
||||||
|
hasBacktick := false
|
||||||
|
closure := pos
|
||||||
|
for ; closure < limit; closure++ {
|
||||||
|
if line[closure] == '`' {
|
||||||
|
hasBacktick = true
|
||||||
|
}
|
||||||
|
if line[closure] == '|' {
|
||||||
|
if closure == 0 || line[closure-1] != '\\' {
|
||||||
|
break
|
||||||
|
} else if hasBacktick {
|
||||||
|
if escapedCell == nil {
|
||||||
|
escapedCell = &escapedPipeCell{node, []int{}, false}
|
||||||
|
escapedList := pc.ComputeIfAbsent(escapedPipeCellListKey,
|
||||||
|
func() interface{} {
|
||||||
|
return []*escapedPipeCell{}
|
||||||
|
}).([]*escapedPipeCell)
|
||||||
|
escapedList = append(escapedList, escapedCell)
|
||||||
|
pc.Set(escapedPipeCellListKey, escapedList)
|
||||||
|
}
|
||||||
|
escapedCell.Pos = append(escapedCell.Pos, segment.Start+closure-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seg := text.NewSegment(segment.Start+pos, segment.Start+closure)
|
||||||
seg = seg.TrimLeftSpace(source)
|
seg = seg.TrimLeftSpace(source)
|
||||||
seg = seg.TrimRightSpace(source)
|
seg = seg.TrimRightSpace(source)
|
||||||
node.Lines().Append(seg)
|
node.Lines().Append(seg)
|
||||||
node.Alignment = alignment
|
|
||||||
row.AppendChild(row, node)
|
row.AppendChild(row, node)
|
||||||
pos += closure + 1
|
pos = closure + 1
|
||||||
}
|
}
|
||||||
for ; i < len(alignments); i++ {
|
for ; i < len(alignments); i++ {
|
||||||
row.AppendChild(row, ast.NewTableCell())
|
row.AppendChild(row, ast.NewTableCell())
|
||||||
|
@ -243,6 +272,61 @@ func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader
|
||||||
return alignments
|
return alignments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tableASTTransformer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultTableASTTransformer = &tableASTTransformer{}
|
||||||
|
|
||||||
|
// NewTableASTTransformer returns a parser.ASTTransformer for tables.
|
||||||
|
func NewTableASTTransformer() parser.ASTTransformer {
|
||||||
|
return defaultTableASTTransformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *tableASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
|
||||||
|
lst := pc.Get(escapedPipeCellListKey)
|
||||||
|
if lst == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pc.Set(escapedPipeCellListKey, nil)
|
||||||
|
for _, v := range lst.([]*escapedPipeCell) {
|
||||||
|
if v.Transformed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_ = gast.Walk(v.Cell, func(n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
|
if !entering || n.Kind() != gast.KindCodeSpan {
|
||||||
|
return gast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for c := n.FirstChild(); c != nil; {
|
||||||
|
next := c.NextSibling()
|
||||||
|
if c.Kind() != gast.KindText {
|
||||||
|
c = next
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parent := c.Parent()
|
||||||
|
ts := &c.(*gast.Text).Segment
|
||||||
|
n := c
|
||||||
|
for _, v := range lst.([]*escapedPipeCell) {
|
||||||
|
for _, pos := range v.Pos {
|
||||||
|
if ts.Start <= pos && pos < ts.Stop {
|
||||||
|
segment := n.(*gast.Text).Segment
|
||||||
|
n1 := gast.NewRawTextSegment(segment.WithStop(pos))
|
||||||
|
n2 := gast.NewRawTextSegment(segment.WithStart(pos + 1))
|
||||||
|
parent.InsertAfter(parent, n, n1)
|
||||||
|
parent.InsertAfter(parent, n1, n2)
|
||||||
|
parent.RemoveChild(parent, n)
|
||||||
|
n = n2
|
||||||
|
v.Transformed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c = next
|
||||||
|
}
|
||||||
|
return gast.WalkContinue, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TableHTMLRenderer is a renderer.NodeRenderer implementation that
|
// TableHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||||
// renders Table nodes.
|
// renders Table nodes.
|
||||||
type TableHTMLRenderer struct {
|
type TableHTMLRenderer struct {
|
||||||
|
@ -419,7 +503,7 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, nod
|
||||||
cob.AppendByte(';')
|
cob.AppendByte(';')
|
||||||
}
|
}
|
||||||
style := fmt.Sprintf("text-align:%s", n.Alignment.String())
|
style := fmt.Sprintf("text-align:%s", n.Alignment.String())
|
||||||
cob.Append(util.StringToReadOnlyBytes(style))
|
cob.AppendString(style)
|
||||||
n.SetAttributeString("style", cob.Bytes())
|
n.SetAttributeString("style", cob.Bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,9 +538,14 @@ func NewTable(opts ...TableOption) goldmark.Extender {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *table) Extend(m goldmark.Markdown) {
|
func (e *table) Extend(m goldmark.Markdown) {
|
||||||
m.Parser().AddOptions(parser.WithParagraphTransformers(
|
m.Parser().AddOptions(
|
||||||
util.Prioritized(NewTableParagraphTransformer(), 200),
|
parser.WithParagraphTransformers(
|
||||||
))
|
util.Prioritized(NewTableParagraphTransformer(), 200),
|
||||||
|
),
|
||||||
|
parser.WithASTTransformers(
|
||||||
|
util.Prioritized(defaultTableASTTransformer, 0),
|
||||||
|
),
|
||||||
|
)
|
||||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
util.Prioritized(NewTableHTMLRenderer(e.options...), 500),
|
util.Prioritized(NewTableHTMLRenderer(e.options...), 500),
|
||||||
))
|
))
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module github.com/yuin/goldmark
|
module github.com/yuin/goldmark
|
||||||
|
|
||||||
go 1.13
|
go 1.15
|
||||||
|
|
|
@ -49,6 +49,12 @@ func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
}
|
}
|
||||||
reader.AdvanceAndSetPadding(pos, padding)
|
reader.AdvanceAndSetPadding(pos, padding)
|
||||||
_, segment = reader.PeekLine()
|
_, segment = reader.PeekLine()
|
||||||
|
|
||||||
|
// if code block line starts with a tab, keep a tab as it is.
|
||||||
|
if segment.Padding != 0 {
|
||||||
|
preserveLeadingTabInCodeBlock(&segment, reader)
|
||||||
|
}
|
||||||
|
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
reader.Advance(segment.Len() - 1)
|
reader.Advance(segment.Len() - 1)
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
|
@ -77,3 +83,14 @@ func (b *codeBlockParser) CanInterruptParagraph() bool {
|
||||||
func (b *codeBlockParser) CanAcceptIndentedLine() bool {
|
func (b *codeBlockParser) CanAcceptIndentedLine() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func preserveLeadingTabInCodeBlock(segment *text.Segment, reader text.Reader) {
|
||||||
|
offsetWithPadding := reader.LineOffset()
|
||||||
|
sl, ss := reader.Position()
|
||||||
|
reader.SetPosition(sl, text.NewSegment(ss.Start-1, ss.Stop))
|
||||||
|
if offsetWithPadding == reader.LineOffset() {
|
||||||
|
segment.Padding = 0
|
||||||
|
segment.Start--
|
||||||
|
}
|
||||||
|
reader.SetPosition(sl, ss)
|
||||||
|
}
|
||||||
|
|
|
@ -71,6 +71,10 @@ func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Con
|
||||||
func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
|
func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
|
||||||
line, segment := reader.PeekLine()
|
line, segment := reader.PeekLine()
|
||||||
fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
|
fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
|
||||||
|
// if code block line starts with a tab, keep a tab as it is.
|
||||||
|
if segment.Padding != 0 {
|
||||||
|
preserveLeadingTabInCodeBlock(&segment, reader)
|
||||||
|
}
|
||||||
w, pos := util.IndentWidth(line, reader.LineOffset())
|
w, pos := util.IndentWidth(line, reader.LineOffset())
|
||||||
if w < 4 {
|
if w < 4 {
|
||||||
i := pos
|
i := pos
|
||||||
|
|
|
@ -2,7 +2,6 @@ package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
|
@ -113,8 +112,6 @@ func (s *linkParser) Trigger() []byte {
|
||||||
return []byte{'!', '[', ']'}
|
return []byte{'!', '[', ']'}
|
||||||
}
|
}
|
||||||
|
|
||||||
var linkDestinationRegexp = regexp.MustCompile(`\s*([^\s].+)`)
|
|
||||||
var linkTitleRegexp = regexp.MustCompile(`\s+(\)|["'\(].+)`)
|
|
||||||
var linkBottom = NewContextKey()
|
var linkBottom = NewContextKey()
|
||||||
|
|
||||||
func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.Node {
|
func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.Node {
|
||||||
|
@ -293,20 +290,17 @@ func (s *linkParser) parseLink(parent ast.Node, last *linkLabelState, block text
|
||||||
func parseLinkDestination(block text.Reader) ([]byte, bool) {
|
func parseLinkDestination(block text.Reader) ([]byte, bool) {
|
||||||
block.SkipSpaces()
|
block.SkipSpaces()
|
||||||
line, _ := block.PeekLine()
|
line, _ := block.PeekLine()
|
||||||
buf := []byte{}
|
|
||||||
if block.Peek() == '<' {
|
if block.Peek() == '<' {
|
||||||
i := 1
|
i := 1
|
||||||
for i < len(line) {
|
for i < len(line) {
|
||||||
c := line[i]
|
c := line[i]
|
||||||
if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
|
if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
|
||||||
buf = append(buf, '\\', line[i+1])
|
|
||||||
i += 2
|
i += 2
|
||||||
continue
|
continue
|
||||||
} else if c == '>' {
|
} else if c == '>' {
|
||||||
block.Advance(i + 1)
|
block.Advance(i + 1)
|
||||||
return line[1:i], true
|
return line[1:i], true
|
||||||
}
|
}
|
||||||
buf = append(buf, c)
|
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@ -316,7 +310,6 @@ func parseLinkDestination(block text.Reader) ([]byte, bool) {
|
||||||
for i < len(line) {
|
for i < len(line) {
|
||||||
c := line[i]
|
c := line[i]
|
||||||
if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
|
if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
|
||||||
buf = append(buf, '\\', line[i+1])
|
|
||||||
i += 2
|
i += 2
|
||||||
continue
|
continue
|
||||||
} else if c == '(' {
|
} else if c == '(' {
|
||||||
|
@ -329,7 +322,6 @@ func parseLinkDestination(block text.Reader) ([]byte, bool) {
|
||||||
} else if util.IsSpace(c) {
|
} else if util.IsSpace(c) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
buf = append(buf, c)
|
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
block.Advance(i)
|
block.Advance(i)
|
||||||
|
|
|
@ -138,6 +138,9 @@ type Context interface {
|
||||||
// Get returns a value associated with the given key.
|
// Get returns a value associated with the given key.
|
||||||
Get(ContextKey) interface{}
|
Get(ContextKey) interface{}
|
||||||
|
|
||||||
|
// ComputeIfAbsent computes a value if a value associated with the given key is absent and returns the value.
|
||||||
|
ComputeIfAbsent(ContextKey, func() interface{}) interface{}
|
||||||
|
|
||||||
// Set sets the given value to the context.
|
// Set sets the given value to the context.
|
||||||
Set(ContextKey, interface{})
|
Set(ContextKey, interface{})
|
||||||
|
|
||||||
|
@ -252,6 +255,15 @@ func (p *parseContext) Get(key ContextKey) interface{} {
|
||||||
return p.store[key]
|
return p.store[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *parseContext) ComputeIfAbsent(key ContextKey, f func() interface{}) interface{} {
|
||||||
|
v := p.store[key]
|
||||||
|
if v == nil {
|
||||||
|
v = f()
|
||||||
|
p.store[key] = v
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func (p *parseContext) Set(key ContextKey, value interface{}) {
|
func (p *parseContext) Set(key ContextKey, value interface{}) {
|
||||||
p.store[key] = value
|
p.store[key] = value
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
"regexp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type rawHTMLParser struct {
|
type rawHTMLParser struct {
|
||||||
|
@ -67,8 +68,6 @@ func (s *rawHTMLParser) parseSingleLineRegexp(reg *regexp.Regexp, block text.Rea
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
var dummyMatch = [][]byte{}
|
|
||||||
|
|
||||||
func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, pc Context) ast.Node {
|
func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, pc Context) ast.Node {
|
||||||
sline, ssegment := block.Position()
|
sline, ssegment := block.Position()
|
||||||
if block.Match(reg) {
|
if block.Match(reg) {
|
||||||
|
@ -102,7 +101,3 @@ func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Read
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *rawHTMLParser) CloseBlock(parent ast.Node, pc Context) {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,6 +37,12 @@ func (b *CopyOnWriteBuffer) Write(value []byte) {
|
||||||
b.buffer = append(b.buffer, value...)
|
b.buffer = append(b.buffer, value...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteString writes given string to the buffer.
|
||||||
|
// WriteString allocate new buffer and clears it at the first time.
|
||||||
|
func (b *CopyOnWriteBuffer) WriteString(value string) {
|
||||||
|
b.Write(StringToReadOnlyBytes(value))
|
||||||
|
}
|
||||||
|
|
||||||
// Append appends given bytes to the buffer.
|
// Append appends given bytes to the buffer.
|
||||||
// Append copy buffer at the first time.
|
// Append copy buffer at the first time.
|
||||||
func (b *CopyOnWriteBuffer) Append(value []byte) {
|
func (b *CopyOnWriteBuffer) Append(value []byte) {
|
||||||
|
@ -49,6 +55,12 @@ func (b *CopyOnWriteBuffer) Append(value []byte) {
|
||||||
b.buffer = append(b.buffer, value...)
|
b.buffer = append(b.buffer, value...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppendString appends given string to the buffer.
|
||||||
|
// AppendString copy buffer at the first time.
|
||||||
|
func (b *CopyOnWriteBuffer) AppendString(value string) {
|
||||||
|
b.Append(StringToReadOnlyBytes(value))
|
||||||
|
}
|
||||||
|
|
||||||
// WriteByte writes the given byte to the buffer.
|
// WriteByte writes the given byte to the buffer.
|
||||||
// WriteByte allocate new buffer and clears it at the first time.
|
// WriteByte allocate new buffer and clears it at the first time.
|
||||||
func (b *CopyOnWriteBuffer) WriteByte(c byte) {
|
func (b *CopyOnWriteBuffer) WriteByte(c byte) {
|
||||||
|
@ -804,7 +816,7 @@ func IsPunct(c byte) bool {
|
||||||
return punctTable[c] == 1
|
return punctTable[c] == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPunct returns true if the given rune is a punctuation, otherwise false.
|
// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
|
||||||
func IsPunctRune(r rune) bool {
|
func IsPunctRune(r rune) bool {
|
||||||
return int32(r) <= 256 && IsPunct(byte(r)) || unicode.IsPunct(r)
|
return int32(r) <= 256 && IsPunct(byte(r)) || unicode.IsPunct(r)
|
||||||
}
|
}
|
||||||
|
@ -814,7 +826,7 @@ func IsSpace(c byte) bool {
|
||||||
return spaceTable[c] == 1
|
return spaceTable[c] == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSpace returns true if the given rune is a space, otherwise false.
|
// IsSpaceRune returns true if the given rune is a space, otherwise false.
|
||||||
func IsSpaceRune(r rune) bool {
|
func IsSpaceRune(r rune) bool {
|
||||||
return int32(r) <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r)
|
return int32(r) <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -745,7 +745,7 @@ github.com/xi2/xz
|
||||||
# github.com/yohcop/openid-go v1.0.0
|
# github.com/yohcop/openid-go v1.0.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/yohcop/openid-go
|
github.com/yohcop/openid-go
|
||||||
# github.com/yuin/goldmark v1.2.1
|
# github.com/yuin/goldmark v1.3.3
|
||||||
## explicit
|
## explicit
|
||||||
github.com/yuin/goldmark
|
github.com/yuin/goldmark
|
||||||
github.com/yuin/goldmark/ast
|
github.com/yuin/goldmark/ast
|
||||||
|
|
Loading…
Reference in New Issue