Tekninen artikkeli

BIFF PivotCache -tietueiden kehystys HotXLS:llä Delphissä

BIFF:n PivotCache-alivirta tallentaa PivotTable:n välimuistitetun datasetin erilleen näkymästä, joka esittää sen, ja HotXLS lukee ja kirjoittaa tuon alivirran tutkimalla tietuerunkoja tietuenumeroihin luottamisen sijaan. Tuo erottelu on koko tarina: sama tietuenumero kantaa kahta yhteensopimatonta runkoasettelua riippuen siitä, kumpi kirjoittaja tuotti tiedoston, joten lukija päättää kehystyksen ensimmäisestä tietuerungosta, jonka se näkee

Tulet tämän kerroksen kohdalle sillä hetkellä, kun PivotTable:n on selättävä edestakainen matka. Pivot-näkymä ilman välimuistiaan on kuori, ja Excel rakentaa välimuistin uudelleen lähdealueesta avatessaan tiedoston, mikä on ihan hyvin siihen pisteeseen asti, jossa lähdealue on poissa, data liimattiin kyselystä tai työkirja on arkistoitu tilinpäätös, jota ei saa muuttua, kun joku avaa sen

Kaksi rakennetta, kaksi paikkaa tiedostossa

Välimuistitettu data ja välimuistin määrittely asuvat työkirjan eri osissa, ja niiden sekoittaminen on ensimmäinen oikein saatava asia. Välimuistitetut tietueet muodostavat oman alivirransa, annettu [MS-XLS] §2.1.7.12:ssä muodossa PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Huomaa, mikä puuttuu: tuon tuotannon alussa ei ole BOF:aa

Määrittely istuu sen sijaan työkirjan globaaleissa, muodossa PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), sijoitettuna muotoilutietueiden jälkeen ennen BoundSheet- ja Country-tietueita. Yksittäinen välimuisti kuvataan siis kahdessa paikassa, jotka ovat satojen tietueiden päässä toisistaan, ja niiden välinen linkki on virtatunniste, jonka on täsmättävä kolmessa paikassa kerralla

HotXLS:n PivotCache-kehystys BIFF8:ssa: PIVOTCACHEDEFINITION SXStreamID-ineen istuu työkirjan globaaleissa muotoilun jälkeen ennen BoundSheetia, kun taas välimuistitetut tietueet asuvat virrassa _SX_DB_CUR-tallennustilan alla, nimettynä nelinumeroisella isolla kirjoitetulla heksalla, sisältäen SXDB-, SXDBEx-, SXFORMULA-, FDB- ja DBB-tietueet ilman BOF:aa, ja SXStreamID.idStm:n, SXDB:n idstm-kentän ja virran nimen on täsmättävä
Yksittäinen pivot-välimuisti kuvataan kahdessa paikassa satojen tietueiden päässä toisistaan, yhdistettynä virtatunnisteella, jonka on täsmättävä globaaleissa, SXDB-otsikossa ja alivirran nimessä kerralla

Jokainen välimuisti kuuluu _SX_DB_CURin alle virtaan, jonka nimi on sen tunnisteen nelinumeroinen isoilla kirjaimilla kirjoitettu heksakirjoitus. SXStreamID.idStm, idstm-kenttä, joka toistuu SXDB-otsikossa, ja tuo virran nimen on kaikkien täsmättävä. Kun varaat uuden tunnisteen, varaa ensin jokainen tiedostosta jo luettu numero, tai uusi välimuisti voi vaatia numeroa, joka kuuluu vanhemmalle välimuistille, johon lukija ei ole vielä kulkenut

Yksi tunniste lisäksi nappaa ihmiset. Pivot-näkymän iCache-arvo on vastaavan SXStreamIDin nollapohjainen sijainti globaalissa järjestyksessä, ei välimuistitunniste, jota saat valita. Kirjoitettaessa se on kuvattava välimuistiobjektista sen todelliseen tulostussijaintiin, ja olemassa olevat näkymät on numeroitava uudelleen sen mukana, tai yhden välimuistin päivittäminen osoittaa hiljaisesti näkymän toiseen

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;     // luota välimuistitietueisiin
    Cache.SaveData := True;

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

    Cache.SetRecordCount(0);          // tyhjennä, sitten koko tietueruudukko
    Cache.SetRecordCount(500);
    Book.StorePivotCaches;
  finally
    Book.Free;
  end;
end;

Tupla-SetRecordCount ei ole taikauskoa. RecordCount on paljas ominaisuussijoitus, joka ei varaa, ja sisäinen kasvupolku alustaa vain uusimmat lisätyt rivit, joten välimuisti, jonka määrä asetettiin otsikkopolun kautta, voi päätyä nollapituiseen indeksiruudukkoon. Kirjoitukset RecordIndicesiin hylätään silloin ilman virhettä. Määrän asettaminen nollaan ja takaisin rakentaa ruudukon uudelleen, ja sen on tapahduttava sen jälkeen, kun jokainen kenttä on lisätty, koska rivin leveys tulee kenttien määrästä

Miksi tietuenumero ei voi kertoa runkoasettelua?

Koska tietuenumerot ja runkoasettelut muuttuivat eri aikoina, joten niiden välinen kuvaus ei ole funktio. Yksi numero legacy-joukossa ilmestyy vain vanhempien kirjoittajien tiedostoissa, mikä tekee siitä luotettavan signaalin yhteen suuntaan. Toinen numero on aidosti monitulkintainen: se ilmestyy sekä oikeissa tiedostoissa että joukossa väliversioita, jotka käyttivät uutta numeroa vanhalla runkoasettelulla

Kehystys on siksi päätettävä rungosta, ja kerran per välimuistialivirta eikä per tietue. HotXLS lukitsee murteen kunkin alivirran ensimmäisen SXDBB-tietueen pituudesta. Spesifikaation kehystyksessä yksi SXDBB pitää täsmälleen yhden välimuistitietueen, joten sen pituus on yhden rivin leveys. Vanhemmassa pakatussa kehystyksessä ensimmäinen tietue pitää niin monta riviä kuin mahtuu, joten mille tahansa välimuistille, jossa on enemmän kuin yksi rivi, se on vähintään kahden rivin leveys. Vertailu on ratkaiseva aina, kun kaksi ennustetta eroavat

HotXLS:n SXDBB-kehystyslukko: yksi tietuenumero kantaa kahta yhteensopimatonta runkoasettelua, joten lukija vertaa ensimmäisen SXDBB-tietueen pituutta rivin leveyteen, yhden rivin leveys lukitsee spesifikaation murteen kun taas kaksi tai useampi rivin leveys lukitsee legacy-paketun kehyksen, tasurit ottavat spesifikaation lukeman, ja murre lukittuu kerran per välimuistialivirta, ei per tietue
Tietuenumerot eivät voi päättää runkoasettelua, koska kaksi muuttuivat eri aikoina, joten HotXLS lukitsee murteen kerran per alivirta ensimmäisen SXDBB-pituudesta ja ottaa spesifikaation lukeman tasurilla

Kun ne eivät eroa, lukija ottaa spesifikaation lukeman periaatteella, että Excelin kirjoittamat tiedostot ylittävät määrältään välibuildin kirjoittamat tiedostot. Tuo sokea piste on kapea rakenteeltaan, ja kun se ilmenee, tiedosto itse toistuu silti tavu tavulta. Vain kutsujille paljastetut tyypitetyt indeksit vaikuttuvat

Indeksileveys asuu eri tietueessa

SXDBB (§2.4.276) kantaa yhden indeksin per välimuistikenttä, jonka distinct-value-lippu on asetettu, kenttäjärjestyksessä, ja kunkin indeksin leveys päätetään muualla: vastaava SXFDB-kenttätietue (§2.4.283) julistaa short-items-lipun, ja tuo lippu sanoo, varaaanko indeksi kaksi tavua vai yhden. Kaksi tietuetta, yksi esitetty sopimus ja yksi lause spesifikaatiossa, joka yhdistää ne

Tuo kytkös on juuri se paikka, jossa itse kehitetty koodaus menee pieleen. Aiempi HotXLS-kirjoittaja pakatoi jokaisen kentän minimibittimäärään, täyttäen tavurajaan rivien välillä, mikä on puolusteltavaa eristettynä ja suoraan ristiriidassa sen leveyden kanssa, jonka sama kirjoittaja oli juuri julistanut SXFDBssä. Kenttä, jolla oli kolme erillistä arvoa, kuvailtiin yhden tavun leveäksi yhdessä tietueessa ja varasi kaksi bittiä toisessa. Korjaus ei ollut aritmetiikan korjaaminen vaan leveyspäätöksen poimiminen yhteen funktioon, jota molemmat emittoijat kutsuvat, joten kaksi tietuetta eivät voi enää ajautua erilleen. Kyseessä on sama defektiluokka, jota artikkeli BIFF-tietueen pituusmäärittelyn ajautuminen kuvaa, jossa julistettu koko ja todellinen runko eroavat

Näiden tietueiden lukematta jättämisen seuraus kannattaa kirjoittaa ääneen, koska sitä on helppo aliarvioida. Kun lukija ohitti tietueindeksit, jokainen tiedostosta ladattu välimuisti raportoi indeksin nolla jokaiselle rivin jokaiselle kentälle, mikä tarkoittaa, että jokainen rivi osoitti jokaisen kentän ensimmäiseen arvoon. Kyseessä ei ole ainoastaan heikentynyt introspektio: pivot-arviointipolku ja välimuistista soluun -täyttöpolku kuluttavat molemmat tuon ruudukon. Ja edestakainen testi ei voi havaita sitä, koska raakatoistossa yhä oleva välimuisti kirjoitetaan takaisin sen alkuperäisistä tavuista

// Alkuperäisyyden liput kertovat, mitä pidät käsissäsi ja mitä saa kirjoittaa uudelleen
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);
  // Uudelleenemittoiminen on häviötöntä vain, kun jokaisella tietueella on malli täällä
  if Cache.CanUpgradeFraming then
    Writeln('safe to rewrite with the current emitters');
end;

Milloin välimuistin uudelleenkirjoittaminen on häviötöntä?

Vasta silloin, kun kolme ehtoa pitävät yhdessä, ja CanUpgradeFraming on se yksittäinen ominaisuus, joka vastaa kysymykseen. Välimuistin on yhä oltava raakatoistossa, alivirran on oltava yhdessä niistä kehyksistä, joita tämä kirjasto kirjoitti aiemmin väärin, ja lukijan on rakennettava täydellinen tyypitetty malli jokaisesta sen tietueesta. Excelin kirjoittama välimuisti ei koskaan kelpaa, koska sen alivirta kantaa tietueita, joille HotXLS:llä ei ole mallia, ja mallista uudelleenemittoiminen pudottaisi ne

Täydellisyystesti on tiukempi kuin se aluksi näyttää. Tietue, jonka lukija piti vain läpinäkymättöminä tavuina, merkitsee mallin epätäydelliseksi. Samoin julistettu määrä kaavatietueita, joita emittoija ei voi toistaa, koska uudelleenemittoiminen kirjoittaisi usean kaavatietueen julistuksen uudelleen julistuksena ei yhtään, ja arvo tiedostossa, jota ei voi toistaa, on yhtä kuin tietue, jota ei voi toistaa

Tahallinen konservatiivisuus kulkee kirjoittajan läpi. Indeksit kiristetään lailliseen alueeseen sen sijaan, että ne koodattaisiin ulkopuoliseksi sentineliksi, koska spesifikaatio määrittelee indeksin eriarvojen jonoon eikä mitään muuta, ja tyhjä solu on itsekin arvo tuossa jonossa. BIFF-tietueen katon ylittävä välimuistitietueen runko ei kirjoiteta lainkaan, mikä vaatisi tuhansia välimuistikenttiä ja on silti saavuttamaton BIFF8:n sarakerajan sisällä; varapolku on, että Excel päivittää lähdealueesta, mikä on määriteltyä käytöstä eikä vioittunutta tiedostoa

Päivämäärät kantavat viimeisen tietueiden välisen riippuvuuden. Sarja-päivämäärä-muunnos riippuu työkirjan päivämääräjärjestelmästä, eikä tietueemittoija näe työkirjaa, joten peruspäivämäärän valinta passitetaan parametrina, joka oletusarvoisesti on 1900-järjestelmä ja jonka työkirjatason tallennuspolku toimittaa. 1900-järjestelmässä sarjanumero on arvo suoraan; 1904-järjestelmä eroaa 1462 päivällä. Laajempi käsittely päivämääräsarjoista on artikkelissa päivämääräsarjat, 1904-järjestelmä ja lukumuodot

Jos työskentelet näkymäkerroksessa eikä välimuistikerroksessa, näkyvää pivotia kuvailevat tietueet on käsitelty artikkelissa BIFF8:n PivotTable-tietuejoukko, ja laskentapuolen käytös artikkelissa lasketut kentät, lasketut kohteet ja päivitys. Kaikki kolme kerrosta toimituvat HotXLS Delphi -taulukkolaskentakomponentissa, minkä takia on mahdollista ladata legacy-työkirja, tutkia, mitä sen välimuisti oikeasti sisältää, ja päättää, onko sen uudelleenkirjoittaminen turvallista ennen kuin teet sen