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.



870
871
872
873
874
875
# File 'lib/goat.rb', line 870

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

Class Method Details

.diff(old, new, id) ⇒ Object



866
867
868
# File 'lib/goat.rb', line 866

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

Instance Method Details

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



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

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

#array_desc(old, new, par) ⇒ Object



947
948
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
# File 'lib/goat.rb', line 947

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



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

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

#body(node) ⇒ Object



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

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

#compare_attrs(a, b) ⇒ Object



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

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



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

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



877
878
879
# File 'lib/goat.rb', line 877

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

#dom_node?(node) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#domid(node) ⇒ Object



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

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

#is_replacement?(d1, d2) ⇒ Boolean

Returns:

  • (Boolean)


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

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

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



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

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



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

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



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

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



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

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



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

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

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



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

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

#tag(node) ⇒ Object



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

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