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