HotXLS compare deux classeurs via TXLSXWorkbookCompare, qui apparie les feuilles de calcul par nom, parcourt les cellules renseignées de chaque paire, et signale ce qui diffère sous forme d'une liste structurée d'enregistrements de différences et, sur demande, sous forme d'une ligne lisible par différence. Aucune installation d'Excel n'est impliquée, et la comparaison s'exécute entièrement sur le modèle objet chargé dans Delphi ou C++Builder
Le besoin apparaît généralement la première fois que quelqu'un demande ce qui a changé. Un classeur financier revient de relecture, un export nocturne est régénéré après un changement de code, ou deux services envoient des versions du même modèle. Ouvrir les deux côte à côte fonctionne pour une feuille et échoue pour vingt. Comparer les fichiers octet par octet ne répond à rien du tout, car deux enregistrements du même classeur diffèrent de manières dont personne ne se soucie
Qu'est-ce qui compte comme une différence ?
La comparaison signale huit types, et l'ensemble est volontairement restreint : une feuille ajoutée ou supprimée, une cellule renseignée ajoutée ou supprimée, une cellule dont la valeur a changé, une cellule dont la formule a changé, et une plage fusionnée ajoutée ou supprimée. Tout est exprimé par rapport au classeur de gauche comme référence, de sorte qu'un élément ajouté n'existe que sur la droite et un élément supprimé que sur la gauche
Les feuilles sont appariées par nom plutôt que par position. Réordonner les feuilles de calcul ne produit donc aucune différence, ce qui est presque toujours le comportement souhaité : un utilisateur qui glisse un onglet n'est pas un changement de données. Une feuille présente d'un seul côté signale une seule entrée au niveau de la feuille plutôt que de développer chaque cellule renseignée qu'elle contient, ce qui garde lisible un rapport comparant deux classeurs structurellement différents, au lieu de milliers de lignes
Valeur ou formule, et comment chacune est comparée
Chaque cellule fournit une signature, et la règle est simple : une cellule portant une formule se compare via son texte de formule précédé d'un signe égal, et une cellule sans formule se compare via sa valeur convertie en texte. Cette distinction compte plus qu'il n'y paraît de prime abord. Deux cellules peuvent afficher le même nombre alors que l'une est un littéral et l'autre une formule, et les traiter comme égales masquerait exactement la modification la plus importante à détecter dans un classeur relu
Cela signifie aussi qu'une formule dont le texte est inchangé ne signale aucune différence même si son résultat mis en cache diffère, ce qui est le comportement correct pour comparer le contenu rédigé, et le mauvais comportement si vous essayez de détecter une dérive de recalcul. Pour cette seconde question, recalculez les deux classeurs avant de comparer, afin que les valeurs comparées soient celles que les formules produisent réellement aujourd'hui
Exécuter une comparaison
Compare prend les deux classeurs chargés et renvoie le nombre de différences trouvées. La liste des différences est ensuite disponible par index, ou peut être déversée dans n'importe quel 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); // une ligne lisible par différence
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;
Une ligne produite par Report se lit comme value changed: Data!A2: 10 -> 99, ce qui suffit pour un relecteur et suffit pour un message de commit. C'est la surface destinée à l'humain. La surface programmatique est l'enregistrement de différence lui-même, et c'est celle à utiliser lorsque la comparaison alimente une décision plutôt qu'un document
Piloter la logique à partir des différences structurées
Chaque différence expose son type, le nom de la feuille, la ligne et la colonne indexées à un pour les entrées au niveau des cellules, une référence A1 pour les entrées au niveau des fusions, et le texte de gauche et de droite. Les entrées au niveau des feuilles et des fusions signalent une ligne et une colonne à zéro, ce qui permet de les distinguer sans examiner le type :
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;
// Une politique de relecture qui ne bloque que sur les modifications de formule
if FormulaEdits > 0 then
raise Exception.CreateFmt(
'%d formula change(s) need sign-off', [FormulaEdits]);
end;
Deux propriétés de la sortie méritent d'être connues avant d'écrire des assertions contre elle. L'ordre des entrées au niveau des cellules suit l'ordre de parcours interne du magasin de cellules, donc les tests doivent être écrits indépendamment de cet ordre. Et la signature de formule porte son propre signe égal en tête, ce qui signifie qu'une chaîne de description construite par concaténation peut afficher un == doublé ; vérifiez les valeurs de champs plutôt que d'analyser la ligne descriptive lorsque le résultat pilote une logique
Où la comparaison de classeurs porte ses fruits
Trois usages justifient à eux seuls cette fonctionnalité. Tests de régression d'un générateur de rapports : conservez un classeur de référence connu comme correct, régénérez, comparez, et faites échouer le build sur toute différence inattendue. Relecture des changements : remettez au relecteur le rapport lisible au lieu de deux fichiers. Et vérification de migration : après conversion d'un lot de classeurs hérités, comparez chaque résultat à sa source pour prouver que rien n'a été perdu
Ce troisième cas s'associe naturellement aux passes d'inventaire et d'audit décrites dans l'atelier d'audit et de conversion de classeurs, où le comptage du contenu d'un classeur a lieu avant la conversion et la comparaison a lieu après. Si vos différences se regroupent autour de lignes insérées, les règles de réécriture de références dans l'ajustement des références de formule lors d'insertions et de suppressions expliquent pourquoi des formules qui semblent inchangées sont signalées comme modifiées
Les limites, énoncées clairement
La comparaison couvre les valeurs, les formules, les fusions et la présence des feuilles. Elle ne compare pas les formats de nombre, les polices, les remplissages, les règles de mise en forme conditionnelle, les validations de données, les graphiques, les images ou les noms définis. Une cellule dont la valeur est identique mais dont le format est passé de Standard à Monétaire ne signale aucune différence, ce qui est correct pour une comparaison de données et insuffisant pour une relecture de mise en forme
Les cellules à valeur de date méritent une mise en garde particulière : elles se comparent via leur conversion en texte, donc un classeur stocké sur le système de dates 1904 et un autre sur le système 1900 peuvent se comparer égaux ou inégaux de manières surprenantes si les numéros de série sous-jacents diffèrent. Les règles du système de dates sont traitées dans les numéros de série de date et le système 1904. Lorsque la fidélité de mise en forme ou au niveau des objets fait partie de la question, combinez la comparaison avec une passe d'audit qui compte ces fonctionnalités de chaque côté
La comparaison, l'audit et la conversion de classeurs s'exécutent tous sur le même moteur pour Delphi et C++Builder ; la liste complète des fonctionnalités se trouve sur la page du composant tableur Delphi HotXLS