HotXLS, det native Delphi- og C++Builder-Excelbibliotek, læser den værdi, Excel allerede har gemt ved siden af en formel, gennem TryGetCachedFormulaValue og IXLSFormulaCacheReader. Ingen af indgangspunkterne kalder lommeregneren, dekompilerer formeltokens, opdaterer dirty-tilstand eller skriver noget tilbage i modellen, så en arbejdsbog, du kun læser, forbliver præcis, som du åbnede den
Scenariet, der driver dette, er kedeligt og ekstremt almindeligt. Et natligt job åbner et par hundrede arbejdsbøger produceret af andre, trækker én kolonne af totaler ud af hver og skubber tallene ind i et data warehouse. Totalerne ligger allerede i filerne — Excel beregnede dem og gemte dem. Alligevel, i det øjeblik jobbet beder en formelcelle om sin værdi, bygger et bibliotek, der kun har ét svar på det spørgsmål, en afhængighedsgraf og evaluerer hele arket, og et job, der burde være I/O-bundet, forvandles til en beregningsbenchmark
Hvorfor koster læsning af en formelcelle en fuld genberegning?
Fordi en værdi-getter på en formelcelle er en anmodning om at producere en værdi, og den eneste universelt korrekte måde at producere én på er at evaluere formlen. Det er det rigtige default for en applikation, der redigerer arbejdsbøger, og det forkerte default for en pipeline, der udtrækker dem. Værre er, at evaluering ikke er fri for sideeffekter: den skriver resultater tilbage i celler, vender dirty-flag, og den kan løse sig anderledes end den producerende applikation, når en funktion er uden understøttelse, eller en ekstern reference er brudt. Et job, du beskrev for din driftsafdeling som read-only, producerer stille en arbejdsbog, der ikke længere matcher den på disken, og hvis noget senere gemmer den, ændres filen på disken også
Cached-value-læsning er den anden halvdel af kontrakten. Den besvarer et snævrere spørgsmål — hvad gemte den producerende applikation her? — og nægter at besvare noget andet. Når du reelt vil have friske tal, giver HotXLS dig stadig inkrementel genberegning drevet af en afhængighedsgraf; pointen er, at udtrækning og evaluering bør være to forskellige kald, ikke ét kald med to humører
Tre ortogonale fakta om én celle
Konklusionen først: en cached formelværdi bærer tre uafhængige fakta, og at skjule dem i en enkelt Variant mister information, du behøver. TXLSFormulaCacheInfo holder dem adskilt som State, Kind og Value. TXLSFormulaCacheState noterer proveniens på tværs af fem tilfælde — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated og xlfcsInvalidated — mens TXLSFormulaCacheValueKind klassificerer nyttelasten som xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean eller xlfcvError. Denne adskillelse er det, der lader tilstedeværelse rapporteres ærligt: en cached blank, en cached tom streng, en cached False, en cached nul og en cached fejl er alle rigtige værdier, så tilstedeværelse kan aldrig udledes af VarIsEmpty eller VarIsNull. TryGetCachedFormulaValue returnerer True kun for xlfcsLoaded og xlfcsCalculated og udfylder stadig en diagnosticerbar tilstand, når den returnerer False
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row og Col er alle enbaserede her
if Book.TryGetCachedFormulaValue(1, 12, 5, Info) then
Writeln('cached value: ', VarToStr(Info.Value))
else
Writeln('no usable cache, state ordinal ', Ord(Info.State));
finally
Book.Free;
end;
end;
Hvorfor mangler den cachede værdi?
Der er præcis fire grunde til, at TryGetCachedFormulaValue overrækker False, og tilstanden fortæller dig, hvilken der gælder. xlfcsNotFormula betyder, at cellen holder en literal eller slet intet, og at koordinaterne er uden for intervallet skjuler sig i samme svar. xlfcsMissing betyder, at cellen reelt er en formel, men at producenten ikke gemte nogen værdinyttelast til den — et almindeligt udfald, når en generator skriver formler og lader Excel udfylde resultater ved første åbning. xlfcsInvalidated betyder, at formelteksten blev udskiftet efter indlæsning, så den værdi, der plejede at være der, beskriver et udtryk, der ikke længere findes. xlfcsCalculated er derimod et succesfelt: det markerer en værdi, din egen kode eller HotXLS-evaluatoren producerede i denne session, i modsætning til xlfcsLoaded, som kom fra filen
Ærlighed om en manglende cache betyder mere end at klister over den. HotXLS nægter at opfinde en værdi, og ved gemning er den lige så streng — kun xlfcsLoaded og xlfcsCalculated emitterer en cached værdi, mens xlfcsMissing og xlfcsInvalidated skriver formlen alene frem for at fryse et forældet tal ind i filen. Det efterlader dig tre fornuftige reaktioner i en pipeline: spring rækken over og notér hullet, genberegn dén arbejdsbog bevidst og accepter omkostningen, eller evaluér og forlig. Hvis det beregnede tal er uenigt med, hvad den producerende applikation ville have skrevet, er formevalueringstraceren værktøjet til at finde ud af, hvor de to beregninger divergerer, frem for at gætte ud fra resultatet
Én læser på tværs af de klassiske, OOXML- og ODF-motorer
En pipeline bør ikke bryde sig om, hvorvidt filen, den lige åbnede, var BIFF, OOXML eller ODF. IXLSFormulaCacheReader er det eneste read-only-indgangspunkt for alle tre: både TXLSWorkbook.CreateFormulaCacheReader og TXLSXWorkbook.CreateFormulaCacheReader returnerer en letvægtsadapter over det sparse celleopslag, hver motor allerede bruger, med identiske enbaserede ark-, række- og kolonnekoordinater. Arbejdsbogsklasserne implementerer bevidst ikke interfacet selv — en interfacereference til arbejdsbogen ville ændre dens ownership-semantik og lade kaldere glide forbi lifetime-leasen. I stedet rydder destruktion af arbejdsbogen den rå pointer inde i den leasen, og enhver læser, din kode stadig holder, udløser EXLSFormulaCacheReaderInvalidated ved sin næste forespørgsel i stedet for at dereferencere frigivet hukommelse. Det er fail-fast lifetime-kontrol, ikke en garanti for samtidighed
var
Reader: IXLSFormulaCacheReader;
Info: TXLSFormulaCacheInfo;
Row, Missing, Errors: Integer;
Total: Double;
begin
Reader := Book.CreateFormulaCacheReader;
Total := 0;
Missing := 0;
Errors := 0;
for Row := 2 to LastRow do
if Reader.TryGetCachedFormulaValue(1, Row, 7, Info) then
begin
case Info.Kind of
xlfcvNumber: Total := Total + Double(Info.Value);
xlfcvError: Inc(Errors);
end;
end
else if Info.State = xlfcsMissing then
Inc(Missing);
// Ingen lommeregner kørte, intet dirty-flag flyttede sig, Book er uændret
end;
Hvor de cachede bytes faktisk bor
For klassiske .xls-filer er cachen FormulaValue-feltet i Formula-recorden, otte byte beskrevet af [MS-XLS] §2.5.133. Når det høje ord er lig $FFFF, er nyttelasten ikke en IEEE 754 double, men en tagget variant, og layoutet er let at tage fejl af på den subtile måde: varianttypen ligger i val[0], og boolean- eller BErr-nyttelasten ligger i val[2], med val[1] udefineret. HotXLS læste tidligere nyttelasten fra val[1], hvilket er den slags off-by-one, der kun viser sig på de specifikke filer, der cacher en boolean eller en fejl frem for et tal. Læseren og writeren til delte formler er nu enige om de samme offsets, så en cached TRUE overlever en load og save intakt i stedet for at forfalde til støj
Typefidelity i pakkformaterne er et separat problem med sin egen fælde. I OOXML hænger den cachede værdi på c-elementet som <v>, med t-attributten, der navngiver typen pr. ECMA-376 Part 1 §18.3.1.4. HotXLS læser t="e" direkte ind i en varError-Variant og mapper den tilbage til standardfejlteksten ved gemning, så fejl aldrig udgiver sig for almindelige heltal — men Delphi-RTL hjælper dig ikke her, fordi VarAsType(Integer, varError) udløser en konverteringsundtagelse. Den virkende konstruktion sætter TVarData.VType og TVarData.VError direkte. Datoer følger samme disciplin i den modsatte retning: t="d" og ODF-dato-værditypen er eksplicitte typede deklarationer og bliver varDate, mens en BIFF-numerisk cache slet ikke bærer et dato-flag og derfor forbliver en Double. HotXLS gætter aldrig en dato ud fra et cellenummerformat, for talformatet er præsentation, og cachen er data. ODF tilføjer endnu et tilfælde, der er værd at kende — office:value-type="void" udtrykker en cache, der er til stede, men ikke bærer nogen værdi, og da ODF ikke har nogen fejl-værditype, bevares fejlagtig tekst som tekst frem for at blive forfremmet til en fejl
function DescribeCache(const Info: TXLSFormulaCacheInfo): string;
begin
case Info.State of
xlfcsNotFormula: Result := 'not a formula cell';
xlfcsMissing: Result := 'formula stored with no cached value';
xlfcsInvalidated: Result := 'formula replaced since load';
else
case Info.Kind of
xlfcvError: Result := 'error code ' + IntToStr(TVarData(Info.Value).VError);
xlfcvDateTime: Result := DateTimeToStr(VarToDateTime(Info.Value));
xlfcvBoolean: Result := BoolToStr(Info.Value, True);
xlfcvNumber: Result := FloatToStr(Double(Info.Value));
xlfcvString: Result := VarToStr(Info.Value);
else
Result := 'present but blank';
end;
end;
end;
Deler delte formler deres cachede værdier?
Nej, og at antage andet er måden, en sweep ender med at rapportere det samme tal for en hel kolonne. En OOXML delt formel deler kun formeludtrykket og lageroptimeringen; hver medlemscelle ejer stadig sin egen <v>. HotXLS propagerer derfor aldrig rodmedlemmets cache til en følger, der ankom uden en værdi, og en følger, der loadede som xlfcsMissing, rapporterer stadig xlfcsMissing efter en gemning og genåbning. Hvis du arbejder dig gennem, hvordan gruppen i første omgang gemmes og udvides, behandles mekanikken i attributten si for delte formler og dens udvidelse separat; for cache-læsning reducerer regelen til én linje — spørg hver celle, stol på intet, du ikke bad om
Cached-value-læsning, den samlede cross-engine-læser og genberegningsmotoren, du kan vælge ikke at kalde, følger alle med den standard HotXLS Delphi Spreadsheet Component til Delphi og C++Builder, uden afhængighed af Excel eller nogen OLE-automatiseringsserver; produktsiden bærer den fulde API-reference til de arbejdsbogs- og læserindgangspunkter, der vises her