Artigo Técnico

Corrigir uma Folha de Cálculo num XLSX Grande a partir do Delphi

O HotXLS consegue reescrever uma folha de cálculo dentro de um pacote XLSX existente sem analisar nem recomprimir o resto do ficheiro. TXLSDirectWriter.BeginPatch abre um pacote de origem, copia cada entrada exceto a folha alvo com os seus bytes comprimidos inalterados, e permite reescrever essa única folha através das chamadas comuns AddSheet, AddRow e Write*. Gráficos, caches de tabelas dinâmicas, temas, estilos e a tabela de strings partilhadas nunca chegam a ser descomprimidos

O fluxo de trabalho que isto resolve surge em relatórios e atualizações de dados. Uma pasta de trabalho chega de uma equipa de negócio a transportar tabelas dinâmicas, segmentadores, formatos condicionais e uma década de formatação acumulada. Todas as noites, uma folha de dados tem de ser substituída por números atualizados. Carregar e voltar a gravar a pasta de trabalho inteira custa minutos por ficheiro e, mais importante, arrisca a fidelidade em funcionalidades que o motor de carregamento tem de reconstruir. A correção pontual evita ambos os problemas, não tocando naquilo que não precisa de tocar

Por que razão copiar bytes comprimidos é a parte interessante?

Uma entrada zip copiada ao nível comprimido custa uma cópia de fluxo. A mesma entrada passada pelo caminho de escrita normal custa uma descompressão à entrada e uma compressão à saída, e a compressão é a metade cara. Numa pasta de trabalho com uma cache de tabela dinâmica grande e umas dezenas de imagens incorporadas, essa diferença é a diferença entre uma correção que termina no tempo que leva a escrever a folha nova e uma que passa a maior parte do tempo a recomprimir bytes que nunca chegou a examinar

O HotXLS usa CopyCompressedFrom para isto, que escreve os bytes comprimidos da entrada de origem diretamente no arquivo de destino. Quando uma entrada não pode ser copiada dessa forma, por usar um método de compressão diferente ou uma encriptação fraca, o escritor recua para uma cópia de fluxo descomprimida em vez de falhar. As entradas de marcador de diretório são ignoradas, já que o escritor produz as suas próprias

Substituir no local, ou escrever para um ficheiro novo

Duas sobrecargas cobrem as duas formas que esta tarefa assume. A forma no local prepara o resultado num ficheiro temporário junto ao original, fecha o identificador da origem, e depois apaga e renomeia, pelo que uma falha a meio da escrita deixa o original intacto. A forma de destino explícito deixa a origem intocada e pode substituir uma folha ou acrescentar uma nova:

var
  W: TXLSDirectWriter;
begin
  W := TXLSDirectWriter.Create;
  try
    W.BeginPatch('monthly-dashboard.xlsx', 'Data');   // no local
    W.AddSheet('Data');
    W.AddRow(1);
    W.WriteString(1, 'Region');
    W.WriteString(2, 'Revenue');
    W.AddRow(2);
    W.WriteString(1, 'North');
    W.WriteNumber(2, 184320.55);
    W.AddRow(3);
    W.WriteFormula(1, '=SUM(B2:B2)');
    W.Close;
  finally
    W.Free;
  end;
end;

A variante de inserção recebe um caminho de origem, um caminho de destino e InsertSheet:

  // A origem fica intocada; o destino recebe uma folha extra chamada Extra
  W.BeginPatch('template.xlsx', 'output.xlsx', 'Extra', True);
  W.AddSheet('Extra');
  W.AddRow(1);
  W.WriteString(1, 'appended by the nightly job');
  W.Close;

A inserção é a parte que exige verdadeira cirurgia de contabilidade interna. O escritor analisa o registo de folhas em xl/workbook.xml e o mapa de relações que liga cada folha à sua parte, e depois escolhe o próximo número de parte livre, o identificador de folha e o identificador de relação. Os tipos de relação seguem as convenções do pacote de origem, pelo que corrigir uma pasta de trabalho ISO 29500 strict emite tipos de relação strict e corrigir uma transitória emite tipos transitórios

O que a correção descarta e restringe deliberadamente

A cadeia de cálculo é descartada em ambos os modos. No modo de substituição, as suas entradas descrevem células de uma folha que já não existe nessa forma; no modo de inserção, o deslocamento do índice de folhas invalida-a por completo. O Excel reconstrói a cadeia no próximo recálculo, pelo que descartá-la é correto, não é uma perda. A parte fica de fora da cópia, e a sua entrada de relação e a sobreposição de tipo de conteúdo são removidas cirurgicamente

Duas semânticas de criação mudam dentro de uma correção, e ambas decorrem do mesmo princípio: a correção não pode perturbar partes que não reescreveu. As strings são escritas em linha na folha em vez de serem acrescentadas à tabela de strings partilhadas, porque a tabela de origem atravessa intocada. E StyleIndex refere-se a entradas na cellXfs do pacote de origem, não a uma tabela de estilos que o escritor construa. Isso significa que se pode referenciar formatos que a pasta de trabalho original já define, o que costuma ser exatamente o que uma atualização de dados quer, mas também significa que é preciso saber qual índice transporta qual formato:

// Dentro de uma correção, StyleIndex indexa o cellXfs do pacote de ORIGEM.
// Uma data precisa de um índice explícito que mapeie para um formato de data lá:
W.WriteDateTime(3, EncodeDate(2026, 8, 22), DateStyleIndexFromTemplate);

// A sobrecarga de WriteDateTime sem estilo é rejeitada em modo de correção,
// porque assume a tabela de estilos do próprio escritor, que uma correção
// nunca cria

Seis pontos de entrada de criação estão vedados: acrescentar tabelas, gráficos, imagens, comentários, nomes definidos e estilos de célula lançam todos uma exceção em modo de correção, com uma segunda rede de segurança no fecho que falha se algum dos respetivos contadores for diferente de zero. Cada uma dessas funcionalidades exigiria editar partes que a correção copia inalteradas, e um pacote meio editado é pior do que uma operação recusada. Só se pode corrigir exatamente uma folha por operação

Quando corrigir e quando carregar

Corrigir é a ferramenta certa quando a pasta de trabalho é grande, a alteração está confinada a uma folha, e o resto do ficheiro tem de sobreviver bit a bit. É a ferramenta errada quando a alteração abrange várias folhas, quando é preciso formatação nova ou objetos novos, ou quando o ficheiro é suficientemente pequeno para um carregamento e gravação normal não custar nada. Para geração em massa a partir do zero, o percurso em fluxo descrito em o escritor direto em fluxo continua a ser a melhor opção, e partilha a mesma API AddRow e Write*, pelo que passar de um para o outro é mecânico

A manipulação ao nível da folha dentro de uma pasta de trabalho carregada, quando se quer de facto o modelo de objetos completo, está descrita em duplicar folhas de cálculo em pacotes XLSX. E se a razão para considerar uma correção for o processamento da pasta de trabalho inteira se ter tornado lento, vale a pena ler as medições e o comportamento de memória em desempenho de pastas de trabalho grandes antes de escolher uma abordagem

Verificar se uma correção fez mesmo o que se pensa

Três verificações apanham quase todos os erros. Confirme que as partes que se esperava que sobrevivessem continuam no arquivo, que xl/calcChain.xml desapareceu, e que voltar a abrir o ficheiro através de TXLSXWorkbook reporta a contagem de folhas esperada, inalterada para uma substituição e incrementada em um para uma inserção. Ler de novo a folha corrigida e comparar alguns valores e fórmulas fecha o ciclo

Um detalhe de implementação do desenvolvimento desta funcionalidade merece ser repetido, porque pode afetar quem escrever código semelhante ao nível do zip. Os nomes das partes de folha de cálculo são comparados por prefixo, e um erro de um a mais ou a menos no comprimento do prefixo faz com que o predicado nunca corresponda, pelo que uma parte recém-escrita colide com um nome existente e os leitores que ficam com a última entrada de um dado nome acabam por escolher silenciosamente a folha errada. Se uma correção parecer trocar o conteúdo de duas folhas, verifique a correspondência de nomes antes de verificar o XML

A correção no local, as escritas em fluxo e o modelo de objetos de pasta de trabalho completo vêm na mesma biblioteca para Delphi e C++Builder; a lista de funcionalidades está na página do componente de folha de cálculo para Delphi HotXLS