Techninis straipsnis

BIFF PivotCache įrašų rėminimas su HotXLS Delphi

BIFF PivotCache substream saugo PivotTable podėlio duomenų rinkinį atskirai nuo vaizdo, kuris jį rodo, ir HotXLS skaito ir rašo tą substream apžiūrėdamas įrašų kūnus, o ne pasitikėdamas įrašų numeriais. Tas skirtumas yra visa istorija: tas pats įrašo numeris neša dvi nesuderinamas kūno išdėstytis, priklausomai nuo to, kuris rašytojas pagamino failą, tad skaitytuvas nusprendžia rėminimą iš pirmojo įrašo kūno, kurį mato

Su šiuo sluoksniu susitinkate akimirką, kai PivotTable turi išgyventi ratą atgal ir pirmyn. Pivot vaizdas be savo podėlio yra kriauklė, ir Excel atkurs podėlį iš šaltinio diapazono, kai atvers failą, kas gerai, kol neužkeliavote iki vietos, kur šaltinio diapazono nebėra, duomenys buvo įklijuoti iš užklausos, arba darbo knyga yra archyvuotas uždarymas, kuris neturi keistis, kai kas nors jį atveria

Dvi struktūros, dvi vietos faile

Podėlio duomenys ir podėlio apibrėžimas gyvena skirtingose darbo knygos dalyse, ir jų supainiojimas yra pirmas dalykas, kurį reikia ištaisyti. Podėlio įrašai sudaro savąjį substream, pateiktą [MS-XLS] §2.1.7.12 kaip PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Pastebėkite, ko nėra: nėra BOF tos produkcijos galvoje

Apibrėžimas vietoj to sėdi darbo knygos globaluose, kaip PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), pozicionuotas po formatavimo įrašais ir prieš BoundSheet ir Country įrašus. Tad vienas podėlis aprašomas dviejose vietose, nutolusiose šimtais įrašų, o jungtis tarp jų yra srauto identifikatorius, kuris turi sutapti trijose vietose vienu metu

HotXLS PivotCache rėminimas BIFF8: PIVOTCACHEDEFINITION su savo SXStreamID sėdi darbo knygos globaluose po formatavimo ir prieš BoundSheet, o podėlio įrašai gyvena sraute po _SX_DB_CUR saugykla, pavadintu keturženklu didžiosiomis rašytu hex, turinčiame SXDB, SXDBEx, SXFORMULA, FDB ir DBB įrašus be BOF, ir SXStreamID.idStm, SXDB idstm laukas ir srauto pavadinimas turi sutapti
Vienas pivot podėlis aprašomas dviejose vietose, nutolusiose šimtais įrašų, sujungtas srauto identifikatoriumi, kuris turi sutapti globaluose, SXDB antraštėje ir substream pavadinime vienu metu

Kiekvienas podėlis priklauso srautui po _SX_DB_CUR, kurio pavadinimas yra jo identifikatoriaus keturženklė didžiosiomis raidėmis šešioliktainė išraiška. SXStreamID.idStm, idstm laukas, kartojamas SXDB antraštėje, ir tas srauto pavadinimas turi visi sutapti. Kai skiriate naują identifikatorių, pirmiausia rezervuokite kiekvieną numerį, jau perskaitytą iš failo, kitaip naujas podėlis gali pareikalauti numerio, priklausančio senesniam podėliui, iki kurio skaitytuvas dar nepaeitų

Dar vienas identifikatorius pagauna žmones. iCache reikšmė pivot vaizde yra atitinkamo SXStreamID pozicija nuo nulio globalioje sekoje, o ne podėlio identifikatorius, kurį renkatės. Rašant jis turi būti sumapintas iš podėlio objekto į jo tikrąją išvesties poziciją, ir esami vaizdai turi būti pernumeruoti kartu su juo, kitaip vieno podėlio atnaujinimas tyliai nukreipia vaizdą į kitą

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;     // pasitikėkite podėlio įrašais
    Cache.SaveData := True;

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

    Cache.SetRecordCount(0);          // išvalykite, tada išmatuokite įrašų gardę
    Cache.SetRecordCount(500);
    Book.StorePivotCaches;
  finally
    Book.Free;
  end;
end;

Dvigubas SetRecordCount nėra prietaras. RecordCount yra paprasta savybės rašymas, kuris nealokuoja, o vidinis augimo kelias inicializuoja tik naujai pridėtas eilutes, tad podėlis, kurio skaičius buvo nustatytas per antraštės kelią, gali gauti nulinio ilgio indeksų gardę. Rašymai į RecordIndices tada išmetami be klaidos. Skaičiaus nustatymas į nulį ir atgal atkuria gardę, ir tai turi įvykti po to, kai pridėtas kiekvienas laukas, nes eilutės plotis ateina iš laukų skaičiaus

Kodėl įrašo numeris negali pasakyti kūno išdėstyties?

Nes įrašų numeriai ir kūno išdėstytys keitėsi skirtingu metu, tad jų tarpusavio mapavimas nėra funkcija. Vienas numeris senajame rinkinyje pasirodo tik failuose iš senesnių rašytojų, kas daro jį patikimu signalu viena kryptimi. Kitas numeris tikrai dviprasmiškas: jis pasirodo ir teisinguose failuose, ir tarpinių versijų aibeje, kurios naudojo naują numerį su senu kūno išdėstymu

Rėminimas todėl turi būti sprendžiamas iš kūno, ir kartą per podėlio substream, o ne per įrašą. HotXLS užfiksuoja dialektą iš pirmojo SXDBB įrašo ilgio kiekviename substream. Specifikacijos rėminime vienas SXDBB laiko tiksliai vieną podėlio įrašą, tad jo ilgas lygus vienam eilutės plyčiui. Senesniame supakuotame rėminime pirmasis įrašas laiko tiek eilučių, kiek telpa, tad bet kuriam podėliui su daugiau nei viena eilute jis yra bent du eilutės plyčiai. Palyginimas yra lemiamas, kada tik dvi prognozės skiriasi

HotXLS SXDBB rėminimo fiksatorius: vienas įrašo numeris neša dvi nesuderinamas kūno išdėstytis, tad skaitytuvas lygina pirmojo SXDBB įrašo ilgį su eilutės plyčiu, vienas eilutės plyčys užfiksuoja specifikacijos dialektą, kol du ar daugiau eilutės plyčių užfiksuoja senąjį supakuotą rėminimą, lygiosios ima specifikacijos skaitymą, o dialektas užfiksuojamas kartą per podėlio substream, o ne per įrašą
Įrašų numeriai negali nuspręsti kūno išdėstyties, nes abu keitėsi skirtingu metu, tad HotXLS užfiksuoja dialektą kartą per substream iš pirmojo SXDBB ilgio ir lygiųjų atveju ima specifikacijos skaitymą

Kai jos nesiskiria, skaitytuvas ima specifikacijos skaitymą, principu, kad Excel parašyti failai gerokai viršija tarpinės versijos parašytus failus. Tas aklasis taškas yra siauras pagal konstrukciją ir, kai jis vis dėlto įvyksta, pats failas vis tiek atgrojamas baitas į baitą. Tik rašyti indeksai, atverstieji kvietėjams, paveikiami

Indeksų plotis gyvena kitame įraše

SXDBB (§2.4.276) neša vieną indeksą kiekvienam podėlio laukui, kurio atskirų reikšmių vėliava nustatyta, laukų tvarka, ir kiekvieno indekso plotis sprendžiamas kitur: atitinkamas SXFDB lauko įrašas (§2.4.283) deklaruoja trumpųjų elementų vėliavą, o ta vėliava sako, ar indeksas užima du baitus, ar vieną. Du įrašai, viena numanoma sutartis ir vienas sakinys specifikacijoje, juos jungiantis

Ta susisietis yra tiksliai vieta, kur sava pasigaminta koduotė klysta. Ankstesnis HotXLS rašytojas supakavo kiekvieną lauką į mažiausią bitų skaičių, užpildydamas iki baito ribos tarp eilučių, kas ginama izoliuotai ir tiesiogiai prieštarauja plyčiui, kurį tas pats rašytojas ką tik deklaravo SXFDB. Laukas su trimis atskirtomis reikšmėmis buvo aprašytas kaip vieno baito plyčio viename įraše ir užėmė du bitus kitame. Pataisymas nebuvo ištaisyti aritmetiką, bet ištraukti plyčio sprendimą į vieną funkciją, kurią kviečia abu aktyvatoriai, tad du įrašai daugiau nebegali išsiskirti. Tai ta pati defektų klasė, aprašyta straipsnyje BIFF įrašo ilgio deklaracijos dreifas, kur deklaruotas dydis ir tikras kūnas išsiskiria

To, kad išvis neskaityti šių įrašų, pasekmė verta išsakyti atvirai, nes lengva ją nuvertinti. Kai skaitytuvas praleido įrašų indeksus, kiekvienas podėlis, pakrautas iš failo, pranešė nulinį indeksą kiekvienam kiekvienos eilutės laukui, kas reiškia, kad kiekviena eilutė rodydavo į pirmąją kiekvieno lauko reikšmę. Tai ne vien sumažinta apžiūra: pivot vertinimo kelias ir podėlio-į-langelį užpildymo kelias abu vartoja tą gardę. Ir rato atgal-pirmyn testas to negali aptikti, nes podėlis, vis dar esantis žaliame atgrojime, rašomas atgal iš savo originalių baitų

// Kilmės vėliavos pasako, ką laikote ir ką galima perrašyti
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);
  // Pakartotinis išdavimas be nuostolių tik tada, kai kiekvienas įrašas turi modelį
  if Cache.CanUpgradeFraming then
    Writeln('safe to rewrite with the current emitters');
end;

Kada podėlio perrašymas yra be nuostolių?

Tik kai trys sąlygos laikosi kartu, o CanUpgradeFraming yra vienintelė savybė, atsakanti į klausimą. Podėlis turi vis dar būti žaliame atgrojime, substream turi būti viename iš rėminimų, kuriuos ši biblioteka anksčiau rašė neteisingai, o skaitytuvas turi būti pastatęs pilną tipizuotą kiekvieno jo įrašo modelį. Excel parašytas podėlis niekada negalioja, nes jo substream neša įrašus, kuriems HotXLS neturi modelio, ir pakartotinis išdavimas iš modelio juos numestų

Pilnumo testas yra griežtesnis, nei iš pirmo žvilgsnio atrodo. Įrašas, kurį skaitytuvas laikė tik kaip nepermatomus baitus, pažymi modelį nepilnu. Taip pat ir deklaruotas formulės įrašų skaičius, kurio aktyvatorius negali atkurti, nes pakartotinis išdavimas perrašytų kelių formulės įrašų deklaraciją kaip nulinę deklaraciją, o failo reikšmė, kurios negalima atkurti, yra lygiaverčiai įrašui, kurio negalima atkurti

Sąmoningas konservatyumas bėga ir per rašytoją. Indeksai suspaudžiami į teisėtą diapazoną, o ne užkoduojami kaip už ribos einantis ženklas, nes specifikacija apibrėžia indeksą į atskirtų reikšmių seką ir nieką kita, o tuščias langelis pats yra reikšmė toje sekoje. Podėlio įrašo kūnas, viršijantis BIFF įrašo lubas, visai nerašomas, kas reikalautų tūkstančių podėlio laukų ir vis tiek nepasiekiama BIFF8 stulpelių ribos viduje; atsarginis kelias tas, kad Excel atnaujina iš šaltinio diapazono, kas yra apibrėžtas elgesys, o ne sugadintas failas

Datos neša paskutinę tarp įrašų priklausomybę. Serijos-į-datą konversija priklauso nuo darbo knygos datos sistemos, o įrašų aktyvatorius mato darbo knygą ne, tad bazinės datos pasirinkimas paduodamas kaip parametras, kuris pagal nutylėjimą yra 1900 sistema ir tiekiamas iš darbo knygos lygio įrašymo kelio. Po 1900 sistema serijos numeris yra reikšmė tiesiogiai; 1904 sistema skiriasi 1462 dienomis. Platesnis datų serijų tvarkymas straipsnyje datų serijos, 1904 sistema ir skaičių formatai

Jei dirbate vaizdo sluoksnyje, o ne podėlio sluoksnyje, įrašai, aprašantys matomą pivot, padengti straipsnyje BIFF8 PivotTable įrašų rinkinys, o skaičiavimo pusės elgesys straipsnyje skaičiuojami laukai, skaičiuojami elementai ir atnaujinimas. Visi trys sluoksniai keliauja su HotXLS Delphi skaičiuoklės komponentu, kas padaro įmanoma pakrauti seną darbo knygą, apžiūrėti, ką jos podėlis iš tikrųjų turi, ir nuspręsti, ar perrašymas saugus, dar prieš tai darant