Skip to content

Commit

Permalink
Merge pull request #799 from aycabta/prevent-making-link-inside-code-tag
Browse files Browse the repository at this point in the history
Disable other notations in <code> tags
  • Loading branch information
aycabta authored Mar 16, 2021
2 parents 45ebbdd + b8baa9a commit 3f2bd42
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 33 deletions.
10 changes: 8 additions & 2 deletions lib/rdoc/markup/attr_span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ class RDoc::Markup::AttrSpan
##
# Creates a new AttrSpan for +length+ characters

def initialize(length)
def initialize(length, exclusive)
@attrs = Array.new(length, 0)
@exclusive = exclusive
end

##
# Toggles +bits+ from +start+ to +length+
def set_attrs(start, length, bits)
updated = false
for i in start ... (start+length)
@attrs[i] |= bits
if (@exclusive & @attrs[i]) == 0 || (@exclusive & bits) != 0
@attrs[i] |= bits
updated = true
end
end
updated
end

##
Expand Down
121 changes: 93 additions & 28 deletions lib/rdoc/markup/attribute_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ class RDoc::Markup::AttributeManager

attr_reader :regexp_handlings

##
# A bits of exclusive maps
attr_reader :exclusive_bitmap

##
# Creates a new attribute manager that understands bold, emphasized and
# teletype text.
Expand All @@ -68,17 +72,18 @@ def initialize
@protectable = %w[<]
@regexp_handlings = []
@word_pair_map = {}
@exclusive_bitmap = 0
@attributes = RDoc::Markup::Attributes.new

add_word_pair "*", "*", :BOLD
add_word_pair "_", "_", :EM
add_word_pair "+", "+", :TT
add_word_pair "*", "*", :BOLD, true
add_word_pair "_", "_", :EM, true
add_word_pair "+", "+", :TT, true

add_html "em", :EM
add_html "i", :EM
add_html "b", :BOLD
add_html "tt", :TT
add_html "code", :TT
add_html "em", :EM, true
add_html "i", :EM, true
add_html "b", :BOLD, true
add_html "tt", :TT, true
add_html "code", :TT, true
end

##
Expand Down Expand Up @@ -122,29 +127,67 @@ def copy_string(start_pos, end_pos)
res
end

def exclusive?(attr)
(attr & @exclusive_bitmap) != 0
end

NON_PRINTING_START = "\1" # :nodoc:
NON_PRINTING_END = "\2" # :nodoc:

##
# Map attributes like <b>text</b>to the sequence
# \001\002<char>\001\003<char>, where <char> is a per-attribute specific
# character

def convert_attrs(str, attrs)
def convert_attrs(str, attrs, exclusive = false)
convert_attrs_matching_word_pairs(str, attrs, exclusive)
convert_attrs_word_pair_map(str, attrs, exclusive)
end

def convert_attrs_matching_word_pairs(str, attrs, exclusive)
# first do matching ones
tags = @matching_word_pairs.keys.join("")
tags = @matching_word_pairs.select { |start, bitmap|
if exclusive && exclusive?(bitmap)
true
elsif !exclusive && !exclusive?(bitmap)
true
else
false
end
}.keys
return if tags.empty?
all_tags = @matching_word_pairs.keys

re = /(^|\W)([#{tags}])([#\\]?[\w:.\/-]+?\S?)\2(\W|$)/
re = /(^|\W|[#{all_tags.join("")}])([#{tags.join("")}])(\2*[#\\]?[\w:.\/\[\]-]+?\S?)\2(?!\2)([#{all_tags.join("")}]|\W|$)/

1 while str.gsub!(re) do
1 while str.gsub!(re) { |orig|
attr = @matching_word_pairs[$2]
attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr)
$1 + NULL * $2.length + $3 + NULL * $2.length + $4
end
attr_updated = attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr)
if attr_updated
$1 + NULL * $2.length + $3 + NULL * $2.length + $4
else
$1 + NON_PRINTING_START + $2 + NON_PRINTING_END + $3 + NON_PRINTING_START + $2 + NON_PRINTING_END + $4
end
}
str.delete!(NON_PRINTING_START + NON_PRINTING_END)
end

def convert_attrs_word_pair_map(str, attrs, exclusive)
# then non-matching
unless @word_pair_map.empty? then
@word_pair_map.each do |regexp, attr|
str.gsub!(regexp) {
attrs.set_attrs($`.length + $1.length, $2.length, attr)
NULL * $1.length + $2 + NULL * $3.length
if !exclusive
next if exclusive?(attr)
else
next if !exclusive?(attr)
end
1 while str.gsub!(regexp) { |orig|
updated = attrs.set_attrs($`.length + $1.length, $2.length, attr)
if updated
NULL * $1.length + $2 + NULL * $3.length
else
orig
end
}
end
end
Expand All @@ -153,10 +196,18 @@ def convert_attrs(str, attrs)
##
# Converts HTML tags to RDoc attributes

def convert_html(str, attrs)
tags = @html_tags.keys.join '|'
def convert_html(str, attrs, exclusive = false)
tags = @html_tags.select { |start, bitmap|
if exclusive && exclusive?(bitmap)
true
elsif !exclusive && !exclusive?(bitmap)
true
else
false
end
}.keys.join '|'

1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) {
1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) { |orig|
attr = @html_tags[$1.downcase]
html_length = $1.length + 2
seq = NULL * html_length
Expand All @@ -168,8 +219,13 @@ def convert_html(str, attrs)
##
# Converts regexp handling sequences to RDoc attributes

def convert_regexp_handlings str, attrs
def convert_regexp_handlings str, attrs, exclusive = false
@regexp_handlings.each do |regexp, attribute|
if exclusive
next if !exclusive?(attribute)
else
next if exclusive?(attribute)
end
str.scan(regexp) do
capture = $~.size == 1 ? 0 : 1

Expand Down Expand Up @@ -205,7 +261,7 @@ def unmask_protected_sequences
#
# am.add_word_pair '*', '*', :BOLD

def add_word_pair(start, stop, name)
def add_word_pair(start, stop, name, exclusive = false)
raise ArgumentError, "Word flags may not start with '<'" if
start[0,1] == '<'

Expand All @@ -220,6 +276,8 @@ def add_word_pair(start, stop, name)

@protectable << start[0,1]
@protectable.uniq!

@exclusive_bitmap |= bitmap if exclusive
end

##
Expand All @@ -228,8 +286,10 @@ def add_word_pair(start, stop, name)
#
# am.add_html 'em', :EM

def add_html(tag, name)
@html_tags[tag.downcase] = @attributes.bitmap_for name
def add_html(tag, name, exclusive = false)
bitmap = @attributes.bitmap_for name
@html_tags[tag.downcase] = bitmap
@exclusive_bitmap |= bitmap if exclusive
end

##
Expand All @@ -238,8 +298,10 @@ def add_html(tag, name)
#
# @am.add_regexp_handling(/((https?:)\S+\w)/, :HYPERLINK)

def add_regexp_handling pattern, name
@regexp_handlings << [pattern, @attributes.bitmap_for(name)]
def add_regexp_handling pattern, name, exclusive = false
bitmap = @attributes.bitmap_for(name)
@regexp_handlings << [pattern, bitmap]
@exclusive_bitmap |= bitmap if exclusive
end

##
Expand All @@ -250,8 +312,11 @@ def flow str

mask_protected_sequences

@attrs = RDoc::Markup::AttrSpan.new @str.length
@attrs = RDoc::Markup::AttrSpan.new @str.length, @exclusive_bitmap

convert_attrs @str, @attrs, true
convert_html @str, @attrs, true
convert_regexp_handlings @str, @attrs, true
convert_attrs @str, @attrs
convert_html @str, @attrs
convert_regexp_handlings @str, @attrs
Expand Down
25 changes: 22 additions & 3 deletions test/rdoc/test_rdoc_markup_attribute_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,22 +172,25 @@ def test_combined

def test_convert_attrs
str = '+foo+'.dup
attrs = RDoc::Markup::AttrSpan.new str.length
attrs = RDoc::Markup::AttrSpan.new str.length, @am.exclusive_bitmap

@am.convert_attrs str, attrs, true
@am.convert_attrs str, attrs

assert_equal "\000foo\000", str

str = '+:foo:+'.dup
attrs = RDoc::Markup::AttrSpan.new str.length
attrs = RDoc::Markup::AttrSpan.new str.length, @am.exclusive_bitmap

@am.convert_attrs str, attrs, true
@am.convert_attrs str, attrs

assert_equal "\000:foo:\000", str

str = '+x-y+'.dup
attrs = RDoc::Markup::AttrSpan.new str.length
attrs = RDoc::Markup::AttrSpan.new str.length, @am.exclusive_bitmap

@am.convert_attrs str, attrs, true
@am.convert_attrs str, attrs

assert_equal "\000x-y\000", str
Expand Down Expand Up @@ -243,6 +246,22 @@ def test_escapes
output('unhandled <p>tag</p> unchanged')
end

def test_exclude_tag
assert_equal '<CODE>aaa</CODE>[:symbol]', output('+aaa+[:symbol]')
assert_equal '<CODE>aaa[:symbol]</CODE>', output('+aaa[:symbol]+')
assert_equal 'aaa[:symbol]', output('aaa[:symbol]')
assert_equal '<B><CODE>index</CODE></B>', output('<b><tt>index</tt></b>')
end

def test_exclude_tag_flow
assert_equal [@tt_on, "aaa", @tt_off, "[:symbol]"],
@am.flow("+aaa+[:symbol]")
assert_equal [@tt_on, "aaa[:symbol]", @tt_off],
@am.flow("+aaa[:symbol]+")
assert_equal ["aaa[:symbol]"],
@am.flow("aaa[:symbol]")
end

def test_html_like_em_bold
assert_equal ["cat ", @em_on, "and ", @em_to_bold, "dog", @bold_off],
@am.flow("cat <i>and </i><b>dog</b>")
Expand Down
11 changes: 11 additions & 0 deletions test/rdoc/test_rdoc_markup_to_html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,17 @@ def test_convert_TIDYLINK_irc
assert_equal "\n<p><a href=\"irc://irc.freenode.net/#ruby-lang\">ruby-lang</a></p>\n", result
end

def test_convert_with_exclude_tag
assert_equal "\n<p><code>aaa</code>[:symbol]</p>\n", @to.convert('+aaa+[:symbol]')
assert_equal "\n<p><code>aaa[:symbol]</code></p>\n", @to.convert('+aaa[:symbol]+')
assert_equal "\n<p><a href=\":symbol\">aaa</a></p>\n", @to.convert('aaa[:symbol]')
end

def test_convert_underscore_adjacent_to_code
assert_equal "\n<p><code>aaa</code>_</p>\n", @to.convert(%q{+aaa+_})
assert_equal "\n<p>`<code>i386-mswin32_</code><em>MSRTVERSION</em>&#39;</p>\n", @to.convert(%q{`+i386-mswin32_+_MSRTVERSION_'})
end

def test_gen_url
assert_equal '<a href="example">example</a>',
@to.gen_url('link:example', 'example')
Expand Down

0 comments on commit 3f2bd42

Please sign in to comment.