Odborný článok

Oprava jedného hárka vo veľkom XLSX z Delphi

HotXLS dokáže prepísať jeden hárok vo vnútri existujúceho balíka XLSX bez parsovania alebo opätovnej kompresie zvyšku súboru. TXLSDirectWriter.BeginPatch otvorí zdrojový balík, skopíruje každú položku okrem cieľového hárka s jej komprimovanými bajtmi doslovne a umožní vám tento jediný hárok znovu zostaviť pomocou bežných volaní AddSheet, AddRow a Write*. Grafy, kontingenčné cache, témy, štýly a zdieľané reťazce sa vôbec nedekomprimujú

Pracovný postup, ktorý toto rieši, sa objavuje pri reportovaní a obnove dát. Zošit príde od obchodného tímu a nesie kontingenčné tabuľky, prieniky, podmienené formáty a desaťročie nahromadeného formátovania. Každú noc treba jeden dátový hárok nahradiť čerstvými číslami. Načítanie a opätovné uloženie celého zošita stojí minúty na súbor, a čo je dôležitejšie, riskuje vernosť funkcií, ktoré musí nástroj na načítanie zrekonštruovať. Opravovanie sa vyhýba obom problémom tým, že sa nedotýka toho, čoho sa dotýkať nemusí

Prečo je kopírovanie komprimovaných bajtov tou zaujímavou časťou?

Položka zip, ktorá sa kopíruje na komprimovanej úrovni, stojí iba kopírovanie streamu. Tá istá položka prevedená bežnou cestou zápisu stojí dekompresiu (inflate) na vstupe a kompresiu (deflate) na výstupe, a deflate je tá drahšia polovica. Pri zošite s veľkou kontingenčnou cache a niekoľkými desiatkami vložených obrázkov je tento rozdiel rozdielom medzi opravou, ktorá sa dokončí za čas potrebný na zápis nového hárka, a opravou, ktorá strávi väčšinu času opätovnou kompresiou bajtov, ktoré nikdy neskúmala

HotXLS na to používa CopyCompressedFrom, ktorá zapíše komprimované bajty zdrojovej položky priamo do cieľového archívu. Keď sa položka takto skopírovať nedá, pretože používa inú metódu kompresie alebo slabé šifrovanie, zapisovač namiesto zlyhania použije náhradnú dekomprimovanú kópiu streamu. Značkovacie položky adresárov sa preskakujú, keďže zapisovač si vytvára vlastné

Nahradenie na mieste, alebo zápis do nového súboru

Dve preťaženia pokrývajú dve podoby, ktoré táto úloha nadobúda. Forma na mieste pripraví výsledok v dočasnom súbore vedľa originálu, zatvorí zdrojový handle a potom vymaže a premenuje, takže pád uprostred zápisu ponechá originál nedotknutý. Forma s explicitným cieľom ponecháva zdroj nedotknutý a dokáže buď nahradiť hárok, alebo pripojiť nový:

var
  W: TXLSDirectWriter;
begin
  W := TXLSDirectWriter.Create;
  try
    W.BeginPatch('monthly-dashboard.xlsx', 'Data');   // na mieste
    W.AddSheet('Data');
    W.AddRow(1);
    W.WriteString(1, 'Region');
    W.WriteString(2, 'Revenue');
    W.AddRow(2);
    W.WriteString(1, 'North');
    W.WriteNumber(2, 184320.55);
    W.AddRow(3);
    W.WriteFormula(1, '=SUM(B2:B2)');
    W.Close;
  finally
    W.Free;
  end;
end;

Vkladací variant prijíma zdrojovú a cieľovú cestu plus InsertSheet:

  // Zdroj zostáva nedotknutý; cieľ získa navyše hárok s názvom Extra
  W.BeginPatch('template.xlsx', 'output.xlsx', 'Extra', True);
  W.AddSheet('Extra');
  W.AddRow(1);
  W.WriteString(1, 'appended by the nightly job');
  W.Close;

Vkladanie je časť, ktorá vyžaduje skutočný chirurgický zásah do evidencie. Zapisovač parsuje register hárkov v xl/workbook.xml a mapu vzťahov, ktorá viaže každý hárok k jeho časti, a potom zvolí ďalšie voľné číslo časti, identifikátor hárka a identifikátor vzťahu. Typy vzťahov sa riadia konvenciami zdrojového balíka, takže oprava prísneho zošita ISO 29500 generuje prísne typy vzťahov a oprava prechodného generuje prechodné typy

Čo oprava zámerne zahadzuje a obmedzuje

Reťazec výpočtov sa zahadzuje v oboch režimoch. V režime nahradenia jeho záznamy opisujú bunky v hárku, ktorý už v tejto podobe neexistuje; v režime vkladania ho posun indexu hárka rovno znehodnotí. Excel reťazec prestavia pri ďalšom prepočte, takže jeho zahodenie je správne, nie stratové. Táto časť sa z kópie vynechá a jej záznam vzťahu a prepísanie typu obsahu sa odstránia chirurgicky presne

Vo vnútri opravy sa menia dve sémantiky tvorby obsahu a obe vychádzajú z toho istého princípu: oprava nesmie narušiť časti, ktoré neprepísala. Reťazce sa zapisujú priamo do hárka namiesto pridania do tabuľky zdieľaných reťazcov, pretože zdrojová tabuľka sa prenáša nedotknutá. A StyleIndex odkazuje na záznamy v cellXfs zdrojového balíka, nie na tabuľku štýlov, ktorú si zapisovač vytvára. To znamená, že môžete odkazovať na formáty, ktoré pôvodný zošit už definuje, čo je zvyčajne presne to, čo obnova dát potrebuje, no zároveň to znamená, že musíte vedieť, ktorý index nesie ktorý formát

// Vo vnútri opravy StyleIndex indexuje cellXfs ZDROJOVÉHO balíka.
// Dátum potrebuje explicitný index, ktorý sa tam mapuje na formát dátumu:
W.WriteDateTime(3, EncodeDate(2026, 8, 22), DateStyleIndexFromTemplate);

// Preťaženie WriteDateTime bez štýlu je v režime opravy odmietnuté,
// pretože predpokladá vlastnú tabuľku štýlov zapisovača, ktorú
// oprava nikdy nevytvára

Šesť vstupných bodov tvorby obsahu je uzamknutých: pridávanie tabuliek, grafov, obrázkov, komentárov, definovaných názvov a štýlov buniek vyvolá v režime opravy výnimku, s druhou poistkou pri zatváraní, ktorá zlyhá, ak je niektorý z ich počítadiel nenulový. Každá z týchto funkcií by vyžadovala úpravu častí, ktoré oprava kopíruje doslovne, a napoly upravený balík je horší než odmietnutá operácia. Za jednu operáciu sa dá opraviť presne jeden hárok

Kedy opravovať a kedy načítať

Opravovanie je správny nástroj, keď je zošit veľký, zmena je obmedzená na jeden hárok a zvyšok súboru musí prežiť bit po bite. Je to nesprávny nástroj, keď zmena zasahuje viacero hárkov, keď je potrebné nové formátovanie alebo nové objekty, alebo keď je súbor dostatočne malý na to, aby bežné načítanie a uloženie nestálo nič. Pre hromadné generovanie od nuly zostáva lepšou voľbou streamovacia cesta opísaná v časti streamovací priamy zapisovač, ktorá zdieľa rovnaké API AddRow a Write*, takže prechod medzi nimi je mechanický

Manipuláciu na úrovni hárkov vo vnútri načítaného zošita, keď skutočne potrebujete plný objektový model, opisuje časť duplikovanie hárkov v balíkoch XLSX. A ak je dôvodom, prečo zvažujete opravu, spomalenie spracovania celého zošita, oplatí sa pred výberom prístupu prečítať si merania a správanie pamäte v časti výkon veľkých zošitov

Overenie, že oprava naozaj urobila to, čo očakávate

Tri kontroly zachytia takmer každú chybu. Overte, že časti, ktoré ste očakávali zachované, sú stále v archíve, že xl/calcChain.xml zmizol a že opätovné otvorenie súboru cez TXLSXWorkbook hlási očakávaný počet hárkov — nezmenený pri nahradení a zvýšený o jeden pri vložení. Spätné čítanie opraveného hárka a porovnanie niekoľkých hodnôt a vzorcov uzatvára slučku

Jeden implementačný detail z vývoja tejto funkcie stojí za zopakovanie, pretože môže postihnúť kohokoľvek, kto píše podobný kód na úrovni zip. Názvy častí hárkov sa porovnávajú podľa prefixu, a chyba o jeden (off-by-one) v dĺžke prefixu znamená, že predikát sa nikdy nezhoduje, takže novo zapísaná časť koliduje s existujúcim názvom a čítačky, ktoré berú poslednú položku s daným názvom, potichu zvolia nesprávny hárok. Ak sa zdá, že oprava zamenila obsah dvoch hárkov, pozrite sa najprv na porovnávanie názvov, až potom na XML

Oprava na mieste, streamovací zápis a plný objektový model zošita sú súčasťou tej istej knižnice pre Delphi a C++Builder; zoznam funkcií nájdete na stránke HotXLS Delphi spreadsheet component