Ein Delphi-Berichts-Backend, das seit Jahren .xlsx ausgibt, bekommt eine neue Anforderung: Die Beschaffungsregeln eines Kunden aus dem öffentlichen Sektor schreiben OpenDocument-Spreadsheet-Ausgabe vor, und die Analysten auf diesem Konto schicken ihre Bearbeitungen als aus LibreOffice gespeicherte .ods-Dateien zurück. Nun muss derselbe Code also ODS schreiben und lesen. HotXLS, die native Object-Pascal-Tabellenkalkulationsbibliothek von losLab für Delphi und C++Builder, beherrscht beide Richtungen, ohne dass irgendwo Excel oder LibreOffice installiert ist. Die beiden Richtungen sind nah beieinander, aber nicht identisch. Seit HotXLS 2.88.229 bringt der Importpfad Formeln als lebendige Formeln neben ihren zwischengespeicherten Ergebnissen zurück, und aktuelle Versionen lesen auch Stile, Zahlenformate, verbundene Bereiche, Kommentare, Bilder, Diagramme und bedingte Formate. Was nicht überlebt, sind die volle OpenFormula-Semantik und eine Handvoll Details auf Paketebene, sodass ein Team, das ein perfektes Spiegelbild annimmt, trotzdem auf Randfälle trifft, irgendwo zwischen der Revision des Kunden und dem nächsten Bericht, meist ohne einen Fehler, auf den man zeigen könnte
Die ODS-Unterstützung lebt auf der XLSX-Fassade, nicht auf der XLS-Fassade
HotXLS liefert zwei unabhängige Klassenhierarchien in einem Paket: TXLSWorkbook in der Unit lxHandle für binäre BIFF8-.xls-Dateien und TXLSXWorkbook in der Unit lxHandleX für OOXML-.xlsx-Pakete. Jeder OpenDocument-Einstiegspunkt - OpenODS, SaveAsODS, GetODSSheetNames - hängt an TXLSXWorkbook. Die Platzierung ist nicht willkürlich. Ein ODS-Paket, wie in OASIS ODF 1.3 spezifiziert, ist ein Zip-Archiv mit einem mimetype-Mitglied, einem Manifest und einem content.xml-Rumpf, was es zu einem strukturellen Verwandten des OOXML-Zips macht; BIFF8 ist ein binärer Record-Stream aus den 1990ern ohne jede Gemeinsamkeit
Diese Platzierung hat eine praktische Kante: Eine alte .xls-Arbeitsmappe kann nicht mit einem Aufruf zu .ods werden. Man überführt den BIFF-Inhalt zuerst mit SaveXLSWorkbookAsXLSX aus der Unit lxXlsxExport in das XLSX-Modell, öffnet das Ergebnis über TXLSXWorkbook erneut und exportiert von dort. Die Brücke ist nicht verlustfrei, und man sollte die Lücken kennen, bevor man darauf aufbaut. Sie kopiert Werte, Formeln, Zahlenformate, Schriftarten, Füllungen, Rahmen, verbundene Bereiche, Spaltenbreiten und Zeilenhöhen. Sie verwirft Kommentare, Diagramme und bedingte Formatierung. Eine .xls-Quelle mit starker Formatierung erreicht ODS schlichter, als sie losgegangen ist, und das ist eine Eigenschaft der Brücke, nicht des ODS-Writers
Die Erkennung auf der Importseite ist automatisch. Die einfache Methode Open erkennt ein ODS-Paket an seinem mimetype-Mitglied und fällt auf eine Prüfung des obersten content.xml zurück, wenn dieses Mitglied fehlt, sodass ein generischer Codepfad „öffne, was auch immer der Nutzer hochgeladen hat" kein eigenes Erweiterungsschnüffeln braucht. Nach dem Öffnen meldet die Eigenschaft SourceFormat, welcher Zweig gegriffen hat
Export nach ODS mit TODSExportOptions
Der Exportaufruf selbst ist eine Zeile; das Optionsobjekt darum herum trägt die Entscheidungen, nach denen ein Prüfer später fragen wird:
var
Book: TXLSXWorkbook;
Opts: TODSExportOptions;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-report.xlsx');
Opts := TODSExportOptions.Create; // der Aufrufer besitzt und gibt dies frei
try
Opts.Generator := 'ReportService 4.2'; // meta:generator überschreiben
Opts.IncludeCharts := True;
Opts.IncludeImages := True;
Book.SaveAsODS('quarterly-report.ods', Opts);
finally
Opts.Free;
end;
finally
Book.Free;
end;
end;
Das Optionsobjekt gehört dem Aufrufer. HotXLS gibt es nicht frei, weshalb das innere try..finally dort steht und nicht optional ist. Die zwei Eigenschaften, die die Ausgabe verändern, statt sie nur zu beschriften, verdienen einen genaueren Blick. IncludeCharts := False zu setzen tut mehr, als Diagramme zu verbergen: Es entfernt die Diagramm-Unterdokumente und ihre Manifest-Einträge aus dem Paket, was genau das ist, was man will, wenn der Abnehmer eine Datenpipeline ist, die darüber stolpern würde. Generator überschreibt den ODF-String meta:generator, der sonst HotXLS/<version> lautet; man überschreibt ihn, wenn nachgelagerte Werkzeuge Dateiproduzenten per Fingerabdruck erkennen, um Support zu routen. Trifft nichts davon zu, lässt man das Optionsobjekt ganz weg. Der Aufruf SaveAs(FileName, xlsxOpenDocumentSpreadsheet) ist dasselbe wie SaveAsODS mit Standardwerten, und Stream-Überladungen auf beiden lassen Sie das Paket ohne temporäre Datei direkt in eine HTTP-Antwort schreiben
Was der Importpfad liest - und was er noch überspringt
Lesen Sie diesen Teil sorgfältig, bevor Sie jemandem Round-Trip-Treue versprechen. Der ODS-Import in HotXLS begann als leichtgewichtiger Pfad, der nur skalare Werte und zwischengespeicherte Formelergebnisse behielt, und er ist weit über dieses Stadium hinausgewachsen. Aktuelle Versionen lesen content.xml zusammen mit styles.xml und settings.xml: skalare Werte und zwischengespeicherte Formelergebnisse, wiederholte Zeilen und Spalten, ins Raster expandiert, verbundene Bereiche, Rich Text, Hyperlinks, Kommentare, eingebettete Bilder und Diagramme, vererbte und Standard-Zellstile, Zahlen- und Datumsformate, Zeilenhöhen und Spaltenbreiten, benannte Bereiche, Fixierungen (Freeze Panes), AutoFilter-Bereiche, Datenvalidierungen und bedingte Formate. Was OpenODS noch nicht konvertiert, sind die volle OpenFormula-Semantik, ODS-Schutzschlüssel, Kommentar-Geometrie, erweiterte Kommentarformatierung und Paketfeatures außerhalb des Tabelleninhalts
Formeln sind der Teil, der am ehesten zubeißt. Eine ODF-Zelle speichert zwei Dinge nebeneinander: den Formelausdruck, geschrieben im OpenFormula-Dialekt aus ODF 1.3 Teil 4, und den letzten Wert, den die erzeugende Anwendung dafür berechnet hat. Frühe Versionen lasen nur den zwischengespeicherten Wert. Seit HotXLS 2.88.229 importiert OpenODS auch den Ausdruck: Eine Zelle mit dem Präfix of:= oder oooc:= behält ihr zwischengespeichertes Ergebnis und bekommt eine normalisierte Excel-Formel, wobei eingeklammerte Referenzen wie [.A1:.B1] und Semikolon-Argumenttrenner in Excel-Syntax konvertiert werden. Seit 2.384.32 konvertiert der Import auch Inline-Array-Zeilen ({1;2|3;4} wird zu {1,2;3,4}) und den Referenz-Vereinigungsoperator ~, und seit 2.384.55 liest eine solche Vereinigung ohne das zweite Klammerpaar zurück. OpenFormula in Excel-Syntax zu übersetzen bleibt ein Dialektkonvertierungsproblem mit echten Randfällen bei Funktionsvokabularen, Referenzsyntax und Fehlermodellen — daher reist der zwischengespeicherte Wert mit: Die importierten Zahlen sind exakt die Zahlen, die der Absender zuletzt gesehen hat
Der Fehlermodus, um den man herum entwerfen muss, hat sich entsprechend verengt. Vor 2.88.229 importierte eine Tabelle, deren Summen korrekt waren, als LibreOffice sie zuletzt gespeichert hat, korrekte Zahlen, die jetzt Konstanten waren: Eingabezelle bearbeitet, neu berechnet, nichts bewegte sich. Auf aktuellen Versionen kommen die Formeln lebendig zurück, also bleibt als Restrisiko ein Konstrukt, das der Übersetzer nicht modelliert, was eine Formel hinterlassen kann, die sich anders neu berechnet als in der erzeugenden Anwendung, während der zwischengespeicherte Wert weiterhin das Ergebnis des Absenders zeigt. Vergleichen Sie neu berechnete Ergebnisse mit den importierten zwischengespeicherten Werten für die Formeln, von denen Ihre Pipeline abhängt, und stellen Sie jede abweichende Formel aus Ihren eigenen Geschäftsregeln über Cell.Formula wieder her, das auf der XLSX-Fassade den Ausdruck ohne führendes Gleichheitszeichen entgegennimmt
Um die verbleibenden Round-Trip-Lücken herum entwerfen
Der Export rendert aus dem vollständigen Arbeitsmappenmodell im Speicher: Werte, Formeln, Stile und, wenn man sie anfordert, Diagramme und Bilder. Der Formelexport wurde von Version zu Version nachgeschärft. Seit 2.384.55 behalten Inline-Arrays, Referenzvereinigungen und absolute Referenzen ihre Bedeutung in OpenFormula; seit 2.384.56 deklariert HotXLS die Namespaces seiner Formelpräfixe, sodass LibreOffice die Formeln auswertet, statt in jeder Formelzelle #VALUE! anzuzeigen; und seit 2.384.65 werden Ganze-Spalte- und Ganze-Zeile-Referenzen in der OpenFormula-Form [.A:.A] geschrieben, sodass Excel sie nicht mehr als #NAME? öffnet. Die bedingten Formate folgten: Seit 2.384.66 werden formelbasierte Regeln als formula-is(...) mit ihrer Basiszelle geschrieben, was LibreOffice erkennt, und seit 2.384.69 werden Wert- und Formelregeln zusätzlich als standardmäßige style:map-Bedingungen geschrieben, die einzige Form, die Excel liest, während die Importseite beide Formen liest. Ein vollständiger Zyklus .xlsx nach .ods nach .xlsx behält auf einer aktuellen Version also Werte, Formeln, Stile und bedingte Formate; was er noch verlieren kann, liegt in den Lücken aus dem vorigen Abschnitt
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // Format wird automatisch erkannt
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Seit HotXLS 2.88.229 kommen Formeln lebendig neben ihren
// zwischengespeicherten Ergebnissen zurück; stellen Sie jede Formel wieder her,
// der die nachgelagerte Pipeline als übersetzt nicht vertraut, bevor Sie speichern.
Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
Book.SaveAs('vendor-revision.xlsx');
end;
finally
Book.Free;
end;
Das Architekturmuster, das daraus folgt: Eingehende .ods-Dateien als Datenfeeds behandeln, nicht als Dokumente, die man an Ort und Stelle bearbeitet. Die kanonische Arbeitsmappe in .xlsx halten, Werte aus Kundenrevisionen auslesen und bei Bedarf frisches ODS aus der kanonischen Kopie ausgeben. Die Verifikation gehört in beide Lager - exportierte Dateien in LibreOffice Calc öffnen, dem Referenz-ODF-Konsumenten, und in Excel, das ODS seit Jahren liest, aber an den Rändern der Diagramm- und Stilunterstützung von LibreOffice abweicht. Blattanzahl, eine Handvoll Schlüsselzellen und das Vorhandensein von Diagrammen ergeben eine ausreichende Smoke-Prüfung je Exportprofil
Eine ODS-Datei vor dem Import triagieren
Wenn ein Endpunkt Uploads annimmt, ist das Auflisten der Blattnamen weit günstiger als ein vollständiges Parsen und fängt strukturelle Überraschungen früh ab:
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
raise Exception.Create('not a readable ODS package');
if Names.IndexOf('Data') < 0 then
raise Exception.Create('revision is missing the Data sheet');
finally
Book.Free;
Names.Free;
end;
Die Rückgabekonvention bringt Leute zu Fall: HotXLS-Aufrufe geben im Allgemeinen bei Erfolg eine positive Anzahl oder 1 zurück und bei Fehler -1, wobei sie die Liste beim Scheitern leeren, daher sollte auf <= 0 geprüft werden, statt mit einem bestimmten positiven Wert zu vergleichen. GetODSSheetNames setzt die Workbook-Instanz weder zurück noch befüllt es sie, sodass ein einzelnes Sondierungsobjekt ein ganzes Verzeichnis eingehender Dateien prüfen kann. Strukturelle Prüfungen wie diese fangen den häufigsten Fehler aus der Praxis - ein Analyst benennt ein Blatt um oder löscht es, bevor er die Revision zurückschickt - am Tor ab, wo die Fehlermeldung noch die Datei und das fehlende Blatt benennen kann, statt drei Ebenen tiefer als nil-Referenz aufzutauchen
Wenn Sie darum herum eine breitere Konvertierungspipeline bauen, zeigt das Muster der Arbeitsmappen-Audit- und Konvertierungswerkbank, wie man die Merkmale einer Datei inventarisiert, bevor man ein Zielformat wählt, und der Leitfaden zur Leistung großer Arbeitsmappen hält Batch-Exporte in vernünftigen Speichergrenzen
HotXLS ist eine native Tabellenkalkulationsbibliothek für Delphi und C++Builder mit vollständigem Quellcode; die komplette Funktionsliste und die Lizenzdetails finden sich auf der Produktseite der HotXLS Delphi Component