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. Las dos direcciones se parecen pero no son idénticas. Desde HotXLS 2.88.229 la ruta de importación devuelve las fórmulas como fórmulas vivas junto a sus resultados en caché, y las versiones actuales también leen estilos, formatos numéricos, rangos combinados, comentarios, imágenes, gráficos y formatos condicionales. Lo que sigue sin sobrevivir son la semántica completa de OpenFormula y un puñado de detalles a nivel de paquete, así que un equipo que asuma un espejo perfecto seguirá encontrando casos límite en algún punto entre la revisión del cliente y el siguiente informe, normalmente 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, bordes, rangos combinados, anchos de columna y alturas de fila. Descarta 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ó
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é sigue omitiendo
Lea esta parte con atención antes de prometerle a nadie fidelidad de ida y vuelta. La importación de ODS en HotXLS empezó siendo una ruta ligera que conservaba solo los valores escalares y los resultados de fórmula en caché, y ha crecido mucho más allá de eso. Las versiones actuales leen content.xml junto con styles.xml y settings.xml: valores escalares y resultados de fórmula en caché, filas y columnas repetidas expandidas hacia la cuadrícula, rangos combinados, texto enriquecido, hipervínculos, comentarios, imágenes incrustadas y gráficos, estilos de celda heredados y predeterminados, formatos numéricos y de fecha, alturas de fila y anchos de columna, rangos con nombre, paneles inmovilizados, rangos de AutoFilter, validaciones de datos y formatos condicionales. Lo que OpenODS sigue sin convertir es la semántica completa de OpenFormula, las claves de protección de ODS, la geometría de los comentarios, el formato avanzado de comentarios y las características del paquete fuera del contenido de hoja de cálculo
Las fórmulas son la parte que más probablemente muerda. 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. Las versiones tempranas leían solo el valor en caché. Desde HotXLS 2.88.229, OpenODS también importa la expresión: una celda escrita con el prefijo of:= o oooc:= conserva su valor en caché y obtiene una fórmula de Excel normalizada, con las referencias entre corchetes como [.A1:.B1] y los separadores de argumentos de punto y coma convertidos a sintaxis de Excel. Desde la 2.384.32 la importación también convierte las filas de array en línea ({1;2|3;4} pasa a {1,2;3,4}) y el operador de unión de referencias ~, y desde la 2.384.55 una unión así se lee de vuelta sin un segundo par de paréntesis. Traducir OpenFormula a sintaxis de Excel sigue siendo un 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; de ahí que el valor en caché siga viniendo con la fórmula: los números que importa son exactamente los números que el remitente vio por última vez
El modo de fallo con el que hay que diseñar se ha estrechado en consecuencia. Antes de la 2.88.229, una hoja de cálculo cuyos totales eran correctos cuando LibreOffice la guardó por última vez se importaba con números correctos que ahora eran constantes: edite una celda de entrada, recalcule, y nada se movía. En las versiones actuales las fórmulas vuelven vivas, así que el riesgo restante es un constructo que el traductor no modela, lo que puede dejar una fórmula que recalcula de forma distinta a la aplicación productora mientras el valor en caché sigue mostrando el resultado del remitente. Compare los resultados recalculados con los valores en caché importados para las fórmulas de las que dependa su pipeline, y restablezca cualquier fórmula que discrepe 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 a las lagunas restantes del viaje de ida y vuelta
La exportación renderiza a partir del modelo completo de libro de trabajo en memoria: valores, fórmulas, estilos y, si los pide, gráficos e imágenes. La exportación de fórmulas se ha ido estrechando versión a versión. Desde la 2.384.55 los arrays en línea, las uniones de referencias y las referencias absolutas conservan su significado en OpenFormula; desde la 2.384.56 HotXLS declara los namespaces de sus prefijos de fórmula, de modo que LibreOffice evalúa las fórmulas en lugar de mostrar #VALUE! en cada celda con fórmula; y desde la 2.384.65 las referencias de columna y de fila completas se escriben en la forma OpenFormula [.A:.A], de modo que Excel ya no las abre como #NAME?. Los formatos condicionales siguieron: desde la 2.384.66 las reglas basadas en fórmula se escriben como formula-is(...) con su celda base, que LibreOffice reconoce, y desde la 2.384.69 las reglas de valor y de fórmula también se escriben como condiciones estándar style:map, la única forma que Excel lee, mientras que el lado de importación lee ambas formas. Un ciclo completo de .xlsx a .ods y de vuelta a .xlsx sobre una versión actual conserva por tanto valores, fórmulas, estilos y formatos condicionales; lo que aún puede perderse está en las lagunas enumeradas en la sección anterior
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // formato detectado automáticamente
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Desde HotXLS 2.88.229 las fórmulas vuelven vivas junto a sus
// resultados en caché; restablezca cualquier fórmula que el pipeline
// posterior no pueda dar por bien traducida 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:
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