##// END OF EJS Templates
Fixed: URL with ~ broken in wiki formatting....
Jean-Philippe Lang -
r785:586231067bb1
parent child
Show More
@@ -1,1132 +1,1132
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, :glyphs_textile]
275 :inline_textile_code, :inline_textile_span, :glyphs_textile]
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
277 :block_markdown_bq, :block_markdown_lists,
277 :block_markdown_bq, :block_markdown_lists,
278 :inline_markdown_reflink, :inline_markdown_link]
278 :inline_markdown_reflink, :inline_markdown_link]
279 @rules = rules.collect do |rule|
279 @rules = rules.collect do |rule|
280 case rule
280 case rule
281 when :markdown
281 when :markdown
282 markdown_rules
282 markdown_rules
283 when :textile
283 when :textile
284 textile_rules
284 textile_rules
285 else
285 else
286 rule
286 rule
287 end
287 end
288 end.flatten
288 end.flatten
289
289
290 # standard clean up
290 # standard clean up
291 incoming_entities text
291 incoming_entities text
292 clean_white_space text
292 clean_white_space text
293
293
294 # start processor
294 # start processor
295 @pre_list = []
295 @pre_list = []
296 rip_offtags text
296 rip_offtags text
297 no_textile text
297 no_textile text
298 hard_break text
298 hard_break text
299 unless @lite_mode
299 unless @lite_mode
300 refs text
300 refs text
301 blocks text
301 blocks text
302 end
302 end
303 inline text
303 inline text
304 smooth_offtags text
304 smooth_offtags text
305
305
306 retrieve text
306 retrieve text
307
307
308 text.gsub!( /<\/?notextile>/, '' )
308 text.gsub!( /<\/?notextile>/, '' )
309 text.gsub!( /x%x%/, '&#38;' )
309 text.gsub!( /x%x%/, '&#38;' )
310 clean_html text if filter_html
310 clean_html text if filter_html
311 text.strip!
311 text.strip!
312 text
312 text
313
313
314 end
314 end
315
315
316 #######
316 #######
317 private
317 private
318 #######
318 #######
319 #
319 #
320 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
320 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
321 # (from PyTextile)
321 # (from PyTextile)
322 #
322 #
323 TEXTILE_TAGS =
323 TEXTILE_TAGS =
324
324
325 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
325 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
326 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
326 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
327 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
327 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
328 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
328 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
329 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
329 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
330
330
331 collect! do |a, b|
331 collect! do |a, b|
332 [a.chr, ( b.zero? and "" or "&#{ b };" )]
332 [a.chr, ( b.zero? and "" or "&#{ b };" )]
333 end
333 end
334
334
335 #
335 #
336 # Regular expressions to convert to HTML.
336 # Regular expressions to convert to HTML.
337 #
337 #
338 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
338 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
339 A_VLGN = /[\-^~]/
339 A_VLGN = /[\-^~]/
340 C_CLAS = '(?:\([^)]+\))'
340 C_CLAS = '(?:\([^)]+\))'
341 C_LNGE = '(?:\[[^\]]+\])'
341 C_LNGE = '(?:\[[^\]]+\])'
342 C_STYL = '(?:\{[^}]+\})'
342 C_STYL = '(?:\{[^}]+\})'
343 S_CSPN = '(?:\\\\\d+)'
343 S_CSPN = '(?:\\\\\d+)'
344 S_RSPN = '(?:/\d+)'
344 S_RSPN = '(?:/\d+)'
345 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
345 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
346 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
346 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
347 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
347 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
348 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
348 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
349 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
349 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
350 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
350 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
351 PUNCT_Q = Regexp::quote( '*-_+^~%' )
351 PUNCT_Q = Regexp::quote( '*-_+^~%' )
352 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
352 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
353
353
354 # Text markup tags, don't conflict with block tags
354 # Text markup tags, don't conflict with block tags
355 SIMPLE_HTML_TAGS = [
355 SIMPLE_HTML_TAGS = [
356 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
356 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
357 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
357 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
358 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
358 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
359 ]
359 ]
360
360
361 QTAGS = [
361 QTAGS = [
362 ['**', 'b'],
362 ['**', 'b', :limit],
363 ['*', 'strong'],
363 ['*', 'strong', :limit],
364 ['??', 'cite', :limit],
364 ['??', 'cite', :limit],
365 ['-', 'del', :limit],
365 ['-', 'del', :limit],
366 ['__', 'i'],
366 ['__', 'i', :limit],
367 ['_', 'em', :limit],
367 ['_', 'em', :limit],
368 ['%', 'span', :limit],
368 ['%', 'span', :limit],
369 ['+', 'ins', :limit],
369 ['+', 'ins', :limit],
370 ['^', 'sup'],
370 ['^', 'sup', :limit],
371 ['~', 'sub']
371 ['~', 'sub', :limit]
372 ]
372 ]
373 QTAGS.collect! do |rc, ht, rtype|
373 QTAGS.collect! do |rc, ht, rtype|
374 rcq = Regexp::quote rc
374 rcq = Regexp::quote rc
375 re =
375 re =
376 case rtype
376 case rtype
377 when :limit
377 when :limit
378 /(\W)
378 /(\W)
379 (#{rcq})
379 (#{rcq})
380 (#{C})
380 (#{C})
381 (?::(\S+?))?
381 (?::(\S+?))?
382 (\S.*?\S|\S)
382 (\S.*?\S|\S)
383 #{rcq}
383 #{rcq}
384 (?=\W)/x
384 (?=\W)/x
385 else
385 else
386 /(#{rcq})
386 /(#{rcq})
387 (#{C})
387 (#{C})
388 (?::(\S+))?
388 (?::(\S+))?
389 (\S.*?\S|\S)
389 (\S.*?\S|\S)
390 #{rcq}/xm
390 #{rcq}/xm
391 end
391 end
392 [rc, ht, re, rtype]
392 [rc, ht, re, rtype]
393 end
393 end
394
394
395 # Elements to handle
395 # Elements to handle
396 GLYPHS = [
396 GLYPHS = [
397 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
397 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
398 [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
398 [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
399 [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
399 [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
400 [ /\'/, '&#8216;' ], # single opening
400 [ /\'/, '&#8216;' ], # single opening
401 [ /</, '&lt;' ], # less-than
401 [ /</, '&lt;' ], # less-than
402 [ />/, '&gt;' ], # greater-than
402 [ />/, '&gt;' ], # greater-than
403 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
403 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
404 [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
404 [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
405 [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
405 [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
406 [ /"/, '&#8220;' ], # double opening
406 [ /"/, '&#8220;' ], # double opening
407 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
407 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
408 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
408 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
409 [ /(^|[^"][>\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
409 [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
410 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
410 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
411 [ /\s->\s/, ' &rarr; ' ], # right arrow
411 [ /\s->\s/, ' &rarr; ' ], # right arrow
412 [ /\s-\s/, ' &#8211; ' ], # en dash
412 [ /\s-\s/, ' &#8211; ' ], # en dash
413 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
413 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
414 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
414 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
415 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
415 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
416 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
416 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
417 ]
417 ]
418
418
419 H_ALGN_VALS = {
419 H_ALGN_VALS = {
420 '<' => 'left',
420 '<' => 'left',
421 '=' => 'center',
421 '=' => 'center',
422 '>' => 'right',
422 '>' => 'right',
423 '<>' => 'justify'
423 '<>' => 'justify'
424 }
424 }
425
425
426 V_ALGN_VALS = {
426 V_ALGN_VALS = {
427 '^' => 'top',
427 '^' => 'top',
428 '-' => 'middle',
428 '-' => 'middle',
429 '~' => 'bottom'
429 '~' => 'bottom'
430 }
430 }
431
431
432 #
432 #
433 # Flexible HTML escaping
433 # Flexible HTML escaping
434 #
434 #
435 def htmlesc( str, mode )
435 def htmlesc( str, mode )
436 str.gsub!( '&', '&amp;' )
436 str.gsub!( '&', '&amp;' )
437 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
437 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
438 str.gsub!( "'", '&#039;' ) if mode == :Quotes
438 str.gsub!( "'", '&#039;' ) if mode == :Quotes
439 str.gsub!( '<', '&lt;')
439 str.gsub!( '<', '&lt;')
440 str.gsub!( '>', '&gt;')
440 str.gsub!( '>', '&gt;')
441 end
441 end
442
442
443 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
443 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
444 def pgl( text )
444 def pgl( text )
445 GLYPHS.each do |re, resub, tog|
445 GLYPHS.each do |re, resub, tog|
446 next if tog and method( tog ).call
446 next if tog and method( tog ).call
447 text.gsub! re, resub
447 text.gsub! re, resub
448 end
448 end
449 end
449 end
450
450
451 # Parses Textile attribute lists and builds an HTML attribute string
451 # Parses Textile attribute lists and builds an HTML attribute string
452 def pba( text_in, element = "" )
452 def pba( text_in, element = "" )
453
453
454 return '' unless text_in
454 return '' unless text_in
455
455
456 style = []
456 style = []
457 text = text_in.dup
457 text = text_in.dup
458 if element == 'td'
458 if element == 'td'
459 colspan = $1 if text =~ /\\(\d+)/
459 colspan = $1 if text =~ /\\(\d+)/
460 rowspan = $1 if text =~ /\/(\d+)/
460 rowspan = $1 if text =~ /\/(\d+)/
461 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
461 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
462 end
462 end
463
463
464 style << "#{ $1 };" if not filter_styles and
464 style << "#{ $1 };" if not filter_styles and
465 text.sub!( /\{([^}]*)\}/, '' )
465 text.sub!( /\{([^}]*)\}/, '' )
466
466
467 lang = $1 if
467 lang = $1 if
468 text.sub!( /\[([^)]+?)\]/, '' )
468 text.sub!( /\[([^)]+?)\]/, '' )
469
469
470 cls = $1 if
470 cls = $1 if
471 text.sub!( /\(([^()]+?)\)/, '' )
471 text.sub!( /\(([^()]+?)\)/, '' )
472
472
473 style << "padding-left:#{ $1.length }em;" if
473 style << "padding-left:#{ $1.length }em;" if
474 text.sub!( /([(]+)/, '' )
474 text.sub!( /([(]+)/, '' )
475
475
476 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
476 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
477
477
478 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
478 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
479
479
480 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
480 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
481
481
482 atts = ''
482 atts = ''
483 atts << " style=\"#{ style.join }\"" unless style.empty?
483 atts << " style=\"#{ style.join }\"" unless style.empty?
484 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
484 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
485 atts << " lang=\"#{ lang }\"" if lang
485 atts << " lang=\"#{ lang }\"" if lang
486 atts << " id=\"#{ id }\"" if id
486 atts << " id=\"#{ id }\"" if id
487 atts << " colspan=\"#{ colspan }\"" if colspan
487 atts << " colspan=\"#{ colspan }\"" if colspan
488 atts << " rowspan=\"#{ rowspan }\"" if rowspan
488 atts << " rowspan=\"#{ rowspan }\"" if rowspan
489
489
490 atts
490 atts
491 end
491 end
492
492
493 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
493 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
494
494
495 # Parses a Textile table block, building HTML from the result.
495 # Parses a Textile table block, building HTML from the result.
496 def block_textile_table( text )
496 def block_textile_table( text )
497 text.gsub!( TABLE_RE ) do |matches|
497 text.gsub!( TABLE_RE ) do |matches|
498
498
499 tatts, fullrow = $~[1..2]
499 tatts, fullrow = $~[1..2]
500 tatts = pba( tatts, 'table' )
500 tatts = pba( tatts, 'table' )
501 tatts = shelve( tatts ) if tatts
501 tatts = shelve( tatts ) if tatts
502 rows = []
502 rows = []
503
503
504 fullrow.
504 fullrow.
505 split( /\|$/m ).
505 split( /\|$/m ).
506 delete_if { |x| x.empty? }.
506 delete_if { |x| x.empty? }.
507 each do |row|
507 each do |row|
508
508
509 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
509 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
510
510
511 cells = []
511 cells = []
512 row.split( '|' ).each do |cell|
512 row.split( '|' ).each do |cell|
513 ctyp = 'd'
513 ctyp = 'd'
514 ctyp = 'h' if cell =~ /^_/
514 ctyp = 'h' if cell =~ /^_/
515
515
516 catts = ''
516 catts = ''
517 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
517 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
518
518
519 unless cell.strip.empty?
519 unless cell.strip.empty?
520 catts = shelve( catts ) if catts
520 catts = shelve( catts ) if catts
521 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
521 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
522 end
522 end
523 end
523 end
524 ratts = shelve( ratts ) if ratts
524 ratts = shelve( ratts ) if ratts
525 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
525 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
526 end
526 end
527 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
527 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
528 end
528 end
529 end
529 end
530
530
531 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
531 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
532 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
532 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
533
533
534 # Parses Textile lists and generates HTML
534 # Parses Textile lists and generates HTML
535 def block_textile_lists( text )
535 def block_textile_lists( text )
536 text.gsub!( LISTS_RE ) do |match|
536 text.gsub!( LISTS_RE ) do |match|
537 lines = match.split( /\n/ )
537 lines = match.split( /\n/ )
538 last_line = -1
538 last_line = -1
539 depth = []
539 depth = []
540 lines.each_with_index do |line, line_id|
540 lines.each_with_index do |line, line_id|
541 if line =~ LISTS_CONTENT_RE
541 if line =~ LISTS_CONTENT_RE
542 tl,atts,content = $~[1..3]
542 tl,atts,content = $~[1..3]
543 if depth.last
543 if depth.last
544 if depth.last.length > tl.length
544 if depth.last.length > tl.length
545 (depth.length - 1).downto(0) do |i|
545 (depth.length - 1).downto(0) do |i|
546 break if depth[i].length == tl.length
546 break if depth[i].length == tl.length
547 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
547 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
548 depth.pop
548 depth.pop
549 end
549 end
550 end
550 end
551 if depth.last and depth.last.length == tl.length
551 if depth.last and depth.last.length == tl.length
552 lines[line_id - 1] << '</li>'
552 lines[line_id - 1] << '</li>'
553 end
553 end
554 end
554 end
555 unless depth.last == tl
555 unless depth.last == tl
556 depth << tl
556 depth << tl
557 atts = pba( atts )
557 atts = pba( atts )
558 atts = shelve( atts ) if atts
558 atts = shelve( atts ) if atts
559 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
559 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
560 else
560 else
561 lines[line_id] = "\t\t<li>#{ content }"
561 lines[line_id] = "\t\t<li>#{ content }"
562 end
562 end
563 last_line = line_id
563 last_line = line_id
564
564
565 else
565 else
566 last_line = line_id
566 last_line = line_id
567 end
567 end
568 if line_id - last_line > 1 or line_id == lines.length - 1
568 if line_id - last_line > 1 or line_id == lines.length - 1
569 depth.delete_if do |v|
569 depth.delete_if do |v|
570 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
570 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
571 end
571 end
572 end
572 end
573 end
573 end
574 lines.join( "\n" )
574 lines.join( "\n" )
575 end
575 end
576 end
576 end
577
577
578 CODE_RE = /(\W)
578 CODE_RE = /(\W)
579 @
579 @
580 (?:\|(\w+?)\|)?
580 (?:\|(\w+?)\|)?
581 (.+?)
581 (.+?)
582 @
582 @
583 (?=\W)/x
583 (?=\W)/x
584
584
585 def inline_textile_code( text )
585 def inline_textile_code( text )
586 text.gsub!( CODE_RE ) do |m|
586 text.gsub!( CODE_RE ) do |m|
587 before,lang,code,after = $~[1..4]
587 before,lang,code,after = $~[1..4]
588 lang = " lang=\"#{ lang }\"" if lang
588 lang = " lang=\"#{ lang }\"" if lang
589 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
589 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
590 end
590 end
591 end
591 end
592
592
593 def lT( text )
593 def lT( text )
594 text =~ /\#$/ ? 'o' : 'u'
594 text =~ /\#$/ ? 'o' : 'u'
595 end
595 end
596
596
597 def hard_break( text )
597 def hard_break( text )
598 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
598 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
599 end
599 end
600
600
601 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
601 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
602
602
603 def blocks( text, deep_code = false )
603 def blocks( text, deep_code = false )
604 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
604 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
605 plain = blk !~ /\A[#*> ]/
605 plain = blk !~ /\A[#*> ]/
606
606
607 # skip blocks that are complex HTML
607 # skip blocks that are complex HTML
608 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
608 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
609 blk
609 blk
610 else
610 else
611 # search for indentation levels
611 # search for indentation levels
612 blk.strip!
612 blk.strip!
613 if blk.empty?
613 if blk.empty?
614 blk
614 blk
615 else
615 else
616 code_blk = nil
616 code_blk = nil
617 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
617 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
618 flush_left iblk
618 flush_left iblk
619 blocks iblk, plain
619 blocks iblk, plain
620 iblk.gsub( /^(\S)/, "\t\\1" )
620 iblk.gsub( /^(\S)/, "\t\\1" )
621 if plain
621 if plain
622 code_blk = iblk; ""
622 code_blk = iblk; ""
623 else
623 else
624 iblk
624 iblk
625 end
625 end
626 end
626 end
627
627
628 block_applied = 0
628 block_applied = 0
629 @rules.each do |rule_name|
629 @rules.each do |rule_name|
630 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
630 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
631 end
631 end
632 if block_applied.zero?
632 if block_applied.zero?
633 if deep_code
633 if deep_code
634 blk = "\t<pre><code>#{ blk }</code></pre>"
634 blk = "\t<pre><code>#{ blk }</code></pre>"
635 else
635 else
636 blk = "\t<p>#{ blk }</p>"
636 blk = "\t<p>#{ blk }</p>"
637 end
637 end
638 end
638 end
639 # hard_break blk
639 # hard_break blk
640 blk + "\n#{ code_blk }"
640 blk + "\n#{ code_blk }"
641 end
641 end
642 end
642 end
643
643
644 end.join( "\n\n" ) )
644 end.join( "\n\n" ) )
645 end
645 end
646
646
647 def textile_bq( tag, atts, cite, content )
647 def textile_bq( tag, atts, cite, content )
648 cite, cite_title = check_refs( cite )
648 cite, cite_title = check_refs( cite )
649 cite = " cite=\"#{ cite }\"" if cite
649 cite = " cite=\"#{ cite }\"" if cite
650 atts = shelve( atts ) if atts
650 atts = shelve( atts ) if atts
651 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
651 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
652 end
652 end
653
653
654 def textile_p( tag, atts, cite, content )
654 def textile_p( tag, atts, cite, content )
655 atts = shelve( atts ) if atts
655 atts = shelve( atts ) if atts
656 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
656 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
657 end
657 end
658
658
659 alias textile_h1 textile_p
659 alias textile_h1 textile_p
660 alias textile_h2 textile_p
660 alias textile_h2 textile_p
661 alias textile_h3 textile_p
661 alias textile_h3 textile_p
662 alias textile_h4 textile_p
662 alias textile_h4 textile_p
663 alias textile_h5 textile_p
663 alias textile_h5 textile_p
664 alias textile_h6 textile_p
664 alias textile_h6 textile_p
665
665
666 def textile_fn_( tag, num, atts, cite, content )
666 def textile_fn_( tag, num, atts, cite, content )
667 atts << " id=\"fn#{ num }\""
667 atts << " id=\"fn#{ num }\""
668 content = "<sup>#{ num }</sup> #{ content }"
668 content = "<sup>#{ num }</sup> #{ content }"
669 atts = shelve( atts ) if atts
669 atts = shelve( atts ) if atts
670 "\t<p#{ atts }>#{ content }</p>"
670 "\t<p#{ atts }>#{ content }</p>"
671 end
671 end
672
672
673 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
673 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
674
674
675 def block_textile_prefix( text )
675 def block_textile_prefix( text )
676 if text =~ BLOCK_RE
676 if text =~ BLOCK_RE
677 tag,tagpre,num,atts,cite,content = $~[1..6]
677 tag,tagpre,num,atts,cite,content = $~[1..6]
678 atts = pba( atts )
678 atts = pba( atts )
679
679
680 # pass to prefix handler
680 # pass to prefix handler
681 if respond_to? "textile_#{ tag }", true
681 if respond_to? "textile_#{ tag }", true
682 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
682 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
683 elsif respond_to? "textile_#{ tagpre }_", true
683 elsif respond_to? "textile_#{ tagpre }_", true
684 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
684 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
685 end
685 end
686 end
686 end
687 end
687 end
688
688
689 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
689 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
690 def block_markdown_setext( text )
690 def block_markdown_setext( text )
691 if text =~ SETEXT_RE
691 if text =~ SETEXT_RE
692 tag = if $2 == "="; "h1"; else; "h2"; end
692 tag = if $2 == "="; "h1"; else; "h2"; end
693 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
693 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
694 blocks cont
694 blocks cont
695 text.replace( blk + cont )
695 text.replace( blk + cont )
696 end
696 end
697 end
697 end
698
698
699 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
699 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
700 [ ]*
700 [ ]*
701 (.+?) # $2 = Header text
701 (.+?) # $2 = Header text
702 [ ]*
702 [ ]*
703 \#* # optional closing #'s (not counted)
703 \#* # optional closing #'s (not counted)
704 $/x
704 $/x
705 def block_markdown_atx( text )
705 def block_markdown_atx( text )
706 if text =~ ATX_RE
706 if text =~ ATX_RE
707 tag = "h#{ $1.length }"
707 tag = "h#{ $1.length }"
708 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
708 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
709 blocks cont
709 blocks cont
710 text.replace( blk + cont )
710 text.replace( blk + cont )
711 end
711 end
712 end
712 end
713
713
714 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
714 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
715
715
716 def block_markdown_bq( text )
716 def block_markdown_bq( text )
717 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
717 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
718 blk.gsub!( /^ *> ?/, '' )
718 blk.gsub!( /^ *> ?/, '' )
719 flush_left blk
719 flush_left blk
720 blocks blk
720 blocks blk
721 blk.gsub!( /^(\S)/, "\t\\1" )
721 blk.gsub!( /^(\S)/, "\t\\1" )
722 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
722 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
723 end
723 end
724 end
724 end
725
725
726 MARKDOWN_RULE_RE = /^(#{
726 MARKDOWN_RULE_RE = /^(#{
727 ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
727 ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
728 })$/
728 })$/
729
729
730 def block_markdown_rule( text )
730 def block_markdown_rule( text )
731 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
731 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
732 "<hr />"
732 "<hr />"
733 end
733 end
734 end
734 end
735
735
736 # XXX TODO XXX
736 # XXX TODO XXX
737 def block_markdown_lists( text )
737 def block_markdown_lists( text )
738 end
738 end
739
739
740 def inline_textile_span( text )
740 def inline_textile_span( text )
741 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
741 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
742 text.gsub!( qtag_re ) do |m|
742 text.gsub!( qtag_re ) do |m|
743
743
744 case rtype
744 case rtype
745 when :limit
745 when :limit
746 sta,qtag,atts,cite,content = $~[1..5]
746 sta,qtag,atts,cite,content = $~[1..5]
747 else
747 else
748 qtag,atts,cite,content = $~[1..4]
748 qtag,atts,cite,content = $~[1..4]
749 sta = ''
749 sta = ''
750 end
750 end
751 atts = pba( atts )
751 atts = pba( atts )
752 atts << " cite=\"#{ cite }\"" if cite
752 atts << " cite=\"#{ cite }\"" if cite
753 atts = shelve( atts ) if atts
753 atts = shelve( atts ) if atts
754
754
755 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
755 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
756
756
757 end
757 end
758 end
758 end
759 end
759 end
760
760
761 LINK_RE = /
761 LINK_RE = /
762 ([\s\[{(]|[#{PUNCT}])? # $pre
762 ([\s\[{(]|[#{PUNCT}])? # $pre
763 " # start
763 " # start
764 (#{C}) # $atts
764 (#{C}) # $atts
765 ([^"]+?) # $text
765 ([^"]+?) # $text
766 \s?
766 \s?
767 (?:\(([^)]+?)\)(?="))? # $title
767 (?:\(([^)]+?)\)(?="))? # $title
768 ":
768 ":
769 (\S+?) # $url
769 (\S+?) # $url
770 (\/)? # $slash
770 (\/)? # $slash
771 ([^\w\/;]*?) # $post
771 ([^\w\/;]*?) # $post
772 (?=<|\s|$)
772 (?=<|\s|$)
773 /x
773 /x
774
774
775 def inline_textile_link( text )
775 def inline_textile_link( text )
776 text.gsub!( LINK_RE ) do |m|
776 text.gsub!( LINK_RE ) do |m|
777 pre,atts,text,title,url,slash,post = $~[1..7]
777 pre,atts,text,title,url,slash,post = $~[1..7]
778
778
779 url, url_title = check_refs( url )
779 url, url_title = check_refs( url )
780 title ||= url_title
780 title ||= url_title
781
781
782 atts = pba( atts )
782 atts = pba( atts )
783 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
783 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
784 atts << " title=\"#{ title }\"" if title
784 atts << " title=\"#{ title }\"" if title
785 atts = shelve( atts ) if atts
785 atts = shelve( atts ) if atts
786
786
787 external = (url =~ /^http:\/\//) ? ' class="external"' : ''
787 external = (url =~ /^http:\/\//) ? ' class="external"' : ''
788
788
789 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
789 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
790 end
790 end
791 end
791 end
792
792
793 MARKDOWN_REFLINK_RE = /
793 MARKDOWN_REFLINK_RE = /
794 \[([^\[\]]+)\] # $text
794 \[([^\[\]]+)\] # $text
795 [ ]? # opt. space
795 [ ]? # opt. space
796 (?:\n[ ]*)? # one optional newline followed by spaces
796 (?:\n[ ]*)? # one optional newline followed by spaces
797 \[(.*?)\] # $id
797 \[(.*?)\] # $id
798 /x
798 /x
799
799
800 def inline_markdown_reflink( text )
800 def inline_markdown_reflink( text )
801 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
801 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
802 text, id = $~[1..2]
802 text, id = $~[1..2]
803
803
804 if id.empty?
804 if id.empty?
805 url, title = check_refs( text )
805 url, title = check_refs( text )
806 else
806 else
807 url, title = check_refs( id )
807 url, title = check_refs( id )
808 end
808 end
809
809
810 atts = " href=\"#{ url }\""
810 atts = " href=\"#{ url }\""
811 atts << " title=\"#{ title }\"" if title
811 atts << " title=\"#{ title }\"" if title
812 atts = shelve( atts )
812 atts = shelve( atts )
813
813
814 "<a#{ atts }>#{ text }</a>"
814 "<a#{ atts }>#{ text }</a>"
815 end
815 end
816 end
816 end
817
817
818 MARKDOWN_LINK_RE = /
818 MARKDOWN_LINK_RE = /
819 \[([^\[\]]+)\] # $text
819 \[([^\[\]]+)\] # $text
820 \( # open paren
820 \( # open paren
821 [ \t]* # opt space
821 [ \t]* # opt space
822 <?(.+?)>? # $href
822 <?(.+?)>? # $href
823 [ \t]* # opt space
823 [ \t]* # opt space
824 (?: # whole title
824 (?: # whole title
825 (['"]) # $quote
825 (['"]) # $quote
826 (.*?) # $title
826 (.*?) # $title
827 \3 # matching quote
827 \3 # matching quote
828 )? # title is optional
828 )? # title is optional
829 \)
829 \)
830 /x
830 /x
831
831
832 def inline_markdown_link( text )
832 def inline_markdown_link( text )
833 text.gsub!( MARKDOWN_LINK_RE ) do |m|
833 text.gsub!( MARKDOWN_LINK_RE ) do |m|
834 text, url, quote, title = $~[1..4]
834 text, url, quote, title = $~[1..4]
835
835
836 atts = " href=\"#{ url }\""
836 atts = " href=\"#{ url }\""
837 atts << " title=\"#{ title }\"" if title
837 atts << " title=\"#{ title }\"" if title
838 atts = shelve( atts )
838 atts = shelve( atts )
839
839
840 "<a#{ atts }>#{ text }</a>"
840 "<a#{ atts }>#{ text }</a>"
841 end
841 end
842 end
842 end
843
843
844 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
844 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
845 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
845 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
846
846
847 def refs( text )
847 def refs( text )
848 @rules.each do |rule_name|
848 @rules.each do |rule_name|
849 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
849 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
850 end
850 end
851 end
851 end
852
852
853 def refs_textile( text )
853 def refs_textile( text )
854 text.gsub!( TEXTILE_REFS_RE ) do |m|
854 text.gsub!( TEXTILE_REFS_RE ) do |m|
855 flag, url = $~[2..3]
855 flag, url = $~[2..3]
856 @urlrefs[flag.downcase] = [url, nil]
856 @urlrefs[flag.downcase] = [url, nil]
857 nil
857 nil
858 end
858 end
859 end
859 end
860
860
861 def refs_markdown( text )
861 def refs_markdown( text )
862 text.gsub!( MARKDOWN_REFS_RE ) do |m|
862 text.gsub!( MARKDOWN_REFS_RE ) do |m|
863 flag, url = $~[2..3]
863 flag, url = $~[2..3]
864 title = $~[6]
864 title = $~[6]
865 @urlrefs[flag.downcase] = [url, title]
865 @urlrefs[flag.downcase] = [url, title]
866 nil
866 nil
867 end
867 end
868 end
868 end
869
869
870 def check_refs( text )
870 def check_refs( text )
871 ret = @urlrefs[text.downcase] if text
871 ret = @urlrefs[text.downcase] if text
872 ret || [text, nil]
872 ret || [text, nil]
873 end
873 end
874
874
875 IMAGE_RE = /
875 IMAGE_RE = /
876 (<p>|.|^) # start of line?
876 (<p>|.|^) # start of line?
877 \! # opening
877 \! # opening
878 (\<|\=|\>)? # optional alignment atts
878 (\<|\=|\>)? # optional alignment atts
879 (#{C}) # optional style,class atts
879 (#{C}) # optional style,class atts
880 (?:\. )? # optional dot-space
880 (?:\. )? # optional dot-space
881 ([^\s(!]+?) # presume this is the src
881 ([^\s(!]+?) # presume this is the src
882 \s? # optional space
882 \s? # optional space
883 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
883 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
884 \! # closing
884 \! # closing
885 (?::#{ HYPERLINK })? # optional href
885 (?::#{ HYPERLINK })? # optional href
886 /x
886 /x
887
887
888 def inline_textile_image( text )
888 def inline_textile_image( text )
889 text.gsub!( IMAGE_RE ) do |m|
889 text.gsub!( IMAGE_RE ) do |m|
890 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
890 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
891 atts = pba( atts )
891 atts = pba( atts )
892 atts = " src=\"#{ url }\"#{ atts }"
892 atts = " src=\"#{ url }\"#{ atts }"
893 atts << " title=\"#{ title }\"" if title
893 atts << " title=\"#{ title }\"" if title
894 atts << " alt=\"#{ title }\""
894 atts << " alt=\"#{ title }\""
895 # size = @getimagesize($url);
895 # size = @getimagesize($url);
896 # if($size) $atts.= " $size[3]";
896 # if($size) $atts.= " $size[3]";
897
897
898 href, alt_title = check_refs( href ) if href
898 href, alt_title = check_refs( href ) if href
899 url, url_title = check_refs( url )
899 url, url_title = check_refs( url )
900
900
901 out = ''
901 out = ''
902 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
902 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
903 out << "<img#{ shelve( atts ) } />"
903 out << "<img#{ shelve( atts ) } />"
904 out << "</a>#{ href_a1 }#{ href_a2 }" if href
904 out << "</a>#{ href_a1 }#{ href_a2 }" if href
905
905
906 if algn
906 if algn
907 algn = h_align( algn )
907 algn = h_align( algn )
908 if stln == "<p>"
908 if stln == "<p>"
909 out = "<p style=\"float:#{ algn }\">#{ out }"
909 out = "<p style=\"float:#{ algn }\">#{ out }"
910 else
910 else
911 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
911 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
912 end
912 end
913 else
913 else
914 out = stln + out
914 out = stln + out
915 end
915 end
916
916
917 out
917 out
918 end
918 end
919 end
919 end
920
920
921 def shelve( val )
921 def shelve( val )
922 @shelf << val
922 @shelf << val
923 " :redsh##{ @shelf.length }:"
923 " :redsh##{ @shelf.length }:"
924 end
924 end
925
925
926 def retrieve( text )
926 def retrieve( text )
927 @shelf.each_with_index do |r, i|
927 @shelf.each_with_index do |r, i|
928 text.gsub!( " :redsh##{ i + 1 }:", r )
928 text.gsub!( " :redsh##{ i + 1 }:", r )
929 end
929 end
930 end
930 end
931
931
932 def incoming_entities( text )
932 def incoming_entities( text )
933 ## turn any incoming ampersands into a dummy character for now.
933 ## turn any incoming ampersands into a dummy character for now.
934 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
934 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
935 ## implying an incoming html entity, to be skipped
935 ## implying an incoming html entity, to be skipped
936
936
937 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
937 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
938 end
938 end
939
939
940 def no_textile( text )
940 def no_textile( text )
941 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
941 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
942 '\1<notextile>\2</notextile>\3' )
942 '\1<notextile>\2</notextile>\3' )
943 text.gsub!( /^ *==([^=]+.*?)==/m,
943 text.gsub!( /^ *==([^=]+.*?)==/m,
944 '\1<notextile>\2</notextile>\3' )
944 '\1<notextile>\2</notextile>\3' )
945 end
945 end
946
946
947 def clean_white_space( text )
947 def clean_white_space( text )
948 # normalize line breaks
948 # normalize line breaks
949 text.gsub!( /\r\n/, "\n" )
949 text.gsub!( /\r\n/, "\n" )
950 text.gsub!( /\r/, "\n" )
950 text.gsub!( /\r/, "\n" )
951 text.gsub!( /\t/, ' ' )
951 text.gsub!( /\t/, ' ' )
952 text.gsub!( /^ +$/, '' )
952 text.gsub!( /^ +$/, '' )
953 text.gsub!( /\n{3,}/, "\n\n" )
953 text.gsub!( /\n{3,}/, "\n\n" )
954 text.gsub!( /"$/, "\" " )
954 text.gsub!( /"$/, "\" " )
955
955
956 # if entire document is indented, flush
956 # if entire document is indented, flush
957 # to the left side
957 # to the left side
958 flush_left text
958 flush_left text
959 end
959 end
960
960
961 def flush_left( text )
961 def flush_left( text )
962 indt = 0
962 indt = 0
963 if text =~ /^ /
963 if text =~ /^ /
964 while text !~ /^ {#{indt}}\S/
964 while text !~ /^ {#{indt}}\S/
965 indt += 1
965 indt += 1
966 end unless text.empty?
966 end unless text.empty?
967 if indt.nonzero?
967 if indt.nonzero?
968 text.gsub!( /^ {#{indt}}/, '' )
968 text.gsub!( /^ {#{indt}}/, '' )
969 end
969 end
970 end
970 end
971 end
971 end
972
972
973 def footnote_ref( text )
973 def footnote_ref( text )
974 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
974 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
975 '<sup><a href="#fn\1">\1</a></sup>\2' )
975 '<sup><a href="#fn\1">\1</a></sup>\2' )
976 end
976 end
977
977
978 OFFTAGS = /(code|pre|kbd|notextile)/
978 OFFTAGS = /(code|pre|kbd|notextile)/
979 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
979 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
980 OFFTAG_OPEN = /<#{ OFFTAGS }/
980 OFFTAG_OPEN = /<#{ OFFTAGS }/
981 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
981 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
982 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
982 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
983 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
983 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
984
984
985 def glyphs_textile( text, level = 0 )
985 def glyphs_textile( text, level = 0 )
986 if text !~ HASTAG_MATCH
986 if text !~ HASTAG_MATCH
987 pgl text
987 pgl text
988 footnote_ref text
988 footnote_ref text
989 else
989 else
990 codepre = 0
990 codepre = 0
991 text.gsub!( ALLTAG_MATCH ) do |line|
991 text.gsub!( ALLTAG_MATCH ) do |line|
992 ## matches are off if we're between <code>, <pre> etc.
992 ## matches are off if we're between <code>, <pre> etc.
993 if $1
993 if $1
994 if line =~ OFFTAG_OPEN
994 if line =~ OFFTAG_OPEN
995 codepre += 1
995 codepre += 1
996 elsif line =~ OFFTAG_CLOSE
996 elsif line =~ OFFTAG_CLOSE
997 codepre -= 1
997 codepre -= 1
998 codepre = 0 if codepre < 0
998 codepre = 0 if codepre < 0
999 end
999 end
1000 elsif codepre.zero?
1000 elsif codepre.zero?
1001 glyphs_textile( line, level + 1 )
1001 glyphs_textile( line, level + 1 )
1002 else
1002 else
1003 htmlesc( line, :NoQuotes )
1003 htmlesc( line, :NoQuotes )
1004 end
1004 end
1005 # p [level, codepre, line]
1005 # p [level, codepre, line]
1006
1006
1007 line
1007 line
1008 end
1008 end
1009 end
1009 end
1010 end
1010 end
1011
1011
1012 def rip_offtags( text )
1012 def rip_offtags( text )
1013 if text =~ /<.*>/
1013 if text =~ /<.*>/
1014 ## strip and encode <pre> content
1014 ## strip and encode <pre> content
1015 codepre, used_offtags = 0, {}
1015 codepre, used_offtags = 0, {}
1016 text.gsub!( OFFTAG_MATCH ) do |line|
1016 text.gsub!( OFFTAG_MATCH ) do |line|
1017 if $3
1017 if $3
1018 offtag, aftertag = $4, $5
1018 offtag, aftertag = $4, $5
1019 codepre += 1
1019 codepre += 1
1020 used_offtags[offtag] = true
1020 used_offtags[offtag] = true
1021 if codepre - used_offtags.length > 0
1021 if codepre - used_offtags.length > 0
1022 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1022 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1023 @pre_list.last << line
1023 @pre_list.last << line
1024 line = ""
1024 line = ""
1025 else
1025 else
1026 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1026 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1027 line = "<redpre##{ @pre_list.length }>"
1027 line = "<redpre##{ @pre_list.length }>"
1028 @pre_list << "#{ $3 }#{ aftertag }"
1028 @pre_list << "#{ $3 }#{ aftertag }"
1029 end
1029 end
1030 elsif $1 and codepre > 0
1030 elsif $1 and codepre > 0
1031 if codepre - used_offtags.length > 0
1031 if codepre - used_offtags.length > 0
1032 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1032 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1033 @pre_list.last << line
1033 @pre_list.last << line
1034 line = ""
1034 line = ""
1035 end
1035 end
1036 codepre -= 1 unless codepre.zero?
1036 codepre -= 1 unless codepre.zero?
1037 used_offtags = {} if codepre.zero?
1037 used_offtags = {} if codepre.zero?
1038 end
1038 end
1039 line
1039 line
1040 end
1040 end
1041 end
1041 end
1042 text
1042 text
1043 end
1043 end
1044
1044
1045 def smooth_offtags( text )
1045 def smooth_offtags( text )
1046 unless @pre_list.empty?
1046 unless @pre_list.empty?
1047 ## replace <pre> content
1047 ## replace <pre> content
1048 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1048 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1049 end
1049 end
1050 end
1050 end
1051
1051
1052 def inline( text )
1052 def inline( text )
1053 [/^inline_/, /^glyphs_/].each do |meth_re|
1053 [/^inline_/, /^glyphs_/].each do |meth_re|
1054 @rules.each do |rule_name|
1054 @rules.each do |rule_name|
1055 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1055 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1056 end
1056 end
1057 end
1057 end
1058 end
1058 end
1059
1059
1060 def h_align( text )
1060 def h_align( text )
1061 H_ALGN_VALS[text]
1061 H_ALGN_VALS[text]
1062 end
1062 end
1063
1063
1064 def v_align( text )
1064 def v_align( text )
1065 V_ALGN_VALS[text]
1065 V_ALGN_VALS[text]
1066 end
1066 end
1067
1067
1068 def textile_popup_help( name, windowW, windowH )
1068 def textile_popup_help( name, windowW, windowH )
1069 ' <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 />'
1069 ' <a target="_blank" href="http://hobix.com/textile/#' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
1070 end
1070 end
1071
1071
1072 # HTML cleansing stuff
1072 # HTML cleansing stuff
1073 BASIC_TAGS = {
1073 BASIC_TAGS = {
1074 'a' => ['href', 'title'],
1074 'a' => ['href', 'title'],
1075 'img' => ['src', 'alt', 'title'],
1075 'img' => ['src', 'alt', 'title'],
1076 'br' => [],
1076 'br' => [],
1077 'i' => nil,
1077 'i' => nil,
1078 'u' => nil,
1078 'u' => nil,
1079 'b' => nil,
1079 'b' => nil,
1080 'pre' => nil,
1080 'pre' => nil,
1081 'kbd' => nil,
1081 'kbd' => nil,
1082 'code' => ['lang'],
1082 'code' => ['lang'],
1083 'cite' => nil,
1083 'cite' => nil,
1084 'strong' => nil,
1084 'strong' => nil,
1085 'em' => nil,
1085 'em' => nil,
1086 'ins' => nil,
1086 'ins' => nil,
1087 'sup' => nil,
1087 'sup' => nil,
1088 'sub' => nil,
1088 'sub' => nil,
1089 'del' => nil,
1089 'del' => nil,
1090 'table' => nil,
1090 'table' => nil,
1091 'tr' => nil,
1091 'tr' => nil,
1092 'td' => ['colspan', 'rowspan'],
1092 'td' => ['colspan', 'rowspan'],
1093 'th' => nil,
1093 'th' => nil,
1094 'ol' => nil,
1094 'ol' => nil,
1095 'ul' => nil,
1095 'ul' => nil,
1096 'li' => nil,
1096 'li' => nil,
1097 'p' => nil,
1097 'p' => nil,
1098 'h1' => nil,
1098 'h1' => nil,
1099 'h2' => nil,
1099 'h2' => nil,
1100 'h3' => nil,
1100 'h3' => nil,
1101 'h4' => nil,
1101 'h4' => nil,
1102 'h5' => nil,
1102 'h5' => nil,
1103 'h6' => nil,
1103 'h6' => nil,
1104 'blockquote' => ['cite']
1104 'blockquote' => ['cite']
1105 }
1105 }
1106
1106
1107 def clean_html( text, tags = BASIC_TAGS )
1107 def clean_html( text, tags = BASIC_TAGS )
1108 text.gsub!( /<!\[CDATA\[/, '' )
1108 text.gsub!( /<!\[CDATA\[/, '' )
1109 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1109 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1110 raw = $~
1110 raw = $~
1111 tag = raw[2].downcase
1111 tag = raw[2].downcase
1112 if tags.has_key? tag
1112 if tags.has_key? tag
1113 pcs = [tag]
1113 pcs = [tag]
1114 tags[tag].each do |prop|
1114 tags[tag].each do |prop|
1115 ['"', "'", ''].each do |q|
1115 ['"', "'", ''].each do |q|
1116 q2 = ( q != '' ? q : '\s' )
1116 q2 = ( q != '' ? q : '\s' )
1117 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1117 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1118 attrv = $1
1118 attrv = $1
1119 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1119 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1120 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1120 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1121 break
1121 break
1122 end
1122 end
1123 end
1123 end
1124 end if tags[tag]
1124 end if tags[tag]
1125 "<#{raw[1]}#{pcs.join " "}>"
1125 "<#{raw[1]}#{pcs.join " "}>"
1126 else
1126 else
1127 " "
1127 " "
1128 end
1128 end
1129 end
1129 end
1130 end
1130 end
1131 end
1131 end
1132
1132
@@ -1,72 +1,73
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
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues
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.' => '<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>.',
33 '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>.',
34 '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>',
35 '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>',
36 '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>'
37 }
38 }
38 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) }
39 end
40 end
40
41
41 def test_auto_mailto
42 def test_auto_mailto
42 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>',
43 textilizable('test@foo.bar')
44 textilizable('test@foo.bar')
44 end
45 end
45
46
46 def test_textile_tags
47 def test_textile_tags
47 to_test = {
48 to_test = {
48 # inline images
49 # inline images
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
50 '!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>',
51 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
51 # textile links
52 # textile links
52 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
53 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
53 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
54 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
54 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
55 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
55 }
56 }
56 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
57 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
57 end
58 end
58
59
59 def test_redmine_links
60 def test_redmine_links
60 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
61 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
61 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
62 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
62 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 1},
63 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 1},
63 :class => 'changeset', :title => 'My very first commit')
64 :class => 'changeset', :title => 'My very first commit')
64
65
65 to_test = {
66 to_test = {
66 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
67 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
67 'r1' => changeset_link
68 'r1' => changeset_link
68 }
69 }
69 @project = Project.find(1)
70 @project = Project.find(1)
70 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
71 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
71 end
72 end
72 end
73 end
General Comments 0
You need to be logged in to leave comments. Login now