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