OSDN Git Service

ruby-1.9.1-rc1
[splhack/AndroidRuby.git] / lib / ruby-1.9.1-rc1 / lib / rdoc / markup / to_html.rb
1 require 'rdoc/markup/formatter'
2 require 'rdoc/markup/fragments'
3 require 'rdoc/markup/inline'
4
5 require 'cgi'
6
7 class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
8
9   LIST_TYPE_TO_HTML = {
10     :BULLET =>     %w[<ul> </ul>],
11     :NUMBER =>     %w[<ol> </ol>],
12     :UPPERALPHA => %w[<ol> </ol>],
13     :LOWERALPHA => %w[<ol> </ol>],
14     :LABELED =>    %w[<dl> </dl>],
15     :NOTE    =>    %w[<table> </table>],
16   }
17
18   InlineTag = Struct.new(:bit, :on, :off)
19
20   def initialize
21     super
22
23     # @in_tt - tt nested levels count
24     # @tt_bit - cache
25     @in_tt = 0
26     @tt_bit = RDoc::Markup::Attribute.bitmap_for :TT
27
28     # external hyperlinks
29     @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
30
31     # and links of the form  <text>[<url>]
32     @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
33
34     init_tags
35   end
36
37   ##
38   # Converts a target url to one that is relative to a given path
39
40   def self.gen_relative_url(path, target)
41     from        = File.dirname path
42     to, to_file = File.split target
43
44     from = from.split "/"
45     to   = to.split "/"
46
47     while from.size > 0 and to.size > 0 and from[0] == to[0] do
48       from.shift
49       to.shift
50     end
51
52     from.fill ".."
53     from.concat to
54     from << to_file
55     File.join(*from)
56   end
57
58   ##
59   # Generate a hyperlink for url, labeled with text. Handle the
60   # special cases for img: and link: described under handle_special_HYPERLINK
61
62   def gen_url(url, text)
63     if url =~ /([A-Za-z]+):(.*)/ then
64       type = $1
65       path = $2
66     else
67       type = "http"
68       path = url
69       url  = "http://#{url}"
70     end
71
72     if type == "link" then
73       url = if path[0, 1] == '#' then # is this meaningful?
74               path
75             else
76               self.class.gen_relative_url @from_path, path
77             end
78     end
79
80     if (type == "http" or type == "link") and
81        url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
82       "<img src=\"#{url}\" />"
83     else
84       "<a href=\"#{url}\">#{text.sub(%r{^#{type}:/*}, '')}</a>"
85     end
86   end
87
88   ##
89   # And we're invoked with a potential external hyperlink mailto:
90   # just gets inserted. http: links are checked to see if they
91   # reference an image. If so, that image gets inserted using an
92   # <img> tag. Otherwise a conventional <a href> is used.  We also
93   # support a special type of hyperlink, link:, which is a reference
94   # to a local file whose path is relative to the --op directory.
95
96   def handle_special_HYPERLINK(special)
97     url = special.text
98     gen_url url, url
99   end
100
101   ##
102   # Here's a hypedlink where the label is different to the URL
103   #  <label>[url] or {long label}[url]
104
105   def handle_special_TIDYLINK(special)
106     text = special.text
107
108     return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
109
110     label = $1
111     url   = $2
112     gen_url url, label
113   end
114
115   ##
116   # are we currently inside <tt> tags?
117
118   def in_tt?
119     @in_tt > 0
120   end
121
122   ##
123   # is +tag+ a <tt> tag?
124
125   def tt?(tag)
126     tag.bit == @tt_bit
127   end
128
129   ##
130   # Set up the standard mapping of attributes to HTML tags
131
132   def init_tags
133     @attr_tags = [
134       InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "<b>", "</b>"),
135       InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT),   "<tt>", "</tt>"),
136       InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM),   "<em>", "</em>"),
137     ]
138   end
139
140   ##
141   # Add a new set of HTML tags for an attribute. We allow separate start and
142   # end tags for flexibility.
143
144   def add_tag(name, start, stop)
145     @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
146   end
147
148   ##
149   # Given an HTML tag, decorate it with class information and the like if
150   # required. This is a no-op in the base class, but is overridden in HTML
151   # output classes that implement style sheets.
152
153   def annotate(tag)
154     tag
155   end
156
157   ##
158   # Here's the client side of the visitor pattern
159
160   def start_accepting
161     @res = ""
162     @in_list_entry = []
163   end
164
165   def end_accepting
166     @res
167   end
168
169   def accept_paragraph(am, fragment)
170     @res << annotate("<p>") + "\n"
171     @res << wrap(convert_flow(am.flow(fragment.txt)))
172     @res << annotate("</p>") + "\n"
173   end
174
175   def accept_verbatim(am, fragment)
176     @res << annotate("<pre>") + "\n"
177     @res << CGI.escapeHTML(fragment.txt)
178     @res << annotate("</pre>") << "\n"
179   end
180
181   def accept_rule(am, fragment)
182     size = fragment.param
183     size = 10 if size > 10
184     @res << "<hr size=\"#{size}\"></hr>"
185   end
186
187   def accept_list_start(am, fragment)
188     @res << html_list_name(fragment.type, true) << "\n"
189     @in_list_entry.push false
190   end
191
192   def accept_list_end(am, fragment)
193     if tag = @in_list_entry.pop
194       @res << annotate(tag) << "\n"
195     end
196     @res << html_list_name(fragment.type, false) << "\n"
197   end
198
199   def accept_list_item(am, fragment)
200     if tag = @in_list_entry.last
201       @res << annotate(tag) << "\n"
202     end
203
204     @res << list_item_start(am, fragment)
205
206     @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n"
207
208     @in_list_entry[-1] = list_end_for(fragment.type)
209   end
210
211   def accept_blank_line(am, fragment)
212     # @res << annotate("<p />") << "\n"
213   end
214
215   def accept_heading(am, fragment)
216     @res << convert_heading(fragment.head_level, am.flow(fragment.txt))
217   end
218
219   ##
220   # This is a higher speed (if messier) version of wrap
221
222   def wrap(txt, line_len = 76)
223     res = ""
224     sp = 0
225     ep = txt.length
226     while sp < ep
227       # scan back for a space
228       p = sp + line_len - 1
229       if p >= ep
230         p = ep
231       else
232         while p > sp and txt[p] != ?\s
233           p -= 1
234         end
235         if p <= sp
236           p = sp + line_len
237           while p < ep and txt[p] != ?\s
238             p += 1
239           end
240         end
241       end
242       res << txt[sp...p] << "\n"
243       sp = p
244       sp += 1 while sp < ep and txt[sp] == ?\s
245     end
246     res
247   end
248
249   private
250
251   def on_tags(res, item)
252     attr_mask = item.turn_on
253     return if attr_mask.zero?
254
255     @attr_tags.each do |tag|
256       if attr_mask & tag.bit != 0
257         res << annotate(tag.on)
258         @in_tt += 1 if tt?(tag)
259       end
260     end
261   end
262
263   def off_tags(res, item)
264     attr_mask = item.turn_off
265     return if attr_mask.zero?
266
267     @attr_tags.reverse_each do |tag|
268       if attr_mask & tag.bit != 0
269         @in_tt -= 1 if tt?(tag)
270         res << annotate(tag.off)
271       end
272     end
273   end
274
275   def convert_flow(flow)
276     res = ""
277
278     flow.each do |item|
279       case item
280       when String
281         res << convert_string(item)
282       when RDoc::Markup::AttrChanger
283         off_tags(res, item)
284         on_tags(res,  item)
285       when RDoc::Markup::Special
286         res << convert_special(item)
287       else
288         raise "Unknown flow element: #{item.inspect}"
289       end
290     end
291
292     res
293   end
294
295   def convert_string(item)
296     in_tt? ? convert_string_simple(item) : convert_string_fancy(item)
297   end
298
299   def convert_string_simple(item)
300     CGI.escapeHTML item
301   end
302
303   ##
304   # some of these patterns are taken from SmartyPants...
305
306   def convert_string_fancy(item)
307     # convert ampersand before doing anything else
308     item.gsub(/&/, '&amp;').
309
310     # convert -- to em-dash, (-- to en-dash)
311       gsub(/---?/, '&#8212;'). #gsub(/--/, '&#8211;').
312       
313     # convert ... to elipsis (and make sure .... becomes .<elipsis>)
314       gsub(/\.\.\.\./, '.&#8230;').gsub(/\.\.\./, '&#8230;').
315
316     # convert single closing quote
317       gsub(%r{([^ \t\r\n\[\{\(])\'}, '\1&#8217;'). # }
318       gsub(%r{\'(?=\W|s\b)}, '&#8217;').
319
320     # convert single opening quote
321       gsub(/'/, '&#8216;').
322
323     # convert double closing quote
324       gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}, '\1&#8221;'). # }
325
326     # convert double opening quote
327       gsub(/"/, '&#8220;').
328
329     # convert copyright
330       gsub(/\(c\)/, '&#169;').
331
332     # convert registered trademark
333       gsub(/\(r\)/, '&#174;')
334   end
335
336   def convert_special(special)
337     handled = false
338     RDoc::Markup::Attribute.each_name_of(special.type) do |name|
339       method_name = "handle_special_#{name}"
340       if self.respond_to? method_name
341         special.text = send(method_name, special)
342         handled = true
343       end
344     end
345     raise "Unhandled special: #{special}" unless handled
346     special.text
347   end
348
349   def convert_heading(level, flow)
350     res =
351       annotate("<h#{level}>") +
352       convert_flow(flow) +
353       annotate("</h#{level}>\n")
354   end
355
356   def html_list_name(list_type, is_open_tag)
357     tags = LIST_TYPE_TO_HTML[list_type] || raise("Invalid list type: #{list_type.inspect}")
358     annotate(tags[ is_open_tag ? 0 : 1])
359   end
360
361   def list_item_start(am, fragment)
362     case fragment.type
363     when :BULLET, :NUMBER then
364       annotate("<li>")
365
366     when :UPPERALPHA then
367       annotate("<li type=\"A\">")
368
369     when :LOWERALPHA then
370       annotate("<li type=\"a\">")
371
372     when :LABELED then
373       annotate("<dt>") +
374         convert_flow(am.flow(fragment.param)) +
375         annotate("</dt>") +
376         annotate("<dd>")
377
378     when :NOTE then
379       annotate("<tr>") +
380         annotate("<td valign=\"top\">") +
381         convert_flow(am.flow(fragment.param)) +
382         annotate("</td>") +
383         annotate("<td>")
384     else
385       raise "Invalid list type"
386     end
387   end
388
389   def list_end_for(fragment_type)
390     case fragment_type
391     when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA then
392       "</li>"
393     when :LABELED then
394       "</dd>"
395     when :NOTE then
396       "</td></tr>"
397     else
398       raise "Invalid list type"
399     end
400   end
401
402 end
403