Un backend de reportes en Delphi que lleva años produciendo archivos .xlsx recibe un nuevo requisito: las reglas de compra de un cliente del sector público exigen salida OpenDocument Spreadsheet, y los analistas de ese cliente devuelven revisiones como archivos .ods guardados desde LibreOffice. HotXLS, la biblioteca nativa Object Pascal de losLab para hojas de cálculo en Delphi y C++Builder, cubre ambas direcciones de ese intercambio sin que Excel ni LibreOffice estén instalados en ningún lado. Sin embargo, las dos direcciones no son simétricas, y fingir que lo son es la forma en que los datos desaparecen silenciosamente entre sistemas. Este artículo mapea exactamente qué lee la ruta de código ODS, qué escribe y cómo diseñar un round trip cuyas pérdidas se conozcan por adelantado en lugar de descubrirse en un ticket de soporte.
El soporte ODS vive en la fachada XLSX, no en la XLS
HotXLS incluye dos jerarquías de clases independientes en un solo paquete: TXLSWorkbook en la unidad lxHandle para archivos binarios BIFF8 .xls, y TXLSXWorkbook en la unidad lxHandleX para paquetes OOXML .xlsx. Cada punto de entrada de OpenDocument - OpenODS, SaveAsODS, GetODSSheetNames - cuelga de TXLSXWorkbook. La ubicación no es arbitraria. Un paquete ODS, según OASIS ODF 1.3, es un archivo zip que contiene un miembro mimetype, un manifiesto y un cuerpo content.xml, lo que lo vuelve un pariente estructural del zip OOXML; BIFF8 es un flujo de registros binarios de los años noventa y no tiene nada en común.
La consecuencia práctica: un libro .xls heredado no puede convertirse en .ods con una sola llamada. La ruta compatible consiste en pasar primero el contenido BIFF al modelo XLSX con SaveXLSWorkbookAsXLSX de la unidad lxXlsxExport, volver a abrir el resultado mediante TXLSXWorkbook y exportar desde ahí. Presupuesten los límites de fidelidad del puente durante la planeación: copia valores, fórmulas, formatos numéricos, fuentes, rellenos y anchos de columna, pero no bordes, rangos combinados, comentarios, gráficos ni formato condicional.
La detección del lado de importación es automática. El método simple Open reconoce un paquete ODS por su miembro mimetype, con una verificación alternativa de content.xml de nivel superior cuando ese miembro no existe, así que una ruta genérica de "abrir lo que el usuario subió" no necesita inspeccionar extensiones por su cuenta. Después de abrir, la propiedad SourceFormat reporta qué rama se activó.
Exportar a ODS con TODSExportOptions
La llamada de exportación en sí es de una línea; el objeto de opciones que la rodea contiene las decisiones que un revisor preguntará más adelante:
var
Book: TXLSXWorkbook;
Opts: TODSExportOptions;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-report.xlsx');
Opts := TODSExportOptions.Create; // caller owns and frees this
try
Opts.Generator := 'ReportService 4.2'; // meta:generator override
Opts.IncludeCharts := True;
Opts.IncludeImages := True;
Book.SaveAsODS('quarterly-report.ods', Opts);
finally
Opts.Free;
end;
finally
Book.Free;
end;
end;
Tres detalles merecen estar en ese listado. Primero, el objeto de opciones pertenece al llamador: HotXLS no lo libera, así que el try..finally interno importa. Segundo, IncludeCharts := False hace más que ocultar gráficos: elimina por completo los subdocumentos de gráfico y sus entradas de manifiesto del paquete, que es la forma correcta cuando el consumidor es una canalización de datos que se atragantaría con ellos. Tercero, Generator sobrescribe la cadena ODF meta:generator (el valor predeterminado es HotXLS/<version>); configúrenlo cuando las herramientas posteriores identifican productores de archivos para enrutar soporte. Llamar a SaveAs(FileName, xlsxOpenDocumentSpreadsheet) equivale a SaveAsODS con opciones predeterminadas, y existen sobrecargas de stream para entregar el paquete directamente a una respuesta HTTP sin archivo temporal.
Qué lee la ruta de importación, y qué omite deliberadamente
Esta es la sección que conviene leer dos veces antes de prometer fidelidad de round trip a alguien. La importación ODS en HotXLS es una ruta ligera: conserva valores escalares de celdas, los resultados en caché de fórmulas y expande filas y columnas repetidas dentro de la cuadrícula. No importa estilos, no importa expresiones de fórmula ODS y no importa dibujos.
La decisión sobre fórmulas merece una explicación, no una disculpa. Una celda ODF almacena tanto la expresión de fórmula, escrita en el dialecto OpenFormula definido en ODF 1.3 Parte 4, como el último valor que la aplicación productora calculó para ella. Traducir OpenFormula a la sintaxis de fórmulas de Excel es un problema de conversión de dialectos con casos límite reales: vocabularios de funciones distintos, sintaxis de referencias distinta y modelos de error distintos. Al leer el valor en caché, HotXLS evita toda esa clase de errores de traducción silenciosa y garantiza que los números importados sean los números que vio el remitente la última vez.
El modo de falla que deben diseñar se desprende directamente: una hoja cuyos totales eran correctos cuando LibreOffice la guardó por última vez se importa con números correctos, pero ahora esos números son constantes. Editen una celda de entrada, recalculen y nada se mueve: la fórmula desapareció, solo queda su resultado final. Si el flujo necesita fórmulas activas después de la importación, restablézcanlas programáticamente desde sus propias reglas de negocio mediante Cell.Formula, que en la fachada XLSX recibe la expresión sin signo igual inicial.
Diseñar alrededor del round trip asimétrico
La exportación se renderiza desde el modelo completo del libro en memoria: valores, estilos y opcionalmente gráficos e imágenes; la importación, en cambio, es solo de valores. Dicho como matriz: .xlsx a .ods tiene alta fidelidad; .ods a .xlsx trae valores y resultados en caché, pero llega sin estilos ni fórmulas activas; por lo tanto, un ciclo completo .xlsx a .ods a .xlsx pierde estilos y fórmulas en el segundo tramo aunque el primero las haya escrito fielmente.
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // format auto-detected
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Values and cached formula results are present after an ODS
// import; styles and live formulas are not. Rebuild whatever
// the downstream pipeline depends on before saving.
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 surge de esto: traten los archivos .ods entrantes como feeds de datos, no como documentos para editar en sitio. Mantengan el libro canónico en .xlsx, lean valores desde las revisiones de clientes y emitan ODS nuevo bajo demanda desde la copia canónica. La verificación corresponde en ambos bandos: abran los archivos exportados en LibreOffice Calc, el consumidor ODF de referencia, y en Excel, que lee ODS desde hace años pero discrepa con LibreOffice en los bordes del soporte de gráficos y estilos. Conteo de hojas, algunas celdas clave y presencia de gráficos forman una comprobación de humo suficiente por perfil de exportación.
Clasificar un archivo ODS antes de comprometerse con una importación
Cuando un endpoint acepta cargas, listar nombres de hojas es mucho más barato que un parseo completo y detecta temprano sorpresas estructurales:
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 confunde a más de uno: las llamadas de HotXLS generalmente devuelven un conteo positivo o 1 en caso de éxito y -1 en caso de falla, vaciando la lista al fallar, así que prueben <= 0 en vez de comparar contra un valor positivo específico. GetODSSheetNames no reinicia ni llena la instancia del libro, por lo que un solo objeto de sondeo puede revisar todo un directorio de archivos entrantes. Las comprobaciones estructurales como esta detectan la falla real más común: un analista renombra o elimina una hoja antes de devolver la revisión, en la puerta de entrada, donde el mensaje todavía puede nombrar el archivo y la hoja faltante en lugar de aparecer como una referencia nil tres capas más abajo.
Preguntas frecuentes
¿HotXLS puede convertir .xls directamente a .ods?
No en una sola llamada. Pasen el .xls a un .xlsx con SaveXLSWorkbookAsXLSX, vuélvanlo a abrir con TXLSXWorkbook y después llamen a SaveAsODS; además, consideren que el puente descarta bordes, combinaciones, comentarios y gráficos durante el camino.
¿Las fórmulas sobreviven a un round trip ODS?
La escritura de ODS conserva lo que contiene el modelo del libro. La lectura de ODS mantiene el resultado en caché de cada fórmula como una constante y descarta la expresión, así que las fórmulas activas no sobreviven al tramo de regreso.
¿Excel abrirá los archivos ODS que produce HotXLS?
Sí: Excel lee hojas de cálculo OpenDocument, pero validen la salida tanto en Excel como en LibreOffice, porque las dos aplicaciones admiten subconjuntos distintos de estilos y funciones de gráficos ODF.
Si están construyendo una canalización de conversión más amplia alrededor de esto, el patrón de mesa de auditoría y conversión de libros muestra cómo inventariar las características de un archivo antes de elegir un formato destino, y la guía de rendimiento para libros grandes mantiene las exportaciones por lotes dentro de límites de memoria razonables.
HotXLS es una biblioteca nativa para hojas de cálculo en 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 Component.