mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
feat: Support named footnote reference links
Uses the provided name for a PHP Markdown Extra Footnote reference when it is not an incorrectly ordered number value, such as: [^named]. The reference names were previously ignored and replaced with ordered index integers. According to: https://michelf.ca/projects/php-markdown/extra/#footnotes > Names can contain any character valid within an id attribute in HTML.
This commit is contained in:
parent
6c741ae251
commit
743f08bde8
3 changed files with 67 additions and 23 deletions
|
|
@ -66,3 +66,26 @@ test![^1]
|
||||||
</ol>
|
</ol>
|
||||||
</section>
|
</section>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
6
|
||||||
|
//- - - - - - - - -//
|
||||||
|
test named[^named].
|
||||||
|
|
||||||
|
test incorrect index number[^42]
|
||||||
|
|
||||||
|
[^named]: footnote
|
||||||
|
[^42]: footnote
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p>test named<sup id="fnref:named"><a href="#fn:named" class="footnote-ref" role="doc-noteref">named</a></sup>.</p>
|
||||||
|
<p>test incorrect index number<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
|
||||||
|
<section class="footnotes" role="doc-endnotes">
|
||||||
|
<hr>
|
||||||
|
<ol>
|
||||||
|
<li id="fn:named" role="doc-endnote">
|
||||||
|
<p>footnote <a href="#fnref:named" class="footnote-backref" role="doc-backlink">↩︎</a></p>
|
||||||
|
</li>
|
||||||
|
<li id="fn:2" role="doc-endnote">
|
||||||
|
<p>footnote <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ type FootnoteLink struct {
|
||||||
gast.BaseInline
|
gast.BaseInline
|
||||||
Index int
|
Index int
|
||||||
RefCount int
|
RefCount int
|
||||||
|
Ref []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump.
|
// Dump implements Node.Dump.
|
||||||
|
|
@ -31,10 +32,11 @@ 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, ref []byte) *FootnoteLink {
|
||||||
return &FootnoteLink{
|
return &FootnoteLink{
|
||||||
Index: index,
|
Index: index,
|
||||||
RefCount: 0,
|
RefCount: 0,
|
||||||
|
Ref: ref,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,6 +46,7 @@ type FootnoteBacklink struct {
|
||||||
gast.BaseInline
|
gast.BaseInline
|
||||||
Index int
|
Index int
|
||||||
RefCount int
|
RefCount int
|
||||||
|
Ref []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump.
|
// Dump implements Node.Dump.
|
||||||
|
|
@ -63,10 +66,11 @@ func (n *FootnoteBacklink) Kind() gast.NodeKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFootnoteBacklink returns a new FootnoteBacklink node.
|
// NewFootnoteBacklink returns a new FootnoteBacklink node.
|
||||||
func NewFootnoteBacklink(index int) *FootnoteBacklink {
|
func NewFootnoteBacklink(index int, ref []byte) *FootnoteBacklink {
|
||||||
return &FootnoteBacklink{
|
return &FootnoteBacklink{
|
||||||
Index: index,
|
Index: index,
|
||||||
RefCount: 0,
|
RefCount: 0,
|
||||||
|
Ref: ref,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
closes := pos + closure
|
closes := pos + closure
|
||||||
value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
|
ref := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
|
||||||
block.Advance(closes + 1)
|
block.Advance(closes + 1)
|
||||||
|
|
||||||
var list *ast.FootnoteList
|
var list *ast.FootnoteList
|
||||||
|
|
@ -153,9 +153,9 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
|
||||||
index := 0
|
index := 0
|
||||||
for def := list.FirstChild(); def != nil; def = def.NextSibling() {
|
for def := list.FirstChild(); def != nil; def = def.NextSibling() {
|
||||||
d := def.(*ast.Footnote)
|
d := def.(*ast.Footnote)
|
||||||
if bytes.Equal(d.Ref, value) {
|
if bytes.Equal(d.Ref, ref) {
|
||||||
if d.Index < 0 {
|
if d.Index < 0 {
|
||||||
list.Count += 1
|
list.Count++
|
||||||
d.Index = list.Count
|
d.Index = list.Count
|
||||||
}
|
}
|
||||||
index = d.Index
|
index = d.Index
|
||||||
|
|
@ -166,7 +166,7 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fnlink := ast.NewFootnoteLink(index)
|
fnlink := ast.NewFootnoteLink(index, ref)
|
||||||
var fnlist []*ast.FootnoteLink
|
var fnlist []*ast.FootnoteLink
|
||||||
if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
|
if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
|
||||||
fnlist = tmp.([]*ast.FootnoteLink)
|
fnlist = tmp.([]*ast.FootnoteLink)
|
||||||
|
|
@ -232,7 +232,7 @@ func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Read
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
list.RemoveChild(list, footnote)
|
list.RemoveChild(list, footnote)
|
||||||
} else {
|
} else {
|
||||||
backLink := ast.NewFootnoteBacklink(index)
|
backLink := ast.NewFootnoteBacklink(index, fn.Ref)
|
||||||
backLink.RefCount = counter[index]
|
backLink.RefCount = counter[index]
|
||||||
container.AppendChild(container, backLink)
|
container.AppendChild(container, backLink)
|
||||||
}
|
}
|
||||||
|
|
@ -511,25 +511,26 @@ func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegist
|
||||||
func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.FootnoteLink)
|
n := node.(*ast.FootnoteLink)
|
||||||
is := strconv.Itoa(n.Index)
|
|
||||||
|
reference := r.getReference(n.Index, n.Ref)
|
||||||
_, _ = w.WriteString(`<sup id="`)
|
_, _ = w.WriteString(`<sup id="`)
|
||||||
_, _ = w.Write(r.idPrefix(node))
|
_, _ = w.Write(r.idPrefix(node))
|
||||||
_, _ = w.WriteString(`fnref:`)
|
_, _ = w.WriteString(`fnref:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.Write(reference)
|
||||||
_, _ = w.WriteString(`"><a href="#`)
|
_, _ = w.WriteString(`"><a href="#`)
|
||||||
_, _ = w.Write(r.idPrefix(node))
|
_, _ = w.Write(r.idPrefix(node))
|
||||||
_, _ = w.WriteString(`fn:`)
|
_, _ = w.WriteString(`fn:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.Write(reference)
|
||||||
_, _ = w.WriteString(`" class="`)
|
_, _ = w.WriteString(`" class="`)
|
||||||
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
|
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
|
||||||
n.Index, n.RefCount))
|
reference, n.RefCount))
|
||||||
if len(r.FootnoteConfig.LinkTitle) > 0 {
|
if len(r.FootnoteConfig.LinkTitle) > 0 {
|
||||||
_, _ = w.WriteString(`" title="`)
|
_, _ = w.WriteString(`" title="`)
|
||||||
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
|
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, reference, n.RefCount)))
|
||||||
}
|
}
|
||||||
_, _ = w.WriteString(`" role="doc-noteref">`)
|
_, _ = w.WriteString(`" role="doc-noteref">`)
|
||||||
|
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.Write(reference)
|
||||||
_, _ = w.WriteString(`</a></sup>`)
|
_, _ = w.WriteString(`</a></sup>`)
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
|
|
@ -538,19 +539,20 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
|
||||||
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)
|
|
||||||
|
reference := r.getReference(n.Index, n.Ref)
|
||||||
_, _ = w.WriteString(` <a href="#`)
|
_, _ = w.WriteString(` <a href="#`)
|
||||||
_, _ = w.Write(r.idPrefix(node))
|
_, _ = w.Write(r.idPrefix(node))
|
||||||
_, _ = w.WriteString(`fnref:`)
|
_, _ = w.WriteString(`fnref:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.Write(reference)
|
||||||
_, _ = w.WriteString(`" class="`)
|
_, _ = w.WriteString(`" class="`)
|
||||||
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
|
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, reference, n.RefCount))
|
||||||
if len(r.FootnoteConfig.BacklinkTitle) > 0 {
|
if len(r.FootnoteConfig.BacklinkTitle) > 0 {
|
||||||
_, _ = w.WriteString(`" title="`)
|
_, _ = w.WriteString(`" title="`)
|
||||||
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
|
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, reference, n.RefCount)))
|
||||||
}
|
}
|
||||||
_, _ = w.WriteString(`" role="doc-backlink">`)
|
_, _ = w.WriteString(`" role="doc-backlink">`)
|
||||||
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
|
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, reference, n.RefCount))
|
||||||
_, _ = w.WriteString(`</a>`)
|
_, _ = w.WriteString(`</a>`)
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
|
|
@ -558,12 +560,13 @@ func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source [
|
||||||
|
|
||||||
func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
n := node.(*ast.Footnote)
|
n := node.(*ast.Footnote)
|
||||||
is := strconv.Itoa(n.Index)
|
|
||||||
|
reference := r.getReference(n.Index, n.Ref)
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString(`<li id="`)
|
_, _ = w.WriteString(`<li id="`)
|
||||||
_, _ = w.Write(r.idPrefix(node))
|
_, _ = w.Write(r.idPrefix(node))
|
||||||
_, _ = w.WriteString(`fn:`)
|
_, _ = w.WriteString(`fn:`)
|
||||||
_, _ = w.WriteString(is)
|
_, _ = w.Write(reference)
|
||||||
_, _ = w.WriteString(`" role="doc-endnote"`)
|
_, _ = w.WriteString(`" role="doc-endnote"`)
|
||||||
if node.Attributes() != nil {
|
if node.Attributes() != nil {
|
||||||
html.RenderAttributes(w, node, html.ListItemAttributeFilter)
|
html.RenderAttributes(w, node, html.ListItemAttributeFilter)
|
||||||
|
|
@ -613,7 +616,21 @@ func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
|
||||||
return []byte("")
|
return []byte("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
|
// getReference returns the correct reference name value: named reference or
|
||||||
|
// sequential index number.
|
||||||
|
func (r *FootnoteHTMLRenderer) getReference(index int, ref []byte) []byte {
|
||||||
|
_, err := strconv.Atoi(string(ref))
|
||||||
|
isInt := err == nil
|
||||||
|
|
||||||
|
if isInt {
|
||||||
|
// ignore ref value and return the index when the ref is an int
|
||||||
|
return []byte(strconv.Itoa(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyFootnoteTemplate(b []byte, reference []byte, refCount int) []byte {
|
||||||
fast := true
|
fast := true
|
||||||
for i, c := range b {
|
for i, c := range b {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
|
|
@ -630,9 +647,9 @@ func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
|
||||||
if fast {
|
if fast {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
is := []byte(strconv.Itoa(index))
|
|
||||||
rs := []byte(strconv.Itoa(refCount))
|
rs := []byte(strconv.Itoa(refCount))
|
||||||
ret := bytes.Replace(b, []byte("^^"), is, -1)
|
ret := bytes.Replace(b, []byte("^^"), reference, -1)
|
||||||
return bytes.Replace(ret, []byte("%%"), rs, -1)
|
return bytes.Replace(ret, []byte("%%"), rs, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue