Technický článek

Oprava jednoho listu ve velkém souboru XLSX z Delphi

HotXLS dokáže přepsat jeden list uvnitř existujícího balíčku XLSX bez parsování nebo opětovné komprese zbytku souboru. TXLSDirectWriter.BeginPatch otevře zdrojový balíček, zkopíruje každou položku kromě cílového listu se svými komprimovanými bajty beze změny a umožní vám tento jediný list přepsat obvyklými voláními AddSheet, AddRow a Write*. Grafy, mezipaměti kontingenčních tabulek, motivy, styly a sdílené řetězce se vůbec nedekomprimují

Pracovní postup, který to řeší, se objevuje při reportingu a obnově dat. Sešit přijde od obchodního týmu s kontingenčními tabulkami, průřezy, podmíněným formátováním a deseti lety nahromaděného formátování. Každou noc je třeba jeden datový list nahradit čerstvými čísly. Načtení a opětovné uložení celého sešitu stojí minuty na soubor a, což je důležitější, riskuje věrnost u funkcí, které musí načítací modul rekonstruovat. Oprava (patching) se oběma problémům vyhýbá tím, že se nedotýká toho, čeho se dotýkat nemusí

Proč je zajímavou částí právě kopírování komprimovaných bajtů?

Položka zip zkopírovaná na komprimované úrovni stojí jen kopii proudu. Stejná položka provedená běžnou zapisovací cestou stojí rozbalení při vstupu a komprimaci při výstupu, a komprimace je ta nákladnější polovina. U sešitu s velkou mezipamětí kontingenční tabulky a několika desítkami vložených obrázků je tento rozdíl rozdílem mezi opravou, která doběhne za dobu potřebnou k zapsání nového listu, a opravou, která stráví většinu času opětovnou kompresí bajtů, jež nikdy neprozkoumala

HotXLS k tomu používá CopyCompressedFrom, který zapíše komprimované bajty zdrojové položky přímo do cílového archivu. Když položku tímto způsobem zkopírovat nelze, protože používá jinou kompresní metodu nebo slabé šifrování, zapisovač se vrátí ke kopii dekomprimovaného proudu, místo aby selhal. Značkové položky adresářů se přeskakují, protože zapisovač vytváří vlastní

Nahrazení na místě, nebo zápis do nového souboru

Dvě přetížení pokrývají dvě podoby tohoto úkolu. Forma na místě připraví výsledek v dočasném souboru vedle originálu, uzavře zdrojový handle a poté smaže a přejmenuje, takže pád uprostřed zápisu ponechá originál nedotčený. Forma s explicitním cílem ponechá zdroj nedotčený a může buď nahradit list, nebo připojit nový:

var
  W: TXLSDirectWriter;
begin
  W := TXLSDirectWriter.Create;
  try
    W.BeginPatch('monthly-dashboard.xlsx', 'Data');   // na místě
    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;

Varianta pro vložení přebírá zdrojovou a cílovou cestu plus InsertSheet:

  // Zdroj zůstává nedotčený; cíl dostane navíc list nazvaný 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;

Vkládání je ta část, která vyžaduje skutečně chirurgickou práci s účetnictvím balíčku. Zapisovač analyzuje registr listů v xl/workbook.xml a mapu vztahů, která váže každý list na jeho část, a poté zvolí další volné číslo části, identifikátor listu a identifikátor vztahu. Typy vztahů se řídí konvencemi zdrojového balíčku, takže oprava striktního sešitu ISO 29500 vydá striktní typy vztahů a oprava přechodného sešitu vydá přechodné typy

Co oprava záměrně zahazuje a omezuje

Řetěz výpočtů se zahazuje v obou režimech. V režimu nahrazení jeho položky popisují buňky v listu, který v této podobě už neexistuje; v režimu vložení jej posun indexu listů rovnou znehodnotí. Excel řetěz přestaví při dalším přepočtu, takže jeho zahození je správné, nikoli ztrátové. Tato část je z kopírování vynechána a její položka vztahu i přepis typu obsahu jsou odstraněny chirurgicky

Uvnitř opravy se mění dvě sémantiky tvorby obsahu, a obě plynou ze stejného principu: oprava nesmí narušit části, které nepřepsala. Řetězce se zapisují přímo do listu místo do tabulky sdílených řetězců, protože zdrojová tabulka se přenáší nedotčená. A StyleIndex odkazuje na položky v cellXfs zdrojového balíčku, ne na tabulku stylů, kterou by sestavil zapisovač. To znamená, že můžete odkazovat na formáty, které původní sešit už definuje, což je obvykle přesně to, co obnova dat potřebuje, ale zároveň to znamená, že musíte vědět, který index nese který formát

// Uvnitř opravy StyleIndex indexuje cellXfs ZDROJOVÉHO balíčku.
// Datum potřebuje explicitní index, který se tam mapuje na formát data:
W.WriteDateTime(3, EncodeDate(2026, 8, 22), DateStyleIndexFromTemplate);

// Přetížení WriteDateTime bez stylu je v režimu opravy odmítnuto,
// protože předpokládá vlastní tabulku stylů zapisovače, kterou oprava
// nikdy nevytváří

Šest vstupních bodů pro tvorbu obsahu je zablokováno: přidávání tabulek, grafů, obrázků, komentářů, definovaných názvů a stylů buněk vyvolá v režimu opravy výjimku, s druhou pojistkou při uzavření, která selže, pokud je kterýkoli z jejich čítačů nenulový. Každá z těchto funkcí by vyžadovala úpravu částí, které oprava kopíruje beze změny, a napůl upravený balíček je horší než odmítnutá operace. Za jednu operaci lze opravit přesně jeden list

Kdy opravovat a kdy načítat

Oprava je správný nástroj, když je sešit velký, změna je omezená na jeden list a zbytek souboru musí přežít bit po bitu. Je to špatný nástroj, když změna zasahuje více listů, když je potřeba nové formátování nebo nové objekty, nebo když je soubor dost malý na to, aby běžné načtení a uložení nestálo nic. Pro hromadné generování od nuly zůstává lepší volbou streamovací cesta popsaná v článku streamovací přímý zapisovač, a sdílí stejné API AddRow a Write*, takže přechod mezi oběma přístupy je mechanický

Manipulace na úrovni listu uvnitř načteného sešitu, když skutečně chcete plný objektový model, je popsána v článku duplikace listů v balíčcích XLSX. A pokud důvodem, proč zvažujete opravu, je to, že zpracování celého sešitu zpomalilo, stojí za přečtení měření a chování paměti v článku výkon velkých sešitů ještě před volbou přístupu

Ověření, že oprava skutečně udělala to, co si myslíte

Tři kontroly zachytí téměř každou chybu. Potvrďte, že části, u kterých jste očekávali přežití, jsou stále v archivu, že xl/calcChain.xml zmizel a že opětovné otevření souboru přes TXLSXWorkbook hlásí počet listů, který očekáváte, nezměněný u nahrazení a zvýšený o jeden u vložení. Zpětné načtení opraveného listu a porovnání několika hodnot a vzorců uzavírá kruh

Jeden implementační detail z vývoje této funkce stojí za zopakování, protože může zasáhnout kohokoli, kdo píše podobný kód na úrovni zip. Názvy částí listů se porovnávají podle prefixu, a chyba o jedna v délce prefixu znamená, že predikát nikdy neodpovídá, takže nově zapsaná část koliduje s existujícím názvem a čtečky, které berou poslední položku s daným názvem, tiše vyberou špatný list. Pokud oprava zdánlivě prohodí obsah dvou listů, podívejte se nejprve na porovnávání názvů, než na XML

Oprava na místě, streamovací zápisy a plný objektový model sešitu jsou součástí stejné knihovny pro Delphi a C++Builder; seznam funkcí je na stránce komponenty HotXLS pro tabulky v Delphi