Technisch artikel

HotXLS Delphi Component: ODS open, save, and round-trip in Delphi

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

Diagram van de HotXLS-klasselay-out in Delphi waarin elk ODS-ingangspunt op TXLSXWorkbook leeft en een SaveXLSWorkbookAsXLSX-brug BIFF8 .xls-content overdraagt
Elk OpenDocument-entrypunt hangt aan TXLSXWorkbook, en een legacy .xls bereikt ODS alleen via de verlieslatende BIFF-naar-XLSX-brug

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

Diagram van de HotXLS ODS-roundtrip vanuit Delphi: export vanuit het in-memory werkboekmodel en een import die vroege releases beperkten tot waarden, waardoor formules vóór HotXLS 2.88.229 constanten bleven
Vroege releases importeerden alleen waarden en gecachte resultaten, dus een volledige .xlsx-naar-.ods-naar-.xlsx-cyclus liet stijlen en levende formules vallen; huidige releases lezen beide terug
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:

Diagram van de HotXLS-uploadtriagepoort in Delphi waarin GetODSSheetNames onleesbare ODS-pakketten en ontbrekende bladen weigert voordat een volledige import draait
Een GetODSSheetNames-probe kost veel minder dan een volledige parse en vangt de hernoemd-sheet-fout terwijl de fout het bestand nog kan benoemen
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