Por vezes, a única pergunta que uma rotina de triagem de entrada necessita de ver respondida é estrutural: se este livro tem uma folha chamada 'Mapping', ou quantos separadores possui. Responder a isso chamando Open é a via mais dispendiosa. Uma abertura completa inflaciona a tabela de strings partilhadas (shared string table), descodifica cada registo de estilo e percorre as células de cada folha de cálculo, porque não tem forma de saber que apenas pretendia o índice. Num ficheiro de grandes dimensões, isto traduz-se em centenas de megabytes de alocações e vários segundos de processador gastos para ler uma lista que ocupa escassos kilobytes. O HotXLS, a biblioteca nativa de folhas de cálculo para Delphi da losLab, fornece essa lista de forma isolada: o método GetSheetNames devolve os nomes das folhas de cálculo, na ordem do livro, sem instanciar uma única célula.
Por que razão o catálogo é barato de ler
Ambos os formatos de folha de cálculo colocam o seu índice perto do início do ficheiro, o que torna a chamada de listagem rápida e não propriamente complexa. Um pacote OOXML mantém o catálogo de folhas em xl/workbook.xml, uma secção que se mantém pequena quer o livro tenha dez linhas ou dez milhões. Um ficheiro BIFF8 .xls armazena os seus registos BoundSheet no início do fluxo global do livro, antes de quaisquer dados de células. Assim, o trabalho que uma chamada de listagem evita não é um mero arredondamento face a uma abertura completa. Evita a maior parte do ficheiro. Ler o catálogo custa o mesmo punhado de kilobytes independentemente do número de linhas, enquanto uma abertura completa escala com os dados; num livro de cálculo de vários megabytes, essa diferença atinge várias ordens de grandeza em bytes processados e memória alocada.
Esse custo fixo é a propriedade em torno da qual vale a pena desenhar o sistema. Um portal de triagem construído com GetSheetNames comporta-se da mesma forma num ficheiro de 200 linhas ou num de 200 MB, pelo que o ficheiro mais lento de um lote deixa de ditar o ritmo para decidir se vale a pena processar o ficheiro.
Uma única chamada para .xls, .xlsx e formatos de modelos
Na fachada XLS, o TXLSWorkbook.GetSheetNames lê mais do que apenas ficheiros .xls. Também aceita os formatos baseados em zip .xlsx, .xlsm, .xltx e .xltm, extraindo apenas o workbook.xml do arquivo. Para inputs .xls genuínos, analisa os registos BoundSheet e para no primeiro registo EOF do subfluxo global, pelo que um ficheiro binário grande continua a custar apenas os seus kilobytes iniciais. A fachada XLSX oferece uma garantia que importa mais para código de serviço de longa duração do que parece à primeira vista: o TXLSXWorkbook.GetSheetNames deixa a instância do livro nem limpa nem preenchida, pelo que uma instância que já contenha um documento aberto pode sondar outros ficheiros sem perturbar o que está em mãos. O GetODSSheetNames aplica a mesma abordagem a pacotes OpenDocument, e cada uma destas chamadas tem uma sobrecarga de fluxo (stream), o que lhe permite inspecionar um upload que nunca chega a ser gravado no disco.
var
Book: TXLSXWorkbook;
Names: TStringList;
I: Integer;
begin
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
if Book.GetSheetNames('upload-7f3a.xlsx', Names) <= 0 then
raise Exception.Create('unreadable workbook package');
if Names.IndexOf('Mapping') < 0 then
raise Exception.Create('required Mapping sheet is missing');
for I := 0 to Names.Count - 1 do
Writeln(Format('sheet %d: %s', [I, Names[I]]));
finally
Book.Free;
Names.Free;
end;
end;
A mesma chamada serve para um bom diálogo de importação em aplicações desktop. Liste as folhas, permita que o utilizador escolha uma e pague pela abertura total apenas após a seleção ter sido feita. Num livro de cálculo com cinquenta folhas, a diferença é visível: um selecionador que aparece imediatamente contra um que bloqueia enquanto o ficheiro completo é carregado em segundo plano.
Ficheiros .xlsm com macros ativas e os formatos de modelos são listados exatamente como um .xlsx simples, uma vez que o catálogo reside no mesmo workbook.xml quer haja ou não um vbaProject.bin dentro do pacote. Um pipeline de entrada pode, por isso, enumerar as folhas de um livro com macros para fins de encaminhamento, sem nunca tocar no payload das macros e sem fazer nada que as pudesse executar, deixando a decisão sobre a política de macros para a etapa que efetivamente abre o ficheiro.
Ler o valor de retorno sem enganos
As convenções de retorno não são uniformes no HotXLS. Algumas chamadas devolvem 1 em caso de sucesso, outras devolvem uma contagem, pelo que, para as funções de listagem, a única validação que se sustenta é tratar qualquer valor igual ou inferior a zero como falha, limpando a lista de strings. Resista à tentação de interpretar uma lista vazia como 'um livro sem folhas'. Tanto a norma ECMA-376 como a especificação BIFF8 exigem pelo menos uma folha de cálculo num livro válido, pelo que zero nomes significa sempre que a leitura falhou, e nunca que o ficheiro está legitimamente vazio.
Uma listagem falhada é, por si só, um sinal que vale a pena registar. Um ficheiro .xlsx que falhe na chamada indica uma de várias situações específicas: está truncado, não é realmente um pacote OOXML (exportações CSV mal etiquetadas de outros sistemas surgem constantemente aqui) ou é um contentor encriptado. Distinguir estas situações é a tarefa da verificação seguinte. Registar os primeiros bytes do ficheiro rejeitado juntamente com o log de falha costuma encurtar a resolução de um pedido de suporte para uma única mensagem.
Detetar contentores encriptados antes do encaminhamento
Um ficheiro .xlsx encriptado não é um zip. É um ficheiro composto OLE que envolve os fluxos EncryptionInfo e EncryptedPackage, pelo que o GetSheetNames não o consegue ler por dentro e devolve falha como qualquer outro ficheiro ilegível. O CanReadEncrypted testa essa estrutura de contentor, permitindo que a triagem encaminhe um ficheiro encriptado de propósito, em vez de ocultar um erro de leitura genérico vindo das profundezas de um processo de trabalho:
type
TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);
function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
// Encrypted OOXML is an OLE container, not a zip: check first,
// because the listing calls cannot look inside it.
if Book.CanReadEncrypted(FileName) then
Exit(irNeedsPassword);
if SameText(ExtractFileExt(FileName), '.ods') then
begin
if Book.GetODSSheetNames(FileName, Names) <= 0 then
Exit(irUnreadable);
end
else if Book.GetSheetNames(FileName, Names) <= 0 then
Exit(irUnreadable);
Result := irNormal;
finally
Book.Free;
end;
end;
A encriptação é o ponto onde o HotXLS é deliberadamente assimétrico, pelo que o encaminhamento tem de respeitar isso. A encriptação legada .xls (RC4, RC4 CryptoAPI, XOR) é legível: TXLSWorkbook.Open(FileName, Password) desencripta com uma palavra-passe armazenada, pelo que esses ficheiros podem permanecer no caminho automatizado. Já os pacotes OOXML encriptados seguem a via oposta. O HotXLS consegue escrever um com SaveAsEncrypted, mas não o consegue ler de volta. O método OpenEncrypted gera a exceção EXlsxEncryptionNotImplemented quando se lhe passa um pacote encriptado, razão pela qual um design de triagem correto envia ficheiros .xlsx encriptados para um utilizador humano com o Excel e mantém os ficheiros .xls com palavra-passe no código.
Para trabalhos em lote, este classificador justifica a sua utilização ao correr sobre um diretório de entrada inteiro antes de qualquer processo iniciar o processamento real, uma vez que cada verificação custa cerca de uma abertura de ficheiro e alguns kilobytes de leituras. Colocar este passo logo no início altera o modo de falha com que a equipa de operações se preocupa. Em vez de uma tarefa noturna falhar às 3 da manhã no ficheiro 412 de 600, obtém 412 ficheiros em fila e 5 rejeitados na entrada com um motivo associado a cada um. As mesmas chamadas de biblioteca resultam numa história operacional muito melhor.
As perguntas que uma chamada de listagem não pode responder
Nomes e ordem são tudo o que obtém. Las chamadas de listagem nada dizem sobre visibilidade, pelo que folhas ocultas e muito ocultas (very-hidden) aparecem na lista como qualquer outra. Não reportam dimensões de intervalo utilizado, contagem de células ou propriedades do documento. A secção docProps/core.xml também é pequena, mas não existe atualmente uma verificação apenas de propriedades, pelo que os metadados de autor e título continuam a custar uma abertura Open completa. A forma limpa de gerir isto é deixar que os factos baratos encaminhem cada ficheiro e reservar os dados dispendiosos para os ficheiros que superam a triagem. Para os ficheiros que prosseguem para uma leitura aprofundada, um varrimento de apenas leitura de um ficheiro .xls grande corre significativamente mais rápido com _DisableGraphics := True, o que ignora a análise de OfficeArt. Contudo, nunca guarde a partir dessa instância: a camada gráfica ignorada desaparece do modelo, e guardar descartaria os desenhos do ficheiro.
Os ficheiros que passam na triagem costumam avançar para uma análise mais profunda. O artigo sobre auditoria de livros e bancada de conversão cobre os contadores por folha que vale a pena recolher quando se justifica uma abertura total, e o guia de desempenho para grandes livros de cálculo ensina a manter essa abertura completa rápida.
O HotXLS é uma biblioteca nativa de folhas de cálculo em Object Pascal para Delphi e C++Builder; a totalidade da API, incluindo as chamadas de inspeção apresentadas aqui, está documentada na página do Componente HotXLS.