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. Was es nicht tut, ist, die beiden Richtungen symmetrisch zu machen. Der Export trägt weit mehr, als der Import zurückgewinnt, und ein Team, das anderes annimmt, wird zusehen, wie Formeln und Formatierung irgendwo zwischen der Revision des Kunden und dem nächsten Bericht verdunsten, 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 und Spaltenbreiten. Sie verwirft Rahmen, verbundene Bereiche, 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 bewusst überspringt
Lesen Sie diesen Teil sorgfältig, bevor Sie jemandem Round-Trip-Treue versprechen. Der ODS-Import in HotXLS ist bewusst ein leichtgewichtiger Pfad. Er bewahrt skalare Zellwerte und das zwischengespeicherte Ergebnis, das jede Formel zum Speicherzeitpunkt trug, und er expandiert wiederholte Zeilen und Spalten in das Raster. Er bringt keine Stile, keine ODS-Formelausdrücke und keine Zeichnungen mit hinüber
Die Formelentscheidung ist die, die am ehesten zubeißt, und sie wurde absichtlich getroffen. 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. OpenFormula in Excel-Formelsyntax zu übersetzen ist ein eigenes Dialektkonvertierungsproblem mit echten Randfällen bei Funktionsvokabularen, Referenzsyntax und Fehlermodellen. Stattdessen den zwischengespeicherten Wert zu lesen umgeht diese ganze Klasse stiller Fehlübersetzungen, sodass die importierten Zahlen exakt die Zahlen sind, die der Absender zuletzt gesehen hat. Der Preis ist, dass sie als Zahlen ankommen, nicht als die lebendigen Formeln, die sie erzeugt haben
Der Fehlermodus, um den man herum entwerfen muss, folgt direkt: Eine Tabelle, deren Summen korrekt waren, als LibreOffice sie zuletzt gespeichert hat, importiert mit korrekten Zahlen, aber diese Zahlen sind jetzt Konstanten. Bearbeitet man eine Eingabezelle und berechnet neu, bewegt sich nichts - die Formel ist weg, nur ihr Endergebnis bleibt. Wenn der Workflow nach dem Import lebendige Formeln braucht, stellt man sie programmatisch aus den eigenen Geschäftsregeln über Cell.Formula wieder her, das auf der XLSX-Fassade den Ausdruck ohne führendes Gleichheitszeichen entgegennimmt
Um den asymmetrischen Round-Trip herum entwerfen
Der Export rendert aus dem vollständigen Arbeitsmappenmodell im Speicher: Werte, Stile und, wenn man sie anfordert, Diagramme und Bilder. Der Import gibt nur Werte zurück. Die Strecke von .xlsx nach .ods ist also hochgradig treu, und die Strecke von .ods nach .xlsx bringt Werte und zwischengespeicherte Ergebnisse zurück, aber keine Gestaltung und keine lebendigen Formeln. Verkettet man die beiden, potenziert sich die Asymmetrie. Ein vollständiger Zyklus .xlsx nach .ods nach .xlsx schreibt auf dem Hinweg alles getreu und verliert auf dem Rückweg Stile und Formeln, obwohl bei keinem der beiden Schritte etwas schiefgelaufen ist
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // Format wird automatisch erkannt
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Nach einem ODS-Import sind Werte und zwischengespeicherte Formelergebnisse
// vorhanden; Stile und lebendige Formeln nicht. Vor dem Speichern alles
// wiederherstellen, wovon die nachgelagerte Pipeline abhängt.
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