diff options
author | yugui <yugui@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-08-25 15:02:05 +0000 |
---|---|---|
committer | yugui <yugui@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-08-25 15:02:05 +0000 |
commit | 0dc342de848a642ecce8db697b8fecd83a63e117 (patch) | |
tree | 2b7ed4724aff1f86073e4740134bda9c4aac1a39 /trunk/lib/rdoc/markup/to_html.rb | |
parent | ef70cf7138ab8034b5b806f466e4b484b24f0f88 (diff) |
added tag v1_9_0_4
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/tags/v1_9_0_4@18845 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'trunk/lib/rdoc/markup/to_html.rb')
-rw-r--r-- | trunk/lib/rdoc/markup/to_html.rb | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/trunk/lib/rdoc/markup/to_html.rb b/trunk/lib/rdoc/markup/to_html.rb new file mode 100644 index 0000000000..ca29373db1 --- /dev/null +++ b/trunk/lib/rdoc/markup/to_html.rb @@ -0,0 +1,400 @@ +require 'rdoc/markup/formatter' +require 'rdoc/markup/fragments' +require 'rdoc/markup/inline' + +require 'cgi' + +class RDoc::Markup::ToHtml < RDoc::Markup::Formatter + + LIST_TYPE_TO_HTML = { + :BULLET => %w[<ul> </ul>], + :NUMBER => %w[<ol> </ol>], + :UPPERALPHA => %w[<ol> </ol>], + :LOWERALPHA => %w[<ol> </ol>], + :LABELED => %w[<dl> </dl>], + :NOTE => %w[<table> </table>], + } + + InlineTag = Struct.new(:bit, :on, :off) + + def initialize + super + + # @in_tt - tt nested levels count + # @tt_bit - cache + @in_tt = 0 + @tt_bit = RDoc::Markup::Attribute.bitmap_for :TT + + # external hyperlinks + @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK) + + # and links of the form <text>[<url>] + @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK) + + init_tags + end + + ## + # Converts a target url to one that is relative to a given path + + def self.gen_relative_url(path, target) + from = File.dirname path + to, to_file = File.split target + + from = from.split "/" + to = to.split "/" + + while from.size > 0 and to.size > 0 and from[0] == to[0] do + from.shift + to.shift + end + + from.fill ".." + from.concat to + from << to_file + File.join(*from) + end + + ## + # Generate a hyperlink for url, labeled with text. Handle the + # special cases for img: and link: described under handle_special_HYPEDLINK + + def gen_url(url, text) + if url =~ /([A-Za-z]+):(.*)/ then + type = $1 + path = $2 + else + type = "http" + path = url + url = "http://#{url}" + end + + if type == "link" then + url = if path[0, 1] == '#' then # is this meaningful? + path + else + self.class.gen_relative_url @from_path, path + end + end + + if (type == "http" or type == "link") and + url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then + "<img src=\"#{url}\" />" + else + "<a href=\"#{url}\">#{text.sub(%r{^#{type}:/*}, '')}</a>" + end + end + + ## + # And we're invoked with a potential external hyperlink mailto: + # just gets inserted. http: links are checked to see if they + # reference an image. If so, that image gets inserted using an + # <img> tag. Otherwise a conventional <a href> is used. We also + # support a special type of hyperlink, link:, which is a reference + # to a local file whose path is relative to the --op directory. + + def handle_special_HYPERLINK(special) + url = special.text + gen_url url, url + end + + ## + # Here's a hypedlink where the label is different to the URL + # <label>[url] or {long label}[url] + + def handle_special_TIDYLINK(special) + text = special.text + + return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/ + + label = $1 + url = $2 + gen_url url, label + end + + ## + # are we currently inside <tt> tags? + + def in_tt? + @in_tt > 0 + end + + ## + # is +tag+ a <tt> tag? + + def tt?(tag) + tag.bit == @tt_bit + end + + ## + # Set up the standard mapping of attributes to HTML tags + + def init_tags + @attr_tags = [ + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "<b>", "</b>"), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "<tt>", "</tt>"), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "<em>", "</em>"), + ] + end + + ## + # Add a new set of HTML tags for an attribute. We allow separate start and + # end tags for flexibility. + + def add_tag(name, start, stop) + @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop) + end + + ## + # Given an HTML tag, decorate it with class information and the like if + # required. This is a no-op in the base class, but is overridden in HTML + # output classes that implement style sheets. + + def annotate(tag) + tag + end + + ## + # Here's the client side of the visitor pattern + + def start_accepting + @res = "" + @in_list_entry = [] + end + + def end_accepting + @res + end + + def accept_paragraph(am, fragment) + @res << annotate("<p>") + "\n" + @res << wrap(convert_flow(am.flow(fragment.txt))) + @res << annotate("</p>") + "\n" + end + + def accept_verbatim(am, fragment) + @res << annotate("<pre>") + "\n" + @res << CGI.escapeHTML(fragment.txt) + @res << annotate("</pre>") << "\n" + end + + def accept_rule(am, fragment) + size = fragment.param + size = 10 if size > 10 + @res << "<hr size=\"#{size}\"></hr>" + end + + def accept_list_start(am, fragment) + @res << html_list_name(fragment.type, true) << "\n" + @in_list_entry.push false + end + + def accept_list_end(am, fragment) + if tag = @in_list_entry.pop + @res << annotate(tag) << "\n" + end + @res << html_list_name(fragment.type, false) << "\n" + end + + def accept_list_item(am, fragment) + if tag = @in_list_entry.last + @res << annotate(tag) << "\n" + end + + @res << list_item_start(am, fragment) + + @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n" + + @in_list_entry[-1] = list_end_for(fragment.type) + end + + def accept_blank_line(am, fragment) + # @res << annotate("<p />") << "\n" + end + + def accept_heading(am, fragment) + @res << convert_heading(fragment.head_level, am.flow(fragment.txt)) + end + + ## + # This is a higher speed (if messier) version of wrap + + def wrap(txt, line_len = 76) + res = "" + sp = 0 + ep = txt.length + while sp < ep + # scan back for a space + p = sp + line_len - 1 + if p >= ep + p = ep + else + while p > sp and txt[p] != ?\s + p -= 1 + end + if p <= sp + p = sp + line_len + while p < ep and txt[p] != ?\s + p += 1 + end + end + end + res << txt[sp...p] << "\n" + sp = p + sp += 1 while sp < ep and txt[sp] == ?\s + end + res + end + + private + + def on_tags(res, item) + attr_mask = item.turn_on + return if attr_mask.zero? + + @attr_tags.each do |tag| + if attr_mask & tag.bit != 0 + res << annotate(tag.on) + @in_tt += 1 if tt?(tag) + end + end + end + + def off_tags(res, item) + attr_mask = item.turn_off + return if attr_mask.zero? + + @attr_tags.reverse_each do |tag| + if attr_mask & tag.bit != 0 + @in_tt -= 1 if tt?(tag) + res << annotate(tag.off) + end + end + end + + def convert_flow(flow) + res = "" + + flow.each do |item| + case item + when String + res << convert_string(item) + when RDoc::Markup::AttrChanger + off_tags(res, item) + on_tags(res, item) + when RDoc::Markup::Special + res << convert_special(item) + else + raise "Unknown flow element: #{item.inspect}" + end + end + + res + end + + def convert_string(item) + in_tt? ? convert_string_simple(item) : convert_string_fancy(item) + end + + def convert_string_simple(item) + CGI.escapeHTML item + end + + ## + # some of these patterns are taken from SmartyPants... + + def convert_string_fancy(item) + # convert -- to em-dash, (-- to en-dash) + item.gsub(/---?/, '—'). #gsub(/--/, '–'). + + # convert ... to elipsis (and make sure .... becomes .<elipsis>) + gsub(/\.\.\.\./, '.…').gsub(/\.\.\./, '…'). + + # convert single closing quote + gsub(%r{([^ \t\r\n\[\{\(])\'}, '\1’'). # } + gsub(%r{\'(?=\W|s\b)}, '’'). + + # convert single opening quote + gsub(/'/, '‘'). + + # convert double closing quote + gsub(%r{([^ \t\r\n\[\{\(])\'(?=\W)}, '\1”'). # } + + # convert double opening quote + gsub(/'/, '“'). + + # convert copyright + gsub(/\(c\)/, '©'). + + # convert and registered trademark + gsub(/\(r\)/, '®') + end + + def convert_special(special) + handled = false + RDoc::Markup::Attribute.each_name_of(special.type) do |name| + method_name = "handle_special_#{name}" + if self.respond_to? method_name + special.text = send(method_name, special) + handled = true + end + end + raise "Unhandled special: #{special}" unless handled + special.text + end + + def convert_heading(level, flow) + res = + annotate("<h#{level}>") + + convert_flow(flow) + + annotate("</h#{level}>\n") + end + + def html_list_name(list_type, is_open_tag) + tags = LIST_TYPE_TO_HTML[list_type] || raise("Invalid list type: #{list_type.inspect}") + annotate(tags[ is_open_tag ? 0 : 1]) + end + + def list_item_start(am, fragment) + case fragment.type + when :BULLET, :NUMBER then + annotate("<li>") + + when :UPPERALPHA then + annotate("<li type=\"A\">") + + when :LOWERALPHA then + annotate("<li type=\"a\">") + + when :LABELED then + annotate("<dt>") + + convert_flow(am.flow(fragment.param)) + + annotate("</dt>") + + annotate("<dd>") + + when :NOTE then + annotate("<tr>") + + annotate("<td valign=\"top\">") + + convert_flow(am.flow(fragment.param)) + + annotate("</td>") + + annotate("<td>") + else + raise "Invalid list type" + end + end + + def list_end_for(fragment_type) + case fragment_type + when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA then + "</li>" + when :LABELED then + "</dd>" + when :NOTE then + "</td></tr>" + else + raise "Invalid list type" + end + end + +end + |