Skip to content

Commit 1483bd7

Browse files
committed
Preserve inline styling inside tidy link labels
1 parent e0051be commit 1483bd7

File tree

5 files changed

+211
-14
lines changed

5 files changed

+211
-14
lines changed

lib/rdoc/markup/to_html.rb

Lines changed: 156 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,16 @@ def handle_regexp_RDOCLINK(target)
158158
def handle_regexp_TIDYLINK(target)
159159
text = target.text
160160

161-
return text unless
162-
text =~ /^\{(.*)\}\[(.*?)\]$/ or text =~ /^(\S+)\[(.*?)\]$/
163-
164-
label = $1
165-
url = CGI.escapeHTML($2)
161+
if tidy_link_capturing?
162+
return finish_tidy_link(text)
163+
end
166164

167-
if /^rdoc-image:/ =~ label
168-
label = handle_RDOCLINK(label)
169-
else
170-
label = CGI.escapeHTML(label)
165+
if text.start_with?('{') && !text.include?('}')
166+
start_tidy_link text
167+
return ''
171168
end
172169

173-
gen_url url, label
170+
convert_complete_tidy_link(text)
174171
end
175172

176173
# :section: Visitor
@@ -458,4 +455,153 @@ def to_html(item)
458455
super convert_flow @am.flow item
459456
end
460457

458+
private
459+
460+
def convert_flow(flow_items)
461+
res = []
462+
463+
flow_items.each do |item|
464+
case item
465+
when String
466+
append_flow_fragment res, convert_string(item)
467+
when RDoc::Markup::AttrChanger
468+
off_tags res, item
469+
on_tags res, item
470+
when RDoc::Markup::RegexpHandling
471+
append_flow_fragment res, convert_regexp_handling(item)
472+
else
473+
raise "Unknown flow element: #{item.inspect}"
474+
end
475+
end
476+
477+
res.join
478+
end
479+
480+
def append_flow_fragment(res, fragment)
481+
return if fragment.nil? || fragment.empty?
482+
483+
emit_tidy_link_fragment(res, fragment)
484+
end
485+
486+
def append_to_tidy_label(fragment)
487+
@tidy_link_buffer << fragment
488+
end
489+
490+
##
491+
# Matches an entire tidy link with a braced label "{label}[url]".
492+
#
493+
# Capture 1: label contents.
494+
# Capture 2: URL text.
495+
# Capture 3: trailing content.
496+
TIDY_LINK_WITH_BRACES = /\A\{(.*?)\}\[(.*?)\](.*)\z/
497+
498+
##
499+
# Matches the tail of a braced tidy link when the opening brace was
500+
# consumed earlier while accumulating the label text.
501+
#
502+
# Capture 1: remaining label content.
503+
# Capture 2: URL text.
504+
# Capture 3: trailing content.
505+
TIDY_LINK_WITH_BRACES_TAIL = /\A(.*?)\}\[(.*?)\](.*)\z/
506+
507+
##
508+
# Matches a tidy link with a single-word label "label[url]".
509+
#
510+
# Capture 1: the single-word label (no whitespace).
511+
# Capture 2: URL text between the brackets.
512+
TIDY_LINK_SINGLE_WORD = /\A(\S+)\[(.*?)\](.*)\z/
513+
514+
def convert_complete_tidy_link(text)
515+
return text unless
516+
text =~ TIDY_LINK_WITH_BRACES or text =~ TIDY_LINK_SINGLE_WORD
517+
518+
label = $1
519+
url = CGI.escapeHTML($2)
520+
521+
label_html = if /^rdoc-image:/ =~ label
522+
handle_RDOCLINK(label)
523+
else
524+
render_tidy_link_label(label)
525+
end
526+
527+
gen_url url, label_html
528+
end
529+
530+
def emit_tidy_link_fragment(res, fragment)
531+
if tidy_link_capturing?
532+
append_to_tidy_label fragment
533+
else
534+
res << fragment
535+
end
536+
end
537+
538+
def finish_tidy_link(text)
539+
label_tail, url, trailing = extract_tidy_link_parts(text)
540+
append_to_tidy_label CGI.escapeHTML(label_tail) unless label_tail.empty?
541+
542+
return '' unless url
543+
544+
label_html = @tidy_link_buffer
545+
@tidy_link_buffer = nil
546+
link = gen_url(url, label_html)
547+
548+
return link if trailing.empty?
549+
550+
link + CGI.escapeHTML(trailing)
551+
end
552+
553+
def extract_tidy_link_parts(text)
554+
if text =~ TIDY_LINK_WITH_BRACES
555+
[$1, CGI.escapeHTML($2), $3]
556+
elsif text =~ TIDY_LINK_WITH_BRACES_TAIL
557+
[$1, CGI.escapeHTML($2), $3]
558+
elsif text =~ TIDY_LINK_SINGLE_WORD
559+
[$1, CGI.escapeHTML($2), $3]
560+
else
561+
[text, nil, '']
562+
end
563+
end
564+
565+
def on_tags(res, item)
566+
each_attr_tag(item.turn_on) do |tag|
567+
emit_tidy_link_fragment(res, annotate(tag.on))
568+
@in_tt += 1 if tt? tag
569+
end
570+
end
571+
572+
def off_tags(res, item)
573+
each_attr_tag(item.turn_off, true) do |tag|
574+
emit_tidy_link_fragment(res, annotate(tag.off))
575+
@in_tt -= 1 if tt? tag
576+
end
577+
end
578+
579+
def start_tidy_link(text)
580+
@tidy_link_buffer = String.new
581+
append_to_tidy_label CGI.escapeHTML(text.delete_prefix('{'))
582+
end
583+
584+
def tidy_link_capturing?
585+
!!@tidy_link_buffer
586+
end
587+
588+
def render_tidy_link_label(label)
589+
RDoc::Markup::LinkLabelToHtml.render(label, @options, @from_path)
590+
end
591+
end
592+
593+
##
594+
# Formatter dedicated to rendering tidy link labels without mutating the
595+
# calling formatter's state.
596+
597+
class RDoc::Markup::LinkLabelToHtml < RDoc::Markup::ToHtml
598+
def self.render(label, options, from_path)
599+
new(options, from_path).to_html(label)
600+
end
601+
602+
def initialize(options, from_path = nil)
603+
super(options)
604+
605+
self.from_path = from_path if from_path
606+
end
461607
end

lib/rdoc/markup/to_html_crossref.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,9 @@ def convert_flow(flow_items, &block)
193193

194194
case item
195195
when RDoc::Markup::AttrChanger
196-
if (text = convert_tt_crossref(flow_items, i))
196+
if !tidy_link_capturing? && (text = convert_tt_crossref(flow_items, i))
197197
text = block.call(text, res) if block
198-
res << text
198+
append_flow_fragment res, text
199199
i += 3
200200
next
201201
end
@@ -206,12 +206,12 @@ def convert_flow(flow_items, &block)
206206
when String
207207
text = convert_string(item)
208208
text = block.call(text, res) if block
209-
res << text
209+
append_flow_fragment res, text
210210
i += 1
211211
when RDoc::Markup::RegexpHandling
212212
text = convert_regexp_handling(item)
213213
text = block.call(text, res) if block
214-
res << text
214+
append_flow_fragment res, text
215215
i += 1
216216
else
217217
raise "Unknown flow element: #{item.inspect}"

test/rdoc/rdoc_markdown_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,26 @@ def test_gfm_table_with_backslashes_in_code_spans
12631263
assert_equal expected, doc
12641264
end
12651265

1266+
def test_markdown_link_with_styled_label
1267+
markdown = <<~MD
1268+
[Link to Foo](https://example.com)
1269+
1270+
[Link to `Foo`](https://example.com)
1271+
1272+
[Link to **Foo**](https://example.com)
1273+
1274+
[Link to `Foo` and `\Bar` and `Baz`](https://example.com)
1275+
MD
1276+
1277+
doc = parse markdown
1278+
html = @to_html.convert doc
1279+
1280+
assert_includes html, '<a href="https://example.com">Link to Foo</a>'
1281+
assert_includes html, '<a href="https://example.com">Link to <code>Foo</code></a>'
1282+
assert_includes html, '<a href="https://example.com">Link to <strong>Foo</strong></a>'
1283+
assert_includes html, '<a href="https://example.com">Link to <code>Foo</code> and <code>Bar</code> and <code>Baz</code></a>'
1284+
end
1285+
12661286
def parse(text)
12671287
@parser.parse text
12681288
end

test/rdoc/rdoc_markup_to_html_crossref_test.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22
require_relative 'xref_test_case'
3+
require 'rdoc/markdown'
34

45
class RDocMarkupToHtmlCrossrefTest < XrefTestCase
56

@@ -288,6 +289,14 @@ def test_handle_regexp_TIDYLINK_label
288289
link, 'C1#m@foo'
289290
end
290291

292+
def test_convert_TIDYLINK_markdown_with_crossrefs
293+
markdown = RDoc::Markdown.parse('[Link to `C1` and `Foo` and `\\C1` and `Bar`](https://example.com)')
294+
295+
result = markdown.accept(@to)
296+
297+
assert_equal para('<a href="https://example.com">Link to <code>C1</code> and <code>Foo</code> and <code>\\C1</code> and <code>Bar</code></a>'), result
298+
end
299+
291300
def test_to_html_CROSSREF_email
292301
@options.hyperlink_all = false
293302

test/rdoc/rdoc_markup_to_html_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,28 @@ def test_convert_TIDYLINK_multiple
734734
assert_equal expected, result
735735
end
736736

737+
def test_convert_TIDYLINK_with_code_label
738+
result = @to.convert '{Link to +Foo+}[https://example.com]'
739+
740+
expected = "\n<p><a href=\"https://example.com\">Link to <code>Foo</code></a></p>\n"
741+
742+
assert_equal expected, result
743+
744+
result = @to.convert '{Link to +Foo+ and +Bar+ and +Baz+}[https://example.com]'
745+
746+
expected = "\n<p><a href=\"https://example.com\">Link to <code>Foo</code> and <code>Bar</code> and <code>Baz</code></a></p>\n"
747+
748+
assert_equal expected, result
749+
end
750+
751+
def test_convert_TIDYLINK_with_bold_label
752+
result = @to.convert '{Link to *Foo*}[https://example.com]'
753+
754+
expected = "\n<p><a href=\"https://example.com\">Link to <strong>Foo</strong></a></p>\n"
755+
756+
assert_equal expected, result
757+
end
758+
737759
def test_convert_TIDYLINK_image
738760
result =
739761
@to.convert '{rdoc-image:path/to/image.jpg}[http://example.com]'

0 commit comments

Comments
 (0)