O HotXLS abre pastas de trabalho gravadas pelo Excel 2.0, 3.0 e 4.0 diretamente a partir do Delphi e do C++Builder. Esses arquivos são anteriores ao contêiner de documento composto OLE que todo .xls posterior usa, então são streams de registros BIFF brutos, sem nenhum invólucro de armazenamento, e um leitor construído para BIFF8 não encontra uma única estrutura reconhecível dentro deles. Abrir um deles usa a mesma chamada Open de qualquer outra pasta de trabalho; o leitor detecta o formato e muda de caminho
Esses arquivos ainda aparecem, e esse é o único motivo pelo qual qualquer coisa disso importa. Arquivos de engenharia, retenção de registros governamentais, dados de laboratório vindos de instrumentos cujo software de controle foi escrito em 1993, e sistemas contábeis de longa duração, todos deixaram para trás pastas de trabalho BIFF2 e BIFF4. O Excel moderno se recusa a abrir vários deles diretamente, tendo removido conversores legados por motivos de segurança, o que deixa um conjunto de dados que ninguém consegue ler com uma ferramenta que alguém tenha
O que torna uma pasta de trabalho pré-OLE diferente?
Todo .xls a partir do Excel 5.0 é um arquivo composto OLE2, um pequeno sistema de arquivos dentro de um arquivo, com a pasta de trabalho residindo em um stream chamado Workbook ou Book. Analisar um deles começa analisando esse contêiner, como descrito em o formato binário de arquivo composto em Pascal
O BIFF2 até o BIFF4 não têm contêiner. O arquivo começa imediatamente com um registro BOF, e o número desse registro codifica a geração: $0009 para BIFF2, $0209 para BIFF3 e $0409 para BIFF4. O HotXLS valida o comprimento do corpo do BOF, que fica entre quatro e seis bytes, e o tipo de substream, $0010 para uma planilha, $0020 para um gráfico e $0040 para uma planilha de macro, antes de se comprometer com o caminho bruto. Essa validação é o que impede que um arquivo corrompido ou identificado incorretamente seja interpretado como uma pasta de trabalho muito antiga
Três gerações, três layouts de registro
Os registros de célula são onde as gerações mais divergem visivelmente. O BIFF2 ocupa um bloco contíguo de números de registro baixos, de $0001 a $0005, para células em branco, inteiras, numéricas, de rótulo e booleanas ou de erro, e cada corpo carrega um campo de atributo de três bytes onde versões posteriores colocam um índice de formato estendido. O BIFF3 e o BIFF4 abandonam isso e reutilizam os números e layouts de registro do BIFF5, $0201, $0203, $0204 e $0205, com um índice XF de dois bytes
Esse último detalhe causa uma falha específica e facilmente mal diagnosticada. Um registro LABEL do BIFF3 ou BIFF4 é estruturalmente idêntico à sua contraparte BIFF5, linha e coluna seguidas pelo índice de formato e depois pela contagem de caracteres. Escreva um leitor que assuma o layout do BIFF2 e ele lê dois bytes a menos, depois passa do fim do registro e interpreta mal tudo o que vem depois. O sintoma não é uma exceção; é uma pasta de trabalho que se lê com lixo plausível dentro
Os registros de fórmula ocupam uma numeração paralela nas três gerações, $0006, $0206 e $0406. Quando uma fórmula produz um resultado em string, essa string chega em um registro separado seguinte, $0007 ou $0207, e a forma BIFF2 dela usa um prefixo de comprimento de um único byte, em vez do de dois bytes usado depois
Por que as fórmulas voltam como valores, não como texto
O HotXLS lê o resultado em cache de uma fórmula nesses arquivos e não tenta reconstruir a expressão da fórmula. Esse é um limite deliberado, não uma lacuna à espera de ser preenchida
A expressão analisada no BIFF2 até o BIFF4 usa uma codificação de token que difere do BIFF5 e posteriores de formas que vão além do cosmético: os comprimentos de token têm prefixos diferentes, os tokens de referência têm tamanhos diferentes, e as tabelas de índice de função foram renumeradas entre gerações. Passar esses bytes por um tradutor de expressões BIFF8 não produz uma fórmula errada, produz uma aleatória. Ler o valor em cache dá a você o número ou a string que o Excel calculou pela última vez, que é exatamente o que uma migração de arquivo precisa
O valor em cache reside em um deslocamento dependente da geração dentro do registro: byte 7 para o BIFF2 e byte 6 para o BIFF3 e o BIFF4. Valores especiais, strings, booleanos, erros e células em branco, são codificados em uma palavra marcadora $FFFF com um discriminador, a mesma convenção que gerações BIFF posteriores mantiveram
Abrindo um arquivo
O código de chamada é comum, e esse é o objetivo. A detecção acontece dentro de Open:
uses
lxHandle;
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
R, C: Integer;
V: Variant;
begin
Book := TXLSWorkbook.Create;
try
if Book.Open('archive\1993-inventory.xls') <> 1 then
begin
Writeln('unreadable - quarantine for manual review');
Exit;
end;
Sheet := Book.Sheets[1]; // Sheets[] é baseado em 1
for R := Sheet.UsedRange.FirstRow + 1 to Sheet.UsedRange.LastRow + 1 do
for C := Sheet.UsedRange.FirstCol + 1 to Sheet.UsedRange.LastCol + 1 do
begin
V := Sheet.Cells[R, C].Value;
if not VarIsEmpty(V) then
Writeln(Format('R%dC%d = %s', [R, C, VarToStr(V)]));
end;
finally
Book.Free;
end;
end;
Observe a aritmética de índices nesse loop. Os limites de UsedRange são baseados em zero, enquanto tanto a coleção de planilhas quanto o acesso a células são baseados em um, uma inconsistência anterior à API atual e preservada por compatibilidade. Esquecer o ajuste audita o retângulo errado e não relata nada de anormal ao fazê-lo. Verificações prévias baratas que evitam carregar um arquivo por completo são abordadas em inspeção leve de pastas de trabalho
O que você não obtém, e o que fazer a respeito
A formatação não é interpretada. O HotXLS não analisa os registros XF e FONT dessas gerações, então fontes, cores, bordas e formatos numéricos ficam indisponíveis, e células que o Excel um dia exibiu como datas voltam como seus números de série brutos
Esse último ponto precisa ser tratado no seu próprio código, não no leitor, e o motivo é honesto: os formatos numéricos no BIFF2 até o BIFF4 não são confiáveis o suficiente para orientar uma decisão automática de data. Uma coluna de números de cinco dígitos pode ser datas, ou pode ser códigos de peça. Converta deliberadamente, usando o sistema de datas da pasta de trabalho, cujas regras são descritas em números de série de data, o sistema 1904 e formatos numéricos:
// Decida por coluna, nunca por valor: um número de cinco dígitos pode
// ser uma data ou um código de peça, e o formato legado não vai dizer
if ColumnHoldsDates(C) then
begin
// Os dois sistemas de data ficam 1462 dias distantes um do outro,
// então o mesmo número de série denota duas datas com quatro anos
// de diferença. Leia o sistema a partir da pasta de trabalho, em
// vez de supor um deles
if Book.Date1904 then
Writeln(DateToStr(SerialToDate1904(V)))
else
Writeln(DateToStr(SerialToDate1900(V)));
end
else
Writeln(VarToStr(V));
Duas observações estruturais completam o quadro. Os registros de proteção por senha e de página de código aparecem dentro do único stream de planilha, em vez de em um stream em nível de pasta de trabalho, porque não existe um stream em nível de pasta de trabalho onde colocá-los, então precisam ser reconhecidos no contexto da planilha. E um arquivo BIFF2 até BIFF4 contém exatamente um substream de planilha; pastas de trabalho com várias planilhas não existiam até o formato ganhar seu contêiner
O caminho de migração pragmático é, portanto, de duas etapas: ler o arquivo legado por seus valores, depois escrever uma pasta de trabalho moderna que carrega esses valores com uma formatação que você mesmo aplica. Leitura legada, escrita moderna e tudo o que há entre elas rodam em uma única biblioteca para Delphi e C++Builder, descrita na página do componente de planilhas para Delphi HotXLS