Artigo Técnico

Viagem de Ida e Volta XLSX Sem Perdas no Delphi: Theme, extLst, calcChain

O HotXLS, a biblioteca nativa do Excel para Delphi e C++Builder, é construído para viagens de ida e volta (round-trips) XLSX sem perdas: abra uma pasta de trabalho, altere uma célula, salve, e o tema personalizado do cliente, os blocos de extensão extLst desconhecidos e a cadeia de cálculo (calculation chain) sobreviverão. Três mecanismos fazem isso funcionar: o armazenamento em cache literal de xl/theme/theme1.xml, a reserialização baseada em eventos de blocos <ext> desconhecidos e um arquivo xl/calcChain.xml novo e válido perante a especificação a cada salvamento de uma pasta de trabalho com fórmulas

O cenário que motiva todos os três é deprimentemente comum. Um serviço de cobrança carrega um modelo que o cliente projetou no Excel — tema de cores corporativas, mini gráficos (sparklines) em uma coluna de KPI, uma regra de formatação condicional adicionada por uma versão mais recente do Excel —, grava o total de uma fatura na célula B3 e salva. O cliente abre o resultado e as cores da marca voltaram para o azul padrão do Office, os mini gráficos sumiram e o Excel oferece para "reparar" o arquivo. Nada no código alterou esses recursos. A biblioteca alterou, simplesmente ao salvar

Por que os arquivos do Excel perdem a formatação após edições de bibliotecas?

Os arquivos do Excel perdem a formatação após as edições das bibliotecas porque a maioria delas não edita o arquivo — elas o reconstroem. Um pacote .xlsx é um arquivo ZIP de partes XML: xl/workbook.xml, um xl/worksheets/sheetN.xml por planilha, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml, e mais. Uma biblioteca típica analisa essas partes em um modelo de objeto ao abrir e gera novamente cada parte a partir desse modelo ao salvar. Qualquer recurso que o modelo não represente — um tema que nunca foi analisado, um bloco de extensão de uma versão mais nova do Excel — não tem onde residir na memória, de modo que a parte regenerada o omite silenciosamente

Por que os arquivos do Excel perdem a formatação após edições de bibliotecas?

A norma ECMA-376 previu metade desse problema. O SpreadsheetML define o extLst (ECMA-376 Parte 1, a "Área de Armazenamento de Dados de Recursos Futuros", §18.2.10 para o elemento no nível da pasta de trabalho) como um ponto de extensão designado: geradores mais recentes colocam recursos ali, cada um envolvido em um elemento <ext> que carrega um atributo uri que identifica o recurso, e espera-se que os consumidores mais antigos preservem o que não compreendem. Mini gráficos (sparklines), segmentadores (slicers) e novos tipos de formatação condicional trafegam dessa forma. Uma biblioteca que descarta blocos <ext> desconhecidos não é apenas falha em termos de perdas — ela viola o contrato de compatibilidade futura em torno do qual o formato foi projetado. A pergunta a ser feita a qualquer biblioteca de planilhas que você esteja avaliando é direta: se eu alterar uma célula, o que mais muda?

Como o HotXLS mantém um tema personalizado byte a byte?

O HotXLS preserva o tema de uma pasta de trabalho armazenando em cache os bytes originais de xl/theme/theme1.xml no momento da abertura e gravando-os de volta integralmente (verbatim) no momento de salvar. A parte do tema (ECMA-376 Parte 1, §14.2.7) é DrawingML, não SpreadsheetML — esquemas de cores, esquemas de fontes, esquemas de formatação — e um mecanismo de planilha não tem razão para modelá-lo profundamente. Versões anteriores do HotXLS geravam um tema fixo do Office a cada salvamento, o que corresponde exatamente à falha de "cores da marca restauradas" mencionada acima; desde a v2.89.46, o tema do pacote aberto é armazenado em estado bruto e reemitido intacto, e o tema integrado do Office é gerado apenas para pastas de trabalho criadas do zero. Bytes brutos são a maior garantia possível de fidelidade: sem análise, sem nova serialização, sem chance de desvio

A cópia literal (verbatim) prevalece intencionalmente sobre o acesso programático ao tema. O TXLSXWorkbook expõe as propriedades ThemeMajorFont e ThemeMinorFont para que você possa escolher tipos de letra para cabeçalhos e corpo em novas pastas de trabalho, mas quando um tema literal foi capturado na abertura, essas definições não têm efeito no arquivo salvo — a viagem de ida e volta (round-trip) tem prioridade. Se você realmente precisar alterar o tema de uma pasta de trabalho existente, isso é um sinal para editar o modelo no próprio Excel, e não por meio de uma API orientada a dados. O caso do dia a dia não precisa de nenhuma API:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('branded-invoice.xlsx');
    Book.Sheets[0].Cells[3, 2].Value := 42750.00;  // the one edit
    Book.SaveAs('branded-invoice-out.xlsx');
    // theme1.xml in the output is byte-identical to the input
  finally
    Book.Free;
  end;
end;

O que acontece com blocos extLst desconhecidos ao salvar?

O HotXLS captura cada bloco <ext> no nível da planilha que não modela nativamente e o reproduz no extLst da planilha salva, de modo que os recursos gravados por versões mais novas do Excel sobrevivam intactos à viagem de ida e volta. Desde a v2.131.0, os fragmentos capturados são visíveis através da propriedade somente leitura RawWorksheetExts, uma TStringList em cada planilha XLSX, o que torna a garantia auditável a partir do código de teste, em vez de um ato de fé:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  i: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('from-newer-excel.xlsx');
    Sheet := Book.Sheets[0];
    WriteLn(Format('%d foreign ext block(s) captured',
      [Sheet.RawWorksheetExts.Count]));
    for i := 0 to Sheet.RawWorksheetExts.Count - 1 do
      WriteLn(Copy(Sheet.RawWorksheetExts[i], 1, 100)); // peek at each uri
  finally
    Book.Free;
  end;
end;

O detalhe de implementação que vale a pena saber é que a captura é uma nova serialização a nível de evento, e não uma cópia de bytes brutos. O leitor XML em fluxo do HotXLS não expõe deslocamentos (offsets) de origem, portanto a subárvore desconhecida é reconstruída a partir dos eventos Element, Text e EndElement à medida que passam pelo fluxo. Essa abordagem esconde uma armadilha clássica: um elemento de fechamento automático como <a/> dispara apenas um evento Element sinalizado como vazio e nunca um EndElement, de modo que qualquer contador de profundidade que decremente unicamente no EndElement nunca verá a subárvore fechar. Se você tratar isso, o fragmento reconstruído será semanticamente equivalente ao original — a citação de atributos e formas de fechamento automático são normalizadas, portanto não é idêntico a nível de bytes, mas o Excel lê o significado, não os bytes. Duas propriedades da própria saída do Excel tornam a reprodução segura: o Excel declara os atributos xmlns necessários no elemento <ext> ou dentro dele, de modo que cada fragmento capturado seja independente em termos de namespace, e essa mesma independência é o motivo pelo qual duplicar uma planilha dentro de uma pasta de trabalho ou entre pastas de trabalho possa carregar os blocos externos juntamente com uma atribuição simples de lista de strings

Gravando calcChain.xml para que o Excel confie em suas fórmulas

O HotXLS grava o xl/calcChain.xml (a parte da Cadeia de Cálculo, ECMA-376 Parte 1, §12.3.1) sempre que a pasta de trabalho salva contiver fórmulas, escolhendo entre duas ordenações. Se o gráfico de dependência de fórmulas já tiver sido construído e for atual — ou seja, você chamou o Recalculate após sua última edição —, a cadeia será emitida em ordem topológica completa, com dependências antes de dependentes, com quaisquer membros de referência circular anexados ao fim. Caso contrário, as células serão listadas na ordem do documento. Ambas estão corretas: as notas de implementação da Microsoft para o formato, [MS-XLSX], tratam a cadeia de cálculo como uma dica que o Excel verifica e reordena durante o carregamento, portanto qualquer listagem completa é legal, e o HotXLS deliberadamente se recusa a forçar a construção do gráfico dentro do SaveAs — a construção de arestas é quadrática em relação à contagem de células, um custo oculto inaceitável em um salvamento de um milhão de células

Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Saved now, calcChain.xml lists formula cells in document order.
// After Recalculate the dependency graph exists, so the same save
// emits a full topological order instead:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');

Onde termina a fidelidade da viagem de ida e volta sem perdas

A honestidade importa mais do que uma caixa de seleção de marketing aqui, portanto os limites merecem destaque equivalente. O HotXLS não copia todo o pacote byte a byte: os XMLs das planilhas, estilos, strings compartilhadas e partes da pasta de trabalho são regenerados a partir do modelo analisado, de modo que a saída seja semanticamente fiel, mas não idêntica a nível binário — os cabeçalhos locais do ZIP por si só carregam registros de data e hora atualizados do DOS. Fragmentos <ext> capturados retornam normalizados, conforme descrito acima. Substituições programáticas de fontes do tema são ignoradas quando um tema literal está presente. E a rede de preservação tem uma malha definida: recursos que o HotXLS modela nativamente (mini gráficos, por exemplo, são analisados e regravados em vez de copiados cegamente) mais o conteúdo extLst externo mais as partes armazenadas em cache integralmente. Uma parte que não é modelada e nem está dentro de um ponto de extensão — digamos, a parte personalizada de um suplemento exótico — fica fora dos três mecanismos que este artigo aborda, portanto teste seus modelos reais em vez de assumir

O trabalho de preservação adjacente completa o quadro. Projetos VBA e referências externas de pastas de trabalho passam pelo salvamento com a mesma filosofia de manter o que você não modela, abordada no artigo complementar sobre preservação de VBA e links externos, e as propriedades do documento em docProps têm sua própria API de leitura e gravação em vez de serem descartadas silenciosamente. Ao avaliar qualquer biblioteca de planilhas, execute o teste de uma única célula: abra uma pasta de trabalho de produção cheia de recursos, altere um único valor, salve e compare as partes descompactadas com o original. O que mudou além da planilha que você alterou diz mais sobre a biblioteca do que qualquer matriz de recursos

Os mecanismos de viagem de ida e volta descritos aqui — verbetim theme retention since v2.89.46, captura de extLst externa e emissão de calcChain.xml desde a v2.131.0 — são fornecidos no HotXLS Delphi Excel Component atual, cuja página de produto documenta o conjunto completo de recursos de leitura e gravação XLSX para Delphi e C++Builder