Artigo Técnico

Ler e escrever pastas de trabalho XLSB em Delphi

O HotXLS lê e escreve arquivos Excel Binary Workbook (XLSB) a partir do Delphi pela mesma fachada TXLSXWorkbook usada para XLSX: o Open detecta uma pasta de trabalho binária pelo conteúdo do pacote, e o SaveAs ou o SaveAsChecked com xlsxExcelBinaryWorkbook escreve uma. O backend cobre um subconjunto validado e rejeita qualquer coisa que não consiga representar antes de tocar no destino

Essa última oração é a parte que vale ler devagar. O XLSB parece uma variação simples do XLSX, e em termos de empacotamento é. O problema mora nos detalhes que os dois formatos codificam de forma diferente, e no que uma biblioteca assume em silêncio ao fazer a ponte de um para o outro. O bug mais instrutivo deste backend não foi um registro malformado de forma alguma. Foi uma fonte padrão que sumiu, e uma coluna de datas que virou #### no Excel mesmo com o valor, o código de formato e a largura da coluna todos corretos

O que é um arquivo XLSB, e como ele difere do XLSX?

Um arquivo XLSB é um pacote OPC com exatamente a estrutura de parts de uma pasta de trabalho XLSX, mas as parts de workbook, worksheet, style e shared-string guardam registros binários em vez de XML. O formato é especificado no [MS-XLSB], e a codificação de registros costuma ser chamada BIFF12. Você ainda tem [Content_Types].xml, parts de relationship e um contêiner ZIP; o que muda é que xl/workbook.bin substitui xl/workbook.xml, xl/worksheets/sheet1.bin substitui o XML da worksheet, e por aí vai

Cada registro BIFF12 começa com um header compacto de comprimento variável. O tipo de registro ocupa um ou dois bytes, sete bits por byte com o bit alto como flag de continuação, então um tipo de registro cabe em 14 bits. O tamanho do payload vem em um a quatro bytes usando o mesmo esquema de sete bits. Strings são uma contagem de caracteres de 32 bits seguida de code units UTF-16. Comparado com o BIFF8, esse é um layout bem mais limpo: sem divisão de CONTINUE de 8224 bytes, sem flags de string comprimida versus wide no meio do registro. Comparado com o XLSX, ele pula o tokenizing de XML por completo, que é o motivo de arquivos XLSB grandes tenderem a ser menores no disco e mais rápidos para o Excel abrir

Um detalhe de empacotamento derruba código de detecção. O Excel registra a part de workbook XLSB com o content type application/vnd.ms-excel.sheet.binary.macroEnabled.main, mesmo para pastas de trabalho que não contêm macros. O HotXLS verifica esse registro no [Content_Types].xml primeiro e só cai para a extensão de part .bin quando o content type está ausente, e qualquer um dos caminhos ainda precisa passar pelo validador estrito de registros. As regras gerais sobre nomes de parts, relationships e content types são as mesmas cobertas em por 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 exige ou quando você troca pastas de trabalho grandes e cheias de valores com usuários do Excel que se importam com o tempo de abertura; caso contrário, o XLSX continua sendo o padrão mais seguro. O XLSX é um padrão ISO (ISO/IEC 29500) com amplo suporte de ferramentas, é inspecionável com uma ferramenta ZIP e um editor de texto, e todo produto de planilha o lê. O XLSB é um formato Microsoft com especificação publicada, mas ecossistema mais estreito

Na prática, os pedidos vêm de duas direções. Alguns pipelines de relatórios e arquivos padronizaram em .xlsb anos atrás e rejeitam qualquer outra coisa. Outras equipes geram pastas de trabalho com centenas de milhares de células numéricas e descobriram que os usuários abrem a versão binária visivelmente mais rápido. Ambos são motivos legítimos. "Parece mais eficiente" não é, porque o backend binário do HotXLS prepara o pacote completo na memória, então ele não é a ferramenta para exportações de servidor com memória limitada. Para essas, o escritor XLSX em streaming é o caminho certo

Abrindo e salvando XLSB com 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 sempre produz uma pasta de trabalho Open XML, mesmo quando o nome do arquivo termina em .xlsb, então a saída binária sempre nomeia o formato

uses
  SysUtils, lxHandleX;

procedure ConvertToXlsb(const Source, Target: string);
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // O Open detecta XLSX, XLSM ou XLSB pelo próprio pacote
    if Book.Open(Source) <> 1 then
      raise Exception.Create('Input is unsupported or invalid');

    // A extensão sozinha nunca seleciona saída binária
    if Book.SaveAsChecked(Target, xlsxExcelBinaryWorkbook) <> 1 then
      raise Exception.Create('XLSB output is unsupported or failed');
  finally
    Book.Free;
  end;
end;

A saída em stream passa pelo SaveAs(Stream, xlsxExcelBinaryWorkbook) ou pelo overload de stream do SaveAsChecked. Internamente ambos encaminham para os métodos privados SaveAsXlsbStream e SaveAsXlsbFile; você nunca os chama diretamente. A saída em arquivo é preparada em um arquivo temporário irmão e confirmada com uma substituição final. Um destino em stream precisa ser legível e posicionável, porque o escritor tira um snapshot dos seus bytes, tamanho e posição e os restaura se a cópia final falhar. O TXLSXWorkbook.LastSaveUsedStreaming reporta False depois de um salvamento binário, o que é preciso: o pacote inteiro existe na memória antes de um único byte chegar ao seu destino

Por que 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 em um pacote Open XML na memória e o parser XML comum o carrega; no salvamento, o escritor XML constrói um pacote e o OpenXmlToXlsb o codifica como registros binários. Todo recurso, portanto, precisa sobreviver a duas traduções, e um recurso que a ponte não consegue traduzir exatamente é rejeitado em vez de aproximado

O conjunto suportado a partir do HotXLS 2.384.113 é amplo o suficiente para a maioria das pastas de trabalho de dados: valores tipados, strings compartilhadas e inline, fontes comuns, fills, bordas e formatos numéricos, alturas de linha e larguras de coluna, fórmulas de célula comuns com resultados em cache, defined names, células mescladas, hyperlinks, painéis congelados e divididos, intervalos de AutoFilter com critérios customizados simples e tabelas de worksheet comuns. Fora dele ficam fórmulas compartilhadas e de array, future functions e funções definidas pelo usuário, referências a pastas de trabalho externas, charts e drawings, PivotTables e slicers, VBA, formatação condicional, validação de dados e configurações de cálculo fora do padrão como cálculo manual ou iteração

A rejeição é obrigatória, não consultiva. O TXLSXWorkbook.GetConversionReport(xlsConversionExcelBinaryWorkbook) lista os recursos não suportados antes de você salvar, e essas entradas fazem o salvamento falhar mesmo com AAllowLossy = True ou um SaveAs sem verificação. Isso é deliberado. Deixar cair uma PivotTable em silêncio de uma cópia binária é o tipo de perda que ninguém nota até a pasta de trabalho estar na 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 é de posse de quem chama
  end;
end;

O relatório cobre o modelo vivo. A validação final do pacote ainda é o último portão para coisas que o modelo nunca vê, como registros malformados ou atributos desconhecidos no arquivo de origem, então um relatório limpo seguido de um salvamento que falha é possível, e o resultado do salvamento é no que você age

Por que a fonte padrão e o formato 0.00 desapareceram?

A fonte padrão e os formatos numéricos internos desapareceram porque o leitor de estilos XML fez duas suposições que valem para arquivos que o próprio HotXLS escreve, mas não para pastas de trabalho que o Excel escreve. A ponte preservou os dois recursos perfeitamente; a perda aconteceu uma camada acima, no caminho público do 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 ao fonts[0]. Quando o HotXLS salva XLSX, ele escreve uma fonte padrão implícita no slot zero, e o seu leitor XML pula esse slot para que a coleção Fonts voltada ao usuário comece no índice zero. Um XLSB escrito pelo Excel carrega o seu próprio padrão explícito nesse slot, e pular esse slot jogou essa escolha fora. A segunda suposição dizia respeito aos formatos numéricos. O leitor só mapeava valores customizados de numFmtId (164 e acima) para NumberFormats; ids internos ficavam como zero. Uma célula em branco estilizada carregando o formato interno 2, que é 0.00, voltava como General

A correção mora inteiramente no caminho de importação binária. Depois que o pacote completo passa pelo preflight, a abertura de XLSB define um modo de estilo privado apenas para o parse XML síncrono. Nesse modo, o leitor carrega toda fonte incluindo o slot zero e desloca cada fontId em um, converte cada id interno suportado em um código de formato explícito no modelo, e ainda usa o numFmtId original para detecção de datas, de modo que um formato de data interno mantém a sua semântica de data. Um bloco finally sempre limpa o modo, e um Open XLSX ou ODS comum posterior também limpa o estado de fonte padrão retido

Como o fonts[0] afeta as larguras de coluna do Excel?

O Excel calcula as larguras de coluna a partir da fonte padrão da pasta de trabalho no fonts[0], e não das fontes aplicadas às células daquela coluna. A width da coluna é guardada em unidades de caractere da largura máxima de dígito da fonte padrão, então mudar o fonts[0] muda a largura em pixels de toda coluna, incluindo colunas cujas células usam todas uma fonte diferente

Isso produziu o sintoma mais confuso de toda a investigação. A pasta de trabalho re-salva tinha valores de célula idênticos, códigos de formato idênticos e atributos width idênticos, a fonte da célula 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 revertido para Calibri 11, a largura máxima de dígito encolheu, e as datas não cabiam mais. O HotXLS agora mantém a fonte padrão de origem e a escreve de volta como fonts[0], caindo para Calibri 11 só para pastas de trabalho que nunca tiveram uma origem binária. A aritmética completa de largura está em largura de coluna do Excel e max digit width (MDW) em Delphi

Formatos internos dependentes de local são rejeitados, não chutados

Um id de formato numérico interno sem um código de formato explícito é aceito apenas quando o seu significado é fixo; ids dependentes de local são rejeitados durante o preflight do pacote. O HotXLS mapeia ids 0 a 22, 37 a 40 e 45 a 49 para códigos conhecidos como 0.00, 0%, mm:ss e @. Os ids restantes abaixo de 164 pertencem a formatos cuja renderização depende das configurações regionais da instalação do Excel, e a importação os recusa 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 chutado parece plausível, faz round-trip sem erro e exibe a coisa errada para metade dos seus usuários. O momento também importa: a verificação roda no preflight do pacote completo, antes de a pasta de trabalho existente ser limpa, então um arquivo rejeitado deixa o modelo carregado anteriormente, o nome do arquivo e o formato de origem intactos. O artigo sobre serial de data do Excel e numFmt cobre como ids internos de data 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 completo ansioso; modos seletivos recusam de cara
  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;   // a pasta de trabalho carregada anteriormente continua intacta
  end;

  // Saída de fórmula precisa de cache a menos que o salvamento recalcule
  Book.RecalcBeforeSave := xrmAlways;
end;

Regras práticas para XLSB em código de produção

O backend binário é seguro de confiar quando você trata as rejeições dele como dados e não como erros a suprimir. Algumas regras decorrem do comportamento acima

  • Nomeie o formato em todo salvamento binário; a extensão .xlsb sozinha ainda produz XLSX
  • Rode GetConversionReport(xlsConversionExcelBinaryWorkbook) antes de salvar pastas de trabalho editadas por usuários e exponha os recursos não suportados ao usuário
  • Deixe MetadataOnly desligado e SelectedSheets vazio para entrada binária, já que o lazy loading exigiria reter um arquivo convertido
  • Dê às fórmulas um resultado em cache ou recalcule antes de salvar, porque um cache ausente é rejeitado sob xrmManual e sob um xrmIfDirty limpo
  • Reserve memória para o pacote convertido completo mais a saída binária; use XLSX em streaming quando a memória precisar ficar limitada

Quando você testa, compare contra o Excel através da API pública e não no nível da ponte. As regressões da fonte padrão e do 0.00 passaram ambas nos testes da ponte, e ambas apareceram imediatamente em uma pasta de trabalho escrita pelo Excel, aberta e re-salva pelo Open e SaveAsChecked

FAQ

O HotXLS detecta arquivos XLSB automaticamente?

Sim. O TXLSXWorkbook.Open inspeciona o content type registrado para a part de workbook e valida os registros binários, então a mesma chamada carrega XLSX e XLSB, e o SourceFormat reporta qual foi lido

Por que o SaveAs com um nome de arquivo .xlsb ainda escreve XLSX?

O HotXLS nunca infere o formato de saída pela extensão. Passe xlsxExcelBinaryWorkbook para o SaveAs ou o SaveAsChecked para escrever uma pasta de trabalho binária

O que acontece quando uma pasta de trabalho usa recursos fora do subconjunto XLSB?

O salvamento falha antes de o destino mudar, mesmo com salvamento com perdas permitido. O GetConversionReport lista os recursos bloqueadores com antecedência, e o Diagnostics explica a falha depois

Suporte a XLSB, o relatório de conversão e o resto da API de pastas de trabalho vêm no HotXLS, o componente de planilha nativo para Delphi e C++Builder