Odborný článok

Rámovanie záznamov BIFF PivotCache s HotXLS v Delphi

Substream BIFF PivotCache ukladá cache dátovú sadu PivotTable oddelene od pohľadu, ktorý ju zobrazuje, a HotXLS číta a zapisuje tento substream skúmaním tiel záznamov namiesto dôvery číslam záznamov. Toto rozlíšenie je celý príbeh: to isté číslo záznamu nesie dve nekompatibilné rozloženia tela podľa toho, ktorý writer súbor vyprodukoval, takže čítačka rozhodne rámovanie z prvého tela záznamu, ktoré vidí

Túto vrstvu stretnete v momente, keď musí PivotTable prežiť round trip. Pivot pohľad bez svojej cache je lastúra a Excel cache znovu postaví zo zdrojového rozsahu, keď otvorí súbor, čo je v poriadku presne do chvíle, keď zdrojový rozsah je preč, dáta boli prilepené z dotazu alebo zošit je archivované uzavretie, ktoré sa nesmie zmeniť, keď si ho niekto otvorí

Dve štruktúry, dve miesta v súbore

Cache dáta a definícia cache bývajú v odlišných častiach zošitu a ich zmiešanie je prvá vec, ktorú treba urobiť správne. Cache záznamy tvoria vlastný substream, uvedený v [MS-XLS] §2.1.7.12 ako PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Všimnite si, čo chýba: na čele tejto produkcie nie je žiadny BOF

Definícia sedí namiesto toho vo workbook globals, ako PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), umiestnená po formátovacích záznamoch a pred záznamami BoundSheet a Country. Jedna cache je tak popísaná na dvoch miestach vzdialených stovky záznamov a väzbou medzi nimi je identifikátor streamu, ktorý sa musí zhodovať na troch miestach naraz

Rámovanie PivotCache v HotXLS v BIFF8: PIVOTCACHEDEFINITION so svojím SXStreamID sedí vo workbook globals po formátovaní a pred BoundSheet, kým cache záznamy bývajú v streame pod úložiskom _SX_DB_CUR pomenovanom štvorciferným veľkým hexom nesúcom záznamy SXDB, SXDBEx, SXFORMULA, FDB a DBB bez BOF a SXStreamID.idStm, pole idstm v SXDB a meno streamu sa musia zhodovať
Jedna pivot cache je popísaná na dvoch miestach vzdialených stovky záznamov, spojených identifikátorom streamu, ktorý sa musí zhodovať v globals, hlavičke SXDB a mene substreamu naraz

Každá cache patrí do streamu pod _SX_DB_CUR, ktorého meno je štvorciferné veľké hexadecimálne znenie jej identifikátora. SXStreamID.idStm, pole idstm opakované v hlavičke SXDB a toto meno streamu sa musia všetky zhodovať. Keď alokujete nový identifikátor, rezervujte najprv každé číslo už prečítané zo súboru, inak nová cache môže nárokovať číslo, ktoré patrí staršej cache, ku ktorej sa čítačka ešte nedošla

Ešte jeden identifikátor ľudí chytá. Hodnota iCache v pivot pohľade je pozícia zodpovedajúceho SXStreamID v globálnej sekvencii od nuly, nie identifikátor cache, ktorý si vyberiete. Pri zápise sa musí namapovať z objektu cache na jej skutočnú výstupnú pozíciu a existujúce pohľady sa musia prečíslovať spolu s ňou, inak upgrade jednej cache poticho namiere pohľad na inú

var
  Book: TXLSWorkbook;
  Cache: TXLSPivotCache;
  Field: TXLSPivotCacheField;
  V: TXLSPivotCacheValue;
begin
  Book := TXLSWorkbook.Create(nil);
  try
    Book.LoadFromFile('sales.xls');
    Cache := Book.PivotCaches.Add;
    Cache.SourceRangeSheet := 'Data';
    Cache.SourceFirstRow := 1;  Cache.SourceFirstCol := 1;
    Cache.SourceLastRow := 500; Cache.SourceLastCol := 6;
    Cache.SourceDataType := 1;        // SXVS SHEET, MS-XLS 2.4.317
    Cache.RefreshOnLoad := False;     // ver cache záznamom
    Cache.SaveData := True;

    Field := Cache.AddField('Region', xlpcftString);
    V.ValueType := xlpcftString;
    V.StrValue := 'North';
    Field.FindOrAddItem(V);

    Cache.SetRecordCount(0);          // vymaž, potom dimenzuj mriežku záznamov
    Cache.SetRecordCount(500);
    Book.StorePivotCaches;
  finally
    Book.Free;
  end;
end;

Dvojitý SetRecordCount nie je povera. RecordCount je obyčajný zápis vlastnosti, ktorý nealokuje, a interná rastová cesta inicializuje len novo pridané riadky, takže cache, ktorej počet bol nastavený cez hlavičkovú cestu, môže skončiť s mriežkou indexov nulovej dĺžky. Zápisy do RecordIndices sa potom zahodia bez chyby. Nastavenie počtu na nulu a späť mriežku obnoví a musí sa to stať po pridaní každého poľa, pretože šírka riadku pochádza z počtu polí

Prečo vám číslo záznamu nemôže povedať rozloženie tela?

Pretože čísla záznamov a rozloženia tiel sa menili v rôznych časoch, takže mapovanie medzi nimi nie je funkcia. Jedno číslo v legacy sade sa objavuje len v súboroch od starších writerov, čo z neho robí spoľahlivý signál v jednom smere. Iné číslo je skutočne nejednoznačné: objavuje sa v správnych súboroch aj v rade medzi verzií, ktoré používali nové číslo so starým rozložením tela

Rámovanie sa preto musí rozhodnúť z tela a raz za cache substream, nie za záznam. HotXLS uzamkne dialekt z dĺžky prvého záznamu SXDBB v každom substreame. V rámovaní špecifikácie drží jeden SXDBB presne jeden cache záznam, takže jeho dĺžka sa rovná jednej šírke riadku. V staršom zabalenom rámovaní drží prvý záznam toľko riadkov, koľko sa zmestí, takže pre každú cache s viac než jedným riadkom je to aspoň dve šírky riadku. Porovnanie je rozhodujúce vždy, keď sa obe predpovede líšia

Uzamknutie rámovania SXDBB v HotXLS: jedno číslo záznamu nesie dve nekompatibilné rozloženia tela, takže čítačka porovná dĺžku prvého záznamu SXDBB so šírkou riadku, jedna šírka riadku uzamkne dialekt špecifikácie, kým dve alebo viac šírok riadku uzamkne legacy zabalené rámovanie, remízy berú čítanie špecifikácie a dialekt sa uzamkne raz za cache substream, nie za záznam
Čísla záznamov nedokážu rozhodnúť rozloženie tela, pretože oboje sa menili v rôznych časoch, takže HotXLS uzamkne dialekt raz za substream z dĺžky prvého SXDBB a pri remíze berie čítanie špecifikácie

Keď sa nelíšia, čítačka berie čítanie špecifikácie, na princípe, že súbory napísané Excelom počtom prevyšujú súbory napísané medzi buildom. To slepé miesto je konštrukčne úzke a keď aj nastane, súbor sám sa stále prehráva bajt po bajte. Ovplyvnené sú len typované indexy vystavené volajúcim

Šírka indexu býva v inom zázname

SXDBB (§2.4.276) nesie jeden index na každé cache pole s nastaveným príznakom odlišných hodnôt, v poradí polí, a šírka každého indexu sa rozhoduje inde: zodpovedajúci záznam poľa SXFDB (§2.4.283) deklaruje príznak krátkych položiek a ten príznak hovorí, či index zaberá dva bajty alebo jeden. Dva záznamy, jedna implikovaná zmluva a jediná veta v špecifikácii, ktorá ich spája

Presne na tejto väzbe sa pokazí doma vypestované kódovanie. Starší writer HotXLS balil každé pole do minimálneho počtu bitov s výplňou na hranicu bajtu medzi riadkami, čo je v izolácii obhájiteľné a priamo odporuje šírke, ktorú ten istý writer práve deklaroval v SXFDB. Pole s tromi odlišnými hodnotami bolo popísané ako široké jeden bajt v jednom zázname a zaberá dva bity v druhom. Opravou nebolo opraviť aritmetiku, ale vyťažiť rozhodnutie o šírke do jednej funkcie, ktorú volajú obaja emitéri, takže dva záznamy už nemôžu odísť od seba. To je tá istá trieda defektov popísaná v článku drift deklarácie dĺžky záznamu BIFF, kde sa deklarovaná veľkosť a skutočné telo rozídu

Dôsledok toho nečítať tieto záznamy vôbec stojí za vyslovenie, pretože sa dá ľahko podceniť. Keď čítačka preskočila indexy záznamov, každá cache načítaná zo súboru hlásila index nula pre každé pole každého riadku, čo znamená, že každý riadok ukazoval na prvú hodnotu každého poľa. To nie je len znížená introspekcia: cesta vyhodnocovania pivota aj cesta plnenia cache do buniek konzumujú túto mriežku. A round-trip test to nedokáže zachytiť, pretože cache ešte na surovej reprodukcii sa zapisuje späť z pôvodných bajtov

// Príznaky provenancie vám povedia, čo držíte a čo sa smie prepísať
if Cache.FromRawBlobs then
begin
  Writeln('stream id        : ', IntToHex(Cache.StreamId, 4));
  Writeln('legacy framing   : ', Cache.RawFramingIsLegacy);
  Writeln('own storage      : ', Cache.RawHasStorageStream);
  Writeln('model complete   : ', Cache.RawModelIsComplete);
  // Znova emitovanie je bezstratové len vtedy, keď každý záznam má tu model
  if Cache.CanUpgradeFraming then
    Writeln('safe to rewrite with the current emitters');
end;

Kedy je prepísanie cache bezstratové?

Len keď spolu platia tri podmienky a CanUpgradeFraming je jediná vlastnosť, ktorá na otázku odpovedá. Cache musí byť stále na surovej reprodukcii, substream musí byť v jednom z rámovaní, ktoré táto knižnica predtým zapisovala nesprávne, a čítačka musela postaviť kompletný typovaný model každého záznamu v ňom. Cache napísaná Excelom nikdy nekvalifikuje, pretože jej substream nesie záznamy, pre ktoré HotXLS nemá model, a znova emitovanie z modelu by ich zahodilo

Test úplnosti je prísnejší, než sa na prvý pohľad javí. Záznam, ktorý čítačka držala len ako nepriehľadné bajty, značí model neúplný. Rovnako deklarovaný počet záznamov vzorcov, ktoré emitér nedokáže reprodukovať, pretože znova emitovanie by prepísalo deklaráciu niekoľkých záznamov vzorcov na deklaráciu žiadnych, a hodnota v súbore, ktorá sa nedá reprodukovať, je ekvivalentom záznamu, ktorý sa nedá reprodukovať

Zámerný konzervativizmus pretkáva aj writera. Indexy sa upínajú do legálneho rozsahu namiesto kódovania ako out-of-band sentinel, pretože špecifikácia definuje index do sekvencie odlišných hodnôt a nič iné, a prázdna bunka je sama o sebe hodnotou v tej sekvencii. Telo cache záznamu presahujúce strop záznamu BIFF sa nezapisuje vôbec, čo by vyžadovalo tisíce cache polí a je v rámci limitu stĺpcov BIFF8 aj tak nedosiahnuteľné; fallback je, že Excel obnoví zo zdrojového rozsahu, čo je definované správanie, nie poškodený súbor

Dátumy nesú poslednú medzizáznamovú závislosť. Konverzia sériového čísla na dátum závisí od dátumového systému zošitu a emitér záznamov nevidí zošit, takže voľba základného dátumu sa podáva ako parameter, ktorý defaultuje na systém 1900 a dodáva ho cesta ukladania na úrovni zošitu. Pod systémom 1900 je sériové číslo priamo hodnotou; systém 1904 sa líši o 1462 dní. Širšie ošetrenie dátumových sériových čísel je v článku dátumové sériové čísla, systém 1904 a formáty čísel

Ak pracujete na vrstve pohľadu, nie cache, záznamy opisujúce viditeľný pivot kryje článok sada záznamov PivotTable BIFF8 a správanie na strane výpočtu článok počítané polia, počítané položky a obnovenie. Všetky tri vrstvy prichádzajú v HotXLS Delphi tabuľkovom komponente, čo je to, čo umožňuje načítať legacy zošit, preskúmať, čo jeho cache reálne obsahuje, a rozhodnúť, či je jeho prepísanie bezpečné, skôr než to urobíte