관리-도구
편집 파일: to_html.rb
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