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