HotXLS compara dos libros de trabajo mediante TXLSXWorkbookCompare, que empareja las hojas de cálculo por nombre, recorre las celdas con contenido de cada par, y reporta lo que difiere como una lista estructurada de registros de diferencia y, si se solicita, como una línea legible por cada diferencia. No interviene ninguna instalación de Excel, y la comparación se ejecuta por completo 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 trabajo financiero vuelve de revisión, una exportación nocturna se regenera después de 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 los archivos byte por byte no responde nada en absoluto, porque dos guardados del mismo libro de trabajo difieren de formas que a nadie le importan
¿Qué cuenta como una diferencia?
La comparación reporta ocho tipos, y el conjunto es deliberadamente pequeño: una hoja agregada o eliminada, una celda con contenido agregada o eliminada, una celda cuyo valor cambió, una celda cuya fórmula cambió, y un rango combinado agregado o eliminado. Todo se expresa contra el libro de trabajo izquierdo como línea base, así que un elemento agregado existe solo en el derecho y un elemento eliminado solo en el izquierdo
Las hojas se emparejan por nombre y no por posición. Por lo tanto, reordenar las hojas de cálculo no produce ninguna diferencia en absoluto, que casi siempre es el comportamiento deseado: que un usuario arrastre una pestaña no es un cambio de datos. Una hoja presente solo en un lado reporta una sola entrada a nivel de hoja en lugar de expandir cada celda con contenido dentro de ella, lo que mantiene legible un informe de dos libros de trabajo estructuralmente distintos, en lugar de que ocupe miles de líneas
Valor o fórmula, y cómo se compara cada uno
Cada celda aporta una firma, y la regla es simple: 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 de trabajo revisado
También significa que una fórmula cuyo texto no cambió no reporta ninguna diferencia aunque su resultado en caché difiera, que es el comportamiento correcto para comparar contenido de autoría, y el comportamiento incorrecto si se está tratando de detectar desviación por recálculo. Para esa segunda pregunta, recalcula ambos libros de trabajo antes de comparar, para que los valores comparados sean los que las fórmulas realmente producen hoy
Ejecutar una comparación
Compare toma los dos libros de trabajo cargados y devuelve el número de diferencias encontradas. La lista de diferencias queda entonces disponible por índice, o puede volcarse 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, que es suficiente para un revisor y suficiente para un mensaje de commit. Esa es la superficie orientada al humano. La superficie programática es el propio registro de diferencia, y es la que se debe usar cuando la comparación alimenta una decisión y no un documento
Impulsar 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 las 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 a nivel de combinación reportan 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 por ediciones de fórmula
if FormulaEdits > 0 then
raise Exception.CreateFmt(
'%d formula change(s) need sign-off', [FormulaEdits]);
end;
Vale la pena conocer dos propiedades de la salida 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 deben escribirse de forma independiente al orden. Y la firma de fórmula lleva su propio signo igual inicial, lo que significa que una cadena descriptiva construida por concatenación puede mostrar un == duplicado; verifica los valores de los campos en lugar de analizar la línea descriptiva cuando el resultado impulsa lógica
Dónde rinde frutos la comparación de libros de trabajo
Tres usos justifican la función por sí solos. Pruebas de regresión de un generador de informes: conserva un libro de trabajo conocido como correcto, regenera, compara, y haz fallar la compilación ante cualquier diferencia inesperada. Revisión de cambios: entrega al revisor el informe legible en lugar de dos archivos. Y verificación de migración: después de convertir un lote de libros de trabajo heredados, compara cada resultado contra su origen para demostrar que no se perdió nada
Ese tercer caso combina de forma natural con los pases de inventario y auditoría descritos en el banco de trabajo de auditoría y conversión de libros, donde contar lo que contiene un libro de trabajo ocurre antes de la conversión y la comparación ocurre después. Si tus diferencias se agrupan alrededor de filas insertadas, las reglas de reescritura de referencias en ajuste de referencias de fórmula al insertar y eliminar explican por qué las fórmulas que parecen sin cambios se reportan como cambiadas
Los límites, dichos con claridad
La comparación cubre valores, fórmulas, combinaciones y presencia de hojas. No compara formatos de número, fuentes, rellenos, reglas de formato condicional, validaciones de datos, gráficos, imágenes ni nombres definidos. Una celda cuyo valor es idéntico pero cuyo formato cambió de General a Moneda no reporta ninguna diferencia, lo cual es correcto para una comparación de datos e insuficiente para una revisión de formato
Las celdas con valores de fecha merecen una advertencia específica: se comparan por su conversión a texto, así que un libro de trabajo almacenado en el sistema de fechas 1904 y otro en el sistema 1900 pueden comparar como iguales o distintos de formas sorprendentes si los números de serie subyacentes difieren. Las reglas del sistema de fechas se cubren en números de serie de fecha y el sistema 1904. Cuando la fidelidad de formato o a nivel de objeto forma parte de la pregunta, combina la comparación con un pase de auditoría que cuente esas funciones en cada lado
La comparación de libros de trabajo, la auditoría y la conversión 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 hoja de cálculo para Delphi HotXLS