HotXLS, de native Excel-bibliotheek voor Delphi en C++Builder, leest de waarde die Excel al naast een formule heeft opgeslagen via TryGetCachedFormulaValue en IXLSFormulaCacheReader. Geen van beide instappunten roept de calculator aan, decompileert formuletokens, werkt dirty-status bij of schrijft iets terug in het model, dus een werkmap die u alleen leest blijft precies zoals u haar opende
Het scenario dat dit vraagt is saai en uitermate gewoon. Een nachtelijke taak opent enkele honderden werkmappen die iemand anders heeft gemaakt, trekt uit elke map één kolom totalen en duwt de cijfers een datawarehouse in. De totalen staan al in de bestanden — Excel heeft ze berekend en opgeslagen. Toch bouwt, zodra de taak een formulecel om haar waarde vraagt, een bibliotheek die daarop slechts één antwoord heeft een afhankelijkheidsgrafiek en evalueert het hele werkblad, en een taak die I/O-gebonden zou moeten zijn verandert in een rekenbenchmark
Waarom kost het lezen van een formulecel een volledige herberekening?
Want een waarde-getter op een formulecel is een verzoek om een waarde te produceren, en de enig algemeen correcte manier om er een te produceren is de formule evalueren. Dat is de juiste standaard voor een applicatie die werkmappen bewerkt, en de verkeerde voor een pipeline die ze extraheert. Erger nog, evaluatie is niet vrij van neveneffecten: ze schrijft resultaten terug in cellen, zet dirty-vlaggen om, en kan anders uitpakken dan bij de producerende applicatie wanneer een functie niet wordt ondersteund of een externe verwijzing kapot is. Een taak die u aan uw operationsteam als read-only hebt beschreven produceert geruisloos een werkmap die niet meer matcht met die op schijf, en als er later iets in opslaat verandert het bestand op schijf ook
Het lezen van cachewaarden is de andere helft van het contract. Ze beantwoordt een smallere vraag — wat heeft de producerende applicatie hier opgeslagen? — en weigert iets anders te antwoorden. Als u echt verse cijfers wilt, geeft HotXLS u nog steeds incrementele herberekening aangedreven door een afhankelijkheidsgrafiek; het punt is dat extractie en evaluatie twee verschillende aanroepen moeten zijn, niet één aanroep met twee gezichten
Drie orthogonale feiten over één cel
Eerst de conclusie: een gecachete formulewaarde draagt drie onafhankelijke feiten, en ze samenvouwen tot één Variant verliest informatie die u nodig hebt. TXLSFormulaCacheInfo houdt ze uit elkaar als State, Kind en Value. TXLSFormulaCacheState registreert de herkomst in vijf gevallen — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated en xlfcsInvalidated — terwijl TXLSFormulaCacheValueKind de payload classificeert als xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean of xlfcvError. Die scheiding maakt het mogelijk om aanwezigheid eerlijk te melden: een gecachete lege cel, een gecachete lege string, een gecachete False, een gecachete nul en een gecachete fout zijn allemaal echte waarden, dus aanwezigheid kan nooit worden afgeleid uit VarIsEmpty of VarIsNull. TryGetCachedFormulaValue geeft True alleen voor xlfcsLoaded en xlfcsCalculated, en vult bij False nog steeds een diagnoseerbare status in
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row en Col zijn hier allemaal 1-based
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;
Waarom ontbreekt de cachewaarde?
Er zijn precies vier redenen waarom TryGetCachedFormulaValue False teruggeeft, en de status vertelt u welke van toepassing is. xlfcsNotFormula betekent dat de cel een literal bevat of helemaal niets, en coördinaten buiten bereik vallen samen met hetzelfde antwoord. xlfcsMissing betekent dat de cel echt een formule is maar de producer geen waarde-payload voor haar heeft opgeslagen — een gangbare uitkomst wanneer een generator formules schrijft en Excel de resultaten bij de eerste open invult. xlfcsInvalidated betekent dat de formulertekst na het laden is vervangen, dus de waarde die er stond beschrijft een expressie die niet meer bestaat. xlfcsCalculated is daarentegen een succesgeval: die markeert een waarde die uw eigen code of de HotXLS-evaluator tijdens deze sessie produceerde, in tegenstelling tot xlfcsLoaded, die uit het bestand kwam
Eerlijkheid over een ontbrekende cache weegt zwaarder dan haar te maskeren. HotXLS weigert een waarde te verzinnen, en bij het opslaan is ze even streng — alleen xlfcsLoaded en xlfcsCalculated emitteren een cachewaarde, terwijl xlfcsMissing en xlfcsInvalidated alleen de formule weg schrijven in plaats van een verouderd getal in het bestand te bevriezen. Dat laat u drie verstandige reacties in een pipeline: de rij overslaan en het gat registreren, die ene werkmap bewust herberekenen en de kosten accepteren, of evalueren en vergelijken. Als het geëvalueerde getal niet klopt met wat de producerende applicatie geschreven zou hebben, is de formule-evaluatietracer het gereedschap om te vinden waar de twee berekeningen uiteenlopen, in plaats van te gissen uit het resultaat
Eén reader voor de klassieke, OOXML- en ODF-engines
Een pipeline zou niet mogen uitmaken of het bestand dat ze zojuist opende BIFF, OOXML of ODF was. IXLSFormulaCacheReader is het enige read-only instappunt voor alle drie: zowel TXLSWorkbook.CreateFormulaCacheReader als TXLSXWorkbook.CreateFormulaCacheReader geeft een lichtgewicht adapter terug over de sparse cel-opzoeking die elke engine al gebruikt, met identieke 1-based werkblad-, rij- en kolomcoördinaten. De werkmapklassen implementeren de interface bewust niet zelf — een interfacereferentie naar de werkmap zou haar eigendomssemantiek veranderen en aanroepers de lifetime lease laten omzeilen. In plaats daarvan wist het vernietigen van de werkmap de raw pointer binnen die lease, en elke reader die uw code nog vasthoudt geeft bij de volgende query EXLSFormulaCacheReaderInvalidated in plaats van vrijgegeven geheugen te derefereren. Het is fail-fast lifetimecontrole, geen garanties over concurrency
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);
// Geen calculator gedraaid, geen dirty-vlag verplaatst, Book is ongewijzigd
end;
Waar de gecachte bytes feitelijk wonen
Bij klassieke .xls-bestanden is de cache het veld FormulaValue van het Formula-record, acht bytes beschreven in [MS-XLS] §2.5.133. Wanneer het hoge woord gelijk is aan $FFFF is de payload geen IEEE 754 double maar een tagged variant, en de layout is gemakkelijk subtiel verkeerd te lezen: het varianttype zit in val[0] en de boolean- of BErr-payload zit in val[2], met val[1] ongedefinieerd. HotXLS las de payload voorheen uit val[1], precies het soort off-by-one dat alleen opduikt op de specifieke bestanden die een boolean of een fout cachen in plaats van een getal. De reader en de shared-formula-writer zijn het nu over dezelfde offsets eens, dus een gecachete TRUE overleeft een load en save ongeschonden in plaats van te vervallen tot ruis
Typegetrouwheid in de pakketformaten is een apart probleem met een eigen valkuil. In OOXML hangt de cachewaarde aan het element c als <v>, met het attribuut t dat het type benoemt volgens ECMA-376 Part 1 §18.3.1.4. HotXLS leest t="e" direct in een varError-Variant en mapt haar bij het opslaan terug naar de standaardfouttekst, dus fouten geven zich nooit uit voor gewone integers — maar de Delphi RTL helpt u hier niet, want VarAsType(Integer, varError) gooit een conversie-exception. De werkende constructie zet TVarData.VType en TVarData.VError direct. Data volgen dezelfde discipline in omgekeerde richting: t="d" en het ODF-datumwaardetype zijn expliciete typedeclaraties en worden varDate, terwijl een BIFF numerieke cache helemaal geen datavlag draagt en dus een Double blijft. HotXLS gist nooit een datum uit een celgetalnotatie, want de getalnotatie is presentatie en de cache is data. ODF voegt nog één geval toe dat de moeite van kennen waard is — office:value-type="void" drukt een cache uit die aanwezig is maar geen waarde draagt, en omdat ODF geen foutwaardetype kent, wordt foutachtige tekst bewaard als tekst in plaats van gepromoveerd tot een fout
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;
Delen gedeelde formules hun cachewaarden?
Nee, en anders aannemen is precies hoe een sweep eropuit komt met hetzelfde getal voor een hele kolom. Een OOXML shared formula deelt alleen de formula-expressie en de opslagoptimalisatie; elke lidcel bezit nog steeds haar eigen <v>. HotXLS propageert daarom de cachewaarde van het root-lid nooit naar een volger die zonder waarde aankwam, en een volger die als xlfcsMissing laadt meldt na een save en heropen nog steeds xlfcsMissing. Als u eerst wilde uitzoeken hoe de groep wordt opgeslagen en uitgebreid, wordt de werking van het si-attribuut van de shared formula en zijn expansie apart behandeld; voor het lezen van caches komt de regel neer op één regel — vraag elke cel, vertrouw niets dat u niet vroeg
Het lezen van cachewaarden, de verenigde cross-engine reader en de herberekeningsengine die u ervoor kunt kiezen niet aan te roepen, zitten allemaal in het standaard HotXLS Delphi Spreadsheet Component voor Delphi en C++Builder, zonder afhankelijkheid van Excel of van een OLE-automatiseringsserver; de productpagina bevat de volledige API-referentie voor de werkmap- en reader-instappunten die hier worden getoond