diff options
Diffstat (limited to 'lib/rdoc/markup/simple_markup/to_html.rb')
-rw-r--r-- | lib/rdoc/markup/simple_markup/to_html.rb | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/lib/rdoc/markup/simple_markup/to_html.rb b/lib/rdoc/markup/simple_markup/to_html.rb new file mode 100644 index 0000000000..26b5f4ce70 --- /dev/null +++ b/lib/rdoc/markup/simple_markup/to_html.rb @@ -0,0 +1,289 @@ +require 'rdoc/markup/simple_markup/fragments' +require 'rdoc/markup/simple_markup/inline' + +require 'cgi' + +module SM + + class ToHtml + + LIST_TYPE_TO_HTML = { + ListBase::BULLET => [ "<ul>", "</ul>" ], + ListBase::NUMBER => [ "<ol>", "</ol>" ], + ListBase::UPPERALPHA => [ "<ol>", "</ol>" ], + ListBase::LOWERALPHA => [ "<ol>", "</ol>" ], + ListBase::LABELED => [ "<dl>", "</dl>" ], + ListBase::NOTE => [ "<table>", "</table>" ], + } + + InlineTag = Struct.new(:bit, :on, :off) + + def initialize + init_tags + end + + ## + # Set up the standard mapping of attributes to HTML tags + # + def init_tags + @attr_tags = [ + InlineTag.new(SM::Attribute.bitmap_for(:BOLD), "<b>", "</b>"), + InlineTag.new(SM::Attribute.bitmap_for(:TT), "<tt>", "</tt>"), + InlineTag.new(SM::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(SM::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) + 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 + 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 AttrChanger + off_tags(res, item) + on_tags(res, item) + when Special + res << convert_special(item) + else + raise "Unknown flow element: #{item.inspect}" + end + end + res + end + + # some of these patterns are taken from SmartyPants... + + def convert_string(item) + CGI.escapeHTML(item). + + + # convert -- to em-dash, (-- to en-dash) + 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 + 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 ListBase::BULLET, ListBase::NUMBER + annotate("<li>") + + when ListBase::UPPERALPHA + annotate("<li type=\"A\">") + + when ListBase::LOWERALPHA + annotate("<li type=\"a\">") + + when ListBase::LABELED + annotate("<dt>") + + convert_flow(am.flow(fragment.param)) + + annotate("</dt>") + + annotate("<dd>") + + when ListBase::NOTE + 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 ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, ListBase::LOWERALPHA + "</li>" + when ListBase::LABELED + "</dd>" + when ListBase::NOTE + "</td></tr>" + else + raise "Invalid list type" + end + end + + end + +end |