Skip to content

Commit 3f2bd42

Browse files
authored
Merge pull request #799 from aycabta/prevent-making-link-inside-code-tag
Disable other notations in <code> tags
2 parents 45ebbdd + b8baa9a commit 3f2bd42

File tree

4 files changed

+134
-33
lines changed

4 files changed

+134
-33
lines changed

lib/rdoc/markup/attr_span.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@ class RDoc::Markup::AttrSpan
77
##
88
# Creates a new AttrSpan for +length+ characters
99

10-
def initialize(length)
10+
def initialize(length, exclusive)
1111
@attrs = Array.new(length, 0)
12+
@exclusive = exclusive
1213
end
1314

1415
##
1516
# Toggles +bits+ from +start+ to +length+
1617
def set_attrs(start, length, bits)
18+
updated = false
1719
for i in start ... (start+length)
18-
@attrs[i] |= bits
20+
if (@exclusive & @attrs[i]) == 0 || (@exclusive & bits) != 0
21+
@attrs[i] |= bits
22+
updated = true
23+
end
1924
end
25+
updated
2026
end
2127

2228
##

lib/rdoc/markup/attribute_manager.rb

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ class RDoc::Markup::AttributeManager
5858

5959
attr_reader :regexp_handlings
6060

61+
##
62+
# A bits of exclusive maps
63+
attr_reader :exclusive_bitmap
64+
6165
##
6266
# Creates a new attribute manager that understands bold, emphasized and
6367
# teletype text.
@@ -68,17 +72,18 @@ def initialize
6872
@protectable = %w[<]
6973
@regexp_handlings = []
7074
@word_pair_map = {}
75+
@exclusive_bitmap = 0
7176
@attributes = RDoc::Markup::Attributes.new
7277

73-
add_word_pair "*", "*", :BOLD
74-
add_word_pair "_", "_", :EM
75-
add_word_pair "+", "+", :TT
78+
add_word_pair "*", "*", :BOLD, true
79+
add_word_pair "_", "_", :EM, true
80+
add_word_pair "+", "+", :TT, true
7681

77-
add_html "em", :EM
78-
add_html "i", :EM
79-
add_html "b", :BOLD
80-
add_html "tt", :TT
81-
add_html "code", :TT
82+
add_html "em", :EM, true
83+
add_html "i", :EM, true
84+
add_html "b", :BOLD, true
85+
add_html "tt", :TT, true
86+
add_html "code", :TT, true
8287
end
8388

8489
##
@@ -122,29 +127,67 @@ def copy_string(start_pos, end_pos)
122127
res
123128
end
124129

130+
def exclusive?(attr)
131+
(attr & @exclusive_bitmap) != 0
132+
end
133+
134+
NON_PRINTING_START = "\1" # :nodoc:
135+
NON_PRINTING_END = "\2" # :nodoc:
136+
125137
##
126138
# Map attributes like <b>text</b>to the sequence
127139
# \001\002<char>\001\003<char>, where <char> is a per-attribute specific
128140
# character
129141

130-
def convert_attrs(str, attrs)
142+
def convert_attrs(str, attrs, exclusive = false)
143+
convert_attrs_matching_word_pairs(str, attrs, exclusive)
144+
convert_attrs_word_pair_map(str, attrs, exclusive)
145+
end
146+
147+
def convert_attrs_matching_word_pairs(str, attrs, exclusive)
131148
# first do matching ones
132-
tags = @matching_word_pairs.keys.join("")
149+
tags = @matching_word_pairs.select { |start, bitmap|
150+
if exclusive && exclusive?(bitmap)
151+
true
152+
elsif !exclusive && !exclusive?(bitmap)
153+
true
154+
else
155+
false
156+
end
157+
}.keys
158+
return if tags.empty?
159+
all_tags = @matching_word_pairs.keys
133160

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

136-
1 while str.gsub!(re) do
163+
1 while str.gsub!(re) { |orig|
137164
attr = @matching_word_pairs[$2]
138-
attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr)
139-
$1 + NULL * $2.length + $3 + NULL * $2.length + $4
140-
end
165+
attr_updated = attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr)
166+
if attr_updated
167+
$1 + NULL * $2.length + $3 + NULL * $2.length + $4
168+
else
169+
$1 + NON_PRINTING_START + $2 + NON_PRINTING_END + $3 + NON_PRINTING_START + $2 + NON_PRINTING_END + $4
170+
end
171+
}
172+
str.delete!(NON_PRINTING_START + NON_PRINTING_END)
173+
end
141174

175+
def convert_attrs_word_pair_map(str, attrs, exclusive)
142176
# then non-matching
143177
unless @word_pair_map.empty? then
144178
@word_pair_map.each do |regexp, attr|
145-
str.gsub!(regexp) {
146-
attrs.set_attrs($`.length + $1.length, $2.length, attr)
147-
NULL * $1.length + $2 + NULL * $3.length
179+
if !exclusive
180+
next if exclusive?(attr)
181+
else
182+
next if !exclusive?(attr)
183+
end
184+
1 while str.gsub!(regexp) { |orig|
185+
updated = attrs.set_attrs($`.length + $1.length, $2.length, attr)
186+
if updated
187+
NULL * $1.length + $2 + NULL * $3.length
188+
else
189+
orig
190+
end
148191
}
149192
end
150193
end
@@ -153,10 +196,18 @@ def convert_attrs(str, attrs)
153196
##
154197
# Converts HTML tags to RDoc attributes
155198

156-
def convert_html(str, attrs)
157-
tags = @html_tags.keys.join '|'
199+
def convert_html(str, attrs, exclusive = false)
200+
tags = @html_tags.select { |start, bitmap|
201+
if exclusive && exclusive?(bitmap)
202+
true
203+
elsif !exclusive && !exclusive?(bitmap)
204+
true
205+
else
206+
false
207+
end
208+
}.keys.join '|'
158209

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

171-
def convert_regexp_handlings str, attrs
222+
def convert_regexp_handlings str, attrs, exclusive = false
172223
@regexp_handlings.each do |regexp, attribute|
224+
if exclusive
225+
next if !exclusive?(attribute)
226+
else
227+
next if exclusive?(attribute)
228+
end
173229
str.scan(regexp) do
174230
capture = $~.size == 1 ? 0 : 1
175231

@@ -205,7 +261,7 @@ def unmask_protected_sequences
205261
#
206262
# am.add_word_pair '*', '*', :BOLD
207263

208-
def add_word_pair(start, stop, name)
264+
def add_word_pair(start, stop, name, exclusive = false)
209265
raise ArgumentError, "Word flags may not start with '<'" if
210266
start[0,1] == '<'
211267

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

221277
@protectable << start[0,1]
222278
@protectable.uniq!
279+
280+
@exclusive_bitmap |= bitmap if exclusive
223281
end
224282

225283
##
@@ -228,8 +286,10 @@ def add_word_pair(start, stop, name)
228286
#
229287
# am.add_html 'em', :EM
230288

231-
def add_html(tag, name)
232-
@html_tags[tag.downcase] = @attributes.bitmap_for name
289+
def add_html(tag, name, exclusive = false)
290+
bitmap = @attributes.bitmap_for name
291+
@html_tags[tag.downcase] = bitmap
292+
@exclusive_bitmap |= bitmap if exclusive
233293
end
234294

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

241-
def add_regexp_handling pattern, name
242-
@regexp_handlings << [pattern, @attributes.bitmap_for(name)]
301+
def add_regexp_handling pattern, name, exclusive = false
302+
bitmap = @attributes.bitmap_for(name)
303+
@regexp_handlings << [pattern, bitmap]
304+
@exclusive_bitmap |= bitmap if exclusive
243305
end
244306

245307
##
@@ -250,8 +312,11 @@ def flow str
250312

251313
mask_protected_sequences
252314

253-
@attrs = RDoc::Markup::AttrSpan.new @str.length
315+
@attrs = RDoc::Markup::AttrSpan.new @str.length, @exclusive_bitmap
254316

317+
convert_attrs @str, @attrs, true
318+
convert_html @str, @attrs, true
319+
convert_regexp_handlings @str, @attrs, true
255320
convert_attrs @str, @attrs
256321
convert_html @str, @attrs
257322
convert_regexp_handlings @str, @attrs

test/rdoc/test_rdoc_markup_attribute_manager.rb

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,22 +172,25 @@ def test_combined
172172

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

177+
@am.convert_attrs str, attrs, true
177178
@am.convert_attrs str, attrs
178179

179180
assert_equal "\000foo\000", str
180181

181182
str = '+:foo:+'.dup
182-
attrs = RDoc::Markup::AttrSpan.new str.length
183+
attrs = RDoc::Markup::AttrSpan.new str.length, @am.exclusive_bitmap
183184

185+
@am.convert_attrs str, attrs, true
184186
@am.convert_attrs str, attrs
185187

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

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

193+
@am.convert_attrs str, attrs, true
191194
@am.convert_attrs str, attrs
192195

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

249+
def test_exclude_tag
250+
assert_equal '<CODE>aaa</CODE>[:symbol]', output('+aaa+[:symbol]')
251+
assert_equal '<CODE>aaa[:symbol]</CODE>', output('+aaa[:symbol]+')
252+
assert_equal 'aaa[:symbol]', output('aaa[:symbol]')
253+
assert_equal '<B><CODE>index</CODE></B>', output('<b><tt>index</tt></b>')
254+
end
255+
256+
def test_exclude_tag_flow
257+
assert_equal [@tt_on, "aaa", @tt_off, "[:symbol]"],
258+
@am.flow("+aaa+[:symbol]")
259+
assert_equal [@tt_on, "aaa[:symbol]", @tt_off],
260+
@am.flow("+aaa[:symbol]+")
261+
assert_equal ["aaa[:symbol]"],
262+
@am.flow("aaa[:symbol]")
263+
end
264+
246265
def test_html_like_em_bold
247266
assert_equal ["cat ", @em_on, "and ", @em_to_bold, "dog", @bold_off],
248267
@am.flow("cat <i>and </i><b>dog</b>")

test/rdoc/test_rdoc_markup_to_html.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,17 @@ def test_convert_TIDYLINK_irc
704704
assert_equal "\n<p><a href=\"irc://irc.freenode.net/#ruby-lang\">ruby-lang</a></p>\n", result
705705
end
706706

707+
def test_convert_with_exclude_tag
708+
assert_equal "\n<p><code>aaa</code>[:symbol]</p>\n", @to.convert('+aaa+[:symbol]')
709+
assert_equal "\n<p><code>aaa[:symbol]</code></p>\n", @to.convert('+aaa[:symbol]+')
710+
assert_equal "\n<p><a href=\":symbol\">aaa</a></p>\n", @to.convert('aaa[:symbol]')
711+
end
712+
713+
def test_convert_underscore_adjacent_to_code
714+
assert_equal "\n<p><code>aaa</code>_</p>\n", @to.convert(%q{+aaa+_})
715+
assert_equal "\n<p>`<code>i386-mswin32_</code><em>MSRTVERSION</em>&#39;</p>\n", @to.convert(%q{`+i386-mswin32_+_MSRTVERSION_'})
716+
end
717+
707718
def test_gen_url
708719
assert_equal '<a href="example">example</a>',
709720
@to.gen_url('link:example', 'example')

0 commit comments

Comments
 (0)