diff --git a/lib/asciidoctor/rx.rb b/lib/asciidoctor/rx.rb index f571e3f79e..cceca56175 100644 --- a/lib/asciidoctor/rx.rb +++ b/lib/asciidoctor/rx.rb @@ -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. # diff --git a/lib/asciidoctor/substitutors.rb b/lib/asciidoctor/substitutors.rb index 47ffc5e7d8..5f45f797ee 100644 --- a/lib/asciidoctor/substitutors.rb +++ b/lib/asciidoctor/substitutors.rb @@ -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 @@ -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 diff --git a/test/substitutions_test.rb b/test/substitutions_test.rb index 858796c427..63507ea689 100644 --- a/test/substitutions_test.rb +++ b/test/substitutions_test.rb @@ -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 [1].), 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[2] 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 [1].), 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[2] 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