##// END OF EJS Templates
Escape double-quotes in image titles....
Jean-Philippe Lang -
r2142:4ec5b1600a9e
parent child
Show More
@@ -1,1168 +1,1172
1 # vim:ts=4:sw=4:
1 # vim:ts=4:sw=4:
2 # = RedCloth - Textile and Markdown Hybrid for Ruby
2 # = RedCloth - Textile and Markdown Hybrid for Ruby
3 #
3 #
4 # Homepage:: http://whytheluckystiff.net/ruby/redcloth/
4 # Homepage:: http://whytheluckystiff.net/ruby/redcloth/
5 # Author:: why the lucky stiff (http://whytheluckystiff.net/)
5 # Author:: why the lucky stiff (http://whytheluckystiff.net/)
6 # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
6 # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
7 # License:: BSD
7 # License:: BSD
8 #
8 #
9 # (see http://hobix.com/textile/ for a Textile Reference.)
9 # (see http://hobix.com/textile/ for a Textile Reference.)
10 #
10 #
11 # Based on (and also inspired by) both:
11 # Based on (and also inspired by) both:
12 #
12 #
13 # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
13 # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
14 # Textism for PHP: http://www.textism.com/tools/textile/
14 # Textism for PHP: http://www.textism.com/tools/textile/
15 #
15 #
16 #
16 #
17
17
18 # = RedCloth
18 # = RedCloth
19 #
19 #
20 # RedCloth is a Ruby library for converting Textile and/or Markdown
20 # RedCloth is a Ruby library for converting Textile and/or Markdown
21 # into HTML. You can use either format, intermingled or separately.
21 # into HTML. You can use either format, intermingled or separately.
22 # You can also extend RedCloth to honor your own custom text stylings.
22 # You can also extend RedCloth to honor your own custom text stylings.
23 #
23 #
24 # RedCloth users are encouraged to use Textile if they are generating
24 # RedCloth users are encouraged to use Textile if they are generating
25 # HTML and to use Markdown if others will be viewing the plain text.
25 # HTML and to use Markdown if others will be viewing the plain text.
26 #
26 #
27 # == What is Textile?
27 # == What is Textile?
28 #
28 #
29 # Textile is a simple formatting style for text
29 # Textile is a simple formatting style for text
30 # documents, loosely based on some HTML conventions.
30 # documents, loosely based on some HTML conventions.
31 #
31 #
32 # == Sample Textile Text
32 # == Sample Textile Text
33 #
33 #
34 # h2. This is a title
34 # h2. This is a title
35 #
35 #
36 # h3. This is a subhead
36 # h3. This is a subhead
37 #
37 #
38 # This is a bit of paragraph.
38 # This is a bit of paragraph.
39 #
39 #
40 # bq. This is a blockquote.
40 # bq. This is a blockquote.
41 #
41 #
42 # = Writing Textile
42 # = Writing Textile
43 #
43 #
44 # A Textile document consists of paragraphs. Paragraphs
44 # A Textile document consists of paragraphs. Paragraphs
45 # can be specially formatted by adding a small instruction
45 # can be specially formatted by adding a small instruction
46 # to the beginning of the paragraph.
46 # to the beginning of the paragraph.
47 #
47 #
48 # h[n]. Header of size [n].
48 # h[n]. Header of size [n].
49 # bq. Blockquote.
49 # bq. Blockquote.
50 # # Numeric list.
50 # # Numeric list.
51 # * Bulleted list.
51 # * Bulleted list.
52 #
52 #
53 # == Quick Phrase Modifiers
53 # == Quick Phrase Modifiers
54 #
54 #
55 # Quick phrase modifiers are also included, to allow formatting
55 # Quick phrase modifiers are also included, to allow formatting
56 # of small portions of text within a paragraph.
56 # of small portions of text within a paragraph.
57 #
57 #
58 # \_emphasis\_
58 # \_emphasis\_
59 # \_\_italicized\_\_
59 # \_\_italicized\_\_
60 # \*strong\*
60 # \*strong\*
61 # \*\*bold\*\*
61 # \*\*bold\*\*
62 # ??citation??
62 # ??citation??
63 # -deleted text-
63 # -deleted text-
64 # +inserted text+
64 # +inserted text+
65 # ^superscript^
65 # ^superscript^
66 # ~subscript~
66 # ~subscript~
67 # @code@
67 # @code@
68 # %(classname)span%
68 # %(classname)span%
69 #
69 #
70 # ==notextile== (leave text alone)
70 # ==notextile== (leave text alone)
71 #
71 #
72 # == Links
72 # == Links
73 #
73 #
74 # To make a hypertext link, put the link text in "quotation
74 # To make a hypertext link, put the link text in "quotation
75 # marks" followed immediately by a colon and the URL of the link.
75 # marks" followed immediately by a colon and the URL of the link.
76 #
76 #
77 # Optional: text in (parentheses) following the link text,
77 # Optional: text in (parentheses) following the link text,
78 # but before the closing quotation mark, will become a Title
78 # but before the closing quotation mark, will become a Title
79 # attribute for the link, visible as a tool tip when a cursor is above it.
79 # attribute for the link, visible as a tool tip when a cursor is above it.
80 #
80 #
81 # Example:
81 # Example:
82 #
82 #
83 # "This is a link (This is a title) ":http://www.textism.com
83 # "This is a link (This is a title) ":http://www.textism.com
84 #
84 #
85 # Will become:
85 # Will become:
86 #
86 #
87 # <a href="http://www.textism.com" title="This is a title">This is a link</a>
87 # <a href="http://www.textism.com" title="This is a title">This is a link</a>
88 #
88 #
89 # == Images
89 # == Images
90 #
90 #
91 # To insert an image, put the URL for the image inside exclamation marks.
91 # To insert an image, put the URL for the image inside exclamation marks.
92 #
92 #
93 # Optional: text that immediately follows the URL in (parentheses) will
93 # Optional: text that immediately follows the URL in (parentheses) will
94 # be used as the Alt text for the image. Images on the web should always
94 # be used as the Alt text for the image. Images on the web should always
95 # have descriptive Alt text for the benefit of readers using non-graphical
95 # have descriptive Alt text for the benefit of readers using non-graphical
96 # browsers.
96 # browsers.
97 #
97 #
98 # Optional: place a colon followed by a URL immediately after the
98 # Optional: place a colon followed by a URL immediately after the
99 # closing ! to make the image into a link.
99 # closing ! to make the image into a link.
100 #
100 #
101 # Example:
101 # Example:
102 #
102 #
103 # !http://www.textism.com/common/textist.gif(Textist)!
103 # !http://www.textism.com/common/textist.gif(Textist)!
104 #
104 #
105 # Will become:
105 # Will become:
106 #
106 #
107 # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
107 # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
108 #
108 #
109 # With a link:
109 # With a link:
110 #
110 #
111 # !/common/textist.gif(Textist)!:http://textism.com
111 # !/common/textist.gif(Textist)!:http://textism.com
112 #
112 #
113 # Will become:
113 # Will become:
114 #
114 #
115 # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
115 # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
116 #
116 #
117 # == Defining Acronyms
117 # == Defining Acronyms
118 #
118 #
119 # HTML allows authors to define acronyms via the tag. The definition appears as a
119 # HTML allows authors to define acronyms via the tag. The definition appears as a
120 # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
120 # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
121 # this should be used at least once for each acronym in documents where they appear.
121 # this should be used at least once for each acronym in documents where they appear.
122 #
122 #
123 # To quickly define an acronym in Textile, place the full text in (parentheses)
123 # To quickly define an acronym in Textile, place the full text in (parentheses)
124 # immediately following the acronym.
124 # immediately following the acronym.
125 #
125 #
126 # Example:
126 # Example:
127 #
127 #
128 # ACLU(American Civil Liberties Union)
128 # ACLU(American Civil Liberties Union)
129 #
129 #
130 # Will become:
130 # Will become:
131 #
131 #
132 # <acronym title="American Civil Liberties Union">ACLU</acronym>
132 # <acronym title="American Civil Liberties Union">ACLU</acronym>
133 #
133 #
134 # == Adding Tables
134 # == Adding Tables
135 #
135 #
136 # In Textile, simple tables can be added by seperating each column by
136 # In Textile, simple tables can be added by seperating each column by
137 # a pipe.
137 # a pipe.
138 #
138 #
139 # |a|simple|table|row|
139 # |a|simple|table|row|
140 # |And|Another|table|row|
140 # |And|Another|table|row|
141 #
141 #
142 # Attributes are defined by style definitions in parentheses.
142 # Attributes are defined by style definitions in parentheses.
143 #
143 #
144 # table(border:1px solid black).
144 # table(border:1px solid black).
145 # (background:#ddd;color:red). |{}| | | |
145 # (background:#ddd;color:red). |{}| | | |
146 #
146 #
147 # == Using RedCloth
147 # == Using RedCloth
148 #
148 #
149 # RedCloth is simply an extension of the String class, which can handle
149 # RedCloth is simply an extension of the String class, which can handle
150 # Textile formatting. Use it like a String and output HTML with its
150 # Textile formatting. Use it like a String and output HTML with its
151 # RedCloth#to_html method.
151 # RedCloth#to_html method.
152 #
152 #
153 # doc = RedCloth.new "
153 # doc = RedCloth.new "
154 #
154 #
155 # h2. Test document
155 # h2. Test document
156 #
156 #
157 # Just a simple test."
157 # Just a simple test."
158 #
158 #
159 # puts doc.to_html
159 # puts doc.to_html
160 #
160 #
161 # By default, RedCloth uses both Textile and Markdown formatting, with
161 # By default, RedCloth uses both Textile and Markdown formatting, with
162 # Textile formatting taking precedence. If you want to turn off Markdown
162 # Textile formatting taking precedence. If you want to turn off Markdown
163 # formatting, to boost speed and limit the processor:
163 # formatting, to boost speed and limit the processor:
164 #
164 #
165 # class RedCloth::Textile.new( str )
165 # class RedCloth::Textile.new( str )
166
166
167 class RedCloth3 < String
167 class RedCloth3 < String
168
168
169 VERSION = '3.0.4'
169 VERSION = '3.0.4'
170 DEFAULT_RULES = [:textile, :markdown]
170 DEFAULT_RULES = [:textile, :markdown]
171
171
172 #
172 #
173 # Two accessor for setting security restrictions.
173 # Two accessor for setting security restrictions.
174 #
174 #
175 # This is a nice thing if you're using RedCloth for
175 # This is a nice thing if you're using RedCloth for
176 # formatting in public places (e.g. Wikis) where you
176 # formatting in public places (e.g. Wikis) where you
177 # don't want users to abuse HTML for bad things.
177 # don't want users to abuse HTML for bad things.
178 #
178 #
179 # If +:filter_html+ is set, HTML which wasn't
179 # If +:filter_html+ is set, HTML which wasn't
180 # created by the Textile processor will be escaped.
180 # created by the Textile processor will be escaped.
181 #
181 #
182 # If +:filter_styles+ is set, it will also disable
182 # If +:filter_styles+ is set, it will also disable
183 # the style markup specifier. ('{color: red}')
183 # the style markup specifier. ('{color: red}')
184 #
184 #
185 attr_accessor :filter_html, :filter_styles
185 attr_accessor :filter_html, :filter_styles
186
186
187 #
187 #
188 # Accessor for toggling hard breaks.
188 # Accessor for toggling hard breaks.
189 #
189 #
190 # If +:hard_breaks+ is set, single newlines will
190 # If +:hard_breaks+ is set, single newlines will
191 # be converted to HTML break tags. This is the
191 # be converted to HTML break tags. This is the
192 # default behavior for traditional RedCloth.
192 # default behavior for traditional RedCloth.
193 #
193 #
194 attr_accessor :hard_breaks
194 attr_accessor :hard_breaks
195
195
196 # Accessor for toggling lite mode.
196 # Accessor for toggling lite mode.
197 #
197 #
198 # In lite mode, block-level rules are ignored. This means
198 # In lite mode, block-level rules are ignored. This means
199 # that tables, paragraphs, lists, and such aren't available.
199 # that tables, paragraphs, lists, and such aren't available.
200 # Only the inline markup for bold, italics, entities and so on.
200 # Only the inline markup for bold, italics, entities and so on.
201 #
201 #
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
203 # r.to_html
203 # r.to_html
204 # #=> "And then? She <strong>fell</strong>!"
204 # #=> "And then? She <strong>fell</strong>!"
205 #
205 #
206 attr_accessor :lite_mode
206 attr_accessor :lite_mode
207
207
208 #
208 #
209 # Accessor for toggling span caps.
209 # Accessor for toggling span caps.
210 #
210 #
211 # Textile places `span' tags around capitalized
211 # Textile places `span' tags around capitalized
212 # words by default, but this wreaks havoc on Wikis.
212 # words by default, but this wreaks havoc on Wikis.
213 # If +:no_span_caps+ is set, this will be
213 # If +:no_span_caps+ is set, this will be
214 # suppressed.
214 # suppressed.
215 #
215 #
216 attr_accessor :no_span_caps
216 attr_accessor :no_span_caps
217
217
218 #
218 #
219 # Establishes the markup predence. Available rules include:
219 # Establishes the markup predence. Available rules include:
220 #
220 #
221 # == Textile Rules
221 # == Textile Rules
222 #
222 #
223 # The following textile rules can be set individually. Or add the complete
223 # The following textile rules can be set individually. Or add the complete
224 # set of rules with the single :textile rule, which supplies the rule set in
224 # set of rules with the single :textile rule, which supplies the rule set in
225 # the following precedence:
225 # the following precedence:
226 #
226 #
227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
228 # block_textile_table:: Textile table block structures
228 # block_textile_table:: Textile table block structures
229 # block_textile_lists:: Textile list structures
229 # block_textile_lists:: Textile list structures
230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
231 # inline_textile_image:: Textile inline images
231 # inline_textile_image:: Textile inline images
232 # inline_textile_link:: Textile inline links
232 # inline_textile_link:: Textile inline links
233 # inline_textile_span:: Textile inline spans
233 # inline_textile_span:: Textile inline spans
234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
235 #
235 #
236 # == Markdown
236 # == Markdown
237 #
237 #
238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
239 # block_markdown_setext:: Markdown setext headers
239 # block_markdown_setext:: Markdown setext headers
240 # block_markdown_atx:: Markdown atx headers
240 # block_markdown_atx:: Markdown atx headers
241 # block_markdown_rule:: Markdown horizontal rules
241 # block_markdown_rule:: Markdown horizontal rules
242 # block_markdown_bq:: Markdown blockquotes
242 # block_markdown_bq:: Markdown blockquotes
243 # block_markdown_lists:: Markdown lists
243 # block_markdown_lists:: Markdown lists
244 # inline_markdown_link:: Markdown links
244 # inline_markdown_link:: Markdown links
245 attr_accessor :rules
245 attr_accessor :rules
246
246
247 # Returns a new RedCloth object, based on _string_ and
247 # Returns a new RedCloth object, based on _string_ and
248 # enforcing all the included _restrictions_.
248 # enforcing all the included _restrictions_.
249 #
249 #
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
251 # r.to_html
251 # r.to_html
252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
253 #
253 #
254 def initialize( string, restrictions = [] )
254 def initialize( string, restrictions = [] )
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
256 super( string )
256 super( string )
257 end
257 end
258
258
259 #
259 #
260 # Generates HTML from the Textile contents.
260 # Generates HTML from the Textile contents.
261 #
261 #
262 # r = RedCloth.new( "And then? She *fell*!" )
262 # r = RedCloth.new( "And then? She *fell*!" )
263 # r.to_html( true )
263 # r.to_html( true )
264 # #=>"And then? She <strong>fell</strong>!"
264 # #=>"And then? She <strong>fell</strong>!"
265 #
265 #
266 def to_html( *rules )
266 def to_html( *rules )
267 rules = DEFAULT_RULES if rules.empty?
267 rules = DEFAULT_RULES if rules.empty?
268 # make our working copy
268 # make our working copy
269 text = self.dup
269 text = self.dup
270
270
271 @urlrefs = {}
271 @urlrefs = {}
272 @shelf = []
272 @shelf = []
273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
275 :inline_textile_code, :inline_textile_span, :glyphs_textile]
275 :inline_textile_code, :inline_textile_span, :glyphs_textile]
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
277 :block_markdown_bq, :block_markdown_lists,
277 :block_markdown_bq, :block_markdown_lists,
278 :inline_markdown_reflink, :inline_markdown_link]
278 :inline_markdown_reflink, :inline_markdown_link]
279 @rules = rules.collect do |rule|
279 @rules = rules.collect do |rule|
280 case rule
280 case rule
281 when :markdown
281 when :markdown
282 markdown_rules
282 markdown_rules
283 when :textile
283 when :textile
284 textile_rules
284 textile_rules
285 else
285 else
286 rule
286 rule
287 end
287 end
288 end.flatten
288 end.flatten
289
289
290 # standard clean up
290 # standard clean up
291 incoming_entities text
291 incoming_entities text
292 clean_white_space text
292 clean_white_space text
293
293
294 # start processor
294 # start processor
295 @pre_list = []
295 @pre_list = []
296 rip_offtags text
296 rip_offtags text
297 no_textile text
297 no_textile text
298 escape_html_tags text
298 escape_html_tags text
299 hard_break text
299 hard_break text
300 unless @lite_mode
300 unless @lite_mode
301 refs text
301 refs text
302 # need to do this before text is split by #blocks
302 # need to do this before text is split by #blocks
303 block_textile_quotes text
303 block_textile_quotes text
304 blocks text
304 blocks text
305 end
305 end
306 inline text
306 inline text
307 smooth_offtags text
307 smooth_offtags text
308
308
309 retrieve text
309 retrieve text
310
310
311 text.gsub!( /<\/?notextile>/, '' )
311 text.gsub!( /<\/?notextile>/, '' )
312 text.gsub!( /x%x%/, '&#38;' )
312 text.gsub!( /x%x%/, '&#38;' )
313 clean_html text if filter_html
313 clean_html text if filter_html
314 text.strip!
314 text.strip!
315 text
315 text
316
316
317 end
317 end
318
318
319 #######
319 #######
320 private
320 private
321 #######
321 #######
322 #
322 #
323 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
323 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
324 # (from PyTextile)
324 # (from PyTextile)
325 #
325 #
326 TEXTILE_TAGS =
326 TEXTILE_TAGS =
327
327
328 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
328 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
329 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
329 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
330 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
330 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
331 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
331 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
332 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
332 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
333
333
334 collect! do |a, b|
334 collect! do |a, b|
335 [a.chr, ( b.zero? and "" or "&#{ b };" )]
335 [a.chr, ( b.zero? and "" or "&#{ b };" )]
336 end
336 end
337
337
338 #
338 #
339 # Regular expressions to convert to HTML.
339 # Regular expressions to convert to HTML.
340 #
340 #
341 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
341 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
342 A_VLGN = /[\-^~]/
342 A_VLGN = /[\-^~]/
343 C_CLAS = '(?:\([^)]+\))'
343 C_CLAS = '(?:\([^)]+\))'
344 C_LNGE = '(?:\[[^\[\]]+\])'
344 C_LNGE = '(?:\[[^\[\]]+\])'
345 C_STYL = '(?:\{[^}]+\})'
345 C_STYL = '(?:\{[^}]+\})'
346 S_CSPN = '(?:\\\\\d+)'
346 S_CSPN = '(?:\\\\\d+)'
347 S_RSPN = '(?:/\d+)'
347 S_RSPN = '(?:/\d+)'
348 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
348 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
349 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
349 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
350 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
350 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
351 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
351 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
352 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
352 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
353 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
353 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
354 PUNCT_Q = Regexp::quote( '*-_+^~%' )
354 PUNCT_Q = Regexp::quote( '*-_+^~%' )
355 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
355 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
356
356
357 # Text markup tags, don't conflict with block tags
357 # Text markup tags, don't conflict with block tags
358 SIMPLE_HTML_TAGS = [
358 SIMPLE_HTML_TAGS = [
359 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
359 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
360 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
360 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
361 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
361 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
362 ]
362 ]
363
363
364 QTAGS = [
364 QTAGS = [
365 ['**', 'b', :limit],
365 ['**', 'b', :limit],
366 ['*', 'strong', :limit],
366 ['*', 'strong', :limit],
367 ['??', 'cite', :limit],
367 ['??', 'cite', :limit],
368 ['-', 'del', :limit],
368 ['-', 'del', :limit],
369 ['__', 'i', :limit],
369 ['__', 'i', :limit],
370 ['_', 'em', :limit],
370 ['_', 'em', :limit],
371 ['%', 'span', :limit],
371 ['%', 'span', :limit],
372 ['+', 'ins', :limit],
372 ['+', 'ins', :limit],
373 ['^', 'sup', :limit],
373 ['^', 'sup', :limit],
374 ['~', 'sub', :limit]
374 ['~', 'sub', :limit]
375 ]
375 ]
376 QTAGS.collect! do |rc, ht, rtype|
376 QTAGS.collect! do |rc, ht, rtype|
377 rcq = Regexp::quote rc
377 rcq = Regexp::quote rc
378 re =
378 re =
379 case rtype
379 case rtype
380 when :limit
380 when :limit
381 /(^|[>\s\(])
381 /(^|[>\s\(])
382 (#{rcq})
382 (#{rcq})
383 (#{C})
383 (#{C})
384 (?::(\S+?))?
384 (?::(\S+?))?
385 ([^\s\-].*?[^\s\-]|\w)
385 ([^\s\-].*?[^\s\-]|\w)
386 #{rcq}
386 #{rcq}
387 (?=[[:punct:]]|\s|\)|$)/x
387 (?=[[:punct:]]|\s|\)|$)/x
388 else
388 else
389 /(#{rcq})
389 /(#{rcq})
390 (#{C})
390 (#{C})
391 (?::(\S+))?
391 (?::(\S+))?
392 ([^\s\-].*?[^\s\-]|\w)
392 ([^\s\-].*?[^\s\-]|\w)
393 #{rcq}/xm
393 #{rcq}/xm
394 end
394 end
395 [rc, ht, re, rtype]
395 [rc, ht, re, rtype]
396 end
396 end
397
397
398 # Elements to handle
398 # Elements to handle
399 GLYPHS = [
399 GLYPHS = [
400 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
400 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
401 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
401 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
402 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
402 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
403 # [ /\'/, '&#8216;' ], # single opening
403 # [ /\'/, '&#8216;' ], # single opening
404 # [ /</, '&lt;' ], # less-than
404 # [ /</, '&lt;' ], # less-than
405 # [ />/, '&gt;' ], # greater-than
405 # [ />/, '&gt;' ], # greater-than
406 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
406 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
407 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
407 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
408 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
408 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
409 # [ /"/, '&#8220;' ], # double opening
409 # [ /"/, '&#8220;' ], # double opening
410 # [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
410 # [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
411 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
411 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
412 # [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
412 # [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
413 # [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
413 # [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
414 # [ /\s->\s/, ' &rarr; ' ], # right arrow
414 # [ /\s->\s/, ' &rarr; ' ], # right arrow
415 # [ /\s-\s/, ' &#8211; ' ], # en dash
415 # [ /\s-\s/, ' &#8211; ' ], # en dash
416 # [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
416 # [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
417 # [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
417 # [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
418 # [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
418 # [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
419 # [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
419 # [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
420 ]
420 ]
421
421
422 H_ALGN_VALS = {
422 H_ALGN_VALS = {
423 '<' => 'left',
423 '<' => 'left',
424 '=' => 'center',
424 '=' => 'center',
425 '>' => 'right',
425 '>' => 'right',
426 '<>' => 'justify'
426 '<>' => 'justify'
427 }
427 }
428
428
429 V_ALGN_VALS = {
429 V_ALGN_VALS = {
430 '^' => 'top',
430 '^' => 'top',
431 '-' => 'middle',
431 '-' => 'middle',
432 '~' => 'bottom'
432 '~' => 'bottom'
433 }
433 }
434
434
435 #
435 #
436 # Flexible HTML escaping
436 # Flexible HTML escaping
437 #
437 #
438 def htmlesc( str, mode )
438 def htmlesc( str, mode=:Quotes )
439 if str
439 str.gsub!( '&', '&amp;' )
440 str.gsub!( '&', '&amp;' )
440 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
441 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
441 str.gsub!( "'", '&#039;' ) if mode == :Quotes
442 str.gsub!( "'", '&#039;' ) if mode == :Quotes
442 str.gsub!( '<', '&lt;')
443 str.gsub!( '<', '&lt;')
443 str.gsub!( '>', '&gt;')
444 str.gsub!( '>', '&gt;')
445 end
446 str
444 end
447 end
445
448
446 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
449 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
447 def pgl( text )
450 def pgl( text )
448 GLYPHS.each do |re, resub, tog|
451 GLYPHS.each do |re, resub, tog|
449 next if tog and method( tog ).call
452 next if tog and method( tog ).call
450 text.gsub! re, resub
453 text.gsub! re, resub
451 end
454 end
452 end
455 end
453
456
454 # Parses Textile attribute lists and builds an HTML attribute string
457 # Parses Textile attribute lists and builds an HTML attribute string
455 def pba( text_in, element = "" )
458 def pba( text_in, element = "" )
456
459
457 return '' unless text_in
460 return '' unless text_in
458
461
459 style = []
462 style = []
460 text = text_in.dup
463 text = text_in.dup
461 if element == 'td'
464 if element == 'td'
462 colspan = $1 if text =~ /\\(\d+)/
465 colspan = $1 if text =~ /\\(\d+)/
463 rowspan = $1 if text =~ /\/(\d+)/
466 rowspan = $1 if text =~ /\/(\d+)/
464 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
467 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
465 end
468 end
466
469
467 style << "#{ $1 };" if not filter_styles and
470 style << "#{ $1 };" if not filter_styles and
468 text.sub!( /\{([^}]*)\}/, '' )
471 text.sub!( /\{([^}]*)\}/, '' )
469
472
470 lang = $1 if
473 lang = $1 if
471 text.sub!( /\[([^)]+?)\]/, '' )
474 text.sub!( /\[([^)]+?)\]/, '' )
472
475
473 cls = $1 if
476 cls = $1 if
474 text.sub!( /\(([^()]+?)\)/, '' )
477 text.sub!( /\(([^()]+?)\)/, '' )
475
478
476 style << "padding-left:#{ $1.length }em;" if
479 style << "padding-left:#{ $1.length }em;" if
477 text.sub!( /([(]+)/, '' )
480 text.sub!( /([(]+)/, '' )
478
481
479 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
482 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
480
483
481 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
484 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
482
485
483 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
486 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
484
487
485 atts = ''
488 atts = ''
486 atts << " style=\"#{ style.join }\"" unless style.empty?
489 atts << " style=\"#{ style.join }\"" unless style.empty?
487 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
490 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
488 atts << " lang=\"#{ lang }\"" if lang
491 atts << " lang=\"#{ lang }\"" if lang
489 atts << " id=\"#{ id }\"" if id
492 atts << " id=\"#{ id }\"" if id
490 atts << " colspan=\"#{ colspan }\"" if colspan
493 atts << " colspan=\"#{ colspan }\"" if colspan
491 atts << " rowspan=\"#{ rowspan }\"" if rowspan
494 atts << " rowspan=\"#{ rowspan }\"" if rowspan
492
495
493 atts
496 atts
494 end
497 end
495
498
496 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
499 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
497
500
498 # Parses a Textile table block, building HTML from the result.
501 # Parses a Textile table block, building HTML from the result.
499 def block_textile_table( text )
502 def block_textile_table( text )
500 text.gsub!( TABLE_RE ) do |matches|
503 text.gsub!( TABLE_RE ) do |matches|
501
504
502 tatts, fullrow = $~[1..2]
505 tatts, fullrow = $~[1..2]
503 tatts = pba( tatts, 'table' )
506 tatts = pba( tatts, 'table' )
504 tatts = shelve( tatts ) if tatts
507 tatts = shelve( tatts ) if tatts
505 rows = []
508 rows = []
506
509
507 fullrow.each_line do |row|
510 fullrow.each_line do |row|
508 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
511 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
509 cells = []
512 cells = []
510 row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell|
513 row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell|
511 next if cell == '|'
514 next if cell == '|'
512 ctyp = 'd'
515 ctyp = 'd'
513 ctyp = 'h' if cell =~ /^_/
516 ctyp = 'h' if cell =~ /^_/
514
517
515 catts = ''
518 catts = ''
516 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
519 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
517
520
518 catts = shelve( catts ) if catts
521 catts = shelve( catts ) if catts
519 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
522 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
520 end
523 end
521 ratts = shelve( ratts ) if ratts
524 ratts = shelve( ratts ) if ratts
522 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
525 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
523 end
526 end
524 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
527 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
525 end
528 end
526 end
529 end
527
530
528 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
531 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
529 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
532 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
530
533
531 # Parses Textile lists and generates HTML
534 # Parses Textile lists and generates HTML
532 def block_textile_lists( text )
535 def block_textile_lists( text )
533 text.gsub!( LISTS_RE ) do |match|
536 text.gsub!( LISTS_RE ) do |match|
534 lines = match.split( /\n/ )
537 lines = match.split( /\n/ )
535 last_line = -1
538 last_line = -1
536 depth = []
539 depth = []
537 lines.each_with_index do |line, line_id|
540 lines.each_with_index do |line, line_id|
538 if line =~ LISTS_CONTENT_RE
541 if line =~ LISTS_CONTENT_RE
539 tl,atts,content = $~[1..3]
542 tl,atts,content = $~[1..3]
540 if depth.last
543 if depth.last
541 if depth.last.length > tl.length
544 if depth.last.length > tl.length
542 (depth.length - 1).downto(0) do |i|
545 (depth.length - 1).downto(0) do |i|
543 break if depth[i].length == tl.length
546 break if depth[i].length == tl.length
544 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
547 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
545 depth.pop
548 depth.pop
546 end
549 end
547 end
550 end
548 if depth.last and depth.last.length == tl.length
551 if depth.last and depth.last.length == tl.length
549 lines[line_id - 1] << '</li>'
552 lines[line_id - 1] << '</li>'
550 end
553 end
551 end
554 end
552 unless depth.last == tl
555 unless depth.last == tl
553 depth << tl
556 depth << tl
554 atts = pba( atts )
557 atts = pba( atts )
555 atts = shelve( atts ) if atts
558 atts = shelve( atts ) if atts
556 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
559 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
557 else
560 else
558 lines[line_id] = "\t\t<li>#{ content }"
561 lines[line_id] = "\t\t<li>#{ content }"
559 end
562 end
560 last_line = line_id
563 last_line = line_id
561
564
562 else
565 else
563 last_line = line_id
566 last_line = line_id
564 end
567 end
565 if line_id - last_line > 1 or line_id == lines.length - 1
568 if line_id - last_line > 1 or line_id == lines.length - 1
566 depth.delete_if do |v|
569 depth.delete_if do |v|
567 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
570 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
568 end
571 end
569 end
572 end
570 end
573 end
571 lines.join( "\n" )
574 lines.join( "\n" )
572 end
575 end
573 end
576 end
574
577
575 QUOTES_RE = /(^>+([^\n]*?)\n?)+/m
578 QUOTES_RE = /(^>+([^\n]*?)\n?)+/m
576 QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
579 QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
577
580
578 def block_textile_quotes( text )
581 def block_textile_quotes( text )
579 text.gsub!( QUOTES_RE ) do |match|
582 text.gsub!( QUOTES_RE ) do |match|
580 lines = match.split( /\n/ )
583 lines = match.split( /\n/ )
581 quotes = ''
584 quotes = ''
582 indent = 0
585 indent = 0
583 lines.each do |line|
586 lines.each do |line|
584 line =~ QUOTES_CONTENT_RE
587 line =~ QUOTES_CONTENT_RE
585 bq,content = $1, $2
588 bq,content = $1, $2
586 l = bq.count('>')
589 l = bq.count('>')
587 if l != indent
590 if l != indent
588 quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
591 quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
589 indent = l
592 indent = l
590 end
593 end
591 quotes << (content + "\n")
594 quotes << (content + "\n")
592 end
595 end
593 quotes << ("\n" + '</blockquote>' * indent + "\n\n")
596 quotes << ("\n" + '</blockquote>' * indent + "\n\n")
594 quotes
597 quotes
595 end
598 end
596 end
599 end
597
600
598 CODE_RE = /(\W)
601 CODE_RE = /(\W)
599 @
602 @
600 (?:\|(\w+?)\|)?
603 (?:\|(\w+?)\|)?
601 (.+?)
604 (.+?)
602 @
605 @
603 (?=\W)/x
606 (?=\W)/x
604
607
605 def inline_textile_code( text )
608 def inline_textile_code( text )
606 text.gsub!( CODE_RE ) do |m|
609 text.gsub!( CODE_RE ) do |m|
607 before,lang,code,after = $~[1..4]
610 before,lang,code,after = $~[1..4]
608 lang = " lang=\"#{ lang }\"" if lang
611 lang = " lang=\"#{ lang }\"" if lang
609 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
612 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
610 end
613 end
611 end
614 end
612
615
613 def lT( text )
616 def lT( text )
614 text =~ /\#$/ ? 'o' : 'u'
617 text =~ /\#$/ ? 'o' : 'u'
615 end
618 end
616
619
617 def hard_break( text )
620 def hard_break( text )
618 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
621 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
619 end
622 end
620
623
621 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
624 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
622
625
623 def blocks( text, deep_code = false )
626 def blocks( text, deep_code = false )
624 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
627 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
625 plain = blk !~ /\A[#*> ]/
628 plain = blk !~ /\A[#*> ]/
626
629
627 # skip blocks that are complex HTML
630 # skip blocks that are complex HTML
628 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
631 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
629 blk
632 blk
630 else
633 else
631 # search for indentation levels
634 # search for indentation levels
632 blk.strip!
635 blk.strip!
633 if blk.empty?
636 if blk.empty?
634 blk
637 blk
635 else
638 else
636 code_blk = nil
639 code_blk = nil
637 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
640 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
638 flush_left iblk
641 flush_left iblk
639 blocks iblk, plain
642 blocks iblk, plain
640 iblk.gsub( /^(\S)/, "\t\\1" )
643 iblk.gsub( /^(\S)/, "\t\\1" )
641 if plain
644 if plain
642 code_blk = iblk; ""
645 code_blk = iblk; ""
643 else
646 else
644 iblk
647 iblk
645 end
648 end
646 end
649 end
647
650
648 block_applied = 0
651 block_applied = 0
649 @rules.each do |rule_name|
652 @rules.each do |rule_name|
650 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
653 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
651 end
654 end
652 if block_applied.zero?
655 if block_applied.zero?
653 if deep_code
656 if deep_code
654 blk = "\t<pre><code>#{ blk }</code></pre>"
657 blk = "\t<pre><code>#{ blk }</code></pre>"
655 else
658 else
656 blk = "\t<p>#{ blk }</p>"
659 blk = "\t<p>#{ blk }</p>"
657 end
660 end
658 end
661 end
659 # hard_break blk
662 # hard_break blk
660 blk + "\n#{ code_blk }"
663 blk + "\n#{ code_blk }"
661 end
664 end
662 end
665 end
663
666
664 end.join( "\n\n" ) )
667 end.join( "\n\n" ) )
665 end
668 end
666
669
667 def textile_bq( tag, atts, cite, content )
670 def textile_bq( tag, atts, cite, content )
668 cite, cite_title = check_refs( cite )
671 cite, cite_title = check_refs( cite )
669 cite = " cite=\"#{ cite }\"" if cite
672 cite = " cite=\"#{ cite }\"" if cite
670 atts = shelve( atts ) if atts
673 atts = shelve( atts ) if atts
671 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
674 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
672 end
675 end
673
676
674 def textile_p( tag, atts, cite, content )
677 def textile_p( tag, atts, cite, content )
675 atts = shelve( atts ) if atts
678 atts = shelve( atts ) if atts
676 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
679 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
677 end
680 end
678
681
679 alias textile_h1 textile_p
682 alias textile_h1 textile_p
680 alias textile_h2 textile_p
683 alias textile_h2 textile_p
681 alias textile_h3 textile_p
684 alias textile_h3 textile_p
682 alias textile_h4 textile_p
685 alias textile_h4 textile_p
683 alias textile_h5 textile_p
686 alias textile_h5 textile_p
684 alias textile_h6 textile_p
687 alias textile_h6 textile_p
685
688
686 def textile_fn_( tag, num, atts, cite, content )
689 def textile_fn_( tag, num, atts, cite, content )
687 atts << " id=\"fn#{ num }\" class=\"footnote\""
690 atts << " id=\"fn#{ num }\" class=\"footnote\""
688 content = "<sup>#{ num }</sup> #{ content }"
691 content = "<sup>#{ num }</sup> #{ content }"
689 atts = shelve( atts ) if atts
692 atts = shelve( atts ) if atts
690 "\t<p#{ atts }>#{ content }</p>"
693 "\t<p#{ atts }>#{ content }</p>"
691 end
694 end
692
695
693 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
696 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
694
697
695 def block_textile_prefix( text )
698 def block_textile_prefix( text )
696 if text =~ BLOCK_RE
699 if text =~ BLOCK_RE
697 tag,tagpre,num,atts,cite,content = $~[1..6]
700 tag,tagpre,num,atts,cite,content = $~[1..6]
698 atts = pba( atts )
701 atts = pba( atts )
699
702
700 # pass to prefix handler
703 # pass to prefix handler
701 if respond_to? "textile_#{ tag }", true
704 if respond_to? "textile_#{ tag }", true
702 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
705 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
703 elsif respond_to? "textile_#{ tagpre }_", true
706 elsif respond_to? "textile_#{ tagpre }_", true
704 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
707 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
705 end
708 end
706 end
709 end
707 end
710 end
708
711
709 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
712 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
710 def block_markdown_setext( text )
713 def block_markdown_setext( text )
711 if text =~ SETEXT_RE
714 if text =~ SETEXT_RE
712 tag = if $2 == "="; "h1"; else; "h2"; end
715 tag = if $2 == "="; "h1"; else; "h2"; end
713 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
716 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
714 blocks cont
717 blocks cont
715 text.replace( blk + cont )
718 text.replace( blk + cont )
716 end
719 end
717 end
720 end
718
721
719 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
722 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
720 [ ]*
723 [ ]*
721 (.+?) # $2 = Header text
724 (.+?) # $2 = Header text
722 [ ]*
725 [ ]*
723 \#* # optional closing #'s (not counted)
726 \#* # optional closing #'s (not counted)
724 $/x
727 $/x
725 def block_markdown_atx( text )
728 def block_markdown_atx( text )
726 if text =~ ATX_RE
729 if text =~ ATX_RE
727 tag = "h#{ $1.length }"
730 tag = "h#{ $1.length }"
728 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
731 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
729 blocks cont
732 blocks cont
730 text.replace( blk + cont )
733 text.replace( blk + cont )
731 end
734 end
732 end
735 end
733
736
734 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
737 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
735
738
736 def block_markdown_bq( text )
739 def block_markdown_bq( text )
737 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
740 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
738 blk.gsub!( /^ *> ?/, '' )
741 blk.gsub!( /^ *> ?/, '' )
739 flush_left blk
742 flush_left blk
740 blocks blk
743 blocks blk
741 blk.gsub!( /^(\S)/, "\t\\1" )
744 blk.gsub!( /^(\S)/, "\t\\1" )
742 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
745 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
743 end
746 end
744 end
747 end
745
748
746 MARKDOWN_RULE_RE = /^(#{
749 MARKDOWN_RULE_RE = /^(#{
747 ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
750 ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
748 })$/
751 })$/
749
752
750 def block_markdown_rule( text )
753 def block_markdown_rule( text )
751 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
754 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
752 "<hr />"
755 "<hr />"
753 end
756 end
754 end
757 end
755
758
756 # XXX TODO XXX
759 # XXX TODO XXX
757 def block_markdown_lists( text )
760 def block_markdown_lists( text )
758 end
761 end
759
762
760 def inline_textile_span( text )
763 def inline_textile_span( text )
761 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
764 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
762 text.gsub!( qtag_re ) do |m|
765 text.gsub!( qtag_re ) do |m|
763
766
764 case rtype
767 case rtype
765 when :limit
768 when :limit
766 sta,qtag,atts,cite,content = $~[1..5]
769 sta,qtag,atts,cite,content = $~[1..5]
767 else
770 else
768 qtag,atts,cite,content = $~[1..4]
771 qtag,atts,cite,content = $~[1..4]
769 sta = ''
772 sta = ''
770 end
773 end
771 atts = pba( atts )
774 atts = pba( atts )
772 atts << " cite=\"#{ cite }\"" if cite
775 atts << " cite=\"#{ cite }\"" if cite
773 atts = shelve( atts ) if atts
776 atts = shelve( atts ) if atts
774
777
775 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
778 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
776
779
777 end
780 end
778 end
781 end
779 end
782 end
780
783
781 LINK_RE = /
784 LINK_RE = /
782 ([\s\[{(]|[#{PUNCT}])? # $pre
785 ([\s\[{(]|[#{PUNCT}])? # $pre
783 " # start
786 " # start
784 (#{C}) # $atts
787 (#{C}) # $atts
785 ([^"\n]+?) # $text
788 ([^"\n]+?) # $text
786 \s?
789 \s?
787 (?:\(([^)]+?)\)(?="))? # $title
790 (?:\(([^)]+?)\)(?="))? # $title
788 ":
791 ":
789 ([\w\/]\S+?) # $url
792 ([\w\/]\S+?) # $url
790 (\/)? # $slash
793 (\/)? # $slash
791 ([^\w\=\/;\(\)]*?) # $post
794 ([^\w\=\/;\(\)]*?) # $post
792 (?=<|\s|$)
795 (?=<|\s|$)
793 /x
796 /x
794 #"
797 #"
795 def inline_textile_link( text )
798 def inline_textile_link( text )
796 text.gsub!( LINK_RE ) do |m|
799 text.gsub!( LINK_RE ) do |m|
797 pre,atts,text,title,url,slash,post = $~[1..7]
800 pre,atts,text,title,url,slash,post = $~[1..7]
798
801
799 url, url_title = check_refs( url )
802 url, url_title = check_refs( url )
800 title ||= url_title
803 title ||= url_title
801
804
802 # Idea below : an URL with unbalanced parethesis and
805 # Idea below : an URL with unbalanced parethesis and
803 # ending by ')' is put into external parenthesis
806 # ending by ')' is put into external parenthesis
804 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
807 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
805 url=url[0..-2] # discard closing parenth from url
808 url=url[0..-2] # discard closing parenth from url
806 post = ")"+post # add closing parenth to post
809 post = ")"+post # add closing parenth to post
807 end
810 end
808 atts = pba( atts )
811 atts = pba( atts )
809 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
812 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
810 atts << " title=\"#{ title }\"" if title
813 atts << " title=\"#{ title }\"" if title
811 atts = shelve( atts ) if atts
814 atts = shelve( atts ) if atts
812
815
813 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
816 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
814
817
815 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
818 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
816 end
819 end
817 end
820 end
818
821
819 MARKDOWN_REFLINK_RE = /
822 MARKDOWN_REFLINK_RE = /
820 \[([^\[\]]+)\] # $text
823 \[([^\[\]]+)\] # $text
821 [ ]? # opt. space
824 [ ]? # opt. space
822 (?:\n[ ]*)? # one optional newline followed by spaces
825 (?:\n[ ]*)? # one optional newline followed by spaces
823 \[(.*?)\] # $id
826 \[(.*?)\] # $id
824 /x
827 /x
825
828
826 def inline_markdown_reflink( text )
829 def inline_markdown_reflink( text )
827 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
830 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
828 text, id = $~[1..2]
831 text, id = $~[1..2]
829
832
830 if id.empty?
833 if id.empty?
831 url, title = check_refs( text )
834 url, title = check_refs( text )
832 else
835 else
833 url, title = check_refs( id )
836 url, title = check_refs( id )
834 end
837 end
835
838
836 atts = " href=\"#{ url }\""
839 atts = " href=\"#{ url }\""
837 atts << " title=\"#{ title }\"" if title
840 atts << " title=\"#{ title }\"" if title
838 atts = shelve( atts )
841 atts = shelve( atts )
839
842
840 "<a#{ atts }>#{ text }</a>"
843 "<a#{ atts }>#{ text }</a>"
841 end
844 end
842 end
845 end
843
846
844 MARKDOWN_LINK_RE = /
847 MARKDOWN_LINK_RE = /
845 \[([^\[\]]+)\] # $text
848 \[([^\[\]]+)\] # $text
846 \( # open paren
849 \( # open paren
847 [ \t]* # opt space
850 [ \t]* # opt space
848 <?(.+?)>? # $href
851 <?(.+?)>? # $href
849 [ \t]* # opt space
852 [ \t]* # opt space
850 (?: # whole title
853 (?: # whole title
851 (['"]) # $quote
854 (['"]) # $quote
852 (.*?) # $title
855 (.*?) # $title
853 \3 # matching quote
856 \3 # matching quote
854 )? # title is optional
857 )? # title is optional
855 \)
858 \)
856 /x
859 /x
857
860
858 def inline_markdown_link( text )
861 def inline_markdown_link( text )
859 text.gsub!( MARKDOWN_LINK_RE ) do |m|
862 text.gsub!( MARKDOWN_LINK_RE ) do |m|
860 text, url, quote, title = $~[1..4]
863 text, url, quote, title = $~[1..4]
861
864
862 atts = " href=\"#{ url }\""
865 atts = " href=\"#{ url }\""
863 atts << " title=\"#{ title }\"" if title
866 atts << " title=\"#{ title }\"" if title
864 atts = shelve( atts )
867 atts = shelve( atts )
865
868
866 "<a#{ atts }>#{ text }</a>"
869 "<a#{ atts }>#{ text }</a>"
867 end
870 end
868 end
871 end
869
872
870 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
873 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
871 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
874 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
872
875
873 def refs( text )
876 def refs( text )
874 @rules.each do |rule_name|
877 @rules.each do |rule_name|
875 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
878 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
876 end
879 end
877 end
880 end
878
881
879 def refs_textile( text )
882 def refs_textile( text )
880 text.gsub!( TEXTILE_REFS_RE ) do |m|
883 text.gsub!( TEXTILE_REFS_RE ) do |m|
881 flag, url = $~[2..3]
884 flag, url = $~[2..3]
882 @urlrefs[flag.downcase] = [url, nil]
885 @urlrefs[flag.downcase] = [url, nil]
883 nil
886 nil
884 end
887 end
885 end
888 end
886
889
887 def refs_markdown( text )
890 def refs_markdown( text )
888 text.gsub!( MARKDOWN_REFS_RE ) do |m|
891 text.gsub!( MARKDOWN_REFS_RE ) do |m|
889 flag, url = $~[2..3]
892 flag, url = $~[2..3]
890 title = $~[6]
893 title = $~[6]
891 @urlrefs[flag.downcase] = [url, title]
894 @urlrefs[flag.downcase] = [url, title]
892 nil
895 nil
893 end
896 end
894 end
897 end
895
898
896 def check_refs( text )
899 def check_refs( text )
897 ret = @urlrefs[text.downcase] if text
900 ret = @urlrefs[text.downcase] if text
898 ret || [text, nil]
901 ret || [text, nil]
899 end
902 end
900
903
901 IMAGE_RE = /
904 IMAGE_RE = /
902 (<p>|.|^) # start of line?
905 (<p>|.|^) # start of line?
903 \! # opening
906 \! # opening
904 (\<|\=|\>)? # optional alignment atts
907 (\<|\=|\>)? # optional alignment atts
905 (#{C}) # optional style,class atts
908 (#{C}) # optional style,class atts
906 (?:\. )? # optional dot-space
909 (?:\. )? # optional dot-space
907 ([^\s(!]+?) # presume this is the src
910 ([^\s(!]+?) # presume this is the src
908 \s? # optional space
911 \s? # optional space
909 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
912 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
910 \! # closing
913 \! # closing
911 (?::#{ HYPERLINK })? # optional href
914 (?::#{ HYPERLINK })? # optional href
912 /x
915 /x
913
916
914 def inline_textile_image( text )
917 def inline_textile_image( text )
915 text.gsub!( IMAGE_RE ) do |m|
918 text.gsub!( IMAGE_RE ) do |m|
916 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
919 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
920 htmlesc title
917 atts = pba( atts )
921 atts = pba( atts )
918 atts = " src=\"#{ url }\"#{ atts }"
922 atts = " src=\"#{ url }\"#{ atts }"
919 atts << " title=\"#{ title }\"" if title
923 atts << " title=\"#{ title }\"" if title
920 atts << " alt=\"#{ title }\""
924 atts << " alt=\"#{ title }\""
921 # size = @getimagesize($url);
925 # size = @getimagesize($url);
922 # if($size) $atts.= " $size[3]";
926 # if($size) $atts.= " $size[3]";
923
927
924 href, alt_title = check_refs( href ) if href
928 href, alt_title = check_refs( href ) if href
925 url, url_title = check_refs( url )
929 url, url_title = check_refs( url )
926
930
927 out = ''
931 out = ''
928 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
932 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
929 out << "<img#{ shelve( atts ) } />"
933 out << "<img#{ shelve( atts ) } />"
930 out << "</a>#{ href_a1 }#{ href_a2 }" if href
934 out << "</a>#{ href_a1 }#{ href_a2 }" if href
931
935
932 if algn
936 if algn
933 algn = h_align( algn )
937 algn = h_align( algn )
934 if stln == "<p>"
938 if stln == "<p>"
935 out = "<p style=\"float:#{ algn }\">#{ out }"
939 out = "<p style=\"float:#{ algn }\">#{ out }"
936 else
940 else
937 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
941 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
938 end
942 end
939 else
943 else
940 out = stln + out
944 out = stln + out
941 end
945 end
942
946
943 out
947 out
944 end
948 end
945 end
949 end
946
950
947 def shelve( val )
951 def shelve( val )
948 @shelf << val
952 @shelf << val
949 " :redsh##{ @shelf.length }:"
953 " :redsh##{ @shelf.length }:"
950 end
954 end
951
955
952 def retrieve( text )
956 def retrieve( text )
953 @shelf.each_with_index do |r, i|
957 @shelf.each_with_index do |r, i|
954 text.gsub!( " :redsh##{ i + 1 }:", r )
958 text.gsub!( " :redsh##{ i + 1 }:", r )
955 end
959 end
956 end
960 end
957
961
958 def incoming_entities( text )
962 def incoming_entities( text )
959 ## turn any incoming ampersands into a dummy character for now.
963 ## turn any incoming ampersands into a dummy character for now.
960 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
964 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
961 ## implying an incoming html entity, to be skipped
965 ## implying an incoming html entity, to be skipped
962
966
963 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
967 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
964 end
968 end
965
969
966 def no_textile( text )
970 def no_textile( text )
967 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
971 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
968 '\1<notextile>\2</notextile>\3' )
972 '\1<notextile>\2</notextile>\3' )
969 text.gsub!( /^ *==([^=]+.*?)==/m,
973 text.gsub!( /^ *==([^=]+.*?)==/m,
970 '\1<notextile>\2</notextile>\3' )
974 '\1<notextile>\2</notextile>\3' )
971 end
975 end
972
976
973 def clean_white_space( text )
977 def clean_white_space( text )
974 # normalize line breaks
978 # normalize line breaks
975 text.gsub!( /\r\n/, "\n" )
979 text.gsub!( /\r\n/, "\n" )
976 text.gsub!( /\r/, "\n" )
980 text.gsub!( /\r/, "\n" )
977 text.gsub!( /\t/, ' ' )
981 text.gsub!( /\t/, ' ' )
978 text.gsub!( /^ +$/, '' )
982 text.gsub!( /^ +$/, '' )
979 text.gsub!( /\n{3,}/, "\n\n" )
983 text.gsub!( /\n{3,}/, "\n\n" )
980 text.gsub!( /"$/, "\" " )
984 text.gsub!( /"$/, "\" " )
981
985
982 # if entire document is indented, flush
986 # if entire document is indented, flush
983 # to the left side
987 # to the left side
984 flush_left text
988 flush_left text
985 end
989 end
986
990
987 def flush_left( text )
991 def flush_left( text )
988 indt = 0
992 indt = 0
989 if text =~ /^ /
993 if text =~ /^ /
990 while text !~ /^ {#{indt}}\S/
994 while text !~ /^ {#{indt}}\S/
991 indt += 1
995 indt += 1
992 end unless text.empty?
996 end unless text.empty?
993 if indt.nonzero?
997 if indt.nonzero?
994 text.gsub!( /^ {#{indt}}/, '' )
998 text.gsub!( /^ {#{indt}}/, '' )
995 end
999 end
996 end
1000 end
997 end
1001 end
998
1002
999 def footnote_ref( text )
1003 def footnote_ref( text )
1000 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1004 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1001 '<sup><a href="#fn\1">\1</a></sup>\2' )
1005 '<sup><a href="#fn\1">\1</a></sup>\2' )
1002 end
1006 end
1003
1007
1004 OFFTAGS = /(code|pre|kbd|notextile)/
1008 OFFTAGS = /(code|pre|kbd|notextile)/
1005 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
1009 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
1006 OFFTAG_OPEN = /<#{ OFFTAGS }/
1010 OFFTAG_OPEN = /<#{ OFFTAGS }/
1007 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1011 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1008 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1012 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1009 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1013 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1010
1014
1011 def glyphs_textile( text, level = 0 )
1015 def glyphs_textile( text, level = 0 )
1012 if text !~ HASTAG_MATCH
1016 if text !~ HASTAG_MATCH
1013 pgl text
1017 pgl text
1014 footnote_ref text
1018 footnote_ref text
1015 else
1019 else
1016 codepre = 0
1020 codepre = 0
1017 text.gsub!( ALLTAG_MATCH ) do |line|
1021 text.gsub!( ALLTAG_MATCH ) do |line|
1018 ## matches are off if we're between <code>, <pre> etc.
1022 ## matches are off if we're between <code>, <pre> etc.
1019 if $1
1023 if $1
1020 if line =~ OFFTAG_OPEN
1024 if line =~ OFFTAG_OPEN
1021 codepre += 1
1025 codepre += 1
1022 elsif line =~ OFFTAG_CLOSE
1026 elsif line =~ OFFTAG_CLOSE
1023 codepre -= 1
1027 codepre -= 1
1024 codepre = 0 if codepre < 0
1028 codepre = 0 if codepre < 0
1025 end
1029 end
1026 elsif codepre.zero?
1030 elsif codepre.zero?
1027 glyphs_textile( line, level + 1 )
1031 glyphs_textile( line, level + 1 )
1028 else
1032 else
1029 htmlesc( line, :NoQuotes )
1033 htmlesc( line, :NoQuotes )
1030 end
1034 end
1031 # p [level, codepre, line]
1035 # p [level, codepre, line]
1032
1036
1033 line
1037 line
1034 end
1038 end
1035 end
1039 end
1036 end
1040 end
1037
1041
1038 def rip_offtags( text )
1042 def rip_offtags( text )
1039 if text =~ /<.*>/
1043 if text =~ /<.*>/
1040 ## strip and encode <pre> content
1044 ## strip and encode <pre> content
1041 codepre, used_offtags = 0, {}
1045 codepre, used_offtags = 0, {}
1042 text.gsub!( OFFTAG_MATCH ) do |line|
1046 text.gsub!( OFFTAG_MATCH ) do |line|
1043 if $3
1047 if $3
1044 offtag, aftertag = $4, $5
1048 offtag, aftertag = $4, $5
1045 codepre += 1
1049 codepre += 1
1046 used_offtags[offtag] = true
1050 used_offtags[offtag] = true
1047 if codepre - used_offtags.length > 0
1051 if codepre - used_offtags.length > 0
1048 htmlesc( line, :NoQuotes )
1052 htmlesc( line, :NoQuotes )
1049 @pre_list.last << line
1053 @pre_list.last << line
1050 line = ""
1054 line = ""
1051 else
1055 else
1052 htmlesc( aftertag, :NoQuotes ) if aftertag
1056 htmlesc( aftertag, :NoQuotes ) if aftertag
1053 line = "<redpre##{ @pre_list.length }>"
1057 line = "<redpre##{ @pre_list.length }>"
1054 $3.match(/<#{ OFFTAGS }([^>]*)>/)
1058 $3.match(/<#{ OFFTAGS }([^>]*)>/)
1055 tag = $1
1059 tag = $1
1056 $2.to_s.match(/(class\=\S+)/i)
1060 $2.to_s.match(/(class\=\S+)/i)
1057 tag << " #{$1}" if $1
1061 tag << " #{$1}" if $1
1058 @pre_list << "<#{ tag }>#{ aftertag }"
1062 @pre_list << "<#{ tag }>#{ aftertag }"
1059 end
1063 end
1060 elsif $1 and codepre > 0
1064 elsif $1 and codepre > 0
1061 if codepre - used_offtags.length > 0
1065 if codepre - used_offtags.length > 0
1062 htmlesc( line, :NoQuotes )
1066 htmlesc( line, :NoQuotes )
1063 @pre_list.last << line
1067 @pre_list.last << line
1064 line = ""
1068 line = ""
1065 end
1069 end
1066 codepre -= 1 unless codepre.zero?
1070 codepre -= 1 unless codepre.zero?
1067 used_offtags = {} if codepre.zero?
1071 used_offtags = {} if codepre.zero?
1068 end
1072 end
1069 line
1073 line
1070 end
1074 end
1071 end
1075 end
1072 text
1076 text
1073 end
1077 end
1074
1078
1075 def smooth_offtags( text )
1079 def smooth_offtags( text )
1076 unless @pre_list.empty?
1080 unless @pre_list.empty?
1077 ## replace <pre> content
1081 ## replace <pre> content
1078 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1082 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1079 end
1083 end
1080 end
1084 end
1081
1085
1082 def inline( text )
1086 def inline( text )
1083 [/^inline_/, /^glyphs_/].each do |meth_re|
1087 [/^inline_/, /^glyphs_/].each do |meth_re|
1084 @rules.each do |rule_name|
1088 @rules.each do |rule_name|
1085 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1089 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1086 end
1090 end
1087 end
1091 end
1088 end
1092 end
1089
1093
1090 def h_align( text )
1094 def h_align( text )
1091 H_ALGN_VALS[text]
1095 H_ALGN_VALS[text]
1092 end
1096 end
1093
1097
1094 def v_align( text )
1098 def v_align( text )
1095 V_ALGN_VALS[text]
1099 V_ALGN_VALS[text]
1096 end
1100 end
1097
1101
1098 def textile_popup_help( name, windowW, windowH )
1102 def textile_popup_help( name, windowW, windowH )
1099 ' <a target="_blank" href="http://hobix.com/textile/#' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
1103 ' <a target="_blank" href="http://hobix.com/textile/#' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
1100 end
1104 end
1101
1105
1102 # HTML cleansing stuff
1106 # HTML cleansing stuff
1103 BASIC_TAGS = {
1107 BASIC_TAGS = {
1104 'a' => ['href', 'title'],
1108 'a' => ['href', 'title'],
1105 'img' => ['src', 'alt', 'title'],
1109 'img' => ['src', 'alt', 'title'],
1106 'br' => [],
1110 'br' => [],
1107 'i' => nil,
1111 'i' => nil,
1108 'u' => nil,
1112 'u' => nil,
1109 'b' => nil,
1113 'b' => nil,
1110 'pre' => nil,
1114 'pre' => nil,
1111 'kbd' => nil,
1115 'kbd' => nil,
1112 'code' => ['lang'],
1116 'code' => ['lang'],
1113 'cite' => nil,
1117 'cite' => nil,
1114 'strong' => nil,
1118 'strong' => nil,
1115 'em' => nil,
1119 'em' => nil,
1116 'ins' => nil,
1120 'ins' => nil,
1117 'sup' => nil,
1121 'sup' => nil,
1118 'sub' => nil,
1122 'sub' => nil,
1119 'del' => nil,
1123 'del' => nil,
1120 'table' => nil,
1124 'table' => nil,
1121 'tr' => nil,
1125 'tr' => nil,
1122 'td' => ['colspan', 'rowspan'],
1126 'td' => ['colspan', 'rowspan'],
1123 'th' => nil,
1127 'th' => nil,
1124 'ol' => nil,
1128 'ol' => nil,
1125 'ul' => nil,
1129 'ul' => nil,
1126 'li' => nil,
1130 'li' => nil,
1127 'p' => nil,
1131 'p' => nil,
1128 'h1' => nil,
1132 'h1' => nil,
1129 'h2' => nil,
1133 'h2' => nil,
1130 'h3' => nil,
1134 'h3' => nil,
1131 'h4' => nil,
1135 'h4' => nil,
1132 'h5' => nil,
1136 'h5' => nil,
1133 'h6' => nil,
1137 'h6' => nil,
1134 'blockquote' => ['cite']
1138 'blockquote' => ['cite']
1135 }
1139 }
1136
1140
1137 def clean_html( text, tags = BASIC_TAGS )
1141 def clean_html( text, tags = BASIC_TAGS )
1138 text.gsub!( /<!\[CDATA\[/, '' )
1142 text.gsub!( /<!\[CDATA\[/, '' )
1139 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1143 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1140 raw = $~
1144 raw = $~
1141 tag = raw[2].downcase
1145 tag = raw[2].downcase
1142 if tags.has_key? tag
1146 if tags.has_key? tag
1143 pcs = [tag]
1147 pcs = [tag]
1144 tags[tag].each do |prop|
1148 tags[tag].each do |prop|
1145 ['"', "'", ''].each do |q|
1149 ['"', "'", ''].each do |q|
1146 q2 = ( q != '' ? q : '\s' )
1150 q2 = ( q != '' ? q : '\s' )
1147 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1151 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1148 attrv = $1
1152 attrv = $1
1149 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1153 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1150 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1154 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1151 break
1155 break
1152 end
1156 end
1153 end
1157 end
1154 end if tags[tag]
1158 end if tags[tag]
1155 "<#{raw[1]}#{pcs.join " "}>"
1159 "<#{raw[1]}#{pcs.join " "}>"
1156 else
1160 else
1157 " "
1161 " "
1158 end
1162 end
1159 end
1163 end
1160 end
1164 end
1161
1165
1162 ALLOWED_TAGS = %w(redpre pre code notextile)
1166 ALLOWED_TAGS = %w(redpre pre code notextile)
1163
1167
1164 def escape_html_tags(text)
1168 def escape_html_tags(text)
1165 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1169 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1166 end
1170 end
1167 end
1171 end
1168
1172
@@ -1,431 +1,433
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :roles, :enabled_modules, :users,
23 fixtures :projects, :roles, :enabled_modules, :users,
24 :repositories, :changesets,
24 :repositories, :changesets,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
26 :wikis, :wiki_pages, :wiki_contents,
26 :wikis, :wiki_pages, :wiki_contents,
27 :boards, :messages,
27 :boards, :messages,
28 :attachments
28 :attachments
29
29
30 def setup
30 def setup
31 super
31 super
32 end
32 end
33
33
34 def test_auto_links
34 def test_auto_links
35 to_test = {
35 to_test = {
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
39 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
40 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
40 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
41 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
41 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
42 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
42 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
43 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
43 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
44 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
44 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
45 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
45 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
46 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
46 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
47 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
47 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
48 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
48 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
49 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
49 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
50 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
50 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
51 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
51 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
52 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
52 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
53 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
53 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
54 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
54 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
55 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
55 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
56 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
56 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
57 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
57 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
58 }
58 }
59 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
59 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
60 end
60 end
61
61
62 def test_auto_mailto
62 def test_auto_mailto
63 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
63 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
64 textilizable('test@foo.bar')
64 textilizable('test@foo.bar')
65 end
65 end
66
66
67 def test_inline_images
67 def test_inline_images
68 to_test = {
68 to_test = {
69 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
69 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
70 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
70 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
71 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
71 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
72 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
72 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
73 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
74 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
73 }
75 }
74 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
76 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
75 end
77 end
76
78
77 def test_attached_images
79 def test_attached_images
78 to_test = {
80 to_test = {
79 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
81 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
80 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />'
82 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />'
81 }
83 }
82 attachments = Attachment.find(:all)
84 attachments = Attachment.find(:all)
83 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
84 end
86 end
85
87
86 def test_textile_external_links
88 def test_textile_external_links
87 to_test = {
89 to_test = {
88 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
90 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
89 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
91 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
90 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
92 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
91 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
93 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
92 # no multiline link text
94 # no multiline link text
93 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
95 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
94 }
96 }
95 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
97 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
96 end
98 end
97
99
98 def test_redmine_links
100 def test_redmine_links
99 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
101 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
100 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
102 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
101
103
102 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
104 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
103 :class => 'changeset', :title => 'My very first commit')
105 :class => 'changeset', :title => 'My very first commit')
104
106
105 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
107 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
106 :class => 'document')
108 :class => 'document')
107
109
108 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
110 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
109 :class => 'version')
111 :class => 'version')
110
112
111 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
113 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
112
114
113 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
115 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
114 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
116 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
115
117
116 to_test = {
118 to_test = {
117 # tickets
119 # tickets
118 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
120 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
119 # changesets
121 # changesets
120 'r1' => changeset_link,
122 'r1' => changeset_link,
121 # documents
123 # documents
122 'document#1' => document_link,
124 'document#1' => document_link,
123 'document:"Test document"' => document_link,
125 'document:"Test document"' => document_link,
124 # versions
126 # versions
125 'version#2' => version_link,
127 'version#2' => version_link,
126 'version:1.0' => version_link,
128 'version:1.0' => version_link,
127 'version:"1.0"' => version_link,
129 'version:"1.0"' => version_link,
128 # source
130 # source
129 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
131 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
130 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
132 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
131 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
133 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
132 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
134 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
133 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
135 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
134 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
136 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
135 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
137 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
136 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
138 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
137 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
139 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
138 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
140 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
139 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
141 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
140 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
142 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
141 # message
143 # message
142 'message#4' => link_to('Post 2', message_url, :class => 'message'),
144 'message#4' => link_to('Post 2', message_url, :class => 'message'),
143 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
145 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
144 # escaping
146 # escaping
145 '!#3.' => '#3.',
147 '!#3.' => '#3.',
146 '!r1' => 'r1',
148 '!r1' => 'r1',
147 '!document#1' => 'document#1',
149 '!document#1' => 'document#1',
148 '!document:"Test document"' => 'document:"Test document"',
150 '!document:"Test document"' => 'document:"Test document"',
149 '!version#2' => 'version#2',
151 '!version#2' => 'version#2',
150 '!version:1.0' => 'version:1.0',
152 '!version:1.0' => 'version:1.0',
151 '!version:"1.0"' => 'version:"1.0"',
153 '!version:"1.0"' => 'version:"1.0"',
152 '!source:/some/file' => 'source:/some/file',
154 '!source:/some/file' => 'source:/some/file',
153 # invalid expressions
155 # invalid expressions
154 'source:' => 'source:',
156 'source:' => 'source:',
155 # url hash
157 # url hash
156 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
158 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
157 }
159 }
158 @project = Project.find(1)
160 @project = Project.find(1)
159 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
161 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
160 end
162 end
161
163
162 def test_wiki_links
164 def test_wiki_links
163 to_test = {
165 to_test = {
164 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
166 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
165 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
167 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
166 # link with anchor
168 # link with anchor
167 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
169 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
168 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
170 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
169 # page that doesn't exist
171 # page that doesn't exist
170 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
172 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
171 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
173 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
172 # link to another project wiki
174 # link to another project wiki
173 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
175 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
174 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
176 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
175 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
177 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
176 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
178 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
177 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
179 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
178 # striked through link
180 # striked through link
179 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
181 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
180 '-[[Another page|Page]] link-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a> link</del>',
182 '-[[Another page|Page]] link-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a> link</del>',
181 # escaping
183 # escaping
182 '![[Another page|Page]]' => '[[Another page|Page]]',
184 '![[Another page|Page]]' => '[[Another page|Page]]',
183 }
185 }
184 @project = Project.find(1)
186 @project = Project.find(1)
185 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
187 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
186 end
188 end
187
189
188 def test_html_tags
190 def test_html_tags
189 to_test = {
191 to_test = {
190 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
192 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
191 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
193 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
192 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
194 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
193 # do not escape pre/code tags
195 # do not escape pre/code tags
194 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
196 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
195 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
197 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
196 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
198 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
197 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
199 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
198 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
200 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
199 # remove attributes except class
201 # remove attributes except class
200 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
202 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
201 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
203 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
202 }
204 }
203 to_test.each { |text, result| assert_equal result, textilizable(text) }
205 to_test.each { |text, result| assert_equal result, textilizable(text) }
204 end
206 end
205
207
206 def test_allowed_html_tags
208 def test_allowed_html_tags
207 to_test = {
209 to_test = {
208 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
210 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
209 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
211 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
210 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
212 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
211 }
213 }
212 to_test.each { |text, result| assert_equal result, textilizable(text) }
214 to_test.each { |text, result| assert_equal result, textilizable(text) }
213 end
215 end
214
216
215 def syntax_highlight
217 def syntax_highlight
216 raw = <<-RAW
218 raw = <<-RAW
217 <pre><code class="ruby">
219 <pre><code class="ruby">
218 # Some ruby code here
220 # Some ruby code here
219 </pre></code>
221 </pre></code>
220 RAW
222 RAW
221
223
222 expected = <<-EXPECTED
224 expected = <<-EXPECTED
223 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
225 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
224 </pre></code>
226 </pre></code>
225 EXPECTED
227 EXPECTED
226
228
227 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
229 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
228 end
230 end
229
231
230 def test_wiki_links_in_tables
232 def test_wiki_links_in_tables
231 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
233 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
232 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
234 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
233 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
235 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
234 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
236 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
235 }
237 }
236 @project = Project.find(1)
238 @project = Project.find(1)
237 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
239 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
238 end
240 end
239
241
240 def test_text_formatting
242 def test_text_formatting
241 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
243 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
242 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
244 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
243 }
245 }
244 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
246 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
245 end
247 end
246
248
247 def test_wiki_horizontal_rule
249 def test_wiki_horizontal_rule
248 assert_equal '<hr />', textilizable('---')
250 assert_equal '<hr />', textilizable('---')
249 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
251 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
250 end
252 end
251
253
252 def test_acronym
254 def test_acronym
253 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
255 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
254 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
256 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
255 end
257 end
256
258
257 def test_footnotes
259 def test_footnotes
258 raw = <<-RAW
260 raw = <<-RAW
259 This is some text[1].
261 This is some text[1].
260
262
261 fn1. This is the foot note
263 fn1. This is the foot note
262 RAW
264 RAW
263
265
264 expected = <<-EXPECTED
266 expected = <<-EXPECTED
265 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
267 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
266 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
268 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
267 EXPECTED
269 EXPECTED
268
270
269 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
271 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
270 end
272 end
271
273
272 def test_table_of_content
274 def test_table_of_content
273 raw = <<-RAW
275 raw = <<-RAW
274 {{toc}}
276 {{toc}}
275
277
276 h1. Title
278 h1. Title
277
279
278 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
280 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
279
281
280 h2. Subtitle
282 h2. Subtitle
281
283
282 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
284 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
283
285
284 h2. Subtitle with %{color:red}red text%
286 h2. Subtitle with %{color:red}red text%
285
287
286 h1. Another title
288 h1. Another title
287
289
288 RAW
290 RAW
289
291
290 expected = '<ul class="toc">' +
292 expected = '<ul class="toc">' +
291 '<li class="heading1"><a href="#Title">Title</a></li>' +
293 '<li class="heading1"><a href="#Title">Title</a></li>' +
292 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
294 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
293 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
295 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
294 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
296 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
295 '</ul>'
297 '</ul>'
296
298
297 assert textilizable(raw).gsub("\n", "").include?(expected)
299 assert textilizable(raw).gsub("\n", "").include?(expected)
298 end
300 end
299
301
300 def test_blockquote
302 def test_blockquote
301 # orig raw text
303 # orig raw text
302 raw = <<-RAW
304 raw = <<-RAW
303 John said:
305 John said:
304 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
306 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
305 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
307 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
306 > * Donec odio lorem,
308 > * Donec odio lorem,
307 > * sagittis ac,
309 > * sagittis ac,
308 > * malesuada in,
310 > * malesuada in,
309 > * adipiscing eu, dolor.
311 > * adipiscing eu, dolor.
310 >
312 >
311 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
313 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
312 > Proin a tellus. Nam vel neque.
314 > Proin a tellus. Nam vel neque.
313
315
314 He's right.
316 He's right.
315 RAW
317 RAW
316
318
317 # expected html
319 # expected html
318 expected = <<-EXPECTED
320 expected = <<-EXPECTED
319 <p>John said:</p>
321 <p>John said:</p>
320 <blockquote>
322 <blockquote>
321 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
323 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
322 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
324 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
323 <ul>
325 <ul>
324 <li>Donec odio lorem,</li>
326 <li>Donec odio lorem,</li>
325 <li>sagittis ac,</li>
327 <li>sagittis ac,</li>
326 <li>malesuada in,</li>
328 <li>malesuada in,</li>
327 <li>adipiscing eu, dolor.</li>
329 <li>adipiscing eu, dolor.</li>
328 </ul>
330 </ul>
329 <blockquote>
331 <blockquote>
330 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
332 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
331 </blockquote>
333 </blockquote>
332 <p>Proin a tellus. Nam vel neque.</p>
334 <p>Proin a tellus. Nam vel neque.</p>
333 </blockquote>
335 </blockquote>
334 <p>He's right.</p>
336 <p>He's right.</p>
335 EXPECTED
337 EXPECTED
336
338
337 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
339 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
338 end
340 end
339
341
340 def test_table
342 def test_table
341 raw = <<-RAW
343 raw = <<-RAW
342 This is a table with empty cells:
344 This is a table with empty cells:
343
345
344 |cell11|cell12||
346 |cell11|cell12||
345 |cell21||cell23|
347 |cell21||cell23|
346 |cell31|cell32|cell33|
348 |cell31|cell32|cell33|
347 RAW
349 RAW
348
350
349 expected = <<-EXPECTED
351 expected = <<-EXPECTED
350 <p>This is a table with empty cells:</p>
352 <p>This is a table with empty cells:</p>
351
353
352 <table>
354 <table>
353 <tr><td>cell11</td><td>cell12</td><td></td></tr>
355 <tr><td>cell11</td><td>cell12</td><td></td></tr>
354 <tr><td>cell21</td><td></td><td>cell23</td></tr>
356 <tr><td>cell21</td><td></td><td>cell23</td></tr>
355 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
357 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
356 </table>
358 </table>
357 EXPECTED
359 EXPECTED
358
360
359 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
361 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
360 end
362 end
361
363
362 def test_default_formatter
364 def test_default_formatter
363 Setting.text_formatting = 'unknown'
365 Setting.text_formatting = 'unknown'
364 text = 'a *link*: http://www.example.net/'
366 text = 'a *link*: http://www.example.net/'
365 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
367 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
366 Setting.text_formatting = 'textile'
368 Setting.text_formatting = 'textile'
367 end
369 end
368
370
369 def test_date_format_default
371 def test_date_format_default
370 today = Date.today
372 today = Date.today
371 Setting.date_format = ''
373 Setting.date_format = ''
372 assert_equal l_date(today), format_date(today)
374 assert_equal l_date(today), format_date(today)
373 end
375 end
374
376
375 def test_date_format
377 def test_date_format
376 today = Date.today
378 today = Date.today
377 Setting.date_format = '%d %m %Y'
379 Setting.date_format = '%d %m %Y'
378 assert_equal today.strftime('%d %m %Y'), format_date(today)
380 assert_equal today.strftime('%d %m %Y'), format_date(today)
379 end
381 end
380
382
381 def test_time_format_default
383 def test_time_format_default
382 now = Time.now
384 now = Time.now
383 Setting.date_format = ''
385 Setting.date_format = ''
384 Setting.time_format = ''
386 Setting.time_format = ''
385 assert_equal l_datetime(now), format_time(now)
387 assert_equal l_datetime(now), format_time(now)
386 assert_equal l_time(now), format_time(now, false)
388 assert_equal l_time(now), format_time(now, false)
387 end
389 end
388
390
389 def test_time_format
391 def test_time_format
390 now = Time.now
392 now = Time.now
391 Setting.date_format = '%d %m %Y'
393 Setting.date_format = '%d %m %Y'
392 Setting.time_format = '%H %M'
394 Setting.time_format = '%H %M'
393 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
395 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
394 assert_equal now.strftime('%H %M'), format_time(now, false)
396 assert_equal now.strftime('%H %M'), format_time(now, false)
395 end
397 end
396
398
397 def test_utc_time_format
399 def test_utc_time_format
398 now = Time.now.utc
400 now = Time.now.utc
399 Setting.date_format = '%d %m %Y'
401 Setting.date_format = '%d %m %Y'
400 Setting.time_format = '%H %M'
402 Setting.time_format = '%H %M'
401 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
403 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
402 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
404 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
403 end
405 end
404
406
405 def test_due_date_distance_in_words
407 def test_due_date_distance_in_words
406 to_test = { Date.today => 'Due in 0 days',
408 to_test = { Date.today => 'Due in 0 days',
407 Date.today + 1 => 'Due in 1 day',
409 Date.today + 1 => 'Due in 1 day',
408 Date.today + 100 => 'Due in 100 days',
410 Date.today + 100 => 'Due in 100 days',
409 Date.today + 20000 => 'Due in 20000 days',
411 Date.today + 20000 => 'Due in 20000 days',
410 Date.today - 1 => '1 day late',
412 Date.today - 1 => '1 day late',
411 Date.today - 100 => '100 days late',
413 Date.today - 100 => '100 days late',
412 Date.today - 20000 => '20000 days late',
414 Date.today - 20000 => '20000 days late',
413 }
415 }
414 to_test.each do |date, expected|
416 to_test.each do |date, expected|
415 assert_equal expected, due_date_distance_in_words(date)
417 assert_equal expected, due_date_distance_in_words(date)
416 end
418 end
417 end
419 end
418
420
419 def test_avatar
421 def test_avatar
420 # turn on avatars
422 # turn on avatars
421 Setting.gravatar_enabled = '1'
423 Setting.gravatar_enabled = '1'
422 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
424 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
423 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
425 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
424 assert_nil avatar('jsmith')
426 assert_nil avatar('jsmith')
425 assert_nil avatar(nil)
427 assert_nil avatar(nil)
426
428
427 # turn off avatars
429 # turn off avatars
428 Setting.gravatar_enabled = '0'
430 Setting.gravatar_enabled = '0'
429 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
431 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
430 end
432 end
431 end
433 end
General Comments 0
You need to be logged in to leave comments. Login now