diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-01-14 03:34:05 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-01-14 03:34:05 +0000 |
commit | fcb0b1f503b392e446e8ee4b1033e1e7c0e7d0fe (patch) | |
tree | 347720b09a9bb0f08d442141b51ed074ce43e6b9 /lib/rdoc/markup | |
parent | cbd4604c530bd798db321a78d2f7bca4f568ba45 (diff) |
Renamespace lib/rdoc/markup from SM::SimpleMarkup to RDoc::Markup.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@15033 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rdoc/markup')
-rw-r--r-- | lib/rdoc/markup/.document | 2 | ||||
-rw-r--r-- | lib/rdoc/markup/fragments.rb (renamed from lib/rdoc/markup/simple_markup/fragments.rb) | 7 | ||||
-rw-r--r-- | lib/rdoc/markup/inline.rb (renamed from lib/rdoc/markup/simple_markup/inline.rb) | 16 | ||||
-rw-r--r-- | lib/rdoc/markup/lines.rb (renamed from lib/rdoc/markup/simple_markup/lines.rb) | 8 | ||||
-rw-r--r-- | lib/rdoc/markup/preprocess.rb | 71 | ||||
-rw-r--r-- | lib/rdoc/markup/simple_markup.rb | 463 | ||||
-rw-r--r-- | lib/rdoc/markup/simple_markup/preprocess.rb | 73 | ||||
-rw-r--r-- | lib/rdoc/markup/simple_markup/to_html.rb | 287 | ||||
-rw-r--r-- | lib/rdoc/markup/simple_markup/to_latex.rb | 332 | ||||
-rw-r--r-- | lib/rdoc/markup/to_flow.rb (renamed from lib/rdoc/markup/simple_markup/to_flow.rb) | 26 | ||||
-rw-r--r-- | lib/rdoc/markup/to_html.rb | 286 | ||||
-rw-r--r-- | lib/rdoc/markup/to_latex.rb | 331 |
12 files changed, 717 insertions, 1185 deletions
diff --git a/lib/rdoc/markup/.document b/lib/rdoc/markup/.document deleted file mode 100644 index 3cf4f21bd7..0000000000 --- a/lib/rdoc/markup/.document +++ /dev/null @@ -1,2 +0,0 @@ -simple_markup -simple_markup.rb diff --git a/lib/rdoc/markup/simple_markup/fragments.rb b/lib/rdoc/markup/fragments.rb index bd60ca0fec..2dd3614fc1 100644 --- a/lib/rdoc/markup/simple_markup/fragments.rb +++ b/lib/rdoc/markup/fragments.rb @@ -1,6 +1,7 @@ -require 'rdoc/markup/simple_markup/lines.rb' +require 'rdoc/markup' +require 'rdoc/markup/lines' -module SM +class RDoc::Markup ## # A Fragment is a chunk of text, subclassed as a paragraph, a list @@ -119,7 +120,7 @@ module SM ## # Collect groups of lines together. Each group will end up containing a flow - # of text + # of text. class LineCollection diff --git a/lib/rdoc/markup/simple_markup/inline.rb b/lib/rdoc/markup/inline.rb index eafda011e8..cbf5032a68 100644 --- a/lib/rdoc/markup/simple_markup/inline.rb +++ b/lib/rdoc/markup/inline.rb @@ -1,8 +1,10 @@ -module SM +require 'rdoc/markup' + +class RDoc::Markup ## # We manage a set of attributes. Each attribute has a symbol name and a bit - # value + # value. class Attribute SPECIAL = 1 @@ -39,7 +41,7 @@ module SM ## # An AttrChanger records a change in attributes. It contains a bitmap of the - # attributes to turn on, and a bitmap of those to turn off + # attributes to turn on, and a bitmap of those to turn off. AttrChanger = Struct.new(:turn_on, :turn_off) @@ -50,7 +52,7 @@ module SM end ## - # An array of attributes which parallels the characters in a string + # An array of attributes which parallels the characters in a string. class AttrSpan def initialize(length) @@ -84,12 +86,12 @@ module SM end def to_s - "Special: type=#{type}, name=#{SM::Attribute.as_string type}, text=#{text.dump}" + "Special: type=#{type}, name=#{RDoc::Markup::Attribute.as_string type}, text=#{text.dump}" end def inspect - "#<SM::Special:0x%x @type=%p, name=%p @text=%p>" % [ - object_id, @type, SM::Attribute.as_string(type), text.dump] + "#<RDoc::Markup::Special:0x%x @type=%p, name=%p @text=%p>" % [ + object_id, @type, RDoc::Markup::Attribute.as_string(type), text.dump] end end diff --git a/lib/rdoc/markup/simple_markup/lines.rb b/lib/rdoc/markup/lines.rb index 8ef66079b2..5fb5bde993 100644 --- a/lib/rdoc/markup/simple_markup/lines.rb +++ b/lib/rdoc/markup/lines.rb @@ -1,4 +1,4 @@ -module SM +class RDoc::Markup ## # We store the lines we're working on as objects of class Line. These @@ -14,7 +14,7 @@ module SM RULE = :RULE PARAGRAPH = :PARAGRAPH VERBATIM = :VERBATIM - + # line type attr_accessor :type @@ -36,7 +36,6 @@ module SM # true if this line has been deleted from the list of lines attr_accessor :deleted - def initialize(text) @text = text.dup @@ -69,7 +68,6 @@ module SM ## # Strip off the leading margin - # def strip_leading(size) if @text.size > size @@ -85,7 +83,7 @@ module SM end ## - # A container for all the lines + # A container for all the lines. class Lines diff --git a/lib/rdoc/markup/preprocess.rb b/lib/rdoc/markup/preprocess.rb new file mode 100644 index 0000000000..760351d386 --- /dev/null +++ b/lib/rdoc/markup/preprocess.rb @@ -0,0 +1,71 @@ +require 'rdoc/markup' + +## +# Handle common directives that can occur in a block of text: +# +# : include : filename + +class RDoc::Markup::PreProcess + + def initialize(input_file_name, include_path) + @input_file_name = input_file_name + @include_path = include_path + end + + ## + # Look for common options in a chunk of text. Options that we don't handle + # are passed back to our caller as |directive, param| + + def handle(text) + text.gsub!(/^([ \t#]*):(\w+):\s*(.+)?\n/) do + prefix = $1 + directive = $2.downcase + param = $3 + + case directive + when "include" + filename = param.split[0] + include_file(filename, prefix) + + else + yield(directive, param) + end + end + end + + private + + ## + # Include a file, indenting it correctly. + + def include_file(name, indent) + if full_name = find_include_file(name) then + content = File.open(full_name) {|f| f.read} + # strip leading '#'s, but only if all lines start with them + if content =~ /^[^#]/ + content.gsub(/^/, indent) + else + content.gsub(/^#?/, indent) + end + else + $stderr.puts "Couldn't find file to include: '#{name}'" + '' + end + end + + ## + # Look for the given file in the directory containing the current file, + # and then in each of the directories specified in the RDOC_INCLUDE path + + def find_include_file(name) + to_search = [ File.dirname(@input_file_name) ].concat @include_path + to_search.each do |dir| + full_name = File.join(dir, name) + stat = File.stat(full_name) rescue next + return full_name if stat.readable? + end + nil + end + +end + diff --git a/lib/rdoc/markup/simple_markup.rb b/lib/rdoc/markup/simple_markup.rb deleted file mode 100644 index ed3b4d3270..0000000000 --- a/lib/rdoc/markup/simple_markup.rb +++ /dev/null @@ -1,463 +0,0 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/lines.rb' - -## -# SimpleMarkup parses plain text documents and attempts to decompose -# them into their constituent parts. Some of these parts are high-level: -# paragraphs, chunks of verbatim text, list entries and the like. Other -# parts happen at the character level: a piece of bold text, a word in -# code font. This markup is similar in spirit to that used on WikiWiki -# webs, where folks create web pages using a simple set of formatting -# rules. -# -# SimpleMarkup itself does no output formatting: this is left to a -# different set of classes. -# -# SimpleMarkup is extendable at runtime: you can add new markup -# elements to be recognised in the documents that SimpleMarkup parses. -# -# SimpleMarkup is intended to be the basis for a family of tools which -# share the common requirement that simple, plain-text should be -# rendered in a variety of different output formats and media. It is -# envisaged that SimpleMarkup could be the basis for formating RDoc -# style comment blocks, Wiki entries, and online FAQs. -# -# = Basic Formatting -# -# * SimpleMarkup looks for a document's natural left margin. This is -# used as the initial margin for the document. -# -# * Consecutive lines starting at this margin are considered to be a -# paragraph. -# -# * If a paragraph starts with a "*", "-", or with "<digit>.", then it is -# taken to be the start of a list. The margin in increased to be the -# first non-space following the list start flag. Subsequent lines -# should be indented to this new margin until the list ends. For -# example: -# -# * this is a list with three paragraphs in -# the first item. This is the first paragraph. -# -# And this is the second paragraph. -# -# 1. This is an indented, numbered list. -# 2. This is the second item in that list -# -# This is the third conventional paragraph in the -# first list item. -# -# * This is the second item in the original list -# -# * You can also construct labeled lists, sometimes called description -# or definition lists. Do this by putting the label in square brackets -# and indenting the list body: -# -# [cat] a small furry mammal -# that seems to sleep a lot -# -# [ant] a little insect that is known -# to enjoy picnics -# -# A minor variation on labeled lists uses two colons to separate the -# label from the list body: -# -# cat:: a small furry mammal -# that seems to sleep a lot -# -# ant:: a little insect that is known -# to enjoy picnics -# -# This latter style guarantees that the list bodies' left margins are -# aligned: think of them as a two column table. -# -# * Any line that starts to the right of the current margin is treated -# as verbatim text. This is useful for code listings. The example of a -# list above is also verbatim text. -# -# * A line starting with an equals sign (=) is treated as a -# heading. Level one headings have one equals sign, level two headings -# have two,and so on. -# -# * A line starting with three or more hyphens (at the current indent) -# generates a horizontal rule. THe more hyphens, the thicker the rule -# (within reason, and if supported by the output device) -# -# * You can use markup within text (except verbatim) to change the -# appearance of parts of that text. Out of the box, SimpleMarkup -# supports word-based and general markup. -# -# Word-based markup uses flag characters around individual words: -# -# [\*word*] displays word in a *bold* font -# [\_word_] displays word in an _emphasized_ font -# [\+word+] displays word in a +code+ font -# -# General markup affects text between a start delimiter and and end -# delimiter. Not surprisingly, these delimiters look like HTML markup. -# -# [\<b>text...</b>] displays word in a *bold* font -# [\<em>text...</em>] displays word in an _emphasized_ font -# [\<i>text...</i>] displays word in an _emphasized_ font -# [\<tt>text...</tt>] displays word in a +code+ font -# -# Unlike conventional Wiki markup, general markup can cross line -# boundaries. You can turn off the interpretation of markup by -# preceding the first character with a backslash, so \\\<b>bold -# text</b> and \\\*bold* produce \<b>bold text</b> and \*bold -# respectively. -# -# = Using SimpleMarkup -# -# For information on using SimpleMarkup programatically, see SM::SimpleMarkup. -#-- -# Author:: Dave Thomas, dave@pragmaticprogrammer.com -# Version:: 0.0 -# License:: Ruby license - -module SM - - # == Synopsis - # - # This code converts <tt>input_string</tt>, which is in the format - # described in markup/simple_markup.rb, to HTML. The conversion - # takes place in the +convert+ method, so you can use the same - # SimpleMarkup object to convert multiple input strings. - # - # require 'rdoc/markup/simple_markup' - # require 'rdoc/markup/simple_markup/to_html' - # - # p = SM::SimpleMarkup.new - # h = SM::ToHtml.new - # - # puts p.convert(input_string, h) - # - # You can extend the SimpleMarkup parser to recognise new markup - # sequences, and to add special processing for text that matches a - # regular epxression. Here we make WikiWords significant to the parser, - # and also make the sequences {word} and \<no>text...</no> signify - # strike-through text. When then subclass the HTML output class to deal - # with these: - # - # require 'rdoc/markup/simple_markup' - # require 'rdoc/markup/simple_markup/to_html' - # - # class WikiHtml < SM::ToHtml - # def handle_special_WIKIWORD(special) - # "<font color=red>" + special.text + "</font>" - # end - # end - # - # p = SM::SimpleMarkup.new - # p.add_word_pair("{", "}", :STRIKE) - # p.add_html("no", :STRIKE) - # - # p.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) - # - # h = WikiHtml.new - # h.add_tag(:STRIKE, "<strike>", "</strike>") - # - # puts "<body>" + p.convert(ARGF.read, h) + "</body>" - - class SimpleMarkup - - SPACE = ?\s - - # List entries look like: - # * text - # 1. text - # [label] text - # label:: text - # - # Flag it as a list entry, and work out the indent for subsequent lines - - SIMPLE_LIST_RE = /^( - ( \* (?# bullet) - |- (?# bullet) - |\d+\. (?# numbered ) - |[A-Za-z]\. (?# alphabetically numbered ) - ) - \s+ - )\S/x - - LABEL_LIST_RE = /^( - ( \[.*?\] (?# labeled ) - |\S.*:: (?# note ) - )(?:\s+|$) - )/x - - ## - # Take a block of text and use various heuristics to determine it's - # structure (paragraphs, lists, and so on). Invoke an event handler as we - # identify significant chunks. - - def initialize - @am = AttributeManager.new - @output = nil - end - - ## - # Add to the sequences used to add formatting to an individual word (such - # as *bold*). Matching entries will generate attibutes that the output - # formatters can recognize by their +name+. - - def add_word_pair(start, stop, name) - @am.add_word_pair(start, stop, name) - end - - ## - # Add to the sequences recognized as general markup. - - def add_html(tag, name) - @am.add_html(tag, name) - end - - ## - # Add to other inline sequences. For example, we could add WikiWords using - # something like: - # - # parser.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) - # - # Each wiki word will be presented to the output formatter via the - # accept_special method. - - def add_special(pattern, name) - @am.add_special(pattern, name) - end - - ## - # We take a string, split it into lines, work out the type of each line, - # and from there deduce groups of lines (for example all lines in a - # paragraph). We then invoke the output formatter using a Visitor to - # display the result. - - def convert(str, op) - @lines = Lines.new(str.split(/\r?\n/).collect { |aLine| - Line.new(aLine) }) - return "" if @lines.empty? - @lines.normalize - assign_types_to_lines - group = group_lines - # call the output formatter to handle the result - # group.to_a.each {|i| p i} - group.accept(@am, op) - end - - private - - ## - # Look through the text at line indentation. We flag each line as being - # Blank, a paragraph, a list element, or verbatim text. - - def assign_types_to_lines(margin = 0, level = 0) - - while line = @lines.next - if line.isBlank? - line.stamp(Line::BLANK, level) - next - end - - # if a line contains non-blanks before the margin, then it must belong - # to an outer level - - text = line.text - - for i in 0...margin - if text[i] != SPACE - @lines.unget - return - end - end - - active_line = text[margin..-1] - - # Rules (horizontal lines) look like - # - # --- (three or more hyphens) - # - # The more hyphens, the thicker the rule - # - - if /^(---+)\s*$/ =~ active_line - line.stamp(Line::RULE, level, $1.length-2) - next - end - - # Then look for list entries. First the ones that have to have - # text following them (* xxx, - xxx, and dd. xxx) - - if SIMPLE_LIST_RE =~ active_line - - offset = margin + $1.length - prefix = $2 - prefix_length = prefix.length - - flag = case prefix - when "*","-" then ListBase::BULLET - when /^\d/ then ListBase::NUMBER - when /^[A-Z]/ then ListBase::UPPERALPHA - when /^[a-z]/ then ListBase::LOWERALPHA - else raise "Invalid List Type: #{self.inspect}" - end - - line.stamp(Line::LIST, level+1, prefix, flag) - text[margin, prefix_length] = " " * prefix_length - assign_types_to_lines(offset, level + 1) - next - end - - - if LABEL_LIST_RE =~ active_line - offset = margin + $1.length - prefix = $2 - prefix_length = prefix.length - - next if handled_labeled_list(line, level, margin, offset, prefix) - end - - # Headings look like - # = Main heading - # == Second level - # === Third - # - # Headings reset the level to 0 - - if active_line[0] == ?= and active_line =~ /^(=+)\s*(.*)/ - prefix_length = $1.length - prefix_length = 6 if prefix_length > 6 - line.stamp(Line::HEADING, 0, prefix_length) - line.strip_leading(margin + prefix_length) - next - end - - # If the character's a space, then we have verbatim text, - # otherwise - - if active_line[0] == SPACE - line.strip_leading(margin) if margin > 0 - line.stamp(Line::VERBATIM, level) - else - line.stamp(Line::PARAGRAPH, level) - end - end - end - - ## - # Handle labeled list entries, We have a special case to deal with. - # Because the labels can be long, they force the remaining block of text - # over the to right: - # - # this is a long label that I wrote:: and here is the - # block of text with - # a silly margin - # - # So we allow the special case. If the label is followed by nothing, and - # if the following line is indented, then we take the indent of that line - # as the new margin. - # - # this is a long label that I wrote:: - # here is a more reasonably indented block which - # will be attached to the label. - # - - def handled_labeled_list(line, level, margin, offset, prefix) - prefix_length = prefix.length - text = line.text - flag = nil - case prefix - when /^\[/ - flag = ListBase::LABELED - prefix = prefix[1, prefix.length-2] - when /:$/ - flag = ListBase::NOTE - prefix.chop! - else raise "Invalid List Type: #{self.inspect}" - end - - # body is on the next line - - if text.length <= offset - original_line = line - line = @lines.next - return(false) unless line - text = line.text - - for i in 0..margin - if text[i] != SPACE - @lines.unget - return false - end - end - i = margin - i += 1 while text[i] == SPACE - if i >= text.length - @lines.unget - return false - else - offset = i - prefix_length = 0 - @lines.delete(original_line) - end - end - - line.stamp(Line::LIST, level+1, prefix, flag) - text[margin, prefix_length] = " " * prefix_length - assign_types_to_lines(offset, level + 1) - return true - end - - ## - # Return a block consisting of fragments which are paragraphs, list - # entries or verbatim text. We merge consecutive lines of the same type - # and level together. We are also slightly tricky with lists: the lines - # following a list introduction look like paragraph lines at the next - # level, and we remap them into list entries instead. - - def group_lines - @lines.rewind - - inList = false - wantedType = wantedLevel = nil - - block = LineCollection.new - group = nil - - while line = @lines.next - if line.level == wantedLevel and line.type == wantedType - group.add_text(line.text) - else - group = block.fragment_for(line) - block.add(group) - if line.type == Line::LIST - wantedType = Line::PARAGRAPH - else - wantedType = line.type - end - wantedLevel = line.type == Line::HEADING ? line.param : line.level - end - end - - block.normalize - block - end - - ## - # For debugging, we allow access to our line contents as text. - - def content - @lines.as_text - end - public :content - - ## - # For debugging, return the list of line types. - - def get_line_types - @lines.line_types - end - public :get_line_types - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/preprocess.rb b/lib/rdoc/markup/simple_markup/preprocess.rb deleted file mode 100644 index c7d29ad48f..0000000000 --- a/lib/rdoc/markup/simple_markup/preprocess.rb +++ /dev/null @@ -1,73 +0,0 @@ -module SM - - ## - # Handle common directives that can occur in a block of text: - # - # : include : filename - - class PreProcess - - def initialize(input_file_name, include_path) - @input_file_name = input_file_name - @include_path = include_path - end - - ## - # Look for common options in a chunk of text. Options that we don't handle - # are passed back to our caller as |directive, param| - - def handle(text) - text.gsub!(/^([ \t#]*):(\w+):\s*(.+)?\n/) do - prefix = $1 - directive = $2.downcase - param = $3 - - case directive - when "include" - filename = param.split[0] - include_file(filename, prefix) - - else - yield(directive, param) - end - end - end - - private - - ## - # Include a file, indenting it correctly. - - def include_file(name, indent) - if (full_name = find_include_file(name)) - content = File.open(full_name) {|f| f.read} - # strip leading '#'s, but only if all lines start with them - if content =~ /^[^#]/ - content.gsub(/^/, indent) - else - content.gsub(/^#?/, indent) - end - else - $stderr.puts "Couldn't find file to include: '#{name}'" - '' - end - end - - ## - # Look for the given file in the directory containing the current file, - # and then in each of the directories specified in the RDOC_INCLUDE path - - def find_include_file(name) - to_search = [ File.dirname(@input_file_name) ].concat @include_path - to_search.each do |dir| - full_name = File.join(dir, name) - stat = File.stat(full_name) rescue next - return full_name if stat.readable? - end - nil - end - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/to_html.rb b/lib/rdoc/markup/simple_markup/to_html.rb deleted file mode 100644 index d78468684f..0000000000 --- a/lib/rdoc/markup/simple_markup/to_html.rb +++ /dev/null @@ -1,287 +0,0 @@ -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 - diff --git a/lib/rdoc/markup/simple_markup/to_latex.rb b/lib/rdoc/markup/simple_markup/to_latex.rb deleted file mode 100644 index b014b6990b..0000000000 --- a/lib/rdoc/markup/simple_markup/to_latex.rb +++ /dev/null @@ -1,332 +0,0 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/inline' - -require 'cgi' - -module SM - - # Convert SimpleMarkup to basic LaTeX report format - - class ToLaTeX - - BS = "\020" # \ - OB = "\021" # { - CB = "\022" # } - DL = "\023" # Dollar - - BACKSLASH = "#{BS}symbol#{OB}92#{CB}" - HAT = "#{BS}symbol#{OB}94#{CB}" - BACKQUOTE = "#{BS}symbol#{OB}0#{CB}" - TILDE = "#{DL}#{BS}sim#{DL}" - LESSTHAN = "#{DL}<#{DL}" - GREATERTHAN = "#{DL}>#{DL}" - - def self.l(str) - str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL) - end - - def l(arg) - SM::ToLaTeX.l(arg) - end - - LIST_TYPE_TO_LATEX = { - ListBase::BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ], - ListBase::NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ], - ListBase::UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ], - ListBase::LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ], - ListBase::LABELED => [ l("\\begin{description}"), l("\\end{description}") ], - ListBase::NOTE => [ - l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"), - l("\\end{tabularx}") ], - } - - InlineTag = Struct.new(:bit, :on, :off) - - def initialize - init_tags - @list_depth = 0 - @prev_list_types = [] - end - - ## - # Set up the standard mapping of attributes to LaTeX - - def init_tags - @attr_tags = [ - InlineTag.new(SM::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")), - InlineTag.new(SM::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")), - InlineTag.new(SM::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")), - ] - end - - ## - # Escape a LaTeX string - - def escape(str) -# $stderr.print "FE: ", str - s = str. -# sub(/\s+$/, ''). - gsub(/([_\${}&%#])/, "#{BS}\\1"). - gsub(/\\/, BACKSLASH). - gsub(/\^/, HAT). - gsub(/~/, TILDE). - gsub(/</, LESSTHAN). - gsub(/>/, GREATERTHAN). - gsub(/,,/, ",{},"). - gsub(/\`/, BACKQUOTE) -# $stderr.print "-> ", s, "\n" - s - end - - ## - # Add a new set of LaTeX 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 - - ## - # Here's the client side of the visitor pattern - - def start_accepting - @res = "" - @in_list_entry = [] - end - - def end_accepting - @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$') - end - - def accept_paragraph(am, fragment) - @res << wrap(convert_flow(am.flow(fragment.txt))) - @res << "\n" - end - - def accept_verbatim(am, fragment) - @res << "\n\\begin{code}\n" - @res << fragment.txt.sub(/[\n\s]+\Z/, '') - @res << "\n\\end{code}\n\n" - end - - def accept_rule(am, fragment) - size = fragment.param - size = 10 if size > 10 - @res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n" - end - - def accept_list_start(am, fragment) - @res << 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 << tag << "\n" - end - @res << list_name(fragment.type, false) << "\n" - end - - def accept_list_item(am, fragment) - if tag = @in_list_entry.last - @res << 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 << "\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 << 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 << tag.off - end - end - end - - def convert_flow(flow) - res = "" - flow.each do |item| - case item - when String -# $stderr.puts "Converting '#{item}'" - 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) - escape(item). - - # convert ... to elipsis (and make sure .... becomes .<elipsis>) - gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}'). - - # 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\)/, '\copyright{}') - - 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 = - case level - when 1 then "\\chapter{" - when 2 then "\\section{" - when 3 then "\\subsection{" - when 4 then "\\subsubsection{" - else "\\paragraph{" - end + - convert_flow(flow) + - "}\n" - end - - def list_name(list_type, is_open_tag) - tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}") - if tags[2] # enumerate - if is_open_tag - @list_depth += 1 - if @prev_list_types[@list_depth] != tags[2] - case @list_depth - when 1 - roman = "i" - when 2 - roman = "ii" - when 3 - roman = "iii" - when 4 - roman = "iv" - else - raise("Too deep list: level #{@list_depth}") - end - @prev_list_types[@list_depth] = tags[2] - return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0] - end - else - @list_depth -= 1 - end - end - tags[ is_open_tag ? 0 : 1] - end - - def list_item_start(am, fragment) - case fragment.type - when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, - ListBase::LOWERALPHA - "\\item " - - when ListBase::LABELED - "\\item[" + convert_flow(am.flow(fragment.param)) + "] " - - when ListBase::NOTE - convert_flow(am.flow(fragment.param)) + " & " - 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, ListBase::LABELED - "" - when ListBase::NOTE - "\\\\\n" - else - raise "Invalid list type" - end - end - - end - -end - diff --git a/lib/rdoc/markup/simple_markup/to_flow.rb b/lib/rdoc/markup/to_flow.rb index fc8c5f5ce6..58ae52be24 100644 --- a/lib/rdoc/markup/simple_markup/to_flow.rb +++ b/lib/rdoc/markup/to_flow.rb @@ -1,8 +1,8 @@ -require 'rdoc/markup/simple_markup/fragments' -require 'rdoc/markup/simple_markup/inline' +require 'rdoc/markup/fragments' +require 'rdoc/markup/inline' require 'cgi' -module SM +class RDoc::Markup module Flow P = Struct.new(:body) @@ -24,12 +24,12 @@ module SM class ToFlow LIST_TYPE_TO_HTML = { - SM::ListBase::BULLET => [ "<ul>", "</ul>" ], - SM::ListBase::NUMBER => [ "<ol>", "</ol>" ], - SM::ListBase::UPPERALPHA => [ "<ol>", "</ol>" ], - SM::ListBase::LOWERALPHA => [ "<ol>", "</ol>" ], - SM::ListBase::LABELED => [ "<dl>", "</dl>" ], - SM::ListBase::NOTE => [ "<table>", "</table>" ], + RDoc::Markup::ListBase::BULLET => [ "<ul>", "</ul>" ], + RDoc::Markup::ListBase::NUMBER => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::UPPERALPHA => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::LOWERALPHA => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::LABELED => [ "<dl>", "</dl>" ], + RDoc::Markup::ListBase::NOTE => [ "<table>", "</table>" ], } InlineTag = Struct.new(:bit, :on, :off) @@ -43,9 +43,9 @@ module SM 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>"), + 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 @@ -54,7 +54,7 @@ module SM # end tags for flexibility def add_tag(name, start, stop) - @attr_tags << InlineTag.new(SM::Attribute.bitmap_for(name), start, stop) + @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop) end ## diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb new file mode 100644 index 0000000000..98259cfd16 --- /dev/null +++ b/lib/rdoc/markup/to_html.rb @@ -0,0 +1,286 @@ +require 'rdoc/markup/fragments' +require 'rdoc/markup/inline' + +require 'cgi' + +class RDoc::Markup::ToHtml + + LIST_TYPE_TO_HTML = { + RDoc::Markup::ListBase::BULLET => [ "<ul>", "</ul>" ], + RDoc::Markup::ListBase::NUMBER => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::UPPERALPHA => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::LOWERALPHA => [ "<ol>", "</ol>" ], + RDoc::Markup::ListBase::LABELED => [ "<dl>", "</dl>" ], + RDoc::Markup::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(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) + 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 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 + + ## + # 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 + 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 RDoc::Markup::ListBase::BULLET, RDoc::Markup::ListBase::NUMBER then + annotate("<li>") + + when RDoc::Markup::ListBase::UPPERALPHA then + annotate("<li type=\"A\">") + + when RDoc::Markup::ListBase::LOWERALPHA then + annotate("<li type=\"a\">") + + when RDoc::Markup::ListBase::LABELED then + annotate("<dt>") + + convert_flow(am.flow(fragment.param)) + + annotate("</dt>") + + annotate("<dd>") + + when RDoc::Markup::ListBase::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 RDoc::Markup::ListBase::BULLET, RDoc::Markup::ListBase::NUMBER, + RDoc::Markup::ListBase::UPPERALPHA, + RDoc::Markup::ListBase::LOWERALPHA then + "</li>" + when RDoc::Markup::ListBase::LABELED then + "</dd>" + when RDoc::Markup::ListBase::NOTE then + "</td></tr>" + else + raise "Invalid list type" + end + end + +end + diff --git a/lib/rdoc/markup/to_latex.rb b/lib/rdoc/markup/to_latex.rb new file mode 100644 index 0000000000..2964e315bd --- /dev/null +++ b/lib/rdoc/markup/to_latex.rb @@ -0,0 +1,331 @@ +require 'rdoc/markup/fragments' +require 'rdoc/markup/inline' + +require 'cgi' + +## +# Convert SimpleMarkup to basic LaTeX report format. + +class RDoc::Markup::ToLaTeX + + BS = "\020" # \ + OB = "\021" # { + CB = "\022" # } + DL = "\023" # Dollar + + BACKSLASH = "#{BS}symbol#{OB}92#{CB}" + HAT = "#{BS}symbol#{OB}94#{CB}" + BACKQUOTE = "#{BS}symbol#{OB}0#{CB}" + TILDE = "#{DL}#{BS}sim#{DL}" + LESSTHAN = "#{DL}<#{DL}" + GREATERTHAN = "#{DL}>#{DL}" + + def self.l(str) + str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL) + end + + def l(arg) + RDoc::Markup::ToLaTeX.l(arg) + end + + LIST_TYPE_TO_LATEX = { + ListBase::BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ], + ListBase::NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ], + ListBase::UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ], + ListBase::LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ], + ListBase::LABELED => [ l("\\begin{description}"), l("\\end{description}") ], + ListBase::NOTE => [ + l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"), + l("\\end{tabularx}") ], + } + + InlineTag = Struct.new(:bit, :on, :off) + + def initialize + init_tags + @list_depth = 0 + @prev_list_types = [] + end + + ## + # Set up the standard mapping of attributes to LaTeX + + def init_tags + @attr_tags = [ + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")), + InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")), + ] + end + + ## + # Escape a LaTeX string + + def escape(str) +$stderr.print "FE: ", str + s = str. + sub(/\s+$/, ''). + gsub(/([_\${}&%#])/, "#{BS}\\1"). + gsub(/\\/, BACKSLASH). + gsub(/\^/, HAT). + gsub(/~/, TILDE). + gsub(/</, LESSTHAN). + gsub(/>/, GREATERTHAN). + gsub(/,,/, ",{},"). + gsub(/\`/, BACKQUOTE) +$stderr.print "-> ", s, "\n" + s + end + + ## + # Add a new set of LaTeX 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 + + ## + # Here's the client side of the visitor pattern + + def start_accepting + @res = "" + @in_list_entry = [] + end + + def end_accepting + @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$') + end + + def accept_paragraph(am, fragment) + @res << wrap(convert_flow(am.flow(fragment.txt))) + @res << "\n" + end + + def accept_verbatim(am, fragment) + @res << "\n\\begin{code}\n" + @res << fragment.txt.sub(/[\n\s]+\Z/, '') + @res << "\n\\end{code}\n\n" + end + + def accept_rule(am, fragment) + size = fragment.param + size = 10 if size > 10 + @res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n" + end + + def accept_list_start(am, fragment) + @res << 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 << tag << "\n" + end + @res << list_name(fragment.type, false) << "\n" + end + + def accept_list_item(am, fragment) + if tag = @in_list_entry.last + @res << 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 << "\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 << 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 << tag.off + end + end + end + + def convert_flow(flow) + res = "" + flow.each do |item| + case item + when String + $stderr.puts "Converting '#{item}'" + 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) + escape(item). + + # convert ... to elipsis (and make sure .... becomes .<elipsis>) + gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}'). + + # 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\)/, '\copyright{}') + + 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 = + case level + when 1 then "\\chapter{" + when 2 then "\\section{" + when 3 then "\\subsection{" + when 4 then "\\subsubsection{" + else "\\paragraph{" + end + + convert_flow(flow) + + "}\n" + end + + def list_name(list_type, is_open_tag) + tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}") + if tags[2] # enumerate + if is_open_tag + @list_depth += 1 + if @prev_list_types[@list_depth] != tags[2] + case @list_depth + when 1 + roman = "i" + when 2 + roman = "ii" + when 3 + roman = "iii" + when 4 + roman = "iv" + else + raise("Too deep list: level #{@list_depth}") + end + @prev_list_types[@list_depth] = tags[2] + return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0] + end + else + @list_depth -= 1 + end + end + tags[ is_open_tag ? 0 : 1] + end + + def list_item_start(am, fragment) + case fragment.type + when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, + ListBase::LOWERALPHA + "\\item " + + when ListBase::LABELED + "\\item[" + convert_flow(am.flow(fragment.param)) + "] " + + when ListBase::NOTE + convert_flow(am.flow(fragment.param)) + " & " + 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, ListBase::LABELED + "" + when ListBase::NOTE + "\\\\\n" + else + raise "Invalid list type" + end + end + +end + +d + |