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