jeremy-RedCloth 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGELOG +135 -0
  2. data/COPYING +18 -0
  3. data/Manifest +56 -0
  4. data/README +171 -0
  5. data/Rakefile +205 -0
  6. data/bin/redcloth +28 -0
  7. data/ext/mingw-rbconfig.rb +176 -0
  8. data/ext/redcloth_scan/extconf.rb +9 -0
  9. data/ext/redcloth_scan/redcloth.h +178 -0
  10. data/ext/redcloth_scan/redcloth_attributes.c.rl +56 -0
  11. data/ext/redcloth_scan/redcloth_attributes.java.rl +96 -0
  12. data/ext/redcloth_scan/redcloth_attributes.rl +33 -0
  13. data/ext/redcloth_scan/redcloth_common.c.rl +18 -0
  14. data/ext/redcloth_scan/redcloth_common.java.rl +18 -0
  15. data/ext/redcloth_scan/redcloth_common.rl +111 -0
  16. data/ext/redcloth_scan/redcloth_inline.c.rl +159 -0
  17. data/ext/redcloth_scan/redcloth_inline.java.rl +108 -0
  18. data/ext/redcloth_scan/redcloth_inline.rl +159 -0
  19. data/ext/redcloth_scan/redcloth_scan.c.rl +237 -0
  20. data/ext/redcloth_scan/redcloth_scan.java.rl +573 -0
  21. data/ext/redcloth_scan/redcloth_scan.rl +325 -0
  22. data/extras/ragel_profiler.rb +73 -0
  23. data/lib/case_sensitive_require/RedCloth.rb +6 -0
  24. data/lib/redcloth.rb +37 -0
  25. data/lib/redcloth/erb_extension.rb +27 -0
  26. data/lib/redcloth/formatters/base.rb +57 -0
  27. data/lib/redcloth/formatters/html.rb +353 -0
  28. data/lib/redcloth/formatters/latex.rb +275 -0
  29. data/lib/redcloth/formatters/latex_entities.yml +2414 -0
  30. data/lib/redcloth/textile_doc.rb +103 -0
  31. data/lib/redcloth/version.rb +28 -0
  32. data/setup.rb +1585 -0
  33. data/test/basic.yml +922 -0
  34. data/test/code.yml +229 -0
  35. data/test/definitions.yml +82 -0
  36. data/test/extra_whitespace.yml +64 -0
  37. data/test/filter_html.yml +177 -0
  38. data/test/filter_pba.yml +20 -0
  39. data/test/helper.rb +108 -0
  40. data/test/html.yml +311 -0
  41. data/test/images.yml +254 -0
  42. data/test/instiki.yml +38 -0
  43. data/test/links.yml +275 -0
  44. data/test/lists.yml +283 -0
  45. data/test/poignant.yml +89 -0
  46. data/test/sanitize_html.yml +42 -0
  47. data/test/table.yml +336 -0
  48. data/test/test_custom_tags.rb +58 -0
  49. data/test/test_erb.rb +13 -0
  50. data/test/test_extensions.rb +31 -0
  51. data/test/test_formatters.rb +24 -0
  52. data/test/test_parser.rb +73 -0
  53. data/test/test_restrictions.rb +41 -0
  54. data/test/textism.yml +480 -0
  55. data/test/threshold.yml +772 -0
  56. data/test/validate_fixtures.rb +74 -0
  57. metadata +133 -0
@@ -0,0 +1,57 @@
1
+ module RedCloth::Formatters
2
+ module Base
3
+
4
+ def pba(opts)
5
+ opts.delete(:style) if filter_styles
6
+ opts.delete(:class) if filter_classes
7
+ opts.delete(:id) if filter_ids
8
+
9
+ atts = ''
10
+ opts[:"text-align"] = opts.delete(:align)
11
+ opts[:style] += ';' if opts[:style] && (opts[:style][-1..-1] != ';')
12
+ [:float, :"text-align", :"vertical-align"].each do |a|
13
+ opts[:style] = "#{a}:#{opts[a]};#{opts[:style]}" if opts[a]
14
+ end
15
+ [:"padding-right", :"padding-left"].each do |a|
16
+ opts[:style] = "#{a}:#{opts[a]}em;#{opts[:style]}" if opts[a]
17
+ end
18
+ [:style, :class, :lang, :id, :colspan, :rowspan, :title, :start, :align].each do |a|
19
+ atts << " #{a}=\"#{ html_esc(opts[a].to_s, :html_escape_attributes) }\"" if opts[a]
20
+ end
21
+ atts
22
+ end
23
+
24
+ def ignore(opts)
25
+ opts[:text]
26
+ end
27
+ alias_method :notextile, :ignore
28
+
29
+ def redcloth_version(opts)
30
+ p(:text => "#{opts[:prefix]}#{RedCloth::VERSION}")
31
+ end
32
+
33
+ def inline_redcloth_version(opts)
34
+ RedCloth::VERSION::STRING
35
+ end
36
+
37
+ [:del_phrase, :sup_phrase, :sub_phrase, :span_phrase].each do |phrase_method|
38
+ method = phrase_method.to_s.split('_')[0]
39
+ define_method(phrase_method) do |opts|
40
+ "#{opts[:beginning_space]}#{self.send(method, opts)}"
41
+ end
42
+ end
43
+
44
+ def method_missing(method, opts)
45
+ opts[:text] || ""
46
+ end
47
+
48
+ def before_transform(text)
49
+
50
+ end
51
+
52
+ def after_transform(text)
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,353 @@
1
+ module RedCloth::Formatters::HTML
2
+ include RedCloth::Formatters::Base
3
+
4
+ # escapement for regular HTML (not in PRE tag)
5
+ def escape(text)
6
+ html_esc(text)
7
+ end
8
+
9
+ # escapement for HTML in a PRE tag
10
+ def escape_pre(text)
11
+ html_esc(text, :html_escape_preformatted)
12
+ end
13
+
14
+ # escaping for HTML attributes
15
+ def escape_attribute(text)
16
+ html_esc(text, :html_escape_attributes)
17
+ end
18
+
19
+ def after_transform(text)
20
+ text.chomp!
21
+ end
22
+
23
+ [:h1, :h2, :h3, :h4, :h5, :h6, :p, :pre, :div].each do |m|
24
+ define_method(m) do |opts|
25
+ "<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>\n"
26
+ end
27
+ end
28
+
29
+ [:strong, :code, :em, :i, :b, :ins, :sup, :sub, :span, :cite].each do |m|
30
+ define_method(m) do |opts|
31
+ opts[:block] = true
32
+ "<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>"
33
+ end
34
+ end
35
+
36
+ def hr(opts)
37
+ "<hr />\n"
38
+ end
39
+
40
+ def acronym(opts)
41
+ opts[:block] = true
42
+ "<acronym#{pba(opts)}>#{caps(:text => opts[:text])}</acronym>"
43
+ end
44
+
45
+ def caps(opts)
46
+ if no_span_caps
47
+ opts[:text]
48
+ else
49
+ opts[:class] = 'caps'
50
+ span(opts)
51
+ end
52
+ end
53
+
54
+ def del(opts)
55
+ opts[:block] = true
56
+ "<del#{pba(opts)}>#{opts[:text]}</del>"
57
+ end
58
+
59
+ [:ol, :ul].each do |m|
60
+ define_method("#{m}_open") do |opts|
61
+ opts[:block] = true
62
+ "#{"\n" if opts[:nest] > 1}#{"\t" * (opts[:nest] - 1)}<#{m}#{pba(opts)}>\n"
63
+ end
64
+ define_method("#{m}_close") do |opts|
65
+ "#{li_close}#{"\t" * (opts[:nest] - 1)}</#{m}>#{"\n" if opts[:nest] <= 1}"
66
+ end
67
+ end
68
+
69
+ def li_open(opts)
70
+ "#{li_close unless opts.delete(:first)}#{"\t" * opts[:nest]}<li#{pba(opts)}>#{opts[:text]}"
71
+ end
72
+
73
+ def li_close(opts=nil)
74
+ "</li>\n"
75
+ end
76
+
77
+ def dl_open(opts)
78
+ opts[:block] = true
79
+ "<dl#{pba(opts)}>\n"
80
+ end
81
+
82
+ def dl_close(opts=nil)
83
+ "</dl>\n"
84
+ end
85
+
86
+ [:dt, :dd].each do |m|
87
+ define_method(m) do |opts|
88
+ "\t<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>\n"
89
+ end
90
+ end
91
+
92
+ def td(opts)
93
+ tdtype = opts[:th] ? 'th' : 'td'
94
+ "\t\t<#{tdtype}#{pba(opts)}>#{opts[:text]}</#{tdtype}>\n"
95
+ end
96
+
97
+ def tr_open(opts)
98
+ "\t<tr#{pba(opts)}>\n"
99
+ end
100
+
101
+ def tr_close(opts)
102
+ "\t</tr>\n"
103
+ end
104
+
105
+ def table_open(opts)
106
+ "<table#{pba(opts)}>\n"
107
+ end
108
+
109
+ def table_close(opts)
110
+ "</table>\n"
111
+ end
112
+
113
+ def bc_open(opts)
114
+ opts[:block] = true
115
+ "<pre#{pba(opts)}>"
116
+ end
117
+
118
+ def bc_close(opts)
119
+ "</pre>\n"
120
+ end
121
+
122
+ def bq_open(opts)
123
+ opts[:block] = true
124
+ cite = opts[:cite] ? " cite=\"#{ escape_attribute opts[:cite] }\"" : ''
125
+ "<blockquote#{cite}#{pba(opts)}>\n"
126
+ end
127
+
128
+ def bq_close(opts)
129
+ "</blockquote>\n"
130
+ end
131
+
132
+ LINK_TEXT_WITH_TITLE_RE = /
133
+ ([^"]+?) # $text
134
+ \s?
135
+ \(([^)]+?)\) # $title
136
+ $
137
+ /x
138
+ def link(opts)
139
+ if opts[:name] =~ LINK_TEXT_WITH_TITLE_RE
140
+ md = LINK_TEXT_WITH_TITLE_RE.match(opts[:name])
141
+ opts[:name] = md[1]
142
+ opts[:title] = md[2]
143
+ end
144
+ "<a href=\"#{escape_attribute opts[:href]}\"#{pba(opts)}>#{opts[:name]}</a>"
145
+ end
146
+
147
+ def image(opts)
148
+ opts.delete(:align)
149
+ opts[:alt] = opts[:title]
150
+ img = "<img src=\"#{escape_attribute opts[:src]}\"#{pba(opts)} alt=\"#{escape_attribute opts[:alt].to_s}\" />"
151
+ img = "<a href=\"#{escape_attribute opts[:href]}\">#{img}</a>" if opts[:href]
152
+ img
153
+ end
154
+
155
+ def footno(opts)
156
+ opts[:id] ||= opts[:text]
157
+ %Q{<sup class="footnote"><a href=\"#fn#{opts[:id]}\">#{opts[:text]}</a></sup>}
158
+ end
159
+
160
+ def fn(opts)
161
+ no = opts[:id]
162
+ opts[:id] = "fn#{no}"
163
+ opts[:class] = ["footnote", opts[:class]].compact.join(" ")
164
+ "<p#{pba(opts)}><sup>#{no}</sup> #{opts[:text]}</p>\n"
165
+ end
166
+
167
+ def snip(opts)
168
+ "<pre#{pba(opts)}><code>#{opts[:text]}</code></pre>\n"
169
+ end
170
+
171
+ def quote1(opts)
172
+ "&#8216;#{opts[:text]}&#8217;"
173
+ end
174
+
175
+ def quote2(opts)
176
+ "&#8220;#{opts[:text]}&#8221;"
177
+ end
178
+
179
+ def multi_paragraph_quote(opts)
180
+ "&#8220;#{opts[:text]}"
181
+ end
182
+
183
+ def ellipsis(opts)
184
+ "#{opts[:text]}&#8230;"
185
+ end
186
+
187
+ def emdash(opts)
188
+ "&#8212;"
189
+ end
190
+
191
+ def endash(opts)
192
+ " &#8211; "
193
+ end
194
+
195
+ def arrow(opts)
196
+ "&#8594;"
197
+ end
198
+
199
+ def dim(opts)
200
+ opts[:text].gsub!('x', '&#215;')
201
+ opts[:text].gsub!("'", '&#8242;')
202
+ opts[:text].gsub!('"', '&#8243;')
203
+ opts[:text]
204
+ end
205
+
206
+ def trademark(opts)
207
+ "&#8482;"
208
+ end
209
+
210
+ def registered(opts)
211
+ "&#174;"
212
+ end
213
+
214
+ def copyright(opts)
215
+ "&#169;"
216
+ end
217
+
218
+ def entity(opts)
219
+ "&#{opts[:text]};"
220
+ end
221
+
222
+ def amp(opts)
223
+ "&amp;"
224
+ end
225
+
226
+ def gt(opts)
227
+ "&gt;"
228
+ end
229
+
230
+ def lt(opts)
231
+ "&lt;"
232
+ end
233
+
234
+ def br(opts)
235
+ if hard_breaks == false
236
+ "\n"
237
+ else
238
+ "<br />\n"
239
+ end
240
+ end
241
+
242
+ def quot(opts)
243
+ "&quot;"
244
+ end
245
+
246
+ def squot(opts)
247
+ "&#8217;"
248
+ end
249
+
250
+ def apos(opts)
251
+ "&#39;"
252
+ end
253
+
254
+ def html(opts)
255
+ "#{opts[:text]}\n"
256
+ end
257
+
258
+ def html_block(opts)
259
+ inline_html(:text => "#{opts[:indent_before_start]}#{opts[:start_tag]}#{opts[:indent_after_start]}") +
260
+ "#{opts[:text]}" +
261
+ inline_html(:text => "#{opts[:indent_before_end]}#{opts[:end_tag]}#{opts[:indent_after_end]}")
262
+ end
263
+
264
+ def notextile(opts)
265
+ if filter_html
266
+ html_esc(opts[:text], :html_escape_preformatted)
267
+ else
268
+ opts[:text]
269
+ end
270
+ end
271
+
272
+ def inline_html(opts)
273
+ if filter_html
274
+ html_esc(opts[:text], :html_escape_preformatted)
275
+ else
276
+ "#{opts[:text]}" # nil-safe
277
+ end
278
+ end
279
+
280
+ def ignored_line(opts)
281
+ opts[:text] + "\n"
282
+ end
283
+
284
+ def before_transform(text)
285
+ clean_html(text) if sanitize_html
286
+ end
287
+
288
+ # HTML cleansing stuff
289
+ BASIC_TAGS = {
290
+ 'a' => ['href', 'title'],
291
+ 'img' => ['src', 'alt', 'title'],
292
+ 'br' => [],
293
+ 'i' => nil,
294
+ 'u' => nil,
295
+ 'b' => nil,
296
+ 'pre' => nil,
297
+ 'kbd' => nil,
298
+ 'code' => ['lang'],
299
+ 'cite' => nil,
300
+ 'strong' => nil,
301
+ 'em' => nil,
302
+ 'ins' => nil,
303
+ 'sup' => nil,
304
+ 'sub' => nil,
305
+ 'del' => nil,
306
+ 'table' => nil,
307
+ 'tr' => nil,
308
+ 'td' => ['colspan', 'rowspan'],
309
+ 'th' => nil,
310
+ 'ol' => ['start'],
311
+ 'ul' => nil,
312
+ 'li' => nil,
313
+ 'p' => nil,
314
+ 'h1' => nil,
315
+ 'h2' => nil,
316
+ 'h3' => nil,
317
+ 'h4' => nil,
318
+ 'h5' => nil,
319
+ 'h6' => nil,
320
+ 'blockquote' => ['cite'],
321
+ 'notextile' => nil
322
+ }
323
+
324
+ # Clean unauthorized tags.
325
+ def clean_html( text, allowed_tags = BASIC_TAGS )
326
+ text.gsub!( /<!\[CDATA\[/, '' )
327
+ text.gsub!( /<(\/*)([A-Za-z]\w*)([^>]*?)(\s?\/?)>/ ) do |m|
328
+ raw = $~
329
+ tag = raw[2].downcase
330
+ if allowed_tags.has_key? tag
331
+ pcs = [tag]
332
+ allowed_tags[tag].each do |prop|
333
+ ['"', "'", ''].each do |q|
334
+ q2 = ( q != '' ? q : '\s' )
335
+ if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
336
+ attrv = $1
337
+ next if (prop == 'src' or prop == 'href') and not attrv =~ %r{^(http|https|ftp):}
338
+ pcs << "#{prop}=\"#{attrv.gsub('"', '\\"')}\""
339
+ break
340
+ end
341
+ end
342
+ end if allowed_tags[tag]
343
+ "<#{raw[1]}#{pcs.join " "}#{raw[4]}>"
344
+ else # Unauthorized tag
345
+ if block_given?
346
+ yield m
347
+ else
348
+ ''
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end
@@ -0,0 +1,275 @@
1
+ require 'yaml'
2
+
3
+ module RedCloth::Formatters::LATEX
4
+ include RedCloth::Formatters::Base
5
+
6
+ ENTITIES = YAML::load(File.read(File.dirname(__FILE__)+'/latex_entities.yml'))
7
+
8
+ module Settings
9
+ # Maps CSS style names to latex formatting options
10
+ def latex_image_styles
11
+ @latex_image_class_styles ||= {}
12
+ end
13
+ end
14
+
15
+ RedCloth::TextileDoc.send(:include, Settings)
16
+
17
+ def escape(text)
18
+ latex_esc(text)
19
+ end
20
+
21
+ def escape_pre(text)
22
+ text
23
+ end
24
+
25
+ # headers
26
+ { :h1 => 'section*',
27
+ :h2 => 'subsection*',
28
+ :h3 => 'subsubsection*',
29
+ :h4 => 'textbf',
30
+ :h5 => 'textbf',
31
+ :h6 => 'textbf',
32
+ }.each do |m,tag|
33
+ define_method(m) do |opts|
34
+ "\\#{tag}{#{opts[:text]}}\n\n"
35
+ end
36
+ end
37
+
38
+ # commands
39
+ { :strong => 'textbf',
40
+ :em => 'emph',
41
+ :i => 'textit',
42
+ :b => 'textbf',
43
+ :ins => 'underline',
44
+ :del => 'sout',
45
+ :acronym => 'MakeUppercase',
46
+ :caps => 'MakeUppercase',
47
+ }.each do |m,tag|
48
+ define_method(m) do |opts|
49
+ "\\#{tag}{#{opts[:text]}}"
50
+ end
51
+ end
52
+
53
+ { :sup => '\ensuremath{^\textrm{#1}}',
54
+ :sub => '\ensuremath{_\textrm{#1}}',
55
+ }.each do |m, expr|
56
+ define_method(m) do |opts|
57
+ expr.sub('#1', opts[:text])
58
+ end
59
+ end
60
+
61
+ # environments
62
+ { :pre => 'verbatim',
63
+ :code => 'verbatim',
64
+ :cite => 'quote',
65
+ }.each do |m, env|
66
+ define_method(m) do |opts|
67
+ begin_chunk(env) + opts[:text] + end_chunk(env)
68
+ end
69
+ end
70
+
71
+ # ignore (or find a good solution later)
72
+ [ :span,
73
+ :div,
74
+ ].each do |m|
75
+ define_method(m) do |opts|
76
+ opts[:text].to_s
77
+ end
78
+ end
79
+
80
+ { :ol => 'enumerate',
81
+ :ul => 'itemize',
82
+ }.each do |m, env|
83
+ define_method("#{m}_open") do |opts|
84
+ opts[:block] = true
85
+ "\\begin{#{env}}\n"
86
+ end
87
+ define_method("#{m}_close") do |opts|
88
+ "#{li_close}\\end{#{env}}\n\n"
89
+ end
90
+ end
91
+
92
+ def li_open(opts)
93
+ "#{li_close unless opts.delete(:first)}\t\\item #{opts[:text]}"
94
+ end
95
+
96
+ def li_close(opts=nil)
97
+ "\n"
98
+ end
99
+
100
+ def p(opts)
101
+ opts[:text] + "\n\n"
102
+ end
103
+
104
+ def td(opts)
105
+ column = @table_row.size
106
+ if opts[:colspan]
107
+ opts[:text] = "\\multicolumn{#{opts[:colspan]}}{ #{"l " * opts[:colspan].to_i}}{#{opts[:text]}}"
108
+ end
109
+ if opts[:rowspan]
110
+ @table_multirow_next[column] = opts[:rowspan].to_i - 1
111
+ opts[:text] = "\\multirow{#{opts[:rowspan]}}{*}{#{opts[:text]}}"
112
+ end
113
+ @table_row.push(opts[:text])
114
+ return ""
115
+ end
116
+
117
+ def tr_open(opts)
118
+ @table_row = []
119
+ return ""
120
+ end
121
+
122
+ def tr_close(opts)
123
+ multirow_columns = @table_multirow.find_all {|c,n| n > 0}
124
+ multirow_columns.each do |c,n|
125
+ @table_row.insert(c,"")
126
+ @table_multirow[c] -= 1
127
+ end
128
+ @table_multirow.merge!(@table_multirow_next)
129
+ @table_multirow_next = {}
130
+ @table.push(@table_row)
131
+ return ""
132
+ end
133
+
134
+ # We need to know the column count before opening tabular context.
135
+ def table_open(opts)
136
+ @table = []
137
+ @table_multirow = {}
138
+ @table_multirow_next = {}
139
+ return ""
140
+ end
141
+
142
+ def table_close(opts)
143
+ output = "\\begin{tabular}{ #{"l " * @table[0].size }}\n"
144
+ @table.each do |row|
145
+ output << " #{row.join(" & ")} \\\\\n"
146
+ end
147
+ output << "\\end{tabular}\n"
148
+ output
149
+ end
150
+
151
+ def bc_open(opts)
152
+ opts[:block] = true
153
+ begin_chunk("verbatim") + "\n"
154
+ end
155
+
156
+ def bc_close(opts)
157
+ end_chunk("verbatim") + "\n"
158
+ end
159
+
160
+ def bq_open(opts)
161
+ opts[:block] = true
162
+ "\\begin{quotation}\n"
163
+ end
164
+
165
+ def bq_close(opts)
166
+ "\\end{quotation}\n\n"
167
+ end
168
+
169
+ def link(opts)
170
+ "\\href{#{opts[:href]}}{#{opts[:name]}}"
171
+ end
172
+
173
+ # FIXME: use includegraphics with security verification
174
+ #
175
+ # Remember to use '\RequirePackage{graphicx}' in your LaTeX header
176
+ #
177
+ # FIXME: Look at dealing with width / height gracefully as this should be
178
+ # specified in a unit like cm rather than px.
179
+ def image(opts)
180
+ # Don't know how to use remote links, plus can we trust them?
181
+ return "" if opts[:src] =~ /^\w+\:\/\//
182
+ # Resolve CSS styles if any have been set
183
+ styling = opts[:class].to_s.split(/\s+/).collect { |style| latex_image_styles[style] }.compact.join ','
184
+ # Build latex code
185
+ [ "\\begin{figure}[htp]",
186
+ " \\includegraphics[#{styling}]{#{opts[:src]}}",
187
+ (" \\caption{#{escape opts[:title]}}" if opts[:title]),
188
+ (" \\label{#{escape opts[:alt]}}" if opts[:alt]),
189
+ "\\end{figure}",
190
+ ].compact.join "\n"
191
+ end
192
+
193
+ def footno(opts)
194
+ # TODO: insert a placeholder until we know the footnote content.
195
+ # For this to work, we need some kind of post-processing...
196
+ "\\footnotemark[#{opts[:text]}]"
197
+ end
198
+
199
+ def fn(opts)
200
+ "\\footnotetext[#{opts[:id]}]{#{opts[:text]}}"
201
+ end
202
+
203
+ def snip(opts)
204
+ "\\begin{verbatim}#{opts[:text]}\\end{verbatim}"
205
+ end
206
+
207
+ def quote1(opts)
208
+ "`#{opts[:text]}'"
209
+ end
210
+
211
+ def quote2(opts)
212
+ "``#{opts[:text]}\""
213
+ end
214
+
215
+ def ellipsis(opts)
216
+ "#{opts[:text]}\\ldots{}"
217
+ end
218
+
219
+ def emdash(opts)
220
+ "---"
221
+ end
222
+
223
+ def endash(opts)
224
+ "--"
225
+ end
226
+
227
+ def arrow(opts)
228
+ "\\rightarrow{}"
229
+ end
230
+
231
+ def trademark(opts)
232
+ "\\texttrademark{}"
233
+ end
234
+
235
+ def registered(opts)
236
+ "\\textregistered{}"
237
+ end
238
+
239
+ def copyright(opts)
240
+ "\\copyright{}"
241
+ end
242
+
243
+ # TODO: what do we do with (unknown) unicode entities ?
244
+ #
245
+ def entity(opts)
246
+ text = opts[:text][0..0] == '#' ? opts[:text][1..-1] : opts[:text]
247
+ ENTITIES[text]
248
+ end
249
+
250
+ def dim(opts)
251
+ space = opts[:space] ? " " : ''
252
+ "#{opts[:text]}#{space}\\texttimes{}#{space}"
253
+ end
254
+
255
+ private
256
+
257
+ # Use this for block level commands that use \begin
258
+ def begin_chunk(type)
259
+ chunk_counter[type] += 1
260
+ return "\\begin{#{type}}" if 1 == chunk_counter[type]
261
+ ''
262
+ end
263
+
264
+ # Use this for block level commands that use \end
265
+ def end_chunk(type)
266
+ chunk_counter[type] -= 1
267
+ raise RuntimeError, "Bad latex #{type} nesting detected" if chunk_counter[type] < 0 # This should never need to happen
268
+ return "\\end{#{type}}" if 0 == chunk_counter[type]
269
+ ''
270
+ end
271
+
272
+ def chunk_counter
273
+ @chunk_counter ||= Hash.new 0
274
+ end
275
+ end