1 # Redmine - project management software
2 # Copyright (C) 2006-2011 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 'digest/md5'
20
21 module Redmine
22 module WikiFormatting
23 module Textile
24 class Formatter < RedCloth3
25 include ActionView::Helpers::TagHelper
26 include Redmine::WikiFormatting::LinksHelper
27
28 alias :inline_auto_link :auto_link!
29 alias :inline_auto_mailto :auto_mailto!
30
31 # auto_link rule after textile rules so that it doesn't break !image_url! tags
32 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto]
33
34 def initialize(*args)
35 super
36 self.hard_breaks=true
37 self.no_span_caps=true
38 self.filter_styles=false
39 end
40
41 def to_html(*rules)
42 @toc = []
43 super(*RULES).to_s
44 end
45
46 def get_section(index)
47 section = extract_sections(index)[1]
48 hash = Digest::MD5.hexdigest(section)
49 return section, hash
50 end
51
52 def update_section(index, update, hash=nil)
53 t = extract_sections(index)
54 if hash.present? && hash != Digest::MD5.hexdigest(t[1])
55 raise Redmine::WikiFormatting::StaleSectionError
56 end
57 t[1] = update unless t[1].blank?
58 t.reject(&:blank?).join "\n\n"
59 end
60
61 def extract_sections(index)
62 @pre_list = []
63 text = self.dup
64 rip_offtags text, false, false
65 before = ''
66 s = ''
67 after = ''
68 i = 0
69 l = 1
70 started = false
71 ended = false
72 text.scan(/(((?:.*?)(\A|\r?\n\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))? (.*?)$)|.*)/m).each do |all, content, lf, heading, level|
73 if heading.nil?
74 if ended
75 after << all
76 elsif started
77 s << all
78 else
79 before << all
80 end
81 break
82 end
83 i += 1
84 if ended
85 after << all
86 elsif i == index
87 l = level.to_i
88 before << content
89 s << heading
90 started = true
91 elsif i > index
92 s << content
93 if level.to_i > l
94 s << heading
95 else
96 after << heading
97 ended = true
98 end
99 else
100 before << all
101 end
102 end
103 sections = [before.strip, s.strip, after.strip]
104 sections.each {|section| smooth_offtags_without_code_highlighting section}
105 sections
106 end
107
108 private
109
110 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
111 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
112 def hard_break( text )
113 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
114 end
115
116 alias :smooth_offtags_without_code_highlighting :smooth_offtags
117 # Patch to add code highlighting support to RedCloth
118 def smooth_offtags( text )
119 unless @pre_list.empty?
120 ## replace <pre> content
121 text.gsub!(/<redpre#(\d+)>/) do
122 content = @pre_list[$1.to_i]
123 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
124 content = "<code class=\"#{$1} syntaxhl\">" +
125 Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
126 end
127 content
128 end
129 end
130 end
131 end
132 end
133 end
134 end