From a20399916c119a2c9fa4b6642044f7168c022620 Mon Sep 17 00:00:00 2001 From: choueric Date: Thu, 8 Jun 2017 14:43:56 +0800 Subject: [PATCH 1/3] fix duplicate and recursive footnotes. (#241) Fix the infinite loop when there is a self-refer footnote by checking if the reference is already added into parser.notes. It also fixes of creating duplicate footnotes through this modification. Add coresponding testcase. --- inline.go | 2 +- inline_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ markdown.go | 10 ++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/inline.go b/inline.go index c1f7475..eff1383 100644 --- a/inline.go +++ b/inline.go @@ -498,7 +498,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { return 0 } - if t == linkDeferredFootnote { + if t == linkDeferredFootnote && !p.isFootnote(lr) { lr.noteId = len(p.notes) + 1 p.notes = append(p.notes, lr) } diff --git a/inline_test.go b/inline_test.go index bcaba09..4061cfb 100644 --- a/inline_test.go +++ b/inline_test.go @@ -1023,6 +1023,28 @@ what happens here +`, + `testing footnotes.[^a] + +test footnotes the second.[^b] + +[^a]: This is the first note[^a]. +[^b]: this is the second note.[^a] +`, + `

testing footnotes.1

+ +

test footnotes the second.2

+
+ +
+ +
    +
  1. This is the first note1. +
  2. +
  3. this is the second note.1 +
  4. +
+
`, } @@ -1076,6 +1098,34 @@ func TestNestedFootnotes(t *testing.T) { +`, + `This uses footnote A.[^A] + +This uses footnote C.[^C] + +[^A]: + A note. use itself.[^A] +[^B]: + B note, uses A to test duplicate.[^A] +[^C]: + C note, uses B.[^B] +`, + `

This uses footnote A.1

+ +

This uses footnote C.2

+
+ +
+ +
    +
  1. A note. use itself.1 +
  2. +
  3. C note, uses B.3 +
  4. +
  5. B note, uses A to test duplicate.1 +
  6. +
+
`, } doTestsInlineParam(t, tests, Options{Extensions: EXTENSION_FOOTNOTES}, 0, diff --git a/markdown.go b/markdown.go index 6d842d3..c82be6b 100644 --- a/markdown.go +++ b/markdown.go @@ -241,6 +241,16 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) { return ref, found } +func (p *parser) isFootnote(ref *reference) bool { + for _, v := range p.notes { + if string(ref.link) == string(v.link) { + return true + } + } + + return false +} + // // // Public interface From 8c89af620080cccc7bd1d6d765e566ec2cdd4f19 Mon Sep 17 00:00:00 2001 From: choueric Date: Fri, 9 Jun 2017 10:20:58 +0800 Subject: [PATCH 2/3] add 'notesRecord' to check footnote existence fast It is necessary to use vector for 'notes' instead of map to keep footnotes ordered. But checking for presence in a vector is not efficient. So add a map variable 'notesRecord' to tackle this problem. --- inline.go | 2 ++ markdown.go | 13 +++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/inline.go b/inline.go index eff1383..ca22af5 100644 --- a/inline.go +++ b/inline.go @@ -488,6 +488,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { } p.notes = append(p.notes, ref) + p.notesRecord[string(ref.link)] = true link = ref.link title = ref.title @@ -501,6 +502,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { if t == linkDeferredFootnote && !p.isFootnote(lr) { lr.noteId = len(p.notes) + 1 p.notes = append(p.notes, lr) + p.notesRecord[string(lr.link)] = true } // keep link and title from reference diff --git a/markdown.go b/markdown.go index c82be6b..ee03fda 100644 --- a/markdown.go +++ b/markdown.go @@ -218,7 +218,8 @@ type parser struct { // Footnotes need to be ordered as well as available to quickly check for // presence. If a ref is also a footnote, it's stored both in refs and here // in notes. Slice is nil if footnotes not enabled. - notes []*reference + notes []*reference + notesRecord map[string]bool } func (p *parser) getRef(refid string) (ref *reference, found bool) { @@ -242,13 +243,8 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) { } func (p *parser) isFootnote(ref *reference) bool { - for _, v := range p.notes { - if string(ref.link) == string(v.link) { - return true - } - } - - return false + _, ok := p.notesRecord[string(ref.link)] + return ok } // @@ -386,6 +382,7 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte { if extensions&EXTENSION_FOOTNOTES != 0 { p.notes = make([]*reference, 0) + p.notesRecord = make(map[string]bool) } first := firstPass(p, input) From 5b2fb1b893850a20a483145b676af75eaad51a5d Mon Sep 17 00:00:00 2001 From: choueric Date: Sat, 10 Jun 2017 22:24:27 +0800 Subject: [PATCH 3/3] use map[string]struct{} as a set. Use map[string]struct{} instead of map[string]bool to implement a set and reduce memory space. --- inline.go | 4 ++-- markdown.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/inline.go b/inline.go index ca22af5..4483b8f 100644 --- a/inline.go +++ b/inline.go @@ -488,7 +488,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { } p.notes = append(p.notes, ref) - p.notesRecord[string(ref.link)] = true + p.notesRecord[string(ref.link)] = struct{}{} link = ref.link title = ref.title @@ -502,7 +502,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { if t == linkDeferredFootnote && !p.isFootnote(lr) { lr.noteId = len(p.notes) + 1 p.notes = append(p.notes, lr) - p.notesRecord[string(lr.link)] = true + p.notesRecord[string(lr.link)] = struct{}{} } // keep link and title from reference diff --git a/markdown.go b/markdown.go index ee03fda..1722a73 100644 --- a/markdown.go +++ b/markdown.go @@ -219,7 +219,7 @@ type parser struct { // presence. If a ref is also a footnote, it's stored both in refs and here // in notes. Slice is nil if footnotes not enabled. notes []*reference - notesRecord map[string]bool + notesRecord map[string]struct{} } func (p *parser) getRef(refid string) (ref *reference, found bool) { @@ -382,7 +382,7 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte { if extensions&EXTENSION_FOOTNOTES != 0 { p.notes = make([]*reference, 0) - p.notesRecord = make(map[string]bool) + p.notesRecord = make(map[string]struct{}) } first := firstPass(p, input)