O HotXLS, a biblioteca Excel nativa para Delphi e C++Builder, lê o valor que o Excel já armazenou ao lado de uma fórmula por meio de TryGetCachedFormulaValue e IXLSFormulaCacheReader. Nenhum dos pontos de entrada chama a calculadora, descompila tokens de fórmula, atualiza estado dirty ou escreve qualquer coisa de volta no modelo, então uma pasta de trabalho que você só lê permanece exatamente como a abriu
O cenário que motiva isso é sem graça e extremamente comum. Um job noturno abre algumas centenas de pastas de trabalho produzidas por outra pessoa, puxa uma coluna de totais de cada uma e empurra os números para um warehouse. Os totais já estão nos arquivos — o Excel os computou e salvou. Ainda assim, no momento em que o job pede o valor de uma célula de fórmula, uma biblioteca que só tem uma resposta para essa pergunta constrói um grafo de dependências e avalia a planilha inteira, e um job que deveria ser limitado por I/O vira um benchmark de cálculo
Por que ler uma célula de fórmula custa um recálculo completo?
Porque um getter de valor numa célula de fórmula é um pedido para produzir um valor, e o único modo universalmente correto de produzir um é avaliar a fórmula. Esse é o padrão certo para um aplicativo que edita pastas de trabalho, e o padrão errado para um pipeline que as extrai. Pior, a avaliação não é livre de efeitos colaterais: escreve resultados de volta nas células, vira flags dirty e pode resolver diferente do aplicativo produtor quando uma função não é suportada ou uma referência externa está quebrada. Um job que você descreveu à sua equipe de operações como somente leitura produz silenciosamente uma pasta de trabalho que não corresponde mais à que está no disco, e se qualquer coisa salvá-la depois, o arquivo em disco muda também
A leitura de valor em cache é a outra metade do contrato. Ela responde uma pergunta mais estreita — o que o aplicativo produtor armazenou aqui? — e se recusa a responder qualquer outra coisa. Quando você genuinamente quer números frescos, o HotXLS ainda te dá recálculo incremental dirigido por um grafo de dependências; o ponto é que extração e avaliação deveriam ser duas chamadas diferentes, não uma chamada com dois humores
Três fatos ortogonais sobre uma célula
Primeiro a conclusão: um valor de fórmula em cache carrega três fatos independentes, e colapsá-los num único Variant perde informação de que você precisa. O TXLSFormulaCacheInfo os mantém separados como State, Kind e Value. O TXLSFormulaCacheState registra procedência em cinco casos — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated e xlfcsInvalidated — enquanto o TXLSFormulaCacheValueKind classifica o payload como xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean ou xlfcvError. Essa separação é o que permite que presença seja reportada com honestidade: um blank em cache, uma string vazia em cache, um False em cache, um zero em cache e um erro em cache são todos valores reais, então presença jamais pode ser inferida de VarIsEmpty ou VarIsNull. O TryGetCachedFormulaValue retorna True só para xlfcsLoaded e xlfcsCalculated, e ainda preenche um estado diagnosticável quando retorna False
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row e Col são todos base um aqui
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;
Por que o valor em cache está ausente?
Existem exatamente quatro razões para o TryGetCachedFormulaValue devolver False, e o estado te diz qual se aplica. xlfcsNotFormula significa que a célula guarda um literal ou nada, e coordenadas fora do range colapsam na mesma resposta. xlfcsMissing significa que a célula realmente é uma fórmula mas o produtor não armazenou payload de valor para ela — desfecho comum quando um gerador escreve fórmulas e deixa o Excel preencher resultados na primeira abertura. xlfcsInvalidated significa que o texto da fórmula foi substituído depois do load, então o valor que costumava estar lá descreve uma expressão que não existe mais. xlfcsCalculated, por contraste, é um caso de sucesso: marca um valor que seu próprio código ou o avaliador do HotXLS produziu nesta sessão, em oposição a xlfcsLoaded, que veio do arquivo
Honestidade sobre um cache ausente importa mais que cobri-lo com tapa-buraco. O HotXLS se recusa a inventar um valor, e no save é igualmente rigoroso — só xlfcsLoaded e xlfcsCalculated emitem um valor em cache, enquanto xlfcsMissing e xlfcsInvalidated escrevem só a fórmula em vez de congelar um número velho no arquivo. Isso te deixa três respostas sãs num pipeline: pular a linha e registrar a lacuna, recalcular deliberadamente aquela pasta de trabalho e aceitar o custo, ou avaliar e reconciliar. Se o número avaliado discorda do que o aplicativo produtor teria escrito, o tracer de avaliação de fórmulas é a ferramenta para descobrir onde as duas computações divergem, em vez de chutar pelo resultado
Um leitor pelos motores clássico, OOXML e ODF
Um pipeline não deveria se importar se o arquivo que acabou de abrir era BIFF, OOXML ou ODF. O IXLSFormulaCacheReader é o único ponto de entrada somente leitura para os três: tanto TXLSWorkbook.CreateFormulaCacheReader quanto TXLSXWorkbook.CreateFormulaCacheReader retornam um adaptador leve sobre a busca esparsa de células que cada motor já usa, com coordenadas de planilha, linha e coluna base um idênticas. As classes de pasta de trabalho deliberadamente não implementam a interface elas mesmas — uma referência de interface à pasta de trabalho mudaria sua semântica de posse e deixaria chamadores escapar do lease de tempo de vida. Em vez disso, destruir a pasta de trabalho limpa o ponteiro bruto dentro desse lease, e qualquer leitor ainda segurado pelo seu código levanta EXLSFormulaCacheReaderInvalidated na próxima consulta em vez de desreferenciar memória liberada. É checagem de tempo de vida fail-fast, não uma garantia de concorrência
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);
// Nenhuma calculadora rodou, nenhuma flag dirty se moveu, Book não muda
end;
Onde os bytes em cache realmente vivem
Para arquivos .xls clássicos o cache é o campo FormulaValue do record Formula, oito bytes descritos pelo [MS-XLS] §2.5.133. Quando a palavra alta é igual a $FFFF o payload não é um double IEEE 754 mas um variant tagado, e o layout é fácil de errar sutilmente: o tipo do variant fica em val[0] e o payload booleano ou BErr fica em val[2], com val[1] indefinido. O HotXLS antes lia o payload de val[1], que é o tipo de off-by-one que só aparece nos arquivos específicos que colocam em cache um booleano ou um erro em vez de um número. O leitor e o writer de fórmulas compartilhadas agora concordam nos mesmos offsets, então um TRUE em cache sobrevive a um load e save intacto em vez de decair em ruído
A fidelidade de tipo nos formatos de pacote é um problema separado com sua própria armadilha. No OOXML o valor em cache pende do elemento c como <v>, com o atributo t nomeando o tipo por ECMA-376 Part 1 §18.3.1.4. O HotXLS lê t="e" direto num Variant varError e o mapeia de volta para o texto de erro padrão no save, então erros nunca se mascaram de inteiros comuns — mas a RTL do Delphi não te ajuda aqui, porque VarAsType(Integer, varError) levanta uma exceção de conversão. A construção que funciona define TVarData.VType e TVarData.VError diretamente. Datas seguem a mesma disciplina na direção oposta: t="d" e o tipo de valor date do ODF são declarações explícitas de tipo e se tornam varDate, enquanto um cache numérico BIFF não carrega flag de data alguma e portanto permanece um Double. O HotXLS nunca chuta uma data pelo formato numérico de célula, porque o formato numérico é apresentação e o cache é dado. O ODF adiciona um caso a mais que vale conhecer — office:value-type="void" expressa um cache que está presente mas não carrega valor, e como o ODF não tem tipo de valor de erro, texto com cara de erro é preservado como texto em vez de promovido a erro
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;
Fórmulas compartilhadas compartilham seus valores em cache?
Não, e assumir o contrário é como uma varredura acaba reportando o mesmo número para uma coluna inteira. Uma fórmula compartilhada do OOXML compartilha apenas a expressão da fórmula e a otimização de armazenamento; cada célula membro ainda é dona do próprio <v>. O HotXLS por isso nunca propaga o cache do membro raiz para um seguidor que chegou sem valor, e um seguidor que carregou como xlfcsMissing continua reportando xlfcsMissing depois de um save e reopen. Se você está trabalhando como o grupo é armazenado e expandido em primeiro lugar, a mecânica do atributo si de fórmula compartilhada e sua expansão é coberta separadamente; para leitura de cache, a regra se reduz a uma linha — pergunte a cada célula, não confie em nada que você não pediu
A leitura de valor em cache, o leitor unificado entre motores e o motor de recálculo que você pode escolher não invocar todos vêm no HotXLS Delphi Spreadsheet Component padrão para Delphi e C++Builder, sem dependência de Excel ou de qualquer servidor de automação OLE; a página do produto carrega a referência completa da API para os pontos de entrada de pasta de trabalho e leitor mostrados aqui