BIFF PivotCache substream čuva keširani skup podataka PivotTable-a odvojeno od prikaza koji ga prikazuje, i HotXLS taj substream čita i piše ispitivanjem tela zapisa, a ne verujući brojevima zapisa. Ta razlika je cela priča: isti broj zapisa nosi dva neskladna rasporeda tela zavisno od toga koji ga je pisac proizveo, pa čitač odlučuje okvir iz prvog tela zapisa koje vidi
Ovaj sloj sretnete u trenutku kad PivotTable mora da preživi povratni krug. Pivot prikaz bez svog keša je ljuska, i Excel će keš ponovo izgraditi iz izvornog opsega kad otvori datoteku, što je sasvim u redu sve do trenutka kad izvorni opseg nestane, podaci su nalepljeni iz upita, ili je radna sveska arhiviran zatvor koji ne sme da se promeni kad ga neko otvori
Dve strukture, dva mesta u datoteci
Keširani podaci i definicija keša žive u različitim delovima radne sveske, i njihovo pomešanje je prvo što treba ispravno razumeti. Keširani zapisi formiraju sopstveni substream, dat u [MS-XLS] §2.1.7.12 kao PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Primetite šta izostaje: nema BOF-a na čelu te produkcije
Definicija sedi u workbook globals umesto toga, kao PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), pozicionirana posle zapisa formatiranja a pre BoundSheet i Country zapisa. Dakle, jedan keš je opisan na dva mesta udaljena stotinama zapisa, i veza između njih je identifikator toka koji se mora poklapati na tri mesta istovremeno
Svaki keš pripada toku pod _SX_DB_CUR čije je ime četvorocifreno veliko heksadekadsko spelovanje njegovog identifikatora. SXStreamID.idStm, idstm polje ponovljeno u SXDB zaglavlju, i to ime toka moraju se sva poklapati. Kad dodeljujete novi identifikator, prvo rezervišite svaki broj već pročitan iz datoteke, ili novi keš može da zatraži broj koji pripada starijem kešu do koga čitač još nije došao
Još jedan identifikator ljude uhvati. Vrednost iCache u pivot prikazu je pozicija odgovarajućeg SXStreamID u globalnom nizu sa bazom nula, a ne identifikator keša koji birate. Pri upisu mora se mapirati iz objekta keša na njegovu stvarnu izlaznu poziciju, i postojeći prikazi moraju se renumerisati uz njega, ili nadogradnja jednog keša tiho usmeri prikaz na drugi
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; // veruj keširanim zapisima
Cache.SaveData := True;
Field := Cache.AddField('Region', xlpcftString);
V.ValueType := xlpcftString;
V.StrValue := 'North';
Field.FindOrAddItem(V);
Cache.SetRecordCount(0); // očisti, pa dimenzioniši mrežu zapisa
Cache.SetRecordCount(500);
Book.StorePivotCaches;
finally
Book.Free;
end;
end;
Dvostruki SetRecordCount nije praznoverje. RecordCount je običan upis svojstva koji ne alocira, i unutrašnja putanja rasta inicijalizuje samo novododate redove, pa keš čiji je broj postavljen kroz putanju zaglavlja može završiti sa mrežom indeksa nulte dužine. Upisi u RecordIndices se tada odbacuju bez greške. Postavljanje broja na nulu i nazad uspostavlja mrežu, i to se mora desiti posle dodavanja svakog polja, jer širina reda dolazi iz broja polja
Zašto broj zapisa ne može da vam kaže raspored tela?
Zato što su se brojevi zapisa i rasporedi tela menjali u različito vreme, pa mapiranje između njih nije funkcija. Jedan broj u legacy skupu pojavljuje se samo u datotekama starijih pisaca, što ga čini pouzdanim signalom u jednom smeru. Drugi broj je stvarno dvosmislen: pojavljuje se i u ispravnim datotekama i u opsegu međuverzija koje su koristile novi broj sa starim rasporedom tela
Okviranje se zato mora odlučiti iz tela, i jednom po cache substream-u, a ne po zapisu. HotXLS učvršćuje dijalekt iz dužine prvog SXDBB zapisa u svakom substream-u. U specifikacijskom okviranju jedan SXDBB drži tačno jedan zapis keša, pa mu je dužina jednaka jednoj širini reda. U starijem pakovanom okviranju prvi zapis drži koliko redova stane, pa je za svaki keš sa više od jednog reda najmanje dve širine reda. Poređenje je presudno kad god se ta dva predviđanja razlikuju
Kad se ne razlikuju, čitač uzima specifikacijsko tumačenje, po principu da datoteke napisane Excel-om brojčano nadmašuju datoteke napisane međuverzijom. Ta slepa mrlja je uska po konstrukciji i, kad se ipak pojavi, sama datoteka se i dalje ponovo reprodukuje bajt po bajt. Pogođeni su samo tipizirani indeksi izloženi pozivaocima
Širina indeksa živi u drugom zapisu
SXDBB (§2.4.276) nosi po jedan indeks za svako cache polje čija je distinct-value zastavica postavljena, u redosledu polja, a širina svakog indeksa odlučuje se drugde: odgovarajući SXFDB zapis polja (§2.4.283) deklariše short-items zastavicu, i ta zastavica kaže da li indeks zauzima dva bajta ili jedan. Dva zapisa, jedan podrazumevani ugovor, i jedna rečenica u specifikaciji koja ih povezuje
To sprezanje je baš mesto gde domaće enkodiranje pogreši. Raniji HotXLS pisac pakovao je svako polje u minimalan broj bitova, popunjavajući do bajt granice između redova, što je branjivo izolovano i direktno protivreči širini koju je isti pisac upravo deklarisao u SXFDB. Polje sa tri različite vrednosti opisano je kao široko jedan bajt u jednom zapisu i zauzimalo je dva bita u drugom. Popravka nije bila da se aritmetika ispravi nego da se odluka o širini izdvoji u jednu funkciju koju oba emittera zovu, pa ta dva zapisa više ne mogu da razbeže. To je ista klasa defekta opisana u članku o driftu deklaracije dužine BIFF zapisa, gde se deklarisana veličina i stvarno telo razidu
Posledica toga da se ovi zapisi uopšte ne čitaju vredi ispisati, jer se lako podceni. Kad je čitač preskakao indekse zapisa, svaki keš učitan iz datoteke prijavljivao je indeks nulu za svako polje svakog reda, što znači da je svaki red pokazivao na prvu vrednost svakog polja. To nije samo umanjena introspekcija: i pivot putanja vrednovanja i putanja punjenja ćelija iz keša troše tu mrežu. I round-trip test to ne može otkriti, jer se keš još na sirovoj reprodukciji upisuje nazad iz svojih originalnih bajtova
// Provenance zastavice vam kažu šta držite u rukama i šta sme da se prepiše
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);
// Ponovno emitovanje je bez gubitka tek kad svaki zapis ima model ovde
if Cache.CanUpgradeFraming then
Writeln('safe to rewrite with the current emitters');
end;
Kada je prepisivanje keša bez gubitka?
Tek kad tri uslova stoje zajedno, i CanUpgradeFraming je jedino svojstvo koje na pitanje odgovara. Keš mora i dalje biti na sirovoj reprodukciji, substream mora biti u jednom od okviranja koje je biblioteka ranije pogrešno pisala, i čitač je morao sagraditi kompletan tipiziran model svakog zapisa u njemu. Keš koji je napisao Excel nikada ne kvalifikuje, jer njegov substream nosi zapise za koje HotXLS nema model, a ponovno emitovanje iz modela bi ih odbacilo
Test potpunosti je stroži nego što deluje na prvi pogled. Zapis koji je čitač čuvao samo kao neprozirne bajtove označava model nepotpunim. Isto čini i deklarisano brojanje zapisa formula koje emitter ne može reprodukovati, jer bi ponovno emitovanje deklaraciju nekoliko zapisa formula prepisalo u deklaraciju nijednog, a vrednost u datoteci koja se ne može reprodukovati jednaka je zapisu koji se ne može reprodukovati
Namerni konzervativizam prolazi i kroz pisca. Indeksi se stežu u legalni opseg umesto da se kodiraju kao out-of-band sentinela, jer specifikacija definiše indeks u niz različitih vrednosti i ništa drugo, i prazna ćelija je sama vrednost u tom nizu. Telo zapisa keša koje premašuje BIFF plafon zapisa ne upisuje se uopšte, što bi tražilo hiljade cache polja i ionako je nedostižno unutar BIFF8 ograničenja kolona; fallback je da Excel osveži iz izvornog opsega, što je definisano ponašanje, a ne pokvarena datoteka
Datumi nose poslednju zavisnost između zapisa. Konverzija serijskog broja u datum zavisi od datumskog sistema radne sveske, i emitter zapisa ne može videti svesku, pa se izbor baznog datuma prosleđuje kao parametar koji po defaultu uzima 1900 sistem i snabdeva ga putanja čuvanja na nivou sveske. Pod 1900 sistemom serijski broj je vrednost direktno; 1904 sistem odstupa 1462 dana. Šire rukovanje datumskim serijskim brojevima je u članku o datumskim serijskim brojevima, 1904 sistemu i formatima brojeva
Ako radite na sloju prikaza, a ne na sloju keša, zapisi koji opisuju vidljivi pivot pokriveni su u članku o BIFF8 PivotTable skupu zapisa, a ponašanje na strani računanja u članku o izračunatim poljima, izračunatim stavkama i osvežavanju. Sva tri sloja stižu u HotXLS Delphi spreadsheet komponenti, što je ono što omogućava da učitate legacy radnu svesku, pregledate šta njen keš stvarno sadrži i odlučite da li je bezbedno njeno prepisivanje pre nego što to uradite