1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
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.
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.
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.
24 class Formatter < RedCloth3
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]
32 self.no_span_caps=true
33 self.filter_styles=true
36 def to_html(*rules, &block)
38 @macros_runner = block
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
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)
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 }
70 # eg. %{color:red}Triggers% => Triggers
71 toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
73 # replaces non word caracters by dashes
74 anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
78 @toc << [$1.to_i, anchor, toc_item]
80 atts << " id=\"#{anchor}\""
81 content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a>"
83 textile_p(tag, atts, cite, content)
86 alias :textile_h1 :textile_p_withtoc
87 alias :textile_h2 :textile_p_withtoc
88 alias :textile_h3 :textile_p_withtoc
91 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
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"
110 (\(([^\}]*)\))? # optional arguments
113 /x unless const_defined?(:MACROS_RE)
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)
121 @macros_runner.call(macro, args)
123 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
133 <\w+.*?>| # leading HTML tag, or
134 [^=<>!:'"/]| # leading punctuation, or
135 ^ # beginning of line
138 (?:https?://)| # protocol spec, or
146 ([^\w\=\/;\(\)]*?) # post
148 }x unless const_defined?(:AUTO_LINK_RE)
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)
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
165 %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
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
174 if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
177 %{<a href="mailto:#{mail}" class="email">#{mail}</a>}