...

Source file src/github.com/yuin/goldmark/extension/footnote.go

Documentation: github.com/yuin/goldmark/extension

     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  }
   688  

View as plain text