HotXLS, det native Delphi- og C++Builder Excel-biblioteket, leser verdien Excel allerede lagret ved siden av en formel gjennom TryGetCachedFormulaValue og IXLSFormulaCacheReader. Ingen av inngangspunktene kaller kalkulatoren, dekompilerer formelsymboler, oppdaterer skitten tilstand, eller skriver noe tilbake inn i modellen, så en arbeidsbok du bare leser forblir nøyaktig slik du åpnet den
Scenariet som driver dette er kjedelig og ekstremt vanlig. En nattlig jobb åpner noen hundre arbeidsbøker produsert av noen andre, trekker ut én kolonne med summer fra hver, og skyver tallene inn i et varehus. Summene sitter allerede i filene — Excel beregnet dem og lagret dem. Likevel, i det øyeblikket jobben ber en formelcelle om sin verdi, bygger et bibliotek som bare har ett svar på det spørsmålet en avhengighetsgraf og evaluerer hele arket, og en jobb som skulle være I/O-bundet blir til en beregningsbenchmark
Hvorfor koster lesing av en formelcelle en full rekalkulering?
Fordi en verdihenter på en formelcelle er en forespørsel om å produsere en verdi, og den eneste universelt korrekte måten å produsere én er å evaluere formelen. Det er riktig standard for en applikasjon som redigerer arbeidsbøker, og feil standard for en pipeline som trekker dem ut. Verre, evaluering er ikke fri for bieffekter: den skriver resultater tilbake inn i celler, den snur skitne flagg, og den kan løse annerledes enn den produserende applikasjonen når en funksjon er ustøttet eller en ekstern referanse er brutt. En jobb du beskrev til driftsteamet ditt som skrivebeskyttet produserer stille en arbeidsbok som ikke lenger stemmer med den på disken, og hvis noe senere lagrer den, endres filen på disken også
Bufret-verdi-lesing er den andre halvdelen av kontrakten. Den svarer på et smalere spørsmål — hva lagret den produserende applikasjonen her? — og nekter å svare på noe annet. Når du genuint vil ha ferske tall, gir HotXLS deg fremdeles inkrementell rekalkulering drevet av en avhengighetsgraf; poenget er at uttrekk og evaluering bør være to forskjellige kall, ikke ett kall med to stemninger
Tre ortogonale fakta om én celle
Konklusjonen først: en bufret formelverdi bærer tre uavhengige fakta, og å kollapse dem til én enkelt Variant mister informasjon du trenger. TXLSFormulaCacheInfo holder dem atskilt som State, Kind og Value. TXLSFormulaCacheState registrerer proveniens på tvers av fem tilfeller — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated og xlfcsInvalidated — mens TXLSFormulaCacheValueKind klassifiserer nyttelasten som xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean eller xlfcvError. Den separasjonen er det som lar tilstedeværelse rapporteres ærlig: en bufret blank, en bufret tom streng, en bufret False, en bufret null og en bufret feil er alle ekte verdier, så tilstedeværelse kan aldri utledes fra VarIsEmpty eller VarIsNull. TryGetCachedFormulaValue returnerer True bare for xlfcsLoaded og xlfcsCalculated, og fyller fremdeles inn en diagnosérbar 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 én-baserte 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 bufrede verdien?
Det er nøyaktig fire grunner til at TryGetCachedFormulaValue overleverer False, og tilstanden forteller deg hvilken som gjelder. xlfcsNotFormula betyr at cellen holder en literal eller ingenting i det hele tatt, og koordinatene utenfor rekkevidde kollapser inn i samme svar. xlfcsMissing betyr at cellen virkelig er en formel men produsenten lagret ingen verdinyttelast for den — et vanlig utfall når en generator skriver formler og lar Excel fylle inn resultater ved første åpning. xlfcsInvalidated betyr at formelteksten ble erstattet etter innlasting, så verdien som pleide å være der beskriver et uttrykk som ikke lenger finnes. xlfcsCalculated er derimot et suksess-tilfelle: den markerer en verdi din egen kode eller HotXLS-evaluatoren produserte i denne økten, i motsetning til xlfcsLoaded, som kom fra filen
Ærlighet om en manglende buffer betyr mer enn å tapetsere over den. HotXLS nekter å finne opp en verdi, og ved lagring er den like streng — bare xlfcsLoaded og xlfcsCalculated avgir en bufret verdi, mens xlfcsMissing og xlfcsInvalidated skriver formelen alene snarere enn å fryse et gammelt tall inn i filen. Det lar deg tre fornuftige svar i en pipeline: hopp over raden og registrer gapet, rekalkuler den ene arbeidsboken villet og aksepter kostnaden, eller evaluer og forson. Dissen det evaluerte tallet med hva den produserende applikasjonen ville ha skrevet, er formel-evaluerings-sporeren verktøyet for å finne ut hvor de to beregningene divergerer, snarere enn å gjette fra resultatet
Én leser på tvers av de klassiske, OOXML- og ODF-motorene
En pipeline bør ikke bry seg om filen den nettopp åpnet var BIFF, OOXML eller ODF. IXLSFormulaCacheReader er det enkelt skrivebeskyttede inngangspunktet for alle tre: både TXLSWorkbook.CreateFormulaCacheReader og TXLSXWorkbook.CreateFormulaCacheReader returnerer en lettvekt adapter over den sparsomme celleslutten hver motor allerede bruker, med identiske én-baserte ark-, rad- og kolonnekoordinater. Arbeidsbok-klassene implementerer villet ikke grensesnittet selv — en grensesnittreferanse til arbeidsboken ville endret dens eierskapssemantikk og latt kallere slippe forbi levetidsleien. I stedet rydder ødeleggelse av arbeidsboken den rå pekeren inne i den leien, og enhver leser fortsatt holdt av koden din utløser EXLSFormulaCacheReaderInvalidated ved sin neste spørring i stedet for å dereferere frigjort minne. Det er fail-fast levetidssjekking, ikke en samtidighetsgaranti
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 kalkulator kjørte, intet skittent flagg flyttet, Book er uendret
end;
Hvor de bufrede bytene faktisk bor
For klassiske .xls-filer er bufferen FormulaValue-feltet til Formula-posten, åtte byte beskrevet av [MS-XLS] §2.5.133. Når høyordet er likt $FFFF er nyttelasten ikke en IEEE 754 double men en tagget variant, og oppsettet er lett å ta subtilt galt: varianttypen sitter i val[0] og boolsk- eller BErr-nyttelasten sitter i val[2], med val[1] udefinert. HotXLS leste tidligere nyttelasten fra val[1], noe som er den typen off-by-one som dukker opp bare på de spesifikke filene som bufre en boolsk eller en feil snarere enn et tall. Leseren og shared-formula skriveren er nå enige om samme offsets, så en bufret TRUE overlever en innlasting og lagring intakt i stedet for å forfalle til støy
Type-trohet i pakkeformatene er et separat problem med sin egen felle. I OOXML henger den bufrede verdien av c-elementet som <v>, med t-attributtet som navngir typen etter ECMA-376 Part 1 §18.3.1.4. HotXLS leser t="e" rett inn i en varError Variant og mapper den tilbake til standard feiltekst ved lagring, så feiler forkleder seg aldri som vanlige heltall — men Delphi RTL hjelper deg ikke her, for VarAsType(Integer, varError) utløser et konverteringsunntak. Den virksomme konstruksjonen setter TVarData.VType og TVarData.VError direkte. Datoer følger samme disiplin i motsatt retning: t="d" og ODF datoverditypen er eksplisitte typedeklarasjoner og blir varDate, mens en BIFF numerisk buffer bærer intet datoflagg i det hele tatt og derfor forblir en Double. HotXLS gjetter aldri en dato fra et cellenummerformat, for nummerformatet er presentasjon og bufferen er data. ODF legger til ett tilfelle til verdt å vite — office:value-type="void" uttrykker en buffer som er til stede men bærer ingen verdi, og siden ODF ikke har noen feilverditype, bevares feilaktig tekst som tekst snarere enn å forfremmes til en feil
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 sine bufrede verdier?
Nei, og å anta ellers er hvordan en gjennomgang ender opp med å rapportere samme tall for en hel kolonne. En OOXML delt formel deler formeluttrykket og lagringsoptimaliseringen bare; hver medlemscelle eier fremdeles sin egen <v>. HotXLS propagerer derfor aldri rotmedlems-bufferen til en følger som ankom uten en verdi, og en følger som lastet som xlfcsMissing rapporterer fremdeles xlfcsMissing etter en lagring og gjenåpning. Jobber du gjennom hvordan gruppen lagres og utvides i første omgang, dekkes mekanikken i shared formula si-attributten og dens ekspansjon separat; for bufferlesing reduseres regelen til én linje — spør hver celle, stol på ingenting du ikke spurte om
Bufret-verdi-lesing, den forente kryssmotor-leseren og rekalkuleringsmotoren du kan velge ikke å kalle, leveres alle i standard HotXLS Delphi Spreadsheet Component for Delphi og C++Builder, uten avhengighet av Excel eller noen OLE-automatiseringsserver; produktsiden bærer den fullstendige API-referansen for arbeidsbok- og leser-inngangspunktene vist her