PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

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

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