HotXLS compara dos libros mediante TXLSXWorkbookCompare, que empareja las hojas de cálculo por nombre, recorre las celdas pobladas de cada par, e informa de lo que difiere como una lista estructurada de registros de diferencia y, si se solicita, como una línea legible por diferencia. No interviene ninguna instalación de Excel, y la comparación se ejecuta enteramente sobre el modelo de objetos cargado en Delphi o C++Builder
La necesidad suele aparecer la primera vez que alguien pregunta qué cambió. Un libro de finanzas vuelve de revisión, una exportación nocturna se regenera tras un cambio de código, o dos departamentos envían versiones de la misma plantilla. Abrir ambos lado a lado funciona para una hoja y falla para veinte. Comparar archivos byte a byte no responde nada en absoluto, porque dos guardados del mismo libro difieren de maneras que a nadie le importan
¿Qué cuenta como diferencia?
La comparación informa de ocho tipos, y el conjunto es deliberadamente pequeño: una hoja añadida o eliminada, una celda poblada añadida o eliminada, una celda cuyo valor cambió, una celda cuya fórmula cambió, y un rango combinado añadido o eliminado. Todo se expresa respecto al libro izquierdo como línea base, así que un elemento añadido existe solo a la derecha y un elemento eliminado solo a la izquierda
Las hojas se emparejan por nombre en lugar de por posición. Reordenar las hojas de cálculo, por tanto, no produce ninguna diferencia en absoluto, que es casi siempre el comportamiento deseado: que un usuario arrastre una pestaña no es un cambio de datos. Una hoja presente solo en un lado informa de una única entrada a nivel de hoja en lugar de expandir cada celda poblada dentro de ella, lo que mantiene legible el informe de dos libros estructuralmente distintos en lugar de miles de líneas
Valor o fórmula, y cómo se compara cada uno
Cada celda aporta una firma, y la regla es sencilla: una celda que lleva una fórmula se compara por su texto de fórmula con un signo igual inicial, y una celda sin fórmula se compara por su valor convertido a texto. Esa distinción importa más de lo que parece a primera vista. Dos celdas pueden mostrar el mismo número visible mientras una es un literal y la otra una fórmula, y tratarlas como iguales ocultaría exactamente la edición más importante de detectar en un libro revisado
También significa que una fórmula cuyo texto no ha cambiado no informa de ninguna diferencia aunque su resultado en caché difiera, que es el comportamiento correcto para comparar contenido redactado, y el comportamiento incorrecto si se intenta detectar una deriva de recálculo. Para esa segunda cuestión, recalcule ambos libros antes de comparar, de modo que los valores que compare sean los que las fórmulas realmente producen hoy
Ejecutar una comparación
Compare recibe los dos libros cargados y devuelve el número de diferencias encontradas. La lista de diferencias queda entonces disponible por índice, o se puede volcar en cualquier TStrings:
uses
lxHandleX, lxCompare;
var
Left, Right: TXLSXWorkbook;
Cmp: TXLSXWorkbookCompare;
Lines: TStringList;
begin
Left := TXLSXWorkbook.Create;
Right := TXLSXWorkbook.Create;
Cmp := TXLSXWorkbookCompare.Create;
Lines := TStringList.Create;
try
if (Left.Open('baseline.xlsx') <> 1) or
(Right.Open('reviewed.xlsx') <> 1) then
Exit;
if Cmp.Compare(Left, Right) = 0 then
Writeln('workbooks are equivalent')
else
begin
Cmp.Report(Lines); // una línea legible por diferencia
Lines.SaveToFile('workbook-diff.txt');
Writeln(Format('%d difference(s) written', [Cmp.Count]));
end;
finally
Lines.Free;
Cmp.Free;
Right.Free;
Left.Free;
end;
end;
Una línea producida por Report se lee como value changed: Data!A2: 10 -> 99, lo cual es suficiente para un revisor y suficiente para un mensaje de commit. Esa es la superficie orientada a personas. La superficie programática es el propio registro de diferencia, y es la que hay que usar cuando la comparación alimenta una decisión en lugar de un documento
Dirigir la lógica a partir de las diferencias estructuradas
Cada diferencia expone su tipo, el nombre de la hoja, la fila y la columna en base uno para entradas a nivel de celda, una referencia A1 para las entradas a nivel de combinación, y el texto izquierdo y derecho. Las entradas a nivel de hoja y de combinación informan de fila y columna como cero, que es cómo se distinguen sin inspeccionar el tipo:
var
I: Integer;
D: TlxCompareDiff;
FormulaEdits: Integer;
begin
FormulaEdits := 0;
for I := 0 to Cmp.Count - 1 do
begin
D := Cmp.Diff(I);
case D.Kind of
lckFormulaChanged:
begin
Inc(FormulaEdits);
Writeln(Format('%s R%dC%d: %s => %s',
[D.Sheet, D.Row, D.Col, D.LeftText, D.RightText]));
end;
lckSheetAdded, lckSheetRemoved:
Writeln(Format('structure: %s', [D.Describe]));
lckMergeAdded, lckMergeRemoved:
Writeln(Format('layout: %s at %s', [D.Describe, D.Ref]));
end;
end;
// Una política de revisión que solo bloquea ante ediciones de fórmula
if FormulaEdits > 0 then
raise Exception.CreateFmt(
'%d formula change(s) need sign-off', [FormulaEdits]);
end;
Dos propiedades de la salida vale la pena conocer antes de escribir aserciones contra ella. El orden de las entradas a nivel de celda sigue el orden de recorrido interno del almacén de celdas, así que las pruebas deberían escribirse de forma independiente del orden. Y la firma de fórmula lleva su propio signo igual inicial, lo que significa que una cadena de descripción construida por concatenación puede mostrar un == duplicado; compruebe los valores de campo en lugar de analizar la línea descriptiva cuando el resultado dirige la lógica
Dónde da sus frutos la comparación de libros
Tres usos justifican la función por sí solos. Pruebas de regresión de un generador de informes: conservar un libro que se sabe correcto, regenerar, comparar, y hacer fallar la compilación ante cualquier diferencia inesperada. Revisión de cambios: entregar a un revisor el informe legible en lugar de dos archivos. Y verificación de migración: tras convertir un lote de libros heredados, comparar cada resultado con su origen para demostrar que no se perdió nada
Ese tercer caso combina de forma natural con las pasadas de inventario y auditoría descritas en el banco de trabajo de auditoría y conversión de libros, donde contar lo que contiene un libro ocurre antes de la conversión y la comparación ocurre después. Si sus diferencias se agrupan en torno a filas insertadas, las reglas de reescritura de referencias de el ajuste de referencias de fórmula al insertar y eliminar explican por qué fórmulas que parecen inalteradas se notifican como cambiadas
Los límites, expuestos con claridad
La comparación cubre valores, fórmulas, combinaciones y presencia de hojas. No compara formatos numéricos, fuentes, rellenos, reglas de formato condicional, validaciones de datos, gráficos, imágenes o nombres definidos. Una celda cuyo valor es idéntico pero cuyo formato cambió de General a Moneda no informa de ninguna diferencia, lo cual es correcto para una comparación de datos e insuficiente para una revisión de formato
Las celdas con valor de fecha merecen una advertencia específica: se comparan por su conversión a texto, así que un libro almacenado en el sistema de fechas de 1904 y otro en el sistema de 1900 pueden compararse como iguales o distintos de formas que sorprenden si los números de serie subyacentes difieren. Las reglas del sistema de fechas se tratan en los números de serie de fecha y el sistema de 1904. Cuando el formato o la fidelidad a nivel de objeto forman parte de la cuestión, combine la comparación con una pasada de auditoría que cuente esas funciones en cada lado
La comparación, la auditoría y la conversión de libros se ejecutan todas sobre el mismo motor para Delphi y C++Builder; la lista completa de funciones está en la página del componente de hojas de cálculo para Delphi de HotXLS