##// END OF EJS Templates
Fixes syntax highlighting broken by r1930 (#2143)....
Jean-Philippe Lang -
r1991:9ae6e60c260e
parent child
Show More
@@ -1,1164 +1,1168
1 # vim:ts=4:sw=4:
1 # vim:ts=4:sw=4:
2 # = RedCloth - Textile and Markdown Hybrid for Ruby
2 # = RedCloth - Textile and Markdown Hybrid for Ruby
3 #
3 #
4 # Homepage:: http://whytheluckystiff.net/ruby/redcloth/
4 # Homepage:: http://whytheluckystiff.net/ruby/redcloth/
5 # Author:: why the lucky stiff (http://whytheluckystiff.net/)
5 # Author:: why the lucky stiff (http://whytheluckystiff.net/)
6 # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
6 # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
7 # License:: BSD
7 # License:: BSD
8 #
8 #
9 # (see http://hobix.com/textile/ for a Textile Reference.)
9 # (see http://hobix.com/textile/ for a Textile Reference.)
10 #
10 #
11 # Based on (and also inspired by) both:
11 # Based on (and also inspired by) both:
12 #
12 #
13 # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
13 # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
14 # Textism for PHP: http://www.textism.com/tools/textile/
14 # Textism for PHP: http://www.textism.com/tools/textile/
15 #
15 #
16 #
16 #
17
17
18 # = RedCloth
18 # = RedCloth
19 #
19 #
20 # RedCloth is a Ruby library for converting Textile and/or Markdown
20 # RedCloth is a Ruby library for converting Textile and/or Markdown
21 # into HTML. You can use either format, intermingled or separately.
21 # into HTML. You can use either format, intermingled or separately.
22 # You can also extend RedCloth to honor your own custom text stylings.
22 # You can also extend RedCloth to honor your own custom text stylings.
23 #
23 #
24 # RedCloth users are encouraged to use Textile if they are generating
24 # RedCloth users are encouraged to use Textile if they are generating
25 # HTML and to use Markdown if others will be viewing the plain text.
25 # HTML and to use Markdown if others will be viewing the plain text.
26 #
26 #
27 # == What is Textile?
27 # == What is Textile?
28 #
28 #
29 # Textile is a simple formatting style for text
29 # Textile is a simple formatting style for text
30 # documents, loosely based on some HTML conventions.
30 # documents, loosely based on some HTML conventions.
31 #
31 #
32 # == Sample Textile Text
32 # == Sample Textile Text
33 #
33 #
34 # h2. This is a title
34 # h2. This is a title
35 #
35 #
36 # h3. This is a subhead
36 # h3. This is a subhead
37 #
37 #
38 # This is a bit of paragraph.
38 # This is a bit of paragraph.
39 #
39 #
40 # bq. This is a blockquote.
40 # bq. This is a blockquote.
41 #
41 #
42 # = Writing Textile
42 # = Writing Textile
43 #
43 #
44 # A Textile document consists of paragraphs. Paragraphs
44 # A Textile document consists of paragraphs. Paragraphs
45 # can be specially formatted by adding a small instruction
45 # can be specially formatted by adding a small instruction
46 # to the beginning of the paragraph.
46 # to the beginning of the paragraph.
47 #
47 #
48 # h[n]. Header of size [n].
48 # h[n]. Header of size [n].
49 # bq. Blockquote.
49 # bq. Blockquote.
50 # # Numeric list.
50 # # Numeric list.
51 # * Bulleted list.
51 # * Bulleted list.
52 #
52 #
53 # == Quick Phrase Modifiers
53 # == Quick Phrase Modifiers
54 #
54 #
55 # Quick phrase modifiers are also included, to allow formatting
55 # Quick phrase modifiers are also included, to allow formatting
56 # of small portions of text within a paragraph.
56 # of small portions of text within a paragraph.
57 #
57 #
58 # \_emphasis\_
58 # \_emphasis\_
59 # \_\_italicized\_\_
59 # \_\_italicized\_\_
60 # \*strong\*
60 # \*strong\*
61 # \*\*bold\*\*
61 # \*\*bold\*\*
62 # ??citation??
62 # ??citation??
63 # -deleted text-
63 # -deleted text-
64 # +inserted text+
64 # +inserted text+
65 # ^superscript^
65 # ^superscript^
66 # ~subscript~
66 # ~subscript~
67 # @code@
67 # @code@
68 # %(classname)span%
68 # %(classname)span%
69 #
69 #
70 # ==notextile== (leave text alone)
70 # ==notextile== (leave text alone)
71 #
71 #
72 # == Links
72 # == Links
73 #
73 #
74 # To make a hypertext link, put the link text in "quotation
74 # To make a hypertext link, put the link text in "quotation
75 # marks" followed immediately by a colon and the URL of the link.
75 # marks" followed immediately by a colon and the URL of the link.
76 #
76 #
77 # Optional: text in (parentheses) following the link text,
77 # Optional: text in (parentheses) following the link text,
78 # but before the closing quotation mark, will become a Title
78 # but before the closing quotation mark, will become a Title
79 # attribute for the link, visible as a tool tip when a cursor is above it.
79 # attribute for the link, visible as a tool tip when a cursor is above it.
80 #
80 #
81 # Example:
81 # Example:
82 #
82 #
83 # "This is a link (This is a title) ":http://www.textism.com
83 # "This is a link (This is a title) ":http://www.textism.com
84 #
84 #
85 # Will become:
85 # Will become:
86 #
86 #
87 # <a href="http://www.textism.com" title="This is a title">This is a link</a>
87 # <a href="http://www.textism.com" title="This is a title">This is a link</a>
88 #
88 #
89 # == Images
89 # == Images
90 #
90 #
91 # To insert an image, put the URL for the image inside exclamation marks.
91 # To insert an image, put the URL for the image inside exclamation marks.
92 #
92 #
93 # Optional: text that immediately follows the URL in (parentheses) will
93 # Optional: text that immediately follows the URL in (parentheses) will
94 # be used as the Alt text for the image. Images on the web should always
94 # be used as the Alt text for the image. Images on the web should always
95 # have descriptive Alt text for the benefit of readers using non-graphical
95 # have descriptive Alt text for the benefit of readers using non-graphical
96 # browsers.
96 # browsers.
97 #
97 #
98 # Optional: place a colon followed by a URL immediately after the
98 # Optional: place a colon followed by a URL immediately after the
99 # closing ! to make the image into a link.
99 # closing ! to make the image into a link.
100 #
100 #
101 # Example:
101 # Example:
102 #
102 #
103 # !http://www.textism.com/common/textist.gif(Textist)!
103 # !http://www.textism.com/common/textist.gif(Textist)!
104 #
104 #
105 # Will become:
105 # Will become:
106 #
106 #
107 # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
107 # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
108 #
108 #
109 # With a link:
109 # With a link:
110 #
110 #
111 # !/common/textist.gif(Textist)!:http://textism.com
111 # !/common/textist.gif(Textist)!:http://textism.com
112 #
112 #
113 # Will become:
113 # Will become:
114 #
114 #
115 # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
115 # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
116 #
116 #
117 # == Defining Acronyms
117 # == Defining Acronyms
118 #
118 #
119 # HTML allows authors to define acronyms via the tag. The definition appears as a
119 # HTML allows authors to define acronyms via the tag. The definition appears as a
120 # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
120 # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
121 # this should be used at least once for each acronym in documents where they appear.
121 # this should be used at least once for each acronym in documents where they appear.
122 #
122 #
123 # To quickly define an acronym in Textile, place the full text in (parentheses)
123 # To quickly define an acronym in Textile, place the full text in (parentheses)
124 # immediately following the acronym.
124 # immediately following the acronym.
125 #
125 #
126 # Example:
126 # Example:
127 #
127 #
128 # ACLU(American Civil Liberties Union)
128 # ACLU(American Civil Liberties Union)
129 #
129 #
130 # Will become:
130 # Will become:
131 #
131 #
132 # <acronym title="American Civil Liberties Union">ACLU</acronym>
132 # <acronym title="American Civil Liberties Union">ACLU</acronym>
133 #
133 #
134 # == Adding Tables
134 # == Adding Tables
135 #
135 #
136 # In Textile, simple tables can be added by seperating each column by
136 # In Textile, simple tables can be added by seperating each column by
137 # a pipe.
137 # a pipe.
138 #
138 #
139 # |a|simple|table|row|
139 # |a|simple|table|row|
140 # |And|Another|table|row|
140 # |And|Another|table|row|
141 #
141 #
142 # Attributes are defined by style definitions in parentheses.
142 # Attributes are defined by style definitions in parentheses.
143 #
143 #
144 # table(border:1px solid black).
144 # table(border:1px solid black).
145 # (background:#ddd;color:red). |{}| | | |
145 # (background:#ddd;color:red). |{}| | | |
146 #
146 #
147 # == Using RedCloth
147 # == Using RedCloth
148 #
148 #
149 # RedCloth is simply an extension of the String class, which can handle
149 # RedCloth is simply an extension of the String class, which can handle
150 # Textile formatting. Use it like a String and output HTML with its
150 # Textile formatting. Use it like a String and output HTML with its
151 # RedCloth#to_html method.
151 # RedCloth#to_html method.
152 #
152 #
153 # doc = RedCloth.new "
153 # doc = RedCloth.new "
154 #
154 #
155 # h2. Test document
155 # h2. Test document
156 #
156 #
157 # Just a simple test."
157 # Just a simple test."
158 #
158 #
159 # puts doc.to_html
159 # puts doc.to_html
160 #
160 #
161 # By default, RedCloth uses both Textile and Markdown formatting, with
161 # By default, RedCloth uses both Textile and Markdown formatting, with
162 # Textile formatting taking precedence. If you want to turn off Markdown
162 # Textile formatting taking precedence. If you want to turn off Markdown
163 # formatting, to boost speed and limit the processor:
163 # formatting, to boost speed and limit the processor:
164 #
164 #
165 # class RedCloth::Textile.new( str )
165 # class RedCloth::Textile.new( str )
166
166
167 class RedCloth3 < String
167 class RedCloth3 < String
168
168
169 VERSION = '3.0.4'
169 VERSION = '3.0.4'
170 DEFAULT_RULES = [:textile, :markdown]
170 DEFAULT_RULES = [:textile, :markdown]
171
171
172 #
172 #
173 # Two accessor for setting security restrictions.
173 # Two accessor for setting security restrictions.
174 #
174 #
175 # This is a nice thing if you're using RedCloth for
175 # This is a nice thing if you're using RedCloth for
176 # formatting in public places (e.g. Wikis) where you
176 # formatting in public places (e.g. Wikis) where you
177 # don't want users to abuse HTML for bad things.
177 # don't want users to abuse HTML for bad things.
178 #
178 #
179 # If +:filter_html+ is set, HTML which wasn't
179 # If +:filter_html+ is set, HTML which wasn't
180 # created by the Textile processor will be escaped.
180 # created by the Textile processor will be escaped.
181 #
181 #
182 # If +:filter_styles+ is set, it will also disable
182 # If +:filter_styles+ is set, it will also disable
183 # the style markup specifier. ('{color: red}')
183 # the style markup specifier. ('{color: red}')
184 #
184 #
185 attr_accessor :filter_html, :filter_styles
185 attr_accessor :filter_html, :filter_styles
186
186
187 #
187 #
188 # Accessor for toggling hard breaks.
188 # Accessor for toggling hard breaks.
189 #
189 #
190 # If +:hard_breaks+ is set, single newlines will
190 # If +:hard_breaks+ is set, single newlines will
191 # be converted to HTML break tags. This is the
191 # be converted to HTML break tags. This is the
192 # default behavior for traditional RedCloth.
192 # default behavior for traditional RedCloth.
193 #
193 #
194 attr_accessor :hard_breaks
194 attr_accessor :hard_breaks
195
195
196 # Accessor for toggling lite mode.
196 # Accessor for toggling lite mode.
197 #
197 #
198 # In lite mode, block-level rules are ignored. This means
198 # In lite mode, block-level rules are ignored. This means
199 # that tables, paragraphs, lists, and such aren't available.
199 # that tables, paragraphs, lists, and such aren't available.
200 # Only the inline markup for bold, italics, entities and so on.
200 # Only the inline markup for bold, italics, entities and so on.
201 #
201 #
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
203 # r.to_html
203 # r.to_html
204 # #=> "And then? She <strong>fell</strong>!"
204 # #=> "And then? She <strong>fell</strong>!"
205 #
205 #
206 attr_accessor :lite_mode
206 attr_accessor :lite_mode
207
207
208 #
208 #
209 # Accessor for toggling span caps.
209 # Accessor for toggling span caps.
210 #
210 #
211 # Textile places `span' tags around capitalized
211 # Textile places `span' tags around capitalized
212 # words by default, but this wreaks havoc on Wikis.
212 # words by default, but this wreaks havoc on Wikis.
213 # If +:no_span_caps+ is set, this will be
213 # If +:no_span_caps+ is set, this will be
214 # suppressed.
214 # suppressed.
215 #
215 #
216 attr_accessor :no_span_caps
216 attr_accessor :no_span_caps
217
217
218 #
218 #
219 # Establishes the markup predence. Available rules include:
219 # Establishes the markup predence. Available rules include:
220 #
220 #
221 # == Textile Rules
221 # == Textile Rules
222 #
222 #
223 # The following textile rules can be set individually. Or add the complete
223 # The following textile rules can be set individually. Or add the complete
224 # set of rules with the single :textile rule, which supplies the rule set in
224 # set of rules with the single :textile rule, which supplies the rule set in
225 # the following precedence:
225 # the following precedence:
226 #
226 #
227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
228 # block_textile_table:: Textile table block structures
228 # block_textile_table:: Textile table block structures
229 # block_textile_lists:: Textile list structures
229 # block_textile_lists:: Textile list structures
230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
231 # inline_textile_image:: Textile inline images
231 # inline_textile_image:: Textile inline images
232 # inline_textile_link:: Textile inline links
232 # inline_textile_link:: Textile inline links
233 # inline_textile_span:: Textile inline spans
233 # inline_textile_span:: Textile inline spans
234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
235 #
235 #
236 # == Markdown
236 # == Markdown
237 #
237 #
238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
239 # block_markdown_setext:: Markdown setext headers
239 # block_markdown_setext:: Markdown setext headers
240 # block_markdown_atx:: Markdown atx headers
240 # block_markdown_atx:: Markdown atx headers
241 # block_markdown_rule:: Markdown horizontal rules
241 # block_markdown_rule:: Markdown horizontal rules
242 # block_markdown_bq:: Markdown blockquotes
242 # block_markdown_bq:: Markdown blockquotes
243 # block_markdown_lists:: Markdown lists
243 # block_markdown_lists:: Markdown lists
244 # inline_markdown_link:: Markdown links
244 # inline_markdown_link:: Markdown links
245 attr_accessor :rules
245 attr_accessor :rules
246
246
247 # Returns a new RedCloth object, based on _string_ and
247 # Returns a new RedCloth object, based on _string_ and
248 # enforcing all the included _restrictions_.
248 # enforcing all the included _restrictions_.
249 #
249 #
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
251 # r.to_html
251 # r.to_html
252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
253 #
253 #
254 def initialize( string, restrictions = [] )
254 def initialize( string, restrictions = [] )
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
256 super( string )
256 super( string )
257 end
257 end
258
258
259 #
259 #
260 # Generates HTML from the Textile contents.
260 # Generates HTML from the Textile contents.
261 #
261 #
262 # r = RedCloth.new( "And then? She *fell*!" )
262 # r = RedCloth.new( "And then? She *fell*!" )
263 # r.to_html( true )
263 # r.to_html( true )
264 # #=>"And then? She <strong>fell</strong>!"
264 # #=>"And then? She <strong>fell</strong>!"
265 #
265 #
266 def to_html( *rules )
266 def to_html( *rules )
267 rules = DEFAULT_RULES if rules.empty?
267 rules = DEFAULT_RULES if rules.empty?
268 # make our working copy
268 # make our working copy
269 text = self.dup
269 text = self.dup
270
270
271 @urlrefs = {}
271 @urlrefs = {}
272 @shelf = []
272 @shelf = []
273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
275 :inline_textile_code, :inline_textile_span, :glyphs_textile]
275 :inline_textile_code, :inline_textile_span, :glyphs_textile]
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
277 :block_markdown_bq, :block_markdown_lists,
277 :block_markdown_bq, :block_markdown_lists,
278 :inline_markdown_reflink, :inline_markdown_link]
278 :inline_markdown_reflink, :inline_markdown_link]
279 @rules = rules.collect do |rule|
279 @rules = rules.collect do |rule|
280 case rule
280 case rule
281 when :markdown
281 when :markdown
282 markdown_rules
282 markdown_rules
283 when :textile
283 when :textile
284 textile_rules
284 textile_rules
285 else
285 else
286 rule
286 rule
287 end
287 end
288 end.flatten
288 end.flatten
289
289
290 # standard clean up
290 # standard clean up
291 incoming_entities text
291 incoming_entities text
292 clean_white_space text
292 clean_white_space text
293
293
294 # start processor
294 # start processor
295 @pre_list = []
295 @pre_list = []
296 rip_offtags text
296 rip_offtags text
297 no_textile text
297 no_textile text
298 escape_html_tags text
298 escape_html_tags text
299 hard_break text
299 hard_break text
300 unless @lite_mode
300 unless @lite_mode
301 refs text
301 refs text
302 # need to do this before text is split by #blocks
302 # need to do this before text is split by #blocks
303 block_textile_quotes text
303 block_textile_quotes text
304 blocks text
304 blocks text
305 end
305 end
306 inline text
306 inline text
307 smooth_offtags text
307 smooth_offtags text
308
308
309 retrieve text
309 retrieve text
310
310
311 text.gsub!( /<\/?notextile>/, '' )
311 text.gsub!( /<\/?notextile>/, '' )
312 text.gsub!( /x%x%/, '&#38;' )
312 text.gsub!( /x%x%/, '&#38;' )
313 clean_html text if filter_html
313 clean_html text if filter_html
314 text.strip!
314 text.strip!
315 text
315 text
316
316
317 end
317 end
318
318
319 #######
319 #######
320 private
320 private
321 #######
321 #######
322 #
322 #
323 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
323 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
324 # (from PyTextile)
324 # (from PyTextile)
325 #
325 #
326 TEXTILE_TAGS =
326 TEXTILE_TAGS =
327
327
328 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
328 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
329 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
329 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
330 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
330 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
331 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
331 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
332 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
332 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
333
333
334 collect! do |a, b|
334 collect! do |a, b|
335 [a.chr, ( b.zero? and "" or "&#{ b };" )]
335 [a.chr, ( b.zero? and "" or "&#{ b };" )]
336 end
336 end
337
337
338 #
338 #
339 # Regular expressions to convert to HTML.
339 # Regular expressions to convert to HTML.
340 #
340 #
341 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
341 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
342 A_VLGN = /[\-^~]/
342 A_VLGN = /[\-^~]/
343 C_CLAS = '(?:\([^)]+\))'
343 C_CLAS = '(?:\([^)]+\))'
344 C_LNGE = '(?:\[[^\]]+\])'
344 C_LNGE = '(?:\[[^\]]+\])'
345 C_STYL = '(?:\{[^}]+\})'
345 C_STYL = '(?:\{[^}]+\})'
346 S_CSPN = '(?:\\\\\d+)'
346 S_CSPN = '(?:\\\\\d+)'
347 S_RSPN = '(?:/\d+)'
347 S_RSPN = '(?:/\d+)'
348 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
348 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
349 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
349 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
350 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
350 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
351 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
351 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
352 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
352 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
353 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
353 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
354 PUNCT_Q = Regexp::quote( '*-_+^~%' )
354 PUNCT_Q = Regexp::quote( '*-_+^~%' )
355 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
355 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
356
356
357 # Text markup tags, don't conflict with block tags
357 # Text markup tags, don't conflict with block tags
358 SIMPLE_HTML_TAGS = [
358 SIMPLE_HTML_TAGS = [
359 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
359 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
360 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
360 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
361 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
361 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
362 ]
362 ]
363
363
364 QTAGS = [
364 QTAGS = [
365 ['**', 'b', :limit],
365 ['**', 'b', :limit],
366 ['*', 'strong', :limit],
366 ['*', 'strong', :limit],
367 ['??', 'cite', :limit],
367 ['??', 'cite', :limit],
368 ['-', 'del', :limit],
368 ['-', 'del', :limit],
369 ['__', 'i', :limit],
369 ['__', 'i', :limit],
370 ['_', 'em', :limit],
370 ['_', 'em', :limit],
371 ['%', 'span', :limit],
371 ['%', 'span', :limit],
372 ['+', 'ins', :limit],
372 ['+', 'ins', :limit],
373 ['^', 'sup', :limit],
373 ['^', 'sup', :limit],
374 ['~', 'sub', :limit]
374 ['~', 'sub', :limit]
375 ]
375 ]
376 QTAGS.collect! do |rc, ht, rtype|
376 QTAGS.collect! do |rc, ht, rtype|
377 rcq = Regexp::quote rc
377 rcq = Regexp::quote rc
378 re =
378 re =
379 case rtype
379 case rtype
380 when :limit
380 when :limit
381 /(^|[>\s\(])
381 /(^|[>\s\(])
382 (#{rcq})
382 (#{rcq})
383 (#{C})
383 (#{C})
384 (?::(\S+?))?
384 (?::(\S+?))?
385 ([^\s\-].*?[^\s\-]|\w)
385 ([^\s\-].*?[^\s\-]|\w)
386 #{rcq}
386 #{rcq}
387 (?=[[:punct:]]|\s|\)|$)/x
387 (?=[[:punct:]]|\s|\)|$)/x
388 else
388 else
389 /(#{rcq})
389 /(#{rcq})
390 (#{C})
390 (#{C})
391 (?::(\S+))?
391 (?::(\S+))?
392 ([^\s\-].*?[^\s\-]|\w)
392 ([^\s\-].*?[^\s\-]|\w)
393 #{rcq}/xm
393 #{rcq}/xm
394 end
394 end
395 [rc, ht, re, rtype]
395 [rc, ht, re, rtype]
396 end
396 end
397
397
398 # Elements to handle
398 # Elements to handle
399 GLYPHS = [
399 GLYPHS = [
400 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
400 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
401 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
401 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
402 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
402 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
403 # [ /\'/, '&#8216;' ], # single opening
403 # [ /\'/, '&#8216;' ], # single opening
404 # [ /</, '&lt;' ], # less-than
404 # [ /</, '&lt;' ], # less-than
405 # [ />/, '&gt;' ], # greater-than
405 # [ />/, '&gt;' ], # greater-than
406 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
406 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
407 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
407 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
408 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
408 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
409 # [ /"/, '&#8220;' ], # double opening
409 # [ /"/, '&#8220;' ], # double opening
410 # [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
410 # [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
411 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
411 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
412 # [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
412 # [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
413 # [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
413 # [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
414 # [ /\s->\s/, ' &rarr; ' ], # right arrow
414 # [ /\s->\s/, ' &rarr; ' ], # right arrow
415 # [ /\s-\s/, ' &#8211; ' ], # en dash
415 # [ /\s-\s/, ' &#8211; ' ], # en dash
416 # [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
416 # [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
417 # [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
417 # [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
418 # [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
418 # [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
419 # [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
419 # [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
420 ]
420 ]
421
421
422 H_ALGN_VALS = {
422 H_ALGN_VALS = {
423 '<' => 'left',
423 '<' => 'left',
424 '=' => 'center',
424 '=' => 'center',
425 '>' => 'right',
425 '>' => 'right',
426 '<>' => 'justify'
426 '<>' => 'justify'
427 }
427 }
428
428
429 V_ALGN_VALS = {
429 V_ALGN_VALS = {
430 '^' => 'top',
430 '^' => 'top',
431 '-' => 'middle',
431 '-' => 'middle',
432 '~' => 'bottom'
432 '~' => 'bottom'
433 }
433 }
434
434
435 #
435 #
436 # Flexible HTML escaping
436 # Flexible HTML escaping
437 #
437 #
438 def htmlesc( str, mode )
438 def htmlesc( str, mode )
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 }\" class=\"footnote\""
687 atts << " id=\"fn#{ num }\" class=\"footnote\""
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 )
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
1052 htmlesc( aftertag, :NoQuotes ) if aftertag
1053 line = "<redpre##{ @pre_list.length }>"
1053 line = "<redpre##{ @pre_list.length }>"
1054 @pre_list << "#{ $3.gsub(/<(#{ OFFTAGS })[^>]*>/, '<\\1>') }#{ aftertag }"
1054 $3.match(/<#{ OFFTAGS }([^>]*)>/)
1055 tag = $1
1056 $2.to_s.match(/(class\=\S+)/i)
1057 tag << " #{$1}" if $1
1058 @pre_list << "<#{ tag }>#{ aftertag }"
1055 end
1059 end
1056 elsif $1 and codepre > 0
1060 elsif $1 and codepre > 0
1057 if codepre - used_offtags.length > 0
1061 if codepre - used_offtags.length > 0
1058 htmlesc( line, :NoQuotes )
1062 htmlesc( line, :NoQuotes )
1059 @pre_list.last << line
1063 @pre_list.last << line
1060 line = ""
1064 line = ""
1061 end
1065 end
1062 codepre -= 1 unless codepre.zero?
1066 codepre -= 1 unless codepre.zero?
1063 used_offtags = {} if codepre.zero?
1067 used_offtags = {} if codepre.zero?
1064 end
1068 end
1065 line
1069 line
1066 end
1070 end
1067 end
1071 end
1068 text
1072 text
1069 end
1073 end
1070
1074
1071 def smooth_offtags( text )
1075 def smooth_offtags( text )
1072 unless @pre_list.empty?
1076 unless @pre_list.empty?
1073 ## replace <pre> content
1077 ## replace <pre> content
1074 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1078 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1075 end
1079 end
1076 end
1080 end
1077
1081
1078 def inline( text )
1082 def inline( text )
1079 [/^inline_/, /^glyphs_/].each do |meth_re|
1083 [/^inline_/, /^glyphs_/].each do |meth_re|
1080 @rules.each do |rule_name|
1084 @rules.each do |rule_name|
1081 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1085 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1082 end
1086 end
1083 end
1087 end
1084 end
1088 end
1085
1089
1086 def h_align( text )
1090 def h_align( text )
1087 H_ALGN_VALS[text]
1091 H_ALGN_VALS[text]
1088 end
1092 end
1089
1093
1090 def v_align( text )
1094 def v_align( text )
1091 V_ALGN_VALS[text]
1095 V_ALGN_VALS[text]
1092 end
1096 end
1093
1097
1094 def textile_popup_help( name, windowW, windowH )
1098 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 />'
1099 ' <a target="_blank" href="http://hobix.com/textile/#' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
1096 end
1100 end
1097
1101
1098 # HTML cleansing stuff
1102 # HTML cleansing stuff
1099 BASIC_TAGS = {
1103 BASIC_TAGS = {
1100 'a' => ['href', 'title'],
1104 'a' => ['href', 'title'],
1101 'img' => ['src', 'alt', 'title'],
1105 'img' => ['src', 'alt', 'title'],
1102 'br' => [],
1106 'br' => [],
1103 'i' => nil,
1107 'i' => nil,
1104 'u' => nil,
1108 'u' => nil,
1105 'b' => nil,
1109 'b' => nil,
1106 'pre' => nil,
1110 'pre' => nil,
1107 'kbd' => nil,
1111 'kbd' => nil,
1108 'code' => ['lang'],
1112 'code' => ['lang'],
1109 'cite' => nil,
1113 'cite' => nil,
1110 'strong' => nil,
1114 'strong' => nil,
1111 'em' => nil,
1115 'em' => nil,
1112 'ins' => nil,
1116 'ins' => nil,
1113 'sup' => nil,
1117 'sup' => nil,
1114 'sub' => nil,
1118 'sub' => nil,
1115 'del' => nil,
1119 'del' => nil,
1116 'table' => nil,
1120 'table' => nil,
1117 'tr' => nil,
1121 'tr' => nil,
1118 'td' => ['colspan', 'rowspan'],
1122 'td' => ['colspan', 'rowspan'],
1119 'th' => nil,
1123 'th' => nil,
1120 'ol' => nil,
1124 'ol' => nil,
1121 'ul' => nil,
1125 'ul' => nil,
1122 'li' => nil,
1126 'li' => nil,
1123 'p' => nil,
1127 'p' => nil,
1124 'h1' => nil,
1128 'h1' => nil,
1125 'h2' => nil,
1129 'h2' => nil,
1126 'h3' => nil,
1130 'h3' => nil,
1127 'h4' => nil,
1131 'h4' => nil,
1128 'h5' => nil,
1132 'h5' => nil,
1129 'h6' => nil,
1133 'h6' => nil,
1130 'blockquote' => ['cite']
1134 'blockquote' => ['cite']
1131 }
1135 }
1132
1136
1133 def clean_html( text, tags = BASIC_TAGS )
1137 def clean_html( text, tags = BASIC_TAGS )
1134 text.gsub!( /<!\[CDATA\[/, '' )
1138 text.gsub!( /<!\[CDATA\[/, '' )
1135 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1139 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1136 raw = $~
1140 raw = $~
1137 tag = raw[2].downcase
1141 tag = raw[2].downcase
1138 if tags.has_key? tag
1142 if tags.has_key? tag
1139 pcs = [tag]
1143 pcs = [tag]
1140 tags[tag].each do |prop|
1144 tags[tag].each do |prop|
1141 ['"', "'", ''].each do |q|
1145 ['"', "'", ''].each do |q|
1142 q2 = ( q != '' ? q : '\s' )
1146 q2 = ( q != '' ? q : '\s' )
1143 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1147 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1144 attrv = $1
1148 attrv = $1
1145 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1149 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1146 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1150 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1147 break
1151 break
1148 end
1152 end
1149 end
1153 end
1150 end if tags[tag]
1154 end if tags[tag]
1151 "<#{raw[1]}#{pcs.join " "}>"
1155 "<#{raw[1]}#{pcs.join " "}>"
1152 else
1156 else
1153 " "
1157 " "
1154 end
1158 end
1155 end
1159 end
1156 end
1160 end
1157
1161
1158 ALLOWED_TAGS = %w(redpre pre code notextile)
1162 ALLOWED_TAGS = %w(redpre pre code notextile)
1159
1163
1160 def escape_html_tags(text)
1164 def escape_html_tags(text)
1161 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1165 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1162 end
1166 end
1163 end
1167 end
1164
1168
@@ -1,424 +1,440
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 :attachments
28 :attachments
29
29
30 def setup
30 def setup
31 super
31 super
32 end
32 end
33
33
34 def test_auto_links
34 def test_auto_links
35 to_test = {
35 to_test = {
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
40 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
40 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
41 '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://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
42 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
42 'http://www.foo.bar/Test_(foobar)' => '<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_(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_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
44 '(see inline link : http://www.foo.bar/Test)' => '(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).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
45 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
46 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
46 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
47 '(see "inline link":http://www.foo.bar/Test)' => '(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 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
48 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
49 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
49 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
50 '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?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
51 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
51 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
52 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
52 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
53 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
53 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
54 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
54 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
55 }
55 }
56 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
56 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
57 end
57 end
58
58
59 def test_auto_mailto
59 def test_auto_mailto
60 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
60 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
61 textilizable('test@foo.bar')
61 textilizable('test@foo.bar')
62 end
62 end
63
63
64 def test_inline_images
64 def test_inline_images
65 to_test = {
65 to_test = {
66 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
66 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
67 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
67 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
68 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
68 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
69 '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 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
70 }
70 }
71 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
71 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
72 end
72 end
73
73
74 def test_attached_images
74 def test_attached_images
75 to_test = {
75 to_test = {
76 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
76 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
77 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />'
77 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />'
78 }
78 }
79 attachments = Attachment.find(:all)
79 attachments = Attachment.find(:all)
80 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
80 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
81 end
81 end
82
82
83 def test_textile_external_links
83 def test_textile_external_links
84 to_test = {
84 to_test = {
85 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
85 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
86 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
86 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
87 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
87 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
88 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
88 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
89 # no multiline link text
89 # no multiline link text
90 "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"
90 "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"
91 }
91 }
92 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
92 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
93 end
93 end
94
94
95 def test_redmine_links
95 def test_redmine_links
96 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
96 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
97 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
97 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
98
98
99 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
99 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
100 :class => 'changeset', :title => 'My very first commit')
100 :class => 'changeset', :title => 'My very first commit')
101
101
102 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
102 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
103 :class => 'document')
103 :class => 'document')
104
104
105 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
105 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
106 :class => 'version')
106 :class => 'version')
107
107
108 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
108 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
109
109
110 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
110 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
111 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
111 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
112
112
113 to_test = {
113 to_test = {
114 # tickets
114 # tickets
115 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
115 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
116 # changesets
116 # changesets
117 'r1' => changeset_link,
117 'r1' => changeset_link,
118 # documents
118 # documents
119 'document#1' => document_link,
119 'document#1' => document_link,
120 'document:"Test document"' => document_link,
120 'document:"Test document"' => document_link,
121 # versions
121 # versions
122 'version#2' => version_link,
122 'version#2' => version_link,
123 'version:1.0' => version_link,
123 'version:1.0' => version_link,
124 'version:"1.0"' => version_link,
124 'version:"1.0"' => version_link,
125 # source
125 # source
126 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
126 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
127 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
127 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
128 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
128 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
129 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
129 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
130 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
130 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
131 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
131 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
132 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
132 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
133 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
133 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
134 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
134 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
135 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
135 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
136 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
136 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
137 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
137 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
138 # message
138 # message
139 'message#4' => link_to('Post 2', message_url, :class => 'message'),
139 'message#4' => link_to('Post 2', message_url, :class => 'message'),
140 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
140 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
141 # escaping
141 # escaping
142 '!#3.' => '#3.',
142 '!#3.' => '#3.',
143 '!r1' => 'r1',
143 '!r1' => 'r1',
144 '!document#1' => 'document#1',
144 '!document#1' => 'document#1',
145 '!document:"Test document"' => 'document:"Test document"',
145 '!document:"Test document"' => 'document:"Test document"',
146 '!version#2' => 'version#2',
146 '!version#2' => 'version#2',
147 '!version:1.0' => 'version:1.0',
147 '!version:1.0' => 'version:1.0',
148 '!version:"1.0"' => 'version:"1.0"',
148 '!version:"1.0"' => 'version:"1.0"',
149 '!source:/some/file' => 'source:/some/file',
149 '!source:/some/file' => 'source:/some/file',
150 # invalid expressions
150 # invalid expressions
151 'source:' => 'source:',
151 'source:' => 'source:',
152 # url hash
152 # url hash
153 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
153 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
154 }
154 }
155 @project = Project.find(1)
155 @project = Project.find(1)
156 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
156 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
157 end
157 end
158
158
159 def test_wiki_links
159 def test_wiki_links
160 to_test = {
160 to_test = {
161 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
161 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
162 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
162 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
163 # link with anchor
163 # link with anchor
164 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
164 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
165 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
165 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
166 # page that doesn't exist
166 # page that doesn't exist
167 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
167 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
168 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
168 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
169 # link to another project wiki
169 # link to another project wiki
170 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
170 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
171 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
171 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
172 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
172 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
173 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
173 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
174 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
174 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
175 # striked through link
175 # striked through link
176 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
176 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
177 # escaping
177 # escaping
178 '![[Another page|Page]]' => '[[Another page|Page]]',
178 '![[Another page|Page]]' => '[[Another page|Page]]',
179 }
179 }
180 @project = Project.find(1)
180 @project = Project.find(1)
181 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
181 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
182 end
182 end
183
183
184 def test_html_tags
184 def test_html_tags
185 to_test = {
185 to_test = {
186 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
186 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
187 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
187 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
188 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
188 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
189 # do not escape pre/code tags
189 # do not escape pre/code tags
190 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
190 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
191 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
191 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
192 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
192 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
193 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
193 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
194 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
194 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
195 # remove attributes
195 # remove attributes except class
196 "<pre class='foo'>some text</pre>" => "<pre>some text</pre>",
196 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
197 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
197 }
198 }
198 to_test.each { |text, result| assert_equal result, textilizable(text) }
199 to_test.each { |text, result| assert_equal result, textilizable(text) }
199 end
200 end
200
201
201 def test_allowed_html_tags
202 def test_allowed_html_tags
202 to_test = {
203 to_test = {
203 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
204 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
204 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
205 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
205 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
206 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
206 }
207 }
207 to_test.each { |text, result| assert_equal result, textilizable(text) }
208 to_test.each { |text, result| assert_equal result, textilizable(text) }
208 end
209 end
209
210
211 def syntax_highlight
212 raw = <<-RAW
213 <pre><code class="ruby">
214 # Some ruby code here
215 </pre></code>
216 RAW
217
218 expected = <<-EXPECTED
219 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
220 </pre></code>
221 EXPECTED
222
223 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
224 end
225
210 def test_wiki_links_in_tables
226 def test_wiki_links_in_tables
211 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
227 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
212 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
228 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
213 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
229 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
214 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
230 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
215 }
231 }
216 @project = Project.find(1)
232 @project = Project.find(1)
217 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
233 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
218 end
234 end
219
235
220 def test_text_formatting
236 def test_text_formatting
221 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
237 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
222 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
238 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
223 }
239 }
224 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
240 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
225 end
241 end
226
242
227 def test_wiki_horizontal_rule
243 def test_wiki_horizontal_rule
228 assert_equal '<hr />', textilizable('---')
244 assert_equal '<hr />', textilizable('---')
229 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
245 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
230 end
246 end
231
247
232 def test_acronym
248 def test_acronym
233 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
249 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
234 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
250 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
235 end
251 end
236
252
237 def test_footnotes
253 def test_footnotes
238 raw = <<-RAW
254 raw = <<-RAW
239 This is some text[1].
255 This is some text[1].
240
256
241 fn1. This is the foot note
257 fn1. This is the foot note
242 RAW
258 RAW
243
259
244 expected = <<-EXPECTED
260 expected = <<-EXPECTED
245 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
261 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
246 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
262 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
247 EXPECTED
263 EXPECTED
248
264
249 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
265 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
250 end
266 end
251
267
252 def test_table_of_content
268 def test_table_of_content
253 raw = <<-RAW
269 raw = <<-RAW
254 {{toc}}
270 {{toc}}
255
271
256 h1. Title
272 h1. Title
257
273
258 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
274 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
259
275
260 h2. Subtitle
276 h2. Subtitle
261
277
262 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
278 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
263
279
264 h2. Subtitle with %{color:red}red text%
280 h2. Subtitle with %{color:red}red text%
265
281
266 h1. Another title
282 h1. Another title
267
283
268 RAW
284 RAW
269
285
270 expected = '<ul class="toc">' +
286 expected = '<ul class="toc">' +
271 '<li class="heading1"><a href="#Title">Title</a></li>' +
287 '<li class="heading1"><a href="#Title">Title</a></li>' +
272 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
288 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
273 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
289 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
274 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
290 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
275 '</ul>'
291 '</ul>'
276
292
277 assert textilizable(raw).gsub("\n", "").include?(expected)
293 assert textilizable(raw).gsub("\n", "").include?(expected)
278 end
294 end
279
295
280 def test_blockquote
296 def test_blockquote
281 # orig raw text
297 # orig raw text
282 raw = <<-RAW
298 raw = <<-RAW
283 John said:
299 John said:
284 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
300 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
285 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
301 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
286 > * Donec odio lorem,
302 > * Donec odio lorem,
287 > * sagittis ac,
303 > * sagittis ac,
288 > * malesuada in,
304 > * malesuada in,
289 > * adipiscing eu, dolor.
305 > * adipiscing eu, dolor.
290 >
306 >
291 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
307 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
292 > Proin a tellus. Nam vel neque.
308 > Proin a tellus. Nam vel neque.
293
309
294 He's right.
310 He's right.
295 RAW
311 RAW
296
312
297 # expected html
313 # expected html
298 expected = <<-EXPECTED
314 expected = <<-EXPECTED
299 <p>John said:</p>
315 <p>John said:</p>
300 <blockquote>
316 <blockquote>
301 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
317 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
302 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
318 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
303 <ul>
319 <ul>
304 <li>Donec odio lorem,</li>
320 <li>Donec odio lorem,</li>
305 <li>sagittis ac,</li>
321 <li>sagittis ac,</li>
306 <li>malesuada in,</li>
322 <li>malesuada in,</li>
307 <li>adipiscing eu, dolor.</li>
323 <li>adipiscing eu, dolor.</li>
308 </ul>
324 </ul>
309 <blockquote>
325 <blockquote>
310 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
326 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
311 </blockquote>
327 </blockquote>
312 <p>Proin a tellus. Nam vel neque.</p>
328 <p>Proin a tellus. Nam vel neque.</p>
313 </blockquote>
329 </blockquote>
314 <p>He's right.</p>
330 <p>He's right.</p>
315 EXPECTED
331 EXPECTED
316
332
317 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
333 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
318 end
334 end
319
335
320 def test_table
336 def test_table
321 raw = <<-RAW
337 raw = <<-RAW
322 This is a table with empty cells:
338 This is a table with empty cells:
323
339
324 |cell11|cell12||
340 |cell11|cell12||
325 |cell21||cell23|
341 |cell21||cell23|
326 |cell31|cell32|cell33|
342 |cell31|cell32|cell33|
327 RAW
343 RAW
328
344
329 expected = <<-EXPECTED
345 expected = <<-EXPECTED
330 <p>This is a table with empty cells:</p>
346 <p>This is a table with empty cells:</p>
331
347
332 <table>
348 <table>
333 <tr><td>cell11</td><td>cell12</td><td></td></tr>
349 <tr><td>cell11</td><td>cell12</td><td></td></tr>
334 <tr><td>cell21</td><td></td><td>cell23</td></tr>
350 <tr><td>cell21</td><td></td><td>cell23</td></tr>
335 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
351 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
336 </table>
352 </table>
337 EXPECTED
353 EXPECTED
338
354
339 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
355 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
340 end
356 end
341
357
342 def test_macro_hello_world
358 def test_macro_hello_world
343 text = "{{hello_world}}"
359 text = "{{hello_world}}"
344 assert textilizable(text).match(/Hello world!/)
360 assert textilizable(text).match(/Hello world!/)
345 # escaping
361 # escaping
346 text = "!{{hello_world}}"
362 text = "!{{hello_world}}"
347 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
363 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
348 end
364 end
349
365
350 def test_macro_include
366 def test_macro_include
351 @project = Project.find(1)
367 @project = Project.find(1)
352 # include a page of the current project wiki
368 # include a page of the current project wiki
353 text = "{{include(Another page)}}"
369 text = "{{include(Another page)}}"
354 assert textilizable(text).match(/This is a link to a ticket/)
370 assert textilizable(text).match(/This is a link to a ticket/)
355
371
356 @project = nil
372 @project = nil
357 # include a page of a specific project wiki
373 # include a page of a specific project wiki
358 text = "{{include(ecookbook:Another page)}}"
374 text = "{{include(ecookbook:Another page)}}"
359 assert textilizable(text).match(/This is a link to a ticket/)
375 assert textilizable(text).match(/This is a link to a ticket/)
360
376
361 text = "{{include(ecookbook:)}}"
377 text = "{{include(ecookbook:)}}"
362 assert textilizable(text).match(/CookBook documentation/)
378 assert textilizable(text).match(/CookBook documentation/)
363
379
364 text = "{{include(unknowidentifier:somepage)}}"
380 text = "{{include(unknowidentifier:somepage)}}"
365 assert textilizable(text).match(/Unknow project/)
381 assert textilizable(text).match(/Unknow project/)
366 end
382 end
367
383
368 def test_default_formatter
384 def test_default_formatter
369 Setting.text_formatting = 'unknown'
385 Setting.text_formatting = 'unknown'
370 text = 'a *link*: http://www.example.net/'
386 text = 'a *link*: http://www.example.net/'
371 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
387 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
372 Setting.text_formatting = 'textile'
388 Setting.text_formatting = 'textile'
373 end
389 end
374
390
375 def test_date_format_default
391 def test_date_format_default
376 today = Date.today
392 today = Date.today
377 Setting.date_format = ''
393 Setting.date_format = ''
378 assert_equal l_date(today), format_date(today)
394 assert_equal l_date(today), format_date(today)
379 end
395 end
380
396
381 def test_date_format
397 def test_date_format
382 today = Date.today
398 today = Date.today
383 Setting.date_format = '%d %m %Y'
399 Setting.date_format = '%d %m %Y'
384 assert_equal today.strftime('%d %m %Y'), format_date(today)
400 assert_equal today.strftime('%d %m %Y'), format_date(today)
385 end
401 end
386
402
387 def test_time_format_default
403 def test_time_format_default
388 now = Time.now
404 now = Time.now
389 Setting.date_format = ''
405 Setting.date_format = ''
390 Setting.time_format = ''
406 Setting.time_format = ''
391 assert_equal l_datetime(now), format_time(now)
407 assert_equal l_datetime(now), format_time(now)
392 assert_equal l_time(now), format_time(now, false)
408 assert_equal l_time(now), format_time(now, false)
393 end
409 end
394
410
395 def test_time_format
411 def test_time_format
396 now = Time.now
412 now = Time.now
397 Setting.date_format = '%d %m %Y'
413 Setting.date_format = '%d %m %Y'
398 Setting.time_format = '%H %M'
414 Setting.time_format = '%H %M'
399 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
415 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
400 assert_equal now.strftime('%H %M'), format_time(now, false)
416 assert_equal now.strftime('%H %M'), format_time(now, false)
401 end
417 end
402
418
403 def test_utc_time_format
419 def test_utc_time_format
404 now = Time.now.utc
420 now = Time.now.utc
405 Setting.date_format = '%d %m %Y'
421 Setting.date_format = '%d %m %Y'
406 Setting.time_format = '%H %M'
422 Setting.time_format = '%H %M'
407 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
423 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
408 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
424 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
409 end
425 end
410
426
411 def test_due_date_distance_in_words
427 def test_due_date_distance_in_words
412 to_test = { Date.today => 'Due in 0 days',
428 to_test = { Date.today => 'Due in 0 days',
413 Date.today + 1 => 'Due in 1 day',
429 Date.today + 1 => 'Due in 1 day',
414 Date.today + 100 => 'Due in 100 days',
430 Date.today + 100 => 'Due in 100 days',
415 Date.today + 20000 => 'Due in 20000 days',
431 Date.today + 20000 => 'Due in 20000 days',
416 Date.today - 1 => '1 day late',
432 Date.today - 1 => '1 day late',
417 Date.today - 100 => '100 days late',
433 Date.today - 100 => '100 days late',
418 Date.today - 20000 => '20000 days late',
434 Date.today - 20000 => '20000 days late',
419 }
435 }
420 to_test.each do |date, expected|
436 to_test.each do |date, expected|
421 assert_equal expected, due_date_distance_in_words(date)
437 assert_equal expected, due_date_distance_in_words(date)
422 end
438 end
423 end
439 end
424 end
440 end
General Comments 0
You need to be logged in to leave comments. Login now