HotXLS, la libreria Excel nativa per Delphi e C++Builder, legge il valore che Excel ha già memorizzato accanto a una formula tramite TryGetCachedFormulaValue e IXLSFormulaCacheReader. Nessuno dei due punti di ingresso chiama il calcolatore, decompila token di formula, aggiorna lo stato dirty o scrive qualcosa nel modello, quindi una cartella di lavoro che fate solo leggere resta esattamente come l'avete aperta
Lo scenario che motiva tutto questo è banale e estremamente comune. Un job notturno apre qualche centinaio di cartelle di lavoro prodotte da altri, estrae una colonna di totali da ciascuna e spinge i numeri in un warehouse. I totali sono già nei file — Excel li ha calcolati e salvati. Eppure nel momento in cui il job chiede il valore a una cella formula, una libreria che ha una sola risposta per quella domanda costruisce un grafo di dipendenze e valuta l'intero foglio, e un job che dovrebbe essere limitato dall'I/O si trasforma in un benchmark di calcolo
Perché leggere una cella formula costa un ricalcolo completo?
Perché un getter di valore su una cella formula è una richiesta di produrre un valore, e l'unico modo universalmente corretto di produrlo è valutare la formula. È il default giusto per un'applicazione che modifica cartelle di lavoro, e il default sbagliato per una pipeline che le estrae. Peggio, la valutazione non è esente da effetti collaterali: riscrive i risultati nelle celle, commuta i flag dirty e può risolvere diversamente dall'applicazione produttrice quando una funzione non è supportata o un riferimento esterno è rotto. Un job che avete descritto al vostro team operativo come read-only produce silenziosamente una cartella di lavoro che non corrisponde più a quella su disco, e se qualcosa la salva in seguito, cambia anche il file su disco
La lettura dei valori cache è l'altra metà del contratto. Risponde a una domanda più stretta — cosa ha memorizzato qui l'applicazione produttrice? — e si rifiuta di rispondere a qualsiasi altra cosa. Quando volete davvero numeri freschi, HotXLS vi dà comunque il ricalcolo incrementale guidato da un grafo di dipendenze; il punto è che estrazione e valutazione dovrebbero essere due chiamate diverse, non una sola chiamata con due umori
Tre fatti ortogonali su una cella
Prima la conclusione: un valore cache di formula porta tre fatti indipendenti, e collassarli in un unico Variant perde informazioni di cui avete bisogno. TXLSFormulaCacheInfo li tiene separati come State, Kind e Value. TXLSFormulaCacheState registra la provenienza su cinque casi — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated e xlfcsInvalidated — mentre TXLSFormulaCacheValueKind classifica il payload come xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean o xlfcvError. Questa separazione è ciò che permette di riportare la presenza onestamente: un blank in cache, una stringa vuota in cache, un False in cache, uno zero in cache e un errore in cache sono tutti valori reali, quindi la presenza non può mai essere inferita da VarIsEmpty o VarIsNull. TryGetCachedFormulaValue restituisce True solo per xlfcsLoaded e xlfcsCalculated, e riempie comunque uno stato diagnostabile quando restituisce False
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row e Col sono tutti a base uno qui
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;
Perché il valore cache manca?
Ci sono esattamente quattro motivi per cui TryGetCachedFormulaValue restituisce False, e lo stato dice quale si applica. xlfcsNotFormula significa che la cella contiene un letterale o niente affatto, e coordinate fuori intervallo collassano nella stessa risposta. xlfcsMissing significa che la cella è davvero una formula ma il produttore non ha memorizzato alcun payload di valore per essa — esito comune quando un generatore scrive formule e lascia che Excel riempia i risultati alla prima apertura. xlfcsInvalidated significa che il testo della formula è stato sostituito dopo il caricamento, quindi il valore che c'era descrive un'espressione che non esiste più. xlfcsCalculated, al contrario, è un caso di successo: contrassegna un valore prodotto dal vostro codice o dall'evaluator di HotXLS durante questa sessione, in opposizione a xlfcsLoaded, che proveniva dal file
L'onestà su una cache mancante conta più del mascherarla. HotXLS si rifiuta di inventare un valore, e al salvataggio è altrettanto rigoroso — solo xlfcsLoaded e xlfcsCalculated emettono un valore cache, mentre xlfcsMissing e xlfcsInvalidated scrivono la formula da sola invece di congelare un numero stantio nel file. Questo lascia tre risposte sane in una pipeline: saltare la riga e registrare il gap, ricalcolare deliberatamente quella cartella di lavoro accettandone il costo, oppure valutare e riconciliare. Se il numero valutato non concorda con ciò che l'applicazione produttrice avrebbe scritto, il tracer di valutazione delle formule è lo strumento per scoprire dove le due computazioni divergono, invece di indovinare dal risultato
Un solo reader per i motori classic, OOXML e ODF
Una pipeline non dovrebbe preoccuparsi se il file appena aperto era BIFF, OOXML o ODF. IXLSFormulaCacheReader è l'unico punto di ingresso read-only per tutti e tre: sia TXLSWorkbook.CreateFormulaCacheReader sia TXLSXWorkbook.CreateFormulaCacheReader restituiscono un adattatore leggero sopra la lookup sparsa di celle che ciascun motore già usa, con coordinate identiche a base uno per foglio, riga e colonna. Le classi workbook deliberatamente non implementano esse stesse l'interfaccia — un riferimento a interfaccia verso il workbook cambierebbe le sue semantiche di ownership e lascerebbe i chiamanti scivolare oltre il lease di lifetime. Invece, distruggere il workbook azzera il puntatore grezzo dentro quel lease, e qualsiasi reader ancora trattenuto dal vostro codice solleva EXLSFormulaCacheReaderInvalidated alla query successiva invece di dereferenziare memoria liberata. È verifica fail-fast del lifetime, non una garanzia di concorrenza
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);
// Nessun calcolatore eseguito, nessun flag dirty mosso, Book invariato
end;
Dove vivono davvero i byte in cache
Per i file classic .xls la cache è il campo FormulaValue del record Formula, otto byte descritti da [MS-XLS] §2.5.133. Quando la word alta vale $FFFF il payload non è un double IEEE 754 ma una variant con tag, e il layout è facile da sbagliare in modo sottile: il tipo della variant siede in val[0] e il payload booleano o BErr siede in val[2], con val[1] non definito. HotXLS in precedenza leggeva il payload da val[1], che è il tipo di off-by-one che emerge solo sui file specifici che mettono in cache un booleano o un errore anziché un numero. Il reader e il writer delle formule condivise ora concordano sugli stessi offset, così un TRUE in cache sopravvive intatto a un load e save invece di degradare in rumore
La fedeltà dei tipi nei formati package è un problema separato con la sua trappola. In OOXML il valore cache pende dall'elemento c come <v>, con l'attributo t che nomina il tipo secondo ECMA-376 Part 1 §18.3.1.4. HotXLS legge t="e" direttamente in un Variant varError e lo rimappa al testo di errore standard al salvataggio, così gli errori non si mascherano mai da interi ordinari — ma la RTL Delphi non vi aiuta qui, perché VarAsType(Integer, varError) solleva un'eccezione di conversione. La costruzione funzionante imposta direttamente TVarData.VType e TVarData.VError. Le date seguono la stessa disciplina nella direzione opposta: t="d" e il tipo valore data di ODF sono dichiarazioni di tipo esplicite e diventano varDate, mentre una cache numerica BIFF non porta alcun flag data e quindi resta un Double. HotXLS non indovina mai una data dal formato numerico di una cella, perché il formato numerico è presentazione e la cache è dati. ODF aggiunge un caso in più che vale la pena conoscere — office:value-type="void" esprime una cache presente ma priva di valore, e poiché ODF non ha un tipo valore errore, il testo dall'aspetto di errore viene preservato come testo anziché promosso a errore
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;
Le formule condivise condividono i propri valori cache?
No, e presupporre il contrario è il modo in cui una scansione finisce per riportare lo stesso numero per un'intera colonna. Una formula condivisa OOXML condivide solo l'espressione della formula e l'ottimizzazione di memorizzazione; ogni cella membro possiede ancora il proprio <v>. HotXLS quindi non propaga mai la cache del membro radice a un follower arrivato senza valore, e un follower caricato come xlfcsMissing continua a riportare xlfcsMissing dopo un salvataggio e riapertura. Se state studiando come il gruppo viene memorizzato ed espanso in partenza, la meccanica dell'attributo si della formula condivisa e la sua espansione è trattata separatamente; per la lettura della cache, la regola si riduce a una riga — chiedete a ogni cella, non fidatevi di nulla che non abbiate chiesto
La lettura dei valori cache, il reader unificato cross-engine e il motore di ricalcolo che potete scegliere di non invocare arrivano tutti nel componente foglio di calcolo HotXLS per Delphi standard per Delphi e C++Builder, senza dipendenza da Excel né da alcun server di automazione OLE; la pagina del prodotto porta il riferimento API completo per i punti di ingresso workbook e reader mostrati qui