Skip to content

Commit

Permalink
resolves asciidoctor#3893 allow footnotes in footnotes
Browse files Browse the repository at this point in the history
* Add 2 tests: basic support of footnotes in footnotes
  and support for escaped ]
* Change InlineFootnoteMacroRx so that it allows recursive footnotes
* Move footnote processing code from sub_macros to separate function
  sub_footnote, call it recursively on matches. It is important not
  to call sub_macros recursively as this results to multiple expansion
  of some macros (e.g. URL)
  • Loading branch information
hedrok committed Dec 28, 2020
1 parent 47c5bf2 commit 2c02055
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 42 deletions.
2 changes: 1 addition & 1 deletion lib/asciidoctor/rx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ module Rx; end
# footnoteref:[id,text] (legacy)
# footnoteref:[id] (legacy)
#
InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!<\/a>)/m
InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|((([^\]]|\\\])*\g<0>)?#{CC_ALL}*?[^\\]))\](?!<\/a>)/m

# Matches an image or icon inline macro.
#
Expand Down
98 changes: 57 additions & 41 deletions lib/asciidoctor/substitutors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,62 @@ def sub_replacements text
text
end

# Public: Substitute replacement footnote
#
# text - The String text to process with footnote
#
# returns the [String] text with footnote substituted
def sub_footnote text
doc = @document
text = text.gsub InlineFootnoteMacroRx do
# honor the escape
next $&.slice 1, $&.length if $&.start_with? RS

# footnoteref
if $1
if $3
id, text = $3.split ',', 2
logger.warn %(found deprecated footnoteref macro: #{$&}; use footnote macro with target instead) unless doc.compat_mode
else
next $&
end
# footnote
else
id = $2
text = $3
end

fn = nil
if id
if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
index, text = footnote.index, footnote.text
type, target, id = :xref, id, nil
elsif text
text = restore_passthroughs(normalize_text text, true, true)
index = doc.counter('footnote-number')
fn = Document::Footnote.new(index, id, text)
doc.register(:footnotes, fn)
type, target = :ref, nil
else
logger.warn %(invalid footnote reference: #{id})
type, target, text, id = :xref, id, id, nil
end
elsif text
text = restore_passthroughs(normalize_text text, true, true)
index = doc.counter('footnote-number')
fn = Document::Footnote.new(index, id, text)
doc.register(:footnotes, fn)
type = target = nil
else
next $&
end
if fn
fn.text = sub_footnote fn.text
end
Inline.new(self, :footnote, text, attributes: { 'index' => index }, id: id, target: target, type: type).convert
end
end

# Public: Substitute inline macros (e.g., links, images, etc)
#
# Replace inline macros, which may span multiple lines, in the provided text
Expand Down Expand Up @@ -830,47 +886,7 @@ def sub_macros text
end

if found_macroish && (text.include? 'tnote')
text = text.gsub InlineFootnoteMacroRx do
# honor the escape
next $&.slice 1, $&.length if $&.start_with? RS

# footnoteref
if $1
if $3
id, text = $3.split ',', 2
logger.warn %(found deprecated footnoteref macro: #{$&}; use footnote macro with target instead) unless doc.compat_mode
else
next $&
end
# footnote
else
id = $2
text = $3
end

if id
if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
index, text = footnote.index, footnote.text
type, target, id = :xref, id, nil
elsif text
text = restore_passthroughs(normalize_text text, true, true)
index = doc.counter('footnote-number')
doc.register(:footnotes, Document::Footnote.new(index, id, text))
type, target = :ref, nil
else
logger.warn %(invalid footnote reference: #{id})
type, target, text, id = :xref, id, id, nil
end
elsif text
text = restore_passthroughs(normalize_text text, true, true)
index = doc.counter('footnote-number')
doc.register(:footnotes, Document::Footnote.new(index, id, text))
type = target = nil
else
next $&
end
Inline.new(self, :footnote, text, attributes: { 'index' => index }, id: id, target: target, type: type).convert
end
text = sub_footnote text
end

text
Expand Down
30 changes: 30 additions & 0 deletions test/substitutions_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2298,5 +2298,35 @@
block.commit_subs
assert_equal [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements], block.subs
end

test 'should parse footnote inside footnote' do
para = block_from_string('Sentence text footnote:[An examplefootnote:[Footnote to footnote.] footnote.].')
assert_equal %(Sentence text <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>.), para.sub_macros(para.source)
assert_equal 2, para.document.catalog[:footnotes].size
footnote = para.document.catalog[:footnotes][0]
assert_equal 1, footnote.index
assert_nil footnote.id
assert_equal 'An example<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup> footnote.', footnote.text

footnote = para.document.catalog[:footnotes][1]
assert_equal 2, footnote.index
assert_nil footnote.id
assert_equal 'Footnote to footnote.', footnote.text
end

test 'should accept escaped square bracket before footnote inside footnote' do
para = block_from_string('Sentence text footnote:[An \] examplefootnote:[Footnote to footnote.] footnote.].')
assert_equal %(Sentence text <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>.), para.sub_macros(para.source)
assert_equal 2, para.document.catalog[:footnotes].size
footnote = para.document.catalog[:footnotes][0]
assert_equal 1, footnote.index
assert_nil footnote.id
assert_equal 'An ] example<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup> footnote.', footnote.text

footnote = para.document.catalog[:footnotes][1]
assert_equal 2, footnote.index
assert_nil footnote.id
assert_equal 'Footnote to footnote.', footnote.text
end
end
end

0 comments on commit 2c02055

Please sign in to comment.