PageRenderTime 77ms CodeModel.GetById 44ms app.highlight 28ms RepoModel.GetById 2ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/rdoc/markup/simple_markup/to_latex.rb

http://github.com/agross/netopenspace
Ruby | 333 lines | 312 code | 16 blank | 5 comment | 0 complexity | 84097e7796c10fc24b8d1977a78300d9 MD5 | raw file
  1require 'rdoc/markup/simple_markup/fragments'
  2require 'rdoc/markup/simple_markup/inline'
  3
  4require 'cgi'
  5
  6module SM
  7
  8  # Convert SimpleMarkup to basic LaTeX report format
  9
 10  class ToLaTeX
 11
 12    BS = "\020"   # \
 13    OB = "\021"   # {
 14    CB = "\022"   # }
 15    DL = "\023"   # Dollar
 16
 17    BACKSLASH   = "#{BS}symbol#{OB}92#{CB}"
 18    HAT         = "#{BS}symbol#{OB}94#{CB}"
 19    BACKQUOTE   = "#{BS}symbol#{OB}0#{CB}"
 20    TILDE       = "#{DL}#{BS}sim#{DL}"
 21    LESSTHAN    = "#{DL}<#{DL}"
 22    GREATERTHAN = "#{DL}>#{DL}"
 23
 24    def self.l(str)
 25      str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL)
 26    end
 27
 28    def l(arg)
 29      SM::ToLaTeX.l(arg)
 30    end
 31
 32    LIST_TYPE_TO_LATEX = {
 33      ListBase::BULLET =>  [ l("\\begin{itemize}"), l("\\end{itemize}") ],
 34      ListBase::NUMBER =>  [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ],
 35      ListBase::UPPERALPHA =>  [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ],
 36      ListBase::LOWERALPHA =>  [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ],
 37      ListBase::LABELED => [ l("\\begin{description}"), l("\\end{description}") ],
 38      ListBase::NOTE    => [
 39        l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"), 
 40        l("\\end{tabularx}") ],
 41    }
 42
 43    InlineTag = Struct.new(:bit, :on, :off)
 44
 45    def initialize
 46      init_tags
 47      @list_depth = 0
 48      @prev_list_types = []
 49    end
 50
 51    ##
 52    # Set up the standard mapping of attributes to LaTeX
 53    #
 54    def init_tags
 55      @attr_tags = [
 56        InlineTag.new(SM::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")),
 57        InlineTag.new(SM::Attribute.bitmap_for(:TT),   l("\\texttt{"), l("}")),
 58        InlineTag.new(SM::Attribute.bitmap_for(:EM),   l("\\emph{"), l("}")),
 59      ]
 60    end
 61
 62    ##
 63    # Escape a LaTeX string
 64    def escape(str)
 65# $stderr.print "FE: ", str
 66      s = str.
 67#        sub(/\s+$/, '').
 68        gsub(/([_\${}&%#])/, "#{BS}\\1").
 69        gsub(/\\/, BACKSLASH).
 70        gsub(/\^/, HAT).
 71        gsub(/~/,  TILDE).
 72        gsub(/</,  LESSTHAN).
 73        gsub(/>/,  GREATERTHAN).
 74        gsub(/,,/, ",{},").
 75        gsub(/\`/,  BACKQUOTE)
 76# $stderr.print "-> ", s, "\n"
 77      s
 78    end
 79
 80    ##
 81    # Add a new set of LaTeX tags for an attribute. We allow
 82    # separate start and end tags for flexibility
 83    #
 84    def add_tag(name, start, stop)
 85      @attr_tags << InlineTag.new(SM::Attribute.bitmap_for(name), start, stop)
 86    end
 87
 88
 89    ## 
 90    # Here's the client side of the visitor pattern
 91
 92    def start_accepting
 93      @res = ""
 94      @in_list_entry = []
 95    end
 96
 97    def end_accepting
 98      @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$')
 99    end
100
101    def accept_paragraph(am, fragment)
102      @res << wrap(convert_flow(am.flow(fragment.txt)))
103      @res << "\n"
104    end
105
106    def accept_verbatim(am, fragment)
107      @res << "\n\\begin{code}\n"
108      @res << fragment.txt.sub(/[\n\s]+\Z/, '')
109      @res << "\n\\end{code}\n\n"
110    end
111
112    def accept_rule(am, fragment)
113      size = fragment.param
114      size = 10 if size > 10
115      @res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n"
116    end
117
118    def accept_list_start(am, fragment)
119      @res << list_name(fragment.type, true) <<"\n"
120      @in_list_entry.push false
121    end
122
123    def accept_list_end(am, fragment)
124      if tag = @in_list_entry.pop
125        @res << tag << "\n"
126      end
127      @res << list_name(fragment.type, false) <<"\n"
128    end
129
130    def accept_list_item(am, fragment)
131      if tag = @in_list_entry.last
132        @res << tag << "\n"
133      end
134      @res << list_item_start(am, fragment)
135      @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n"
136      @in_list_entry[-1] = list_end_for(fragment.type)
137    end
138
139    def accept_blank_line(am, fragment)
140      # @res << "\n"
141    end
142
143    def accept_heading(am, fragment)
144      @res << convert_heading(fragment.head_level, am.flow(fragment.txt))
145    end
146
147    # This is a higher speed (if messier) version of wrap
148
149    def wrap(txt, line_len = 76)
150      res = ""
151      sp = 0
152      ep = txt.length
153      while sp < ep
154        # scan back for a space
155        p = sp + line_len - 1
156        if p >= ep
157          p = ep
158        else
159          while p > sp and txt[p] != ?\s
160            p -= 1
161          end
162          if p <= sp
163            p = sp + line_len
164            while p < ep and txt[p] != ?\s
165              p += 1
166            end
167          end
168        end
169        res << txt[sp...p] << "\n"
170        sp = p
171        sp += 1 while sp < ep and txt[sp] == ?\s
172      end
173      res
174    end
175
176    #######################################################################
177
178    private
179
180    #######################################################################
181
182    def on_tags(res, item)
183      attr_mask = item.turn_on
184      return if attr_mask.zero?
185
186      @attr_tags.each do |tag|
187        if attr_mask & tag.bit != 0
188          res << tag.on
189        end
190      end
191    end
192
193    def off_tags(res, item)
194      attr_mask = item.turn_off
195      return if attr_mask.zero?
196
197      @attr_tags.reverse_each do |tag|
198        if attr_mask & tag.bit != 0
199          res << tag.off
200        end
201      end
202    end
203
204    def convert_flow(flow)
205      res = ""
206      flow.each do |item|
207        case item
208        when String
209#          $stderr.puts "Converting '#{item}'"
210          res << convert_string(item)
211        when AttrChanger
212          off_tags(res, item)
213          on_tags(res,  item)
214        when Special
215          res << convert_special(item)
216        else
217          raise "Unknown flow element: #{item.inspect}"
218        end
219      end
220      res
221    end
222
223    # some of these patterns are taken from SmartyPants...
224
225    def convert_string(item)
226
227      escape(item).
228      
229      
230      # convert ... to elipsis (and make sure .... becomes .<elipsis>)
231        gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}').
232
233      # convert single closing quote
234        gsub(%r{([^ \t\r\n\[\{\(])\'}) { "#$1'" }.
235        gsub(%r{\'(?=\W|s\b)}) { "'" }.
236
237      # convert single opening quote
238        gsub(/'/, '`').
239
240      # convert double closing quote
241        gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}) { "#$1''" }.
242
243      # convert double opening quote
244        gsub(/"/, "``").
245
246      # convert copyright
247        gsub(/\(c\)/, '\copyright{}')
248
249    end
250
251    def convert_special(special)
252      handled = false
253      Attribute.each_name_of(special.type) do |name|
254        method_name = "handle_special_#{name}"
255        if self.respond_to? method_name
256          special.text = send(method_name, special)
257          handled = true
258        end
259      end
260      raise "Unhandled special: #{special}" unless handled
261      special.text
262    end
263
264    def convert_heading(level, flow)
265      res =
266        case level
267        when 1 then "\\chapter{"
268        when 2 then "\\section{"
269        when 3 then "\\subsection{"
270        when 4 then "\\subsubsection{"
271        else  "\\paragraph{"
272        end +
273        convert_flow(flow) + 
274        "}\n"
275    end
276
277    def list_name(list_type, is_open_tag)
278      tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}")
279      if tags[2] # enumerate
280        if is_open_tag
281          @list_depth += 1
282          if @prev_list_types[@list_depth] != tags[2]
283            case @list_depth
284            when 1
285              roman = "i"
286            when 2
287              roman = "ii"
288            when 3
289              roman = "iii"
290            when 4
291              roman = "iv"
292            else
293              raise("Too deep list: level #{@list_depth}")
294            end
295            @prev_list_types[@list_depth] = tags[2]
296            return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0]
297          end
298        else
299          @list_depth -= 1
300        end
301      end
302      tags[ is_open_tag ? 0 : 1]
303    end
304
305    def list_item_start(am, fragment)
306      case fragment.type
307      when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, ListBase::LOWERALPHA
308        "\\item "
309
310      when ListBase::LABELED
311        "\\item[" + convert_flow(am.flow(fragment.param)) + "] "
312
313      when ListBase::NOTE
314          convert_flow(am.flow(fragment.param)) + " & "
315      else
316        raise "Invalid list type"
317      end
318    end
319
320    def list_end_for(fragment_type)
321      case fragment_type
322      when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, ListBase::LOWERALPHA, ListBase::LABELED
323        ""
324      when ListBase::NOTE
325        "\\\\\n"
326      else
327        raise "Invalid list type"
328      end
329    end
330
331  end
332
333end