Technical Article

Otváranie a ukladanie tabuliek ODS v Delphi pomocou HotXLS

Reportovací backend v Delphi, ktorý roky generoval súbory .xlsx, získava novú požiadavku: pravidlá verejného obstarávania u zákazníka z verejného sektora vyžadujú výstup vo formáte OpenDocument Spreadsheet a analytici posielajú svoje úpravy späť ako súbory .ods uložené z programu LibreOffice. Ten istý kód tak teraz musí formát ODS zapisovať aj čítať. HotXLS, natívna knižnica pre tabuľky v Object Pascale od losLab pre Delphi a C++Builder, zvláda oba smery bez potreby inštalácie Excelu alebo LibreOffice. Čo však nerobí, je symetria oboch smerov. Export prenáša oveľa viac vlastností než import dokáže obnoviť, a tím, ktorý predpokladá opak, bude sledovať, ako sa vzorce a formátovanie strácajú kdesi medzi revíziou od zákazníka a ďalším reportom bez toho, aby bol k dispozícii akýkoľvek chybový log.

Podpora ODS žije v rozhraní XLSX, nie v XLS

HotXLS dodáva dve nezávislé hierarchie tried v jednom balíku: TXLSWorkbook v jednotke lxHandle pre binárne súbory .xls (BIFF8) a TXLSXWorkbook v jednotke lxHandleX pre balíky OOXML .xlsx. Každý vstupný bod pre OpenDocument (metódy OpenODS, SaveAsODS, GetODSSheetNames) patrí pod triedu TXLSXWorkbook. Toto umiestnenie nie je náhodné. Balík ODS, ako ho definuje špecifikácia OASIS ODF 1.3, je zip archív obsahujúci súbor mimetype, manifest a telo content.xml, čo z neho robí štrukturálneho príbuzného s formátom OOXML zip; formát BIFF8 je binárny prúd záznamov z 90. rokov, ktorý s nimi nemá nič spoločné.

Toto umiestnenie má aj praktickú stránku: staršie zošity .xls nie je možné previesť na .ods jedným volaním. Najprv musíte preniesť obsah BIFF do modelu XLSX pomocou funkcie SaveXLSWorkbookAsXLSX z jednotky lxXlsxExport, výsledok znova otvoriť prostredníctvom TXLSXWorkbook a až potom exportovať. Tento prechod však nie je bezstratový a pred integráciou je dobré poznať obmedzenia. Kopírujú sa hodnoty, vzorce, číselné formáty, písma, výplne a šírky stĺpcov; vypúšťajú sa orámovania, zlúčené rozsahy, komentáre, grafy a podmienené formátovanie. Zdroj .xls s bohatým formátovaním sa tak do formátu ODS prenesie v zjednodušenej podobe, čo je vlastnosť samotného prevodného mostíka, nie zapisovača ODS.

Detekcia na strane importu je automatická. Bežná metóda Open rozpozná balík ODS podľa súboru mimetype, a ak tento chýba, vráti sa k overeniu hlavného súboru content.xml. Vďaka tomu univerzálna cesta „otvor čokoľvek, čo používateľ nahral“ nepotrebuje vlastnú kontrolu prípon. Po otvorení vlastnosť SourceFormat uvádza, ktorý formát sa reálne spracoval.

Export do ODS pomocou TODSExportOptions

Samotné volanie exportu je záležitosťou jedného riadku; objekt volieb okolo neho nesie rozhodnutia, na ktoré sa neskôr môže pýtať napríklad kontrola kvality:

var
  Book: TXLSXWorkbook;
  Opts: TODSExportOptions;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('quarterly-report.xlsx');
    Opts := TODSExportOptions.Create;        // caller owns and frees this
    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;

Objekt volieb vlastní volajúci kód. HotXLS ho neuvoľní, preto je vnútorná konštrukcia try..finally povinná. Dve vlastnosti, ktoré reálne menia výstup a nielen ho označujú, si zaslúžia podrobnejší pohľad. Nastavenie IncludeCharts := False urobí viac, než len skrytie grafov: úplne odstráni poddokumenty grafov a ich záznamy v manifeste z balíka, čo je presne to, čo potrebujete pri dátovom spracovaní, ktoré by na nich mohlo zlyhať. Vlastnosť Generator prepisuje reťazec meta:generator v ODF, ktorý inak uvádza HotXLS/<version>; prepíšte ho, ak nadväzujúce nástroje overujú tvorcov súborov na účely smerovania podpory. Ak nič z toho nepotrebujete, objekt volieb vynechajte. Volanie SaveAs(FileName, xlsxOpenDocumentSpreadsheet) je zhodné s metódou SaveAsODS s predvolenými hodnotami, pričom preťaženia prúdov na oboch stranách umožňujú zapísať balík priamo do HTTP odpovede bez dočasného súboru.

Čo cesta importu číta – a čo zámerne vynecháva

Túto časť čítajte pozorne skôr, ako niekomu sľúbite obojstrannú vernosť prevodu. Import ODS v knižnici HotXLS je zámerne navrhnutý ako ľahká cesta. Zachováva skalárne hodnoty buniek a vypočítanú hodnotu, ktorú mal každý vzorec v čase uloženia, a rozširuje opakujúce sa riadky a stĺpce do tabuľky. Neprenáša však štýly, vzorce v syntaxi ODS ani kresby.

Rozhodnutie ohľadom vzorcov je tým najčastejším zdrojom komplikácií a bolo prijaté zámerne. Bunka ODF ukladá dve veci vedľa seba: samotný vzorec zapísaný v syntaxi OpenFormula (definovaný v špecifikácii ODF 1.3, časť 4) a poslednú hodnotu, ktorú preň aplikácia vypočítala. Preklad z OpenFormula do syntaxe vzorcov Excelu je samostatný prekladateľský problém s rizikami okolo sady funkcií, syntaxe odkazov a chybových modelov. Čítanie uloženej hodnoty namiesto vzorca obchádza celú túto triedu tichých chýb pri preklade, takže čísla, ktoré importujete, sú presne tie čísla, ktoré odosielateľ naposledy videl. Daňou za to je, že prichádzajú ako konštantné čísla, a nie ako živé vzorce.

Z toho priamo vyplýva možný chybový stav, pre ktorý musíte navrhnúť systém: tabuľka, ktorej súčty boli pri poslednom uložení v LibreOffice správne, sa síce naimportuje so správnymi číslami, no tieto čísla sú teraz pevnými hodnotami. Upravte vstupnú bunku, prepočítajte zošit a nič sa nezmení – vzorec chýba, zostal len jeho konečný výsledok. Ak proces po importe vyžaduje živé vzorce, vytvorte ich programovo podľa vlastných obchodných pravidiel cez vlastnosť Cell.Formula, ktorá v rozhraní XLSX prijíma výraz bez úvodného znaku rovnosti.

Návrh systému okolo nesymetrického prevodu

Export vykresľuje z plného pamäťového modelu zošita: hodnoty, štýly a voliteľne aj grafy a obrázky. Import vracia iba hodnoty. Smer z .xlsx do .ods je teda vysokoverný, zatiaľ čo smer z .ods do .xlsx prináša späť hodnoty a uložené výsledky vzorcov, no bez formátovania a živých vzorcov. Ich spojením sa táto nesymetria znásobuje. Úplný cyklus z .xlsx do .ods a späť do .xlsx zapíše všetko správne smerom von, no stratí štýly a vzorce smerom dnu, hoci v žiadnom kroku nedošlo k chybe.

Book := TXLSXWorkbook.Create;
try
  Book.Open('vendor-revision.ods');          // format auto-detected
  if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
  begin
    // Values and cached formula results are present after an ODS
    // import; styles and live formulas are not. Rebuild whatever
    // the downstream pipeline depends on before saving.
    Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
    Book.SaveAs('vendor-revision.xlsx');
  end;
finally
  Book.Free;
end;

Z toho vyplývajúci architektonický vzor: pristupujte k prichádzajúcim súborom .ods ako k dátovým zdrojom, nie ako k dokumentom, ktoré budete upravovať priamo na mieste. Udržiavajte kľúčový zošit vo formáte .xlsx, čítajte hodnoty z revízií od zákazníkov a na požiadanie generujte nové súbory ODS z tohto kľúčového súboru. Overovanie patrí na obe strany – otvárajte exportované súbory v programe LibreOffice Calc (referenčnom programe pre formát ODF) a v Exceli, ktorý formát ODS podporuje už roky, no na hraniciach podpory grafov a štýlov sa s LibreOffice rozchádza. Počet hárkov, niekoľko kľúčových buniek a prítomnosť grafu predstavujú dostatočné overenie pre každý exportný profil.

Kontrola súboru ODS pred spustením importu

Keď koncový bod prijíma nahrané súbory, výpis názvov hárkov je oveľa lacnejší ako plná analýza a zachytí konštrukčné prekvapenia včas:

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;

Konvencia návratových hodnôt môže byť mätúca: volania HotXLS vo všeobecnosti vracajú kladný počet alebo 1 pri úspechu a -1 pri zlyhaní (pričom pri zlyhaní vyprázdnia zoznam), preto testujte podmienku <= 0 a neporovnávajte výsledok len s jedným konkrétnym kladným číslom. Metóda GetODSSheetNames neinicializuje ani neplní inštanciu zošita, takže jeden testovací objekt dokáže preveriť celý adresár prichádzajúcich súborov. Štrukturálne overenia tohto typu zachytia najbežnejšiu chybu v praxi – premenovanie alebo vymazanie hárka analytikom pred odoslaním revízie späť – priamo na vstupe, kde chybové hlásenie môže uviesť konkrétny súbor a chýbajúci hárok, namiesto toho, aby sa prejavilo ako prázdny odkaz (nil reference) o tri úrovne hlbšie.

Ak staviate širší proces konverzie, článok o audite zošitov a konverznom nástroji ukazuje, ako inventarizovať vlastnosti súboru pred výberom cieľového formátu, a sprievodca výkonom veľkých zošitov pomáha udržať dávkové exporty v rozumných pamäťových medziach.

HotXLS je natívna knižnica pre tabuľky v Delphi a C++Builderi s kompletnými zdrojovými kódmi; úplný zoznam funkcií a licenčné podrobnosti nájdete na produktovej stránke komponentu HotXLS.