BIFF:s PivotCache-underström lagrar en PivotTables cachade datamängd skild från vyn som visar den, och HotXLS läser och skriver den underströmmen genom att inspektera postkroppar i stället för att lita på postnummer. Den distinktionen är hela historien: samma postnummer bär två inkompatibla kroppslayouter beroende på vilken skrivare som producerade filen, så läsaren bestämmer inramningen från den första postkropp den ser
Du möter detta lager i samma stund en PivotTable ska överleva en rundresa. En pivotvy utan sin cache är ett skal, och Excel bygger om cachen från källområdet när den öppnar filen, vilket är fint ända till dess att källområdet är borta, datan klistrats in från en query, eller arbetsboken är ett arkiverat bokslut som inte får ändras när någon öppnar den
Två strukturer, två ställen i filen
De cachade data och cachedefinitionen bor i olika delar av arbetsboken, och att blanda ihop dem är det första att få rätt. De cachade posterna bildar sin egen underström, given i [MS-XLS] §2.1.7.12 som PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Notera vad som saknas: det finns ingen BOF i huvudet på den produktionen
Definitionen sitter i stället i arbetsbokens globals, som PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), positionerad efter formatposterna och före BoundSheet- och Country-posterna. Så en enskild cache beskrivs på två ställen som ligger hundratals poster isär, och länken mellan dem är en stream-identifierare som måste stämma på tre ställen samtidigt
Varje cache hör hemma i en ström under _SX_DB_CUR vars namn är dess identifiers fyrsiffriga versala hexadecimala stavning. SXStreamID.idStm, fältet idstm repeterat i SXDB-huvudet, och det strömnamnet måste alla matcha. När du allokerar en ny identifierare, reservera först vartenda nummer redan läst ur filen, annars kan en ny cache kräva ett nummer som tillhör en äldre cache läsaren ännu inte vandrat till
En identifierare till får folk att snubbla. Värdet iCache i en pivotvy är den nollbaserade positionen av motsvarande SXStreamID i den globala sekvensen, inte en cacheidentifierare du får välja. Vid skrivning måste den mappas från cacheobjektet till dess faktiska utmatningsposition, och befintliga vyer måste numreras om tillsammans med den, annars pekar en uppgradering av en cache tyst en vy mot en annan
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; // lita på de cachade posterna
Cache.SaveData := True;
Field := Cache.AddField('Region', xlpcftString);
V.ValueType := xlpcftString;
V.StrValue := 'North';
Field.FindOrAddItem(V);
Cache.SetRecordCount(0); // nollställ, storlekssätt sedan postnätet
Cache.SetRecordCount(500);
Book.StorePivotCaches;
finally
Book.Free;
end;
end;
Den dubbla SetRecordCount är inte vidskepelse. RecordCount är en enkel egenskapsskrivning som inte allokerar, och den interna tillväxtvägen initierar endast de nyligen tillagda raderna, så en cache vars antal satts genom huvudvägen kan sluta med ett nollängdat indexnät. Skrivningar till RecordIndices kastas då bort utan fel. Att sätta antalet till noll och tillbaka återupprättar nätet, och det måste hända efter att varje fält lagts till, eftersom radbredden kommer från fältantalet
Varför kan ett postnummer inte berätta kroppslayouten?
Därför att postnummer och kroppslayouter ändrades vid olika tidpunkter, så mappningen mellan dem inte är en funktion. Ett nummer i den äldre uppsättningen uppträder bara i filer från äldre skrivare, vilket gör det till en pålitlig signal i en riktning. Ett annat nummer är genuint tvetydigt: det uppträder både i korrekta filer och i ett intervall av mellanliggande versioner som använde det nya numret med den gamla kroppslayouten
Inramningen måste därför bestämmas från kroppen, och en gång per cacheunderström snarare än per post. HotXLS låser dialekten från längden på den första SXDBB-posten i varje underström. I specifikationsinramningen håller en SXDBB exakt en cache-post, så dess längd lika en radbredd. I den äldre packade inramningen håller den första posten så många rader som får plats, så för vilken cache med mer än en rad är den åtminstone två radbredder. Jämförelsen är avgörande närhelst de två förutsägelserna skiljer
När de inte skiljer tar läsaren specifikationsläsningen, på principen att filer skrivna av Excel är fler än filer skrivna av ett mellanbygge. Den blinda fläcken är smal av konstruktion och, när den väl inträffar, spelas filen själv ändå upp byte för byte. Endast de typade index exponerade mot anropare påverkas
Indexbredden bor i en annan post
SXDBB (§2.4.276) bär ett index per cachefält vars unika-värde-flagga är satt, i fältordning, och bredden på varje index bestäms någon annanstans: den motsvarande SXFDB-fältposten (§2.4.283) deklarerar en short-items-flagga, och den flaggan säger huruvida indexet upptar två byte eller en. Två poster, ett underförstått kontrakt, och en enda mening i specifikationen som förbinder dem
Den kopplingen är exakt där en hemmapularencoding går fel. En tidigare HotXLS-skrivare packade varje fält i det minsta antalet bitar, paddande till en bytegräns mellan rader, vilket är försvarbart isolerat och direkt motsäger bredden samma skrivare just deklarerat i SXFDB. Ett fält med tre unika värden beskrevs som en byte bred i en post och upptog två bitar i den andra. Fixen var inte att korrigera aritmetiken utan att extrahera breddsbeslutet till en funktion som båda emitterarna anropar, så de två posterna inte längre kan driva isär. Det är samma klass av defekt som beskrivs i BIFF-postlängdsdeklarationsdrift, där en deklarerad storlek och en faktisk kropp skiljs åt
Konsekvensen av att inte läsa dessa poster alls är värd att stavas ut, eftersom den är lätt att underskatta. När läsaren hoppade över postindexen rapporterade varje cache inläst från en fil index noll för vartenda fält i varje rad, vilket betyder att varje rad pekade på det första värdet i varje fält. Det är inte blott minskad introspektion: pivotutvärderingsvägen och cache-till-cell-fyllnadsvägen konsumerar båda det nätet. Och ett roundtrip-test kan inte upptäcka det, för en cache fortfarande på rå uppspelning skrivs tillbaka från sina ursprungliga byte
// Proveniensflaggor talar om vad du håller och vad som får skrivas om
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);
// Återutsändning är bara förlustfri när varje post har en modell här
if Cache.CanUpgradeFraming then
Writeln('safe to rewrite with the current emitters');
end;
När är omskrivning av en cache förlustfri?
Endast när tre villkor gäller tillsammans, och CanUpgradeFraming är den enskilda egenskap som svarar på frågan. Cachen måste fortfarande vara på rå uppspelning, underströmmen måste vara i en av de inramningar detta bibliotek tidigare skrivit felaktigt, och läsaren måste ha byggt en komplett typad modell av varje post i den. En cache Excel skrev kvalificerar sig aldrig, för dess underström bär poster HotXLS saknar modell för, och återutsändning från modellen skulle tappa dem
Kompletthetstestet är strängare än det först framstår. En post läsaren bara behållit som opaka byte märker modellen inkomplett. Samma sak gäller ett deklarerat antal formelposter som emitteraren inte kan reproducera, eftersom återutsändning skulle skriva om en deklaration av flera formelposter till en deklaration av ingen, och ett värde i filen som inte kan reproduceras är likvärdigt med en post som inte kan reproduceras
Medveten konservatism genomsyrar skrivaren också. Index kläms in i det lagliga intervallet i stället för att kodas som en out-of-band-sentinel, eftersom specifikationen definierar ett index in i sekvensen av unika värden och ingenting annat, och en tom cell är själv ett värde i den sekvensen. En cache-postkropp som överskrider BIFF:s posttak skrivs inte alls, vilket skulle kräva tusentals cachefält och är oåtkomligt inom BIFF8-kolumngränsen ändå; reservvägen är att Excel uppdaterar från källområdet, vilket är definierat beteende snarare än en korrupt fil
Datum bär det sista korspostberoendet. Konverteringen seriellt nummer till datum beror på arbetsbokens datumssystem, och postemitteraren kan inte se arbetsboken, så basdatumsvalet skickas in som en parameter med 1900-systemet som förval och tillhandahålls av sparvägen på arbetsboksnivå. Under 1900-systemet är serienumret värdet direkt; 1904-systemet skiljer med 1462 dagar. Den bredare behandlingen av datumserier finns i datumserier, 1904-systemet och talformat
Om du arbetar på vylagret i stället för cachelagret täcks posterna som beskriver den synliga pivoten i BIFF8 PivotTable-postsetet, och beräkningssidans beteende i beräknade fält, beräknade poster och uppdatering. Alla tre lagren medföljer HotXLS Delphi spreadsheet component, vilket är det som gör det möjligt att läsa in en äldre arbetsbok, granska vad dess cache faktiskt innehåller och avgöra huruvida omskrivning är säker innan du gör det