Imagine um processamento noturno que cria um livro de faturas por código e o exporta como CSV para ser importado por um sistema posterior. Os números parecem corretos no Excel. O ficheiro CSV abre sem problemas num editor de texto. No entanto, o importador falha ao processar a coluna dos totais, porque o campo do valor na linha 42 contém =SUM(D2:D41), ou seja, a fórmula em formato de texto literal e não o valor calculado correspondente. Nada está avariado. Este é o comportamento documentado e constitui o primeiro detalhe a compreender na exportação com o HotXLS: o gravador serializa o modelo de células exatamente como ele se encontra, e uma célula com fórmula cujo valor nunca foi calculado tem apenas o texto da sua fórmula para entregar.
Porque é que o seu CSV contém fórmulas em vez de números
O HotXLS armazena o texto da fórmula e o valor calculado como dois elementos independentes. Por conceção, a função SaveAsCSV não executa o motor de cálculo ao exportar: uma exportação não deve modificar o livro nem correr o risco de bloquear numa cadeia complexa de fórmulas. Os ficheiros gravados pelo próprio Excel contêm resultados guardados em cache ao lado das fórmulas, pelo que a sua reexportação funciona conforme esperado. O problema afeta apenas os livros gerados pelo seu próprio código, nos quais as fórmulas foram escritas mas nunca avaliadas. A solução consiste em garantir a existência dos valores antes de exportar, utilizando o mesmo motor Calculate que processa referências entre folhas e funções personalizadas:
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
R: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('invoice-run.xlsx');
Sheet := Book.Sheets[0];
// Materialize formula results so the CSV carries numbers, not '=...' text
for R := 2 to 41 do
if Sheet.Cells[R, 4].Formula <> '' then
Sheet.Cells[R, 4].Value := Book.Calculate(Sheet.Cells[R, 4].Formula);
Book.SaveAsCSV('feed.csv', 0, ','); // sheet 0, comma
Book.SaveAsCSV('feed.tsv', 0, #9); // same sheet as TSV
finally
Book.Free;
end;
end;
Observe o que o ciclo faz na realidade: substitui as células de fórmulas pelos seus valores calculados. Isso é adequado para uma exportação temporária, mas incorreto se pretender voltar a gravar o livro como .xlsx posteriormente, pois terá substituído fórmulas dinâmicas por números estáticos. Efetue a exportação a partir de uma cópia ou limite a escrita de volta para que afete apenas o processo de exportação. O motor por trás de Calculate oferece mais possibilidades, incluindo o registo das suas próprias funções, o que é abordado no artigo o motor de fórmulas e funções personalizadas no HotXLS.
O que o gravador delimitado garante
A exportação para CSV produz um ficheiro UTF-8 com uma marca de ordem de bytes (BOM), quebras de linha CRLF e a citação da norma RFC 4180. Qualquer campo que contenha o delimitador, aspas ou uma quebra de linha é colocado entre aspas, e as aspas internas são duplicadas. As datas são renderizadas como yyyy-mm-dd hh:nn:ss, independentemente do formato de visualização da célula. Essa é a decisão correta para um processador automático, embora possa surpreender quem esperava que a formatação visual do ecrã fosse mantida. As células com rich text são simplificadas ao concatenar os seus trechos de texto.
Estas opções padrão resolvem a maior parte dos conflitos com um importador antes de começarem, mas duas delas devem fazer parte do seu contrato de interface em qualquer caso. A primeira é a BOM. É ela que permite ao Excel abrir o ficheiro mantendo os caracteres acentuados legíveis, contudo, alguns analisadores rígidos processam esses três bytes como dados; se for o caso do seu sistema, remova-os na entrega. A segunda é a exportação TSV. Não se trata de uma funcionalidade independente, mas apenas do mesmo gravador chamado com #9 como delimitador, pelo que tudo o que foi dito acima se aplica sem alterações. A folha a exportar é escolhida por um índice baseado em 0 na sobrecarga com múltiplos argumentos, enquanto o atalho SaveAsCSV(FileName) com um único argumento seleciona a folha ativa.
A exportação HTML é um instantâneo, não um formato de intercâmbio
Enquanto o CSV descarta tudo exceto os valores, a função SaveAsHTML tenta preservar o aspeto visual: uma <table> por folha, regiões unidas expressas como colspan e rowspan, e estilos de células básicos definidos na própria linha (inlined) sob a forma de CSS. As cores associadas ao tema do documento são ignoradas em vez de resolvidas, pelo que um modelo baseado em slots de temas será apresentado com um aspeto mais simples do que no Excel. Defina cores RGB explícitas em tudo o que deva sobreviver à exportação. O objeto de opções controla a estrutura envolvente:
var
Opts: TXLSXHtmlExportOptions;
begin
Opts := TXLSXHtmlExportOptions.Create;
try
Opts.Title := 'Weekly settlement';
Opts.TableClass := 'report-grid'; // hook for the host page stylesheet
Opts.WriteDocument := True; // full page, not a fragment
if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
raise Exception.Create('Sheet index out of range');
finally
Opts.Free;
end;
end;
Dois detalhes nesse trecho merecem atenção. Defina a propriedade WriteDocument como False e o resultado passará a ser um mero fragmento de tabela em vez de uma página completa, o que é o pretendido para integrar uma pré-visualização num layout já existente: configure TableClass e deixe que a folha de estilos do anfitrião trate do tema visual. A convenção de retorno também é o oposto da maioria das chamadas do HotXLS. A função SaveAsHTML devolve 0 em caso de sucesso e -1 para um índice de folha inválido, pelo que uma verificação baseada em = 1 reportará todas as exportações bem-sucedidas como falhas. Quando necessita apenas de uma área específica e não de uma folha inteira, talvez para enviar por correio eletrónico ou incorporar um único bloco, a função TXLSXRange.SaveAsHTML exporta qualquer intervalo retangular segundo as mesmas regras de renderização.
A exportação RTF e onde esta ainda tem utilidade
O quarto destino grava tabelas RTF 1.6, uma folha por chamada através de SaveAsRTF. A largura das colunas é estimada em cerca de 96 twips por caractere de largura de coluna. A limitação estrutural a ter em conta é que as células unidas não se estendem no resultado: apenas a célula âncora contém o seu conteúdo e as células abrangidas são apresentadas como espaços em branco. Isto afeta o RTF de modelos com layouts complexos. Mantém, contudo, a sua utilidade como o caminho de menor resistência para colocar resultados tabulares num processador de texto ou num sistema de gestão de documentos legado anterior ao processamento de HTML.
Fluxo completo: a importação de CSV é destrutiva por conceção
Ler ficheiros CSV tem a sua própria convenção. A função OpenCSV limpa o livro completo e reconstrói-o como uma folha única designada Sheet1. Trata-se de um construtor em termos práticos, e não de uma mesclagem, pelo que nunca o deve chamar num livro que contenha conteúdo não guardado. Passar #0 como separador ativa a deteção automática de delimitadores. A flag ADetectTypes controla a conversão automática de tipos: com esta ativa, as strings numéricas convertem-se em números, as strings ISO-8601 em datas e os valores true/false em booleanos. Desative-a quando o fluxo contiver identificadores com zeros à esquerda, códigos postais ou códigos de produtos, pois a conversão automática transformá-los-á silenciosamente em números (um zero à esquerda desaparece se 00123 for convertido em 123). Ambas as fachadas expõem a mesma importação. Combine-a com as chamadas de exportação indicadas acima e terá uma ponte de formatos que dispensa a instalação do Excel no pipeline, o cenário abordado no artigo geração de relatórios da base de dados para o Excel com o HotXLS.
Exportar diretamente para um fluxo (stream)
Todos os gravadores apresentados possuem uma sobrecarga de fluxo ao lado da versão de nome de ficheiro: CSV, HTML, RTF e os próprios formatos de livro. Em código de servidor, estas sobrecargas são as indicadas. Um ponto de acesso web que forneça a descarga de um CSV pode escrever num TMemoryStream e entregá-lo diretamente ao objeto de resposta, sem ficheiros temporários, sem tarefas de limpeza e sem risco de colisão entre dois pedidos que escolham o mesmo nome gerado. O mesmo se aplica ao envio de exportações para armazenamento de blobs ou anexação em mensagens de correio eletrónico. O sistema de ficheiros deixa de fazer parte do processo.
Este padrão beneficia da forma como a biblioteca é implementada. Ambas as fachadas são leitores e gravadores nativos em Object Pascal, pelo que não existe instalação do Excel, nem automatização COM, nem estrangulamentos por processo ao serializar pedidos no servidor. Cada pedido pode gerir o seu próprio objeto de livro, executar o cálculo antes da gravação descrito na primeira secção e transmitir a sua exportação em fluxo paralelamente a outros processos. A memória é o único recurso a monitorizar. O livro em RAM durante a exportação, pelo que um serviço que abra ficheiros muito grandes apenas para os reemitir como CSV deve limitar as tarefas simultâneas, ou colocar em fila os ficheiros maiores, em vez de permitir que um pico de tráfego determine o consumo.
Uma configuração mais pequena: defina IncludeBOM nas opções do HTML quando o fragmento for gravado como um ficheiro autónomo cuja codificação possa ser analisada por ferramentas posteriores. Quando o HTML é servido diretamente sobre HTTP, delegue a declaração do charset nos cabeçalhos da resposta.
Quando os bytes chegam incorretos ao destino
A dúvida de suporte mais comum sobre a exportação de CSV é o problema inicial sob uma perspetiva diferente: o Excel mostra caracteres ilegíveis (mojibake) em vez de acentos. O instinto inicial é culpar o gravador, mas este emite um BOM UTF-8 exatamente por esse motivo, e o ficheiro está quase sempre correto quando sai do seu código. Algum elemento entre o seu código e o Excel eliminou o BOM. Uma transferência por FTP em modo de texto, uma cópia de fluxo que ignore os três primeiros bytes ou um proxy que altere a codificação no percurso: qualquer um destes removerá a marca e deixará o Excel a tentar estimar a codificação, o que este faz de forma deficiente. Identifique este erro nas fronteiras do processo, e não na chamada de exportação. Abra o ficheiro entregue num visualizador hexadecimal e confirme se os bytes EF BB BF ainda se encontram no início do mesmo.
Este é o princípio comum aos quatro formatos. A chamada de exportação é a parte simples, e o HotXLS adota decisões seguras em cada opção do gravador. As falhas residem nas ligações entre sistemas, onde o texto da fórmula encontra um analisador que esperava um número, onde um BOM encontra um transporte que não o preserva ou onde uma célula unida encontra o modelo de tabela plana do RTF. Cada um destes aspetos constitui um facto a documentar no contrato de interface entre o seu processo de exportação e os sistemas que o consomem, já que estes não conseguem ler as suas intenções a partir dos bytes recebidos. Para consultar a lista de métodos completa nas duas fachadas de livros, a página de produto HotXLS Component disponibiliza a referência detalhada.