Class: Goat::DOMDiff

Inherits:
Object show all
Defined in:
lib/goat.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(old, new, id) ⇒ DOMDiff

Returns a new instance of DOMDiff.



872
873
874
875
876
877
# File 'lib/goat.rb', line 872

def initialize(old, new, id)
  @old = old
  @new = new
  @id = id
  @diffs = []
end

Class Method Details

.diff(old, new, id) ⇒ Object



868
869
870
# File 'lib/goat.rb', line 868

def self.diff(old, new, id)
  self.new(old, new, id).diff
end

Instance Method Details

#added(new, par, pos = nil) ⇒ Object



917
# File 'lib/goat.rb', line 917

def added(new, par, pos=nil); [:add, new, par, pos]; end

#array_desc(old, new, par) ⇒ Object



949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
# File 'lib/goat.rb', line 949

def array_desc(old, new, par)
  #$stderr.puts "array_desc #{old.inspect} #{new.inspect} #{par.inspect}"

  chgs = Diff::LCS.diff(old, new).map do |diff|
    #$stderr.puts "diff: #{diff.inspect}"
    if is_replacement?(diff[0], diff[1])
      dold, dnew = old_and_new(diff[0], diff[1])
      old = dold.element
      new = dnew.element
      if dom_node?(old) && dom_node?(new) && tag(old) == tag(new) && compare_attrs(old, new)
        localized_change(dold, dnew, par, desc(body(old), body(new), domid(old)))
      elsif old.is_a?(Array) && new.is_a?(Array) #&& old.all?{|x| !dom_node?(x)} && new.all?{|x| !dom_node?(x)}
        array_desc(old, new, par)
      else
        [added(new, par, diff[1].position), removed(old, par, diff[0].position)]
      end
    else
      if diff.size == 1
        diff = diff[0]
        if diff.action == '+'
          [added(diff.element, par, diff.position)]
        elsif diff.action == '-'
          [removed(diff.element, par, diff.position)]
        else
          raise "Don't understand diff in a bad way"
        end
      else
        $stderr.puts "Don't understand diff"
        #$stderr.puts "Diff failed"
        raise JustRerenderError
      end
    end
  end

  chgs.flatten(1)
end

#attrs(node) ⇒ Object



924
# File 'lib/goat.rb', line 924

def attrs(node); node[1] if node[1].is_a?(Hash); end

#body(node) ⇒ Object



925
# File 'lib/goat.rb', line 925

def body(node); node[1].is_a?(Hash) ? node[2..-1] : node[1..-1]; end

#compare_attrs(a, b) ⇒ Object



986
987
988
989
990
991
992
993
994
995
# File 'lib/goat.rb', line 986

def compare_attrs(a, b)
  if a.is_a?(Hash) && b.is_a?(Hash)
    a_, b_ = a.clone, b.clone
    a.select{|k,v| a_.delete(k) if v =~ /^dom_/}
    b.select{|k,v| b_.delete(k) if v =~ /^dom_/}
    a_ == b_
  else
    false
  end
end

#desc(old, new, par) ⇒ Object



1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
# File 'lib/goat.rb', line 1015

def desc(old, new, par)
  #$stderr.puts "desc #{old.inspect} #{new.inspect} #{par.inspect}"
  #$stderr.puts "desc #{self.class.name} #{par.inspect}"

  if old.class != new.class
    [added(new, par), removed(old, par)]
  elsif old.is_a?(Array)
    dom_node?(old) ? node_desc(old, new, domid(old)) : array_desc(old, new, par)
  elsif old.is_a?(String)
    if old != new
      [added(new, par), removed(old, par)]
    end
  else
    raise TypeError.new("Unknown object in the DOM: #{old.class} #{new.class}")
  end
end

#diffObject



879
880
881
# File 'lib/goat.rb', line 879

def diff
  nested_application(minimized_changes(desc(@old, @new, @id)))
end

#dom_node?(node) ⇒ Boolean

Returns:

  • (Boolean)


920
921
922
# File 'lib/goat.rb', line 920

def dom_node?(node)
  node.is_a?(Array) && node.first.is_a?(Symbol)
end

#domid(node) ⇒ Object



926
# File 'lib/goat.rb', line 926

def domid(node); attrs(node) ? attrs(node)[:id] : nil; end

#is_replacement?(d1, d2) ⇒ Boolean

Returns:

  • (Boolean)


939
940
941
942
943
# File 'lib/goat.rb', line 939

def is_replacement?(d1, d2)
  d1 && d2 && (
    (d1.action == '+' && d2.action == '-') ||
    (d1.action == '-' && d2.action == '+'))
end

#localized_change(dold, dnew, par, changes) ⇒ Object



928
929
930
931
932
933
934
935
936
937
# File 'lib/goat.rb', line 928

def localized_change(dold, dnew, par, changes)
  old = dold.element
  new = dnew.element
  #$stderr.puts "changes: #{changes.inspect} / #{old.inspect} / #{par.inspect}"
  if changes.all?{|ch| ch[2].nil?} && par
    [added(new, par, dold.position), removed(old, par, dnew.position)]
  else
    changes
  end
end

#minimized_changes(ch) ⇒ Object



899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
# File 'lib/goat.rb', line 899

def minimized_changes(ch)
  new = []
  merged = Set.new
  ch.each do |c|
    if c[0] == :rem
      if n = ch.detect{|x| x[0] == :add && x[2] == c[2] && x[3] == c[3]}
        new << [:rep, *n[1..3]]
        merged << n
      else
        new << c
      end
    else
      new << c
    end
  end
  new.reject{|c| merged.include?(c)}
end

#nested_application(ch) ⇒ Object



883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
# File 'lib/goat.rb', line 883

def nested_application(ch)
  applied = Set.new

  ch.sort_by{|x| x[3]}.each do |c|
    if (c[0] == :rem || c[0] == :add) && !applied.include?(c)
      if n = ch.detect{|x| x != c && x[2] == c[2] && x[3] >= c[3]}
        n[3] += (c[0] == :rem) ? -1 : 1
      end
    end

    applied << c
  end

  ch
end

#node_desc(old, new, par) ⇒ Object



997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
# File 'lib/goat.rb', line 997

def node_desc(old, new, par)
  #$stderr.puts "node_desc #{old.inspect} #{new.inspect} #{par.inspect}"

  if tag(old) != tag(new) || !compare_attrs(old, new)
    [removed(old, par), added(new, par)]
  else # only body changed (maybe)
    bold = body(old)
    bnew = body(new)
    if bold && bnew
      desc(bold, bnew, domid(old))
    elsif bold && !bnew
      [removed(bold, domid(old))]
    else
      [added(bnew, domid(old))]
    end
  end
end

#old_and_new(d1, d2) ⇒ Object



945
946
947
# File 'lib/goat.rb', line 945

def old_and_new(d1, d2)
  d1.action == '+' ? [d2, d1] : [d1, d2]
end

#removed(old, par, pos = nil) ⇒ Object



918
# File 'lib/goat.rb', line 918

def removed(old, par, pos=nil); [:rem, old, par, pos]; end

#tag(node) ⇒ Object



923
# File 'lib/goat.rb', line 923

def tag(node); node[0]; end