Technical Article

Listing Sheet Names Fast in Delphi with HotXLS GetSheetNames

Niekedy je jedinou otázkou, ktorú potrebuje vstupný proces zodpovedať, otázka štrukturálna: či má tento zošit hárok s názvom „Mapping“, prípadne koľko záložiek obsahuje. Odpovedať na to zavolaním metódy Open je ten najnákladnejší spôsob. Plné otvorenie zošita naplní tabuľku zdieľaných reťazcov, dekóduje každý záznam štýlu a prejde bunky každého hárka, pretože nedokáže vedieť, že ste chceli len zoznam hárkov. Pri veľkom súbore to znamená stovky megabajtov alokácií a niekoľko sekúnd procesorového času na prečítanie zoznamu, ktorý zaberá len niekoľko kilobajtov. HotXLS, natívna knižnica pre tabuľky v Delphi od losLab, vám tento zoznam poskytne samostatne: metóda GetSheetNames vráti názvy hárkov v poradí, v akom sú v zošite, bez toho, aby vytvárala čo i len jednu bunku.

Prečo je čítanie zoznamu nenáročné

Oba tabuľkové formáty ukladajú svoj obsah blízko začiatku súboru, vďaka čomu je získanie zoznamu rýchle a jednoduché. Balík OOXML ukladá katalóg hárkov v súbore xl/workbook.xml, ktorý zostáva malý bez ohľadu na to, či zošit obsahuje desať riadkov alebo desať miliónov. Formát .xls (BIFF8) ukladá svoje záznamy BoundSheet na začiatku globálneho prúdu zošita, pred akýmikoľvek dátami buniek. Práca, ktorej sa volaním tejto metódy vyhnete, preto nie je zanedbateľná; predstavuje väčšinu súboru. Čítanie katalógu stojí rovnakých pár kilobajtov bez ohľadu na počet riadkov, zatiaľ čo plné otvorenie rastie s dátami, a pri viacmegabajtovom zošite sa tento rozdiel prejaví o niekoľko poriadkov v množstve spracovaných bajtov aj alokovanej pamäte.

Tento stály náklad je vlastnosť, okolo ktorej sa oplatí navrhnúť systém. Vstupný filter postavený na metóde GetSheetNames sa správa rovnako pri súbore s 200 riadkami ako pri 200 MB súbore, takže najpomalší súbor v dávke už nebude určovať čas potrebný na rozhodnutie, či súbor vôbec spracovávať.

Jedno volanie pre .xls, .xlsx aj formáty šablón

V rozhraní XLS metóda TXLSWorkbook.GetSheetNames číta viac než len .xls. Prijíma aj formáty založené na zip – .xlsx, .xlsm, .xltx a .xltm, pričom z archívu vyťahuje iba súbor workbook.xml. Pri binárnom súbore .xls prehľadáva záznamy BoundSheet a zastaví sa na prvom zázname EOF globálneho čiastkového prúdu, takže veľký binárny súbor spotrebuje len svoje počiatočné kilobajty. Rozhranie XLSX nesie záruku, ktorá je pre dlho bežiaci kód služby dôležitejšia, než sa na prvý pohľad zdá: metóda TXLSXWorkbook.GetSheetNames ponecháva inštanciu zošita neinicializovanú a neobsadenú, takže inštancia s otvoreným dokumentom môže preverovať iné súbory bez narušenia rozpracovanej práce. Metóda GetODSSheetNames aplikuje rovnaký prístup na balíky OpenDocument a každé z týchto volaní má preťaženie prúdu, čo umožňuje kontrolovať nahrané dáta, ktoré sa nikdy neuložia na disk.

var
  Book: TXLSXWorkbook;
  Names: TStringList;
  I: Integer;
begin
  Names := TStringList.Create;
  Book := TXLSXWorkbook.Create;
  try
    if Book.GetSheetNames('upload-7f3a.xlsx', Names) <= 0 then
      raise Exception.Create('unreadable workbook package');
    if Names.IndexOf('Mapping') < 0 then
      raise Exception.Create('required Mapping sheet is missing');
    for I := 0 to Names.Count - 1 do
      Writeln(Format('sheet %d: %s', [I, Names[I]]));
  finally
    Book.Free;
    Names.Free;
  end;
end;

Rovnaké volanie poslúži ako dobrý dialóg pre import v desktopovej aplikácii. Vypíšte hárky, nechajte používateľa vybrať si a zaplaťte za plné otvorenie až po výbere. Pri zošite s päťdesiatimi hárkami je rozdiel okamžite viditeľný: výberové okno, ktoré sa zobrazí ihneď, oproti oknu, ktoré čaká na načítanie celého súboru na pozadí.

Súbory .xlsm s podporou makier a formáty šablón sa vypisujú presne rovnako ako čisté .xlsx, keďže katalóg hárkov sa nachádza v rovnakom súbore workbook.xml bez ohľadu na to, či balík obsahuje aj vbaProject.bin. Vstupný filter tak môže vypísať hárky zošita s makrami na účely smerovania bez toho, aby sa dotkol samotného obsahu makra alebo ho spustil, a ponechať rozhodnutie o makrách na fázu, kedy sa súbor skutočne otvorí.

Vyhodnotenie návratovej hodnoty bez klamania seba samého

Návratové konvencie nie sú v HotXLS jednotné. Niektoré volania vracajú pri úspechu 1, iné vracajú počet, takže pri funkciách výpisu je jedinou správnou kontrolou považovať akúkoľvek hodnotu rovnú alebo menšiu ako nula za zlyhanie (pričom zoznam reťazcov sa vyprázdni). Odolajte pokušeniu považovať prázdny zoznam za „zošit bez hárkov“. Špecifikácia ECMA-376 aj BIFF8 vyžadujú v platnom zošite aspoň jeden hárok, takže nula názvov vždy znamená, že čítanie zlyhalo, a nie že je súbor prázdny.

Zlyhanie výpisu je samo o sebe dôležitým signálom. Súbor .xlsx, ktorý pri tomto volaní zlyhá, je zvyčajne jednou z troch vecí: je skrátený, nejde o balík OOXML (často sa tu objavujú nesprávne označené exporty do CSV z iných systémov) alebo ide o šifrovaný kontajner. Rozlíšiť tieto prípady je úlohou ďalšej kontroly. Zaznamenanie prvých bajtov odmietnutého súboru spolu so zlyhaním zvyčajne zmení siahodlhé hľadanie chyby na jednu jasnú správu.

Detekcia šifrovaných kontajnerov pred smerovaním

Šifrovaný súbor .xlsx nie je zip archív. Ide o zložený súbor OLE (OLE compound file) obaľujúci prúdy EncryptionInfo a EncryptedPackage, takže metóda GetSheetNames doň nedokáže vidieť a vráti zlyhanie ako pri akomkoľvek inom nečitateľnom súbore. Metóda CanReadEncrypted overuje túto štruktúru kontajnera, čo umožňuje vstupnej kontrole zámerne nasmerovať šifrovaný súbor inam, namiesto toho, aby spracovala generickú chybu čítania z hĺbky programu:

type
  TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);

function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // Encrypted OOXML is an OLE container, not a zip: check first,
    // because the listing calls cannot look inside it.
    if Book.CanReadEncrypted(FileName) then
      Exit(irNeedsPassword);
    if SameText(ExtractFileExt(FileName), '.ods') then
    begin
      if Book.GetODSSheetNames(FileName, Names) <= 0 then
        Exit(irUnreadable);
    end
    else if Book.GetSheetNames(FileName, Names) <= 0 then
      Exit(irUnreadable);
    Result := irNormal;
  finally
    Book.Free;
  end;
end;

Šifrovanie je oblasť, kde je HotXLS zámerne nesymetrický, a smerovanie to musí rešpektovať. Staršia ochrana súborov .xls (RC4, RC4 CryptoAPI, XOR) je čitateľná: metóda TXLSWorkbook.Open(FileName, Password) dešifruje súbor pomocou uloženého hesla a tieto súbory môžu zostať na automatizovanej ceste spracovania. Šifrované balíky OOXML idú inou cestou. HotXLS ich dokáže zapísať pomocou SaveAsEncrypted, ale nedokáže ich prečítať. Metóda OpenEncrypted vyhodí výnimku EXlsxEncryptionNotImplemented, ak dostane šifrovaný balík, čo je dôvod, prečo korektný návrh vstupu posiela šifrované súbory .xlsx na spracovanie človeku a automatizuje len heslom chránené súbory .xls.

Pri dávkovom spracovaní si tento klasifikátor nájde svoje miesto spustením nad celým adresárom prichádzajúcich súborov pred tým, než sa začne skutočné spracovanie, keďže každá kontrola stojí len jedno otvorenie súboru a pár kilobajtov čítania. Jeho predradenie mení spôsob, akým sa riešia chyby v prevádzke. Namiesto toho, aby nočná úloha zlyhala na súbore 412 zo 600, dostanete 412 súborov zaradených do frontu a 5 súborov odmietnutých na vstupe s uvedeným dôvodom zlyhania pre každý z nich. Použijú sa rovnaké volania knižnice, no s oveľa lepším výsledkom pre prevádzku.

Otázky, ktoré volanie výpisu nedokáže zodpovedať

Názvy a poradie sú všetko, čo získate. Volania výpisu nehovoria nič o viditeľnosti, takže skryté a veľmi skryté hárky prichádzajú v zozname rovnako ako ostatné. Neobsahujú rozmery použitého rozsahu, počty buniek ani vlastnosti dokumentu. Časť docProps/core.xml je síce tiež malá, no dnes neexistuje metóda na zisťovanie iba vlastností, takže metadáta autora a názvu stále vyžadujú plnú metódu Open. Správnym prístupom je nechať lacné kontroly rozhodnúť o smerovaní každého súboru a drahé kontroly si vyhradiť pre súbory, ktoré smerovaním prejdú. Pre súbory, ktoré prejdú do fázy plného čítania, prebieha čítanie veľkých súborov .xls výrazne rýchlejšie s nastavením _DisableGraphics := True, ktoré preskakuje analýzu OfficeArt. Z takejto inštancie však nikdy neukladajte dáta: vrstva kreslenia, ktorú preskočila, v modeli chýba a uloženie by ju zo súboru odstránilo.

Súbory, ktoré prejdú vstupnou kontrolou, zvyčajne smerujú na hlbšiu analýzu. Článok o audite zošitov a konverznom nástroji sa zaoberá počítadlami pre jednotlivé hárky, ktoré sa oplatí zbierať po plnom otvorení súboru, a sprievodca výkonom veľkých zošitov pomáha udržať toto plné otvorenie rýchle.

HotXLS je natívna knižnica pre tabuľky v Object Pascale pre Delphi a C++Builder; kompletné rozhranie API vrátane tu zobrazených kontrolných volaní je zdokumentované na produktovej stránke komponentu HotXLS.