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
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
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