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.



889
890
891
892
893
894
# File 'lib/goat.rb', line 889

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

Class Method Details

.diff(old, new, id) ⇒ Object



885
886
887
# File 'lib/goat.rb', line 885

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

Instance Method Details

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



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

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

#array_desc(old, new, par) ⇒ Object



966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
# File 'lib/goat.rb', line 966

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



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

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

#body(node) ⇒ Object



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

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

#compare_attrs(a, b) ⇒ Object



1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
# File 'lib/goat.rb', line 1003

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



1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
# File 'lib/goat.rb', line 1032

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



896
897
898
# File 'lib/goat.rb', line 896

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

#dom_node?(node) ⇒ Boolean

Returns:

  • (Boolean)


937
938
939
# File 'lib/goat.rb', line 937

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

#domid(node) ⇒ Object



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

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

#is_replacement?(d1, d2) ⇒ Boolean

Returns:

  • (Boolean)


956
957
958
959
960
# File 'lib/goat.rb', line 956

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

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



945
946
947
948
949
950
951
952
953
954
# File 'lib/goat.rb', line 945

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



916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
# File 'lib/goat.rb', line 916

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



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

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



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

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



962
963
964
# File 'lib/goat.rb', line 962

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

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



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

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

#tag(node) ⇒ Object



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

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