gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

footnote.go (19220B)


      1 package extension
      2 
      3 import (
      4 	"bytes"
      5 	"fmt"
      6 	"strconv"
      7 
      8 	"github.com/yuin/goldmark"
      9 	gast "github.com/yuin/goldmark/ast"
     10 	"github.com/yuin/goldmark/extension/ast"
     11 	"github.com/yuin/goldmark/parser"
     12 	"github.com/yuin/goldmark/renderer"
     13 	"github.com/yuin/goldmark/renderer/html"
     14 	"github.com/yuin/goldmark/text"
     15 	"github.com/yuin/goldmark/util"
     16 )
     17 
     18 var footnoteListKey = parser.NewContextKey()
     19 var footnoteLinkListKey = parser.NewContextKey()
     20 
     21 type footnoteBlockParser struct {
     22 }
     23 
     24 var defaultFootnoteBlockParser = &footnoteBlockParser{}
     25 
     26 // NewFootnoteBlockParser returns a new parser.BlockParser that can parse
     27 // footnotes of the Markdown(PHP Markdown Extra) text.
     28 func NewFootnoteBlockParser() parser.BlockParser {
     29 	return defaultFootnoteBlockParser
     30 }
     31 
     32 func (b *footnoteBlockParser) Trigger() []byte {
     33 	return []byte{'['}
     34 }
     35 
     36 func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
     37 	line, segment := reader.PeekLine()
     38 	pos := pc.BlockOffset()
     39 	if pos < 0 || line[pos] != '[' {
     40 		return nil, parser.NoChildren
     41 	}
     42 	pos++
     43 	if pos > len(line)-1 || line[pos] != '^' {
     44 		return nil, parser.NoChildren
     45 	}
     46 	open := pos + 1
     47 	closes := 0
     48 	closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
     49 	closes = pos + 1 + closure
     50 	next := closes + 1
     51 	if closure > -1 {
     52 		if next >= len(line) || line[next] != ':' {
     53 			return nil, parser.NoChildren
     54 		}
     55 	} else {
     56 		return nil, parser.NoChildren
     57 	}
     58 	padding := segment.Padding
     59 	label := reader.Value(text.NewSegment(segment.Start+open-padding, segment.Start+closes-padding))
     60 	if util.IsBlank(label) {
     61 		return nil, parser.NoChildren
     62 	}
     63 	item := ast.NewFootnote(label)
     64 
     65 	pos = next + 1 - padding
     66 	if pos >= len(line) {
     67 		reader.Advance(pos)
     68 		return item, parser.NoChildren
     69 	}
     70 	reader.AdvanceAndSetPadding(pos, padding)
     71 	return item, parser.HasChildren
     72 }
     73 
     74 func (b *footnoteBlockParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
     75 	line, _ := reader.PeekLine()
     76 	if util.IsBlank(line) {
     77 		return parser.Continue | parser.HasChildren
     78 	}
     79 	childpos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
     80 	if childpos < 0 {
     81 		return parser.Close
     82 	}
     83 	reader.AdvanceAndSetPadding(childpos, padding)
     84 	return parser.Continue | parser.HasChildren
     85 }
     86 
     87 func (b *footnoteBlockParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
     88 	var list *ast.FootnoteList
     89 	if tlist := pc.Get(footnoteListKey); tlist != nil {
     90 		list = tlist.(*ast.FootnoteList)
     91 	} else {
     92 		list = ast.NewFootnoteList()
     93 		pc.Set(footnoteListKey, list)
     94 		node.Parent().InsertBefore(node.Parent(), node, list)
     95 	}
     96 	node.Parent().RemoveChild(node.Parent(), node)
     97 	list.AppendChild(list, node)
     98 }
     99 
    100 func (b *footnoteBlockParser) CanInterruptParagraph() bool {
    101 	return true
    102 }
    103 
    104 func (b *footnoteBlockParser) CanAcceptIndentedLine() bool {
    105 	return false
    106 }
    107 
    108 type footnoteParser struct {
    109 }
    110 
    111 var defaultFootnoteParser = &footnoteParser{}
    112 
    113 // NewFootnoteParser returns a new parser.InlineParser that can parse
    114 // footnote links of the Markdown(PHP Markdown Extra) text.
    115 func NewFootnoteParser() parser.InlineParser {
    116 	return defaultFootnoteParser
    117 }
    118 
    119 func (s *footnoteParser) Trigger() []byte {
    120 	// footnote syntax probably conflict with the image syntax.
    121 	// So we need trigger this parser with '!'.
    122 	return []byte{'!', '['}
    123 }
    124 
    125 func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
    126 	line, segment := block.PeekLine()
    127 	pos := 1
    128 	if len(line) > 0 && line[0] == '!' {
    129 		pos++
    130 	}
    131 	if pos >= len(line) || line[pos] != '^' {
    132 		return nil
    133 	}
    134 	pos++
    135 	if pos >= len(line) {
    136 		return nil
    137 	}
    138 	open := pos
    139 	closure := util.FindClosure(line[pos:], '[', ']', false, false)
    140 	if closure < 0 {
    141 		return nil
    142 	}
    143 	closes := pos + closure
    144 	value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
    145 	block.Advance(closes + 1)
    146 
    147 	var list *ast.FootnoteList
    148 	if tlist := pc.Get(footnoteListKey); tlist != nil {
    149 		list = tlist.(*ast.FootnoteList)
    150 	}
    151 	if list == nil {
    152 		return nil
    153 	}
    154 	index := 0
    155 	for def := list.FirstChild(); def != nil; def = def.NextSibling() {
    156 		d := def.(*ast.Footnote)
    157 		if bytes.Equal(d.Ref, value) {
    158 			if d.Index < 0 {
    159 				list.Count += 1
    160 				d.Index = list.Count
    161 			}
    162 			index = d.Index
    163 			break
    164 		}
    165 	}
    166 	if index == 0 {
    167 		return nil
    168 	}
    169 
    170 	fnlink := ast.NewFootnoteLink(index)
    171 	var fnlist []*ast.FootnoteLink
    172 	if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
    173 		fnlist = tmp.([]*ast.FootnoteLink)
    174 	} else {
    175 		fnlist = []*ast.FootnoteLink{}
    176 		pc.Set(footnoteLinkListKey, fnlist)
    177 	}
    178 	pc.Set(footnoteLinkListKey, append(fnlist, fnlink))
    179 	if line[0] == '!' {
    180 		parent.AppendChild(parent, gast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+1)))
    181 	}
    182 
    183 	return fnlink
    184 }
    185 
    186 type footnoteASTTransformer struct {
    187 }
    188 
    189 var defaultFootnoteASTTransformer = &footnoteASTTransformer{}
    190 
    191 // NewFootnoteASTTransformer returns a new parser.ASTTransformer that
    192 // insert a footnote list to the last of the document.
    193 func NewFootnoteASTTransformer() parser.ASTTransformer {
    194 	return defaultFootnoteASTTransformer
    195 }
    196 
    197 func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
    198 	var list *ast.FootnoteList
    199 	var fnlist []*ast.FootnoteLink
    200 	if tmp := pc.Get(footnoteListKey); tmp != nil {
    201 		list = tmp.(*ast.FootnoteList)
    202 	}
    203 	if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
    204 		fnlist = tmp.([]*ast.FootnoteLink)
    205 	}
    206 
    207 	pc.Set(footnoteListKey, nil)
    208 	pc.Set(footnoteLinkListKey, nil)
    209 
    210 	if list == nil {
    211 		return
    212 	}
    213 
    214 	counter := map[int]int{}
    215 	if fnlist != nil {
    216 		for _, fnlink := range fnlist {
    217 			if fnlink.Index >= 0 {
    218 				counter[fnlink.Index]++
    219 			}
    220 		}
    221 		refCounter := map[int]int{}
    222 		for _, fnlink := range fnlist {
    223 			fnlink.RefCount = counter[fnlink.Index]
    224 			if _, ok := refCounter[fnlink.Index]; !ok {
    225 				refCounter[fnlink.Index] = 0
    226 			}
    227 			fnlink.RefIndex = refCounter[fnlink.Index]
    228 			refCounter[fnlink.Index]++
    229 		}
    230 	}
    231 	for footnote := list.FirstChild(); footnote != nil; {
    232 		var container gast.Node = footnote
    233 		next := footnote.NextSibling()
    234 		if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
    235 			container = fc
    236 		}
    237 		fn := footnote.(*ast.Footnote)
    238 		index := fn.Index
    239 		if index < 0 {
    240 			list.RemoveChild(list, footnote)
    241 		} else {
    242 			refCount := counter[index]
    243 			backLink := ast.NewFootnoteBacklink(index)
    244 			backLink.RefCount = refCount
    245 			backLink.RefIndex = 0
    246 			container.AppendChild(container, backLink)
    247 			if refCount > 1 {
    248 				for i := 1; i < refCount; i++ {
    249 					backLink := ast.NewFootnoteBacklink(index)
    250 					backLink.RefCount = refCount
    251 					backLink.RefIndex = i
    252 					container.AppendChild(container, backLink)
    253 				}
    254 			}
    255 		}
    256 		footnote = next
    257 	}
    258 	list.SortChildren(func(n1, n2 gast.Node) int {
    259 		if n1.(*ast.Footnote).Index < n2.(*ast.Footnote).Index {
    260 			return -1
    261 		}
    262 		return 1
    263 	})
    264 	if list.Count <= 0 {
    265 		list.Parent().RemoveChild(list.Parent(), list)
    266 		return
    267 	}
    268 
    269 	node.AppendChild(node, list)
    270 }
    271 
    272 // FootnoteConfig holds configuration values for the footnote extension.
    273 //
    274 // Link* and Backlink* configurations have some variables:
    275 // Occurrances of “^^” in the string will be replaced by the
    276 // corresponding footnote number in the HTML output.
    277 // Occurrances of “%%” will be replaced by a number for the
    278 // reference (footnotes can have multiple references).
    279 type FootnoteConfig struct {
    280 	html.Config
    281 
    282 	// IDPrefix is a prefix for the id attributes generated by footnotes.
    283 	IDPrefix []byte
    284 
    285 	// IDPrefix is a function that determines the id attribute for given Node.
    286 	IDPrefixFunction func(gast.Node) []byte
    287 
    288 	// LinkTitle is an optional title attribute for footnote links.
    289 	LinkTitle []byte
    290 
    291 	// BacklinkTitle is an optional title attribute for footnote backlinks.
    292 	BacklinkTitle []byte
    293 
    294 	// LinkClass is a class for footnote links.
    295 	LinkClass []byte
    296 
    297 	// BacklinkClass is a class for footnote backlinks.
    298 	BacklinkClass []byte
    299 
    300 	// BacklinkHTML is an HTML content for footnote backlinks.
    301 	BacklinkHTML []byte
    302 }
    303 
    304 // FootnoteOption interface is a functional option interface for the extension.
    305 type FootnoteOption interface {
    306 	renderer.Option
    307 	// SetFootnoteOption sets given option to the extension.
    308 	SetFootnoteOption(*FootnoteConfig)
    309 }
    310 
    311 // NewFootnoteConfig returns a new Config with defaults.
    312 func NewFootnoteConfig() FootnoteConfig {
    313 	return FootnoteConfig{
    314 		Config:        html.NewConfig(),
    315 		LinkTitle:     []byte(""),
    316 		BacklinkTitle: []byte(""),
    317 		LinkClass:     []byte("footnote-ref"),
    318 		BacklinkClass: []byte("footnote-backref"),
    319 		BacklinkHTML:  []byte("&#x21a9;&#xfe0e;"),
    320 	}
    321 }
    322 
    323 // SetOption implements renderer.SetOptioner.
    324 func (c *FootnoteConfig) SetOption(name renderer.OptionName, value interface{}) {
    325 	switch name {
    326 	case optFootnoteIDPrefixFunction:
    327 		c.IDPrefixFunction = value.(func(gast.Node) []byte)
    328 	case optFootnoteIDPrefix:
    329 		c.IDPrefix = value.([]byte)
    330 	case optFootnoteLinkTitle:
    331 		c.LinkTitle = value.([]byte)
    332 	case optFootnoteBacklinkTitle:
    333 		c.BacklinkTitle = value.([]byte)
    334 	case optFootnoteLinkClass:
    335 		c.LinkClass = value.([]byte)
    336 	case optFootnoteBacklinkClass:
    337 		c.BacklinkClass = value.([]byte)
    338 	case optFootnoteBacklinkHTML:
    339 		c.BacklinkHTML = value.([]byte)
    340 	default:
    341 		c.Config.SetOption(name, value)
    342 	}
    343 }
    344 
    345 type withFootnoteHTMLOptions struct {
    346 	value []html.Option
    347 }
    348 
    349 func (o *withFootnoteHTMLOptions) SetConfig(c *renderer.Config) {
    350 	if o.value != nil {
    351 		for _, v := range o.value {
    352 			v.(renderer.Option).SetConfig(c)
    353 		}
    354 	}
    355 }
    356 
    357 func (o *withFootnoteHTMLOptions) SetFootnoteOption(c *FootnoteConfig) {
    358 	if o.value != nil {
    359 		for _, v := range o.value {
    360 			v.SetHTMLOption(&c.Config)
    361 		}
    362 	}
    363 }
    364 
    365 // WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
    366 func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption {
    367 	return &withFootnoteHTMLOptions{opts}
    368 }
    369 
    370 const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"
    371 
    372 type withFootnoteIDPrefix struct {
    373 	value []byte
    374 }
    375 
    376 func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) {
    377 	c.Options[optFootnoteIDPrefix] = o.value
    378 }
    379 
    380 func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
    381 	c.IDPrefix = o.value
    382 }
    383 
    384 // WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
    385 func WithFootnoteIDPrefix(a []byte) FootnoteOption {
    386 	return &withFootnoteIDPrefix{a}
    387 }
    388 
    389 const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
    390 
    391 type withFootnoteIDPrefixFunction struct {
    392 	value func(gast.Node) []byte
    393 }
    394 
    395 func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) {
    396 	c.Options[optFootnoteIDPrefixFunction] = o.value
    397 }
    398 
    399 func (o *withFootnoteIDPrefixFunction) SetFootnoteOption(c *FootnoteConfig) {
    400 	c.IDPrefixFunction = o.value
    401 }
    402 
    403 // WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
    404 func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption {
    405 	return &withFootnoteIDPrefixFunction{a}
    406 }
    407 
    408 const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"
    409 
    410 type withFootnoteLinkTitle struct {
    411 	value []byte
    412 }
    413 
    414 func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) {
    415 	c.Options[optFootnoteLinkTitle] = o.value
    416 }
    417 
    418 func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
    419 	c.LinkTitle = o.value
    420 }
    421 
    422 // WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
    423 func WithFootnoteLinkTitle(a []byte) FootnoteOption {
    424 	return &withFootnoteLinkTitle{a}
    425 }
    426 
    427 const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
    428 
    429 type withFootnoteBacklinkTitle struct {
    430 	value []byte
    431 }
    432 
    433 func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) {
    434 	c.Options[optFootnoteBacklinkTitle] = o.value
    435 }
    436 
    437 func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
    438 	c.BacklinkTitle = o.value
    439 }
    440 
    441 // WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
    442 func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
    443 	return &withFootnoteBacklinkTitle{a}
    444 }
    445 
    446 const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
    447 
    448 type withFootnoteLinkClass struct {
    449 	value []byte
    450 }
    451 
    452 func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) {
    453 	c.Options[optFootnoteLinkClass] = o.value
    454 }
    455 
    456 func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
    457 	c.LinkClass = o.value
    458 }
    459 
    460 // WithFootnoteLinkClass is a functional option that is a class for footnote links.
    461 func WithFootnoteLinkClass(a []byte) FootnoteOption {
    462 	return &withFootnoteLinkClass{a}
    463 }
    464 
    465 const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
    466 
    467 type withFootnoteBacklinkClass struct {
    468 	value []byte
    469 }
    470 
    471 func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) {
    472 	c.Options[optFootnoteBacklinkClass] = o.value
    473 }
    474 
    475 func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
    476 	c.BacklinkClass = o.value
    477 }
    478 
    479 // WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
    480 func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
    481 	return &withFootnoteBacklinkClass{a}
    482 }
    483 
    484 const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
    485 
    486 type withFootnoteBacklinkHTML struct {
    487 	value []byte
    488 }
    489 
    490 func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) {
    491 	c.Options[optFootnoteBacklinkHTML] = o.value
    492 }
    493 
    494 func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
    495 	c.BacklinkHTML = o.value
    496 }
    497 
    498 // WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
    499 func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
    500 	return &withFootnoteBacklinkHTML{a}
    501 }
    502 
    503 // FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
    504 // renders FootnoteLink nodes.
    505 type FootnoteHTMLRenderer struct {
    506 	FootnoteConfig
    507 }
    508 
    509 // NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
    510 func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer {
    511 	r := &FootnoteHTMLRenderer{
    512 		FootnoteConfig: NewFootnoteConfig(),
    513 	}
    514 	for _, opt := range opts {
    515 		opt.SetFootnoteOption(&r.FootnoteConfig)
    516 	}
    517 	return r
    518 }
    519 
    520 // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
    521 func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
    522 	reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
    523 	reg.Register(ast.KindFootnoteBacklink, r.renderFootnoteBacklink)
    524 	reg.Register(ast.KindFootnote, r.renderFootnote)
    525 	reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
    526 }
    527 
    528 func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
    529 	if entering {
    530 		n := node.(*ast.FootnoteLink)
    531 		is := strconv.Itoa(n.Index)
    532 		_, _ = w.WriteString(`<sup id="`)
    533 		_, _ = w.Write(r.idPrefix(node))
    534 		_, _ = w.WriteString(`fnref`)
    535 		if n.RefIndex > 0 {
    536 			_, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
    537 		}
    538 		_ = w.WriteByte(':')
    539 		_, _ = w.WriteString(is)
    540 		_, _ = w.WriteString(`"><a href="#`)
    541 		_, _ = w.Write(r.idPrefix(node))
    542 		_, _ = w.WriteString(`fn:`)
    543 		_, _ = w.WriteString(is)
    544 		_, _ = w.WriteString(`" class="`)
    545 		_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
    546 			n.Index, n.RefCount))
    547 		if len(r.FootnoteConfig.LinkTitle) > 0 {
    548 			_, _ = w.WriteString(`" title="`)
    549 			_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
    550 		}
    551 		_, _ = w.WriteString(`" role="doc-noteref">`)
    552 
    553 		_, _ = w.WriteString(is)
    554 		_, _ = w.WriteString(`</a></sup>`)
    555 	}
    556 	return gast.WalkContinue, nil
    557 }
    558 
    559 func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
    560 	if entering {
    561 		n := node.(*ast.FootnoteBacklink)
    562 		is := strconv.Itoa(n.Index)
    563 		_, _ = w.WriteString(`&#160;<a href="#`)
    564 		_, _ = w.Write(r.idPrefix(node))
    565 		_, _ = w.WriteString(`fnref`)
    566 		if n.RefIndex > 0 {
    567 			_, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
    568 		}
    569 		_ = w.WriteByte(':')
    570 		_, _ = w.WriteString(is)
    571 		_, _ = w.WriteString(`" class="`)
    572 		_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
    573 		if len(r.FootnoteConfig.BacklinkTitle) > 0 {
    574 			_, _ = w.WriteString(`" title="`)
    575 			_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
    576 		}
    577 		_, _ = w.WriteString(`" role="doc-backlink">`)
    578 		_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
    579 		_, _ = w.WriteString(`</a>`)
    580 	}
    581 	return gast.WalkContinue, nil
    582 }
    583 
    584 func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
    585 	n := node.(*ast.Footnote)
    586 	is := strconv.Itoa(n.Index)
    587 	if entering {
    588 		_, _ = w.WriteString(`<li id="`)
    589 		_, _ = w.Write(r.idPrefix(node))
    590 		_, _ = w.WriteString(`fn:`)
    591 		_, _ = w.WriteString(is)
    592 		_, _ = w.WriteString(`"`)
    593 		if node.Attributes() != nil {
    594 			html.RenderAttributes(w, node, html.ListItemAttributeFilter)
    595 		}
    596 		_, _ = w.WriteString(">\n")
    597 	} else {
    598 		_, _ = w.WriteString("</li>\n")
    599 	}
    600 	return gast.WalkContinue, nil
    601 }
    602 
    603 func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
    604 	if entering {
    605 		_, _ = w.WriteString(`<div class="footnotes" role="doc-endnotes"`)
    606 		if node.Attributes() != nil {
    607 			html.RenderAttributes(w, node, html.GlobalAttributeFilter)
    608 		}
    609 		_ = w.WriteByte('>')
    610 		if r.Config.XHTML {
    611 			_, _ = w.WriteString("\n<hr />\n")
    612 		} else {
    613 			_, _ = w.WriteString("\n<hr>\n")
    614 		}
    615 		_, _ = w.WriteString("<ol>\n")
    616 	} else {
    617 		_, _ = w.WriteString("</ol>\n")
    618 		_, _ = w.WriteString("</div>\n")
    619 	}
    620 	return gast.WalkContinue, nil
    621 }
    622 
    623 func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
    624 	if r.FootnoteConfig.IDPrefix != nil {
    625 		return r.FootnoteConfig.IDPrefix
    626 	}
    627 	if r.FootnoteConfig.IDPrefixFunction != nil {
    628 		return r.FootnoteConfig.IDPrefixFunction(node)
    629 	}
    630 	return []byte("")
    631 }
    632 
    633 func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
    634 	fast := true
    635 	for i, c := range b {
    636 		if i != 0 {
    637 			if b[i-1] == '^' && c == '^' {
    638 				fast = false
    639 				break
    640 			}
    641 			if b[i-1] == '%' && c == '%' {
    642 				fast = false
    643 				break
    644 			}
    645 		}
    646 	}
    647 	if fast {
    648 		return b
    649 	}
    650 	is := []byte(strconv.Itoa(index))
    651 	rs := []byte(strconv.Itoa(refCount))
    652 	ret := bytes.Replace(b, []byte("^^"), is, -1)
    653 	return bytes.Replace(ret, []byte("%%"), rs, -1)
    654 }
    655 
    656 type footnote struct {
    657 	options []FootnoteOption
    658 }
    659 
    660 // Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
    661 var Footnote = &footnote{
    662 	options: []FootnoteOption{},
    663 }
    664 
    665 // NewFootnote returns a new extension with given options.
    666 func NewFootnote(opts ...FootnoteOption) goldmark.Extender {
    667 	return &footnote{
    668 		options: opts,
    669 	}
    670 }
    671 
    672 func (e *footnote) Extend(m goldmark.Markdown) {
    673 	m.Parser().AddOptions(
    674 		parser.WithBlockParsers(
    675 			util.Prioritized(NewFootnoteBlockParser(), 999),
    676 		),
    677 		parser.WithInlineParsers(
    678 			util.Prioritized(NewFootnoteParser(), 101),
    679 		),
    680 		parser.WithASTTransformers(
    681 			util.Prioritized(NewFootnoteASTTransformer(), 999),
    682 		),
    683 	)
    684 	m.Renderer().AddOptions(renderer.WithNodeRenderers(
    685 		util.Prioritized(NewFootnoteHTMLRenderer(e.options...), 500),
    686 	))
    687 }