##// END OF EJS Templates
Fixes html escaping....
Jean-Philippe Lang -
r1899:2e7e26fbb4c8
parent child
Show More
@@ -1,1164 +1,1164
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]
275 :inline_textile_code, :inline_textile_span]
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 )
439 str.gsub!( '&', '&amp;' )
439 str.gsub!( '&', '&amp;' )
440 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
440 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
441 str.gsub!( "'", '&#039;' ) if mode == :Quotes
441 str.gsub!( "'", '&#039;' ) if mode == :Quotes
442 str.gsub!( '<', '&lt;')
442 str.gsub!( '<', '&lt;')
443 str.gsub!( '>', '&gt;')
443 str.gsub!( '>', '&gt;')
444 end
444 end
445
445
446 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
446 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
447 def pgl( text )
447 def pgl( text )
448 GLYPHS.each do |re, resub, tog|
448 GLYPHS.each do |re, resub, tog|
449 next if tog and method( tog ).call
449 next if tog and method( tog ).call
450 text.gsub! re, resub
450 text.gsub! re, resub
451 end
451 end
452 end
452 end
453
453
454 # Parses Textile attribute lists and builds an HTML attribute string
454 # Parses Textile attribute lists and builds an HTML attribute string
455 def pba( text_in, element = "" )
455 def pba( text_in, element = "" )
456
456
457 return '' unless text_in
457 return '' unless text_in
458
458
459 style = []
459 style = []
460 text = text_in.dup
460 text = text_in.dup
461 if element == 'td'
461 if element == 'td'
462 colspan = $1 if text =~ /\\(\d+)/
462 colspan = $1 if text =~ /\\(\d+)/
463 rowspan = $1 if text =~ /\/(\d+)/
463 rowspan = $1 if text =~ /\/(\d+)/
464 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
464 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
465 end
465 end
466
466
467 style << "#{ $1 };" if not filter_styles and
467 style << "#{ $1 };" if not filter_styles and
468 text.sub!( /\{([^}]*)\}/, '' )
468 text.sub!( /\{([^}]*)\}/, '' )
469
469
470 lang = $1 if
470 lang = $1 if
471 text.sub!( /\[([^)]+?)\]/, '' )
471 text.sub!( /\[([^)]+?)\]/, '' )
472
472
473 cls = $1 if
473 cls = $1 if
474 text.sub!( /\(([^()]+?)\)/, '' )
474 text.sub!( /\(([^()]+?)\)/, '' )
475
475
476 style << "padding-left:#{ $1.length }em;" if
476 style << "padding-left:#{ $1.length }em;" if
477 text.sub!( /([(]+)/, '' )
477 text.sub!( /([(]+)/, '' )
478
478
479 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
479 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
480
480
481 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
481 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
482
482
483 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
483 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
484
484
485 atts = ''
485 atts = ''
486 atts << " style=\"#{ style.join }\"" unless style.empty?
486 atts << " style=\"#{ style.join }\"" unless style.empty?
487 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
487 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
488 atts << " lang=\"#{ lang }\"" if lang
488 atts << " lang=\"#{ lang }\"" if lang
489 atts << " id=\"#{ id }\"" if id
489 atts << " id=\"#{ id }\"" if id
490 atts << " colspan=\"#{ colspan }\"" if colspan
490 atts << " colspan=\"#{ colspan }\"" if colspan
491 atts << " rowspan=\"#{ rowspan }\"" if rowspan
491 atts << " rowspan=\"#{ rowspan }\"" if rowspan
492
492
493 atts
493 atts
494 end
494 end
495
495
496 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
496 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
497
497
498 # Parses a Textile table block, building HTML from the result.
498 # Parses a Textile table block, building HTML from the result.
499 def block_textile_table( text )
499 def block_textile_table( text )
500 text.gsub!( TABLE_RE ) do |matches|
500 text.gsub!( TABLE_RE ) do |matches|
501
501
502 tatts, fullrow = $~[1..2]
502 tatts, fullrow = $~[1..2]
503 tatts = pba( tatts, 'table' )
503 tatts = pba( tatts, 'table' )
504 tatts = shelve( tatts ) if tatts
504 tatts = shelve( tatts ) if tatts
505 rows = []
505 rows = []
506
506
507 fullrow.each_line do |row|
507 fullrow.each_line do |row|
508 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
508 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
509 cells = []
509 cells = []
510 row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell|
510 row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell|
511 next if cell == '|'
511 next if cell == '|'
512 ctyp = 'd'
512 ctyp = 'd'
513 ctyp = 'h' if cell =~ /^_/
513 ctyp = 'h' if cell =~ /^_/
514
514
515 catts = ''
515 catts = ''
516 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
516 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
517
517
518 catts = shelve( catts ) if catts
518 catts = shelve( catts ) if catts
519 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
519 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
520 end
520 end
521 ratts = shelve( ratts ) if ratts
521 ratts = shelve( ratts ) if ratts
522 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
522 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
523 end
523 end
524 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
524 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
525 end
525 end
526 end
526 end
527
527
528 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
528 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
529 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
529 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
530
530
531 # Parses Textile lists and generates HTML
531 # Parses Textile lists and generates HTML
532 def block_textile_lists( text )
532 def block_textile_lists( text )
533 text.gsub!( LISTS_RE ) do |match|
533 text.gsub!( LISTS_RE ) do |match|
534 lines = match.split( /\n/ )
534 lines = match.split( /\n/ )
535 last_line = -1
535 last_line = -1
536 depth = []
536 depth = []
537 lines.each_with_index do |line, line_id|
537 lines.each_with_index do |line, line_id|
538 if line =~ LISTS_CONTENT_RE
538 if line =~ LISTS_CONTENT_RE
539 tl,atts,content = $~[1..3]
539 tl,atts,content = $~[1..3]
540 if depth.last
540 if depth.last
541 if depth.last.length > tl.length
541 if depth.last.length > tl.length
542 (depth.length - 1).downto(0) do |i|
542 (depth.length - 1).downto(0) do |i|
543 break if depth[i].length == tl.length
543 break if depth[i].length == tl.length
544 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
544 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
545 depth.pop
545 depth.pop
546 end
546 end
547 end
547 end
548 if depth.last and depth.last.length == tl.length
548 if depth.last and depth.last.length == tl.length
549 lines[line_id - 1] << '</li>'
549 lines[line_id - 1] << '</li>'
550 end
550 end
551 end
551 end
552 unless depth.last == tl
552 unless depth.last == tl
553 depth << tl
553 depth << tl
554 atts = pba( atts )
554 atts = pba( atts )
555 atts = shelve( atts ) if atts
555 atts = shelve( atts ) if atts
556 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
556 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
557 else
557 else
558 lines[line_id] = "\t\t<li>#{ content }"
558 lines[line_id] = "\t\t<li>#{ content }"
559 end
559 end
560 last_line = line_id
560 last_line = line_id
561
561
562 else
562 else
563 last_line = line_id
563 last_line = line_id
564 end
564 end
565 if line_id - last_line > 1 or line_id == lines.length - 1
565 if line_id - last_line > 1 or line_id == lines.length - 1
566 depth.delete_if do |v|
566 depth.delete_if do |v|
567 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
567 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
568 end
568 end
569 end
569 end
570 end
570 end
571 lines.join( "\n" )
571 lines.join( "\n" )
572 end
572 end
573 end
573 end
574
574
575 QUOTES_RE = /(^>+([^\n]*?)\n?)+/m
575 QUOTES_RE = /(^>+([^\n]*?)\n?)+/m
576 QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
576 QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
577
577
578 def block_textile_quotes( text )
578 def block_textile_quotes( text )
579 text.gsub!( QUOTES_RE ) do |match|
579 text.gsub!( QUOTES_RE ) do |match|
580 lines = match.split( /\n/ )
580 lines = match.split( /\n/ )
581 quotes = ''
581 quotes = ''
582 indent = 0
582 indent = 0
583 lines.each do |line|
583 lines.each do |line|
584 line =~ QUOTES_CONTENT_RE
584 line =~ QUOTES_CONTENT_RE
585 bq,content = $1, $2
585 bq,content = $1, $2
586 l = bq.count('>')
586 l = bq.count('>')
587 if l != indent
587 if l != indent
588 quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
588 quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
589 indent = l
589 indent = l
590 end
590 end
591 quotes << (content + "\n")
591 quotes << (content + "\n")
592 end
592 end
593 quotes << ("\n" + '</blockquote>' * indent + "\n\n")
593 quotes << ("\n" + '</blockquote>' * indent + "\n\n")
594 quotes
594 quotes
595 end
595 end
596 end
596 end
597
597
598 CODE_RE = /(\W)
598 CODE_RE = /(\W)
599 @
599 @
600 (?:\|(\w+?)\|)?
600 (?:\|(\w+?)\|)?
601 (.+?)
601 (.+?)
602 @
602 @
603 (?=\W)/x
603 (?=\W)/x
604
604
605 def inline_textile_code( text )
605 def inline_textile_code( text )
606 text.gsub!( CODE_RE ) do |m|
606 text.gsub!( CODE_RE ) do |m|
607 before,lang,code,after = $~[1..4]
607 before,lang,code,after = $~[1..4]
608 lang = " lang=\"#{ lang }\"" if lang
608 lang = " lang=\"#{ lang }\"" if lang
609 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
609 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
610 end
610 end
611 end
611 end
612
612
613 def lT( text )
613 def lT( text )
614 text =~ /\#$/ ? 'o' : 'u'
614 text =~ /\#$/ ? 'o' : 'u'
615 end
615 end
616
616
617 def hard_break( text )
617 def hard_break( text )
618 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
618 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
619 end
619 end
620
620
621 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
621 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
622
622
623 def blocks( text, deep_code = false )
623 def blocks( text, deep_code = false )
624 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
624 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
625 plain = blk !~ /\A[#*> ]/
625 plain = blk !~ /\A[#*> ]/
626
626
627 # skip blocks that are complex HTML
627 # skip blocks that are complex HTML
628 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
628 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
629 blk
629 blk
630 else
630 else
631 # search for indentation levels
631 # search for indentation levels
632 blk.strip!
632 blk.strip!
633 if blk.empty?
633 if blk.empty?
634 blk
634 blk
635 else
635 else
636 code_blk = nil
636 code_blk = nil
637 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
637 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
638 flush_left iblk
638 flush_left iblk
639 blocks iblk, plain
639 blocks iblk, plain
640 iblk.gsub( /^(\S)/, "\t\\1" )
640 iblk.gsub( /^(\S)/, "\t\\1" )
641 if plain
641 if plain
642 code_blk = iblk; ""
642 code_blk = iblk; ""
643 else
643 else
644 iblk
644 iblk
645 end
645 end
646 end
646 end
647
647
648 block_applied = 0
648 block_applied = 0
649 @rules.each do |rule_name|
649 @rules.each do |rule_name|
650 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
650 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
651 end
651 end
652 if block_applied.zero?
652 if block_applied.zero?
653 if deep_code
653 if deep_code
654 blk = "\t<pre><code>#{ blk }</code></pre>"
654 blk = "\t<pre><code>#{ blk }</code></pre>"
655 else
655 else
656 blk = "\t<p>#{ blk }</p>"
656 blk = "\t<p>#{ blk }</p>"
657 end
657 end
658 end
658 end
659 # hard_break blk
659 # hard_break blk
660 blk + "\n#{ code_blk }"
660 blk + "\n#{ code_blk }"
661 end
661 end
662 end
662 end
663
663
664 end.join( "\n\n" ) )
664 end.join( "\n\n" ) )
665 end
665 end
666
666
667 def textile_bq( tag, atts, cite, content )
667 def textile_bq( tag, atts, cite, content )
668 cite, cite_title = check_refs( cite )
668 cite, cite_title = check_refs( cite )
669 cite = " cite=\"#{ cite }\"" if cite
669 cite = " cite=\"#{ cite }\"" if cite
670 atts = shelve( atts ) if atts
670 atts = shelve( atts ) if atts
671 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
671 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
672 end
672 end
673
673
674 def textile_p( tag, atts, cite, content )
674 def textile_p( tag, atts, cite, content )
675 atts = shelve( atts ) if atts
675 atts = shelve( atts ) if atts
676 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
676 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
677 end
677 end
678
678
679 alias textile_h1 textile_p
679 alias textile_h1 textile_p
680 alias textile_h2 textile_p
680 alias textile_h2 textile_p
681 alias textile_h3 textile_p
681 alias textile_h3 textile_p
682 alias textile_h4 textile_p
682 alias textile_h4 textile_p
683 alias textile_h5 textile_p
683 alias textile_h5 textile_p
684 alias textile_h6 textile_p
684 alias textile_h6 textile_p
685
685
686 def textile_fn_( tag, num, atts, cite, content )
686 def textile_fn_( tag, num, atts, cite, content )
687 atts << " id=\"fn#{ num }\""
687 atts << " id=\"fn#{ num }\""
688 content = "<sup>#{ num }</sup> #{ content }"
688 content = "<sup>#{ num }</sup> #{ content }"
689 atts = shelve( atts ) if atts
689 atts = shelve( atts ) if atts
690 "\t<p#{ atts }>#{ content }</p>"
690 "\t<p#{ atts }>#{ content }</p>"
691 end
691 end
692
692
693 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
693 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
694
694
695 def block_textile_prefix( text )
695 def block_textile_prefix( text )
696 if text =~ BLOCK_RE
696 if text =~ BLOCK_RE
697 tag,tagpre,num,atts,cite,content = $~[1..6]
697 tag,tagpre,num,atts,cite,content = $~[1..6]
698 atts = pba( atts )
698 atts = pba( atts )
699
699
700 # pass to prefix handler
700 # pass to prefix handler
701 if respond_to? "textile_#{ tag }", true
701 if respond_to? "textile_#{ tag }", true
702 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
702 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
703 elsif respond_to? "textile_#{ tagpre }_", true
703 elsif respond_to? "textile_#{ tagpre }_", true
704 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
704 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
705 end
705 end
706 end
706 end
707 end
707 end
708
708
709 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
709 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
710 def block_markdown_setext( text )
710 def block_markdown_setext( text )
711 if text =~ SETEXT_RE
711 if text =~ SETEXT_RE
712 tag = if $2 == "="; "h1"; else; "h2"; end
712 tag = if $2 == "="; "h1"; else; "h2"; end
713 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
713 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
714 blocks cont
714 blocks cont
715 text.replace( blk + cont )
715 text.replace( blk + cont )
716 end
716 end
717 end
717 end
718
718
719 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
719 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
720 [ ]*
720 [ ]*
721 (.+?) # $2 = Header text
721 (.+?) # $2 = Header text
722 [ ]*
722 [ ]*
723 \#* # optional closing #'s (not counted)
723 \#* # optional closing #'s (not counted)
724 $/x
724 $/x
725 def block_markdown_atx( text )
725 def block_markdown_atx( text )
726 if text =~ ATX_RE
726 if text =~ ATX_RE
727 tag = "h#{ $1.length }"
727 tag = "h#{ $1.length }"
728 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
728 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
729 blocks cont
729 blocks cont
730 text.replace( blk + cont )
730 text.replace( blk + cont )
731 end
731 end
732 end
732 end
733
733
734 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
734 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
735
735
736 def block_markdown_bq( text )
736 def block_markdown_bq( text )
737 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
737 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
738 blk.gsub!( /^ *> ?/, '' )
738 blk.gsub!( /^ *> ?/, '' )
739 flush_left blk
739 flush_left blk
740 blocks blk
740 blocks blk
741 blk.gsub!( /^(\S)/, "\t\\1" )
741 blk.gsub!( /^(\S)/, "\t\\1" )
742 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
742 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
743 end
743 end
744 end
744 end
745
745
746 MARKDOWN_RULE_RE = /^(#{
746 MARKDOWN_RULE_RE = /^(#{
747 ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
747 ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
748 })$/
748 })$/
749
749
750 def block_markdown_rule( text )
750 def block_markdown_rule( text )
751 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
751 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
752 "<hr />"
752 "<hr />"
753 end
753 end
754 end
754 end
755
755
756 # XXX TODO XXX
756 # XXX TODO XXX
757 def block_markdown_lists( text )
757 def block_markdown_lists( text )
758 end
758 end
759
759
760 def inline_textile_span( text )
760 def inline_textile_span( text )
761 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
761 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
762 text.gsub!( qtag_re ) do |m|
762 text.gsub!( qtag_re ) do |m|
763
763
764 case rtype
764 case rtype
765 when :limit
765 when :limit
766 sta,qtag,atts,cite,content = $~[1..5]
766 sta,qtag,atts,cite,content = $~[1..5]
767 else
767 else
768 qtag,atts,cite,content = $~[1..4]
768 qtag,atts,cite,content = $~[1..4]
769 sta = ''
769 sta = ''
770 end
770 end
771 atts = pba( atts )
771 atts = pba( atts )
772 atts << " cite=\"#{ cite }\"" if cite
772 atts << " cite=\"#{ cite }\"" if cite
773 atts = shelve( atts ) if atts
773 atts = shelve( atts ) if atts
774
774
775 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
775 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
776
776
777 end
777 end
778 end
778 end
779 end
779 end
780
780
781 LINK_RE = /
781 LINK_RE = /
782 ([\s\[{(]|[#{PUNCT}])? # $pre
782 ([\s\[{(]|[#{PUNCT}])? # $pre
783 " # start
783 " # start
784 (#{C}) # $atts
784 (#{C}) # $atts
785 ([^"\n]+?) # $text
785 ([^"\n]+?) # $text
786 \s?
786 \s?
787 (?:\(([^)]+?)\)(?="))? # $title
787 (?:\(([^)]+?)\)(?="))? # $title
788 ":
788 ":
789 ([\w\/]\S+?) # $url
789 ([\w\/]\S+?) # $url
790 (\/)? # $slash
790 (\/)? # $slash
791 ([^\w\=\/;\(\)]*?) # $post
791 ([^\w\=\/;\(\)]*?) # $post
792 (?=<|\s|$)
792 (?=<|\s|$)
793 /x
793 /x
794 #"
794 #"
795 def inline_textile_link( text )
795 def inline_textile_link( text )
796 text.gsub!( LINK_RE ) do |m|
796 text.gsub!( LINK_RE ) do |m|
797 pre,atts,text,title,url,slash,post = $~[1..7]
797 pre,atts,text,title,url,slash,post = $~[1..7]
798
798
799 url, url_title = check_refs( url )
799 url, url_title = check_refs( url )
800 title ||= url_title
800 title ||= url_title
801
801
802 # Idea below : an URL with unbalanced parethesis and
802 # Idea below : an URL with unbalanced parethesis and
803 # ending by ')' is put into external parenthesis
803 # ending by ')' is put into external parenthesis
804 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
804 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
805 url=url[0..-2] # discard closing parenth from url
805 url=url[0..-2] # discard closing parenth from url
806 post = ")"+post # add closing parenth to post
806 post = ")"+post # add closing parenth to post
807 end
807 end
808 atts = pba( atts )
808 atts = pba( atts )
809 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
809 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
810 atts << " title=\"#{ title }\"" if title
810 atts << " title=\"#{ title }\"" if title
811 atts = shelve( atts ) if atts
811 atts = shelve( atts ) if atts
812
812
813 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
813 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
814
814
815 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
815 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
816 end
816 end
817 end
817 end
818
818
819 MARKDOWN_REFLINK_RE = /
819 MARKDOWN_REFLINK_RE = /
820 \[([^\[\]]+)\] # $text
820 \[([^\[\]]+)\] # $text
821 [ ]? # opt. space
821 [ ]? # opt. space
822 (?:\n[ ]*)? # one optional newline followed by spaces
822 (?:\n[ ]*)? # one optional newline followed by spaces
823 \[(.*?)\] # $id
823 \[(.*?)\] # $id
824 /x
824 /x
825
825
826 def inline_markdown_reflink( text )
826 def inline_markdown_reflink( text )
827 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
827 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
828 text, id = $~[1..2]
828 text, id = $~[1..2]
829
829
830 if id.empty?
830 if id.empty?
831 url, title = check_refs( text )
831 url, title = check_refs( text )
832 else
832 else
833 url, title = check_refs( id )
833 url, title = check_refs( id )
834 end
834 end
835
835
836 atts = " href=\"#{ url }\""
836 atts = " href=\"#{ url }\""
837 atts << " title=\"#{ title }\"" if title
837 atts << " title=\"#{ title }\"" if title
838 atts = shelve( atts )
838 atts = shelve( atts )
839
839
840 "<a#{ atts }>#{ text }</a>"
840 "<a#{ atts }>#{ text }</a>"
841 end
841 end
842 end
842 end
843
843
844 MARKDOWN_LINK_RE = /
844 MARKDOWN_LINK_RE = /
845 \[([^\[\]]+)\] # $text
845 \[([^\[\]]+)\] # $text
846 \( # open paren
846 \( # open paren
847 [ \t]* # opt space
847 [ \t]* # opt space
848 <?(.+?)>? # $href
848 <?(.+?)>? # $href
849 [ \t]* # opt space
849 [ \t]* # opt space
850 (?: # whole title
850 (?: # whole title
851 (['"]) # $quote
851 (['"]) # $quote
852 (.*?) # $title
852 (.*?) # $title
853 \3 # matching quote
853 \3 # matching quote
854 )? # title is optional
854 )? # title is optional
855 \)
855 \)
856 /x
856 /x
857
857
858 def inline_markdown_link( text )
858 def inline_markdown_link( text )
859 text.gsub!( MARKDOWN_LINK_RE ) do |m|
859 text.gsub!( MARKDOWN_LINK_RE ) do |m|
860 text, url, quote, title = $~[1..4]
860 text, url, quote, title = $~[1..4]
861
861
862 atts = " href=\"#{ url }\""
862 atts = " href=\"#{ url }\""
863 atts << " title=\"#{ title }\"" if title
863 atts << " title=\"#{ title }\"" if title
864 atts = shelve( atts )
864 atts = shelve( atts )
865
865
866 "<a#{ atts }>#{ text }</a>"
866 "<a#{ atts }>#{ text }</a>"
867 end
867 end
868 end
868 end
869
869
870 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
870 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
871 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
871 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
872
872
873 def refs( text )
873 def refs( text )
874 @rules.each do |rule_name|
874 @rules.each do |rule_name|
875 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
875 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
876 end
876 end
877 end
877 end
878
878
879 def refs_textile( text )
879 def refs_textile( text )
880 text.gsub!( TEXTILE_REFS_RE ) do |m|
880 text.gsub!( TEXTILE_REFS_RE ) do |m|
881 flag, url = $~[2..3]
881 flag, url = $~[2..3]
882 @urlrefs[flag.downcase] = [url, nil]
882 @urlrefs[flag.downcase] = [url, nil]
883 nil
883 nil
884 end
884 end
885 end
885 end
886
886
887 def refs_markdown( text )
887 def refs_markdown( text )
888 text.gsub!( MARKDOWN_REFS_RE ) do |m|
888 text.gsub!( MARKDOWN_REFS_RE ) do |m|
889 flag, url = $~[2..3]
889 flag, url = $~[2..3]
890 title = $~[6]
890 title = $~[6]
891 @urlrefs[flag.downcase] = [url, title]
891 @urlrefs[flag.downcase] = [url, title]
892 nil
892 nil
893 end
893 end
894 end
894 end
895
895
896 def check_refs( text )
896 def check_refs( text )
897 ret = @urlrefs[text.downcase] if text
897 ret = @urlrefs[text.downcase] if text
898 ret || [text, nil]
898 ret || [text, nil]
899 end
899 end
900
900
901 IMAGE_RE = /
901 IMAGE_RE = /
902 (<p>|.|^) # start of line?
902 (<p>|.|^) # start of line?
903 \! # opening
903 \! # opening
904 (\<|\=|\>)? # optional alignment atts
904 (\<|\=|\>)? # optional alignment atts
905 (#{C}) # optional style,class atts
905 (#{C}) # optional style,class atts
906 (?:\. )? # optional dot-space
906 (?:\. )? # optional dot-space
907 ([^\s(!]+?) # presume this is the src
907 ([^\s(!]+?) # presume this is the src
908 \s? # optional space
908 \s? # optional space
909 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
909 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
910 \! # closing
910 \! # closing
911 (?::#{ HYPERLINK })? # optional href
911 (?::#{ HYPERLINK })? # optional href
912 /x
912 /x
913
913
914 def inline_textile_image( text )
914 def inline_textile_image( text )
915 text.gsub!( IMAGE_RE ) do |m|
915 text.gsub!( IMAGE_RE ) do |m|
916 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
916 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
917 atts = pba( atts )
917 atts = pba( atts )
918 atts = " src=\"#{ url }\"#{ atts }"
918 atts = " src=\"#{ url }\"#{ atts }"
919 atts << " title=\"#{ title }\"" if title
919 atts << " title=\"#{ title }\"" if title
920 atts << " alt=\"#{ title }\""
920 atts << " alt=\"#{ title }\""
921 # size = @getimagesize($url);
921 # size = @getimagesize($url);
922 # if($size) $atts.= " $size[3]";
922 # if($size) $atts.= " $size[3]";
923
923
924 href, alt_title = check_refs( href ) if href
924 href, alt_title = check_refs( href ) if href
925 url, url_title = check_refs( url )
925 url, url_title = check_refs( url )
926
926
927 out = ''
927 out = ''
928 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
928 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
929 out << "<img#{ shelve( atts ) } />"
929 out << "<img#{ shelve( atts ) } />"
930 out << "</a>#{ href_a1 }#{ href_a2 }" if href
930 out << "</a>#{ href_a1 }#{ href_a2 }" if href
931
931
932 if algn
932 if algn
933 algn = h_align( algn )
933 algn = h_align( algn )
934 if stln == "<p>"
934 if stln == "<p>"
935 out = "<p style=\"float:#{ algn }\">#{ out }"
935 out = "<p style=\"float:#{ algn }\">#{ out }"
936 else
936 else
937 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
937 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
938 end
938 end
939 else
939 else
940 out = stln + out
940 out = stln + out
941 end
941 end
942
942
943 out
943 out
944 end
944 end
945 end
945 end
946
946
947 def shelve( val )
947 def shelve( val )
948 @shelf << val
948 @shelf << val
949 " :redsh##{ @shelf.length }:"
949 " :redsh##{ @shelf.length }:"
950 end
950 end
951
951
952 def retrieve( text )
952 def retrieve( text )
953 @shelf.each_with_index do |r, i|
953 @shelf.each_with_index do |r, i|
954 text.gsub!( " :redsh##{ i + 1 }:", r )
954 text.gsub!( " :redsh##{ i + 1 }:", r )
955 end
955 end
956 end
956 end
957
957
958 def incoming_entities( text )
958 def incoming_entities( text )
959 ## turn any incoming ampersands into a dummy character for now.
959 ## turn any incoming ampersands into a dummy character for now.
960 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
960 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
961 ## implying an incoming html entity, to be skipped
961 ## implying an incoming html entity, to be skipped
962
962
963 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
963 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
964 end
964 end
965
965
966 def no_textile( text )
966 def no_textile( text )
967 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
967 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
968 '\1<notextile>\2</notextile>\3' )
968 '\1<notextile>\2</notextile>\3' )
969 text.gsub!( /^ *==([^=]+.*?)==/m,
969 text.gsub!( /^ *==([^=]+.*?)==/m,
970 '\1<notextile>\2</notextile>\3' )
970 '\1<notextile>\2</notextile>\3' )
971 end
971 end
972
972
973 def clean_white_space( text )
973 def clean_white_space( text )
974 # normalize line breaks
974 # normalize line breaks
975 text.gsub!( /\r\n/, "\n" )
975 text.gsub!( /\r\n/, "\n" )
976 text.gsub!( /\r/, "\n" )
976 text.gsub!( /\r/, "\n" )
977 text.gsub!( /\t/, ' ' )
977 text.gsub!( /\t/, ' ' )
978 text.gsub!( /^ +$/, '' )
978 text.gsub!( /^ +$/, '' )
979 text.gsub!( /\n{3,}/, "\n\n" )
979 text.gsub!( /\n{3,}/, "\n\n" )
980 text.gsub!( /"$/, "\" " )
980 text.gsub!( /"$/, "\" " )
981
981
982 # if entire document is indented, flush
982 # if entire document is indented, flush
983 # to the left side
983 # to the left side
984 flush_left text
984 flush_left text
985 end
985 end
986
986
987 def flush_left( text )
987 def flush_left( text )
988 indt = 0
988 indt = 0
989 if text =~ /^ /
989 if text =~ /^ /
990 while text !~ /^ {#{indt}}\S/
990 while text !~ /^ {#{indt}}\S/
991 indt += 1
991 indt += 1
992 end unless text.empty?
992 end unless text.empty?
993 if indt.nonzero?
993 if indt.nonzero?
994 text.gsub!( /^ {#{indt}}/, '' )
994 text.gsub!( /^ {#{indt}}/, '' )
995 end
995 end
996 end
996 end
997 end
997 end
998
998
999 def footnote_ref( text )
999 def footnote_ref( text )
1000 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1000 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1001 '<sup><a href="#fn\1">\1</a></sup>\2' )
1001 '<sup><a href="#fn\1">\1</a></sup>\2' )
1002 end
1002 end
1003
1003
1004 OFFTAGS = /(code|pre|kbd|notextile)/
1004 OFFTAGS = /(code|pre|kbd|notextile)/
1005 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
1005 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
1006 OFFTAG_OPEN = /<#{ OFFTAGS }/
1006 OFFTAG_OPEN = /<#{ OFFTAGS }/
1007 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1007 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1008 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1008 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1009 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1009 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1010
1010
1011 def glyphs_textile( text, level = 0 )
1011 def glyphs_textile( text, level = 0 )
1012 if text !~ HASTAG_MATCH
1012 if text !~ HASTAG_MATCH
1013 pgl text
1013 pgl text
1014 footnote_ref text
1014 footnote_ref text
1015 else
1015 else
1016 codepre = 0
1016 codepre = 0
1017 text.gsub!( ALLTAG_MATCH ) do |line|
1017 text.gsub!( ALLTAG_MATCH ) do |line|
1018 ## matches are off if we're between <code>, <pre> etc.
1018 ## matches are off if we're between <code>, <pre> etc.
1019 if $1
1019 if $1
1020 if line =~ OFFTAG_OPEN
1020 if line =~ OFFTAG_OPEN
1021 codepre += 1
1021 codepre += 1
1022 elsif line =~ OFFTAG_CLOSE
1022 elsif line =~ OFFTAG_CLOSE
1023 codepre -= 1
1023 codepre -= 1
1024 codepre = 0 if codepre < 0
1024 codepre = 0 if codepre < 0
1025 end
1025 end
1026 elsif codepre.zero?
1026 elsif codepre.zero?
1027 glyphs_textile( line, level + 1 )
1027 glyphs_textile( line, level + 1 )
1028 else
1028 else
1029 htmlesc( line, :NoQuotes )
1029 htmlesc( line, :NoQuotes )
1030 end
1030 end
1031 # p [level, codepre, line]
1031 # p [level, codepre, line]
1032
1032
1033 line
1033 line
1034 end
1034 end
1035 end
1035 end
1036 end
1036 end
1037
1037
1038 def rip_offtags( text )
1038 def rip_offtags( text )
1039 if text =~ /<.*>/
1039 if text =~ /<.*>/
1040 ## strip and encode <pre> content
1040 ## strip and encode <pre> content
1041 codepre, used_offtags = 0, {}
1041 codepre, used_offtags = 0, {}
1042 text.gsub!( OFFTAG_MATCH ) do |line|
1042 text.gsub!( OFFTAG_MATCH ) do |line|
1043 if $3
1043 if $3
1044 offtag, aftertag = $4, $5
1044 offtag, aftertag = $4, $5
1045 codepre += 1
1045 codepre += 1
1046 used_offtags[offtag] = true
1046 used_offtags[offtag] = true
1047 if codepre - used_offtags.length > 0
1047 if codepre - used_offtags.length > 0
1048 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1048 htmlesc( line, :NoQuotes )
1049 @pre_list.last << line
1049 @pre_list.last << line
1050 line = ""
1050 line = ""
1051 else
1051 else
1052 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1052 htmlesc( aftertag, :NoQuotes ) if aftertag
1053 line = "<redpre##{ @pre_list.length }>"
1053 line = "<redpre##{ @pre_list.length }>"
1054 @pre_list << "#{ $3 }#{ aftertag }"
1054 @pre_list << "#{ $3 }#{ aftertag }"
1055 end
1055 end
1056 elsif $1 and codepre > 0
1056 elsif $1 and codepre > 0
1057 if codepre - used_offtags.length > 0
1057 if codepre - used_offtags.length > 0
1058 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1058 htmlesc( line, :NoQuotes )
1059 @pre_list.last << line
1059 @pre_list.last << line
1060 line = ""
1060 line = ""
1061 end
1061 end
1062 codepre -= 1 unless codepre.zero?
1062 codepre -= 1 unless codepre.zero?
1063 used_offtags = {} if codepre.zero?
1063 used_offtags = {} if codepre.zero?
1064 end
1064 end
1065 line
1065 line
1066 end
1066 end
1067 end
1067 end
1068 text
1068 text
1069 end
1069 end
1070
1070
1071 def smooth_offtags( text )
1071 def smooth_offtags( text )
1072 unless @pre_list.empty?
1072 unless @pre_list.empty?
1073 ## replace <pre> content
1073 ## replace <pre> content
1074 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1074 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1075 end
1075 end
1076 end
1076 end
1077
1077
1078 def inline( text )
1078 def inline( text )
1079 [/^inline_/, /^glyphs_/].each do |meth_re|
1079 [/^inline_/, /^glyphs_/].each do |meth_re|
1080 @rules.each do |rule_name|
1080 @rules.each do |rule_name|
1081 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1081 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1082 end
1082 end
1083 end
1083 end
1084 end
1084 end
1085
1085
1086 def h_align( text )
1086 def h_align( text )
1087 H_ALGN_VALS[text]
1087 H_ALGN_VALS[text]
1088 end
1088 end
1089
1089
1090 def v_align( text )
1090 def v_align( text )
1091 V_ALGN_VALS[text]
1091 V_ALGN_VALS[text]
1092 end
1092 end
1093
1093
1094 def textile_popup_help( name, windowW, windowH )
1094 def textile_popup_help( name, windowW, windowH )
1095 ' <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 />'
1095 ' <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 />'
1096 end
1096 end
1097
1097
1098 # HTML cleansing stuff
1098 # HTML cleansing stuff
1099 BASIC_TAGS = {
1099 BASIC_TAGS = {
1100 'a' => ['href', 'title'],
1100 'a' => ['href', 'title'],
1101 'img' => ['src', 'alt', 'title'],
1101 'img' => ['src', 'alt', 'title'],
1102 'br' => [],
1102 'br' => [],
1103 'i' => nil,
1103 'i' => nil,
1104 'u' => nil,
1104 'u' => nil,
1105 'b' => nil,
1105 'b' => nil,
1106 'pre' => nil,
1106 'pre' => nil,
1107 'kbd' => nil,
1107 'kbd' => nil,
1108 'code' => ['lang'],
1108 'code' => ['lang'],
1109 'cite' => nil,
1109 'cite' => nil,
1110 'strong' => nil,
1110 'strong' => nil,
1111 'em' => nil,
1111 'em' => nil,
1112 'ins' => nil,
1112 'ins' => nil,
1113 'sup' => nil,
1113 'sup' => nil,
1114 'sub' => nil,
1114 'sub' => nil,
1115 'del' => nil,
1115 'del' => nil,
1116 'table' => nil,
1116 'table' => nil,
1117 'tr' => nil,
1117 'tr' => nil,
1118 'td' => ['colspan', 'rowspan'],
1118 'td' => ['colspan', 'rowspan'],
1119 'th' => nil,
1119 'th' => nil,
1120 'ol' => nil,
1120 'ol' => nil,
1121 'ul' => nil,
1121 'ul' => nil,
1122 'li' => nil,
1122 'li' => nil,
1123 'p' => nil,
1123 'p' => nil,
1124 'h1' => nil,
1124 'h1' => nil,
1125 'h2' => nil,
1125 'h2' => nil,
1126 'h3' => nil,
1126 'h3' => nil,
1127 'h4' => nil,
1127 'h4' => nil,
1128 'h5' => nil,
1128 'h5' => nil,
1129 'h6' => nil,
1129 'h6' => nil,
1130 'blockquote' => ['cite']
1130 'blockquote' => ['cite']
1131 }
1131 }
1132
1132
1133 def clean_html( text, tags = BASIC_TAGS )
1133 def clean_html( text, tags = BASIC_TAGS )
1134 text.gsub!( /<!\[CDATA\[/, '' )
1134 text.gsub!( /<!\[CDATA\[/, '' )
1135 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1135 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1136 raw = $~
1136 raw = $~
1137 tag = raw[2].downcase
1137 tag = raw[2].downcase
1138 if tags.has_key? tag
1138 if tags.has_key? tag
1139 pcs = [tag]
1139 pcs = [tag]
1140 tags[tag].each do |prop|
1140 tags[tag].each do |prop|
1141 ['"', "'", ''].each do |q|
1141 ['"', "'", ''].each do |q|
1142 q2 = ( q != '' ? q : '\s' )
1142 q2 = ( q != '' ? q : '\s' )
1143 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1143 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1144 attrv = $1
1144 attrv = $1
1145 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1145 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1146 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1146 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1147 break
1147 break
1148 end
1148 end
1149 end
1149 end
1150 end if tags[tag]
1150 end if tags[tag]
1151 "<#{raw[1]}#{pcs.join " "}>"
1151 "<#{raw[1]}#{pcs.join " "}>"
1152 else
1152 else
1153 " "
1153 " "
1154 end
1154 end
1155 end
1155 end
1156 end
1156 end
1157
1157
1158 ALLOWED_TAGS = %w(redpre pre code notextile)
1158 ALLOWED_TAGS = %w(redpre pre code notextile)
1159
1159
1160 def escape_html_tags(text)
1160 def escape_html_tags(text)
1161 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1161 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1162 end
1162 end
1163 end
1163 end
1164
1164
@@ -1,384 +1,385
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,
23 fixtures :projects, :roles, :enabled_modules,
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
28
29 def setup
29 def setup
30 super
30 super
31 end
31 end
32
32
33 def test_auto_links
33 def test_auto_links
34 to_test = {
34 to_test = {
35 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
35 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
36 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
36 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
37 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
37 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
39 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
40 '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>.',
40 '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>.',
41 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
41 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
42 '(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>)',
42 '(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>)',
43 '(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>)',
43 '(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>)',
44 '(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>).',
44 '(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_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
45 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
46 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
46 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
47 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
47 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
48 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
48 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
49 '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>',
49 '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>',
50 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
50 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
51 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
51 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
52 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
52 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
53 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
53 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
54 }
54 }
55 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
56 end
56 end
57
57
58 def test_auto_mailto
58 def test_auto_mailto
59 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
59 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
60 textilizable('test@foo.bar')
60 textilizable('test@foo.bar')
61 end
61 end
62
62
63 def test_inline_images
63 def test_inline_images
64 to_test = {
64 to_test = {
65 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
65 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
66 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
66 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
67 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
67 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
68 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
68 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
69 }
69 }
70 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
70 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
71 end
71 end
72
72
73 def test_textile_external_links
73 def test_textile_external_links
74 to_test = {
74 to_test = {
75 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
75 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
76 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
76 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
77 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
77 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
78 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
78 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
79 # no multiline link text
79 # no multiline link text
80 "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"
80 "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"
81 }
81 }
82 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
82 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
83 end
83 end
84
84
85 def test_redmine_links
85 def test_redmine_links
86 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
86 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
87 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
87 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
88
88
89 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
89 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
90 :class => 'changeset', :title => 'My very first commit')
90 :class => 'changeset', :title => 'My very first commit')
91
91
92 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
92 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
93 :class => 'document')
93 :class => 'document')
94
94
95 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
95 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
96 :class => 'version')
96 :class => 'version')
97
97
98 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
98 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
99
99
100 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
100 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
101 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
101 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
102
102
103 to_test = {
103 to_test = {
104 # tickets
104 # tickets
105 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
105 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
106 # changesets
106 # changesets
107 'r1' => changeset_link,
107 'r1' => changeset_link,
108 # documents
108 # documents
109 'document#1' => document_link,
109 'document#1' => document_link,
110 'document:"Test document"' => document_link,
110 'document:"Test document"' => document_link,
111 # versions
111 # versions
112 'version#2' => version_link,
112 'version#2' => version_link,
113 'version:1.0' => version_link,
113 'version:1.0' => version_link,
114 'version:"1.0"' => version_link,
114 'version:"1.0"' => version_link,
115 # source
115 # source
116 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
116 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
117 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
117 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
118 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
118 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
119 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
119 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
120 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
120 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
121 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
121 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
122 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
122 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
123 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
123 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
124 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
124 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
125 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
125 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
126 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
126 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
127 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
127 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
128 # message
128 # message
129 'message#4' => link_to('Post 2', message_url, :class => 'message'),
129 'message#4' => link_to('Post 2', message_url, :class => 'message'),
130 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
130 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
131 # escaping
131 # escaping
132 '!#3.' => '#3.',
132 '!#3.' => '#3.',
133 '!r1' => 'r1',
133 '!r1' => 'r1',
134 '!document#1' => 'document#1',
134 '!document#1' => 'document#1',
135 '!document:"Test document"' => 'document:"Test document"',
135 '!document:"Test document"' => 'document:"Test document"',
136 '!version#2' => 'version#2',
136 '!version#2' => 'version#2',
137 '!version:1.0' => 'version:1.0',
137 '!version:1.0' => 'version:1.0',
138 '!version:"1.0"' => 'version:"1.0"',
138 '!version:"1.0"' => 'version:"1.0"',
139 '!source:/some/file' => 'source:/some/file',
139 '!source:/some/file' => 'source:/some/file',
140 # invalid expressions
140 # invalid expressions
141 'source:' => 'source:',
141 'source:' => 'source:',
142 # url hash
142 # url hash
143 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
143 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
144 }
144 }
145 @project = Project.find(1)
145 @project = Project.find(1)
146 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
146 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
147 end
147 end
148
148
149 def test_wiki_links
149 def test_wiki_links
150 to_test = {
150 to_test = {
151 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
151 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
152 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
152 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
153 # link with anchor
153 # link with anchor
154 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
154 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
155 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
155 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
156 # page that doesn't exist
156 # page that doesn't exist
157 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
157 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
158 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
158 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
159 # link to another project wiki
159 # link to another project wiki
160 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
160 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
161 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
161 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
162 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
162 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
163 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
163 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
164 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
164 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
165 # striked through link
165 # striked through link
166 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
166 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
167 # escaping
167 # escaping
168 '![[Another page|Page]]' => '[[Another page|Page]]',
168 '![[Another page|Page]]' => '[[Another page|Page]]',
169 }
169 }
170 @project = Project.find(1)
170 @project = Project.find(1)
171 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
171 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
172 end
172 end
173
173
174 def test_html_tags
174 def test_html_tags
175 to_test = {
175 to_test = {
176 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
176 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
177 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
177 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
178 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
178 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
179 # do not escape pre/code tags
179 # do not escape pre/code tags
180 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
180 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
181 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
181 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
182 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
182 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
183 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
183 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
184 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
184 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
185 }
185 }
186 to_test.each { |text, result| assert_equal result, textilizable(text) }
186 to_test.each { |text, result| assert_equal result, textilizable(text) }
187 end
187 end
188
188
189 def test_allowed_html_tags
189 def test_allowed_html_tags
190 to_test = {
190 to_test = {
191 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
191 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
192 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
192 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
193 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
193 }
194 }
194 to_test.each { |text, result| assert_equal result, textilizable(text) }
195 to_test.each { |text, result| assert_equal result, textilizable(text) }
195 end
196 end
196
197
197 def test_wiki_links_in_tables
198 def test_wiki_links_in_tables
198 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
199 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
199 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
200 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
200 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
201 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
201 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
202 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
202 }
203 }
203 @project = Project.find(1)
204 @project = Project.find(1)
204 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
205 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
205 end
206 end
206
207
207 def test_text_formatting
208 def test_text_formatting
208 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
209 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
209 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
210 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
210 }
211 }
211 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
212 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
212 end
213 end
213
214
214 def test_wiki_horizontal_rule
215 def test_wiki_horizontal_rule
215 assert_equal '<hr />', textilizable('---')
216 assert_equal '<hr />', textilizable('---')
216 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
217 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
217 end
218 end
218
219
219 def test_table_of_content
220 def test_table_of_content
220 raw = <<-RAW
221 raw = <<-RAW
221 {{toc}}
222 {{toc}}
222
223
223 h1. Title
224 h1. Title
224
225
225 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
226 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
226
227
227 h2. Subtitle
228 h2. Subtitle
228
229
229 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
230 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
230
231
231 h2. Subtitle with %{color:red}red text%
232 h2. Subtitle with %{color:red}red text%
232
233
233 h1. Another title
234 h1. Another title
234
235
235 RAW
236 RAW
236
237
237 expected = '<ul class="toc">' +
238 expected = '<ul class="toc">' +
238 '<li class="heading1"><a href="#Title">Title</a></li>' +
239 '<li class="heading1"><a href="#Title">Title</a></li>' +
239 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
240 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
240 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
241 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
241 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
242 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
242 '</ul>'
243 '</ul>'
243
244
244 assert textilizable(raw).gsub("\n", "").include?(expected)
245 assert textilizable(raw).gsub("\n", "").include?(expected)
245 end
246 end
246
247
247 def test_blockquote
248 def test_blockquote
248 # orig raw text
249 # orig raw text
249 raw = <<-RAW
250 raw = <<-RAW
250 John said:
251 John said:
251 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
252 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
252 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
253 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
253 > * Donec odio lorem,
254 > * Donec odio lorem,
254 > * sagittis ac,
255 > * sagittis ac,
255 > * malesuada in,
256 > * malesuada in,
256 > * adipiscing eu, dolor.
257 > * adipiscing eu, dolor.
257 >
258 >
258 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
259 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
259 > Proin a tellus. Nam vel neque.
260 > Proin a tellus. Nam vel neque.
260
261
261 He's right.
262 He's right.
262 RAW
263 RAW
263
264
264 # expected html
265 # expected html
265 expected = <<-EXPECTED
266 expected = <<-EXPECTED
266 <p>John said:</p>
267 <p>John said:</p>
267 <blockquote>
268 <blockquote>
268 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
269 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
269 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
270 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
270 <ul>
271 <ul>
271 <li>Donec odio lorem,</li>
272 <li>Donec odio lorem,</li>
272 <li>sagittis ac,</li>
273 <li>sagittis ac,</li>
273 <li>malesuada in,</li>
274 <li>malesuada in,</li>
274 <li>adipiscing eu, dolor.</li>
275 <li>adipiscing eu, dolor.</li>
275 </ul>
276 </ul>
276 <blockquote>
277 <blockquote>
277 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
278 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
278 </blockquote>
279 </blockquote>
279 <p>Proin a tellus. Nam vel neque.</p>
280 <p>Proin a tellus. Nam vel neque.</p>
280 </blockquote>
281 </blockquote>
281 <p>He's right.</p>
282 <p>He's right.</p>
282 EXPECTED
283 EXPECTED
283
284
284 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
285 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
285 end
286 end
286
287
287 def test_table
288 def test_table
288 raw = <<-RAW
289 raw = <<-RAW
289 This is a table with empty cells:
290 This is a table with empty cells:
290
291
291 |cell11|cell12||
292 |cell11|cell12||
292 |cell21||cell23|
293 |cell21||cell23|
293 |cell31|cell32|cell33|
294 |cell31|cell32|cell33|
294 RAW
295 RAW
295
296
296 expected = <<-EXPECTED
297 expected = <<-EXPECTED
297 <p>This is a table with empty cells:</p>
298 <p>This is a table with empty cells:</p>
298
299
299 <table>
300 <table>
300 <tr><td>cell11</td><td>cell12</td><td></td></tr>
301 <tr><td>cell11</td><td>cell12</td><td></td></tr>
301 <tr><td>cell21</td><td></td><td>cell23</td></tr>
302 <tr><td>cell21</td><td></td><td>cell23</td></tr>
302 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
303 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
303 </table>
304 </table>
304 EXPECTED
305 EXPECTED
305
306
306 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
307 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
307 end
308 end
308
309
309 def test_macro_hello_world
310 def test_macro_hello_world
310 text = "{{hello_world}}"
311 text = "{{hello_world}}"
311 assert textilizable(text).match(/Hello world!/)
312 assert textilizable(text).match(/Hello world!/)
312 # escaping
313 # escaping
313 text = "!{{hello_world}}"
314 text = "!{{hello_world}}"
314 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
315 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
315 end
316 end
316
317
317 def test_macro_include
318 def test_macro_include
318 @project = Project.find(1)
319 @project = Project.find(1)
319 # include a page of the current project wiki
320 # include a page of the current project wiki
320 text = "{{include(Another page)}}"
321 text = "{{include(Another page)}}"
321 assert textilizable(text).match(/This is a link to a ticket/)
322 assert textilizable(text).match(/This is a link to a ticket/)
322
323
323 @project = nil
324 @project = nil
324 # include a page of a specific project wiki
325 # include a page of a specific project wiki
325 text = "{{include(ecookbook:Another page)}}"
326 text = "{{include(ecookbook:Another page)}}"
326 assert textilizable(text).match(/This is a link to a ticket/)
327 assert textilizable(text).match(/This is a link to a ticket/)
327
328
328 text = "{{include(ecookbook:)}}"
329 text = "{{include(ecookbook:)}}"
329 assert textilizable(text).match(/CookBook documentation/)
330 assert textilizable(text).match(/CookBook documentation/)
330
331
331 text = "{{include(unknowidentifier:somepage)}}"
332 text = "{{include(unknowidentifier:somepage)}}"
332 assert textilizable(text).match(/Unknow project/)
333 assert textilizable(text).match(/Unknow project/)
333 end
334 end
334
335
335 def test_date_format_default
336 def test_date_format_default
336 today = Date.today
337 today = Date.today
337 Setting.date_format = ''
338 Setting.date_format = ''
338 assert_equal l_date(today), format_date(today)
339 assert_equal l_date(today), format_date(today)
339 end
340 end
340
341
341 def test_date_format
342 def test_date_format
342 today = Date.today
343 today = Date.today
343 Setting.date_format = '%d %m %Y'
344 Setting.date_format = '%d %m %Y'
344 assert_equal today.strftime('%d %m %Y'), format_date(today)
345 assert_equal today.strftime('%d %m %Y'), format_date(today)
345 end
346 end
346
347
347 def test_time_format_default
348 def test_time_format_default
348 now = Time.now
349 now = Time.now
349 Setting.date_format = ''
350 Setting.date_format = ''
350 Setting.time_format = ''
351 Setting.time_format = ''
351 assert_equal l_datetime(now), format_time(now)
352 assert_equal l_datetime(now), format_time(now)
352 assert_equal l_time(now), format_time(now, false)
353 assert_equal l_time(now), format_time(now, false)
353 end
354 end
354
355
355 def test_time_format
356 def test_time_format
356 now = Time.now
357 now = Time.now
357 Setting.date_format = '%d %m %Y'
358 Setting.date_format = '%d %m %Y'
358 Setting.time_format = '%H %M'
359 Setting.time_format = '%H %M'
359 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
360 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
360 assert_equal now.strftime('%H %M'), format_time(now, false)
361 assert_equal now.strftime('%H %M'), format_time(now, false)
361 end
362 end
362
363
363 def test_utc_time_format
364 def test_utc_time_format
364 now = Time.now.utc
365 now = Time.now.utc
365 Setting.date_format = '%d %m %Y'
366 Setting.date_format = '%d %m %Y'
366 Setting.time_format = '%H %M'
367 Setting.time_format = '%H %M'
367 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
368 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
368 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
369 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
369 end
370 end
370
371
371 def test_due_date_distance_in_words
372 def test_due_date_distance_in_words
372 to_test = { Date.today => 'Due in 0 days',
373 to_test = { Date.today => 'Due in 0 days',
373 Date.today + 1 => 'Due in 1 day',
374 Date.today + 1 => 'Due in 1 day',
374 Date.today + 100 => 'Due in 100 days',
375 Date.today + 100 => 'Due in 100 days',
375 Date.today + 20000 => 'Due in 20000 days',
376 Date.today + 20000 => 'Due in 20000 days',
376 Date.today - 1 => '1 day late',
377 Date.today - 1 => '1 day late',
377 Date.today - 100 => '100 days late',
378 Date.today - 100 => '100 days late',
378 Date.today - 20000 => '20000 days late',
379 Date.today - 20000 => '20000 days late',
379 }
380 }
380 to_test.each do |date, expected|
381 to_test.each do |date, expected|
381 assert_equal expected, due_date_distance_in_words(date)
382 assert_equal expected, due_date_distance_in_words(date)
382 end
383 end
383 end
384 end
384 end
385 end
General Comments 0
You need to be logged in to leave comments. Login now