lib/coopy/table_diff.rb
module Coopy
class TableDiff
def initialize(align, flags)
@align = align # Alignment
@flags = flags # CompareFlags
end
def get_separator(t, t2, root)
sep = root
w = t.width
h = t.height
view = t.get_cell_view
(0..h-1).each do |y|
(0..w-1).each do |x|
txt = view.to_s(t.get_cell(x,y))
next if (txt.nil?)
while (txt.index(sep))
sep = "-" + sep
end
end
end
if (t2)
w = t2.width
h = t2.height
(0..h-1).each do |y|
(0..w-1).each do |x|
txt = view.to_s(t2.get_cell(x,y))
next if (txt.nil?)
while (txt.index(sep))
sep = "-" + sep
end
end
end
end
return sep
end
def quote_for_diff(v, d)
nil_str = "nil"
if (v.equals(d,nil))
return nil_str
end
str = v.to_s(d)
score = 0
(0..str.length-1).each do |i|
break if (str[score]!='_')
score+=1
end
if (str.slice(score)==nil_str)
str = "_" + str
end
return str
end
def is_reordered(m, ct)
reordered = false
l = -1
r = -1
(0..ct-1).each do |i|
unit = m[i]
next if (unit.nil?)
if (unit.l>=0)
if (unit.l<l)
reordered = true
break
end
l = unit.l
end
if (unit.r>=0)
if (unit.r<r)
reordered = true
break
end
r = unit.r
end
end
return reordered
end
def spread_context(units, del, active)
if (del>0 && active != nil)
# forward
mark = -del-1
skips = 0
(0..units.length-1).each do |i|
if (active[i]==-3)
# inserted/deleted row that is not to be shown, ignore
skips+=1
next
end
if (active[i]==0||active[i]==3)
if (i-mark<=del+skips)
active[i] = 2
elsif (i-mark==del+1+skips)
active[i] = 3
end
elsif (active[i]==1)
mark = i
skips = 0
end
end
# reverse
mark = units.length + del + 1
skips = 0
(0..units.length-1).each do |j|
i = units.length-1-j
if (active[i]==-3)
# inserted/deleted row that is not to be shown, ignore
skips+=1
next
end
if (active[i]==0||active[i]==3)
if (mark-i<=del+skips)
active[i] = 2
elsif (mark-i==del+1+skips)
active[i] = 3
end
elsif (active[i]==1)
mark = i
skips = 0
end
end
end
end
def report_unit(unit)
txt = unit.to_s
reordered = false
if (unit.l>=0)
if (unit.l<@l_prev)
reordered = true
end
@l_prev = unit.l
end
if (unit.r>=0)
if (unit.r<@r_prev)
reordered = true
end
@r_prev = unit.r
end
txt = "[" + txt + "]" if (reordered)
return txt
end
def hilite(output)
return false if (!output.is_resizable?)
output.resize(0,0)
output.clear
row_map = {}
col_map = {}
order = @align.to_order
units = order.get_list
has_parent = (@align.reference != nil)
a = nil
b = nil
p = nil
ra_header = 0
rb_header = 0
is_index_p = {}
is_index_a = {}
is_index_b = {}
if (has_parent)
p = @align.get_source
a = @align.reference.get_target
b = @align.get_target
ra_header = @align.reference.meta.get_target_header
rb_header = @align.meta.get_target_header
if (@align.get_index_columns)
@align.get_index_columns.each do |p2b|
is_index_p.set(p2b.l,true) if (p2b.l>=0)
is_index_b.set(p2b.r,true) if (p2b.r>=0)
end
end
if (@align.reference.get_index_columns)
@align.reference.get_index_columns.each do |p2a|
is_index_p.set(p2a.l,true) if (p2a.l>=0)
is_index_a.set(p2a.r,true) if (p2a.r>=0)
end
end
else
a = @align.get_source
b = @align.get_target
p = a
ra_header = @align.meta.get_source_header
rb_header = @align.meta.get_target_header
if (@align.get_index_columns)
@align.get_index_columns.each do |a2b|
is_index_a[a2b.l] = true if (a2b.l>=0)
is_index_b[a2b.r] = true if (a2b.r>=0)
end
end
end
column_order = @align.meta.to_order
column_units = column_order.get_list
show_rc_numbers = false
row_moves = nil
col_moves = nil
if (@flags.ordered)
row_moves = {}
moves = Mover.move_units(units)
(0...moves.length).each do |i|
row_moves[moves[i]] = i
end
col_moves = {}
moves = Mover.move_units(column_units)
(0...moves.length).each do |i|
col_moves[moves[i]] = i
end
end
active = []
active_column = nil
if (!@flags.show_unchanged)
(0...units.length).each do |i|
active[i] = 0
end
end
allow_insert = @flags.allow_insert
allow_delete = @flags.allow_delete
allow_update = @flags.allow_update
if (!@flags.show_unchanged_columns)
active_column = []
(0..column_units.length-1).each do |i|
v = 0
unit = column_units[i]
v = 1 if (unit.l>=0 && is_index_a[unit.l])
v = 1 if (unit.r>=0 && is_index_b[unit.r])
v = 1 if (unit.p>=0 && is_index_p[unit.p])
active_column[i] = v
end
end
outer_reps_needed =
(@flags.show_unchanged&&@flags.show_unchanged_columns) ? 1 : 2
v = a.get_cell_view
sep = ""
conflict_sep = ""
schema = []
have_schema = false
(0...column_units.length).each do |j|
cunit = column_units[j]
reordered = false
if (@flags.ordered)
if (col_moves.has_key?(j))
reordered = true
end
show_rc_numbers = true if (reordered)
end
act = ""
if (cunit.r>=0 && cunit.lp==-1)
have_schema = true
act = "+++"
if (active_column)
active_column[j] = 1 if (allow_update)
end
end
if (cunit.r<0 && cunit.lp>=0)
have_schema = true
act = "---"
if (active_column)
active_column[j] = 1 if (allow_update)
end
end
if (cunit.r>=0 && cunit.lp>=0)
if (a.height>=ra_header && b.height>=rb_header)
aa = a.get_cell(cunit.lp,ra_header)
bb = b.get_cell(cunit.r,rb_header)
if (!v.equals(aa,bb))
have_schema = true
act = "("
act += v.to_s(aa)
act += ")"
active_column[j] = 1 if (active_column)
end
end
end
if (reordered)
act = ":" + act
have_schema = true
active_column = nil if (active_column) # bail
end
schema << act
end
if (have_schema)
at = output.height
output.resize(column_units.length+1,at+1)
output.set_cell(0,at,v.to_datum("!"))
(0...column_units.length).each do |j|
output.set_cell(j+1,at,v.to_datum(schema[j]))
end
end
top_line_done = false
if (@flags.always_show_header)
at = output.height
output.resize(column_units.length+1,at+1)
output.set_cell(0,at,v.to_datum("@@"))
(0...column_units.length).each do |j|
cunit = column_units[j]
if (cunit.r>=0)
if (b.height>0)
output.set_cell(j+1,at,
b.get_cell(cunit.r,rb_header))
end
elsif (cunit.lp>=0)
if (a.height>0)
output.set_cell(j+1,at,
a.get_cell(cunit.lp,ra_header))
end
end
col_map[j+1] = cunit
end
top_line_done = true
end
# If we are dropping unchanged rows/cols, we repeat this loop twice.
(0..outer_reps_needed-1).each do |out|
if (out==1)
spread_context(units,@flags.unchanged_context,active)
spread_context(column_units,@flags.unchanged_column_context,
active_column)
if (active_column)
(0..column_units.length).each do |i|
if (active_column[i]==3)
active_column[i] = 0
end
end
end
end
showed_dummy = false
l = -1
r = -1
(0..units.length-1).each do |i|
unit = units[i]
reordered = false
if (@flags.ordered)
if (row_moves.has_key?(i))
reordered = true
end
show_rc_numbers = true if (reordered)
end
next if (unit.r<0 && unit.l<0)
next if (unit.r==0 && unit.lp==0 && top_line_done)
act = ""
act = ":" if (reordered)
publish = @flags.show_unchanged
dummy = false
if (out==1)
publish = active[i]>0
dummy = active[i]==3
next if (dummy&&showed_dummy)
next if (!publish)
end
showed_dummy = false if (!dummy)
at = output.height
if (publish)
output.resize(column_units.length+1,at+1)
end
if (dummy)
(0...column_units.length+1).each do |j|
output.set_cell(j,at,v.to_datum("..."))
showed_dummy = true
end
next
end
have_addition = false
skip = false
if (unit.p<0 && unit.l<0 && unit.r>=0)
skip = true if (!allow_insert)
act = "+++"
end
if ((unit.p>=0||!has_parent) && unit.l>=0 && unit.r<0)
skip = true if (!allow_delete)
act = "---"
end
if (skip)
if (!publish)
if (active)
active[i] = -3
end
end
next
end
(0...column_units.length).each do |j|
cunit = column_units[j]
pp = nil
ll = nil
rr = nil
dd = nil
dd_to = nil
have_dd_to = false
dd_to_alt = nil
have_dd_to_alt = false
have_pp = false
have_ll = false
have_rr = false
if (cunit.p>=0 && unit.p>=0)
pp = p.get_cell(cunit.p,unit.p)
have_pp = true
end
if (cunit.l>=0 && unit.l>=0)
ll = a.get_cell(cunit.l,unit.l)
have_ll = true
end
if (cunit.r>=0 && unit.r>=0)
rr = b.get_cell(cunit.r,unit.r)
have_rr = true
if ((have_pp ? cunit.p : cunit.l)<0)
if (rr != nil)
if (v.to_s(rr) != "")
if (@flags.allow_update)
have_addition = true
end
end
end
end
end
# for now, just interested in p->r
if (have_pp)
if (!have_rr)
dd = pp
else
# have_pp, have_rr
if (v.equals(pp,rr))
dd = pp
else
# rr is different
dd = pp
dd_to = rr
have_dd_to = true
if (!v.equals(pp,ll))
if (!v.equals(pp,rr))
dd_to_alt = ll
have_dd_to_alt = true
end
end
end
end
elsif (have_ll)
if (!have_rr)
dd = ll
else
if (v.equals(ll,rr))
dd = ll
else
# rr is different
dd = ll
dd_to = rr
have_dd_to = true
end
end
else
dd = rr
end
txt = nil
if (have_dd_to&&allow_update)
if (active_column)
active_column[j] = 1
end
txt = quote_for_diff(v,dd)
# modification: x -> y
if (sep=="")
# strictly speaking getSeparator(a,nil,..)
# would be ok - but very confusing
sep = get_separator(a,b,"->")
end
is_conflict = false
if (have_dd_to_alt)
if (!v.equals(dd_to,dd_to_alt))
is_conflict = true
end
end
if (!is_conflict)
txt = txt + sep + quote_for_diff(v,dd_to)
if (sep.length>act.length)
act = sep
end
else
if (conflict_sep=="")
conflict_sep = get_separator(p,a,"!") + sep
end
txt = txt +
conflict_sep + quote_for_diff(v,dd_to_alt) +
conflict_sep + quote_for_diff(v,dd_to)
act = conflict_sep
end
end
if (act == "" && have_addition)
act = "+"
end
if (act == "+++")
if (have_rr)
if (active_column)
active_column[j] = 1
end
end
end
if (publish)
if (active_column.nil? || active_column[j]>0)
if (txt != nil)
output.set_cell(j+1,at,v.to_datum(txt))
else
output.set_cell(j+1,at,dd)
end
end
end
end
if (publish)
output.set_cell(0,at,v.to_datum(act))
row_map[at] = unit
end
if (act!="")
if (!publish)
if (active)
active[i] = 1
end
end
end
end
end
# add row/col numbers?
if (!show_rc_numbers)
if (@flags.always_show_order)
show_rc_numbers = true
elsif (@flags.ordered)
show_rc_numbers = is_reordered(row_map,output.height)
if (!show_rc_numbers)
show_rc_numbers = is_reordered(col_map,output.width)
end
end
end
admin_w = 1
if (show_rc_numbers&&!@flags.never_show_order)
admin_w+=1
target = new Array<Int>
(0..output.width-1).each do |i|
target.push(i+1)
end
output.insert_or_delete_columns(target,output.width+1)
@l_prev = -1
@r_prev = -1
(0..output.height-1).each do |i|
unit = row_map.get(i)
next if (unit.nil?)
output.setCell(0,i,reportUnit(unit))
end
target = []
(0..output.height-1).each do |i|
target.push(i+1)
end
output.insert_or_delete_rows(target,output.height+1)
@l_prev = -1
@r_prev = -1
(1..output.width-1).each do |i|
unit = col_map.get(i-1)
next if (unit.nil?)
output.setCell(i,0,reportUnit(unit))
end
output.setCell(0,0,"@:@")
end
if (active_column)
all_active = true
(0..active_column.length-1).each do |i|
if (active_column[i]==0)
all_active = false
break
end
end
if (!all_active)
fate = new Array<Int>
(0..admin_w-1).each do |i|
fate.push(i)
end
at = admin_w
ct = 0
dots = new Array<Int>
(0..active_column.length-1).each do |i|
off = (active_column[i]==0)
ct = off ? (ct+1) : 0
if (off && ct>1)
fate.push(-1)
else
dots.push(at) if (off)
fate.push(at)
at+=1
end
end
output.insertOrDeleteColumns(fate,at)
dots.each do |d|
(0..output.height-1).each do |j|
output.setCell(d,j,"...")
end
end
end
end
return true
end
end
end