O HotXLS lê e escreve ficheiros Excel Binary Workbook (XLSB) a partir de Delphi através da mesma fachada TXLSXWorkbook usada para XLSX: o Open deteta um workbook binário a partir do conteúdo do pacote, e o SaveAs ou SaveAsChecked com xlsxExcelBinaryWorkbook escreve um. O backend cobre um subconjunto validado e recusa tudo o que não consegue representar antes de tocar no destino
Essa última cláusula é a parte que vale a pena ler devagar. O XLSB parece uma variação simples do XLSX, e em termos de empacotamento é. O problema vive nos detalhes que os dois formatos codificam de forma diferente, e naquilo que uma biblioteca assume em silêncio quando faz a ponte de um para o outro. O bug mais instrutivo deste backend não foi de todo um registo malformado. Foi uma fonte predefinida que desapareceu, e uma coluna de datas que virou #### no Excel embora o valor, o código de formato e a largura da coluna estivessem todos corretos
O que é um ficheiro XLSB e em que difere do XLSX?
Um ficheiro XLSB é um pacote OPC com exatamente a estrutura de partes de um workbook XLSX, mas as partes de workbook, worksheet, estilos e strings partilhadas guardam registos binários em vez de XML. O formato está especificado no [MS-XLSB], e a codificação de registos é muitas vezes chamada BIFF12. Continua a ter [Content_Types].xml, partes de relacionamento e um contentor ZIP; o que muda é que xl/workbook.bin substitui xl/workbook.xml, xl/worksheets/sheet1.bin substitui o XML da worksheet, e por aí fora
Cada registo BIFF12 começa com um cabeçalho compacto de comprimento variável. O tipo de registo leva um ou dois bytes, sete bits por byte com o bit alto como flag de continuação, por isso um tipo de registo cabe em 14 bits. O tamanho do payload segue em um a quatro bytes com o mesmo esquema de sete bits. Strings são uma contagem de caracteres de 32 bits seguida de unidades de código UTF-16. Comparado com o BIFF8, este é um layout muito mais limpo: sem divisão CONTINUE de 8224 bytes, sem flags de string comprimida-versus-larga a meio do registo. Comparado com o XLSX, salta inteiramente a tokenização de XML, razão pela quais ficheiros XLSB grandes tendem a ser mais pequenos em disco e mais rápidos para o Excel abrir
Um detalhe de empacotamento faz tropeçar o código de deteção. O Excel registra a parte de workbook XLSB com o content type application/vnd.ms-excel.sheet.binary.macroEnabled.main, mesmo para workbooks que não contêm macros. O HotXLS verifica esse registro no [Content_Types].xml primeiro e só recorre à extensão de parte .bin quando o content type está ausente, e qualquer um dos caminhos continua a ter de passar o validador estrito de registos. As regras gerais sobre nomes de partes, relacionamentos e content types são as mesmas cobertas em porque é que o Excel repara um XLSX válido e as regras de pacote OPC por trás disso
Quando escolher XLSB em vez de XLSX?
Escolha XLSB quando um sistema a jusante o exigir ou quando trocar workbooks grandes e carregados de valores com utilizadores de Excel que se importam com o tempo de abertura; caso contrário o XLSX continua a ser a predefinição mais segura. O XLSX é uma norma ISO (ISO/IEC 29500) com amplo suporte de ferramentas, é inspecionável com uma ferramenta ZIP e um editor de texto, e todos os produtos de folhas de cálculo o leem. O XLSB é um formato Microsoft com especificação publicada mas um ecossistema mais estreito
Na prática os pedidos vêm de duas direções. Alguns pipelines de relatórios e arquivos padronizaram .xlsb há anos e rejeitam qualquer outra coisa. Outras equipas geram workbooks com centenas de milhares de células numéricas e descobriram que os utilizadores abrem a versão binária visivelmente mais depressa. Ambas são razões legítimas. "Parece mais eficiente" não é, porque o backend binário do HotXLS prepara o pacote completo em memória, por isso não é a ferramenta para exportações de servidor com memória limitada. Para essas, o escritor XLSX em streaming é o caminho certo
Abrir e gravar XLSB com o TXLSXWorkbook
O TXLSXWorkbook.Open não precisa de dica de formato: a mesma chamada carrega XLSX, XLSM ou XLSB, e depois de um carregamento binário bem-sucedido o TXLSXWorkbook.SourceFormat reporta xlsxExcelBinaryWorkbook. A escrita é explícita. O SaveAs(FileName) sem formato produz sempre um workbook Open XML, mesmo quando o nome do ficheiro acaba em .xlsb, por isso o output binário nomeia sempre o formato
uses
SysUtils, lxHandleX;
procedure ConvertToXlsb(const Source, Target: string);
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
// O Open deteta XLSX, XLSM ou XLSB a partir do próprio pacote
if Book.Open(Source) <> 1 then
raise Exception.Create('Input is unsupported or invalid');
// A extensão sozinha nunca seleciona output binário
if Book.SaveAsChecked(Target, xlsxExcelBinaryWorkbook) <> 1 then
raise Exception.Create('XLSB output is unsupported or failed');
finally
Book.Free;
end;
end;
O output para stream passa por SaveAs(Stream, xlsxExcelBinaryWorkbook) ou pelo overload de stream do SaveAsChecked. Internamente ambos encaminham para os métodos privados SaveAsXlsbStream e SaveAsXlsbFile; nunca os chama diretamente. O output para ficheiro é preparado num ficheiro temporário irmão e consignado com uma única substituição final. Um destino de stream tem de ser legível e posicionável, porque o escritor tira um instantâneo dos seus bytes, tamanho e posição e restaura-os se a cópia final falhar. O TXLSXWorkbook.LastSaveUsedStreaming reporta False depois de uma gravação binária, o que é exato: o pacote inteiro existe em memória antes de um único byte chegar ao seu destino
Porquê um subconjunto validado em vez de fidelidade total?
O HotXLS não mantém um segundo modelo de objetos para XLSB. Na abertura, o XlsbToOpenXml converte o pacote binário num pacote Open XML em memória e o parser XML comum carrega-o; na gravação, o escritor XML constrói um pacote e o OpenXmlToXlsb codifica-o como registos binários. Cada funcionalidade tem por isso de sobreviver a duas traduções, e uma funcionalidade que a ponte não consiga traduzir exatamente é recusada em vez de aproximada
O conjunto suportado a partir do HotXLS 2.384.113 é suficientemente largo para a maioria dos workbooks de dados: valores tipados, strings partilhadas e inline, fontes comuns, preenchimentos, bordas e formatos numéricos, alturas de linha e larguras de coluna, fórmulas de célula comuns com resultados em cache, nomes definidos, células fundidas, hyperlinks, painéis congelados e divididos, intervalos AutoFilter com critérios personalizados simples, e tabelas de worksheet comuns. Fora dele ficam fórmulas partilhadas e de array, funções futuras e definidas pelo utilizador, referências a workbooks externos, gráficos e desenhos, PivotTables e slicers, VBA, formatação condicional, validação de dados e definições de cálculo não predefinidas como cálculo manual ou iteração
A recusa é obrigatória, não consultiva. O TXLSXWorkbook.GetConversionReport(xlsConversionExcelBinaryWorkbook) lista as funcionalidades não suportadas antes de gravar, e essas entradas fazem a gravação falhar mesmo com AAllowLossy = True ou um SaveAs sem verificação. Isso é deliberado. Deitar silenciosamente fora uma PivotTable de uma cópia binária é o género de perda que ninguém repara até o workbook estar à frente de um cliente
uses
Classes, lxHandleX, lxDiagnostics;
function CanSaveAsXlsb(Book: TXLSXWorkbook; Log: TStrings): Boolean;
var
Report: TXLSConversionReport;
I: Integer;
begin
Report := Book.GetConversionReport(xlsConversionExcelBinaryWorkbook);
try
Result := True;
for I := 0 to Report.Count - 1 do
if Report[I].Fidelity = xlsConversionUnsupported then
begin
Result := False;
Log.Add(Report[I].SheetName + ' ' + Report[I].Location + ': ' +
Report[I].Feature + ' - ' + Report[I].Message);
end;
finally
Report.Free; // o relatório pertence a quem chama
end;
end;
O relatório cobre o modelo vivo. A validação final do pacote continua a ser a última barreira para coisas que o modelo nunca vê, como registos malformados ou atributos desconhecidos no ficheiro de origem, por isso um relatório limpo seguido de uma gravação falhada é possível, e o resultado da gravação é aquilo em que age
Porque é que a fonte predefinida e o formato 0.00 desapareceram?
A fonte predefinida e os formatos numéricos internos desapareceram porque o leitor de estilos XML fez duas suposições que valem para ficheiros que o próprio HotXLS escreve mas não para workbooks que o Excel escreve. A ponte preservou ambos os recursos perfeitamente; a perda aconteceu uma camada acima, no caminho público Open. Essa lacuna é a principal lição deste backend: uma ponte de formato correta não garante um round trip público fiel
A primeira suposição dizia respeito a fonts[0]. Quando o HotXLS grava XLSX, escreve uma fonte predefinida implícita no slot zero, e o seu leitor XML salta esse slot para que a coleção Fonts voltada ao utilizador comece no índice zero. Um XLSB escrito pelo Excel traz a sua própria predefinição explícita nesse slot, e saltá-la deitou essa escolha fora. A segunda suposição dizia respeito a formatos numéricos. O leitor só mapeava valores numFmtId personalizados (164 e acima) para NumberFormats; os ids internos ficavam como zero. Uma célula vazia com estilo a transportar o formato interno 2, que é 0.00, voltava como General
A correção vive inteiramente no caminho de importação binário. Depois de o pacote completo passar o preflight, a abertura XLSB define um modo de estilos privado apenas para o parse XML síncrono. Nesse modo o leitor carrega todas as fontes incluindo o slot zero e desloca cada fontId em um, converte cada id interno suportado num código de formato explícito no modelo, e continua a usar o numFmtId original para a deteção de datas, por isso um formato de data interno mantém a sua semântica de data. Um bloco finally limpa sempre o modo, e um Open XLSX ou ODS comum posterior limpa também o estado de fonte predefinida retido
Como é que fonts[0] afeta as larguras de coluna do Excel?
O Excel calcula as larguras de coluna a partir da fonte predefinida do workbook em fonts[0], e não a partir das fontes aplicadas às células dessa coluna. A width da coluna é guardada em unidades de caractere da largura máxima de dígito da fonte predefinida, por isso mudar o fonts[0] muda a largura em pixéis de todas as colunas, incluindo colunas cujas células usam todas uma fonte diferente
Isso produziu o sintoma mais confuso de toda a investigação. O workbook regravado tinha valores de célula idênticos, códigos de formato idênticos e atributos width idênticos, a fonte das células e o estilo Normal apontavam ambos para a fonte preservada, e o Excel ainda renderizava uma coluna de datas como ####. O fonts[0] escrito tinha voltado a Calibri 11, a largura máxima de dígito encolheu, e as datas já não cabiam. O HotXLS agora mantém a fonte predefinida de origem e escreve-a de volta como fonts[0], recorrendo a Calibri 11 apenas para workbooks que nunca tiveram uma origem binária. A aritmética completa de larguras está em largura de coluna Excel e max digit width (MDW) em Delphi
Formatos internos dependentes de locale são recusados, não adivinhados
Um id de formato numérico interno sem código de formato explícito só é aceite quando o seu significado é fixo; ids dependentes de locale são recusados durante o preflight do pacote. O HotXLS mapeia os ids 0 a 22, 37 a 40 e 45 a 49 para códigos conhecidos como 0.00, 0%, mm:ss e @. Os restantes ids abaixo de 164 pertencem a formatos cuja renderização depende das definições regionais da instalação de Excel, e a importação recusa-os com a mensagem "A locale-dependent number format requires an explicit format code"
Inventar um código para esses ids seria pior do que falhar. Um padrão de data do Leste Asiático adivinhado parece plausível, faz round trip sem erro e mostra a coisa errada a metade dos seus utilizadores. O tempo também importa: a verificação corre no preflight do pacote inteiro, antes de o workbook existente ser limpo, por isso um ficheiro recusado deixa o modelo carregado anteriormente, o nome de ficheiro e o formato de origem intocados. O artigo sobre serial de data Excel e numFmt cobre como os ids de data internos interagem com os sistemas de datas 1900 e 1904
procedure LoadBinaryWorkbook(Book: TXLSXWorkbook; const FileName: string);
var
I: Integer;
begin
// Entrada binária precisa de carregamento total eager; modos seletivos recusam logo
Book.MetadataOnly := False;
Book.SelectedSheets.Clear;
if Book.Open(FileName) <> 1 then
begin
for I := 0 to Book.Diagnostics.Count - 1 do
Writeln(Book.Diagnostics[I].Message);
Exit; // o workbook carregado anteriormente continua intacto
end;
// Output de fórmulas precisa de cache exceto se a gravação recalcular
Book.RecalcBeforeSave := xrmAlways;
end;
Regras práticas para XLSB em código de produção
O backend binário é seguro de confiar quando trata as suas recusas como dados e não como erros para suprimir. Algumas regras decorrem do comportamento acima
- Nomeie o formato em cada gravação binária; a extensão
.xlsbsozinha ainda produz XLSX - Corra
GetConversionReport(xlsConversionExcelBinaryWorkbook)antes de gravar workbooks editados por utilizadores e mostre as funcionalidades não suportadas ao utilizador - Deixe
MetadataOnlydesligado eSelectedSheetsvazio para entrada binária, já que o carregamento preguiçoso exigiria reter um arquivo convertido - Dê às fórmulas um resultado em cache ou recalcule antes de gravar, porque uma cache em falta recusa sob
xrmManuale sob umxrmIfDirtylimpo - Faça contas à memória para o pacote convertido completo mais o output binário; use XLSX em streaming quando a memória tiver de se manter limitada
Quando testar, compare contra o Excel através da API pública e não ao nível da ponte. As regressões da fonte predefinida e do 0.00 passaram ambas nos testes da ponte, e ambas apareceram logo num workbook escrito pelo Excel aberto e regravado através do Open e do SaveAsChecked
FAQ
O HotXLS deteta ficheiros XLSB automaticamente?
Sim. O TXLSXWorkbook.Open inspeciona o content type registrado para a parte do workbook e valida os registos binários, por isso a mesma chamada carrega XLSX e XLSB, e o SourceFormat reporta qual foi lido
Porque é que o SaveAs com um nome .xlsb ainda escreve XLSX?
O HotXLS nunca infere o formato de output a partir da extensão. Passe xlsxExcelBinaryWorkbook ao SaveAs ou ao SaveAsChecked para escrever um workbook binário
O que acontece quando um workbook usa funcionalidades fora do subconjunto XLSB?
A gravação falha antes de o destino mudar, mesmo com gravação com perdas permitida. O GetConversionReport lista as funcionalidades bloqueadoras com antecedência, e o Diagnostics explica a falha depois
O suporte XLSB, o relatório de conversão e o resto da API de workbook vêm no HotXLS, o componente de folhas de cálculo nativo para Delphi e C++Builder