Műszaki cikk

HotXLS GetSheetNames: lapnevek gyors listázása Delphiben

Néha az egyetlen kérdés, amelyre egy beérkeztető rutinnak választ kell kapnia, szerkezeti: van-e ebben a munkafüzetben „Mapping” nevű lap, vagy hány fület hordoz. Erre az Open hívással válaszolni a drága mód. Egy teljes megnyitás kibontja a közös karakterlánctáblát, dekódolja az összes stílusrekordot, és bejárja minden munkalap celláit, mert semmiképp nem tudhatja, hogy Ön csak a tartalomjegyzéket akarta. Egy nagy fájlon ez több száz megabájtnyi foglalás és több másodpercnyi CPU idő azért, hogy egy néhány kilobájtos listát olvasson be. A HotXLS, a losLab natív Delphi táblázatkezelő könyvtára, önmagában adja ezt a listát: a GetSheetNames munkafüzet-sorrendben adja vissza a munkalapneveket anélkül, hogy egyetlen cellát is felépítene

Miért olcsó a katalógus olvasása

Mindkét táblázatformátum a fájl elejére teszi a tartalomjegyzékét, és ettől lesz gyors, nem pedig ravasz egy listázó hívás. Egy OOXML csomag az xl/workbook.xml részben tartja a lapkatalógust, amely akkor is kicsi marad, ha a munkafüzet tíz sort vagy tízmilliót tart. Egy BIFF8 .xls a BoundSheet rekordjait a munkafüzet globális folyamának elején tárolja, minden cellaadat előtt. Vagyis az a munka, amelyet egy listázó hívás elkerül, nem kerekítési hiba egy teljes megnyitáshoz képest. Hanem a fájl nagy része. A katalógus olvasása ugyanabba a néhány kilobájtba kerül a sorszámtól függetlenül, míg a teljes megnyitás az adatokkal skálázódik, és egy több megabájtos munkafüzeten ez a szakadék több nagyságrendre rúg mind az érintett bájtokban, mind a lefoglalt memóriában

A HotXLS GetSheetNames hívása Delphiben egy XLSX vagy XLS fájlnak csak a lapkatalógusát olvassa, míg egy teljes megnyitás minden cellát bejár
A katalógus a workbook.xml részben vagy a BoundSheet rekordokban ül, így a listázás néhány kilobájtba kerül, míg a teljes megnyitás az adatokkal skálázódik

Ez a lapos költség az a tulajdonság, amely köré érdemes tervezni. A GetSheetNames hívásra épített beérkeztető kapu ugyanúgy viselkedik egy 200 soros és egy 200 MB-os fájlon, így egy köteg leglassabb fájlja többé nem szabja meg a tempót annak eldöntésénél, hogy egy fájlt egyáltalán érdemes-e feldolgozni

Egy hívás az .xls, .xlsx és a sablonformátumok fölött

Az XLS homlokzaton a TXLSWorkbook.GetSheetNames többet olvas .xls fájloknál. Elfogadja a zip alapú .xlsx, .xlsm, .xltx és .xltm formátumot is, és csak a workbook.xml részt húzza ki az archívumból. Valódi .xls bemenetnél a BoundSheet rekordokat pásztázza, és a globális alfolyam első EOF rekordjánál megáll, így egy nagy bináris fájl is csak a nyitó kilobájtjaiba kerül. Az XLSX homlokzat olyan garanciát hordoz, amely a hosszan futó szolgáltatáskód szempontjából többet számít, mint elsőre látszik: a TXLSXWorkbook.GetSheetNames sem alaphelyzetbe nem állítja, sem fel nem tölti a munkafüzet-példányt, így egy már megnyitott dokumentumot tartó példány úgy tapogathat le más fájlokat, hogy a kezében lévőt nem zavarja meg. A GetODSSheetNames ugyanezt a megközelítést alkalmazza az OpenDocument csomagokra, és mindegyik hívásnak van adatfolyamos túlterhelése, ami lehetővé teszi olyan feltöltés vizsgálatát, amely sosem kerül lemezre

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;

Ugyanez a hívás jó asztali importálási párbeszédpanelt is ad. Listázza a lapokat, hagyja, hogy a felhasználó válasszon egyet, és csak a választás után fizessen a teljes megnyitásért. Egy ötven lapos munkafüzetnél a különbség látható: az azonnal megjelenő választó szemben azzal, amely megakad, míg mögötte betöltődik az egész fájl

A makróbarát .xlsm fájlok és a sablonformátumok pontosan úgy listázódnak, mint egy sima .xlsx, hiszen a katalógus ugyanabban a workbook.xml részben ül, akár utazik a csomagban egy vbaProject.bin, akár nem. Egy beérkeztető folyamat így felsorolhatja egy makrós munkafüzet lapjait az útválasztáshoz úgy, hogy sosem érinti a makrós rakományt, és sosem tesz semmit, ami lefuttatná, a makrókra vonatkozó döntést pedig arra a szakaszra hagyja, amely ténylegesen megnyitja a fájlt

A visszatérési érték olvasása önámítás nélkül

A visszatérési egyezmények nem egységesek a HotXLS-ben. Egyes hívások sikerkor 1-et adnak vissza, mások darabszámot, ezért a listázó függvényeknél az egyetlen megálló ellenőrzés az, ha minden nulla vagy annál kisebb értéket hibának tekint, üresre törölt karakterlánclistával. Álljon ellen a kísértésnek, hogy az üres listát „lap nélküli munkafüzetként” olvassa. Mind az ECMA-376, mind a BIFF8 specifikáció legalább egy lapot megkövetel egy érvényes munkafüzetben, így a nulla név mindig azt jelenti, hogy az olvasás megbukott, sosem azt, hogy a fájl jogosan üres

Egy sikertelen listázás önmagában is megőrzésre érdemes jelzés. Az az .xlsx fájl, amely megbuktatja a hívást, néhány konkrét dolog egyike: csonka, valójában egyáltalán nem OOXML csomag (más rendszerekből származó, félrecímkézett CSV exportok folyton itt bukkannak fel), vagy titkosított tároló. Ezek megkülönböztetése a következő ellenőrzés dolga. Az elutasított fájl első bájtjainak naplózása a hiba mellé rendszerint egyetlen üzenetté rövidít egy támogatási szálat

Titkosított tárolók felismerése az útválasztás előtt

Egy titkosított .xlsx nem zip. OLE összetett fájl, amely EncryptionInfo és EncryptedPackage folyamokat burkol, így a GetSheetNames nem lát bele, és bármely más olvashatatlan fájlhoz hasonlóan hibát ad vissza. A CanReadEncrypted ezt a tárolóalakot vizsgálja, ami lehetővé teszi, hogy a beérkeztetés szándékosan irányítson útjára egy titkosított fájlt, ahelyett hogy egy munkaszál mélyéről jövő általános olvasási hibát nyelne le:

Delphi beérkezés-osztályozási folyamat, ahol a HotXLS CanReadEncrypted és GetSheetNames hívása jelszót igénylő, olvashatatlan vagy normál útra irányítja a feltöltéseket
A CanReadEncrypted fut előbb, mert egy titkosított OOXML fájl olyan OLE tároló, amelybe a listázó hívások nem látnak bele
type
  TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);

function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // A titkosított OOXML OLE tároló, nem zip: ezt nézze előbb,
    // mert a listázó hívások nem látnak bele.
    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;

A titkosítás az a terület, ahol a HotXLS szándékosan aszimmetrikus, ezért az útválasztásnak ezt tiszteletben kell tartania. Az örökölt .xls titkosítás (RC4, RC4 CryptoAPI, XOR) olvasható: a TXLSWorkbook.Open(FileName, Password) tárolt jelszóval dekódol, és ezek a fájlok maradhatnak az automatizált úton. A titkosított OOXML csomagok másfelé mennek. A HotXLS tud írni ilyet a SaveAsEncrypted hívással, de nem tud visszaolvasni. Az OpenEncrypted EXlsxEncryptionNotImplemented kivételt vált ki, amikor titkosított csomagot kap, ezért küldi egy őszinte beérkeztetési terv a titkosított .xlsx fájlt egy Excellel rendelkező emberhez, a jelszót hordozó .xls fájlt pedig kódban tartja

Kötegelt munkánál ez az osztályozó azzal érdemli ki a helyét, hogy végigfut egy egész beérkező könyvtáron, mielőtt bármely munkaszál valódi feldolgozásba kezdene, hiszen minden tapogatás körülbelül egy fájlmegnyitásba és néhány kilobájtnyi olvasásba kerül. Előrehozni azt a hibamódot változtatja meg, amely az üzemeltetést valóban érdekli. Egy hajnali 3 órakor a 600-ból a 412. fájlon elhaló feladat helyett 412 sorba állított fájlt kap és 5 beérkezéskor elutasítottat, mindegyikhez csatolt indokkal. Ugyanazok a könyvtárhívások, sokkal jobb üzemeltetési történet

Amire egy listázó hívás nem tud válaszolni

A nevek és a sorrend minden, amit kap. A listázó hívások semmit nem mondanak a láthatóságról, így a rejtett és a nagyon rejtett lapok is a többihez hasonlóan érkeznek a listába. Nem közölnek használt tartományra vonatkozó méreteket, cellaszámokat és dokumentumtulajdonságokat. A docProps/core.xml rész szintén kicsi, de ma nincs csak tulajdonságokat olvasó tapogatás, így a szerző és a cím metaadata továbbra is egy teljes Open hívásba kerül. A tiszta mód ezzel együtt élni az, hogy az olcsó tények irányítsanak minden fájlt, a drágákat pedig azoknak tartsa fenn, amelyek túlélik az irányítást. Azoknál a fájloknál, amelyek mély olvasásra jutnak, egy nagy .xls csak olvasásra szánt pásztázása észrevehetően gyorsabban fut a _DisableGraphics := True beállítással, amely kihagyja az OfficeArt elemzését. Csak sose mentsen abból a példányból: a kihagyott rajzréteg hiányzik a modellből, a mentés pedig kiejtené a fájlból

Az osztályozáson átjutó fájlok rendszerint mélyebb elemzésre mennek. A munkafüzet-ellenőrző és átalakító műhely azokat a laponkénti számlálókat tárgyalja, amelyeket érdemes összegyűjteni, ha egy teljes megnyitás már indokolt, a nagy munkafüzetek teljesítményéről szóló útmutató pedig azt, hogyan tartsa gyorsan azt a teljes megnyitást

A HotXLS natív Object Pascal táblázatkezelő könyvtár Delphihez és C++Builderhez; a teljes API felület, benne az itt bemutatott vizsgáló hívásokkal, a HotXLS Delphi Component termékoldalán van dokumentálva