jeremy-RedCloth 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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