Műszaki cikk

BIFF PivotCache rekordok keretezése HotXLS-sel Delphiben

A BIFF PivotCache substream a PivotTable gyorsítótárazott adathalmazát külön tárolja attól a nézettől, amely megjeleníti, a HotXLS pedig rekordtestek vizsgálatával olvassa és írja azt a substreamet, rekordszámokba vetett bizalom helyett. Ez a megkülönböztetés az egész történet: ugyanaz a rekordszám két összeegyeztethetetlen test-elrendezést hordoz attól függően, melyik író gyártotta a fájlt, tehát az olvasó az első látott rekordtestből dönti el a keretezést

Ezzel a réteggel akkor találkozik, amikor egy PivotTablenek körutat kell vészelnie. Egy nézet gyorsítótár nélkül héj, és az Excel a forrástartományból építi újra a gyorsítótárat, amikor megnyitja a fájlt, ami pontosan addig rendben van, amíg a forrástartomány megvan, az adat lekérdezésből ragasztás nem került be, és a munkafüzet nem egy archivált zárást jelent, amely nem változhat meg, ha valaki megnyitja

Két struktúra, két hely a fájlban

A gyorsítótárazott adat és a gyorsítótárdefiníció a munkafüzet különböző részeiben él, és az összemosásuk az első dolog, amelyet el lehet rontani. A gyorsítótárazott rekordok saját substreamet alkotnak, amelyet a [MS-XLS] §2.1.7.12 így ad meg: PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Figyelje meg, mi hiányzik: nincs BOF a produkciónak a fején

A definíció ehelyett a munkafüzet globális részeiben ül, mint PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), a formázási rekordok után és a BoundSheet és Country rekordok előtt pozicionálva. Tehát egyetlen gyorsítótárat két olyan hely ír le, amelyek több száz rekordnyira vannak egymástól, és a köztük lévő kapcsolat egy streamazonosító, amelynek három helyen kell egyszerre egyeznie

HotXLS PivotCache keretezés BIFF8-ban: a PIVOTCACHEDEFINITION az SXStreamID-jével a munkafüzet globális részeiben ül a formázás után és a BoundSheet előtt, miközben a gyorsítótárazott rekordok egy, a _SX_DB_CUR tár alatt lévő streamben élnek, négyjegyű nagybetűs hexadecimális névvel, SXDB, SXDBEx, SXFORMULA, FDB és DBB rekordokat tartva BOF nélkül, és az SXStreamID.idStm, az SXDB idstm mezője és a streamnév egyeznie kell
Egyetlen pivot gyorsítótárat két, több száz rekordnyira lévő hely ír le, egy streamazonosító köti össze őket, amelynek a globális részben, az SXDB fejlécben és a substream nevében kell egyszerre egyeznie

Minden gyorsítótár egy, a _SX_DB_CUR alatt lévő streambe való, amelynek a neve azonosítójának négyjegyű nagybetűs hexadecimális leírása. A SXStreamID.idStm, az SXDB fejlécében ismételt idstm mező és az a streamnév: mindháromnak egyeznie kell. Amikor új azonosítót foglal, előbb foglalja le a fájlból már kiolvasott minden számot, különben egy új gyorsítótár olyan számot jelölhet ki, amely egy annál idősebb gyorsítótáré, amelyhez az olvasó még nem ért el

Még egy azonosító kap el embereket. Az iCache érték egy pivot nézetben a megfelelő SXStreamID nulla alapú pozíciója a globális sorrendben, nem olyan gyorsítótárazonosító, amelyet választhat. Íráskor a gyorsítótárobjektumtól a tényleges kimeneti pozíciójáig kell leképezni, és a meglévő nézeteket vele együtt át kell számozni, különben egy gyorsítótár frissítése csendben egy másikra irányít át egy nézetet

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;     // bízzon a gyorsítótárazott rekordokban
    Cache.SaveData := True;

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

    Cache.SetRecordCount(0);          // törölje, aztán méretezze a rekordrácsot
    Cache.SetRecordCount(500);
    Book.StorePivotCaches;
  finally
    Book.Free;
  end;
end;

A kettős SetRecordCount nem babona. A RecordCount sima tulajdonságírás, amely nem foglal, és a belső növekedési út csak az újonnan hozzáadott sorokat inicializálja, tehát egy olyan gyorsítótár, amelynek a darabszámát a fejlécúton állították, nulla hosszúságú indexráccsal végződhet. A RecordIndices-be írt írások ezután hibátlanul elvesznek. A darabszám nullára, majd vissza állítása újjáépíti a rácsot, és ennek minden mező hozzáadása után kell történnie, mert a sorszélesség a mezőszámból jön

Miért nem mondhatja meg a rekordszám a test-elrendezést?

Mert a rekordszámok és a test-elrendezések különböző időpontokban változtak, tehát a köztük lévő leképezés nem függvény. A legacy halmaz egyik száma csak idősebb írók fájaiban jelenik meg, ami egy irányban megbízható jeletté teszi. Egy másik szám valóban kétértelmű: helyes fájlokban és egy olyan köztes verziótartományban egyaránt megjelenik, amely az új számot a régi test-elrendezéssel használta

A keretezést ezért a testből kell eldönteni, és gyorsítótár substreamenként egyszer, rekordonkénti helyett. A HotXLS a legelső SXDBB rekord hosszából rögzíti a dialektust minden substreamben. A specifikációs keretezésben egy SXDBB pontosan egy gyorsítótárrekordot hordoz, tehát a hossza egy sorszélesség. Az idősebb, tömörített keretezésben az első rekord annyi sort hordoz, amennyi belefér, tehát minden egynél több soros gyorsítótárnál legalább két sorszélesség. Az összehasonlítás döntő, amikor a két jóslat eltér

HotXLS SXDBB keretezési rögzítés: egy rekordszám két összeegyeztethetetlen test-elrendezést hordoz, tehát az olvasó az első SXDBB rekord hosszát veti össze a sorszélességgel, egy sorszélesség a specifikációs dialektust rögzíti, míg két vagy több sorszélesség a legacy tömör keretezést, döntetlennél a specifikációs olvasatot veszik, és a dialektus gyorsítótár substreamenként rögzül egyszer, rekordonkénti helyett
A rekordszámok nem dönthetik el a test-elrendezést, mert a kettő különböző időpontokban változott, ezért a HotXLS substreamenként egyszer, az első SXDBB hosszából rögzíti a dialektust, és döntetlennél a specifikációs olvasatot veszi

Amikor nem térnek el, az olvasó a specifikációs olvasatot veszi, azon az elven, hogy az Excel által írt fájlok egy köztes build által írtaknál többen vannak. Ez a vakfolt szerkezeténél fogva szűk, és amikor mégis előfordul, maga a fájl bájtról bájtra visszajátszik továbbra is. Csak a hívóknak kitért típusos indexek érintettek

Az indexszélesség egy másik rekordban él

A SXDBB (§2.4.276) minden olyan gyorsítótármezőhöz egy indexet hordoz, amelynek a distinct-value zászlója be van állítva, mezősorrendben, és minden index szélességét máshol döntik el: a megfelelő SXFDB mezőrekord (§2.4.283) short-items zászlót deklarál, és az a zászló mondja meg, hogy az index két bájtot vagy egyet foglal-e. Két rekord, egy sugallt szerződés, és a specifikációnak egyetlen mondata köti össze őket

Az a csatolás pontosan az a hely, ahol egy házilag gyártott kódolás elront. Egy korábbi HotXLS író minden mezőt a minimális bitszámra csomagolt, sorközi bájthatárig bővítve, ami önmagában védhető, és közvetlenül ellentmond annak a szélességnek, amelyet ugyanaz az író éppen a SXFDB-ben deklarált. Egy három különböző értékű mezőt az egyik rekordban egy bájt szélesnek írtak le, a másikban két bitet foglalt. A javítás nem a számtan kijavítása volt, hanem a szélességi döntés egyetlen függvénybe emelése, amelyet mindkét emitter hív, így a két rekord már nem sodródhat szét. Ez a BIFF rekordhossz-deklaráció sodródás cikkben leírt hibakategória, ahol egy deklarált méret és egy tényleges test elszakad egymástól

Az, hogy mi következik abból, ha ezeket a rekordokat egyáltalán nem olvassák, megérdemli, hogy kimondják, mert könnyű alábecsülni. Amikor az olvasó kihagyta a rekordindexeket, fájlból betöltött minden gyorsítótár minden sora minden mezőjéhez nulla indexet jelentett, ami azt jelenti, hogy minden sor minden mező első értékére mutatott. Ez nem pusztán visszafogott introspekció: a pivot kiértékelési út és a cellába töltő út is fogyasztja azt a rácsot. És egy oda-vissza teszt nem tudja észrevenni, mert egy még nyers visszajátszáson lévő gyorsítótár az eredeti bájtjairól íródik vissza

// A provenancia zászlók megmondják, mit tart a kezében és mit lehet újraírni
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);
  // Az újrakibocsátás csak akkor veszteségmentes, ha itt minden rekordnak van modellje
  if Cache.CanUpgradeFraming then
    Writeln('safe to rewrite with the current emitters');
end;

Mikor veszteségmentes egy gyorsítótár újraírása?

Csak akkor, ha három feltétel egyszerre áll, és a CanUpgradeFraming az az egyetlen tulajdonság, amely a kérdésre válaszol. A gyorsítótárnak még mindig nyers visszajátszáson kell lennie, a substreamnek a könyvtár által korábban hibásan írt keretezések egyikében, és az olvasónak a benne lévő minden rekord teljes típusos modelljét fel kellett építenie. Egy Excel által írt gyorsítótár sosem minősül, mert a substreamje olyan rekordokat hordoz, amelyekre a HotXLSnek nincs modellje, és a modellből való újrakibocsátás azokat eldobná

A teljességi teszt szigorúbb, mint elsőre látszik. Egy rekord, amelyet az olvasó csak opakként, bájtokként tartott meg, befejezetlenné teszi a modellt. Ugyanígy egy deklarált képletrekordszám, amelyet az emitter nem tud reprodukálni, mert az újrakibocsátás több képletrekord deklarációját nulla deklarációjaként írná át, és egy fájlon belüli, nem reprodukálható érték egy nem reprodukálható rekorddal egyenértékű

A szándékos konzervativizmus az írón is végigmegy. Az indexek a legális tartományba szoríttatnak, out-of-band jelzőként kódolás helyett, mert a specifikáció a distinct-value sorozatba mutató indexet definiál, semmi mást, és egy üres cella maga is érték abban a sorozatban. A BIFF rekordplafonján túllépő gyorsítótárrekordtest egyáltalán nem íródik, ami ezernyi gyorsítótármezőt igényelne, és a BIFF8 oszlophatáron belül úgyis elérhetetlen; a tartalék az, hogy az Excel a forrástartományból frissít, ami definiált viselkedés, nem sérült fájl

A dátumok hordozzák az utolsó rekordok közti függést. A sorozatszám-dátum átváltás a munkafüzet dátumrendszerétől függ, a rekordemitter pedig nem látja a munkafüzetet, tehát az alapdátum-választás paraméterként érkezik, amely alapértelmezés szerint az 1900-as rendszert választja, és a munkafüzet szintjén lévő mentési út szolgáltatja. Az 1900-as rendszer alatt a sorozatszám közvetlenül az érték; az 1904-es rendszer 1462 nappal tér el. A dátumsorozatszámok tágabb kezelését a dátumsorozatszámok, az 1904-es rendszer és a számformátumok tárgyalja

Ha a nézet rétegén dolgozik, nem a gyorsítótárén, a látható pivotot leíró rekordokat a BIFF8 PivotTable rekordkészlet tárgyalja, a számítási oldali viselkedést pedig a számított mezők, számított elemek és frissítés. Mindhárom réteg szállul a HotXLS Delphi táblázatkezelő komponensben, ami az, ami lehetővé teszi egy legacy munkafüzet betöltését, annak kiderítését, mit tartalmaz valójában a gyorsítótára, és annak eldöntését, hogy az újraírása biztonságos-e, mielőtt megtenné