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