Artículo técnico

HotXLS Delphi Component: ODS open, save, and round-trip in Delphi

Un backend de informes en Delphi que lleva años emitiendo .xlsx recibe un requisito nuevo: las normas de contratación de un cliente del sector público exigen salida en OpenDocument Spreadsheet, y los analistas de esa cuenta envían de vuelta sus ediciones como archivos .ods guardados desde LibreOffice. Así que ahora el mismo código tiene que escribir ODS y también leerlo. HotXLS, la biblioteca nativa de hoja de cálculo en Object Pascal de losLab para Delphi y C++Builder, gestiona ambas direcciones sin Excel ni LibreOffice instalados en ningún sitio. Lo que no hace es que las dos direcciones sean simétricas. La exportación lleva mucho más de lo que la importación recupera, y un equipo que asuma lo contrario verá cómo las fórmulas y el formato se evaporan en algún punto entre la revisión del cliente y el siguiente informe, sin ningún error al que señalar

El soporte de ODS vive en la fachada XLSX, no en la XLS

HotXLS distribuye dos jerarquías de clases independientes en un solo paquete: TXLSWorkbook en la unidad lxHandle para archivos .xls binarios BIFF8, y TXLSXWorkbook en la unidad lxHandleX para paquetes .xlsx OOXML. Todo punto de entrada de OpenDocument - OpenODS, SaveAsODS, GetODSSheetNames - cuelga de TXLSXWorkbook. La ubicación no es arbitraria. Un paquete ODS, tal como lo especifica OASIS ODF 1.3, es un archivo zip que lleva un miembro mimetype, un manifiesto, y un cuerpo content.xml, lo que lo convierte en un primo estructural del zip OOXML; BIFF8 es un flujo de registros binario de los años noventa sin nada en común

Esa ubicación tiene un filo práctico: un libro de trabajo .xls heredado no puede convertirse en .ods en una sola llamada. Primero se puentea el contenido BIFF hacia el modelo XLSX, con SaveXLSWorkbookAsXLSX de la unidad lxXlsxExport, se reabre el resultado a través de TXLSXWorkbook, y luego se exporta desde ahí. El puente no es sin pérdidas, y merece la pena conocer sus lagunas antes de construir sobre él. Copia valores, fórmulas, formatos numéricos, fuentes, rellenos, y anchos de columna. Descarta bordes, rangos combinados, comentarios, gráficos, y formato condicional. Un origen .xls con formato intenso llegará a ODS con un aspecto más simple del que tenía al salir, y esa es una propiedad del puente, no del escritor de ODS

La detección en el lado de importación es automática. El método Open normal reconoce un paquete ODS por su miembro mimetype, recurriendo a una comprobación de content.xml de nivel superior cuando ese miembro está ausente, así que una ruta de código genérica de "abrir lo que sea que el usuario haya subido" no necesita ningún rastreo de extensión propio. Tras abrir, la propiedad SourceFormat reporta qué rama se activó

Diagrama de la disposición de clases de HotXLS en Delphi donde cada punto de entrada ODS vive en TXLSXWorkbook y un puente SaveXLSWorkbookAsXLSX transporta el contenido .xls BIFF8
Todo punto de entrada de OpenDocument cuelga de TXLSXWorkbook, y un .xls heredado llega a ODS solo a través del puente con pérdida de BIFF a XLSX

Exportar a ODS con TODSExportOptions

La propia llamada de exportación es una sola línea; el objeto de opciones que la rodea lleva las decisiones sobre las que un revisor preguntará más tarde:

var
  Book: TXLSXWorkbook;
  Opts: TODSExportOptions;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('quarterly-report.xlsx');
    Opts := TODSExportOptions.Create;        // el llamador es propietario y lo libera
    try
      Opts.Generator := 'ReportService 4.2'; // sobrescribe meta:generator
      Opts.IncludeCharts := True;
      Opts.IncludeImages := True;
      Book.SaveAsODS('quarterly-report.ods', Opts);
    finally
      Opts.Free;
    end;
  finally
    Book.Free;
  end;
end;

El objeto de opciones es propiedad del llamador. HotXLS no lo liberará, razón por la cual el try..finally interior está ahí y no es opcional. Las dos propiedades que cambian la salida, en lugar de simplemente etiquetarla, merecen una mirada más de cerca. Establecer IncludeCharts := False hace más que ocultar los gráficos: elimina los subdocumentos de gráfico y sus entradas de manifiesto del paquete, que es exactamente lo que se quiere cuando el consumidor es un pipeline de datos que tropezaría con ellos. Generator sobrescribe la cadena meta:generator de ODF, que de otro modo dice HotXLS/<version>; sobrescríbala cuando alguna herramienta posterior identifique a los productores de archivos para dirigir el soporte. Si nada de eso se aplica, omita el objeto de opciones por completo. Llamar a SaveAs(FileName, xlsxOpenDocumentSpreadsheet) es lo mismo que SaveAsODS con los valores por defecto, y las sobrecargas de stream en ambos le permiten escribir el paquete directamente en una respuesta HTTP sin archivo temporal

Qué lee la ruta de importación, y qué omite deliberadamente

Lea esta parte con atención antes de prometerle a nadie fidelidad de ida y vuelta. La importación de ODS en HotXLS es deliberadamente una ruta ligera. Preserva los valores escalares de celda y el resultado en caché que cada fórmula llevaba en el momento de guardar, y expande las filas y columnas repetidas hacia la cuadrícula. No traslada los estilos, las expresiones de fórmula de ODS, ni los dibujos

La elección de la fórmula es la que más probablemente muerda, y se tomó a propósito. Una celda ODF almacena dos cosas una junto a otra: la expresión de la fórmula, escrita en el dialecto OpenFormula definido en ODF 1.3 Parte 4, y el último valor que la aplicación productora calculó para ella. Traducir OpenFormula a la sintaxis de fórmulas de Excel es su propio problema de conversión de dialecto, con casos límite reales en torno a los vocabularios de funciones, la sintaxis de referencias, y los modelos de error. Leer en cambio el valor en caché evita toda esa clase de mala traducción silenciosa, así que los números que importa son exactamente los números que el remitente vio por última vez. El coste es que llegan como números, no como las fórmulas vivas que los produjeron

El modo de fallo con el que hay que diseñar se deduce directamente: una hoja de cálculo cuyos totales eran correctos cuando LibreOffice la guardó por última vez se importa con números correctos, pero esos números ahora son constantes. Edite una celda de entrada, recalcule, y nada se mueve - la fórmula ha desaparecido, solo queda su resultado final. Si el flujo de trabajo necesita fórmulas vivas después de importar, restablézcalas mediante programación a partir de sus propias reglas de negocio a través de Cell.Formula, que en la fachada XLSX toma la expresión sin un signo igual inicial

Diseñar en torno al viaje de ida y vuelta asimétrico

La exportación renderiza a partir del modelo completo de libro de trabajo en memoria: valores, estilos, y, si los pide, gráficos e imágenes. La importación devuelve solo valores. Así que el tramo de .xlsx a .ods es de alta fidelidad, y el tramo de .ods a .xlsx trae de vuelta valores y resultados en caché pero sin estilo y sin fórmulas vivas. Encadene los dos y la asimetría se acumula. Un ciclo completo de .xlsx a .ods y de vuelta a .xlsx escribe todo fielmente a la salida y pierde los estilos y las fórmulas a la entrada, aunque nada haya salido mal en ninguno de los dos pasos

Diagrama del viaje de ida y vuelta ODS asimétrico de HotXLS desde Delphi: exportación con fidelidad completa desde el modelo de libro en memoria y una importación de solo valores que deja las fórmulas como constantes
Export renderiza el modelo completo en memoria mientras que la importación devuelve valores y resultados en caché, de modo que un ciclo completo de .xlsx a .ods a .xlsx pierde silenciosamente estilos y fórmulas vivas
Book := TXLSXWorkbook.Create;
try
  Book.Open('vendor-revision.ods');          // formato detectado automáticamente
  if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
  begin
    // Los valores y los resultados de fórmula en caché están presentes tras una
    // importación de ODS; los estilos y las fórmulas vivas no lo están. Reconstruya
    // lo que necesite el pipeline posterior antes de guardar.
    Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
    Book.SaveAs('vendor-revision.xlsx');
  end;
finally
  Book.Free;
end;

El patrón arquitectónico que se desprende de esto: trate los archivos .ods entrantes como fuentes de datos, no como documentos para editar in situ. Mantenga el libro de trabajo canónico en .xlsx, lea los valores de las revisiones de los clientes, y emita ODS fresco bajo demanda a partir de la copia canónica. La verificación pertenece a ambos bandos - abra los archivos exportados en LibreOffice Calc, el consumidor de referencia de ODF, y en Excel, que lleva años leyendo ODS pero discrepa con LibreOffice en los límites del soporte de gráficos y estilos. El número de hojas, un puñado de celdas clave, y la presencia de gráficos constituyen una comprobación de humo suficiente por perfil de exportación

Clasificar un archivo ODS antes de comprometerse a una importación

Cuando un endpoint acepta subidas, listar los nombres de hoja es mucho más barato que un análisis completo y detecta sorpresas estructurales pronto:

Diagrama de la puerta de triaje de cargas de HotXLS en Delphi donde GetODSSheetNames rechaza paquetes ODS ilegibles y hojas ausentes antes de que corra una importación completa
Una sonda GetODSSheetNames cuesta mucho menos que un análisis completo y atrapa el fallo de hoja renombrada mientras el error aún puede nombrar el archivo
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
  if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
    raise Exception.Create('not a readable ODS package');
  if Names.IndexOf('Data') < 0 then
    raise Exception.Create('revision is missing the Data sheet');
finally
  Book.Free;
  Names.Free;
end;

La convención de retorno hace tropezar a la gente: las llamadas de HotXLS generalmente devuelven un recuento positivo o 1 en caso de éxito y -1 en caso de fallo, vaciando la lista cuando fallan, así que compruebe <= 0 en lugar de comparar contra un valor positivo específico. GetODSSheetNames ni reinicia ni rellena la instancia del libro de trabajo, así que un único objeto de sondeo puede examinar todo un directorio de archivos entrantes. Comprobaciones estructurales como esta detectan el fallo del mundo real más común - un analista que renombra o elimina una hoja antes de enviar la revisión de vuelta - en la puerta, donde el mensaje de error todavía puede nombrar el archivo y la hoja que falta en lugar de aparecer como una referencia nil tres capas más abajo

Si está construyendo un pipeline de conversión más amplio en torno a esto, el patrón de banco de trabajo de auditoría y conversión de libros muestra cómo inventariar las características de un archivo antes de elegir un formato de destino, y la guía de rendimiento con libros de trabajo grandes mantiene las exportaciones por lotes dentro de límites de memoria razonables

HotXLS es una biblioteca nativa de hoja de cálculo para Delphi y C++Builder con código fuente completo; la lista completa de funciones y los detalles de licencia están en la página de producto de HotXLS Delphi Component