1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
|
# frozen_string_literal: true
require 'cgi'
##
# Outputs RDoc markup as HTML.
class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
include RDoc::Text
# :section: Utilities
##
# Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags
LIST_TYPE_TO_HTML = {
:BULLET => ['<ul>', '</ul>'],
:LABEL => ['<dl class="rdoc-list label-list">', '</dl>'],
:LALPHA => ['<ol style="list-style-type: lower-alpha">', '</ol>'],
:NOTE => ['<dl class="rdoc-list note-list">', '</dl>'],
:NUMBER => ['<ol>', '</ol>'],
:UALPHA => ['<ol style="list-style-type: upper-alpha">', '</ol>'],
}
attr_reader :res # :nodoc:
attr_reader :in_list_entry # :nodoc:
attr_reader :list # :nodoc:
##
# The RDoc::CodeObject HTML is being generated for. This is used to
# generate namespaced URI fragments
attr_accessor :code_object
##
# Path to this document for relative links
attr_accessor :from_path
# :section:
##
# Creates a new formatter that will output HTML
def initialize options, markup = nil
super
@code_object = nil
@from_path = ''
@in_list_entry = nil
@list = nil
@th = nil
@hard_break = "<br>\n"
# external links
@markup.add_special(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)\S+\w/,
:HYPERLINK)
add_special_RDOCLINK
add_special_TIDYLINK
init_tags
end
# :section: Special Handling
#
# These methods handle special markup added by RDoc::Markup#add_special.
def handle_RDOCLINK url # :nodoc:
case url
when /^rdoc-ref:/
$'
when /^rdoc-label:/
text = $'
text = case text
when /\Alabel-/ then $'
when /\Afootmark-/ then $'
when /\Afoottext-/ then $'
else text
end
gen_url url, text
when /^rdoc-image:/
"<img src=\"#{$'}\">"
else
url =~ /\Ardoc-[a-z]+:/
$'
end
end
##
# +special+ is a <code><br></code>
def handle_special_HARD_BREAK special
'<br>'
end
##
# +special+ is a potential link. The following schemes are handled:
#
# <tt>mailto:</tt>::
# Inserted as-is.
# <tt>http:</tt>::
# Links are checked to see if they reference an image. If so, that image
# gets inserted using an <tt><img></tt> tag. Otherwise a conventional
# <tt><a href></tt> is used.
# <tt>link:</tt>::
# Reference to a local file relative to the output directory.
def handle_special_HYPERLINK(special)
url = special.text
gen_url url, url
end
##
# +special+ is an rdoc-schemed link that will be converted into a hyperlink.
#
# For the +rdoc-ref+ scheme the named reference will be returned without
# creating a link.
#
# For the +rdoc-label+ scheme the footnote and label prefixes are stripped
# when creating a link. All other contents will be linked verbatim.
def handle_special_RDOCLINK special
handle_RDOCLINK special.text
end
##
# This +special+ is a link where the label is different from the URL
# <tt>label[url]</tt> or <tt>{long label}[url]</tt>
def handle_special_TIDYLINK(special)
text = special.text
return text unless
text =~ /^\{(.*)\}\[(.*?)\]$/ or text =~ /^(\S+)\[(.*?)\]$/
label = $1
url = $2
label = handle_RDOCLINK label if /^rdoc-image:/ =~ label
gen_url url, label
end
# :section: Visitor
#
# These methods implement the HTML visitor.
##
# Prepares the visitor for HTML generation
def start_accepting
@res = []
@in_list_entry = []
@list = []
end
##
# Returns the generated output
def end_accepting
@res.join
end
##
# Adds +block_quote+ to the output
def accept_block_quote block_quote
@res << "\n<blockquote>"
block_quote.parts.each do |part|
part.accept self
end
@res << "</blockquote>\n"
end
##
# Adds +paragraph+ to the output
def accept_paragraph paragraph
@res << "\n<p>"
text = paragraph.text @hard_break
text = text.gsub(/\r?\n/, ' ')
@res << wrap(to_html(text))
@res << "</p>\n"
end
##
# Adds +verbatim+ to the output
def accept_verbatim verbatim
text = verbatim.text.rstrip
klass = nil
content = if verbatim.ruby? or parseable? text then
begin
tokens = RDoc::Parser::RipperStateLex.parse text
klass = ' class="ruby"'
result = RDoc::TokenStream.to_html tokens
result = result + "\n" unless "\n" == result[-1]
result
rescue
CGI.escapeHTML text
end
else
CGI.escapeHTML text
end
if @options.pipe then
@res << "\n<pre><code>#{CGI.escapeHTML text}\n</code></pre>\n"
else
@res << "\n<pre#{klass}>#{content}</pre>\n"
end
end
##
# Adds +rule+ to the output
def accept_rule rule
@res << "<hr>\n"
end
##
# Prepares the visitor for consuming +list+
def accept_list_start(list)
@list << list.type
@res << html_list_name(list.type, true)
@in_list_entry.push false
end
##
# Finishes consumption of +list+
def accept_list_end(list)
@list.pop
if tag = @in_list_entry.pop
@res << tag
end
@res << html_list_name(list.type, false) << "\n"
end
##
# Prepares the visitor for consuming +list_item+
def accept_list_item_start(list_item)
if tag = @in_list_entry.last
@res << tag
end
@res << list_item_start(list_item, @list.last)
end
##
# Finishes consumption of +list_item+
def accept_list_item_end(list_item)
@in_list_entry[-1] = list_end_for(@list.last)
end
##
# Adds +blank_line+ to the output
def accept_blank_line(blank_line)
# @res << annotate("<p />") << "\n"
end
##
# Adds +heading+ to the output. The headings greater than 6 are trimmed to
# level 6.
def accept_heading heading
level = [6, heading.level].min
label = heading.label @code_object
@res << if @options.output_decoration
"\n<h#{level} id=\"#{label}\">"
else
"\n<h#{level}>"
end
@res << to_html(heading.text)
unless @options.pipe then
@res << "<span><a href=\"##{label}\">¶</a>"
@res << " <a href=\"#top\">↑</a></span>"
end
@res << "</h#{level}>\n"
end
##
# Adds +raw+ to the output
def accept_raw raw
@res << raw.parts.join("\n")
end
# :section: Utilities
##
# CGI-escapes +text+
def convert_string(text)
CGI.escapeHTML text
end
##
# Generate a link to +url+ with content +text+. Handles the special cases
# for img: and link: described under handle_special_HYPERLINK
def gen_url url, text
scheme, url, id = parse_url url
if %w[http https link].include?(scheme) and
url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
"<img src=\"#{url}\" />"
else
text = text.sub %r%^#{scheme}:/*%i, ''
text = text.sub %r%^[*\^](\d+)$%, '\1'
link = "<a#{id} href=\"#{url}\">#{text}</a>"
link = "<sup>#{link}</sup>" if /"foot/ =~ id
link
end
end
##
# Determines the HTML list element for +list_type+ and +open_tag+
def html_list_name(list_type, open_tag)
tags = LIST_TYPE_TO_HTML[list_type]
raise RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags
tags[open_tag ? 0 : 1]
end
##
# Maps attributes to HTML tags
def init_tags
add_tag :BOLD, "<strong>", "</strong>"
add_tag :TT, "<code>", "</code>"
add_tag :EM, "<em>", "</em>"
end
##
# Returns the HTML tag for +list_type+, possible using a label from
# +list_item+
def list_item_start(list_item, list_type)
case list_type
when :BULLET, :LALPHA, :NUMBER, :UALPHA then
"<li>"
when :LABEL, :NOTE then
Array(list_item.label).map do |label|
"<dt>#{to_html label}\n"
end.join << "<dd>"
else
raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
end
end
##
# Returns the HTML end-tag for +list_type+
def list_end_for(list_type)
case list_type
when :BULLET, :LALPHA, :NUMBER, :UALPHA then
"</li>"
when :LABEL, :NOTE then
"</dd>"
else
raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
end
end
##
# Returns true if text is valid ruby syntax
def parseable? text
verbose, $VERBOSE = $VERBOSE, nil
eval("BEGIN {return true}\n#{text}")
rescue SyntaxError
false
ensure
$VERBOSE = verbose
end
##
# Converts +item+ to HTML using RDoc::Text#to_html
def to_html item
super convert_flow @am.flow item
end
end
|