HotXLS, det nativa Delphi- och C++Builder-Excelbiblioteket, läser värdet som Excel redan lagrat bredvid en formel via TryGetCachedFormulaValue och IXLSFormulaCacheReader. Ingen av ingångspunkterna anropar kalkylatorn, dekompilerar formeltokens, uppdaterar smutstillstånd eller skriver något tillbaka in i modellen, så en arbetsbok du bara läser förblir exakt som du öppnade den
Scenariot som driver detta är tråkigt och extremt vanligt. Ett nattjobb öppnar några hundra arbetsböcker producerade av någon annan, drar ut en kolumn av totalsummor ur var och en, och trycker in talen i ett datalager. Totalsummorna ligger redan i filerna — Excel beräknade dem och sparade dem. Men i samma ögonblick som jobbet frågar en formelcell efter dess värde bygger ett bibliotek som bara har ett svar på den frågan en beroendegraf och evaluerar hela arket, och ett jobb som borde vara I/O-bundet förvandlas till ett beräkningsriktmärke
Varför kostar det en fullständig omberäkning att läsa en formelcell?
Eftersom en värdgetter på en formelcell är en begäran om att producera ett värde, och det enda universellt korrekta sättet att producera ett är att evaluera formeln. Det är rätt default för en applikation som redigerar arbetsböcker, och fel default för en pipeline som extraherar dem. Värre är att evaluering inte är fri från sidoeffekter: den skriver tillbaka resultat in i celler, den flippar smutsflaggor, och den kan lösa annorlunda än den producerande applikationen när en funktion inte stöds eller en extern referens är bruten. Ett jobb du beskrev för ditt driftteam som skrivskyddat producerar tyst en arbetsbok som inte längre matchar den på disk, och om något senare sparar den ändras filen på disk också
Cachelagrad värdeläsning är kontraktets andra hälft. Det svarar på en snävare fråga — vad lagrade den producerande applikationen här? — och vägrar svara på något annat. När du genuint vill ha färska tal ger HotXLS dig fortfarande inkrementell omberäkning driven av en beroendegraf; poängen är att extrahering och evaluering ska vara två olika anrop, inte ett anrop med två humör
Tre ortogonala fakta om en cell
Slutsatsen först: ett cachelagrat formelvärde bär tre oberoende fakta, och att slå ihop dem till en enda Variant tappar information du behöver. TXLSFormulaCacheInfo håller dem isär som State, Kind och Value. TXLSFormulaCacheState registrerar proveniens över fem fall — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated och xlfcsInvalidated — medan TXLSFormulaCacheValueKind klassificerar nyttolasten som xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean eller xlfcvError. Denna separation är vad som låter närvaro rapporteras ärligt: ett cachelagrat blankt, en cachelagrad tom sträng, ett cachelagrat False, en cachelagrad nolla och ett cachelagrat fel är alla riktiga värden, så närvaro kan aldrig härledas från VarIsEmpty eller VarIsNull. TryGetCachedFormulaValue returnerar True bara för xlfcsLoaded och xlfcsCalculated, och fyller fortfarande i ett diagnostiserbart tillstånd när den returnerar False
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row och Col är alla 1-baserade här
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;
Varför saknas det cachelagrade värdet?
Det finns exakt fyra skäl till att TryGetCachedFormulaValue lämnar tillbaka False, och tillståndet talar om vilket som gäller. xlfcsNotFormula betyder att cellen håller en litteral eller inget alls, och att koordinaterna är utanför intervallet kollapsar till samma svar. xlfcsMissing betyder att cellen verkligen är en formel men producenten lagrade ingen värdenyttolast för den — ett vanligt utfall när en generator skriver formler och låter Excel fylla i resultat vid första öppning. xlfcsInvalidated betyder att formeltexten ersattes efter inläsning, så värdet som brukade finnas där beskriver ett uttryck som inte längre existerar. xlfcsCalculated, däremot, är ett framgångsfall: det markerar ett värde som din egen kod eller HotXLS-evalueraren producerade under denna session, till skillnad från xlfcsLoaded, som kom från filen
Ärlighet om en saknad cache spelar större roll än att mörka den. HotXLS vägrar att hitta på ett värde, och vid sparning är den lika strikt — bara xlfcsLoaded och xlfcsCalculated skriver ut ett cachelagrat värde, medan xlfcsMissing och xlfcsInvalidated skriver formeln ensam i stället för att frysa in ett inaktuellt tal i filen. Det lämnar dig tre sunda svar i en pipeline: hoppa över raden och registrera luckan, beräkna om just den arbetsboken avsiktligt och acceptera kostnaden, eller evaluera och stämma av. Om det evaluerade talet inte stämmer överens med vad den producerande applikationen skulle ha skrivit är formelevalueringstraceraren verktyget för att ta reda på var de två beräkningarna divergerar, i stället för att gissa från resultatet
En läsare över de klassiska, OOXML- och ODF-motorerna
En pipeline ska inte bry sig om filen den just öppnade var BIFF, OOXML eller ODF. IXLSFormulaCacheReader är den enda skrivskyddade ingångspunkten för alla tre: både TXLSWorkbook.CreateFormulaCacheReader och TXLSXWorkbook.CreateFormulaCacheReader returnerar en lättviktig adapter över den glesa celluppslagning varje motor redan använder, med identiska 1-baserade ark-, rad- och kolumnkoordinater. Arbetsboksklasserna implementerar avsiktligt inte interfacet själva — en interfacereferens till arbetsboken skulle ändra dess ägarskapssemantik och låta anropare slinka förbi livstidsleaset. I stället rensar förstörelsen av arbetsboken den råa pekaren inuti det leaset, och varje läsare som din kod fortfarande håller kastar EXLSFormulaCacheReaderInvalidated vid sin nästa fråga i stället för att dereferera frigjort minne. Det är fail-fast-livstidskontroll, inte en konkurrensgaranti
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 kalkylator kördes, ingen smutsflagga flyttades, Book är oförändrad
end;
Där de cachelagrade byten faktiskt bor
För klassiska .xls-filer är cachen fältet FormulaValue i Formula-posten, åtta byte beskrivna av [MS-XLS] §2.5.133. När höga ordet är lika med $FFFF är nyttolasten inte en IEEE 754 double utan en taggad variant, och layouten är lätt att få subtilt fel: varianttypen sitter i val[0] och boolean- eller BErr-nyttolasten sitter i val[2], med val[1] odefinierad. HotXLS läste tidigare nyttolasten från val[1], vilket är den sorts off-by-one som bara dyker upp på de specifika filer som cachelagrar en boolean eller ett fel i stället för ett tal. Läsaren och den delade formelskrivaren är nu överens om samma offsets, så ett cachelagrat TRUE överlever en inläsning och sparning intakt i stället för att falla sönder till brus
Typfidelity i paketformaten är ett separat problem med sin egen fälla. I OOXML hänger det cachelagrade värdet på c-elementet som <v>, med attributet t som namnger typen enligt ECMA-376 Part 1 §18.3.1.4. HotXLS läser t="e" rakt in i en varError Variant och mappar tillbaka den till standardfeltexten vid sparning, så fel maskerar sig aldrig som vanliga heltal — men Delphi RTL hjälper dig inte här, eftersom VarAsType(Integer, varError) kastar ett konverteringsundantag. Den fungerande konstruktionen sätter TVarData.VType och TVarData.VError direkt. Datum följer samma disciplin i motsatt riktning: t="d" och ODF:s datumvärdetyp är explicita typdeklarationer och blir varDate, medan en BIFF-numerisk cache inte bär någon datumflagga alls och därför förblir en Double. HotXLS gissar aldrig ett datum från ett cellnummerformat, eftersom talformatet är presentation och cachen är data. ODF lägger till ytterligare ett fall värt att känna till — office:value-type="void" uttrycker en cache som är närvarande men bär inget värde, och eftersom ODF saknar felvärdetyp bevaras felliknande text som text i stället för att befordras till ett fel
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;
Delar delade formler sina cachelagrade värden?
Nej, och att anta motsatsen är hur en svepning slutar rapportera samma tal för en hel kolumn. En OOXML delad formel delar bara formeluttrycket och lagringsoptimeringen; varje medlemscell äger fortfarande sin egen <v>. HotXLS propagerar därför aldrig rotmedlemmens cache till en följare som anlände utan ett värde, och en följare som laddades som xlfcsMissing rapporterar fortfarande xlfcsMissing efter en sparning och återöppning. Om du redan arbetar igenom hur gruppen lagras och expanderas från början täcks mekaniken för si-attributet för delade formler och dess expansion separat; för cacheläsning reduceras regeln till en rad — fråga varje cell, lita på inget du inte frågat efter
Cacheläsning, den enhetliga läsaren över motorerna och omberäkningsmotorn du kan välja att inte anropa levereras alla i standardkomponenten HotXLS Delphi Spreadsheet Component för Delphi och C++Builder, utan beroende på Excel eller någon OLE-automationsserver; produktsidan bär den fullständiga API-referensen för de arbetsboks- och läsaringångspunkter som visas här