Technischer Artikel

Verlustfreier XLSX-Round-Trip in Delphi: Theme, extLst, calcChain

HotXLS, die native Excel-Bibliothek für Delphi und C++Builder, ist für verlustfreie XLSX-Round-Trips ausgelegt: Öffnen Sie eine Arbeitsmappe, ändern Sie eine einzige Zelle, speichern Sie — und das benutzerdefinierte Design (Theme) des Kunden, fremde extLst-Erweiterungsblöcke sowie die Berechnungskette (calcChain) bleiben vollständig erhalten. Drei Mechanismen stellen dies sicher: das unveränderte Caching von xl/theme/theme1.xml, die ereignisbasierte Reserialisierung unbekannter <ext>-Blöcke und das Schreiben einer neuen, standardkonformen xl/calcChain.xml bei jedem Speichern einer Arbeitsmappe mit Formeln

Das Szenario, das diese Entwicklung motiviert hat, ist deprimierend häufig: Ein Abrechnungsdienst lädt eine vom Kunden in Excel erstellte Vorlage (mit Firmenfarbthema, Sparklines in einer KPI-Spalte und einer von einer neueren Excel-Version hinzugefügten bedingten Formatierungsregel), schreibt eine Rechnungssumme in die Zelle B3 und speichert die Datei. Öffnet der Kunde das Ergebnis, sind die Markenfarben auf das standardmäßige Office-Blau zurückgesetzt, die Sparklines sind verschwunden und Excel bietet an, die Datei zu „reparieren“. Nichts im Code hat diese Funktionen berührt, aber die Bibliothek tat es — einfach durch den Speichervorgang

Warum verlieren Excel-Dateien nach der Bearbeitung durch Bibliotheken ihre Formatierung?

Excel-Dateien verlieren nach Bearbeitungen durch Bibliotheken oft ihre Formatierung, weil die meisten Bibliotheken die Datei nicht bearbeiten, sondern neu aufbauen. Ein .xlsx-Paket ist ein ZIP-Archiv aus XML-Teilen: xl/workbook.xml, eine xl/worksheets/sheetN.xml pro Blatt, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml und weitere. Eine typische Bibliothek parst diese Teile beim Öffnen in ein Objektmodell und generiert beim Speichern jeden Teil neu aus diesem Modell. Jede Funktion, die das Modell nicht abbildet — wie ein Design, das es nie geparst hat, oder ein Erweiterungsblock einer neueren Excel-Version — hat keinen Platz im Arbeitsspeicher, weshalb der neu generierte XML-Teil diese Informationen stillschweigend weglässt

ECMA-376 sah die Hälfte dieses Problems voraus. SpreadsheetML definiert extLst (ECMA-376 Teil 1, der „Future Feature Data Storage Area“, §18.2.10 für das Element auf Arbeitsmappenebene) als dedizierten Erweiterungspunkt: Neuere Erstellungsprogramme legen dort Funktionen ab, jeweils verpackt in ein <ext>-Element mit einem uri-Attribut, das die Funktion identifiziert. Ältere Consumer sollen das erhalten, was sie nicht verstehen. Sparklines, Datenschnitte (Slicer) und neuere Typen bedingter Formatierung werden so transportiert. Eine Bibliothek, die unbekannte <ext>-Blöcke verwirft, arbeitet nicht nur verlustbehaftet — sie verletzt den Vertrag zur Aufwärtskompatibilität (Forward Compatibility), um den herum das Format entworfen wurde. Die Frage an jede Tabellenkalkulations-Bibliothek, die Sie evaluieren, ist einfach: Wenn ich eine Zelle ändere, was ändert sich noch?

Wie behält HotXLS ein benutzerdefiniertes Design Byte für Byte?

HotXLS bewahrt das Design einer Arbeitsmappe, indem es die Original-Bytes von xl/theme/theme1.xml beim Öffnen cacht und beim Speichern unverändert zurückschreibt. Der Design-Teil (ECMA-376 Teil 1, §14.2.7) ist DrawingML und nicht SpreadsheetML — Farbschemata, Schriftartenschemata, Formatierungsschemata — und eine Tabellenkalkulations-Engine hat keinen Grund, diesen tiefgehend zu modellieren. Frühere Versionen von HotXLS generierten bei jedem Speichern ein festes Office-Design, was genau zu dem oben beschriebenen Zurücksetzen der Markenfarben führte. Seit v2.89.46 wird das Design des geöffneten Pakets roh gespeichert und unverändert wieder ausgegeben; das integrierte Office-Design wird nur für neu erstellte Arbeitsmappen generiert. Rohe Bytes sind die stärkste Qualitätsgarantie: kein Parsen, keine Reserialisierung, kein Risiko von Abweichungen

Die Byte-genaue Kopie hat bewusst Vorrang vor dem programmgesteuerten Zugriff auf das Design. TXLSXWorkbook stellt zwar ThemeMajorFont und ThemeMinorFont bereit, um Schriftarten für neue Arbeitsmappen festzulegen, aber wenn beim Öffnen ein Design erfasst wurde, haben diese Setter keine Auswirkung auf die gespeicherte Datei — die Originaltreue des Round-Trips hat Priorität. Wenn Sie das Design einer bestehenden Arbeitsmappe tatsächlich ändern müssen, ist das ein Signal, die Vorlage in Excel selbst zu bearbeiten und nicht über eine datenorientierte API. Der alltägliche Fall benötigt überhaupt keine API:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('branded-invoice.xlsx');
    Book.Sheets[0].Cells[3, 2].Value := 42750.00;  // Die einzige Änderung
    Book.SaveAs('branded-invoice-out.xlsx');
    // theme1.xml in der Ausgabe ist Byte-identisch mit der Eingabe
  finally
    Book.Free;
  end;
end;

Was geschieht mit unbekannten extLst-Blöcken beim Speichern?

HotXLS erfasst jeden <ext>-Block auf Arbeitsblattebene, den es nicht nativ abbildet, und gibt ihn im extLst-Bereich des gespeicherten Arbeitsblatts wieder aus. So überleben von neueren Excel-Versionen geschriebene Funktionen den Round-Trip unbeschadet. Seit v2.131.0 sind die erfassten Fragmente über die schreibgeschützte Eigenschaft RawWorksheetExts (eine TStringList auf jedem XLSX-Arbeitsblatt) sichtbar. Das macht die Garantie im Testcode überprüfbar, statt auf bloßen Glauben angewiesen zu sein:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  i: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('from-newer-excel.xlsx');
    Sheet := Book.Sheets[0];
    WriteLn(Format('%d fremde(r) ext-Block/Blöcke erfasst',
      [Sheet.RawWorksheetExts.Count]));
    for i := 0 to Sheet.RawWorksheetExts.Count - 1 do
      WriteLn(Copy(Sheet.RawWorksheetExts[i], 1, 100)); // URI jedes Blocks prüfen
  finally
    Book.Free;
  end;
end;

Wichtig bei der Implementierung ist, dass die Erfassung eine Reserialisierung auf Ereignisebene und keine rohe Byte-Kopie ist. Der XML-Streaming-Reader von HotXLS stellt keine Quell-Offsets zur Verfügung, sodass das unbekannte Teilfragment aus den Ereignissen Element, Text und EndElement rekonstruiert wird. Dieser Ansatz birgt eine klassische Falle: Ein selbstschließendes Element wie <a/> löst nur ein leeres Element-Ereignis und niemals ein EndElement aus. Jeder Tiefenzähler, der sich ausschließlich bei EndElement verringert, würde das Schließen des Teilfragments nicht erkennen. Richtig behandelt ist das rekonstruierte Fragment semantisch äquivalent zum Original — Attribut-Quoting und selbstschließende Formen werden normalisiert, weshalb es nicht Byte-identisch ist, aber Excel interpretiert die Bedeutung und nicht die Bytes. Zwei Eigenschaften der Excel-Ausgabe machen diese Wiedergabe sicher: Excel deklariert die erforderlichen xmlns-Attribute auf dem <ext>-Element oder darin, sodass jedes erfasste Fragment bezüglich der Namensräume (Namespaces) in sich geschlossen ist. Diese Eigenschaft ermöglicht es auch, dass das Duplizieren eines Arbeitsblatts innerhalb oder über Arbeitsmappen hinweg die fremden Blöcke mit einer einfachen Zuweisung der String-Liste mitsendet

Schreiben von calcChain.xml für das Vertrauen von Excel in Ihre Formeln

HotXLS schreibt xl/calcChain.xml (den Berechnungskettenteil, ECMA-376 Teil 1, §12.3.1), wann immer die gespeicherte Arbeitsmappe Formeln enthält. Dabei wählt es zwischen zwei Reihenfolgen: Ist der Formel-Abhängigkeitsgraph bereits aufgebaut und aktuell — Sie haben nach der letzten Bearbeitung Recalculate aufgerufen — wird die Kette in vollständiger topologischer Reihenfolge (Vorgänger vor Nachfolgern) ausgegeben, wobei Kreisbezüge am Ende angehängt werden. Andernfalls werden die Zellen in Dokumentenreihenfolge aufgelistet. Beide Varianten sind korrekt: Die Microsoft-Implementierungshinweise für das Format, [MS-XLSX], behandeln die Berechnungskette als Hinweis, den Excel beim Laden überprüft und neu ordnet. Jede vollständige Auflistung ist somit zulässig, und HotXLS weigert sich bewusst, den Aufbau des Graphen innerhalb von SaveAs zu erzwingen — der Aufbau von Abhängigkeiten verhält sich quadratisch zur Zellzahl, was bei großen Arbeitsmappen eine inakzeptable versteckte Verzögerung bedeuten würde

Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Jetzt gespeichert, listet calcChain.xml die Formelzellen in Dokumentenreihenfolge auf.
// Nach Recalculate existiert der Abhängigkeitsgraph, so dass derselbe Speichervorgang
// stattdessen eine vollständige topologische Reihenfolge ausgibt:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');

Warum sollte man sich um einen Teil kümmern, den Excel als bloße Empfehlung behandelt? Weil sein Fehlen ein Signal ist. Einige Consumer — Reparatur-Heuristiken, Drittanbieter-Viewer, Diff-Tools — erwarten, dass eine Formel-Arbeitsmappe eine Berechnungskette enthält. Eine Bibliothek, die diesen Teil beim Speichern verwirft, erzeugt Dateien, die sich von den Excel-Ausgaben unterscheiden. Das Schreiben einer gültigen Kette hält die Ausgabe im Rahmen dessen, womit das restliche Ökosystem getestet wurde — das ist der unscheinbare Kern der Round-Trip-Kompatibilität

Wo die Originaltreue des Round-Trips endet

Die Systemgrenzen von HotXLS seien ehrlich benannt: HotXLS kopiert das Paket nicht vollständig Byte für Byte. Das XML-Arbeitsblatt, Stile, gemeinsam genutzte Zeichenfolgen (Shared Strings) und die Arbeitsmappen-Teile werden aus dem parsed Modell neu generiert. Die Ausgabe ist semantisch exakt, aber nicht binäridentisch — schon die lokalen ZIP-Header tragen aktuelle DOS-Zeitstempel. Erfasste <ext>-Fragmente werden wie beschrieben normalisiert zurückgegeben. Programmatische Zuweisungen von Design-Schriftarten werden ignoriert, wenn ein erfasstes Original-Design vorhanden ist. Das Erhaltungsnetz hat eine klar definierte Maschenweite: Funktionen, die HotXLS nativ modelliert (Sparklines werden beispielsweise geparst und neu geschrieben, nicht blind kopiert), fremde extLst-Inhalte und die unverändert gecachten Teile. Ein Teil, der weder modelliert noch innerhalb eines Erweiterungspunkts liegt — wie ein spezieller Teil eines Add-ins — fällt nicht unter die beschriebenen Mechanismen. Testen Sie daher Ihre Vorlagen, anstatt bloße Annahmen zu treffen

Benachbarte Erhaltungsarbeiten runden das Bild ab: VBA-Projekte und externe Arbeitsmappen-Referenzen überstehen das Speichern nach dem gleichen Prinzip des Erhalts nicht-modellierter Teile, wie im Begleitartikel über den Erhalt von VBA und externen Links beschrieben. Dokumenteigenschaften in docProps haben ihre eigene Lese-Schreib-API, anstatt verworfen zu werden. Wenn Sie eine Tabellenkalkulations-Bibliothek testen, machen Sie den Ein-Zell-Test: Öffnen Sie eine komplexe Arbeitsmappe, ändern Sie einen Wert, speichern Sie und vergleichen Sie die entzippten XML-Teile mit dem Original. Was sich jenseits der geänderten Zelle noch verändert hat, sagt mehr über die Qualität der Bibliothek aus als jede Feature-Matrix

Die beschriebenen Mechanismen — Byte-genaue Design-Erhaltung seit v2.89.46, fremde extLst-Erfassung und calcChain.xml-Ausgabe seit v2.131.0 — sind in der aktuellen Version von HotXLS Delphi Excel Component enthalten, deren Produktseite den vollen XLSX-Funktionsumfang für Delphi und C++Builder dokumentiert