Technischer Artikel

Zwei Excel-Arbeitsmappen in Delphi mit HotXLS vergleichen

HotXLS vergleicht zwei Arbeitsmappen über TXLSXWorkbookCompare, das Arbeitsblätter nach Namen paart, die befüllten Zellen jedes Paares durchläuft und meldet, was abweicht, als strukturierte Liste von Unterschiedsdatensätzen und auf Wunsch als eine lesbare Zeile pro Unterschied. Es ist keine Excel-Installation beteiligt, und der Vergleich läuft vollständig auf dem geladenen Objektmodell in Delphi oder C++Builder

Der Bedarf zeigt sich meist, sobald erstmals jemand fragt, was sich geändert hat. Eine Finanz-Arbeitsmappe kommt aus der Prüfung zurück, ein nächtlicher Export wird nach einer Codeänderung neu erzeugt, oder zwei Abteilungen schicken Versionen derselben Vorlage. Beide nebeneinander zu öffnen funktioniert bei einem Blatt und scheitert bei zwanzig. Dateien byteweise zu vergleichen beantwortet gar nichts, weil zwei Speichervorgänge derselben Arbeitsmappe sich auf eine Weise unterscheiden, die niemanden interessiert

Was zählt als Unterschied?

Der Vergleich meldet acht Arten, und die Menge ist bewusst klein gehalten: ein hinzugefügtes oder entferntes Blatt, eine hinzugefügte oder entfernte befüllte Zelle, eine Zelle, deren Wert sich geändert hat, eine Zelle, deren Formel sich geändert hat, sowie ein hinzugefügter oder entfernter verbundener Bereich. Alles wird gegen die linke Arbeitsmappe als Basislinie ausgedrückt, sodass ein hinzugefügtes Element nur rechts existiert und ein entferntes nur links

Blätter werden nach Namen gepaart, nicht nach Position. Das Umordnen von Arbeitsblättern erzeugt daher überhaupt keine Unterschiede, was fast immer das gewünschte Verhalten ist: Ein Nutzer, der einen Reiter verschiebt, ist keine Datenänderung. Ein Blatt, das nur auf einer Seite vorhanden ist, meldet einen einzigen Eintrag auf Blattebene, statt jede darin befüllte Zelle einzeln aufzuführen, was den Bericht zweier strukturell unterschiedlicher Arbeitsmappen lesbar hält, statt Tausende Zeilen lang werden zu lassen

Wert oder Formel, und wie beides verglichen wird

Jede Zelle liefert eine Signatur, und die Regel ist einfach: Eine Zelle mit einer Formel wird anhand ihres Formeltexts mit führendem Gleichheitszeichen verglichen, eine Zelle ohne Formel anhand ihres in Text umgewandelten Werts. Diese Unterscheidung ist wichtiger, als sie zunächst wirkt. Zwei Zellen können dieselbe angezeigte Zahl tragen, während die eine ein Literal und die andere eine Formel ist, und sie als gleich zu behandeln würde genau die Änderung verbergen, die in einer geprüften Arbeitsmappe am ehesten auffallen sollte

Es bedeutet zudem, dass eine Formel, deren Text unverändert ist, keinen Unterschied meldet, selbst wenn ihr zwischengespeichertes Ergebnis abweicht, was für den Vergleich erstellten Inhalts korrekt ist und falsch, wenn man Neuberechnungs-Drift erkennen möchte. Für diese zweite Frage sollte man beide Arbeitsmappen vor dem Vergleich neu berechnen, sodass die verglichenen Werte diejenigen sind, die die Formeln heute tatsächlich liefern

Einen Vergleich durchführen

Compare nimmt die beiden geladenen Arbeitsmappen entgegen und gibt die Anzahl gefundener Unterschiede zurück. Die Unterschiedsliste steht anschließend indexiert zur Verfügung oder kann in beliebige TStrings ausgegeben werden:

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);              // eine lesbare Zeile pro Unterschied
      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;

Eine von Report erzeugte Zeile liest sich wie value changed: Data!A2: 10 -> 99, was für einen Prüfer und für eine Commit-Nachricht ausreicht. Das ist die menschenlesbare Oberfläche. Die programmatische Oberfläche ist der Unterschiedsdatensatz selbst, und dieser sollte verwendet werden, wenn der Vergleich eine Entscheidung speist statt ein Dokument

Logik aus den strukturierten Unterschieden ableiten

Jeder Unterschied gibt seine Art preis, den Blattnamen, eins-basierte Zeile und Spalte bei Einträgen auf Zellebene, eine A1-Referenz bei Einträgen auf Verbund-Ebene sowie den linken und rechten Text. Einträge auf Blatt- und Verbund-Ebene melden Zeile und Spalte als null, woran man sie ohne Prüfung der Art unterscheiden kann:

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;

  // Eine Prüfrichtlinie, die nur bei Formeländerungen blockiert
  if FormulaEdits > 0 then
    raise Exception.CreateFmt(
      '%d formula change(s) need sign-off', [FormulaEdits]);
end;

Zwei Eigenschaften der Ausgabe sind es wert, vor dem Schreiben von Assertions bekannt zu sein. Die Reihenfolge der Einträge auf Zellebene folgt der internen Durchlaufreihenfolge des Zellspeichers, daher sollten Tests reihenfolgeunabhängig geschrieben werden. Und die Formelsignatur trägt ihr eigenes führendes Gleichheitszeichen, was bedeutet, dass eine durch Verkettung gebaute Beschreibungszeichenkette ein doppeltes == zeigen kann; man sollte die Feldwerte prüfen, statt die Beschreibungszeile zu parsen, wenn das Ergebnis Logik antreibt

Wo sich der Arbeitsmappen-Diff auszahlt

Drei Anwendungen rechtfertigen die Funktion für sich allein. Regressionstests eines Berichtsgenerators: eine bekannt gute Arbeitsmappe aufbewahren, neu generieren, vergleichen und den Build bei jedem unerwarteten Unterschied fehlschlagen lassen. Änderungsprüfung: einem Prüfer den lesbaren Bericht statt zweier Dateien geben. Und Migrationsverifikation: nach der Konvertierung einer Charge von Legacy-Arbeitsmappen jedes Ergebnis mit seiner Quelle vergleichen, um zu beweisen, dass nichts verloren ging

Dieser dritte Fall passt natürlich zu den in der Arbeitsmappen-Audit- und Konvertierungswerkbank beschriebenen Bestands- und Prüfdurchläufen, bei denen das Zählen dessen, was eine Arbeitsmappe enthält, vor der Konvertierung stattfindet und der Vergleich danach. Wenn sich die Unterschiede um eingefügte Zeilen häufen, erklären die Regeln zur Referenzumschreibung in Formel-Referenzanpassung beim Einfügen und Löschen, warum scheinbar unveränderte Formeln als geändert gemeldet werden

Die Grenzen, unverblümt genannt

Der Vergleich deckt Werte, Formeln, Verbünde und das Vorhandensein von Blättern ab. Er vergleicht keine Zahlenformate, Schriftarten, Füllungen, Regeln für bedingte Formatierung, Datenüberprüfungen, Diagramme, Bilder oder definierte Namen. Eine Zelle mit identischem Wert, deren Format sich aber von Standard zu Währung geändert hat, meldet keinen Unterschied, was für einen Datenvergleich korrekt und für eine Formatierungsprüfung unzureichend ist

Zellen mit Datumswerten verdienen eine besondere Warnung: Sie werden anhand ihrer Textumwandlung verglichen, sodass eine auf dem 1904-Datumssystem gespeicherte Arbeitsmappe und eine auf dem 1900-System sich auf eine Weise gleich oder ungleich verhalten können, die überrascht, falls die zugrunde liegenden fortlaufenden Zahlen abweichen. Die Regeln des Datumssystems werden in Datumsseriennummern und das 1904-System behandelt. Wenn Formatierungs- oder Objektebenen-Treue Teil der Fragestellung ist, sollte der Diff mit einem Audit-Durchlauf kombiniert werden, der diese Merkmale auf beiden Seiten zählt

Arbeitsmappen-Vergleich, Auditing und Konvertierung laufen alle auf derselben Engine für Delphi und C++Builder; die vollständige Funktionsliste findet sich auf der HotXLS-Delphi-Tabellenkalkulationskomponentenseite