Een Delphi-rapportagebackend die al jaren .xlsx uitzendt, krijgt een nieuwe eis: de inkoopregels van een klant uit de publieke sector schrijven OpenDocument Spreadsheet-uitvoer voor, en de analisten van dat account sturen hun bewerkingen terug als .ods-bestanden opgeslagen vanuit LibreOffice. Dus nu moet dezelfde code ODS schrijven én lezen. HotXLS, losLabs native Object Pascal-spreadsheetbibliotheek voor Delphi en C++Builder, behandelt beide richtingen zonder dat er ergens Excel of LibreOffice geïnstalleerd hoeft te zijn. De twee richtingen liggen dicht bij elkaar maar zijn niet identiek. Sinds HotXLS 2.88.229 brengt het importpad formules terug als levende formules naast hun gecachte resultaten, en lezen huidige releases ook stijlen, getalnotaties, samengevoegde bereiken, opmerkingen, afbeeldingen, grafieken en voorwaardelijke opmaken. Wat nog steeds niet overleeft, is de volledige OpenFormula-semantiek plus een handvol details op pakketniveau, dus een team dat een perfecte spiegel veronderstelt, loopt nog steeds tegen randgevallen aan ergens tussen de revisie van de klant en het volgende rapport, meestal zonder een fout om naar te wijzen
ODS-ondersteuning zit op de XLSX-facade, niet de XLS-facade
HotXLS levert twee onafhankelijke klassenhiërarchieën in één pakket: TXLSWorkbook in unit lxHandle voor binaire BIFF8-.xls-bestanden, en TXLSXWorkbook in unit lxHandleX voor OOXML-.xlsx-pakketten. Elk OpenDocument-toegangspunt - OpenODS, SaveAsODS, GetODSSheetNames - hangt aan TXLSXWorkbook. Die plaatsing is niet willekeurig. Een ODS-pakket, zoals gespecificeerd in OASIS ODF 1.3, is een zip-archief met een mimetype-lid, een manifest en een content.xml-body, wat het een structurele neef van de OOXML-zip maakt; BIFF8 is een binaire recordstroom uit de jaren negentig zonder iets gemeenschappelijks
Die plaatsing heeft een praktische scherpe kant: een legacy .xls-werkmap kan niet in één aanroep .ods worden. Je overbrugt de BIFF-inhoud eerst naar het XLSX-model, met SaveXLSWorkbookAsXLSX uit unit lxXlsxExport, heropent het resultaat via TXLSXWorkbook, en exporteert dan vandaar. De brug is niet verliesvrij, en het is de moeite waard de gaten te kennen voordat je erop bouwt. Hij kopieert waarden, formules, getalnotaties, lettertypen, vullingen, randen, samengevoegde bereiken, kolombreedtes en rijhoogtes. Hij laat opmerkingen, grafieken en voorwaardelijke opmaak vallen. Een .xls-bron met zware opmaak komt kaler bij ODS aan dan hij vertrok, en dat is een eigenschap van de brug, niet van de ODS-schrijver
Detectie aan de importkant is automatisch. De gewone Open-methode herkent een ODS-pakket aan zijn mimetype-lid, met een terugval naar een controle van een top-level content.xml wanneer dat lid ontbreekt, dus een generiek codepad "open wat de gebruiker ook heeft geüpload" heeft geen eigen extensiedetectie nodig. Na het openen meldt de eigenschap SourceFormat welke tak is geactiveerd
Exporteren naar ODS met TODSExportOptions
De exportaanroep zelf is één regel; het opties-object eromheen draagt de beslissingen waar een reviewer later naar zal vragen:
var
Book: TXLSXWorkbook;
Opts: TODSExportOptions;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-report.xlsx');
Opts := TODSExportOptions.Create; // aanroeper bezit dit en geeft het vrij
try
Opts.Generator := 'ReportService 4.2'; // meta:generator-override
Opts.IncludeCharts := True;
Opts.IncludeImages := True;
Book.SaveAsODS('quarterly-report.ods', Opts);
finally
Opts.Free;
end;
finally
Book.Free;
end;
end;
Het opties-object is eigendom van de aanroeper. HotXLS geeft het niet vrij, en daarom is de binnenste try..finally aanwezig en niet optioneel. De twee eigenschappen die de uitvoer daadwerkelijk veranderen, in plaats van hem alleen te labelen, verdienen een nadere blik. IncludeCharts := False instellen doet meer dan grafieken verbergen: het strippt de grafieksubdocumenten en hun manifestitems uit het pakket, wat precies is wat je wilt wanneer de consument een datapipeline is die erover zou struikelen. Generator overschrijft de ODF-meta:generator-string, die anders HotXLS/<version> luidt; overschrijf hem wanneer downstream-tooling bestandsproducenten fingerprint om support te routeren. Als niets daarvan van toepassing is, sla het opties-object volledig over. SaveAs(FileName, xlsxOpenDocumentSpreadsheet) aanroepen is hetzelfde als SaveAsODS met standaardwaarden, en stream-overloads op beide laten je het pakket rechtstreeks in een HTTP-respons schrijven zonder tijdelijk bestand
Wat het importpad leest - en wat het nog steeds overslaat
Lees dit deel zorgvuldig voordat je iemand round-trip-getrouwheid belooft. ODS-import in HotXLS begon als een lichtgewicht pad dat alleen scalaire waarden en gecachte formuleresultaten bijhield, en is inmiddels ver daaraan voorbijgegroeid. Huidige releases lezen content.xml samen met styles.xml en settings.xml: scalaire waarden en gecachte formuleresultaten, herhaalde rijen en kolommen die tot het raster worden uitgevouwen, samengevoegde bereiken, rich text, hyperlinks, opmerkingen, ingesloten afbeeldingen en grafieken, overgeërfde en standaardcelstijlen, getal- en datumnotaties, rijhoogtes en kolombreedtes, benoemde bereiken, freeze panes, AutoFilter-bereiken, datavalidaties en voorwaardelijke opmaken. Wat OpenODS nog steeds niet converteert, is de volledige OpenFormula-semantiek, ODS-beschermingssleutels, opmerkingsgeometrie, geavanceerde opmerkingsopmaak en pakketfuncties buiten de spreadsheetinhoud
Formules zijn het deel dat het meest waarschijnlijk bijt. Een ODF-cel slaat twee dingen naast elkaar op: de formule-expressie, geschreven in het OpenFormula-dialect gedefinieerd in ODF 1.3 Deel 4, en de laatste waarde die de producerende applicatie ervoor heeft berekend. Vroege releases lazen alleen de gecachte waarde. Sinds HotXLS 2.88.229 importeert OpenODS ook de expressie: een cel geschreven met het of:=- of oooc:=-voorvoegsel behoudt zijn gecachte waarde en krijgt een genormaliseerde Excel-formule, waarbij referenties met haakjes zoals [.A1:.B1] en puntkomma's als argumentscheidingsteken naar Excel-syntax worden omgezet. Sinds 2.384.32 converteert de import ook inline-arrayrijen ({1;2|3;4} wordt {1,2;3,4}) en de ~-referentie-unieoperator, en sinds 2.384.55 wordt zo'n unie zonder een tweede paar haakjes teruggelezen. OpenFormula naar Excel-syntax vertalen blijft een dialectconversieprobleem met echte randgevallen rond functiewoordenschat, verwijzingssyntax en foutmodellen, en daarom reist de gecachte waarde mee: de getallen die je importeert zijn precies de getallen die de afzender voor het laatst zag
Het faalscenario om omheen te ontwerpen is dienovereenkomstig versmald. Vóór 2.88.229 importeerde een spreadsheet waarvan de totalen correct waren toen LibreOffice hem voor het laatst opsloeg, correcte getallen die inmiddels constanten waren: bewerk een invoercel, herbereken, en er bewoog niets. Op huidige releases komen de formules levend terug, dus het resterende risico is een constructie die de vertaler niet modelleert, wat een formule kan opleveren die anders herberekent dan de producerende applicatie terwijl de gecachte waarde nog steeds het resultaat van de afzender toont. Vergelijk herberekende resultaten met de geïmporteerde gecachte waarden voor de formules waar je pipeline van afhangt, en herstel elke afwijkende formule vanuit je eigen bedrijfsregels via Cell.Formula, dat op de XLSX-facade de expressie zonder voorafgaand isgelijkteken neemt
Ontwerpen rond de resterende round trip-gaten
Export rendert vanuit het volledige in-memory workbook-model: waarden, formules, stijlen en, als je erom vraagt, grafieken en afbeeldingen. Formule-export is release na release aangescherpt. Sinds 2.384.55 behouden inline-arrays, referentie-unies en absolute verwijzingen hun betekenis in OpenFormula; sinds 2.384.56 declareert HotXLS de namespaces van zijn formulevoorvoegsels, zodat LibreOffice de formules evalueert in plaats van in elke formulecel #VALUE! te tonen; en sinds 2.384.65 worden verwijzingen naar hele kolommen en hele rijen geschreven in de OpenFormula-vorm [.A:.A], zodat Excel ze niet langer opent als #NAME?. Voorwaardelijke opmaken volgden: sinds 2.384.66 worden formulegebaseerde regels geschreven als formula-is(...) met hun basiscel, wat LibreOffice herkent, en sinds 2.384.69 worden waarde- en formuleregels ook geschreven als standaard style:map-condities, de enige vorm die Excel leest, terwijl de importkant beide vormen leest. Een volledige cyclus .xlsx naar .ods naar .xlsx op een huidige release behoudt dus waarden, formules, stijlen en voorwaardelijke opmaken; wat hij alsnog kan verliezen, ligt in de gaten die in de vorige sectie zijn opgesomd
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // formaat automatisch gedetecteerd
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Sinds HotXLS 2.88.229 komen formules levend terug naast hun
// gecachte resultaten; herstel elke formule die de downstream
// pipeline niet als vertaald vertrouwt vóórdat je opslaat.
Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
Book.SaveAs('vendor-revision.xlsx');
end;
finally
Book.Free;
end;
Het architecturale patroon dat hieruit voortvloeit: behandel binnenkomende .ods-bestanden als datafeeds, niet als documenten om ter plekke te bewerken. Houd de canonieke werkmap in .xlsx, lees waarden uit klantrevisies, en genereer verse ODS op aanvraag vanuit de canonieke kopie. Verificatie hoort thuis in beide kampen - open geëxporteerde bestanden in LibreOffice Calc, de referentie-ODF-consument, en in Excel, dat al jaren ODS leest maar aan de randen van grafiek- en stijlondersteuning van LibreOffice verschilt. Bladaantal, een handvol sleutelcellen en de aanwezigheid van grafieken vormen een voldoende smoke-check per exportprofiel
Een ODS-bestand triageren vóórdat je aan een import commit
Wanneer een endpoint uploads accepteert, is het opsommen van bladnamen veel goedkoper dan een volledige parse en vangt het structurele verrassingen vroeg op:
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;
De returnconventie doet mensen struikelen: HotXLS-aanroepen geven doorgaans een positief aantal of 1 terug bij succes en -1 bij falen, waarbij de lijst wordt geleegd bij falen, dus test op <= 0 in plaats van te vergelijken met één specifieke positieve waarde. GetODSSheetNames reset noch vult de workbook-instantie, dus één probe-object kan een hele map met binnenkomende bestanden keuren. Structurele controles zoals deze vangen de meest voorkomende praktijkfout - een analist die een blad hernoemt of verwijdert voordat hij de revisie terugstuurt - af bij de poort, waar de foutmelding nog steeds het bestand en het ontbrekende blad kan noemen in plaats van drie lagen dieper op te duiken als een nil-referentie
Als je hier een bredere conversiepipeline omheen bouwt, laat het patroon voor de workbook-audit- en conversiewerkbank zien hoe je de functies van een bestand inventariseert voordat je een doelformaat kiest, en de handleiding voor prestaties van grote werkmappen houdt batch-exports binnen redelijke geheugengrenzen
HotXLS is een native spreadsheetbibliotheek voor Delphi en C++Builder met volledige broncode; de complete functielijst en licentiedetails staan op de productpagina van HotXLS Delphi Component