More goodness to lib/diff.rb!

This commit is contained in:
Alexey Verkhovsky 2006-01-23 06:57:19 +00:00
parent 3285737917
commit 01c9636ffd
4 changed files with 54 additions and 20 deletions

View file

@ -1,10 +1,11 @@
* TRUNK: * TRUNK:
SQL-based backend (ActiveRecord) SQL-based backend (ActiveRecord)
File uploads (finally) File uploads (finally)
Upgraded to Rails 0.14.3 (also know as 1.0 RC 4) Upgraded to Rails 1.0.0
Replaced internal link generator with routing Replaced internal link generator with routing
Fixed --daemon option Fixed --daemon option
Removed Rubygem and native OS X distributions Removed Rubygem and native OS X distributions
Improved HTML diff
More accurate "See Changes" More accurate "See Changes"
* 0.10.2: * 0.10.2:

View file

@ -23,7 +23,7 @@ module HTMLDiff
def build def build
split_inputs_to_words split_inputs_to_words
index_new_words index_new_words
operations.each {|opcode| perform_operation(opcode)} operations.each {|op| perform_operation(op) }
return @content.join return @content.join
end end
@ -40,7 +40,13 @@ module HTMLDiff
def operations def operations
position_in_old = position_in_new = 0 position_in_old = position_in_new = 0
operations = [] operations = []
matching_blocks.each do |match|
matches = matching_blocks
# an empty match at the end forces the loop below to handle the unmatched tails
# I'm sure it can be done more gracefully, but not at 23:52
matches << Match.new(@old_words.length, @new_words.length, 0)
matches.each_with_index do |match, i|
match_starts_at_current_position_in_old = (position_in_old == match.start_in_old) match_starts_at_current_position_in_old = (position_in_old == match.start_in_old)
match_starts_at_current_position_in_new = (position_in_new == match.start_in_new) match_starts_at_current_position_in_new = (position_in_new == match.start_in_new)
@ -72,6 +78,7 @@ module HTMLDiff
position_in_old = match.end_in_old position_in_old = match.end_in_old
position_in_new = match.end_in_new position_in_new = match.end_in_new
end end
operations operations
end end
@ -167,22 +174,40 @@ module HTMLDiff
opening_tag?(item) or closing_tag?(item) opening_tag?(item) or closing_tag?(item)
end end
# Tries to enclose diff tags (<ins> or <del>) within <p> tags def extract_consecutive_words(words, &condition)
# As a result the diff tags should be the "most inside" possible. index_of_first_tag = nil
def insert_tag(tagname, cssclass, words) words.each_with_index do |word, i|
unless words.any? { |word| tag?(word) } if !condition.call(word)
@content << wrap_text(words.join, tagname, cssclass) index_of_first_tag = i
break
end
end
if index_of_first_tag
return words.slice!(0...index_of_first_tag)
else else
return words.slice!(0..words.length)
end
end
# This method encloses words within a specified tag (ins or del), and adds this into @content,
# with a twist: if there are words contain tags, it actually creates multiple ins or del,
# so that they don't include any ins or del. This handles cases like
# old: '<p>a</p>'
# new: '<p>ab</p><p>c</b>'
# diff result: '<p>a<ins>b</ins></p><p><ins>c</ins></p>'
# this still doesn't guarantee valid HTML (hint: think about diffing a text containing ins or
# del tags), but handles correctly more cases than earlier version.
#
# PS: Spare a thought for people who write HTML browsers. They live in this ... every day.
def insert_tag(tagname, cssclass, words)
loop do loop do
break if words.empty? break if words.empty?
@content << words and break if words.all? { |word| tag?(word) } non_tags = extract_consecutive_words(words) { |word| not tag?(word) }
# We are outside of a diff tag @content << wrap_text(non_tags.join, tagname, cssclass) unless non_tags.empty?
@content << words.shift while not words.empty? and tag?(words.first)
@content << %(<#{tagname} class="#{cssclass}">) break if words.empty?
# We are inside a diff tag @content += extract_consecutive_words(words) { |word| tag?(word) }
@content << words.shift until words.empty? or tag?(words.first)
@content << %(</#{tagname}>)
end
end end
end end

View file

@ -4,6 +4,8 @@ require 'diff'
class PageRenderer class PageRenderer
include HTMLDiff
def self.setup_url_generator(url_generator) def self.setup_url_generator(url_generator)
@@url_generator = url_generator @@url_generator = url_generator
end end
@ -40,7 +42,7 @@ class PageRenderer
previous_revision = @revision.page.previous_revision(@revision) previous_revision = @revision.page.previous_revision(@revision)
if previous_revision if previous_revision
rendered_previous_revision = WikiContent.new(previous_revision, @@url_generator).render! rendered_previous_revision = WikiContent.new(previous_revision, @@url_generator).render!
HTMLDiff.diff(rendered_previous_revision, display_content) diff(rendered_previous_revision, display_content)
else else
display_content display_content
end end

View file

@ -90,4 +90,10 @@ class DiffTest < Test::Unit::TestCase
diff(a, b)) diff(a, b))
end end
def test_html_diff_with_tags
a = ""
b = "<div>foo</div>"
assert_equal '<div><ins class="diffins">foo</ins></div>', diff(a, b)
end
end end