PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/ruby/1.9/rdoc/markup/to_html.rb

https://bitbucket.org/nicksieger/jruby
Ruby | 344 lines | 243 code | 59 blank | 42 comment | 22 complexity | 3d3b8c886405ab0f1609d2cae2239974 MD5 | raw file
Possible License(s): GPL-3.0, JSON
  1. require 'rdoc/markup/formatter'
  2. require 'rdoc/markup/inline'
  3. require 'cgi'
  4. ##
  5. # Outputs RDoc markup as HTML
  6. class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
  7. ##
  8. # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags
  9. LIST_TYPE_TO_HTML = {
  10. :BULLET => ['<ul>', '</ul>'],
  11. :LABEL => ['<dl>', '</dl>'],
  12. :LALPHA => ['<ol style="display: lower-alpha">', '</ol>'],
  13. :NOTE => ['<table>', '</table>'],
  14. :NUMBER => ['<ol>', '</ol>'],
  15. :UALPHA => ['<ol style="display: upper-alpha">', '</ol>'],
  16. }
  17. attr_reader :res # :nodoc:
  18. attr_reader :in_list_entry # :nodoc:
  19. attr_reader :list # :nodoc:
  20. ##
  21. # Converts a target url to one that is relative to a given path
  22. def self.gen_relative_url(path, target)
  23. from = File.dirname path
  24. to, to_file = File.split target
  25. from = from.split "/"
  26. to = to.split "/"
  27. from.delete '.'
  28. to.delete '.'
  29. while from.size > 0 and to.size > 0 and from[0] == to[0] do
  30. from.shift
  31. to.shift
  32. end
  33. from.fill ".."
  34. from.concat to
  35. from << to_file
  36. File.join(*from)
  37. end
  38. def initialize
  39. super
  40. @th = nil
  41. @in_list_entry = nil
  42. @list = nil
  43. # external hyperlinks
  44. @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
  45. # and links of the form <text>[<url>]
  46. @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
  47. init_tags
  48. end
  49. ##
  50. # Maps attributes to HTML tags
  51. def init_tags
  52. add_tag :BOLD, "<b>", "</b>"
  53. add_tag :TT, "<tt>", "</tt>"
  54. add_tag :EM, "<em>", "</em>"
  55. end
  56. ##
  57. # Generate a hyperlink for url, labeled with text. Handle the
  58. # special cases for img: and link: described under handle_special_HYPERLINK
  59. def gen_url(url, text)
  60. if url =~ /([A-Za-z]+):(.*)/ then
  61. type = $1
  62. path = $2
  63. else
  64. type = "http"
  65. path = url
  66. url = "http://#{url}"
  67. end
  68. if type == "link" then
  69. url = if path[0, 1] == '#' then # is this meaningful?
  70. path
  71. else
  72. self.class.gen_relative_url @from_path, path
  73. end
  74. end
  75. if (type == "http" or type == "link") and
  76. url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
  77. "<img src=\"#{url}\" />"
  78. else
  79. "<a href=\"#{url}\">#{text.sub(%r{^#{type}:/*}, '')}</a>"
  80. end
  81. end
  82. ##
  83. # And we're invoked with a potential external hyperlink mailto:
  84. # just gets inserted. http: links are checked to see if they
  85. # reference an image. If so, that image gets inserted using an
  86. # <img> tag. Otherwise a conventional <a href> is used. We also
  87. # support a special type of hyperlink, link:, which is a reference
  88. # to a local file whose path is relative to the --op directory.
  89. def handle_special_HYPERLINK(special)
  90. url = special.text
  91. gen_url url, url
  92. end
  93. ##
  94. # Here's a hypedlink where the label is different to the URL
  95. # <label>[url] or {long label}[url]
  96. def handle_special_TIDYLINK(special)
  97. text = special.text
  98. return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
  99. label = $1
  100. url = $2
  101. gen_url url, label
  102. end
  103. ##
  104. # This is a higher speed (if messier) version of wrap
  105. def wrap(txt, line_len = 76)
  106. res = []
  107. sp = 0
  108. ep = txt.length
  109. while sp < ep
  110. # scan back for a space
  111. p = sp + line_len - 1
  112. if p >= ep
  113. p = ep
  114. else
  115. while p > sp and txt[p] != ?\s
  116. p -= 1
  117. end
  118. if p <= sp
  119. p = sp + line_len
  120. while p < ep and txt[p] != ?\s
  121. p += 1
  122. end
  123. end
  124. end
  125. res << txt[sp...p] << "\n"
  126. sp = p
  127. sp += 1 while sp < ep and txt[sp] == ?\s
  128. end
  129. res.join
  130. end
  131. ##
  132. # :section: Visitor
  133. def start_accepting
  134. @res = []
  135. @in_list_entry = []
  136. @list = []
  137. end
  138. def end_accepting
  139. @res.join
  140. end
  141. def accept_paragraph(paragraph)
  142. @res << annotate("<p>") + "\n"
  143. @res << wrap(convert_flow(@am.flow(paragraph.text)))
  144. @res << annotate("</p>") + "\n"
  145. end
  146. def accept_verbatim(verbatim)
  147. @res << annotate("<pre>") << "\n"
  148. @res << CGI.escapeHTML(verbatim.text)
  149. @res << annotate("</pre>") << "\n"
  150. end
  151. def accept_rule(rule)
  152. size = rule.weight
  153. size = 10 if size > 10
  154. @res << "<hr style=\"height: #{size}px\"></hr>"
  155. end
  156. def accept_list_start(list)
  157. @list << list.type
  158. @res << html_list_name(list.type, true) << "\n"
  159. @in_list_entry.push false
  160. end
  161. def accept_list_end(list)
  162. @list.pop
  163. if tag = @in_list_entry.pop
  164. @res << annotate(tag) << "\n"
  165. end
  166. @res << html_list_name(list.type, false) << "\n"
  167. end
  168. def accept_list_item_start(list_item)
  169. if tag = @in_list_entry.last
  170. @res << annotate(tag) << "\n"
  171. end
  172. @res << list_item_start(list_item, @list.last)
  173. end
  174. def accept_list_item_end(list_item)
  175. @in_list_entry[-1] = list_end_for(@list.last)
  176. end
  177. def accept_blank_line(blank_line)
  178. # @res << annotate("<p />") << "\n"
  179. end
  180. def accept_heading(heading)
  181. @res << convert_heading(heading.level, @am.flow(heading.text))
  182. end
  183. def accept_raw raw
  184. @res << raw.parts.join("\n")
  185. end
  186. private
  187. ##
  188. # Converts string +item+
  189. def convert_string(item)
  190. in_tt? ? convert_string_simple(item) : convert_string_fancy(item)
  191. end
  192. ##
  193. # Escapes HTML in +item+
  194. def convert_string_simple(item)
  195. CGI.escapeHTML item
  196. end
  197. ##
  198. # Converts ampersand, dashes, elipsis, quotes, copyright and registered
  199. # trademark symbols to HTML escaped Unicode.
  200. def convert_string_fancy(item)
  201. # convert ampersand before doing anything else
  202. item.gsub(/&/, '&amp;').
  203. # convert -- to em-dash, (-- to en-dash)
  204. gsub(/---?/, '&#8212;'). #gsub(/--/, '&#8211;').
  205. # convert ... to elipsis (and make sure .... becomes .<elipsis>)
  206. gsub(/\.\.\.\./, '.&#8230;').gsub(/\.\.\./, '&#8230;').
  207. # convert single closing quote
  208. gsub(%r{([^ \t\r\n\[\{\(])\'}, '\1&#8217;'). # }
  209. gsub(%r{\'(?=\W|s\b)}, '&#8217;').
  210. # convert single opening quote
  211. gsub(/'/, '&#8216;').
  212. # convert double closing quote
  213. gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}, '\1&#8221;'). # }
  214. # convert double opening quote
  215. gsub(/"/, '&#8220;').
  216. # convert copyright
  217. gsub(/\(c\)/, '&#169;').
  218. # convert registered trademark
  219. gsub(/\(r\)/, '&#174;')
  220. end
  221. ##
  222. # Converts headings to hN elements
  223. def convert_heading(level, flow)
  224. [annotate("<h#{level}>"),
  225. convert_flow(flow),
  226. annotate("</h#{level}>\n")].join
  227. end
  228. ##
  229. # Determins the HTML list element for +list_type+ and +open_tag+
  230. def html_list_name(list_type, open_tag)
  231. tags = LIST_TYPE_TO_HTML[list_type]
  232. raise RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags
  233. annotate tags[open_tag ? 0 : 1]
  234. end
  235. ##
  236. # Starts a list item
  237. def list_item_start(list_item, list_type)
  238. case list_type
  239. when :BULLET, :LALPHA, :NUMBER, :UALPHA then
  240. annotate("<li>")
  241. when :LABEL then
  242. annotate("<dt>") +
  243. convert_flow(@am.flow(list_item.label)) +
  244. annotate("</dt>") +
  245. annotate("<dd>")
  246. when :NOTE then
  247. annotate("<tr>") +
  248. annotate("<td valign=\"top\">") +
  249. convert_flow(@am.flow(list_item.label)) +
  250. annotate("</td>") +
  251. annotate("<td>")
  252. else
  253. raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
  254. end
  255. end
  256. ##
  257. # Ends a list item
  258. def list_end_for(list_type)
  259. case list_type
  260. when :BULLET, :LALPHA, :NUMBER, :UALPHA then
  261. "</li>"
  262. when :LABEL then
  263. "</dd>"
  264. when :NOTE then
  265. "</td></tr>"
  266. else
  267. raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
  268. end
  269. end
  270. end