BBRedCloth 0.8.0

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 (51) hide show
  1. data/BBRedCloth.gemspec +36 -0
  2. data/CHANGELOG +123 -0
  3. data/COPYING +18 -0
  4. data/Manifest +45 -0
  5. data/README.textile +149 -0
  6. data/Rakefile +247 -0
  7. data/bin/bbredcloth +34 -0
  8. data/ext/mingw-rbconfig.rb +176 -0
  9. data/ext/redcloth_scan/extconf.rb +9 -0
  10. data/ext/redcloth_scan/redcloth.h +196 -0
  11. data/ext/redcloth_scan/redcloth_attributes.c +655 -0
  12. data/ext/redcloth_scan/redcloth_bbcode.c +2457 -0
  13. data/ext/redcloth_scan/redcloth_bbcode_inline.c +1890 -0
  14. data/ext/redcloth_scan/redcloth_inline.c +12387 -0
  15. data/ext/redcloth_scan/redcloth_scan.c +10848 -0
  16. data/extras/ragel_profiler.rb +73 -0
  17. data/lib/case_sensitive_require/RedCloth.rb +6 -0
  18. data/lib/redcloth/erb_extension.rb +27 -0
  19. data/lib/redcloth/formatters/base.rb +57 -0
  20. data/lib/redcloth/formatters/html.rb +487 -0
  21. data/lib/redcloth/formatters/latex.rb +249 -0
  22. data/lib/redcloth/formatters/latex_entities.yml +2414 -0
  23. data/lib/redcloth/textile_doc.rb +196 -0
  24. data/lib/redcloth/version.rb +28 -0
  25. data/lib/redcloth.rb +37 -0
  26. data/setup.rb +1585 -0
  27. data/test/basic.yml +933 -0
  28. data/test/code.yml +229 -0
  29. data/test/definitions.yml +82 -0
  30. data/test/extra_whitespace.yml +64 -0
  31. data/test/filter_html.yml +177 -0
  32. data/test/filter_pba.yml +20 -0
  33. data/test/helper.rb +109 -0
  34. data/test/html.yml +313 -0
  35. data/test/images.yml +254 -0
  36. data/test/instiki.yml +38 -0
  37. data/test/links.yml +275 -0
  38. data/test/lists.yml +283 -0
  39. data/test/poignant.yml +89 -0
  40. data/test/sanitize_html.yml +42 -0
  41. data/test/table.yml +267 -0
  42. data/test/test_custom_tags.rb +46 -0
  43. data/test/test_erb.rb +13 -0
  44. data/test/test_extensions.rb +31 -0
  45. data/test/test_formatters.rb +24 -0
  46. data/test/test_parser.rb +73 -0
  47. data/test/test_restrictions.rb +61 -0
  48. data/test/textism.yml +484 -0
  49. data/test/threshold.yml +772 -0
  50. data/test/validate_fixtures.rb +74 -0
  51. metadata +139 -0
@@ -0,0 +1,73 @@
1
+ class RagelProfiler
2
+ MEM_CONVERSION = 1024
3
+
4
+ COMMANDS = { :compile => %w(ragel rlgen-cd gcc-4.0 gnumake cc1),
5
+ :test => %w(ruby) }
6
+
7
+ FIELDS = %w(compile_time compile_max_rss test_time test_max_rss ext_so_size)
8
+
9
+ @@results = {}
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ @@results[name] = []
14
+ end
15
+
16
+ def measure(type)
17
+ raise "not a valid type" unless COMMANDS.keys.include?(type)
18
+ regex = COMMANDS[type].map {|c| Regexp.escape(c) }.join("|")
19
+ t = Thread.new do
20
+ Thread.current[:max] = 0
21
+ loop do
22
+ Thread.current[:max] = [run(regex), Thread.current[:max]].max
23
+ sleep 0.5
24
+ end
25
+ end
26
+ begin_time = Time.now
27
+ yield
28
+ total_time = Time.now - begin_time
29
+
30
+ t.kill
31
+ store_result(type, "time", total_time)
32
+ store_result(type, "max_rss", t[:max])
33
+ end
34
+
35
+ def ext_size(ext_so)
36
+ store_result(:ext_so, "size", File.size(ext_so) / MEM_CONVERSION)
37
+ end
38
+
39
+ def self.results
40
+ out = []
41
+ out << "name\t" + FIELDS.join("\t")
42
+ @@results.each do |name, results|
43
+ out << [name, results ].flatten.join("\t")
44
+ end
45
+ out.join("\n")
46
+ end
47
+
48
+ private
49
+
50
+ def store_result(type, metric, value)
51
+ index = FIELDS.index("#{type.to_s}_#{metric}")
52
+ @@results[@name][index] = "%.2f" % value
53
+ end
54
+
55
+ def run(ps_regex)
56
+ ps_command = "ps axucww"
57
+ ps_output = `#{ps_command}`
58
+ fields = ps_output.to_a.first.downcase.split
59
+ memory_index = fields.index("rss")
60
+ pid_index = fields.index("pid")
61
+ ppid_index = fields.index("ppid")
62
+ total = ps_output.grep(/(#{ps_regex})\s+$/i).map do |com|
63
+ Float(com.split[memory_index]).abs
64
+ end.inject(0) { |s,v| s += v }
65
+ if total
66
+ return total/MEM_CONVERSION
67
+ else
68
+ STDERR.puts "Command not found. No processes found matching #{ps_regex}."
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,6 @@
1
+ # A workaround to make Rails 2.1 gem dependency easier on case-sensitive filesystems.
2
+ # Since the gem name is RedCloth and the file is redcloth.rb, config.gem 'RedCloth' doesn't
3
+ # work. You'd have to use config.gem 'RedCloth', :lib => 'redcloth', and that's not
4
+ # immediately obvious. This file remedies that.
5
+ #
6
+ require File.join(File.dirname(__FILE__), '..', 'redcloth')
@@ -0,0 +1,27 @@
1
+ class ERB
2
+ module Util
3
+
4
+ #
5
+ # A utility method for transforming Textile in _s_ to HTML.
6
+ #
7
+ # require "erb"
8
+ # include ERB::Util
9
+ #
10
+ # puts textilize("Isn't ERB *great*?")
11
+ #
12
+ # _Generates_
13
+ #
14
+ # <p>Isn&#8217;t <span class="caps">ERB</span> <strong>great</strong>?</p>
15
+ #
16
+ def textilize( s )
17
+ if s && s.respond_to?(:to_s)
18
+ RedCloth.new( s.to_s ).to_html
19
+ end
20
+ end
21
+
22
+ alias t textilize
23
+ module_function :t
24
+ module_function :textilize
25
+
26
+ end
27
+ end
@@ -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,487 @@
1
+ unless defined?(NET::HTTP)
2
+ require 'net/http'
3
+ end
4
+
5
+ unless defined?(URI)
6
+ require 'URI'
7
+ end
8
+
9
+ unless defined?(REXML::Document)
10
+ require 'rexml/document'
11
+ end
12
+
13
+ module RedCloth::Formatters::HTML
14
+ include RedCloth::Formatters::Base
15
+
16
+ SWEARWORDS = [
17
+ {:code=>/bullshit/,:replace=>"bullcrap"},
18
+ {:code=>/f[^a-zA-Z0-9]*[u*]+[^a-zA-Z0-9]*c[^a-zA-Z0-9]*k(er)?/,:replace=>"****"},
19
+ {:code=>/sh[i1!]t(t?y)?/,:replace=>"****"},
20
+ {:code=>/tits/,:replace=>"****"},
21
+ {:code=>/motherfucker/,:replace=>"******"},
22
+ {:code=>/bitch/,:replace=>"****"},
23
+ {:code=>/cunt/,:replace=>"****"},
24
+ {:code=>/asshole/,:replace=>"*******"},
25
+ {:code=>/cocksucker/,:replace=>"****"},
26
+ {:code=>/cock/,:replace=>"****"},
27
+ {:code=>/n(!|\||\!|i|1|\*)gg((!|3|e|\*)(!|r|4)|a)/,:replace=>"African-American"},
28
+ {:code=>/fag(got)?/,:replace=>"***"}
29
+ ]
30
+
31
+ # escapement for regular HTML (not in PRE tag)
32
+ def escape(text)
33
+ if self.filter_swears == true
34
+ for swear in SWEARWORDS
35
+ text.gsub!(swear[:code],swear[:replace])
36
+ end
37
+ end
38
+ html_esc(text)
39
+ end
40
+
41
+ # escapement for HTML in a PRE tag
42
+ def escape_pre(text)
43
+ html_esc(text, :html_escape_preformatted)
44
+ end
45
+
46
+ def escape_pre_bb(text)
47
+ text.gsub(/\"/,"&quot;")
48
+ end
49
+
50
+ # escaping for HTML attributes
51
+ def escape_attribute(text)
52
+ html_esc(text, :html_escape_attributes)
53
+ end
54
+
55
+ def after_transform(text)
56
+ text.chomp!
57
+ end
58
+
59
+ [:h1, :h2, :h3, :h4, :h5, :h6, :p, :pre, :div].each do |m|
60
+ define_method(m) do |opts|
61
+ "<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>\n"
62
+ end
63
+ end
64
+
65
+ [:strong, :code, :em, :i, :b, :ins, :sup, :sub, :span, :cite].each do |m|
66
+ define_method(m) do |opts|
67
+ opts[:block] = true
68
+ "<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>"
69
+ end
70
+ end
71
+
72
+ def hr(opts)
73
+ "<hr />\n"
74
+ end
75
+
76
+ def acronym(opts)
77
+ opts[:block] = true
78
+ "<acronym#{pba(opts)}>#{caps(:text => opts[:text])}</acronym>"
79
+ end
80
+
81
+ def caps(opts)
82
+ if no_span_caps
83
+ opts[:text]
84
+ else
85
+ opts[:class] = 'caps'
86
+ span(opts)
87
+ end
88
+ end
89
+
90
+ def del(opts)
91
+ opts[:block] = true
92
+ "<del#{pba(opts)}>#{opts[:text]}</del>"
93
+ end
94
+
95
+ [:ol, :ul].each do |m|
96
+ define_method("#{m}_open") do |opts|
97
+ opts[:block] = true
98
+ "#{"\n" if opts[:nest] > 1}#{"\t" * (opts[:nest] - 1)}<#{m}#{pba(opts)}>\n"
99
+ end
100
+ define_method("#{m}_close") do |opts|
101
+ "#{li_close}#{"\t" * (opts[:nest] - 1)}</#{m}>#{"\n" if opts[:nest] <= 1}"
102
+ end
103
+ end
104
+
105
+ def li_open(opts)
106
+ "#{li_close unless opts.delete(:first)}#{"\t" * opts[:nest]}<li#{pba(opts)}>#{opts[:text]}"
107
+ end
108
+
109
+ def li_close(opts=nil)
110
+ "</li>\n"
111
+ end
112
+
113
+ def dl_open(opts)
114
+ opts[:block] = true
115
+ "<dl#{pba(opts)}>\n"
116
+ end
117
+
118
+ def dl_close(opts=nil)
119
+ "</dl>\n"
120
+ end
121
+
122
+ [:dt, :dd].each do |m|
123
+ define_method(m) do |opts|
124
+ "\t<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>\n"
125
+ end
126
+ end
127
+
128
+ def td(opts)
129
+ tdtype = opts[:th] ? 'th' : 'td'
130
+ "\t\t<#{tdtype}#{pba(opts)}>#{opts[:text]}</#{tdtype}>\n"
131
+ end
132
+
133
+ def tr_open(opts)
134
+ "\t<tr#{pba(opts)}>\n"
135
+ end
136
+
137
+ def tr_close(opts)
138
+ "\t</tr>\n"
139
+ end
140
+
141
+ def table_open(opts)
142
+ "<table#{pba(opts)}>\n"
143
+ end
144
+
145
+ def table_close(opts)
146
+ "</table>\n"
147
+ end
148
+
149
+ def bc_open(opts)
150
+ opts[:block] = true
151
+ "<pre#{pba(opts)}>"
152
+ end
153
+
154
+ def bc_close(opts)
155
+ "</pre>\n"
156
+ end
157
+
158
+ def bq_open(opts)
159
+ opts[:block] = true
160
+ cite = opts[:cite] ? " cite=\"#{ escape_attribute opts[:cite] }\"" : ''
161
+ "<blockquote#{cite}#{pba(opts)}>\n"
162
+ end
163
+
164
+ def bq_close(opts)
165
+ "</blockquote>\n"
166
+ end
167
+
168
+ LINK_TEXT_WITH_TITLE_RE = /
169
+ ([^"]+?) # $text
170
+ \s?
171
+ \(([^)]+?)\) # $title
172
+ $
173
+ /x
174
+ def link(opts)
175
+ if opts[:name] =~ LINK_TEXT_WITH_TITLE_RE
176
+ md = LINK_TEXT_WITH_TITLE_RE.match(opts[:name])
177
+ opts[:name] = md[1]
178
+ opts[:title] = md[2]
179
+ elsif opts[:name].nil? && !opts[:href].nil?
180
+ opts[:name] = opts[:href]
181
+ end
182
+ "<a href=\"#{escape_attribute opts[:href]}\"#{pba(opts)}>#{opts[:name]}</a>"
183
+ end
184
+
185
+ def autolink(opts)
186
+ " #{link(opts)}"
187
+ end
188
+
189
+ def image(opts)
190
+ opts.delete(:align)
191
+ opts[:alt] = opts[:title]
192
+ img = "<img src=\"#{escape_attribute opts[:src]}\"#{pba(opts)} alt=\"#{escape_attribute opts[:alt].to_s}\" />"
193
+ img = "<a href=\"#{escape_attribute opts[:href]}\">#{img}</a>" if opts[:href]
194
+ img
195
+ end
196
+
197
+ def color(opts)
198
+ "<span style=\"color:#{opts[:color]};\">#{opts[:text]}</span>"
199
+ end
200
+
201
+ def bbsize(opts)
202
+ "<span style=\"font-size:#{opts[:size]}#{"em" unless %w(em px pt %).include?(opts[:size][-2..-1])};\">#{opts[:text]}</span>"
203
+ end
204
+
205
+ def bbalign(opts)
206
+ "<div style=\"text-align:#{opts[:bbalign]};\">#{opts[:text]}</div>"
207
+ end
208
+
209
+ def bbquote(opts)
210
+ quote = "<blockquote>"
211
+ quote += "<cite>#{opts[:cite]}</cite>" if opts[:cite]
212
+ quote += "#{opts[:text]}</blockquote>\n"
213
+ end
214
+
215
+ def bb_block_pre(opts)
216
+ #quote = "<blockquote>"
217
+ #quote += "<cite>#{opts[:cite]}</cite>" if opts[:cite]
218
+ #quote += opts[:text]+"</blockquote>\n"
219
+ "#{bb_pre(opts)}\n"
220
+ end
221
+
222
+ def bb_spoiler(opts)
223
+ "<span class=\"inline_spoiler\" style=\"color:#333;background-color:#333;\">#{"<span class=\"inline_spoiler_title\" style=\"color:#F00\">#{opts[:title]}</span>" if opts[:title]}#{opts[:text]}</span>"
224
+ end
225
+
226
+ def bb_block_spoiler(opts)
227
+ "<div class=\"spoiler_container\"><div class=\"spoileroncontainer\"><button type=\"button\" class=\"button spoileron\" title=\"Click to show the spoiler.\">#{opts[:title] || "Show Spoiler"}</button></div><div class=\"spoiler\"><div class=\"spoileroffcontainer\"><button type=\"button\" class=\"button spoileroff\" title=\"Click to hide the spoiler.\">hide spoiler</button><br/></div><div class=\"spoilertext\">#{opts[:text]}</div></div></div>"
228
+ end
229
+
230
+ def bb_pre(opts)
231
+ "<pre><code>#{opts[:text]}</code></pre>"
232
+ end
233
+
234
+ def footno(opts)
235
+ opts[:id] ||= opts[:text]
236
+ %Q{<sup class="footnote"><a href=\"#fn#{opts[:id]}\">#{opts[:text]}</a></sup>}
237
+ end
238
+
239
+ def fn(opts)
240
+ no = opts[:id]
241
+ opts[:id] = "fn#{no}"
242
+ opts[:class] = ["footnote", opts[:class]].compact.join(" ")
243
+ "<p#{pba(opts)}><sup>#{no}</sup> #{opts[:text]}</p>\n"
244
+ end
245
+
246
+ def snip(opts)
247
+ "<pre#{pba(opts)}><code>#{opts[:text]}</code></pre>\n"
248
+ end
249
+
250
+ def quote1(opts)
251
+ "&#8216;#{opts[:text]}&#8217;"
252
+ end
253
+
254
+ def quote2(opts)
255
+ "&#8220;#{opts[:text]}&#8221;"
256
+ end
257
+
258
+ def multi_paragraph_quote(opts)
259
+ "&#8220;#{opts[:text]}"
260
+ end
261
+
262
+ def ellipsis(opts)
263
+ "#{opts[:text]}&#8230;"
264
+ end
265
+
266
+ def emdash(opts)
267
+ "&#8212;"
268
+ end
269
+
270
+ def endash(opts)
271
+ " &#8211; "
272
+ end
273
+
274
+ def arrow(opts)
275
+ "&#8594;"
276
+ end
277
+
278
+ def dim(opts)
279
+ opts[:text].gsub!('x', '&#215;')
280
+ opts[:text].gsub!("'", '&#8242;')
281
+ opts[:text].gsub!('"', '&#8243;')
282
+ opts[:text]
283
+ end
284
+
285
+ def trademark(opts)
286
+ "&#8482;"
287
+ end
288
+
289
+ def registered(opts)
290
+ "&#174;"
291
+ end
292
+
293
+ def copyright(opts)
294
+ "&#169;"
295
+ end
296
+
297
+ def entity(opts)
298
+ "&#{opts[:text]};"
299
+ end
300
+
301
+ def amp(opts)
302
+ "&amp;"
303
+ end
304
+
305
+ def gt(opts)
306
+ "&gt;"
307
+ end
308
+
309
+ def lt(opts)
310
+ "&lt;"
311
+ end
312
+
313
+ def br(opts)
314
+ if hard_breaks == false
315
+ "\n"
316
+ else
317
+ "<br />\n"
318
+ end
319
+ end
320
+
321
+ def quot(opts)
322
+ "&quot;"
323
+ end
324
+
325
+ def squot(opts)
326
+ "&#8217;"
327
+ end
328
+
329
+ def apos(opts)
330
+ "&#39;"
331
+ end
332
+
333
+ def html(opts)
334
+ "#{opts[:text]}\n"
335
+ end
336
+
337
+ def youtube(opts)
338
+ #gdata.youtube.com/feeds/api/videos/Gkwhxh5txJ0
339
+
340
+ begin
341
+ url = URI.parse("http://gdata.youtube.com/feeds/api/videos/#{opts[:youtubeid]}")
342
+ req = Net::HTTP::Get.new(url.path)
343
+ res = Net::HTTP.start(url.host, url.port) {|http|
344
+ http.request(req)
345
+ }
346
+ d = REXML::Document.new(res.body)
347
+ title = d.elements["entry/title"].text
348
+ rescue
349
+ title = "http://www.youtube.com/watch?v=#{opts[:youtubeid]}"
350
+ end
351
+
352
+ case opts[:youtubefmt]
353
+ when "22"
354
+ width = 700
355
+ height = 395
356
+ when "18"
357
+ width = 480
358
+ height = 320
359
+ when "6"
360
+ width = 480
361
+ height = 320
362
+ else
363
+ width = 320
364
+ height = 240
365
+ end
366
+
367
+ out = "<div class=\"youtube\"><div class=\"youtube_title\"><a href=\"http://www.youtube.com/watch?v=#{opts[:youtubeid]}#{"&fmt=#{opts[:youtubefmt]}" if opts[:youtubefmt]}\">#{title}</a></div><div class=\"youtube_video\"><object width=\"#{width}\" height=\"#{height}\"><param name=\"movie\" value=\"http://www.youtube.com/v/#{opts[:youtubeid]}&hl=en&fs=1&\"></param><param name=\"allowFullScreen\" value=\"true\"></param><param name=\"allowscriptaccess\" value=\"always\"></param><embed src=\"http://www.youtube.com/v/#{opts[:youtubeid]}&hl=en&fs=1&\" type=\"application/x-shockwave-flash\" allowscriptaccess=\"always\" allowfullscreen=\"true\" width=\"#{width}\" height=\"#{height}\"></embed></object></div></div>\n"
368
+ out
369
+ end
370
+
371
+ def vimeo(opts)
372
+ begin
373
+ #http://www.vimeo.com/api/v2/video/6583886.xml
374
+
375
+ url = URI.parse("http://www.vimeo.com/api/v2/video/#{opts[:vimeoid]}.xml")
376
+ req = Net::HTTP::Get.new(url.path)
377
+ res = Net::HTTP.start(url.host, url.port) {|http|
378
+ http.request(req)
379
+ }
380
+ d = REXML::Document.new(res.body)
381
+ title = d.elements["videos/video/title"].text
382
+ out = "#{title}"
383
+ width = 480
384
+ height = 320
385
+ out = "<div class=\"youtube\"><div class=\"youtube_title\"><a href=\"http://vimeo.com/#{opts[:vimeoid]}\">#{title}</a></div><div class=\"youtube_video\"><object width=\"#{width}\" height=\"#{height}\"><param name=\"allowfullscreen\" value=\"true\" /><param name=\"allowscriptaccess\" value=\"always\" /><param name=\"movie\" value=\"http://vimeo.com/moogaloop.swf?clip_id=#{opts[:vimeoid]}&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;fullscreen=1\" /><embed src=\"http://vimeo.com/moogaloop.swf?clip_id=#{opts[:vimeoid]}&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;fullscreen=1\" type=\"application/x-shockwave-flash\" allowfullscreen=\"true\" allowscriptaccess=\"always\" width=\"#{width}\" height=\"#{height}\"></embed></object></div></div>"
386
+ rescue
387
+ out = "ERROR WITH VIMEO API! VIDEO DOES NOT EXIST? VIDEO NOT ALLOWED TO EMBED?"
388
+ end
389
+ out
390
+ end
391
+
392
+ def html_block(opts)
393
+ inline_html(:text => "#{opts[:indent_before_start]}#{opts[:start_tag]}#{opts[:indent_after_start]}") +
394
+ "#{opts[:text]}" +
395
+ inline_html(:text => "#{opts[:indent_before_end]}#{opts[:end_tag]}#{opts[:indent_after_end]}")
396
+ end
397
+
398
+ def notextile(opts)
399
+ if filter_html
400
+ html_esc(opts[:text], :html_escape_preformatted)
401
+ else
402
+ opts[:text]
403
+ end
404
+ end
405
+
406
+ def inline_html(opts)
407
+ if filter_html
408
+ html_esc(opts[:text], :html_escape_preformatted)
409
+ else
410
+ "#{opts[:text]}" # nil-safe
411
+ end
412
+ end
413
+
414
+ def ignored_line(opts)
415
+ opts[:text] + "\n"
416
+ end
417
+
418
+ def before_transform(text)
419
+ clean_html(text) if sanitize_html
420
+ end
421
+
422
+ # HTML cleansing stuff
423
+ BASIC_TAGS = {
424
+ 'a' => ['href', 'title'],
425
+ 'img' => ['src', 'alt', 'title'],
426
+ 'br' => [],
427
+ 'i' => nil,
428
+ 'u' => nil,
429
+ 'b' => nil,
430
+ 'pre' => nil,
431
+ 'kbd' => nil,
432
+ 'code' => ['lang'],
433
+ 'cite' => nil,
434
+ 'strong' => nil,
435
+ 'em' => nil,
436
+ 'ins' => nil,
437
+ 'sup' => nil,
438
+ 'sub' => nil,
439
+ 'del' => nil,
440
+ 'table' => nil,
441
+ 'tr' => nil,
442
+ 'td' => ['colspan', 'rowspan'],
443
+ 'th' => nil,
444
+ 'ol' => ['start'],
445
+ 'ul' => nil,
446
+ 'li' => nil,
447
+ 'p' => nil,
448
+ 'h1' => nil,
449
+ 'h2' => nil,
450
+ 'h3' => nil,
451
+ 'h4' => nil,
452
+ 'h5' => nil,
453
+ 'h6' => nil,
454
+ 'blockquote' => ['cite'],
455
+ 'notextile' => nil
456
+ }
457
+
458
+ # Clean unauthorized tags.
459
+ def clean_html( text, allowed_tags = BASIC_TAGS )
460
+ text.gsub!( /<!\[CDATA\[/, '' )
461
+ text.gsub!( /<(\/*)([A-Za-z]\w*)([^>]*?)(\s?\/?)>/ ) do |m|
462
+ raw = $~
463
+ tag = raw[2].downcase
464
+ if allowed_tags.has_key? tag
465
+ pcs = [tag]
466
+ allowed_tags[tag].each do |prop|
467
+ ['"', "'", ''].each do |q|
468
+ q2 = ( q != '' ? q : '\s' )
469
+ if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
470
+ attrv = $1
471
+ next if (prop == 'src' or prop == 'href') and not attrv =~ %r{^(http|https|ftp):}
472
+ pcs << "#{prop}=\"#{attrv.gsub('"', '\\"')}\""
473
+ break
474
+ end
475
+ end
476
+ end if allowed_tags[tag]
477
+ "<#{raw[1]}#{pcs.join " "}#{raw[4]}>"
478
+ else # Unauthorized tag
479
+ if block_given?
480
+ yield m
481
+ else
482
+ ''
483
+ end
484
+ end
485
+ end
486
+ end
487
+ end