##// END OF EJS Templates
Fixes #880: code tags not formatted correctly in the wiki (broken by r1216)....
Jean-Philippe Lang -
r1256:7673d69b96cf
parent child
Show More
@@ -1,1139 +1,1139
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 RedCloth < String
167 class RedCloth < String
168
168
169 VERSION = '3.0.4'
169 VERSION = '3.0.4'
170 DEFAULT_RULES = [:textile, :markdown]
170 DEFAULT_RULES = [:textile, :markdown]
171
171
172 #
172 #
173 # Two accessor for setting security restrictions.
173 # Two accessor for setting security restrictions.
174 #
174 #
175 # This is a nice thing if you're using RedCloth for
175 # This is a nice thing if you're using RedCloth for
176 # formatting in public places (e.g. Wikis) where you
176 # formatting in public places (e.g. Wikis) where you
177 # don't want users to abuse HTML for bad things.
177 # don't want users to abuse HTML for bad things.
178 #
178 #
179 # If +:filter_html+ is set, HTML which wasn't
179 # If +:filter_html+ is set, HTML which wasn't
180 # created by the Textile processor will be escaped.
180 # created by the Textile processor will be escaped.
181 #
181 #
182 # If +:filter_styles+ is set, it will also disable
182 # If +:filter_styles+ is set, it will also disable
183 # the style markup specifier. ('{color: red}')
183 # the style markup specifier. ('{color: red}')
184 #
184 #
185 attr_accessor :filter_html, :filter_styles
185 attr_accessor :filter_html, :filter_styles
186
186
187 #
187 #
188 # Accessor for toggling hard breaks.
188 # Accessor for toggling hard breaks.
189 #
189 #
190 # If +:hard_breaks+ is set, single newlines will
190 # If +:hard_breaks+ is set, single newlines will
191 # be converted to HTML break tags. This is the
191 # be converted to HTML break tags. This is the
192 # default behavior for traditional RedCloth.
192 # default behavior for traditional RedCloth.
193 #
193 #
194 attr_accessor :hard_breaks
194 attr_accessor :hard_breaks
195
195
196 # Accessor for toggling lite mode.
196 # Accessor for toggling lite mode.
197 #
197 #
198 # In lite mode, block-level rules are ignored. This means
198 # In lite mode, block-level rules are ignored. This means
199 # that tables, paragraphs, lists, and such aren't available.
199 # that tables, paragraphs, lists, and such aren't available.
200 # Only the inline markup for bold, italics, entities and so on.
200 # Only the inline markup for bold, italics, entities and so on.
201 #
201 #
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
203 # r.to_html
203 # r.to_html
204 # #=> "And then? She <strong>fell</strong>!"
204 # #=> "And then? She <strong>fell</strong>!"
205 #
205 #
206 attr_accessor :lite_mode
206 attr_accessor :lite_mode
207
207
208 #
208 #
209 # Accessor for toggling span caps.
209 # Accessor for toggling span caps.
210 #
210 #
211 # Textile places `span' tags around capitalized
211 # Textile places `span' tags around capitalized
212 # words by default, but this wreaks havoc on Wikis.
212 # words by default, but this wreaks havoc on Wikis.
213 # If +:no_span_caps+ is set, this will be
213 # If +:no_span_caps+ is set, this will be
214 # suppressed.
214 # suppressed.
215 #
215 #
216 attr_accessor :no_span_caps
216 attr_accessor :no_span_caps
217
217
218 #
218 #
219 # Establishes the markup predence. Available rules include:
219 # Establishes the markup predence. Available rules include:
220 #
220 #
221 # == Textile Rules
221 # == Textile Rules
222 #
222 #
223 # The following textile rules can be set individually. Or add the complete
223 # The following textile rules can be set individually. Or add the complete
224 # set of rules with the single :textile rule, which supplies the rule set in
224 # set of rules with the single :textile rule, which supplies the rule set in
225 # the following precedence:
225 # the following precedence:
226 #
226 #
227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
228 # block_textile_table:: Textile table block structures
228 # block_textile_table:: Textile table block structures
229 # block_textile_lists:: Textile list structures
229 # block_textile_lists:: Textile list structures
230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
231 # inline_textile_image:: Textile inline images
231 # inline_textile_image:: Textile inline images
232 # inline_textile_link:: Textile inline links
232 # inline_textile_link:: Textile inline links
233 # inline_textile_span:: Textile inline spans
233 # inline_textile_span:: Textile inline spans
234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
235 #
235 #
236 # == Markdown
236 # == Markdown
237 #
237 #
238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
239 # block_markdown_setext:: Markdown setext headers
239 # block_markdown_setext:: Markdown setext headers
240 # block_markdown_atx:: Markdown atx headers
240 # block_markdown_atx:: Markdown atx headers
241 # block_markdown_rule:: Markdown horizontal rules
241 # block_markdown_rule:: Markdown horizontal rules
242 # block_markdown_bq:: Markdown blockquotes
242 # block_markdown_bq:: Markdown blockquotes
243 # block_markdown_lists:: Markdown lists
243 # block_markdown_lists:: Markdown lists
244 # inline_markdown_link:: Markdown links
244 # inline_markdown_link:: Markdown links
245 attr_accessor :rules
245 attr_accessor :rules
246
246
247 # Returns a new RedCloth object, based on _string_ and
247 # Returns a new RedCloth object, based on _string_ and
248 # enforcing all the included _restrictions_.
248 # enforcing all the included _restrictions_.
249 #
249 #
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
251 # r.to_html
251 # r.to_html
252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
253 #
253 #
254 def initialize( string, restrictions = [] )
254 def initialize( string, restrictions = [] )
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
256 super( string )
256 super( string )
257 end
257 end
258
258
259 #
259 #
260 # Generates HTML from the Textile contents.
260 # Generates HTML from the Textile contents.
261 #
261 #
262 # r = RedCloth.new( "And then? She *fell*!" )
262 # r = RedCloth.new( "And then? She *fell*!" )
263 # r.to_html( true )
263 # r.to_html( true )
264 # #=>"And then? She <strong>fell</strong>!"
264 # #=>"And then? She <strong>fell</strong>!"
265 #
265 #
266 def to_html( *rules )
266 def to_html( *rules )
267 rules = DEFAULT_RULES if rules.empty?
267 rules = DEFAULT_RULES if rules.empty?
268 # make our working copy
268 # make our working copy
269 text = self.dup
269 text = self.dup
270
270
271 @urlrefs = {}
271 @urlrefs = {}
272 @shelf = []
272 @shelf = []
273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
275 :inline_textile_code, :inline_textile_span]
275 :inline_textile_code, :inline_textile_span]
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
277 :block_markdown_bq, :block_markdown_lists,
277 :block_markdown_bq, :block_markdown_lists,
278 :inline_markdown_reflink, :inline_markdown_link]
278 :inline_markdown_reflink, :inline_markdown_link]
279 @rules = rules.collect do |rule|
279 @rules = rules.collect do |rule|
280 case rule
280 case rule
281 when :markdown
281 when :markdown
282 markdown_rules
282 markdown_rules
283 when :textile
283 when :textile
284 textile_rules
284 textile_rules
285 else
285 else
286 rule
286 rule
287 end
287 end
288 end.flatten
288 end.flatten
289
289
290 # standard clean up
290 # standard clean up
291 incoming_entities text
291 incoming_entities text
292 clean_white_space text
292 clean_white_space text
293
293
294 # start processor
294 # start processor
295 @pre_list = []
295 @pre_list = []
296 rip_offtags text
296 rip_offtags text
297 no_textile text
297 no_textile text
298 escape_html_tags text
298 escape_html_tags text
299 hard_break text
299 hard_break text
300 unless @lite_mode
300 unless @lite_mode
301 refs text
301 refs text
302 blocks text
302 blocks text
303 end
303 end
304 inline text
304 inline text
305 smooth_offtags text
305 smooth_offtags text
306
306
307 retrieve text
307 retrieve text
308
308
309 text.gsub!( /<\/?notextile>/, '' )
309 text.gsub!( /<\/?notextile>/, '' )
310 text.gsub!( /x%x%/, '&#38;' )
310 text.gsub!( /x%x%/, '&#38;' )
311 clean_html text if filter_html
311 clean_html text if filter_html
312 text.strip!
312 text.strip!
313 text
313 text
314
314
315 end
315 end
316
316
317 #######
317 #######
318 private
318 private
319 #######
319 #######
320 #
320 #
321 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
321 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
322 # (from PyTextile)
322 # (from PyTextile)
323 #
323 #
324 TEXTILE_TAGS =
324 TEXTILE_TAGS =
325
325
326 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
326 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
327 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
327 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
328 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
328 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
329 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
329 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
330 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
330 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
331
331
332 collect! do |a, b|
332 collect! do |a, b|
333 [a.chr, ( b.zero? and "" or "&#{ b };" )]
333 [a.chr, ( b.zero? and "" or "&#{ b };" )]
334 end
334 end
335
335
336 #
336 #
337 # Regular expressions to convert to HTML.
337 # Regular expressions to convert to HTML.
338 #
338 #
339 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
339 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
340 A_VLGN = /[\-^~]/
340 A_VLGN = /[\-^~]/
341 C_CLAS = '(?:\([^)]+\))'
341 C_CLAS = '(?:\([^)]+\))'
342 C_LNGE = '(?:\[[^\]]+\])'
342 C_LNGE = '(?:\[[^\]]+\])'
343 C_STYL = '(?:\{[^}]+\})'
343 C_STYL = '(?:\{[^}]+\})'
344 S_CSPN = '(?:\\\\\d+)'
344 S_CSPN = '(?:\\\\\d+)'
345 S_RSPN = '(?:/\d+)'
345 S_RSPN = '(?:/\d+)'
346 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
346 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
347 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
347 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
348 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
348 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
349 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
349 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
350 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
350 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
351 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
351 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
352 PUNCT_Q = Regexp::quote( '*-_+^~%' )
352 PUNCT_Q = Regexp::quote( '*-_+^~%' )
353 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
353 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
354
354
355 # Text markup tags, don't conflict with block tags
355 # Text markup tags, don't conflict with block tags
356 SIMPLE_HTML_TAGS = [
356 SIMPLE_HTML_TAGS = [
357 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
357 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
358 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
358 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
359 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
359 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
360 ]
360 ]
361
361
362 QTAGS = [
362 QTAGS = [
363 ['**', 'b', :limit],
363 ['**', 'b', :limit],
364 ['*', 'strong', :limit],
364 ['*', 'strong', :limit],
365 ['??', 'cite', :limit],
365 ['??', 'cite', :limit],
366 ['-', 'del', :limit],
366 ['-', 'del', :limit],
367 ['__', 'i', :limit],
367 ['__', 'i', :limit],
368 ['_', 'em', :limit],
368 ['_', 'em', :limit],
369 ['%', 'span', :limit],
369 ['%', 'span', :limit],
370 ['+', 'ins', :limit],
370 ['+', 'ins', :limit],
371 ['^', 'sup', :limit],
371 ['^', 'sup', :limit],
372 ['~', 'sub', :limit]
372 ['~', 'sub', :limit]
373 ]
373 ]
374 QTAGS.collect! do |rc, ht, rtype|
374 QTAGS.collect! do |rc, ht, rtype|
375 rcq = Regexp::quote rc
375 rcq = Regexp::quote rc
376 re =
376 re =
377 case rtype
377 case rtype
378 when :limit
378 when :limit
379 /(^|[>\s])
379 /(^|[>\s])
380 (#{rcq})
380 (#{rcq})
381 (#{C})
381 (#{C})
382 (?::(\S+?))?
382 (?::(\S+?))?
383 ([^\s\-].*?[^\s\-]|\w)
383 ([^\s\-].*?[^\s\-]|\w)
384 #{rcq}
384 #{rcq}
385 (?=[[:punct:]]|\s|$)/x
385 (?=[[:punct:]]|\s|$)/x
386 else
386 else
387 /(#{rcq})
387 /(#{rcq})
388 (#{C})
388 (#{C})
389 (?::(\S+))?
389 (?::(\S+))?
390 ([^\s\-].*?[^\s\-]|\w)
390 ([^\s\-].*?[^\s\-]|\w)
391 #{rcq}/xm
391 #{rcq}/xm
392 end
392 end
393 [rc, ht, re, rtype]
393 [rc, ht, re, rtype]
394 end
394 end
395
395
396 # Elements to handle
396 # Elements to handle
397 GLYPHS = [
397 GLYPHS = [
398 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
398 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
399 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
399 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
400 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
400 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
401 # [ /\'/, '&#8216;' ], # single opening
401 # [ /\'/, '&#8216;' ], # single opening
402 [ /</, '&lt;' ], # less-than
402 [ /</, '&lt;' ], # less-than
403 [ />/, '&gt;' ], # greater-than
403 [ />/, '&gt;' ], # greater-than
404 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
404 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
405 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
405 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
406 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
406 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
407 # [ /"/, '&#8220;' ], # double opening
407 # [ /"/, '&#8220;' ], # double opening
408 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
408 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
409 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
409 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
410 [ /(^|[^"][>\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
410 [ /(^|[^"][>\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
411 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
411 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
412 [ /\s->\s/, ' &rarr; ' ], # right arrow
412 [ /\s->\s/, ' &rarr; ' ], # right arrow
413 [ /\s-\s/, ' &#8211; ' ], # en dash
413 [ /\s-\s/, ' &#8211; ' ], # en dash
414 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
414 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
415 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
415 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
416 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
416 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
417 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
417 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
418 ]
418 ]
419
419
420 H_ALGN_VALS = {
420 H_ALGN_VALS = {
421 '<' => 'left',
421 '<' => 'left',
422 '=' => 'center',
422 '=' => 'center',
423 '>' => 'right',
423 '>' => 'right',
424 '<>' => 'justify'
424 '<>' => 'justify'
425 }
425 }
426
426
427 V_ALGN_VALS = {
427 V_ALGN_VALS = {
428 '^' => 'top',
428 '^' => 'top',
429 '-' => 'middle',
429 '-' => 'middle',
430 '~' => 'bottom'
430 '~' => 'bottom'
431 }
431 }
432
432
433 #
433 #
434 # Flexible HTML escaping
434 # Flexible HTML escaping
435 #
435 #
436 def htmlesc( str, mode )
436 def htmlesc( str, mode )
437 str.gsub!( '&', '&amp;' )
437 str.gsub!( '&', '&amp;' )
438 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
438 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
439 str.gsub!( "'", '&#039;' ) if mode == :Quotes
439 str.gsub!( "'", '&#039;' ) if mode == :Quotes
440 str.gsub!( '<', '&lt;')
440 str.gsub!( '<', '&lt;')
441 str.gsub!( '>', '&gt;')
441 str.gsub!( '>', '&gt;')
442 end
442 end
443
443
444 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
444 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
445 def pgl( text )
445 def pgl( text )
446 GLYPHS.each do |re, resub, tog|
446 GLYPHS.each do |re, resub, tog|
447 next if tog and method( tog ).call
447 next if tog and method( tog ).call
448 text.gsub! re, resub
448 text.gsub! re, resub
449 end
449 end
450 end
450 end
451
451
452 # Parses Textile attribute lists and builds an HTML attribute string
452 # Parses Textile attribute lists and builds an HTML attribute string
453 def pba( text_in, element = "" )
453 def pba( text_in, element = "" )
454
454
455 return '' unless text_in
455 return '' unless text_in
456
456
457 style = []
457 style = []
458 text = text_in.dup
458 text = text_in.dup
459 if element == 'td'
459 if element == 'td'
460 colspan = $1 if text =~ /\\(\d+)/
460 colspan = $1 if text =~ /\\(\d+)/
461 rowspan = $1 if text =~ /\/(\d+)/
461 rowspan = $1 if text =~ /\/(\d+)/
462 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
462 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
463 end
463 end
464
464
465 style << "#{ $1 };" if not filter_styles and
465 style << "#{ $1 };" if not filter_styles and
466 text.sub!( /\{([^}]*)\}/, '' )
466 text.sub!( /\{([^}]*)\}/, '' )
467
467
468 lang = $1 if
468 lang = $1 if
469 text.sub!( /\[([^)]+?)\]/, '' )
469 text.sub!( /\[([^)]+?)\]/, '' )
470
470
471 cls = $1 if
471 cls = $1 if
472 text.sub!( /\(([^()]+?)\)/, '' )
472 text.sub!( /\(([^()]+?)\)/, '' )
473
473
474 style << "padding-left:#{ $1.length }em;" if
474 style << "padding-left:#{ $1.length }em;" if
475 text.sub!( /([(]+)/, '' )
475 text.sub!( /([(]+)/, '' )
476
476
477 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
477 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
478
478
479 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
479 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
480
480
481 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
481 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
482
482
483 atts = ''
483 atts = ''
484 atts << " style=\"#{ style.join }\"" unless style.empty?
484 atts << " style=\"#{ style.join }\"" unless style.empty?
485 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
485 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
486 atts << " lang=\"#{ lang }\"" if lang
486 atts << " lang=\"#{ lang }\"" if lang
487 atts << " id=\"#{ id }\"" if id
487 atts << " id=\"#{ id }\"" if id
488 atts << " colspan=\"#{ colspan }\"" if colspan
488 atts << " colspan=\"#{ colspan }\"" if colspan
489 atts << " rowspan=\"#{ rowspan }\"" if rowspan
489 atts << " rowspan=\"#{ rowspan }\"" if rowspan
490
490
491 atts
491 atts
492 end
492 end
493
493
494 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
494 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
495
495
496 # Parses a Textile table block, building HTML from the result.
496 # Parses a Textile table block, building HTML from the result.
497 def block_textile_table( text )
497 def block_textile_table( text )
498 text.gsub!( TABLE_RE ) do |matches|
498 text.gsub!( TABLE_RE ) do |matches|
499
499
500 tatts, fullrow = $~[1..2]
500 tatts, fullrow = $~[1..2]
501 tatts = pba( tatts, 'table' )
501 tatts = pba( tatts, 'table' )
502 tatts = shelve( tatts ) if tatts
502 tatts = shelve( tatts ) if tatts
503 rows = []
503 rows = []
504
504
505 fullrow.
505 fullrow.
506 split( /\|$/m ).
506 split( /\|$/m ).
507 delete_if { |x| x.empty? }.
507 delete_if { |x| x.empty? }.
508 each do |row|
508 each do |row|
509
509
510 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
510 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
511
511
512 cells = []
512 cells = []
513 row.split( '|' ).each do |cell|
513 row.split( '|' ).each do |cell|
514 ctyp = 'd'
514 ctyp = 'd'
515 ctyp = 'h' if cell =~ /^_/
515 ctyp = 'h' if cell =~ /^_/
516
516
517 catts = ''
517 catts = ''
518 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
518 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
519
519
520 unless cell.strip.empty?
520 unless cell.strip.empty?
521 catts = shelve( catts ) if catts
521 catts = shelve( catts ) if catts
522 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
522 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
523 end
523 end
524 end
524 end
525 ratts = shelve( ratts ) if ratts
525 ratts = shelve( ratts ) if ratts
526 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
526 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
527 end
527 end
528 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
528 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
529 end
529 end
530 end
530 end
531
531
532 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
532 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
533 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
533 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
534
534
535 # Parses Textile lists and generates HTML
535 # Parses Textile lists and generates HTML
536 def block_textile_lists( text )
536 def block_textile_lists( text )
537 text.gsub!( LISTS_RE ) do |match|
537 text.gsub!( LISTS_RE ) do |match|
538 lines = match.split( /\n/ )
538 lines = match.split( /\n/ )
539 last_line = -1
539 last_line = -1
540 depth = []
540 depth = []
541 lines.each_with_index do |line, line_id|
541 lines.each_with_index do |line, line_id|
542 if line =~ LISTS_CONTENT_RE
542 if line =~ LISTS_CONTENT_RE
543 tl,atts,content = $~[1..3]
543 tl,atts,content = $~[1..3]
544 if depth.last
544 if depth.last
545 if depth.last.length > tl.length
545 if depth.last.length > tl.length
546 (depth.length - 1).downto(0) do |i|
546 (depth.length - 1).downto(0) do |i|
547 break if depth[i].length == tl.length
547 break if depth[i].length == tl.length
548 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
548 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
549 depth.pop
549 depth.pop
550 end
550 end
551 end
551 end
552 if depth.last and depth.last.length == tl.length
552 if depth.last and depth.last.length == tl.length
553 lines[line_id - 1] << '</li>'
553 lines[line_id - 1] << '</li>'
554 end
554 end
555 end
555 end
556 unless depth.last == tl
556 unless depth.last == tl
557 depth << tl
557 depth << tl
558 atts = pba( atts )
558 atts = pba( atts )
559 atts = shelve( atts ) if atts
559 atts = shelve( atts ) if atts
560 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
560 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
561 else
561 else
562 lines[line_id] = "\t\t<li>#{ content }"
562 lines[line_id] = "\t\t<li>#{ content }"
563 end
563 end
564 last_line = line_id
564 last_line = line_id
565
565
566 else
566 else
567 last_line = line_id
567 last_line = line_id
568 end
568 end
569 if line_id - last_line > 1 or line_id == lines.length - 1
569 if line_id - last_line > 1 or line_id == lines.length - 1
570 depth.delete_if do |v|
570 depth.delete_if do |v|
571 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
571 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
572 end
572 end
573 end
573 end
574 end
574 end
575 lines.join( "\n" )
575 lines.join( "\n" )
576 end
576 end
577 end
577 end
578
578
579 CODE_RE = /(\W)
579 CODE_RE = /(\W)
580 @
580 @
581 (?:\|(\w+?)\|)?
581 (?:\|(\w+?)\|)?
582 (.+?)
582 (.+?)
583 @
583 @
584 (?=\W)/x
584 (?=\W)/x
585
585
586 def inline_textile_code( text )
586 def inline_textile_code( text )
587 text.gsub!( CODE_RE ) do |m|
587 text.gsub!( CODE_RE ) do |m|
588 before,lang,code,after = $~[1..4]
588 before,lang,code,after = $~[1..4]
589 lang = " lang=\"#{ lang }\"" if lang
589 lang = " lang=\"#{ lang }\"" if lang
590 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
590 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
591 end
591 end
592 end
592 end
593
593
594 def lT( text )
594 def lT( text )
595 text =~ /\#$/ ? 'o' : 'u'
595 text =~ /\#$/ ? 'o' : 'u'
596 end
596 end
597
597
598 def hard_break( text )
598 def hard_break( text )
599 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
599 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
600 end
600 end
601
601
602 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
602 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
603
603
604 def blocks( text, deep_code = false )
604 def blocks( text, deep_code = false )
605 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
605 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
606 plain = blk !~ /\A[#*> ]/
606 plain = blk !~ /\A[#*> ]/
607
607
608 # skip blocks that are complex HTML
608 # skip blocks that are complex HTML
609 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
609 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
610 blk
610 blk
611 else
611 else
612 # search for indentation levels
612 # search for indentation levels
613 blk.strip!
613 blk.strip!
614 if blk.empty?
614 if blk.empty?
615 blk
615 blk
616 else
616 else
617 code_blk = nil
617 code_blk = nil
618 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
618 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
619 flush_left iblk
619 flush_left iblk
620 blocks iblk, plain
620 blocks iblk, plain
621 iblk.gsub( /^(\S)/, "\t\\1" )
621 iblk.gsub( /^(\S)/, "\t\\1" )
622 if plain
622 if plain
623 code_blk = iblk; ""
623 code_blk = iblk; ""
624 else
624 else
625 iblk
625 iblk
626 end
626 end
627 end
627 end
628
628
629 block_applied = 0
629 block_applied = 0
630 @rules.each do |rule_name|
630 @rules.each do |rule_name|
631 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
631 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
632 end
632 end
633 if block_applied.zero?
633 if block_applied.zero?
634 if deep_code
634 if deep_code
635 blk = "\t<pre><code>#{ blk }</code></pre>"
635 blk = "\t<pre><code>#{ blk }</code></pre>"
636 else
636 else
637 blk = "\t<p>#{ blk }</p>"
637 blk = "\t<p>#{ blk }</p>"
638 end
638 end
639 end
639 end
640 # hard_break blk
640 # hard_break blk
641 blk + "\n#{ code_blk }"
641 blk + "\n#{ code_blk }"
642 end
642 end
643 end
643 end
644
644
645 end.join( "\n\n" ) )
645 end.join( "\n\n" ) )
646 end
646 end
647
647
648 def textile_bq( tag, atts, cite, content )
648 def textile_bq( tag, atts, cite, content )
649 cite, cite_title = check_refs( cite )
649 cite, cite_title = check_refs( cite )
650 cite = " cite=\"#{ cite }\"" if cite
650 cite = " cite=\"#{ cite }\"" if cite
651 atts = shelve( atts ) if atts
651 atts = shelve( atts ) if atts
652 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
652 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
653 end
653 end
654
654
655 def textile_p( tag, atts, cite, content )
655 def textile_p( tag, atts, cite, content )
656 atts = shelve( atts ) if atts
656 atts = shelve( atts ) if atts
657 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
657 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
658 end
658 end
659
659
660 alias textile_h1 textile_p
660 alias textile_h1 textile_p
661 alias textile_h2 textile_p
661 alias textile_h2 textile_p
662 alias textile_h3 textile_p
662 alias textile_h3 textile_p
663 alias textile_h4 textile_p
663 alias textile_h4 textile_p
664 alias textile_h5 textile_p
664 alias textile_h5 textile_p
665 alias textile_h6 textile_p
665 alias textile_h6 textile_p
666
666
667 def textile_fn_( tag, num, atts, cite, content )
667 def textile_fn_( tag, num, atts, cite, content )
668 atts << " id=\"fn#{ num }\""
668 atts << " id=\"fn#{ num }\""
669 content = "<sup>#{ num }</sup> #{ content }"
669 content = "<sup>#{ num }</sup> #{ content }"
670 atts = shelve( atts ) if atts
670 atts = shelve( atts ) if atts
671 "\t<p#{ atts }>#{ content }</p>"
671 "\t<p#{ atts }>#{ content }</p>"
672 end
672 end
673
673
674 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
674 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
675
675
676 def block_textile_prefix( text )
676 def block_textile_prefix( text )
677 if text =~ BLOCK_RE
677 if text =~ BLOCK_RE
678 tag,tagpre,num,atts,cite,content = $~[1..6]
678 tag,tagpre,num,atts,cite,content = $~[1..6]
679 atts = pba( atts )
679 atts = pba( atts )
680
680
681 # pass to prefix handler
681 # pass to prefix handler
682 if respond_to? "textile_#{ tag }", true
682 if respond_to? "textile_#{ tag }", true
683 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
683 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
684 elsif respond_to? "textile_#{ tagpre }_", true
684 elsif respond_to? "textile_#{ tagpre }_", true
685 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
685 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
686 end
686 end
687 end
687 end
688 end
688 end
689
689
690 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
690 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
691 def block_markdown_setext( text )
691 def block_markdown_setext( text )
692 if text =~ SETEXT_RE
692 if text =~ SETEXT_RE
693 tag = if $2 == "="; "h1"; else; "h2"; end
693 tag = if $2 == "="; "h1"; else; "h2"; end
694 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
694 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
695 blocks cont
695 blocks cont
696 text.replace( blk + cont )
696 text.replace( blk + cont )
697 end
697 end
698 end
698 end
699
699
700 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
700 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
701 [ ]*
701 [ ]*
702 (.+?) # $2 = Header text
702 (.+?) # $2 = Header text
703 [ ]*
703 [ ]*
704 \#* # optional closing #'s (not counted)
704 \#* # optional closing #'s (not counted)
705 $/x
705 $/x
706 def block_markdown_atx( text )
706 def block_markdown_atx( text )
707 if text =~ ATX_RE
707 if text =~ ATX_RE
708 tag = "h#{ $1.length }"
708 tag = "h#{ $1.length }"
709 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
709 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
710 blocks cont
710 blocks cont
711 text.replace( blk + cont )
711 text.replace( blk + cont )
712 end
712 end
713 end
713 end
714
714
715 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
715 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
716
716
717 def block_markdown_bq( text )
717 def block_markdown_bq( text )
718 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
718 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
719 blk.gsub!( /^ *> ?/, '' )
719 blk.gsub!( /^ *> ?/, '' )
720 flush_left blk
720 flush_left blk
721 blocks blk
721 blocks blk
722 blk.gsub!( /^(\S)/, "\t\\1" )
722 blk.gsub!( /^(\S)/, "\t\\1" )
723 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
723 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
724 end
724 end
725 end
725 end
726
726
727 MARKDOWN_RULE_RE = /^(#{
727 MARKDOWN_RULE_RE = /^(#{
728 ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
728 ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
729 })$/
729 })$/
730
730
731 def block_markdown_rule( text )
731 def block_markdown_rule( text )
732 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
732 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
733 "<hr />"
733 "<hr />"
734 end
734 end
735 end
735 end
736
736
737 # XXX TODO XXX
737 # XXX TODO XXX
738 def block_markdown_lists( text )
738 def block_markdown_lists( text )
739 end
739 end
740
740
741 def inline_textile_span( text )
741 def inline_textile_span( text )
742 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
742 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
743 text.gsub!( qtag_re ) do |m|
743 text.gsub!( qtag_re ) do |m|
744
744
745 case rtype
745 case rtype
746 when :limit
746 when :limit
747 sta,qtag,atts,cite,content = $~[1..5]
747 sta,qtag,atts,cite,content = $~[1..5]
748 else
748 else
749 qtag,atts,cite,content = $~[1..4]
749 qtag,atts,cite,content = $~[1..4]
750 sta = ''
750 sta = ''
751 end
751 end
752 atts = pba( atts )
752 atts = pba( atts )
753 atts << " cite=\"#{ cite }\"" if cite
753 atts << " cite=\"#{ cite }\"" if cite
754 atts = shelve( atts ) if atts
754 atts = shelve( atts ) if atts
755
755
756 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
756 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
757
757
758 end
758 end
759 end
759 end
760 end
760 end
761
761
762 LINK_RE = /
762 LINK_RE = /
763 ([\s\[{(]|[#{PUNCT}])? # $pre
763 ([\s\[{(]|[#{PUNCT}])? # $pre
764 " # start
764 " # start
765 (#{C}) # $atts
765 (#{C}) # $atts
766 ([^"]+?) # $text
766 ([^"]+?) # $text
767 \s?
767 \s?
768 (?:\(([^)]+?)\)(?="))? # $title
768 (?:\(([^)]+?)\)(?="))? # $title
769 ":
769 ":
770 (\S+?) # $url
770 (\S+?) # $url
771 (\/)? # $slash
771 (\/)? # $slash
772 ([^\w\/;]*?) # $post
772 ([^\w\/;]*?) # $post
773 (?=<|\s|$)
773 (?=<|\s|$)
774 /x
774 /x
775
775
776 def inline_textile_link( text )
776 def inline_textile_link( text )
777 text.gsub!( LINK_RE ) do |m|
777 text.gsub!( LINK_RE ) do |m|
778 pre,atts,text,title,url,slash,post = $~[1..7]
778 pre,atts,text,title,url,slash,post = $~[1..7]
779
779
780 url, url_title = check_refs( url )
780 url, url_title = check_refs( url )
781 title ||= url_title
781 title ||= url_title
782
782
783 atts = pba( atts )
783 atts = pba( atts )
784 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
784 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
785 atts << " title=\"#{ title }\"" if title
785 atts << " title=\"#{ title }\"" if title
786 atts = shelve( atts ) if atts
786 atts = shelve( atts ) if atts
787
787
788 external = (url =~ /^http:\/\//) ? ' class="external"' : ''
788 external = (url =~ /^http:\/\//) ? ' class="external"' : ''
789
789
790 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
790 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
791 end
791 end
792 end
792 end
793
793
794 MARKDOWN_REFLINK_RE = /
794 MARKDOWN_REFLINK_RE = /
795 \[([^\[\]]+)\] # $text
795 \[([^\[\]]+)\] # $text
796 [ ]? # opt. space
796 [ ]? # opt. space
797 (?:\n[ ]*)? # one optional newline followed by spaces
797 (?:\n[ ]*)? # one optional newline followed by spaces
798 \[(.*?)\] # $id
798 \[(.*?)\] # $id
799 /x
799 /x
800
800
801 def inline_markdown_reflink( text )
801 def inline_markdown_reflink( text )
802 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
802 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
803 text, id = $~[1..2]
803 text, id = $~[1..2]
804
804
805 if id.empty?
805 if id.empty?
806 url, title = check_refs( text )
806 url, title = check_refs( text )
807 else
807 else
808 url, title = check_refs( id )
808 url, title = check_refs( id )
809 end
809 end
810
810
811 atts = " href=\"#{ url }\""
811 atts = " href=\"#{ url }\""
812 atts << " title=\"#{ title }\"" if title
812 atts << " title=\"#{ title }\"" if title
813 atts = shelve( atts )
813 atts = shelve( atts )
814
814
815 "<a#{ atts }>#{ text }</a>"
815 "<a#{ atts }>#{ text }</a>"
816 end
816 end
817 end
817 end
818
818
819 MARKDOWN_LINK_RE = /
819 MARKDOWN_LINK_RE = /
820 \[([^\[\]]+)\] # $text
820 \[([^\[\]]+)\] # $text
821 \( # open paren
821 \( # open paren
822 [ \t]* # opt space
822 [ \t]* # opt space
823 <?(.+?)>? # $href
823 <?(.+?)>? # $href
824 [ \t]* # opt space
824 [ \t]* # opt space
825 (?: # whole title
825 (?: # whole title
826 (['"]) # $quote
826 (['"]) # $quote
827 (.*?) # $title
827 (.*?) # $title
828 \3 # matching quote
828 \3 # matching quote
829 )? # title is optional
829 )? # title is optional
830 \)
830 \)
831 /x
831 /x
832
832
833 def inline_markdown_link( text )
833 def inline_markdown_link( text )
834 text.gsub!( MARKDOWN_LINK_RE ) do |m|
834 text.gsub!( MARKDOWN_LINK_RE ) do |m|
835 text, url, quote, title = $~[1..4]
835 text, url, quote, title = $~[1..4]
836
836
837 atts = " href=\"#{ url }\""
837 atts = " href=\"#{ url }\""
838 atts << " title=\"#{ title }\"" if title
838 atts << " title=\"#{ title }\"" if title
839 atts = shelve( atts )
839 atts = shelve( atts )
840
840
841 "<a#{ atts }>#{ text }</a>"
841 "<a#{ atts }>#{ text }</a>"
842 end
842 end
843 end
843 end
844
844
845 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
845 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
846 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
846 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
847
847
848 def refs( text )
848 def refs( text )
849 @rules.each do |rule_name|
849 @rules.each do |rule_name|
850 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
850 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
851 end
851 end
852 end
852 end
853
853
854 def refs_textile( text )
854 def refs_textile( text )
855 text.gsub!( TEXTILE_REFS_RE ) do |m|
855 text.gsub!( TEXTILE_REFS_RE ) do |m|
856 flag, url = $~[2..3]
856 flag, url = $~[2..3]
857 @urlrefs[flag.downcase] = [url, nil]
857 @urlrefs[flag.downcase] = [url, nil]
858 nil
858 nil
859 end
859 end
860 end
860 end
861
861
862 def refs_markdown( text )
862 def refs_markdown( text )
863 text.gsub!( MARKDOWN_REFS_RE ) do |m|
863 text.gsub!( MARKDOWN_REFS_RE ) do |m|
864 flag, url = $~[2..3]
864 flag, url = $~[2..3]
865 title = $~[6]
865 title = $~[6]
866 @urlrefs[flag.downcase] = [url, title]
866 @urlrefs[flag.downcase] = [url, title]
867 nil
867 nil
868 end
868 end
869 end
869 end
870
870
871 def check_refs( text )
871 def check_refs( text )
872 ret = @urlrefs[text.downcase] if text
872 ret = @urlrefs[text.downcase] if text
873 ret || [text, nil]
873 ret || [text, nil]
874 end
874 end
875
875
876 IMAGE_RE = /
876 IMAGE_RE = /
877 (<p>|.|^) # start of line?
877 (<p>|.|^) # start of line?
878 \! # opening
878 \! # opening
879 (\<|\=|\>)? # optional alignment atts
879 (\<|\=|\>)? # optional alignment atts
880 (#{C}) # optional style,class atts
880 (#{C}) # optional style,class atts
881 (?:\. )? # optional dot-space
881 (?:\. )? # optional dot-space
882 ([^\s(!]+?) # presume this is the src
882 ([^\s(!]+?) # presume this is the src
883 \s? # optional space
883 \s? # optional space
884 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
884 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
885 \! # closing
885 \! # closing
886 (?::#{ HYPERLINK })? # optional href
886 (?::#{ HYPERLINK })? # optional href
887 /x
887 /x
888
888
889 def inline_textile_image( text )
889 def inline_textile_image( text )
890 text.gsub!( IMAGE_RE ) do |m|
890 text.gsub!( IMAGE_RE ) do |m|
891 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
891 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
892 atts = pba( atts )
892 atts = pba( atts )
893 atts = " src=\"#{ url }\"#{ atts }"
893 atts = " src=\"#{ url }\"#{ atts }"
894 atts << " title=\"#{ title }\"" if title
894 atts << " title=\"#{ title }\"" if title
895 atts << " alt=\"#{ title }\""
895 atts << " alt=\"#{ title }\""
896 # size = @getimagesize($url);
896 # size = @getimagesize($url);
897 # if($size) $atts.= " $size[3]";
897 # if($size) $atts.= " $size[3]";
898
898
899 href, alt_title = check_refs( href ) if href
899 href, alt_title = check_refs( href ) if href
900 url, url_title = check_refs( url )
900 url, url_title = check_refs( url )
901
901
902 out = ''
902 out = ''
903 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
903 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
904 out << "<img#{ shelve( atts ) } />"
904 out << "<img#{ shelve( atts ) } />"
905 out << "</a>#{ href_a1 }#{ href_a2 }" if href
905 out << "</a>#{ href_a1 }#{ href_a2 }" if href
906
906
907 if algn
907 if algn
908 algn = h_align( algn )
908 algn = h_align( algn )
909 if stln == "<p>"
909 if stln == "<p>"
910 out = "<p style=\"float:#{ algn }\">#{ out }"
910 out = "<p style=\"float:#{ algn }\">#{ out }"
911 else
911 else
912 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
912 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
913 end
913 end
914 else
914 else
915 out = stln + out
915 out = stln + out
916 end
916 end
917
917
918 out
918 out
919 end
919 end
920 end
920 end
921
921
922 def shelve( val )
922 def shelve( val )
923 @shelf << val
923 @shelf << val
924 " :redsh##{ @shelf.length }:"
924 " :redsh##{ @shelf.length }:"
925 end
925 end
926
926
927 def retrieve( text )
927 def retrieve( text )
928 @shelf.each_with_index do |r, i|
928 @shelf.each_with_index do |r, i|
929 text.gsub!( " :redsh##{ i + 1 }:", r )
929 text.gsub!( " :redsh##{ i + 1 }:", r )
930 end
930 end
931 end
931 end
932
932
933 def incoming_entities( text )
933 def incoming_entities( text )
934 ## turn any incoming ampersands into a dummy character for now.
934 ## turn any incoming ampersands into a dummy character for now.
935 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
935 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
936 ## implying an incoming html entity, to be skipped
936 ## implying an incoming html entity, to be skipped
937
937
938 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
938 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
939 end
939 end
940
940
941 def no_textile( text )
941 def no_textile( text )
942 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
942 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
943 '\1<notextile>\2</notextile>\3' )
943 '\1<notextile>\2</notextile>\3' )
944 text.gsub!( /^ *==([^=]+.*?)==/m,
944 text.gsub!( /^ *==([^=]+.*?)==/m,
945 '\1<notextile>\2</notextile>\3' )
945 '\1<notextile>\2</notextile>\3' )
946 end
946 end
947
947
948 def clean_white_space( text )
948 def clean_white_space( text )
949 # normalize line breaks
949 # normalize line breaks
950 text.gsub!( /\r\n/, "\n" )
950 text.gsub!( /\r\n/, "\n" )
951 text.gsub!( /\r/, "\n" )
951 text.gsub!( /\r/, "\n" )
952 text.gsub!( /\t/, ' ' )
952 text.gsub!( /\t/, ' ' )
953 text.gsub!( /^ +$/, '' )
953 text.gsub!( /^ +$/, '' )
954 text.gsub!( /\n{3,}/, "\n\n" )
954 text.gsub!( /\n{3,}/, "\n\n" )
955 text.gsub!( /"$/, "\" " )
955 text.gsub!( /"$/, "\" " )
956
956
957 # if entire document is indented, flush
957 # if entire document is indented, flush
958 # to the left side
958 # to the left side
959 flush_left text
959 flush_left text
960 end
960 end
961
961
962 def flush_left( text )
962 def flush_left( text )
963 indt = 0
963 indt = 0
964 if text =~ /^ /
964 if text =~ /^ /
965 while text !~ /^ {#{indt}}\S/
965 while text !~ /^ {#{indt}}\S/
966 indt += 1
966 indt += 1
967 end unless text.empty?
967 end unless text.empty?
968 if indt.nonzero?
968 if indt.nonzero?
969 text.gsub!( /^ {#{indt}}/, '' )
969 text.gsub!( /^ {#{indt}}/, '' )
970 end
970 end
971 end
971 end
972 end
972 end
973
973
974 def footnote_ref( text )
974 def footnote_ref( text )
975 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
975 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
976 '<sup><a href="#fn\1">\1</a></sup>\2' )
976 '<sup><a href="#fn\1">\1</a></sup>\2' )
977 end
977 end
978
978
979 OFFTAGS = /(code|pre|kbd|notextile)/
979 OFFTAGS = /(code|pre|kbd|notextile)/
980 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
980 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
981 OFFTAG_OPEN = /<#{ OFFTAGS }/
981 OFFTAG_OPEN = /<#{ OFFTAGS }/
982 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
982 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
983 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
983 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
984 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
984 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
985
985
986 def glyphs_textile( text, level = 0 )
986 def glyphs_textile( text, level = 0 )
987 if text !~ HASTAG_MATCH
987 if text !~ HASTAG_MATCH
988 pgl text
988 pgl text
989 footnote_ref text
989 footnote_ref text
990 else
990 else
991 codepre = 0
991 codepre = 0
992 text.gsub!( ALLTAG_MATCH ) do |line|
992 text.gsub!( ALLTAG_MATCH ) do |line|
993 ## matches are off if we're between <code>, <pre> etc.
993 ## matches are off if we're between <code>, <pre> etc.
994 if $1
994 if $1
995 if line =~ OFFTAG_OPEN
995 if line =~ OFFTAG_OPEN
996 codepre += 1
996 codepre += 1
997 elsif line =~ OFFTAG_CLOSE
997 elsif line =~ OFFTAG_CLOSE
998 codepre -= 1
998 codepre -= 1
999 codepre = 0 if codepre < 0
999 codepre = 0 if codepre < 0
1000 end
1000 end
1001 elsif codepre.zero?
1001 elsif codepre.zero?
1002 glyphs_textile( line, level + 1 )
1002 glyphs_textile( line, level + 1 )
1003 else
1003 else
1004 htmlesc( line, :NoQuotes )
1004 htmlesc( line, :NoQuotes )
1005 end
1005 end
1006 # p [level, codepre, line]
1006 # p [level, codepre, line]
1007
1007
1008 line
1008 line
1009 end
1009 end
1010 end
1010 end
1011 end
1011 end
1012
1012
1013 def rip_offtags( text )
1013 def rip_offtags( text )
1014 if text =~ /<.*>/
1014 if text =~ /<.*>/
1015 ## strip and encode <pre> content
1015 ## strip and encode <pre> content
1016 codepre, used_offtags = 0, {}
1016 codepre, used_offtags = 0, {}
1017 text.gsub!( OFFTAG_MATCH ) do |line|
1017 text.gsub!( OFFTAG_MATCH ) do |line|
1018 if $3
1018 if $3
1019 offtag, aftertag = $4, $5
1019 offtag, aftertag = $4, $5
1020 codepre += 1
1020 codepre += 1
1021 used_offtags[offtag] = true
1021 used_offtags[offtag] = true
1022 if codepre - used_offtags.length > 0
1022 if codepre - used_offtags.length > 0
1023 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1023 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1024 @pre_list.last << line
1024 @pre_list.last << line
1025 line = ""
1025 line = ""
1026 else
1026 else
1027 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1027 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1028 line = "<redpre##{ @pre_list.length }>"
1028 line = "<redpre##{ @pre_list.length }>"
1029 @pre_list << "#{ $3 }#{ aftertag }"
1029 @pre_list << "#{ $3 }#{ aftertag }"
1030 end
1030 end
1031 elsif $1 and codepre > 0
1031 elsif $1 and codepre > 0
1032 if codepre - used_offtags.length > 0
1032 if codepre - used_offtags.length > 0
1033 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1033 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1034 @pre_list.last << line
1034 @pre_list.last << line
1035 line = ""
1035 line = ""
1036 end
1036 end
1037 codepre -= 1 unless codepre.zero?
1037 codepre -= 1 unless codepre.zero?
1038 used_offtags = {} if codepre.zero?
1038 used_offtags = {} if codepre.zero?
1039 end
1039 end
1040 line
1040 line
1041 end
1041 end
1042 end
1042 end
1043 text
1043 text
1044 end
1044 end
1045
1045
1046 def smooth_offtags( text )
1046 def smooth_offtags( text )
1047 unless @pre_list.empty?
1047 unless @pre_list.empty?
1048 ## replace <pre> content
1048 ## replace <pre> content
1049 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1049 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1050 end
1050 end
1051 end
1051 end
1052
1052
1053 def inline( text )
1053 def inline( text )
1054 [/^inline_/, /^glyphs_/].each do |meth_re|
1054 [/^inline_/, /^glyphs_/].each do |meth_re|
1055 @rules.each do |rule_name|
1055 @rules.each do |rule_name|
1056 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1056 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1057 end
1057 end
1058 end
1058 end
1059 end
1059 end
1060
1060
1061 def h_align( text )
1061 def h_align( text )
1062 H_ALGN_VALS[text]
1062 H_ALGN_VALS[text]
1063 end
1063 end
1064
1064
1065 def v_align( text )
1065 def v_align( text )
1066 V_ALGN_VALS[text]
1066 V_ALGN_VALS[text]
1067 end
1067 end
1068
1068
1069 def textile_popup_help( name, windowW, windowH )
1069 def textile_popup_help( name, windowW, windowH )
1070 ' <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 />'
1070 ' <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 />'
1071 end
1071 end
1072
1072
1073 # HTML cleansing stuff
1073 # HTML cleansing stuff
1074 BASIC_TAGS = {
1074 BASIC_TAGS = {
1075 'a' => ['href', 'title'],
1075 'a' => ['href', 'title'],
1076 'img' => ['src', 'alt', 'title'],
1076 'img' => ['src', 'alt', 'title'],
1077 'br' => [],
1077 'br' => [],
1078 'i' => nil,
1078 'i' => nil,
1079 'u' => nil,
1079 'u' => nil,
1080 'b' => nil,
1080 'b' => nil,
1081 'pre' => nil,
1081 'pre' => nil,
1082 'kbd' => nil,
1082 'kbd' => nil,
1083 'code' => ['lang'],
1083 'code' => ['lang'],
1084 'cite' => nil,
1084 'cite' => nil,
1085 'strong' => nil,
1085 'strong' => nil,
1086 'em' => nil,
1086 'em' => nil,
1087 'ins' => nil,
1087 'ins' => nil,
1088 'sup' => nil,
1088 'sup' => nil,
1089 'sub' => nil,
1089 'sub' => nil,
1090 'del' => nil,
1090 'del' => nil,
1091 'table' => nil,
1091 'table' => nil,
1092 'tr' => nil,
1092 'tr' => nil,
1093 'td' => ['colspan', 'rowspan'],
1093 'td' => ['colspan', 'rowspan'],
1094 'th' => nil,
1094 'th' => nil,
1095 'ol' => nil,
1095 'ol' => nil,
1096 'ul' => nil,
1096 'ul' => nil,
1097 'li' => nil,
1097 'li' => nil,
1098 'p' => nil,
1098 'p' => nil,
1099 'h1' => nil,
1099 'h1' => nil,
1100 'h2' => nil,
1100 'h2' => nil,
1101 'h3' => nil,
1101 'h3' => nil,
1102 'h4' => nil,
1102 'h4' => nil,
1103 'h5' => nil,
1103 'h5' => nil,
1104 'h6' => nil,
1104 'h6' => nil,
1105 'blockquote' => ['cite']
1105 'blockquote' => ['cite']
1106 }
1106 }
1107
1107
1108 def clean_html( text, tags = BASIC_TAGS )
1108 def clean_html( text, tags = BASIC_TAGS )
1109 text.gsub!( /<!\[CDATA\[/, '' )
1109 text.gsub!( /<!\[CDATA\[/, '' )
1110 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1110 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1111 raw = $~
1111 raw = $~
1112 tag = raw[2].downcase
1112 tag = raw[2].downcase
1113 if tags.has_key? tag
1113 if tags.has_key? tag
1114 pcs = [tag]
1114 pcs = [tag]
1115 tags[tag].each do |prop|
1115 tags[tag].each do |prop|
1116 ['"', "'", ''].each do |q|
1116 ['"', "'", ''].each do |q|
1117 q2 = ( q != '' ? q : '\s' )
1117 q2 = ( q != '' ? q : '\s' )
1118 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1118 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1119 attrv = $1
1119 attrv = $1
1120 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1120 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1121 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1121 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1122 break
1122 break
1123 end
1123 end
1124 end
1124 end
1125 end if tags[tag]
1125 end if tags[tag]
1126 "<#{raw[1]}#{pcs.join " "}>"
1126 "<#{raw[1]}#{pcs.join " "}>"
1127 else
1127 else
1128 " "
1128 " "
1129 end
1129 end
1130 end
1130 end
1131 end
1131 end
1132
1132
1133 ALLOWED_TAGS = %w(redpre pre)
1133 ALLOWED_TAGS = %w(redpre pre code)
1134
1134
1135 def escape_html_tags(text)
1135 def escape_html_tags(text)
1136 text.gsub!(%r{<((\/?)(\w+))}) {|m| ALLOWED_TAGS.include?($3) ? "<#{$1}" : "&lt;#{$1}" }
1136 text.gsub!(%r{<((\/?)(\w+))}) {|m| ALLOWED_TAGS.include?($3) ? "<#{$1}" : "&lt;#{$1}" }
1137 end
1137 end
1138 end
1138 end
1139
1139
@@ -1,170 +1,183
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, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents
24
24
25 def setup
25 def setup
26 super
26 super
27 end
27 end
28
28
29 def test_auto_links
29 def test_auto_links
30 to_test = {
30 to_test = {
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
34 '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>.',
34 '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>.',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
36 '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>',
36 '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>',
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
38 }
38 }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
40 end
40 end
41
41
42 def test_auto_mailto
42 def test_auto_mailto
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
44 textilizable('test@foo.bar')
44 textilizable('test@foo.bar')
45 end
45 end
46
46
47 def test_inline_images
47 def test_inline_images
48 to_test = {
48 to_test = {
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
53 }
53 }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 end
55 end
56
56
57 def test_textile_external_links
57 def test_textile_external_links
58 to_test = {
58 to_test = {
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
62 }
62 }
63 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
63 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
64 end
64 end
65
65
66 def test_redmine_links
66 def test_redmine_links
67 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
67 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
68 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
68 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
69
69
70 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
70 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
71 :class => 'changeset', :title => 'My very first commit')
71 :class => 'changeset', :title => 'My very first commit')
72
72
73 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
73 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
74 :class => 'document')
74 :class => 'document')
75
75
76 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
76 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
77 :class => 'version')
77 :class => 'version')
78
78
79 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => 'some/file'}
79 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => 'some/file'}
80
80
81 to_test = {
81 to_test = {
82 # tickets
82 # tickets
83 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
83 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
84 # changesets
84 # changesets
85 'r1' => changeset_link,
85 'r1' => changeset_link,
86 # documents
86 # documents
87 'document#1' => document_link,
87 'document#1' => document_link,
88 'document:"Test document"' => document_link,
88 'document:"Test document"' => document_link,
89 # versions
89 # versions
90 'version#2' => version_link,
90 'version#2' => version_link,
91 'version:1.0' => version_link,
91 'version:1.0' => version_link,
92 'version:"1.0"' => version_link,
92 'version:"1.0"' => version_link,
93 # source
93 # source
94 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
94 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
95 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
95 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
96 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
96 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
97 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
97 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
98 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
98 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
99 # escaping
99 # escaping
100 '!#3.' => '#3.',
100 '!#3.' => '#3.',
101 '!r1' => 'r1',
101 '!r1' => 'r1',
102 '!document#1' => 'document#1',
102 '!document#1' => 'document#1',
103 '!document:"Test document"' => 'document:"Test document"',
103 '!document:"Test document"' => 'document:"Test document"',
104 '!version#2' => 'version#2',
104 '!version#2' => 'version#2',
105 '!version:1.0' => 'version:1.0',
105 '!version:1.0' => 'version:1.0',
106 '!version:"1.0"' => 'version:"1.0"',
106 '!version:"1.0"' => 'version:"1.0"',
107 '!source:/some/file' => 'source:/some/file',
107 '!source:/some/file' => 'source:/some/file',
108 # invalid expressions
108 # invalid expressions
109 'source:' => 'source:'
109 'source:' => 'source:'
110 }
110 }
111 @project = Project.find(1)
111 @project = Project.find(1)
112 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
112 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
113 end
113 end
114
114
115 def test_wiki_links
115 def test_wiki_links
116 to_test = {
116 to_test = {
117 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
117 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
118 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
118 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
119 # page that doesn't exist
119 # page that doesn't exist
120 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
120 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
121 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
121 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
122 # link to another project wiki
122 # link to another project wiki
123 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
123 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
124 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
124 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
125 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
125 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
126 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
126 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
127 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
127 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
128 # escaping
128 # escaping
129 '![[Another page|Page]]' => '[[Another page|Page]]',
129 '![[Another page|Page]]' => '[[Another page|Page]]',
130 }
130 }
131 @project = Project.find(1)
131 @project = Project.find(1)
132 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
132 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
133 end
133 end
134
134
135 def test_html_tags
136 to_test = {
137 "<div>content</div>" => "<p>&lt;div>content&lt;/div></p>",
138 "<script>some script;</script>" => "<p>&lt;script>some script;&lt;/script></p>",
139 # do not escape pre/code tags
140 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
141 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
142 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
143 }
144 to_test.each { |text, result| assert_equal result, textilizable(text) }
145
146 end
147
135 def test_macro_hello_world
148 def test_macro_hello_world
136 text = "{{hello_world}}"
149 text = "{{hello_world}}"
137 assert textilizable(text).match(/Hello world!/)
150 assert textilizable(text).match(/Hello world!/)
138 # escaping
151 # escaping
139 text = "!{{hello_world}}"
152 text = "!{{hello_world}}"
140 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
153 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
141 end
154 end
142
155
143 def test_date_format_default
156 def test_date_format_default
144 today = Date.today
157 today = Date.today
145 Setting.date_format = ''
158 Setting.date_format = ''
146 assert_equal l_date(today), format_date(today)
159 assert_equal l_date(today), format_date(today)
147 end
160 end
148
161
149 def test_date_format
162 def test_date_format
150 today = Date.today
163 today = Date.today
151 Setting.date_format = '%d %m %Y'
164 Setting.date_format = '%d %m %Y'
152 assert_equal today.strftime('%d %m %Y'), format_date(today)
165 assert_equal today.strftime('%d %m %Y'), format_date(today)
153 end
166 end
154
167
155 def test_time_format_default
168 def test_time_format_default
156 now = Time.now
169 now = Time.now
157 Setting.date_format = ''
170 Setting.date_format = ''
158 Setting.time_format = ''
171 Setting.time_format = ''
159 assert_equal l_datetime(now), format_time(now)
172 assert_equal l_datetime(now), format_time(now)
160 assert_equal l_time(now), format_time(now, false)
173 assert_equal l_time(now), format_time(now, false)
161 end
174 end
162
175
163 def test_time_format
176 def test_time_format
164 now = Time.now
177 now = Time.now
165 Setting.date_format = '%d %m %Y'
178 Setting.date_format = '%d %m %Y'
166 Setting.time_format = '%H %M'
179 Setting.time_format = '%H %M'
167 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
180 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
168 assert_equal now.strftime('%H %M'), format_time(now, false)
181 assert_equal now.strftime('%H %M'), format_time(now, false)
169 end
182 end
170 end
183 end
General Comments 0
You need to be logged in to leave comments. Login now