O HotXLS, a biblioteca Excel nativa para Delphi e C++Builder, foi concebido para conversões sem perdas (lossless round-trips) em XLSX: abra um livro de trabalho, altere uma célula, grave, 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 viabilizam esta operação — o armazenamento em cache literal de xl/theme/theme1.xml, a reserialização baseada em eventos de blocos <ext> desconhecidos e um xl/calcChain.xml novo e válido segundo as especificações em cada gravação de um livro de trabalho com fórmulas
O cenário que motiva estes três mecanismos é deprimentemente comum. Um serviço de faturação carrega um modelo que o cliente desenhou no Excel — tema de cores corporativo, minigráficos (sparklines) numa coluna de KPIs, uma regra de formatação condicional adicionada por uma versão mais recente do Excel — escreve o total de uma fatura na célula B3 e grava. O cliente abre o resultado e as cores da marca voltaram ao azul padrão do Office, os minigráficos desapareceram e o Excel propõe "reparar" o ficheiro. Nada no código alterou essas funcionalidades. A biblioteca alterou-as, simplesmente ao gravar
Por que razão os ficheiros Excel perdem a formatação após edições com bibliotecas?
Os ficheiros Excel perdem a formatação após edições efetuadas por bibliotecas porque a maioria das bibliotecas não edita o ficheiro — reconstrói-o. Um pacote .xlsx é um ZIP de componentes XML: xl/workbook.xml, um xl/worksheets/sheetN.xml por folha, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml, entre outros. Uma biblioteca típica analisa esses componentes num modelo de objetos ao abrir e regenera cada componente a partir desse modelo ao gravar. Qualquer funcionalidade que o modelo não represente — um tema que nunca tenha analisado, um bloco de extensão de um Excel mais recente — não tem onde residir na memória, pelo que o componente regenerado simplesmente o omite
A norma ECMA-376 antecipou metade deste problema. O SpreadsheetML define extLst (ECMA-376 Parte 1, a "Área de Armazenamento de Dados de Funcionalidades Futuras", §18.2.10 para o elemento ao nível do livro de trabalho) como um ponto de extensão designado: os geradores mais recentes colocam aí funcionalidades, cada uma encapsulada num elemento <ext> que contém um atributo uri que identifica a funcionalidade, e os consumidores mais antigos devem preservar aquilo que não compreendem. Os minigráficos, segmentações de dados (slicers) e novos tipos de formatação condicional são todos transmitidos desta forma. Uma biblioteca que ignore blocos <ext> desconhecidos não é apenas incompleta — viola o contrato de compatibilidade futura em torno do qual o formato foi concebido. A pergunta a fazer a qualquer biblioteca de folhas de cálculo que esteja a avaliar é direta: se eu alterar uma célula, o que mais se altera?
Como é que o HotXLS mantém um tema personalizado byte a byte?
O HotXLS preserva o tema de um livro de trabalho armazenando em cache os bytes originais de xl/theme/theme1.xml no momento de abertura e reescrevendo-os textualmente no momento de gravação. O componente do tema (norma ECMA-376 Parte 1, §14.2.7) é DrawingML, e não SpreadsheetML — esquemas de cores, esquemas de tipos de letra, esquemas de formatação — e um motor de folha de cálculo não necessita de o modelar detalhadamente. As versões anteriores do HotXLS regeneravam um tema Office fixo a cada gravação, o que provocava exatamente a falha referida acima sobre o retorno às cores padrão da marca; desde a versão v2.89.46, o tema do pacote aberto é armazenado no seu estado bruto e reemitido inalterado, e o tema Office integrado é gerado apenas para livros de trabalho criados do zero. Bytes brutos constituem a melhor garantia de fidelidade possível: sem análise, sem reserialização, sem possibilidade de desvios
A cópia literal prevalece deliberadamente sobre o acesso programático ao tema. O TXLSXWorkbook expõe a ThemeMajorFont e a ThemeMinorFont para que possa escolher tipos de letra de cabeçalho e corpo para novos livros de trabalho, mas quando um tema literal foi recolhido no momento de abertura, essas definições não têm qualquer efeito no ficheiro gravado — a integridade da conversão (round-trip) tem prioridade. Se necessitar realmente de alterar o tema de um livro de trabalho existente, isso indica que deve editar o modelo no próprio Excel, e não através de uma API focada em dados. O cenário quotidiano não necessita de qualquer 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 aos blocos extLst desconhecidos ao gravar?
O HotXLS captura cada bloco <ext> ao nível da folha de trabalho que não modela nativamente e reproduz-o no extLst da folha de trabalho gravada, garantindo que as funcionalidades escritas por versões mais recentes do Excel sobrevivam intactas à conversão. Desde a v2.131.0, os fragmentos capturados são visíveis através da propriedade apenas de leitura RawWorksheetExts, uma TStringList em cada folha de trabalho XLSX, o que torna a garantia verificável a partir do código de testes, em vez de ser 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 conhecer é que a captura consiste numa reserialização ao nível dos eventos, e não numa cópia de bytes brutos. O leitor XML de streaming do HotXLS não expõe desvios de origem, pelo que a subárvore desconhecida é reconstruída a partir de eventos Element, Text e EndElement à medida que passam. Esta abordagem esconde uma armadilha clássica: um elemento de autofechamento como <a/> aciona apenas um evento Element marcado como vazio e nunca um EndElement, pelo que qualquer contador de profundidade que decremente exclusivamente no EndElement nunca verá a subárvore fechar-se. Tratando este caso, o fragmento reconstruído é semanticamente equivalente ao original — a delimitação de atributos e as formas de autofechamento são normalizadas, pelo que não é idêntico em bytes, mas o Excel lê significado, e não 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, pelo que cada fragmento capturado é autónomo em termos de namespace, e essa mesma autonomia é o motivo pelo qual duplicar uma folha de trabalho dentro ou entre livros de trabalho pode transportar os blocos externos juntamente com uma atribuição simples de string-list
Gravar calcChain.xml para o Excel validar as suas fórmulas
O HotXLS grava o xl/calcChain.xml (o componente Cadeia de Cálculo, norma ECMA-376 Parte 1, §12.3.1) sempre que o livro de trabalho gravado contém fórmulas, e escolhe entre duas ordenações. Se o gráfico de dependências de fórmulas já tiver sido construído e estiver atualizado — chamou o Recalculate após a sua última edição — a cadeia é emitida em ordem topológica completa, as dependências antes dos dependentes, com quaisquer elementos de referência circular anexados no final. Caso contrário, as células sã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 pista que o Excel valida e reordena durante o carregamento, pelo que qualquer listagem completa é legal, e o HotXLS recusa-se deliberadamente a forçar a construção do gráfico dentro de SaveAs — a construção de arestas é quadrática em relação à contagem de células, um custo oculto inaceitável numa gravação 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 conversão sem perdas
A honestidade é mais importante aqui do que um visto de marketing, pelo que os limites merecem igual destaque. O HotXLS não copia todo o pacote byte a byte: os ficheiros XML das folhas de trabalho, estilos, strings partilhadas e componentes do livro de trabalho são regenerados a partir do modelo analisado, pelo que a saída é semanticamente fiel mas não binária-idêntica — os cabeçalhos locais do ZIP contêm carimbos de data/hora DOS atualizados. Os fragmentos <ext> capturados regressam normalizados, conforme descrito acima. As sobreposições de tipos de letra do tema programáticas são ignoradas quando existe um tema literal. E a rede de preservação tem uma malha definida: funcionalidades que o HotXLS modela nativamente (minigráficos, por exemplo, são analisados e reescritos em vez de copiados cegamente) mais conteúdo extLst externo mais os componentes mantidos literalmente em cache. Um componente que não seja modelado nem se encontre num ponto de extensão — como o componente personalizado de um suplemento (add-in) exótico — fica fora dos três mecanismos que este artigo aborda, pelo que deve testar os seus modelos reais em vez de assumir
O trabalho de preservação adjacente completa o cenário. Os projetos VBA e as referências de livros de trabalho externos mantêm-se na gravação sob a mesma filosofia de preservar o que não é modelado, descrita no artigo complementar sobre a preservação de VBA e ligações externas, e as propriedades do documento em docProps possuem a sua própria API de leitura e escrita, em vez de serem eliminadas silenciosamente. Ao avaliar qualquer biblioteca de folhas de cálculo, execute o teste de uma única célula: abra um livro de trabalho de produção rico em funcionalidades, altere um único valor, grave e compare (diff) os componentes descompactados com os originais. O que mudou além da folha em que tocou indica-lhe mais sobre a biblioteca do que qualquer matriz de funcionalidades
Os mecanismos de conversão descritos neste artigo — retenção literal de temas desde a v2.89.46, captura de extLst externos e emissão de calcChain.xml desde a v2.131.0 — são fornecidos no atual HotXLS Delphi Excel Component, cuja página de produto documenta o conjunto completo de funcionalidades de leitura e escrita XLSX para Delphi e C++Builder