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