formatter.rb

Modified formatter.rb file - Trond Lossius, 08/23/2009 08:33 pm

Download (7.7 kB)

 
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 />\n" ) 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(/(\[\[|\]\])/, '')
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
        
106
        # %CHANGED: The following has been modified to work with the latex plugin [TL]:
107
        
108
        # From this:
109
        
110
        #MACROS_RE = /
111
        #              (!)?                        # escaping
112
        #              (
113
        #              \{\{                        # opening tag
114
        #              ([\w]+)                     # macro name
115
        #              (\(([^\}]*)\))?             # optional arguments
116
        #              \}\}                        # closing tag
117
        #              )
118
        #            /x unless const_defined?(:MACROS_RE)
119
        
120
        # to this:
121
                
122
        MACROS_RE = /
123
                      (!)?                        # escaping
124
                      (
125
                      \{\{                        # opening tag
126
                      ([\w]+)                     # macro name
127
                      (\((.*?)\))?                # optional arguments
128
                      \}\}                        # closing tag
129
                      )
130
                    /mx unless const_defined?(:MACROS_RE)                   
131
                               
132
        
133
        def inline_macros(text)
134
          text.gsub!(MACROS_RE) do
135
            esc, all, macro = $1, $2, $3.downcase
136
            args = ($5 || '').split(',').each(&:strip)
137
            if esc.nil?
138
              begin
139
                @macros_runner.call(macro, args)
140
              rescue => e
141
                "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
142
              end || all
143
            else
144
              all
145
            end
146
          end
147
        end
148
        
149
        AUTO_LINK_RE = %r{
150
                        (                          # leading text
151
                          <\w+.*?>|                # leading HTML tag, or
152
                          [^=<>!:'"/]|             # leading punctuation, or 
153
                          ^                        # beginning of line
154
                        )
155
                        (
156
                          (?:https?://)|           # protocol spec, or
157
                          (?:s?ftps?://)|
158
                          (?:www\.)                # www.*
159
                        )
160
                        (
161
                          (\S+?)                   # url
162
                          (\/)?                    # slash
163
                        )
164
                        ([^\w\=\/;\(\)]*?)               # post
165
                        (?=<|\s|$)
166
                       }x unless const_defined?(:AUTO_LINK_RE)
167
  
168
        # Turns all urls into clickable links (code from Rails).
169
        def inline_auto_link(text)
170
          text.gsub!(AUTO_LINK_RE) do
171
            all, leading, proto, url, post = $&, $1, $2, $3, $6
172
            if leading =~ /<a\s/i || leading =~ /![<>=]?/
173
              # don't replace URL's that are already linked
174
              # and URL's prefixed with ! !> !< != (textile images)
175
              all
176
            else
177
              # Idea below : an URL with unbalanced parethesis and
178
              # ending by ')' is put into external parenthesis
179
              if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
180
                url=url[0..-2] # discard closing parenth from url
181
                post = ")"+post # add closing parenth to post
182
              end
183
              %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
184
            end
185
          end
186
        end
187
  
188
        # Turns all email addresses into clickable links (code from Rails).
189
        def inline_auto_mailto(text)
190
          text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
191
            mail = $1
192
            if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
193
              mail
194
            else
195
              %{<a href="mailto:#{mail}" class="email">#{mail}</a>}
196
            end
197
          end
198
        end
199
      end
200
    end
201
  end
202
end