Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 368
- Log:
Log for Changeset #367 is inaccurate. This change patches around invalid
XHTML markup written directly into pages, which would cause exceptions if
used as part of a diff. The post-Textile-processing data is run through
Hpricot to clean it up and a warning is included in the diff. If further
exceptions occur, the user is told that the diff can't be shown, rather
than just given a 500 error.This new version of the file just tidies up the location of the 'require'
for Hpricot.
- Author:
- rool
- Date:
- Sat Mar 19 22:10:54 +0000 2011
- Size:
- 8789 Bytes
1 | require 'hpricot' |
2 | require 'xhtmldiff' |
3 | |
4 | # Temporary class containing all rendering stuff from a Revision |
5 | # I want to shift all rendering loguc to the controller eventually |
6 | |
7 | class PageRenderer |
8 | |
9 | def self.setup_url_generator(url_generator) |
10 | @@url_generator = url_generator |
11 | end |
12 | |
13 | def self.teardown_url_generator |
14 | @@url_generator = nil |
15 | end |
16 | |
17 | attr_reader :revision |
18 | |
19 | def initialize(revision = nil) |
20 | self.revision = revision |
21 | end |
22 | |
23 | def revision=(r) |
24 | @revision = r |
25 | @display_content = @display_published = @wiki_words_cache = @wiki_includes_cache = |
26 | @wiki_references_cache = nil |
27 | end |
28 | |
29 | def display_content(update_references = false) |
30 | @display_content ||= render(:update_references => update_references) |
31 | end |
32 | |
33 | def display_content_for_export |
34 | render :mode => :export |
35 | end |
36 | |
37 | def display_published |
38 | @display_published ||= render(:mode => :publish) |
39 | end |
40 | |
41 | def display_diff |
42 | previous_revision = @revision.page.previous_revision(@revision) |
43 | if previous_revision |
44 | |
45 | # Pass one: Normal Instiki behaviour |
46 | |
47 | previous_content = "<div>" + WikiContent.new(previous_revision, @@url_generator).render!.to_s + "</div>" |
48 | current_content = "<div>" + display_content.to_s + "</div>" |
49 | diff_doc = REXML::Document.new |
50 | div = REXML::Element.new('div', nil, {:respect_whitespace =>:all}) |
51 | div.attributes['class'] = 'xhtmldiff_wrapper' |
52 | diff_doc << div |
53 | |
54 | begin |
55 | |
56 | # Allow exceptions here. We expect them from REXML. |
57 | |
58 | parsed_previous_revision = REXML::HashableElementDelegator.new( |
59 | REXML::XPath.first(REXML::Document.new(previous_content), '/div')) |
60 | parsed_display_content = REXML::HashableElementDelegator.new( |
61 | REXML::XPath.first(REXML::Document.new(current_content), '/div')) |
62 | |
63 | hd = XHTMLDiff.new(div) |
64 | Diff::LCS.traverse_balanced(parsed_previous_revision, parsed_display_content, hd) |
65 | |
66 | diffs = '' |
67 | diff_doc.write(diffs, -1, true, true) |
68 | diffs.gsub(/\A<div class='xhtmldiff_wrapper'>(.*)<\/div>\Z/m, '\1').html_safe |
69 | |
70 | rescue |
71 | |
72 | # To reach here, REXML probably complained. Users probably wrote HTML |
73 | # in the page, but the markup was invalid XHTML - forgetting to close |
74 | # "<li>" tags is a common example. So, try the diff again, this time |
75 | # using Hpricot as a wrapper around the post-processed content to try |
76 | # and sanitise the markup at the possible expense of correctness. |
77 | |
78 | begin |
79 | |
80 | previous_content = "<div>" + Hpricot(WikiContent.new(previous_revision, @@url_generator).render!.to_s).to_s + "</div>" |
81 | current_content = "<div>" + Hpricot(display_content.to_s).to_s + "</div>" |
82 | |
83 | parsed_previous_revision = REXML::HashableElementDelegator.new( |
84 | REXML::XPath.first(REXML::Document.new(previous_content), '/div')) |
85 | parsed_display_content = REXML::HashableElementDelegator.new( |
86 | REXML::XPath.first(REXML::Document.new(current_content), '/div')) |
87 | |
88 | hd = XHTMLDiff.new(div) |
89 | Diff::LCS.traverse_balanced(parsed_previous_revision, parsed_display_content, hd) |
90 | |
91 | diffs = "<p><ins class=\"diffins\"><strong>Warning: The current and/or previous revision contained markup errors which prevented proper diff analysis. A guess at markup correction has been made but the diff may not be a truly accurate reflection of the real changes.</strong></ins></p>" |
92 | diff_doc.write(diffs, -1, true, true) |
93 | diffs.gsub(/\A<div class='xhtmldiff_wrapper'>(.*)<\/div>\Z/m, '\1').html_safe |
94 | |
95 | rescue |
96 | |
97 | # Further exceptions indicate a lost cause! |
98 | "<p><ins class=\"diffins\"><strong>The current and/or previous revision contain invalid markup which is too broken for the diff parser to understand.</strong></ins></p>".html_safe |
99 | |
100 | end |
101 | end |
102 | else |
103 | display_content |
104 | end |
105 | end |
106 | |
107 | attr :s5_theme |
108 | def s5_theme=(s) |
109 | @s5_theme = s |
110 | end |
111 | |
112 | # Renders an S5 slideshow |
113 | def display_s5 |
114 | @display_s5 ||= render(:mode => :s5, |
115 | :engine_opts => {:author => @author, :title => @plain_name}, |
116 | :renderer => self) |
117 | end |
118 | |
119 | # Returns an array of all the WikiIncludes present in the content of this revision. |
120 | def wiki_includes |
121 | unless @wiki_includes_cache |
122 | chunks = display_content.find_chunks(Include) |
123 | @wiki_includes_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq |
124 | end |
125 | @wiki_includes_cache |
126 | end |
127 | |
128 | # Returns an array of all the WikiReferences present in the content of this revision. |
129 | def wiki_references |
130 | unless @wiki_references_cache |
131 | chunks = display_content.find_chunks(WikiChunk::WikiReference) |
132 | @wiki_references_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq |
133 | end |
134 | @wiki_references_cache |
135 | end |
136 | |
137 | # Returns an array of all the WikiWords present in the content of this revision. |
138 | def wiki_words |
139 | @wiki_words_cache ||= find_wiki_words(display_content) |
140 | end |
141 | |
142 | def find_wiki_words(rendering_result) |
143 | the_wiki_words = wiki_links(rendering_result) |
144 | # Exclude backslash-escaped wiki words, such as \WikiWord, as well as links to files |
145 | # and pictures, such as [[foo.txt:file]] or [[foo.jpg:pic]] |
146 | the_wiki_words.delete_if { |link| link.escaped? or [:pic, :file, :audio, :video, :delete].include?(link.link_type) } |
147 | # convert to the list of unique page names |
148 | the_wiki_words.map { |link| ( link.page_name ) }.uniq |
149 | end |
150 | |
151 | # Returns an array of all the WikiWords present in the content of this revision. |
152 | def wiki_files |
153 | @wiki_files_cache ||= find_wiki_files(display_content) |
154 | end |
155 | |
156 | def find_wiki_files(rendering_result) |
157 | the_wiki_files = wiki_links(rendering_result) |
158 | the_wiki_files.delete_if { |link| ![:pic, :file, :audio, :video].include?(link.link_type) } |
159 | the_wiki_files.map { |link| ( link.page_name ) }.uniq |
160 | end |
161 | |
162 | def wiki_links(rendering_result) |
163 | rendering_result.find_chunks(WikiChunk::WikiLink) |
164 | end |
165 | |
166 | # Returns an array of all the WikiWords present in the content of this revision. |
167 | # that already exists as a page in the web. |
168 | def existing_pages |
169 | wiki_words.select { |wiki_word| @revision.page.web.page(wiki_word) } |
170 | end |
171 | |
172 | # Returns an array of all the WikiWords present in the content of this revision |
173 | # that *doesn't* already exists as a page in the web. |
174 | def unexisting_pages |
175 | wiki_words - existing_pages |
176 | end |
177 | |
178 | private |
179 | |
180 | def render(options = {}) |
181 | rendering_result = WikiContent.new(@revision, @@url_generator, options).render! |
182 | update_references(rendering_result) if options[:update_references] |
183 | rendering_result |
184 | end |
185 | |
186 | def update_references(rendering_result) |
187 | WikiReference.delete_all ['page_id = ?', @revision.page_id] |
188 | |
189 | references = @revision.page.wiki_references |
190 | |
191 | wiki_words = find_wiki_words(rendering_result) |
192 | # TODO it may be desirable to save links to files and pictures as WikiReference objects |
193 | # present version doesn't do it |
194 | |
195 | wiki_words.each do |referenced_name| |
196 | # Links to self are always considered linked |
197 | if referenced_name == @revision.page.name |
198 | link_type = WikiReference::LINKED_PAGE |
199 | else |
200 | link_type = WikiReference.link_type(@revision.page.web, referenced_name) |
201 | end |
202 | references.build :referenced_name => referenced_name, :link_type => link_type |
203 | end |
204 | |
205 | wiki_files = find_wiki_files(rendering_result) |
206 | wiki_files.each do |referenced_name| |
207 | references.build :referenced_name => referenced_name, :link_type => WikiReference::FILE |
208 | end |
209 | |
210 | include_chunks = rendering_result.find_chunks(Include) |
211 | includes = include_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq |
212 | includes.each do |included_page_name| |
213 | references.build :referenced_name => included_page_name, |
214 | :link_type => WikiReference::INCLUDED_PAGE |
215 | end |
216 | |
217 | redirect_chunks = rendering_result.find_chunks(Redirect) |
218 | redirects = redirect_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq |
219 | redirects.each do |redirected_page_name| |
220 | references.build :referenced_name => redirected_page_name, |
221 | :link_type => WikiReference::REDIRECTED_PAGE |
222 | end |
223 | |
224 | # ugly hack: store these in a thread-local variable, so that the cache-sweeper has access to it. |
225 | Thread.current[:page_redirects] ? |
226 | Thread.current[:page_redirects].update({ @revision.page => redirects}) : |
227 | Thread.current[:page_redirects] = { @revision.page => redirects} |
228 | |
229 | categories = rendering_result.find_chunks(Category).map { |cat| cat.list }.flatten |
230 | categories.each do |category| |
231 | references.build :referenced_name => category, :link_type => WikiReference::CATEGORY |
232 | end |
233 | end |
234 | end |