BIFF PivotCache substream čuva keširani skup podataka PivotTablea odvojeno od pogleda koji ga prikazuje, i HotXLS čita i piše taj substream pregledavajući tijela recorda umjesto da vjeruje brojevima recorda. Ta je razlika cijela priča: isti broj recorda nosi dva nekompatibilna rasporeda tijela ovisno o tome koji ju je writer proizveo, pa čitač odlučuje uokvirivanje iz prvog tijela recorda koje vidi
Ovaj sloj sretnete u trenutku kad PivotTable mora preživjeti round trip. Pivot pogled bez svog keša je ljuska, i Excel će obnoviti keš iz source raspona kad otvori datoteku, što je u redu sve do točke gdje je source raspon nestao, podaci su zalijepljeni iz upita, ili je radna knjiga arhivirano knjigovodstveno zatvaranje koje se ne smije promijeniti kad ju itko otvori
Dvije strukture, dva mjesta u datoteci
Keširani podaci i definicija keša žive u različitim dijelovima radne knjige, i miješanje ta dva je prvo što treba posložiti. Keširani recordi čine vlastiti substream, dan u [MS-XLS] §2.1.7.12 kao PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Primijetite što nedostaje: nema BOF na čelu te produkcije
Definicija sjedi u workbook globalsima umjesto toga, kao PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), smještena nakon formatacijskih recorda i prije BoundSheet i Country recorda. Dakle jedan keš je opisan na dva mjesta udaljena stotine recorda, i veza između njih je stream identifikator koji se mora slagati na tri mjesta odjednom
Svaki keš pripada u stream pod _SX_DB_CUR čije je ime četveroznamenkasti veliki heksadecimalni zapis njegova identifikatora. SXStreamID.idStm, idstm polje ponovljeno u SXDB headeru, i to ime streama se moraju svi poklapati. Kad alocirate novi identifikator, prvo rezervirajte svaki broj već pročitan iz datoteke, ili novi keš može preuzeti broj koji pripada starijem kešu do kojega čitač još nije došao
Još jedan identifikator hvata ljude. iCache vrijednost u pivot pogledu je pozicija od nule odgovarajućeg SXStreamIDa u globalnom slijedu, a ne identifikator keša koji vi birate. Pri pisanju mora se mapirati iz cache objekta na njegovu stvarnu output poziciju, i postojeći pogledi moraju se prenumerirati s njime, ili nadogradnja jednog keša tiho usmjeri pogled 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; // vjeruj keširanim recordima
Cache.SaveData := True;
Field := Cache.AddField('Region', xlpcftString);
V.ValueType := xlpcftString;
V.StrValue := 'North';
Field.FindOrAddItem(V);
Cache.SetRecordCount(0); // očisti, pa dimenzioniraj grid recorda
Cache.SetRecordCount(500);
Book.StorePivotCaches;
finally
Book.Free;
end;
end;
Dvostruki SetRecordCount nije praznovjerje. RecordCount je obično pisanje propertyja koje ne alocira, i unutarnji put rasta inicijalizira samo novo dodane retke, pa keš čiji je count postavljen kroz header put može završiti s gridom indeksa duljine nula. Pisanja u RecordIndices se onda odbacuju bez greške. Postavljanje counta na nulu i natrag ponovno uspostavlja grid, i to se mora desiti nakon što je svako polje dodano, jer širina retka dolazi iz broja polja
Zašto vam broj recorda ne može reći raspored tijela?
Jer su se brojevi recorda i rasporedi tijela mijenjali u različito vrijeme, pa mapiranje između njih nije funkcija. Jedan broj u legacy skupu pojavljuje se samo u datotekama starijih writera, što ga čini pouzdanim signalom u jednom smjeru. Drugi je broj stvarno dvosmislen: pojavljuje se i u ispravnim datotekama i u rasponu među-verzija koje su koristile novi broj sa starim rasporedom tijela
Uokvirivanje se zato mora odlučiti iz tijela, i jednom po cache substreamu umjesto po recordu. HotXLS zaključava dijalekt iz duljine prvog SXDBB recorda u svakom substreamu. U specifikacijskom uokvirivanju jedan SXDBB drži točno jedan cache record, pa mu je duljina jednaka širini jednog retka. U starijem pakiranom uokvirivanju prvi record drži koliko se redaka uguralo, pa za svaki keš s više od jednog retka je barem dvije širine retka. Usporedba je odlučujuća kad god se dvije predikcije razlikuju
Kad se ne razlikuju, čitač uzima specifikacijsko čitanje, na principu da datoteke pisane Excelom brojčano nadmašuju datoteke pisane među-buildom. Taj slijepi kut je uzak po konstrukciji i, kad se doista dogodi, sama datoteka se i dalje reproducira bajt po bajt. Dodirnuti su samo tipizirani indeksi izloženi pozivateljima
Širina indeksa živi u drugom recordu
SXDBB (§2.4.276) nosi po jedan indeks za svako cache polje čija je distinct-value zastavica postavljena, u redu polja, a širina svakog indeksa odlučuje se negdje drugdje: odgovarajući SXFDB field record (§2.4.283) deklarira short-items zastavicu, i ta zastavica kaže zauzima li indeks dva bajta ili jedan. Dva recorda, jedan implicitni ugovor, i jedna jedina rečenica u specifikaciji koja ih povezuje
To spajanje je točno tamo gdje domaća encoding pogriješi. Raniji HotXLS writer pakovao je svako polje u najmanji mogući broj bitova, nadopunjavajući do byte granice između redaka, što je obranjivo u izolaciji i izravno proturječi širini koju je isti writer upravo deklarirao u SXFDB-u. Polje s tri različite vrijednosti opisano je kao jedno bajt široko u jednom recordu i zauzimalo je dva bita u drugom. Fix nije bio ispraviti aritmetiku nego izvući odluku o širini u jednu function koju oba emittera zovu, pa se ta dva recorda više ne mogu razdvojiti. To je ista klasa defekta opisana u driftu deklaracije duljine BIFF recorda, gdje se deklarirana veličina i stvarno tijelo rastanu
Posljedica potpunog nečitanja ovih recorda vrijedi da se izgovori, jer je lako podcijeniti. Kad je čitač preskočio indekse recorda, svaki keš učitan iz datoteke javljao je indeks nula za svako polje svakog retka, što znači da je svaki redak pokazivao na prvu vrijednost svakog polja. To nije samo smanjena introspekcija: put evaluacije pivota i put punjenja iz keša u ćelije oboje konzumiraju taj grid. I round-trip test to ne može otkriti, jer se keš još na raw replayu zapisuje natrag iz svojih izvornih bajtova
// Provenance zastavice vam kažu što držite i što se smije prepisati
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 emitiranje je bez gubitka tek kad svaki record ovdje ima model
if Cache.CanUpgradeFraming then
Writeln('safe to rewrite with the current emitters');
end;
Kad je prepisivanje keša bez gubitka?
Tek kad tri uvjeta stoje zajedno, i CanUpgradeFraming je jedini property koji odgovara na pitanje. Keš mora još biti na raw replayu, substream mora biti u jednom od uokvirivanja koje je ova biblioteka ranije krivo pisala, i čitač mora imati izgrađen potpuni tipizirani model svakog recorda u njemu. Keš kojeg je napisao Excel nikad ne kvalificira, jer njegov substream nosi recorde za koje HotXLS nema modela, i ponovno emitiranje iz modela bi ih ispustilo
Test potpunosti je stroži nego što na prvi pogled izgleda. Record koji je čitač čuvao samo kao neprozirne bajtove označava model nepotpunim. Tako i deklarirani broj formula recorda koje emitter ne može reproducirati, jer bi ponovno emitiranje prepisalo deklaraciju nekoliko formula recorda u deklaraciju nijednog, i vrijednost u datoteci koja se ne može reproducirati je ekvivalent recorda koji se ne može reproducirati
Namjerni konzervativizam prolazi i kroz writer. Indeksi se clampaju u legalni raspon umjesto da se kodiraju kao out-of-band sentinel, jer specifikacija definira indeks u slijed različitih vrijednosti i ništa drugo, i prazna ćelija je sama po sebi vrijednost u tom slijedu. Tijelo cache recorda koje prelazi BIFF record strop se uopće ne piše, što bi zahtijevalo tisuće cache polja i ionako je nedostižno unutar BIFF8 limita stupaca; fallback je da Excel refresha iz source raspona, što je definirano ponašanje umjesto oštećene datoteke
Datumi nose zadnju cross-record ovisnost. Konverzija seriala u datum ovisi o datumskom sustavu radne knjige, i record emitter ne može vidjeti radnu knjigu, pa se izbor baznog datuma prenosi kao parametar koji defaulta na 1900 sustav i isporučuje ga put spašavanja na razini radne knjige. Pod 1900 sustavom serijski broj je vrijednost izravno; 1904 sustav odstupa 1462 dana. Šira obrada datumskih seriala je u datumskim serialima, 1904 sustavu i number formatima
Ako radite na sloju pogleda umjesto na sloju keša, recordi koji opisuju vidljivi pivot pokriveni su u BIFF8 PivotTable setu recorda, a ponašanje na strani računanja u izračunatim poljima, izračunatim stavkama i refreshu. Sva tri sloja isporučuje HotXLS Delphi spreadsheet komponenta, što je ono što omogućuje učitati legacy radnu knjigu, pregledati što njezin keš stvarno sadrži, i odlučiti je li prepisivanje sigurno prije nego što ga učinite