Odborný článok

Bezstratový prenos XLSX (Round-Trip) v Delphi: Téma, extLst, calcChain

Knižnica HotXLS, natívna Excel knižnica pre Delphi a C++Builder, je navrhnutá pre bezstratový prenos XLSX (round-trip): otvoriť zošit, zmeniť jednu bunku, uložiť a vlastná téma zákazníka, cudzie rozširujúce bloky extLst aj kalkulačný reťazec (calculation chain) prežijú. Tri mechanizmy to umožňujú — doslovné ukladanie xl/theme/theme1.xml do vyrovnávacej pamäte, opätovná serializácia neznámych blokov <ext> na základe udalostí a vytvorenie nového, podľa špecifikácie platného súboru xl/calcChain.xml pri každom uložení zošita so vzorcami

Tri mechanizmy za bezstratovou obojsmernou cestou HotXLS XLSX v Delphi: xl/theme/theme1.xml cachovaný ako surové bajty a zapísaný späť bajtovo identicky, cudzie bloky extLst zachytené z udalostí XML a prehrané, a čerstvý spec-validný calcChain.xml vyslaný pri každom uložení vzorca
Každá zachovaná časť kráča vlastnou cestou cez uloženie — theme bajty doslova, event-level extLst replay a znovu vygenerovaný výpočtový reťaz — zatiaľ čo XML pracovného hárka a štýly sa znovu vybudujú z modelu

Scenár, ktorý motivuje všetky tri mechanizmy, je až frustrujúco bežný. Fakturačná služba načíta šablónu, ktorú zákazník navrhol v Exceli — firemnú farebnú tému, krivky (sparklines) v KPI stĺpci, pravidlo podmieneného formátovania pridané novšou verziou Excelu — zapíše celkovú sumu faktúry do bunky B3 a uloží ju. Zákazník otvorí výsledok a farby značky sa vrátia na predvolenú modrú farbu Office, krivky zmiznú a Excel ponúkne možnosť „opraviť“ súbor. Nič v kóde sa týchto funkcií nedotklo. Urobila to knižnica, jednoducho tým, že súbor uložila

Prečo Excel súbory po úprave knižnicou strácajú formátovanie?

Excel súbory strácajú formátovanie po úprave knižnicou, pretože väčšina knižníc súbor neupravuje — rekonštruujú ho. Balík .xlsx je archív ZIP obsahujúci časti XML: xl/workbook.xml, jeden súbor xl/worksheets/sheetN.xml na hárok, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml a ďalšie. Typická knižnica tieto časti pri otvorení analyzuje do objektového modelu a pri uložení z tohto modelu vygeneruje každú časť znova. Akákoľvek funkcia, ktorou model nereprezentuje — téma, ktorú nikdy neanalyzoval, alebo rozširujúci blok z novšieho Excelu — nemá v pamäti kde existovať, takže pregenerovaná časť ju potichu vynechá

Štandard ECMA-376 predvídal polovicu tohto problému. Súčasť SpreadsheetML definuje element extLst (ECMA-376 Časť 1, „Future Feature Data Storage Area“, §18.2.10 pre úroveň zošita) ako určený bod rozšírenia: novšie aplikácie tam ukladajú funkcie, z ktorých každá je zabalená v elemente <ext> nesúcom atribút uri, ktorý identifikuje danú funkciu, pričom od starších čítačiek sa očakáva, že zachovajú to, čomu nerozumejú. Krivky, rýchle filtre (slicers) a novšie typy podmieneného formátovania sa prenášajú týmto spôsobom. Knižnica, ktorá zahadzuje neznáme bloky <ext>, nie je len stratová — porušuje dohodu o spätnej kompatibilite, na ktorej bol formát navrhnutý. Otázka na akúkoľvek knižnicu pre tabuľkové procesory, ktorou hodnotíte, je priama: ak zmením jednu bunku, čo všetko sa zmení?

Ako HotXLS zachováva vlastnú tému bajt po bajte?

HotXLS zachováva tému zošita tak, že pri otvorení uloží pôvodné bajty súboru xl/theme/theme1.xml do vyrovnávacej pamäte a pri uložení ich zapíše doslovne späť. Časť témy (ECMA-376 Časť 1, §14.2.7) je DrawingML a nie SpreadsheetML — farebné schémy, schémy písiem, schémy formátov — a tabuľkový procesor nemá dôvod ju podrobne modelovať. Staršie verzie HotXLS regenerovali pri každom uložení fixnú tému Office, čo vledlo presne k zlyhaniu „navrátenia farieb značky“ popísanému vyššie; od verzie v2.89.46 sa téma otvoreného balíka ukladá v surovom stave a znova vygeneruje nedotknutá, pričom vstavaná téma Office sa generuje len pre zošity vytvorené od nuly. Surové bajty sú najsilnejšou možnou zárukou vernosti: žiadna analýza, žiadna opätovná serializácia, žiadna šanca na odchýlku

Doslovná kópia má zámerne prednosť pred programovým prístupom k téme. Trieda TXLSXWorkbook sprístupňuje vlastnosti ThemeMajorFont a ThemeMinorFont, takže môžete vybrať písma pre nadpisy a telo pre nové zošity, ale ak bola pri otvorení zachytená téma, tieto nastavenia nemajú na uložený súbor žiadny vplyv — prednosť má vernosť prenosu. Ak naozaj potrebujete zmeniť tému existujúceho zošita, je to signál pre úpravu šablóny priamo v Exceli a nie cez dátovo orientované API. Každodenný prípad nevyžaduje vôbec žiadne API:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('branded-invoice.xlsx');
    Book.Sheets[0].Cells[3, 2].Value := 42750.00;  // jedna úprava
    Book.SaveAs('branded-invoice-out.xlsx');
    // theme1.xml vo výstupe je bajtovo identická so vstupom
  finally
    Book.Free;
  end;
end;

Čo sa stane s neznámymi blokmi extLst pri uložení?

HotXLS zachytáva každý blok <ext> na úrovni hárka, ktorý natívne nemodeluje, a prehrá ho do extLst uloženého hárka, takže funkcie zapísané novšími verziami Excelu prežijú prenos nedotknuté. Od verzie v2.131.0 sú zachytené fragmenty viditeľné cez vlastnosť RawWorksheetExts určenú len na čítanie, čo je TStringList na každom XLSX hárku, vďaka čomu je táto záruka overiteľná z testovacieho kódu a nie len predmetom viery:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  i: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('from-newer-excel.xlsx');
    Sheet := Book.Sheets[0];
    WriteLn(Format('%d foreign ext block(s) captured',
      [Sheet.RawWorksheetExts.Count]));
    for i := 0 to Sheet.RawWorksheetExts.Count - 1 do
      WriteLn(Copy(Sheet.RawWorksheetExts[i], 1, 100)); // nazrite do každého uri
  finally
    Book.Free;
  end;
end;

Detail implementácie, ktorý stojí za to poznať, je, že zachytenie je opätovná serializácia na úrovni udalostí a nie kópia surových bajtov. Streaming XML čítač v HotXLS neposkytuje žiadne zdrojové posuny, takže neznámy podstrom sa rekonštruuje z udalostí Element, Text a EndElement, ako prechádzajú tokom. Tento prístup skrýva jednu klasickú pascu: samozatvárací element, napríklad <a/>, vyvolá iba udalosť Element s príznakom empty a nikdy nie EndElement, takže počítadlo hĺbky, ktoré sa znižuje výhradne pri EndElement, nikdy neuvidí uzavretie podstromu. Ak to spracujete, prestavaný fragment je sémanticky ekvivalentný originálu — citovanie atribútov a samozatváracie formy sú normalizované, takže nie je bajtovo identický, ale Excel číta význam, nie bajty. Dve vlastnosti vlastného výstupu Excelu robia prenos bezpečným: Excel deklaruje potrebné atribúty xmlns na elemente <ext> alebo vo vnútri neho, takže každý zachytený fragment je samostatný z hľadiska názvových priestorov, a táto samostatnosť je tiež dôvodom, prečo duplikovanie hárka vo vnútri alebo naprieč zošitmi môže preniesť cudzie bloky pomocou jednoduchého priradenia string-listu

HotXLS cachuje surové bajty xl/theme/theme1.xml pri otvorení a zapisuje ich späť bajtovo identicky pri ukladaní, zatiaľ čo knižnica prestavujúca z modelu vygeneruje štandardnú tému Office a vráti značkové farby zákazníka naspäť
Cachovanie theme1.xml doslova nepotrebuje vôbec žiadny theme model a ThemeMajorFont s ThemeMinorFont štýlujú len zošity, ktoré neniesu žiadny zachytený theme

Zápis calcChain.xml, aby Excel dôveroval vašim vzorcom

HotXLS zapisuje xl/calcChain.xml (časť Calculation Chain, ECMA-376 Časť 1, §12.3.1) zakaždým, keď uložený zošit obsahuje vzorce, pričom si vyberá medzi dvoma usporiadaniami. Ak bol graf závislostí vzorcov už vytvorený a je aktuálny — zavolali ste Recalculate po poslednej úprave — reťazec sa vygeneruje v plnom topologickom poradí, závislosti pred nasledovníkmi, s prípadnými členmi kruhového odkazu pripojenými na konci. V opačnom prípade sú bunky uvedené v poradí dokumentu. Obe možnosti sú správne: poznámky k implementácii formátu od spoločnosti Microsoft, [MS-XLSX], pristupujú k tomuto reťazcu ako k pomôcke, ktorú Excel pri načítaní overuje a reorganizuje, takže akýkoľvek kompletný zoznam je legálny a HotXLS zámerne nevynucuje zostavenie grafu vo vnútri metódy SaveAs — vytváranie hrán je kvadratické k počtu buniek, čo by bol neprijateľný skrytý náklad pri ukladaní zošita s miliónom buniek

Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Uložené teraz, calcChain.xml uvádza bunky so vzorcami v poradí dokumentu.
// Po Recalculate existuje graf závislostí, takže to isté uloženie
// namiesto toho vydá plné topologické poradie:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');

Prečo sa starať o časť, ktorú Excel považuje len za pomôcku? Pretože jej absencia je signál. Niektorí konzumenti — opravné heuristiky, prehliadače tretích strán, diff nástroje — očakávajú, že zošit so vzorcami bude obsahovať kalkulačný reťazec, a knižnica, ktorá túto časť pri ukladaní potichu zahodí, produkuje súbory jemne odlišné od čohokoľvek, čo Excel zapisuje. Zapísanie platného reťazca udržiava výstup v rámci toho, proti čomu bol zvyšok ekosystému testovaný, čo je tiché, neglamurálne jadro inžinierstva round-trip

Kde bezstratový prenos končí

Úprimnosť tu zaváži viac než marketingové zaškrtávacie políčko, preto si limity zaslúžia rovnakú pozornosť. HotXLS nekopíruje celý balík bajt po bajte: XML hárkov, štýly, zdieľané reťazce a časti zošita sa generujú z analyzovaného modelu, takže výstup je sémanticky verný, ale nie binárne identický — uzly lokálnych hlavičiek ZIP nesú nové DOS časové pečiatky. Zachytené fragmenty <ext> sa vracajú normalizované, ako je popísané vyššie. Programové prepisy písiem témy sa ignorujú, ak je prítomná doslovná téma. A záchranná sieť má definovanú veľkosť oka: obsahuje funkcie, ktoré HotXLS modeluje natívne (napríklad krivky sú analyzované a prepisované, nie slepo kopírované), plus cudzí obsah extLst, plus časti uložené doslovne vo vyrovnávacej pamäti. Časť, ktorá nie je modelovaná ani sa nenachádza v bode rozšírenia — napríklad vlastná časť nejakého exotického doplnku — spadá mimo troch mechanizmov, o ktorých hovorí tento článok, preto testujte svoje reálne šablóny a nepredpokladajte

HotXLS vysiela xl/calcChain.xml v topologickom poradí, keď Recalculate postavil graf závislostí, a inak v poradí dokumentu; Excel zaobchádza s ktorýmkoľvek úplným výpisom ako s tipom a pri načítaní ho preusporiada
Oba usporiadania zostávajú legálne, pretože Excel znovu overí reťaz pri načítaní a HotXLS nikdy nevynúti kvadraticky drahé vybudovanie grafu vo vnútri SaveAs

Súvisiace konzervačné práce dopĺňajú obraz. Projekty VBA a odkazy na externé zošity prechádzajú uložením s rovnakou filozofiou zachovania-toho-čo-nemodelujeme, popísanou v sprievodnom článku o zachovaní projektov VBA a externých odkazov, pričom vlastnosti dokumentu v docProps majú svoje vlastné API pre čítanie a zápis a nie sú potichu zahodené. Keď hodnotíte akúkoľvek knižnicu pre tabuľkové procesory, spustite test s jednou bunkou: otvorte produkčný zošit plný funkcií, zmeňte jednu hodnotu, uložte ho a porovnajte rozbalené časti s originálom. To, čo sa zmenilo mimo hárka, ktorého ste sa dotkli, vám o knižnici povie viac než akákoľvek tabuľka vlastností

Mechanizmy prenosu (round-trip) popísané v tomto článku — doslovné zachovanie témy od verzie v2.89.46, zachytenie cudzích extLst a generovanie calcChain.xml od verzie v2.131.0 — sa dodávajú v aktuálnom komponente HotXLS Delphi Excel Component, ktorého stránka produktu dokumentuje kompletnú sadu funkcií pre čítanie a zápis formátu XLSX pre Delphi a C++Builder