diff options
| author | Dan Allen <dan.j.allen@gmail.com> | 2022-07-05 01:55:45 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-05 01:55:45 -0600 |
| commit | 7822fbe9223ca81e9895f6132b2274e89f26c623 (patch) | |
| tree | 634d315adf29cfe5df18647d2e87fffe7afb67de | |
| parent | 3c575b9de21790d3b812c74948c42eb6470ec02b (diff) | |
resolves #4306 disallow the use of the left square bracket in an attribute list on formatted text (PR #4307)
| -rw-r--r-- | CHANGELOG.adoc | 1 | ||||
| -rw-r--r-- | lib/asciidoctor.rb | 36 | ||||
| -rw-r--r-- | lib/asciidoctor/rx.rb | 6 | ||||
| -rw-r--r-- | test/substitutions_test.rb | 5 |
4 files changed, 28 insertions, 20 deletions
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 707a6fb1..0fec8b7f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -38,6 +38,7 @@ Compliance:: * Don't promote level-0 special section at start of document to document title (#4151) * Disallow the use of dot (`.`) in the name of a named element attribute (#4147) + * Disallow the use of the left square bracket in an attribute list on formatted text (#4306) Improvements:: diff --git a/lib/asciidoctor.rb b/lib/asciidoctor.rb index 581c8647..45c28f6a 100644 --- a/lib/asciidoctor.rb +++ b/lib/asciidoctor.rb @@ -436,6 +436,8 @@ module Asciidoctor CC_WORD = CG_WORD = '\p{Word}' end + QuoteAttributeListRxt = %(\\[([^\\[\\]]+)\\]) + QUOTE_SUBS = {}.tap do |accum| # unconstrained quotes:: can appear anywhere # constrained quotes:: must be bordered by non-word characters @@ -443,46 +445,46 @@ module Asciidoctor # the order in which they are replaced is important accum[false] = normal = [ # **strong** - [:strong, :unconstrained, /\\?(?:\[([^\]]+)\])?\*\*(#{CC_ALL}+?)\*\*/m], + [:strong, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?\*\*(#{CC_ALL}+?)\*\*/m], # *strong* - [:strong, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?\*(\S|\S#{CC_ALL}*?\S)\*(?!#{CG_WORD})/m], + [:strong, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?\*(\S|\S#{CC_ALL}*?\S)\*(?!#{CG_WORD})/m], # "`double-quoted`" - [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?"`(\S|\S#{CC_ALL}*?\S)`"(?!#{CG_WORD})/m], + [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?"`(\S|\S#{CC_ALL}*?\S)`"(?!#{CG_WORD})/m], # '`single-quoted`' - [:single, :constrained, /(^|[^#{CC_WORD};:`}])(?:\[([^\]]+)\])?'`(\S|\S#{CC_ALL}*?\S)`'(?!#{CG_WORD})/m], + [:single, :constrained, /(^|[^#{CC_WORD};:`}])(?:#{QuoteAttributeListRxt})?'`(\S|\S#{CC_ALL}*?\S)`'(?!#{CG_WORD})/m], # ``monospaced`` - [:monospaced, :unconstrained, /\\?(?:\[([^\]]+)\])?``(#{CC_ALL}+?)``/m], + [:monospaced, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?``(#{CC_ALL}+?)``/m], # `monospaced` - [:monospaced, :constrained, /(^|[^#{CC_WORD};:"'`}])(?:\[([^\]]+)\])?`(\S|\S#{CC_ALL}*?\S)`(?![#{CC_WORD}"'`])/m], + [:monospaced, :constrained, /(^|[^#{CC_WORD};:"'`}])(?:#{QuoteAttributeListRxt})?`(\S|\S#{CC_ALL}*?\S)`(?![#{CC_WORD}"'`])/m], # __emphasis__ - [:emphasis, :unconstrained, /\\?(?:\[([^\]]+)\])?__(#{CC_ALL}+?)__/m], + [:emphasis, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?__(#{CC_ALL}+?)__/m], # _emphasis_ - [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m], + [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m], # ##mark## (referred to in AsciiDoc.py as unquoted) - [:mark, :unconstrained, /\\?(?:\[([^\]]+)\])?##(#{CC_ALL}+?)##/m], + [:mark, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?##(#{CC_ALL}+?)##/m], # #mark# (referred to in AsciiDoc.py as unquoted) - [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:\[([^\]]+)\])?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m], + [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:#{QuoteAttributeListRxt})?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m], # ^superscript^ - [:superscript, :unconstrained, /\\?(?:\[([^\]]+)\])?\^(\S+?)\^/], + [:superscript, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?\^(\S+?)\^/], # ~subscript~ - [:subscript, :unconstrained, /\\?(?:\[([^\]]+)\])?~(\S+?)~/], + [:subscript, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?~(\S+?)~/], ] accum[true] = compat = normal.drop 0 # ``quoted'' - compat[2] = [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?``(\S|\S#{CC_ALL}*?\S)''(?!#{CG_WORD})/m] + compat[2] = [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?``(\S|\S#{CC_ALL}*?\S)''(?!#{CG_WORD})/m] # `quoted' - compat[3] = [:single, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?`(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m] + compat[3] = [:single, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?`(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m] # ++monospaced++ - compat[4] = [:monospaced, :unconstrained, /\\?(?:\[([^\]]+)\])?\+\+(#{CC_ALL}+?)\+\+/m] + compat[4] = [:monospaced, :unconstrained, /\\?(?:#{QuoteAttributeListRxt})?\+\+(#{CC_ALL}+?)\+\+/m] # +monospaced+ - compat[5] = [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?\+(\S|\S#{CC_ALL}*?\S)\+(?!#{CG_WORD})/m] + compat[5] = [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?\+(\S|\S#{CC_ALL}*?\S)\+(?!#{CG_WORD})/m] # #unquoted# #compat[8] = [:unquoted, *compat[8][1..-1]] # ##unquoted## #compat[9] = [:unquoted, *compat[9][1..-1]] # 'emphasis' - compat.insert 3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?'(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m] + compat.insert 3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:#{QuoteAttributeListRxt})?'(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m] end # NOTE order of replacements is significant diff --git a/lib/asciidoctor/rx.rb b/lib/asciidoctor/rx.rb index 6cc378ea..5e73b13f 100644 --- a/lib/asciidoctor/rx.rb +++ b/lib/asciidoctor/rx.rb @@ -573,8 +573,8 @@ module Asciidoctor # # NOTE we always capture the attributes so we know when to use compatible (i.e., legacy) behavior InlinePassRx = { - false => ['+', '`', /(^|[^#{CC_WORD};:])(?:\[([^\]]+)\])?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m], - true => ['`', nil, /(^|[^`#{CC_WORD}])(?:\[([^\]]+)\])?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m], + false => ['+', '`', /(^|[^#{CC_WORD};:])(?:#{QuoteAttributeListRxt})?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m], + true => ['`', nil, /(^|[^`#{CC_WORD}])(?:#{QuoteAttributeListRxt})?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m], } # Matches an inline plus passthrough spanning multiple lines, but only when it occurs directly @@ -595,7 +595,7 @@ module Asciidoctor # pass:quotes[text] # # NOTE we have to support an empty pass:[] for compatibility with AsciiDoc.py - InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m + InlinePassMacroRx = /(?:(?:(\\?)#{QuoteAttributeListRxt})?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m # Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines. # diff --git a/test/substitutions_test.rb b/test/substitutions_test.rb index b4b4ab09..f75abfa4 100644 --- a/test/substitutions_test.rb +++ b/test/substitutions_test.rb @@ -203,6 +203,11 @@ context 'Substitutions' do assert_equal '<span class="statement">a few words</span>', para.sub_quotes(para.source) end + test 'does not recognize attribute list with left square bracket on formatted text' do + para = block_from_string 'key: [ *before [.redacted]#redacted# after* ]' + assert_equal 'key: [ <strong>before <span class="redacted">redacted</span> after</strong> ]', para.sub_quotes(para.source) + end + test 'single-line constrained strong string' do para = block_from_string '*a few strong words*' assert_equal '<strong>a few strong words</strong>', para.sub_quotes(para.source) |
