Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolves #3893 allow footnotes in footnotes #3894

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/asciidoctor/rx.rb
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
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
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