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 #3788 allow closing square bracket in reftext of inline anchor shorthand to be escaped #3792

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
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Expand Up @@ -44,6 +44,7 @@ Bug Fixes::
* Use custom init function for highlight.js to select the correct `code` elements (#3761)
* Fix resolved value of :to_dir when both :to_file and :to_dir options are set to absolute paths (#3778)
* Restore label in front of each bibliography entry in DocBook output that was dropped by fix for #3085 (#3782)
* Allow closing square bracket in reftext of inline anchor shorthand to be escaped (#3788)

Compliance::

Expand Down
6 changes: 5 additions & 1 deletion lib/asciidoctor/parser.rb
Expand Up @@ -1131,7 +1131,10 @@ def self.catalog_callouts(text, document)
#
# Returns nothing
def self.catalog_inline_anchor id, reftext, node, location, doc = node.document
reftext = doc.sub_attributes reftext if reftext && (reftext.include? ATTR_REF_HEAD)
if reftext
reftext = reftext.gsub '\]', ']' if reftext.include? ']'
reftext = doc.sub_attributes reftext if reftext.include? ATTR_REF_HEAD
end
unless doc.register :refs, [id, (Inline.new node, :anchor, reftext, type: :ref, id: id)]
location = location.cursor if Reader === location
logger.warn message_with_context %(id assigned to anchor already in use: #{id}), source_location: location
Expand All @@ -1150,6 +1153,7 @@ def self.catalog_inline_anchors text, block, document, reader
text.scan InlineAnchorScanRx do
if (id = $1)
if (reftext = $2)
reftext = reftext.gsub '\]', ']' if reftext.include? ']'
next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
end
else
Expand Down
6 changes: 3 additions & 3 deletions lib/asciidoctor/rx.rb
Expand Up @@ -438,13 +438,13 @@ module Rx; end
# anchor:idname[]
# anchor:idname[Reference Text]
#
InlineAnchorRx = /(\\)?(?:\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]|anchor:([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)\[(?:\]|(#{CC_ANY}*?[^\\])\]))/
InlineAnchorRx = /(\\)?(?:\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}*?[^\\]))?\]\]|anchor:([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)\[(?:\]|(#{CC_ANY}*?[^\\])\]))/

# Scans for a non-escaped anchor (i.e., id + optional reference text) in the flow of text.
InlineAnchorScanRx = /(?:^|[^\\\[])\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]|(?:^|[^\\])anchor:([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)\[(?:\]|(#{CC_ANY}*?[^\\])\])/
InlineAnchorScanRx = /(?:^|[^\\\[])\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}*?[^\\]))?\]\]|(?:^|[^\\])anchor:([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)\[(?:\]|(#{CC_ANY}*?[^\\])\])/

# Scans for a leading, non-escaped anchor (i.e., id + optional reference text).
LeadingInlineAnchorRx = /^\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}+?))?\]\]/
LeadingInlineAnchorRx = /^\[\[([#{CC_ALPHA}_:][#{CC_WORD}\-:.]*)(?:, *(#{CC_ANY}*?[^\\]))?\]\]/

# Matches a bibliography anchor at the start of the list item text (in a bibliography list).
#
Expand Down
4 changes: 3 additions & 1 deletion lib/asciidoctor/substitutors.rb
Expand Up @@ -719,7 +719,9 @@ def sub_macros text

# NOTE reftext is only relevant for DocBook output; used as value of xreflabel attribute
if (id = $2)
reftext = $3
if (reftext = $3) && (reftext.include? R_SB)
reftext = reftext.gsub ESC_R_SB, R_SB
end
else
id = $4
if (reftext = $5) && (reftext.include? R_SB)
Expand Down
38 changes: 27 additions & 11 deletions test/links_test.rb
Expand Up @@ -396,20 +396,36 @@
end
end

test 'unescapes square bracket in reftext of anchor macro' do
input = <<~'EOS'
see <<foo>>
test 'unescapes square bracket at end of reftext in inline anchor' do
%w([[foo,ba[r\]]] anchor:foo[ba[r\]]).each do |variation|
input = <<~EOS
see <<foo>>

anchor:foo[b[a\]r]tex
EOS
result = convert_string_to_embedded input
assert_includes result, 'see <a href="#foo">b[a]r</a>'
#{variation}content
EOS
result = convert_string_to_embedded input
assert_includes result, 'see <a href="#foo">ba[r]</a>'
end
end

test 'unescapes square bracket in reftext of anchor macro in DocBook output' do
input = 'anchor:foo[b[a\]r]'
result = convert_inline_string input, backend: :docbook
assert_equal '<anchor xml:id="foo" xreflabel="b[a]r"/>', result
test 'unescapes square bracket in middle of reftext in inline anchor' do
%w([[foo,b[a\]r]] anchor:foo[b[a\]r]).each do |variation|
input = <<~EOS
see <<foo>>

#{variation}content
EOS
result = convert_string_to_embedded input
assert_includes result, 'see <a href="#foo">b[a]r</a>'
end
end

test 'unescapes square bracket in reftext of inline anchor in DocBook output' do
%w([[foo,b[a\]r]] anchor:foo[b[a\]r]).each do |variation|
input = %(#{variation}text)
result = convert_inline_string input, backend: :docbook
assert_equal '<anchor xml:id="foo" xreflabel="b[a]r"/>text', result
end
end

test 'xref using angled bracket syntax' do
Expand Down
38 changes: 38 additions & 0 deletions test/lists_test.rb
Expand Up @@ -839,6 +839,24 @@
assert_xpath '(//p)[1]/a[@href="#mount-evans"][text()="Mount Evans"]', output, 1
end

test 'should allow closing square bracket in anchor at start of unordered list item text to be escaped' do
input = <<~'EOS'
The highest peak in the Front Range is <<grays-peak>>, which tops <<mount-evans>> by just a few feet.
* [[mount-evans,[Mount\] Evans]]At 14,271 feet, Mount Evans is the highest summit of the Chicago Peaks in the Front Range of the Rocky Mountains.
* [[grays-peak,Grays [Peak\]]]
Grays Peak rises to 14,278 feet, making it the highest summit in the Front Range of the Rocky Mountains.
EOS

doc = document_from_string input
refs = doc.catalog[:refs]
assert refs.key?('mount-evans')
assert refs.key?('grays-peak')
output = doc.convert standalone: false
assert_xpath '(//p)[1]/a[@href="#grays-peak"][text()="Grays [Peak]"]', output, 1
assert_xpath '(//p)[1]/a[@href="#mount-evans"][text()="[Mount] Evans"]', output, 1
end

test 'should discover anchor at start of ordered list item text and register it as a reference' do
input = <<~'EOS'
This is a cross-reference to <<step-2>>.
Expand All @@ -859,6 +877,26 @@
assert_xpath '(//p)[1]/a[@href="#step-4"][text()="Step 4"]', output, 1
end

test 'should allow closing square bracket in anchor at start of ordered list item text to be escaped' do
input = <<~'EOS'
This is a cross-reference to <<step-2>>.
This is a cross-reference to <<step-3b>>.
. Ordered list, item 1, without anchor
. [[step-2,Step [2\]]]Ordered list, item 2, with anchor
. Ordered list, item 3, without anchor
.. [[step-3b,Step [3\]b]]Ordered list, item 3b, with anchor
EOS

doc = document_from_string input
refs = doc.catalog[:refs]
assert refs.key?('step-2')
assert refs.key?('step-3b')
output = doc.convert standalone: false
assert_xpath '(//p)[1]/a[@href="#step-2"][text()="Step [2]"]', output, 1
assert_xpath '(//p)[1]/a[@href="#step-3b"][text()="Step [3]b"]', output, 1
end

test 'should discover anchor at start of callout list item text and register it as a reference' do
input = <<~'EOS'
This is a cross-reference to <<url-mapping>>.
Expand Down
23 changes: 23 additions & 0 deletions test/tables_test.rb
Expand Up @@ -1398,6 +1398,29 @@
assert_xpath '(//table/tbody/tr)[2]//th//a[@id="grays-peak"]', output, 1
end

test 'should allow closing square bracket in anchor at start of table cell to be escaped' do
input = <<~'EOS'
The highest peak in the Front Range is <<grays-peak>>, which tops <<mount-evans>> by just a few feet.
[cols="1s,1"]
|===
|[[mount-evans,[Mount\] Evans]]Mount Evans
|14,271 feet
h|[[grays-peak,Grays [Peak\]]]
Grays Peak
|14,278 feet
|===
EOS
doc = document_from_string input
refs = doc.catalog[:refs]
assert refs.key?('mount-evans')
assert refs.key?('grays-peak')
output = doc.convert standalone: false
assert_xpath '(//p)[1]/a[@href="#grays-peak"][text()="Grays [Peak]"]', output, 1
assert_xpath '(//p)[1]/a[@href="#mount-evans"][text()="[Mount] Evans"]', output, 1
end

test 'footnotes should not be shared between an AsciiDoc table cell and the main document' do
input = <<~'EOS'
|===
Expand Down