Artículo técnico

Leer y escribir workbooks binarios XLSB en Delphi

HotXLS lee y escribe archivos Excel Binary Workbook (XLSB) desde Delphi a través de la misma fachada TXLSXWorkbook que se usa para XLSX: Open detecta un workbook binario desde el contenido del paquete, y SaveAs o SaveAsChecked con xlsxExcelBinaryWorkbook escribe uno. El backend cubre un subset validado y rechaza todo lo que no puede representar antes de tocar el destino

Esa última cláusula es la parte que vale leer despacio. XLSB parece una variación simple de XLSX, y en términos de empaquetado lo es. El problema vive en los detalles que los dos formatos codifican distinto, y en lo que una biblioteca asume silenciosamente cuando hace de puente entre uno y otro. El bug más instructivo de este backend no fue para nada un record malformado. Fue una fuente default que desapareció, y una columna de fechas que se volvió #### en Excel aunque el valor, el código de formato y el ancho de columna estaban todos correctos

¿Qué es un archivo XLSB y en qué difiere de XLSX?

Un archivo XLSB es un paquete OPC con exactamente la estructura de partes de un workbook XLSX, pero las partes de workbook, worksheet, estilos y shared-strings almacenan records binarios en lugar de XML. El formato está especificado en [MS-XLSB], y la codificación de records suele llamarse BIFF12. Usted sigue teniendo [Content_Types].xml, partes de relationship y un contenedor ZIP; lo que cambia es que xl/workbook.bin reemplaza a xl/workbook.xml, xl/worksheets/sheet1.bin reemplaza el XML del worksheet, y así sucesivamente

Cada record BIFF12 arranca con un header compacto de longitud variable. El tipo de record toma uno o dos bytes, siete bits por byte con el bit alto como flag de continuación, así que un tipo de record cabe en 14 bits. El tamaño del payload sigue en uno a cuatro bytes usando el mismo esquema de siete bits. Las cadenas son un conteo de caracteres de 32 bits seguido de code units UTF-16. Comparado con BIFF8 este es un layout mucho más limpio: sin partición CONTINUE de 8224 bytes, sin flags de cadena comprimida-versus-ancha a media estructura. Comparado con XLSX se salta el tokenizado XML por completo, que es por lo que los archivos XLSB grandes tienden a pesar menos en disco y a abrirse más rápido en Excel

Un detalle de empaquetado hace tropezar al código de detección. Excel registra la parte de workbook XLSB con el content type application/vnd.ms-excel.sheet.binary.macroEnabled.main, incluso para workbooks que no contienen macros. HotXLS revisa ese registro en [Content_Types].xml primero y solo cae de vuelta a la extensión de parte .bin cuando el content type está ausente, y cualquiera de las dos rutas igual tiene que pasar el validador estricto de records. Las reglas generales sobre nombres de partes, relationships y content types son las mismas que cubre por qué Excel repara un XLSX válido y las reglas de paquete OPC detrás de ello

¿Cuándo elegir XLSB en lugar de XLSX?

Elija XLSB cuando un sistema aguas abajo lo exige o cuando usted intercambia workbooks grandes cargados de valores con usuarios de Excel a quienes les importa el tiempo de apertura; de lo contrario XLSX sigue siendo el default más seguro. XLSX es un estándar ISO (ISO/IEC 29500) con amplio soporte de herramientas, es inspeccionable con una herramienta ZIP y un editor de texto, y cada producto de hojas de cálculo lo lee. XLSB es un formato de Microsoft con especificación publicada pero un ecosistema más angosto

En la práctica los pedidos vienen de dos direcciones. Algunos pipelines de reportes y archivos se estandarizaron en .xlsb hace años y rechazan cualquier otra cosa. Otros equipos generan workbooks con cientos de miles de celdas numéricas y encontraron que los usuarios abren la versión binaria notablemente más rápido. Ambas son razones legítimas. "Suena más eficiente" no lo es, porque el backend binario de HotXLS pone en staging el paquete completo en memoria, así que no es la herramienta para exportaciones de servidor con memoria acotada. Para esas, el escritor XLSX de streaming es el camino correcto

Abrir y guardar XLSB con TXLSXWorkbook

TXLSXWorkbook.Open no necesita ninguna pista de formato: la misma llamada carga XLSX, XLSM o XLSB, y después de una carga binaria exitosa TXLSXWorkbook.SourceFormat reporta xlsxExcelBinaryWorkbook. La escritura es explícita. SaveAs(FileName) sin formato siempre produce un workbook Open XML, incluso cuando el nombre de archivo termina en .xlsb, así que la salida binaria siempre nombra el formato

uses
  SysUtils, lxHandleX;

procedure ConvertToXlsb(const Source, Target: string);
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // Open detecta XLSX, XLSM o XLSB desde el paquete mismo
    if Book.Open(Source) <> 1 then
      raise Exception.Create('Input is unsupported or invalid');

    // La extensión sola nunca selecciona salida binaria
    if Book.SaveAsChecked(Target, xlsxExcelBinaryWorkbook) <> 1 then
      raise Exception.Create('XLSB output is unsupported or failed');
  finally
    Book.Free;
  end;
end;

La salida por stream va por SaveAs(Stream, xlsxExcelBinaryWorkbook) o por la sobrecarga de stream de SaveAsChecked. Internamente ambas rutas van a los métodos privados SaveAsXlsbStream y SaveAsXlsbFile; usted nunca los llama directamente. La salida a archivo se pone en staging en un archivo temporal hermano y se compromete con un reemplazo final único. Un destino de stream debe ser legible y con seek, porque el escritor toma una foto de sus bytes, tamaño y posición y los restaura si la copia final falla. TXLSXWorkbook.LastSaveUsedStreaming reporta False después de un guardado binario, lo cual es exacto: el paquete entero existe en memoria antes de que un solo byte llegue a su destino

¿Por qué un subset validado en lugar de fidelidad completa?

HotXLS no mantiene un segundo modelo de objetos para XLSB. Al abrir, XlsbToOpenXml convierte el paquete binario en un paquete Open XML en memoria y el parser XML ordinario lo carga; al guardar, el escritor XML construye un paquete y OpenXmlToXlsb lo codifica como records binarios. Cada función por lo tanto tiene que sobrevivir a dos traducciones, y una función que el bridge no puede traducir exactamente se rechaza en lugar de aproximarse

El conjunto soportado a partir de HotXLS 2.384.113 es lo bastante amplio para la mayoría de los workbooks de datos: valores tipados, shared e inline strings, fuentes comunes, fills, bordes y formatos numéricos, alturas de fila y anchos de columna, fórmulas de celda ordinarias con resultados cacheados, defined names, celdas combinadas, hyperlinks, paneles congelados y divididos, rangos AutoFilter con criterios personalizados simples, y tablas de worksheet ordinarias. Fuera de él quedan las shared y array formulas, las future y user-defined functions, las referencias a workbooks externos, los charts y drawings, los PivotTables y slicers, VBA, el formato condicional, la validación de datos y los ajustes de cálculo no default como cálculo manual o iteración

El rechazo es obligatorio, no consultivo. TXLSXWorkbook.GetConversionReport(xlsConversionExcelBinaryWorkbook) lista las funciones no soportadas antes de que usted guarde, y esas entradas hacen fallar el guardado incluso con AAllowLossy = True o un SaveAs sin chequear. Eso es deliberado. Tirar silenciosamente un PivotTable de una copia binaria es el tipo de pérdida que nadie nota hasta que el workbook está frente a un 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;   // quien llama es dueño del reporte
  end;
end;

El reporte cubre el modelo vivo. La validación final del paquete sigue siendo la última compuerta para las cosas que el modelo nunca ve, como records malformados o atributos desconocidos en el archivo fuente, así que un reporte limpio seguido de un guardado fallido es posible y el resultado del guardado es sobre lo que usted actúa

¿Por qué desaparecieron la fuente default y el formato 0.00?

La fuente default y los formatos numéricos integrados desaparecieron porque el lector de estilos XML hizo dos suposiciones que se cumplen para los archivos que HotXLS escribe él mismo pero no para los workbooks que escribe Excel. El bridge preservó ambos recursos perfectamente; la pérdida ocurrió una capa arriba, en la ruta pública de Open. Ese hueco es la lección principal de este backend: un bridge de formatos correcto no garantiza un round trip público fiel

La primera suposición concernía a fonts[0]. Cuando HotXLS guarda XLSX, escribe una fuente default implícita en el slot cero, y su lector XML se salta ese slot para que la colección Fonts orientada al usuario arranque en el índice cero. Un XLSB escrito por Excel carga su propia default explícita en ese slot, y saltárselo tiraba esa elección. La segunda suposición concernía a los formatos numéricos. El lector solo mapeaba valores numFmtId personalizados (164 en adelante) hacia NumberFormats; los ids integrados quedaban como cero. Una celda en blanco con estilo que cargaba el formato integrado 2, que es 0.00, volvía como General

El arreglo vive por completo en la ruta de importación binaria. Después de que el paquete completo pasa el preflight, la apertura XLSB fija un modo de estilos privado solo para el parseo XML síncrono. En ese modo el lector carga cada fuente incluido el slot cero y desplaza cada fontId en uno, convierte cada id integrado soportado en un código de formato explícito en el modelo, y sigue usando el numFmtId original para la detección de fechas, así que un formato de fecha integrado conserva su semántica de fecha. Un bloque finally siempre limpia el modo, y una Open posterior plana de XLSX u ODS limpia también el estado de fuente default retenida

¿Cómo afecta fonts[0] a los anchos de columna de Excel?

Excel calcula los anchos de columna a partir de la fuente default del workbook en fonts[0], no a partir de las fuentes aplicadas a las celdas de esa columna. El width de columna se almacena en unidades de carácter del ancho máximo de dígito de la fuente default, así que cambiar fonts[0] cambia el ancho en píxeles de cada columna, incluidas las columnas cuyas celdas usan todas una fuente distinta

Eso produjo el síntoma más confuso de toda la investigación. El workbook re-guardado tenía valores de celda idénticos, códigos de formato idénticos y atributos width idénticos, la fuente de la celda y el estilo Normal apuntaban ambos a la fuente preservada, y Excel todavía renderizaba una columna de fechas como ####. El fonts[0] escrito había revertido a Calibri 11, el ancho máximo de dígito se encogió, y las fechas ya no cupieron. HotXLS ahora conserva la fuente default del origen y la escribe de vuelta como fonts[0], cayendo a Calibri 11 solo para workbooks que nunca tuvieron una fuente binaria de origen. La aritmética completa de anchos está en ancho de columna de Excel y max digit width (MDW) en Delphi

Los formatos integrados dependientes de locale se rechazan, no se adivinan

Un id de formato numérico integrado sin código de formato explícito se acepta solo cuando su significado está fijado; los ids dependientes de locale se rechazan durante el preflight del paquete. HotXLS mapea los ids 0 a 22, 37 a 40 y 45 a 49 a códigos conocidos como 0.00, 0%, mm:ss y @. Los ids restantes por debajo de 164 pertenecen a formatos cuyo render depende de la configuración regional de la instalación de Excel, y la importación los rechaza con el mensaje "A locale-dependent number format requires an explicit format code"

Inventar un código para esos ids sería peor que fallar. Un patrón de fecha de Asia Oriental adivinado se ve plausible, hace round trip sin error y muestra lo incorrecto para la mitad de sus usuarios. El momento también importa: el chequeo corre en el preflight del paquete completo, antes de que el workbook existente se limpie, así que un archivo rechazado deja el modelo cargado previamente, el nombre de archivo y el formato de fuente intactos. El artículo sobre serial de fecha de Excel y numFmt cubre cómo los ids de fecha integrados interactúan con los sistemas de fecha 1900 y 1904

procedure LoadBinaryWorkbook(Book: TXLSXWorkbook; const FileName: string);
var
  I: Integer;
begin
  // La entrada binaria necesita carga completa eager; los modos selectivos rechazan de entrada
  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;   // el workbook cargado anteriormente sigue intacto
  end;

  // La salida de fórmulas necesita un cache a menos que el guardado recalcule
  Book.RecalcBeforeSave := xrmAlways;
end;

Reglas prácticas para XLSB en código de producción

El backend binario es seguro de usar como base cuando usted trata sus rechazos como datos y no como errores que suprimir. Algunas reglas se siguen del comportamiento de arriba

  • Nombre el formato en cada guardado binario; la extensión .xlsb sola sigue produciendo XLSX
  • Corra GetConversionReport(xlsConversionExcelBinaryWorkbook) antes de guardar workbooks editados por usuarios y muestre las funciones no soportadas al usuario
  • Deje MetadataOnly apagado y SelectedSheets vacío para entrada binaria, ya que la carga lazy exigiría retener un archivo convertido
  • Dé a las fórmulas un resultado cacheado o recalcule antes de guardar, porque un cache faltante se rechaza bajo xrmManual y bajo un xrmIfDirty limpio
  • Presupueste memoria para el paquete convertido completo más la salida binaria; use XLSX de streaming cuando la memoria debe quedarse acotada

Cuando pruebe, compare contra Excel a través de la API pública en lugar de a nivel del bridge. Las regresiones de fuente default y de 0.00 pasaron ambas las pruebas del bridge, y ambas aparecieron de inmediato en un workbook escrito por Excel abierto y re-guardado vía Open y SaveAsChecked

Preguntas frecuentes

¿HotXLS detecta archivos XLSB automáticamente?

Sí. TXLSXWorkbook.Open inspecciona el content type registrado para la parte del workbook y valida los records binarios, así que la misma llamada carga XLSX y XLSB, y SourceFormat reporta cuál se leyó

¿Por qué SaveAs con un nombre .xlsb todavía escribe XLSX?

HotXLS nunca infiere el formato de salida desde la extensión. Pase xlsxExcelBinaryWorkbook a SaveAs o a SaveAsChecked para escribir un workbook binario

¿Qué pasa cuando un workbook usa funciones fuera del subset XLSB?

El guardado falla antes de que el destino cambie, incluso con el guardado con pérdida permitido. GetConversionReport lista por adelantado las funciones bloqueantes, y Diagnostics explica la falla después

El soporte XLSB, el reporte de conversión y el resto de la API de workbooks vienen en HotXLS, el componente de hojas de cálculo nativo para Delphi y C++Builder