OSDN Git Service

7e20c7bfcb1c19fc67d0bdef9a1d51d5e859d1a1
[redminele/redminele.git] / redmine / lib / redmine / wiki_formatting / textile / formatter.rb
1 # Redmine - project management software
2 # Copyright (C) 2006-2008  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 'redcloth3'
19 require 'coderay'
20
21 module Redmine
22   module WikiFormatting
23     module Textile
24       class Formatter < RedCloth3
25         
26         # auto_link rule after textile rules so that it doesn't break !image_url! tags
27         RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros]
28         
29         def initialize(*args)
30           super
31           self.hard_breaks=true
32           self.no_span_caps=true
33           self.filter_styles=true
34         end
35         
36         def to_html(*rules, &block)
37           @toc = []
38           @macros_runner = block
39           super(*RULES).to_s
40         end
41   
42       private
43   
44         # Patch for RedCloth.  Fixed in RedCloth r128 but _why hasn't released it yet.
45         # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
46         def hard_break( text ) 
47           text.gsub!( /(.)\n(?!\n|\Z|>| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
48         end
49         
50         # Patch to add code highlighting support to RedCloth
51         def smooth_offtags( text )
52           unless @pre_list.empty?
53             ## replace <pre> content
54             text.gsub!(/<redpre#(\d+)>/) do
55               content = @pre_list[$1.to_i]
56               if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
57                 content = "<code class=\"#{$1} CodeRay\">" + 
58                   CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline)
59               end
60               content
61             end
62           end
63         end
64         
65         # Patch to add 'table of content' support to RedCloth
66         def textile_p_withtoc(tag, atts, cite, content)
67           # removes wiki links from the item
68           toc_item = content.gsub(/(\[\[([^\]\|]*)(\|([^\]]*))?\]\])/) { $4 || $2 }
69           # removes styles
70           # eg. %{color:red}Triggers% => Triggers
71           toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
72           
73           # replaces non word caracters by dashes
74           anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
75   
76           unless anchor.blank?
77             if tag =~ /^h(\d)$/
78               @toc << [$1.to_i, anchor, toc_item]
79             end
80             atts << " id=\"#{anchor}\""
81             content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a>"
82           end
83           textile_p(tag, atts, cite, content)
84         end
85   
86         alias :textile_h1 :textile_p_withtoc
87         alias :textile_h2 :textile_p_withtoc
88         alias :textile_h3 :textile_p_withtoc
89         
90         def inline_toc(text)
91           text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
92             div_class = 'toc'
93             div_class << ' right' if $1 == '>'
94             div_class << ' left' if $1 == '<'
95             out = "<ul class=\"#{div_class}\">"
96             @toc.each do |heading|
97               level, anchor, toc_item = heading
98               out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n"
99             end
100             out << '</ul>'
101             out
102           end
103         end
104         
105         MACROS_RE = /
106                       (!)?                        # escaping
107                       (
108                       \{\{                        # opening tag
109                       ([\w]+)                     # macro name
110                       (\(([^\}]*)\))?             # optional arguments
111                       \}\}                        # closing tag
112                       )
113                     /x unless const_defined?(:MACROS_RE)
114         
115         def inline_macros(text)
116           text.gsub!(MACROS_RE) do
117             esc, all, macro = $1, $2, $3.downcase
118             args = ($5 || '').split(',').each(&:strip)
119             if esc.nil?
120               begin
121                 @macros_runner.call(macro, args)
122               rescue => e
123                 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
124               end || all
125             else
126               all
127             end
128           end
129         end
130         
131         AUTO_LINK_RE = %r{
132                         (                          # leading text
133                           <\w+.*?>|                # leading HTML tag, or
134                           [^=<>!:'"/]|             # leading punctuation, or 
135                           ^                        # beginning of line
136                         )
137                         (
138                           (?:https?://)|           # protocol spec, or
139                           (?:s?ftps?://)|
140                           (?:www\.)                # www.*
141                         )
142                         (
143                           (\S+?)                   # url
144                           (\/)?                    # slash
145                         )
146                         ([^\w\=\/;\(\)]*?)               # post
147                         (?=<|\s|$)
148                        }x unless const_defined?(:AUTO_LINK_RE)
149   
150         # Turns all urls into clickable links (code from Rails).
151         def inline_auto_link(text)
152           text.gsub!(AUTO_LINK_RE) do
153             all, leading, proto, url, post = $&, $1, $2, $3, $6
154             if leading =~ /<a\s/i || leading =~ /![<>=]?/
155               # don't replace URL's that are already linked
156               # and URL's prefixed with ! !> !< != (textile images)
157               all
158             else
159               # Idea below : an URL with unbalanced parethesis and
160               # ending by ')' is put into external parenthesis
161               if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
162                 url=url[0..-2] # discard closing parenth from url
163                 post = ")"+post # add closing parenth to post
164               end
165               %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
166             end
167           end
168         end
169   
170         # Turns all email addresses into clickable links (code from Rails).
171         def inline_auto_mailto(text)
172           text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
173             mail = $1
174             if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
175               mail
176             else
177               %{<a href="mailto:#{mail}" class="email">#{mail}</a>}
178             end
179           end
180         end
181       end
182     end
183   end
184 end