Tehnični članak

Okvirjanje zapisov BIFF PivotCache s HotXLS v Delphi

Podtok BIFF PivotCache hrani predpomnjen nabor podatkov vrtilne tabele ločeno od pogleda, ki ga prikazuje, HotXLS pa ta podtok bere in piše z pregledovanjem teles zapisov in ne z zaupanjem številam zapisov. Ta razlika je cela zgodba: ista številka zapisa nosi dve neskladni postavitvi telesa, odvisno od tega, kateri pisec je datoteko izdelal, zato bralec odloči okvirjanje iz prvega telesa zapisa, ki ga vidi

To plast srečate v trenutku, ko mora vrtilna tabela preživeti povratni skok. Vrtilni pogled brez predpomnilnika je lupina, Excel pa bo predpomnilnik znova zgradil iz vira obsega, ko odpre datoteko — kar je v redu vse do trenutka, ko vira obsega ni več, podatki so bili prilepljeni iz poizvedbe ali pa je delovna knjiga arhivirana zaključitev, ki se ne sme spremeniti, ko jo nekdo odpre

Dve strukturi, dve mesti v datoteki

Predpomnjeni podatki in definicija predpomnilnika živita v različnih delih delovne knjige, njuno zamegljevanje pa je prvo, kar je treba urediti prav. Predpomnjeni zapisi tvorijo svoj podtok, podan v [MS-XLS] §2.1.7.12 kot PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Opazite, česa ni: na čelu te produkcije ni BOF

Definicija sede namesto tega v globalne podatke delovne knjige, kot PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), postavljena za zapisi oblikovanja in pred zapisa BoundSheet ter Country. Tako je en sam predpomnilnik opisan na dveh mestih, stotine zapisov narazen, povezava med njima pa je identifikator toka, ki se mora ujemati na treh mestih hkrati

Okvirjanje PivotCache v HotXLS v BIFF8: PIVOTCACHEDEFINITION s svojim SXStreamID sede v globalnih podatkih delovne knjige za oblikovanjem in pred BoundSheet, predpomnjeni zapisi pa živijo v toku pod shrambo _SX_DB_CUR, poimenovani s štirimestno veliko šestnajstiško obliko, ki drži zapise SXDB, SXDBEx, SXFORMULA, FDB in DBB brez BOF, SXStreamID.idStm, polje idstm v SXDB in ime toka pa se morajo ujemati
En sam vrtilni predpomnilnik je opisan na dveh mestih, stotine zapisov narazen, povezan z identifikatorjem toka, ki se mora hkrati ujemati v globalnih podatkih, glavi SXDB in imenu podtoka

Vsak predpomnilnik sodi v tok pod _SX_DB_CUR, katerega ime je štirimestni veliki šestnajstiški zapis njegovega identifikatorja. SXStreamID.idStm, polje idstm, ponovljeno v glavi SXDB, in to ime toka se morajo vsi ujemati. Ko dodeljujete nov identifikator, najprej rezervirajte vsako številko, že prebrano iz datoteke, sicer lahko nov predpomnilnik zahteva številko, ki pripada starejšemu predpomnilniku, do katerega bralec še ni prehodil

Še en identifikator ljudi ujame. Vrednost iCache v vrtilnem pogledu je položaj pripadajočega SXStreamID v globalnem zaporedju od nič in ne identifikator predpomnilnika, ki ga lahko izberete. Ob pisanju se mora preslikati iz objekta predpomnilnika na njegov dejanski izhodni položaj, obstoječi pogledi pa se morajo preštevilčiti skupaj z njim, sicer nadgradnja enega predpomnilnika tiho usmeri pogled v drugega

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;     // zaupaj predpomnjenim zapisom
    Cache.SaveData := True;

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

    Cache.SetRecordCount(0);          // počisti, nato dimenzioniraj mrežo zapisov
    Cache.SetRecordCount(500);
    Book.StorePivotCaches;
  finally
    Book.Free;
  end;
end;

Dvojni SetRecordCount ni vraževerje. RecordCount je navadno pisanje lastnosti, ki ne dodeljuje, notranja pot rasti pa inicializira le novododane vrstice, tako da lahko predpomnilnik, kateremu je bilo štetje nastavljeno skozi glavno pot, konča z mrežo indeksov ničelne dolžine. Pisanja v RecordIndices se potem zavržejo brez napake. Nastavitev štetja na nič in nazaj ponovno vzpostavi mrežo, in to se mora zgoditi, potem ko je bilo dodano vsako polje, ker širina vrstice pride iz štetja polij

Zakaj vam številka zapisa ne more povedati postavitve telesa?

Ker sta se številke zapisov in postavitvi teles spreminjali v različnih časih, preslikava med njima ni funkcija. Ena številka v stari množici se v datotekah starejših piscev pojavi izključno, kar jo naredi zanesljiv signal v eno smer. Druga številka je dejansko dvoumna: pojavi se tako v pravilnih datotekah kot v obsegu vmesnih verzij, ki so uporabile novo številko s staro postavitvijo telesa

Okvirjanje je zato treba odločiti iz telesa in to enkrat na podtok predpomnilnika in ne na zapis. HotXLS pripne narečje iz dolžine prvega zapisa SXDBB v vsakem podtoku. V okvirjanju specifikacije en SXDBB drži točno en zapis predpomnilnika, tako da je njegova dolžina enaka eni širini vrstice. V starejšem stisnjenem okvirjanju prvi zapis drži toliko vrstic, kolikor se jih zloži, tako da je za vsak predpomnilnik z več kot eno vrstico vsaj dve širini vrstic. Primerjava je odločilna, kadar koli se dve napovedi razlikujeta

Pripetošče okvirjanja SXDBB v HotXLS: ena številka zapisa nosi dve neskladni postavitvi telesa, zato bralec primerja dolžino prvega zapisa SXDBB s širino vrstice; ena širina vrstice pripne narečje specifikacije, dve ali več širin vrstic pa pripne staro stisnjeno okvirjanje, izenačitve vzamejo branje specifikacije, narečje pa se pripne enkrat na podtok predpomnilnika in ne na zapis
Številke zapisov ne morejo odločiti postavitve telesa, ker sta se obe spremenili v različnih časih, zato HotXLS pripne narečje enkrat na podtok iz dolžine prvega SXDBB in pri izenačitvi vzame branje specifikacije

Ko se ne razlikujeta, bralec vzame branje specifikacije, po načelu, da datoteke, zapisane s strani Excela, številčno presegajo datoteke, zapisane s strani vmesne gradnje. Ta slepa pega je po zasnovi ozka in, ko res nastopi, se datoteka sama še vedno predvaja bajt za bajt. Prizadeti so le tipizirani indeksi, izpostavljeni klicateljem

Širina indeksa živi v drugem zapisu

SXDBB (§2.4.276) nosi en indeks na polje predpomnilnika, ki mu je nastavljena zastavica različnih vrednosti, v vrstnem redu polij, širina vsakega indeksa pa se odloči drugje: pripadajoči zapis polja SXFDB (§2.4.283) razglasi zastavico kratkih postavk in ta zastavica pravi, ali indeks zasede dva bajta ali enega. Dva zapisa, ena implicirana pogodba in en sam stavek v specifikaciji, ki ju povezuje

Ta sklopnost je natanko tisto mesto, kjer se zavela doma vzgojeno kodiranje. Zgodnejši pisec HotXLS je vsako polje spravil v najmanjše število bitov, z oblazinjenjem do meje bajta med vrsticami — kar je branljivo v izolaciji in neposredno nasprotuje širini, ki jo je isti pisec pravkar razglasil v SXFDB. Polje s tremi različnimi vrednostmi je bilo opisano kot en bajt široko v enem zapisu in je zasedlo dva bita v drugem. Popravilo ni bilo popraviti aritmetiko, ampak izvleči odločitev o širini v eno funkcijo, ki jo kličeta oba oddajnika, tako da se dva zapisa ne moreta več raziti. To je isti razred pomanjkljivosti, opisan v drsenju deklaracije dolžine zapisa BIFF, kjer razglašena velikost in dejansko telo gresta vsak na svojo stran

Posledica tega, da teh zapisov sploh ne berete, si zasluži, da se izreče, ker jo je enostavno podceniti. Ko je bralec preskočil indekse zapisov, je vsak predpomnilnik, naložen iz datoteke, sporočil indeks nič za vsako polje vsake vrstice — kar pomeni, da je vsaka vrstica kazala na prvo vrednost vsakega polja. To ni le zmanjšana samoraziskava: pot vrednotenja vrtilne tabele in pot polnjenja iz predpomnilnika v celice obe porabita to mrežo. In test povratnega skoka je ne more zaznati, ker se predpomnilnik, še vedno na surovem predvajanju, zapiše nazaj iz svojih izvirnih bajtov

// Zastavice porekla vam povedo, kaj držite in kaj se sme 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 oddajanje je brez izgube le, kadar ima vsak zapis tu model
  if Cache.CanUpgradeFraming then
    Writeln('safe to rewrite with the current emitters');
end;

Kdaj je prepisovanje predpomnilnika brez izgube?

Le ko trije pogoji držijo skupaj, CanUpgradeFraming pa je ena sama lastnost, ki odgovori na vprašanje. Predpomnilnik mora biti še vedno na surovem predvajanju, podtok mora biti v enem od okvirjanj, ki jih je ta knjižnica prej zapisala napačno, bralec pa mora imeti zgrajen popoln tipiziran model vsakega zapisa v njem. Predpomnilnik, ki ga je zapisal Excel, nikoli ne ustreza, ker njegov podtok nosi zapise, za katere HotXLS nima modela, ponovno oddajanje iz modela pa bi jih spustilo

Test popolnosti je strožji, kot se na prvi pogled zdi. Zapis, ki ga je bralec obdržal le kot motne bajte, označi model za nepopoln. Tudi razglašeno štetje zapisov formul, ki ga oddajnik ne more reproducirati, označi model za nepopoln, ker bi ponovno oddajanje razglasitev več zapisov formul prepisalo v razglasitev ničelnih, vrednost v datoteki, ki je ni mogoče reproducirati, pa je enakovredna zapisu, ki ga ni mogoče reproducirati

Namerni konzervativizem teče tudi skozi pisca. Indeksi so pripeti v zakoniti obseg in ne kodirani kot zasilni znak izven pasu, ker specifikacija definira indeks v zaporedje različnih vrednosti in nič drugega, prazna celica pa je sama vrednost v tem zaporedju. Telo zapisa predpomnilnika, ki presega strop zapisa BIFF, se sploh ne zapiše — kar bi zahtevalo tisoče polij predpomnilnika in je znotraj meje stolpcev BIFF8 vseeno nedosegljivo; rezerva je, da Excel osveži iz vira obsega, kar je definirano vedenje in ne pokvarjena datoteka

Datumi nosijo zadnjo odvisnost čez zapise. Pretvorba serijska-v-datum je odvisna od datumskega sistema delovne knjige, oddajnik zapisov pa delovne knjige ne vidi, zato se izbira osnovnega datuma poda kot parameter, privzet na sistem 1900, ki ga preskrbi pot shranjevanja na ravni delovne knjige. Pod sistemom 1900 je serijska številka vrednost neposredno; sistem 1904 se razlikuje za 1462 dni. Širša obravnava datumskih serijskih številk je v datumskih serijskih številkah, sistemu 1904 in formatih števil

Če delate na plasti pogleda in ne na plasti predpomnilnika, so zapisi, ki opisujejo vidno vrtilno tabelo, pokriti v naboru zapisov PivotTable BIFF8, vedenje na računski strani pa v izračunanih poljih, izračunanih postavkah in osvežitvi. Vse tri plasti pridejo z HotXLS Delphi spreadsheet component, kar je tisto, ki omogoča naložiti staro delovno knjigo, pregledati, kaj njen predpomnilnik dejansko vsebuje, in odločiti, ali je prepisovanje varno, preden ga storite