Source File
footnote.go
Belonging Package
github.com/yuin/goldmark/extension
package extension
import (
gast
)
var footnoteListKey = parser.NewContextKey()
var footnoteLinkListKey = parser.NewContextKey()
type footnoteBlockParser struct {
}
var defaultFootnoteBlockParser = &footnoteBlockParser{}
func () parser.BlockParser {
return defaultFootnoteBlockParser
}
func ( *footnoteBlockParser) () []byte {
return []byte{'['}
}
func ( *footnoteBlockParser) ( gast.Node, text.Reader, parser.Context) (gast.Node, parser.State) {
, := .PeekLine()
:= .BlockOffset()
if < 0 || [] != '[' {
return nil, parser.NoChildren
}
++
if > len()-1 || [] != '^' {
return nil, parser.NoChildren
}
:= + 1
:= 0
:= util.FindClosure([+1:], '[', ']', false, false)
= + 1 +
:= + 1
if > -1 {
if >= len() || [] != ':' {
return nil, parser.NoChildren
}
} else {
return nil, parser.NoChildren
}
:= .Padding
:= .Value(text.NewSegment(.Start+-, .Start+-))
if util.IsBlank() {
return nil, parser.NoChildren
}
:= ast.NewFootnote()
= + 1 -
if >= len() {
.Advance()
return , parser.NoChildren
}
.AdvanceAndSetPadding(, )
return , parser.HasChildren
}
func ( *footnoteBlockParser) ( gast.Node, text.Reader, parser.Context) parser.State {
, := .PeekLine()
if util.IsBlank() {
return parser.Continue | parser.HasChildren
}
, := util.IndentPosition(, .LineOffset(), 4)
if < 0 {
return parser.Close
}
.AdvanceAndSetPadding(, )
return parser.Continue | parser.HasChildren
}
func ( *footnoteBlockParser) ( gast.Node, text.Reader, parser.Context) {
var *ast.FootnoteList
if := .Get(footnoteListKey); != nil {
= .(*ast.FootnoteList)
} else {
= ast.NewFootnoteList()
.Set(footnoteListKey, )
.Parent().InsertBefore(.Parent(), , )
}
.Parent().RemoveChild(.Parent(), )
.AppendChild(, )
}
func ( *footnoteBlockParser) () bool {
return true
}
func ( *footnoteBlockParser) () bool {
return false
}
type footnoteParser struct {
}
var defaultFootnoteParser = &footnoteParser{}
func () parser.InlineParser {
return defaultFootnoteParser
}
return []byte{'!', '['}
}
func ( *footnoteParser) ( gast.Node, text.Reader, parser.Context) gast.Node {
, := .PeekLine()
:= 1
if len() > 0 && [0] == '!' {
++
}
if >= len() || [] != '^' {
return nil
}
++
if >= len() {
return nil
}
:=
:= util.FindClosure([:], '[', ']', false, false)
if < 0 {
return nil
}
:= +
:= .Value(text.NewSegment(.Start+, .Start+))
.Advance( + 1)
var *ast.FootnoteList
if := .Get(footnoteListKey); != nil {
= .(*ast.FootnoteList)
}
if == nil {
return nil
}
:= 0
for := .FirstChild(); != nil; = .NextSibling() {
:= .(*ast.Footnote)
if bytes.Equal(.Ref, ) {
if .Index < 0 {
.Count += 1
.Index = .Count
}
= .Index
break
}
}
if == 0 {
return nil
}
:= ast.NewFootnoteLink()
var []*ast.FootnoteLink
if := .Get(footnoteLinkListKey); != nil {
= .([]*ast.FootnoteLink)
} else {
= []*ast.FootnoteLink{}
.Set(footnoteLinkListKey, )
}
.Set(footnoteLinkListKey, append(, ))
if [0] == '!' {
.AppendChild(, gast.NewTextSegment(text.NewSegment(.Start, .Start+1)))
}
return
}
type footnoteASTTransformer struct {
}
var defaultFootnoteASTTransformer = &footnoteASTTransformer{}
func () parser.ASTTransformer {
return defaultFootnoteASTTransformer
}
func ( *footnoteASTTransformer) ( *gast.Document, text.Reader, parser.Context) {
var *ast.FootnoteList
var []*ast.FootnoteLink
if := .Get(footnoteListKey); != nil {
= .(*ast.FootnoteList)
}
if := .Get(footnoteLinkListKey); != nil {
= .([]*ast.FootnoteLink)
}
.Set(footnoteListKey, nil)
.Set(footnoteLinkListKey, nil)
if == nil {
return
}
:= map[int]int{}
if != nil {
for , := range {
if .Index >= 0 {
[.Index]++
}
}
for , := range {
.RefCount = [.Index]
}
}
for := .FirstChild(); != nil; {
var gast.Node =
:= .NextSibling()
if := .LastChild(); != nil && gast.IsParagraph() {
=
}
:= .(*ast.Footnote)
:= .Index
if < 0 {
.RemoveChild(, )
} else {
:= ast.NewFootnoteBacklink()
.RefCount = []
.AppendChild(, )
}
=
}
.SortChildren(func(, gast.Node) int {
if .(*ast.Footnote).Index < .(*ast.Footnote).Index {
return -1
}
return 1
})
if .Count <= 0 {
.Parent().RemoveChild(.Parent(), )
return
}
.AppendChild(, )
}
type FootnoteConfig struct {
html.Config
IDPrefixFunction func(gast.Node) []byte
BacklinkHTML []byte
}
type FootnoteOption interface {
SetFootnoteOption(*FootnoteConfig)
}
func () FootnoteConfig {
return FootnoteConfig{
Config: html.NewConfig(),
LinkTitle: []byte(""),
BacklinkTitle: []byte(""),
LinkClass: []byte("footnote-ref"),
BacklinkClass: []byte("footnote-backref"),
BacklinkHTML: []byte("↩︎"),
}
}
func ( *FootnoteConfig) ( renderer.OptionName, interface{}) {
switch {
case optFootnoteIDPrefixFunction:
.IDPrefixFunction = .(func(gast.Node) []byte)
case optFootnoteIDPrefix:
.IDPrefix = .([]byte)
case optFootnoteLinkTitle:
.LinkTitle = .([]byte)
case optFootnoteBacklinkTitle:
.BacklinkTitle = .([]byte)
case optFootnoteLinkClass:
.LinkClass = .([]byte)
case optFootnoteBacklinkClass:
.BacklinkClass = .([]byte)
case optFootnoteBacklinkHTML:
.BacklinkHTML = .([]byte)
default:
.Config.SetOption(, )
}
}
type withFootnoteHTMLOptions struct {
value []html.Option
}
func ( *withFootnoteHTMLOptions) ( *renderer.Config) {
if .value != nil {
for , := range .value {
.(renderer.Option).SetConfig()
}
}
}
func ( *withFootnoteHTMLOptions) ( *FootnoteConfig) {
if .value != nil {
for , := range .value {
.SetHTMLOption(&.Config)
}
}
}
func ( ...html.Option) FootnoteOption {
return &withFootnoteHTMLOptions{}
}
const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"
type withFootnoteIDPrefix struct {
value []byte
}
func ( *withFootnoteIDPrefix) ( *renderer.Config) {
.Options[optFootnoteIDPrefix] = .value
}
func ( *withFootnoteIDPrefix) ( *FootnoteConfig) {
.IDPrefix = .value
}
func ( []byte) FootnoteOption {
return &withFootnoteIDPrefix{}
}
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
type withFootnoteIDPrefixFunction struct {
value func(gast.Node) []byte
}
func ( *withFootnoteIDPrefixFunction) ( *renderer.Config) {
.Options[optFootnoteIDPrefixFunction] = .value
}
func ( *withFootnoteIDPrefixFunction) ( *FootnoteConfig) {
.IDPrefixFunction = .value
}
func ( func(gast.Node) []byte) FootnoteOption {
return &withFootnoteIDPrefixFunction{}
}
const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"
type withFootnoteLinkTitle struct {
value []byte
}
func ( *withFootnoteLinkTitle) ( *renderer.Config) {
.Options[optFootnoteLinkTitle] = .value
}
func ( *withFootnoteLinkTitle) ( *FootnoteConfig) {
.LinkTitle = .value
}
func ( []byte) FootnoteOption {
return &withFootnoteLinkTitle{}
}
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
type withFootnoteBacklinkTitle struct {
value []byte
}
func ( *withFootnoteBacklinkTitle) ( *renderer.Config) {
.Options[optFootnoteBacklinkTitle] = .value
}
func ( *withFootnoteBacklinkTitle) ( *FootnoteConfig) {
.BacklinkTitle = .value
}
func ( []byte) FootnoteOption {
return &withFootnoteBacklinkTitle{}
}
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
type withFootnoteLinkClass struct {
value []byte
}
func ( *withFootnoteLinkClass) ( *renderer.Config) {
.Options[optFootnoteLinkClass] = .value
}
func ( *withFootnoteLinkClass) ( *FootnoteConfig) {
.LinkClass = .value
}
func ( []byte) FootnoteOption {
return &withFootnoteLinkClass{}
}
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
type withFootnoteBacklinkClass struct {
value []byte
}
func ( *withFootnoteBacklinkClass) ( *renderer.Config) {
.Options[optFootnoteBacklinkClass] = .value
}
func ( *withFootnoteBacklinkClass) ( *FootnoteConfig) {
.BacklinkClass = .value
}
func ( []byte) FootnoteOption {
return &withFootnoteBacklinkClass{}
}
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
type withFootnoteBacklinkHTML struct {
value []byte
}
func ( *withFootnoteBacklinkHTML) ( *renderer.Config) {
.Options[optFootnoteBacklinkHTML] = .value
}
func ( *withFootnoteBacklinkHTML) ( *FootnoteConfig) {
.BacklinkHTML = .value
}
func ( []byte) FootnoteOption {
return &withFootnoteBacklinkHTML{}
}
type FootnoteHTMLRenderer struct {
FootnoteConfig
}
func ( ...FootnoteOption) renderer.NodeRenderer {
:= &FootnoteHTMLRenderer{
FootnoteConfig: NewFootnoteConfig(),
}
for , := range {
.SetFootnoteOption(&.FootnoteConfig)
}
return
}
func ( *FootnoteHTMLRenderer) ( renderer.NodeRendererFuncRegisterer) {
.Register(ast.KindFootnoteLink, .renderFootnoteLink)
.Register(ast.KindFootnoteBacklink, .renderFootnoteBacklink)
.Register(ast.KindFootnote, .renderFootnote)
.Register(ast.KindFootnoteList, .renderFootnoteList)
}
func ( *FootnoteHTMLRenderer) ( util.BufWriter, []byte, gast.Node, bool) (gast.WalkStatus, error) {
if {
:= .(*ast.FootnoteLink)
:= strconv.Itoa(.Index)
_, _ = .WriteString(`<sup id="`)
_, _ = .Write(.idPrefix())
_, _ = .WriteString(`fnref:`)
_, _ = .WriteString()
_, _ = .WriteString(`"><a href="#`)
_, _ = .Write(.idPrefix())
_, _ = .WriteString(`fn:`)
_, _ = .WriteString()
_, _ = .WriteString(`" class="`)
_, _ = .Write(applyFootnoteTemplate(.FootnoteConfig.LinkClass,
.Index, .RefCount))
if len(.FootnoteConfig.LinkTitle) > 0 {
_, _ = .WriteString(`" title="`)
_, _ = .Write(util.EscapeHTML(applyFootnoteTemplate(.FootnoteConfig.LinkTitle, .Index, .RefCount)))
}
_, _ = .WriteString(`" role="doc-noteref">`)
_, _ = .WriteString()
_, _ = .WriteString(`</a></sup>`)
}
return gast.WalkContinue, nil
}
func ( *FootnoteHTMLRenderer) ( util.BufWriter, []byte, gast.Node, bool) (gast.WalkStatus, error) {
if {
:= .(*ast.FootnoteBacklink)
:= strconv.Itoa(.Index)
_, _ = .WriteString(` <a href="#`)
_, _ = .Write(.idPrefix())
_, _ = .WriteString(`fnref:`)
_, _ = .WriteString()
_, _ = .WriteString(`" class="`)
_, _ = .Write(applyFootnoteTemplate(.FootnoteConfig.BacklinkClass, .Index, .RefCount))
if len(.FootnoteConfig.BacklinkTitle) > 0 {
_, _ = .WriteString(`" title="`)
_, _ = .Write(util.EscapeHTML(applyFootnoteTemplate(.FootnoteConfig.BacklinkTitle, .Index, .RefCount)))
}
_, _ = .WriteString(`" role="doc-backlink">`)
_, _ = .Write(applyFootnoteTemplate(.FootnoteConfig.BacklinkHTML, .Index, .RefCount))
_, _ = .WriteString(`</a>`)
}
return gast.WalkContinue, nil
}
func ( *FootnoteHTMLRenderer) ( util.BufWriter, []byte, gast.Node, bool) (gast.WalkStatus, error) {
:= .(*ast.Footnote)
:= strconv.Itoa(.Index)
if {
_, _ = .WriteString(`<li id="`)
_, _ = .Write(.idPrefix())
_, _ = .WriteString(`fn:`)
_, _ = .WriteString()
_, _ = .WriteString(`" role="doc-endnote"`)
if .Attributes() != nil {
html.RenderAttributes(, , html.ListItemAttributeFilter)
}
_, _ = .WriteString(">\n")
} else {
_, _ = .WriteString("</li>\n")
}
return gast.WalkContinue, nil
}
func ( *FootnoteHTMLRenderer) ( util.BufWriter, []byte, gast.Node, bool) (gast.WalkStatus, error) {
:= "section"
if .Config.XHTML {
= "div"
}
if {
_, _ = .WriteString("<")
_, _ = .WriteString()
_, _ = .WriteString(` class="footnotes" role="doc-endnotes"`)
if .Attributes() != nil {
html.RenderAttributes(, , html.GlobalAttributeFilter)
}
_ = .WriteByte('>')
if .Config.XHTML {
_, _ = .WriteString("\n<hr />\n")
} else {
_, _ = .WriteString("\n<hr>\n")
}
_, _ = .WriteString("<ol>\n")
} else {
_, _ = .WriteString("</ol>\n")
_, _ = .WriteString("</")
_, _ = .WriteString()
_, _ = .WriteString(">\n")
}
return gast.WalkContinue, nil
}
func ( *FootnoteHTMLRenderer) ( gast.Node) []byte {
if .FootnoteConfig.IDPrefix != nil {
return .FootnoteConfig.IDPrefix
}
if .FootnoteConfig.IDPrefixFunction != nil {
return .FootnoteConfig.IDPrefixFunction()
}
return []byte("")
}
func ( []byte, , int) []byte {
:= true
for , := range {
if != 0 {
if [-1] == '^' && == '^' {
= false
break
}
if [-1] == '%' && == '%' {
= false
break
}
}
}
if {
return
}
:= []byte(strconv.Itoa())
:= []byte(strconv.Itoa())
:= bytes.Replace(, []byte("^^"), , -1)
return bytes.Replace(, []byte("%%"), , -1)
}
type footnote struct {
options []FootnoteOption
}
var Footnote = &footnote{
options: []FootnoteOption{},
}
func ( ...FootnoteOption) goldmark.Extender {
return &footnote{
options: ,
}
}
func ( *footnote) ( goldmark.Markdown) {
.Parser().AddOptions(
parser.WithBlockParsers(
util.Prioritized(NewFootnoteBlockParser(), 999),
),
parser.WithInlineParsers(
util.Prioritized(NewFootnoteParser(), 101),
),
parser.WithASTTransformers(
util.Prioritized(NewFootnoteASTTransformer(), 999),
),
)
.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewFootnoteHTMLRenderer(.options...), 500),
))
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |