Artículo técnico

Exportar libros de Excel a CSV, TSV, HTML y RTF desde Delphi con HotXLS

El ERP aguas abajo rechazó el feed nocturno con un error de análisis en la columna de totales. El CSV se veía bien en un editor de texto hasta la fila 42, donde el campo de importe decía =SUM(D2:D41): la fórmula misma, como texto, no el número que calcula. Nada estaba roto; ese es el comportamiento documentado. La exportación CSV en HotXLS serializa el modelo de celdas tal como está, y una celda de fórmula cuyo valor nunca se calculó no tiene nada que entregar salvo el texto de la fórmula. Todo equipo que conecta las llamadas de exportación de la biblioteca a un pipeline de integración encuentra alguna versión de esto en la primera semana, así que es el lugar correcto para empezar

Por qué su CSV contiene fórmulas en lugar de números

HotXLS mantiene el texto de fórmula y el valor calculado como cosas separadas, y SaveAsCSV deliberadamente no ejecuta el motor de cálculo: una exportación no debe mutar el libro ni quedarse detenida por una cadena patológica de fórmulas. Los archivos guardados por Excel llevan resultados en caché junto a las fórmulas, así que exportarlos funciona como se espera; la trampa aparece con libros generados por su propio código, donde se escribieron fórmulas pero nunca se evaluaron. La solución es hacer que los valores existan antes de exportar, usando el mismo motor Calculate que maneja referencias entre hojas y funciones personalizadas:

Diagrama que muestra una celda de libro HotXLS de Delphi conteniendo solo texto de fórmula hasta que Book.Calculate calcula el valor, de modo que la exportación CSV emite un número en lugar de texto =SUM
SaveAsCSV serializa el modelo de celdas tal como está — sin Calculate el campo de monto lleva texto literal de fórmula y el importador lo rechaza
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('invoice-run.xlsx');
    Sheet := Book.Sheets[0];

    // Materializa los resultados de las fórmulas para que el CSV lleve números, no texto '=...'
    for R := 2 to 41 do
      if Sheet.Cells[R, 4].Formula <> '' then
        Sheet.Cells[R, 4].Value := Book.Calculate(Sheet.Cells[R, 4].Formula);

    Book.SaveAsCSV('feed.csv', 0, ',');    // hoja 0, coma
    Book.SaveAsCSV('feed.tsv', 0, #9);     // misma hoja como TSV
  finally
    Book.Free;
  end;
end;

Noten lo que hace el bucle: sobrescribe las celdas de fórmula con sus valores calculados, lo cual es exactamente correcto para una pasada de exportación descartable, pero incorrecto si luego planean guardar nuevamente el libro como .xlsx. Exporten desde una copia, o limiten la escritura de vuelta a la ejecución de exportación. Las capacidades más profundas del motor detrás de Calculate, incluido el registro de sus propias funciones, se cubren en el motor de fórmulas de HotXLS y funciones personalizadas

Lo que garantiza el escritor delimitado

La ruta CSV produce UTF-8 con byte order mark, finales de línea CRLF y comillas RFC 4180: los campos que contienen el delimitador, comillas o saltos de línea se envuelven, y las comillas internas se duplican. Las fechas se renderizan como yyyy-mm-dd hh:nn:ss sin importar el formato visual de la celda, que es la elección sensata para consumidores máquina pero sorprende a quienes esperan el formato en pantalla. Las celdas con texto enriquecido se aplanan concatenando sus runs

Diagrama del escritor delimitado único de HotXLS en Delphi produciendo CSV con coma y TSV con #9, mientras ambas salidas comparten BOM UTF-8, finales CRLF y entrecomillado RFC 4180
CSV y TSV provienen del mismo escritor, así que el BOM UTF-8, los finales CRLF y el entrecomillado RFC 4180 aplican a ambos sin cambios

Estos valores predeterminados resuelven la mayoría de las discusiones con importadores antes de que empiecen, con dos advertencias que conviene escribir en el contrato de interfaz. Primero, el BOM: es lo que hace que Excel abra el archivo con acentos correctos, pero algunos parsers estrictos tratan esos tres bytes como datos; si el suyo lo hace, quítenlos en la entrega. Segundo, TSV no es una función separada; es el mismo escritor con #9 como parámetro de delimitador, así que todo lo anterior aplica de forma idéntica. La hoja se selecciona por índice 0-based en el overload de varios argumentos, mientras que el atajo de un solo argumento SaveAsCSV(FileName) toma la hoja activa

La exportación HTML es una instantánea, no un formato de intercambio

Donde CSV descarta todo salvo valores, SaveAsHTML intenta preservar la apariencia: una <table> por hoja, regiones combinadas expresadas como colspan y rowspan, y estilos básicos de celda inline como CSS. Los colores relativos al tema se omiten en vez de resolverse, así que una plantilla corporativa que depende de ranuras de tema saldrá más plana de lo que se ve en Excel; usen colores RGB explícitos en todo lo que deba sobrevivir. El objeto de opciones controla el contenedor:

var
  Opts: TXLSXHtmlExportOptions;
begin
  Opts := TXLSXHtmlExportOptions.Create;
  try
    Opts.Title := 'Weekly settlement';
    Opts.TableClass := 'report-grid';     // gancho para la hoja de estilos de la página anfitriona
    Opts.WriteDocument := True;           // página completa, no un fragmento
    if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
      raise Exception.Create('Sheet index out of range');
  finally
    Opts.Free;
  end;
end;

Dos detalles de ese fragmento merecen atención. WriteDocument := False cambia la salida a un fragmento de tabla desnudo, que es lo que necesitan al inyectar una vista previa en una página existente: definan TableClass y dejen que la hoja de estilos envolvente haga el tema. Y la convención de retorno es la opuesta de la mayoría de las llamadas de HotXLS: SaveAsHTML devuelve 0 en éxito y -1 para un índice de hoja inválido, así que una comprobación de éxito por hábito con = 1 reportará mal cada exportación. Para enviar por correo o incrustar solo una región en lugar de una hoja, TXLSXRange.SaveAsHTML exporta cualquier bloque rectangular con las mismas reglas de renderizado

Salida RTF y dónde aún gana su lugar

El cuarto destino de exportación escribe tablas RTF 1.6, una hoja por llamada mediante SaveAsRTF. Los anchos de columna se aproximan a unos 96 twips por carácter de ancho de columna y, esta es la limitación estructural, las celdas combinadas no abarcan columnas en la salida: solo la celda ancla lleva contenido y las celdas cubiertas salen como blancos. Eso hace que RTF sea inadecuado para plantillas con mucho diseño, pero sigue siendo la ruta de menor resistencia para pegar resultados tabulares en documentos de procesador de texto y sistemas heredados de gestión documental anteriores a la ingesta HTML

Round-tripping: importar CSV es destructivo por diseño

La dirección de importación tiene su propio contrato. OpenCSV limpia todo el libro y lo reconstruye como una sola hoja llamada Sheet1: en espíritu es un constructor, no una fusión, así que nunca lo llamen sobre un libro con contenido sin guardar. Pasar #0 como separador activa la detección automática de delimitador, y la bandera ADetectTypes controla la promoción de tipos: con ella activada, cadenas numéricas se vuelven números, cadenas ISO-8601 se vuelven fechas y true/false se vuelven booleanos. Desactívenla cuando el feed contenga identificadores con ceros a la izquierda o códigos postales, que la promoción de tipos corrompe en silencio convirtiéndolos en números. Ambas fachadas de libro exponen la misma importación; combínenla con las llamadas de exportación anteriores y tendrán un puente de formatos que no necesita instalación de Excel en ninguna parte del pipeline, el escenario explorado más a fondo en generación de reportes de base de datos a Excel con HotXLS

Exportar directo a un stream

Todos los escritores tratados aquí, CSV, HTML, RTF y los propios formatos de libro, tienen overloads de stream junto a las versiones con nombre de archivo, y en código de servidor esos son los que conviene usar. Un endpoint web que produce una descarga CSV puede escribir en un TMemoryStream y entregarlo directamente al objeto de respuesta: sin archivo temporal, sin trabajo de limpieza, sin colisión entre dos solicitudes que exportan con el mismo nombre generado. Lo mismo aplica a depositar exportaciones en blob storage o adjuntarlas a correo saliente: el sistema de archivos deja de formar parte del pipeline

Este patrón se suma a la propiedad central de despliegue de la biblioteca: ambas fachadas son lectores y escritores nativos de Object Pascal, así que no hay instalación de Excel, no hay automatización COM y no hay cuello de botella de un solo hilo por proceso en el servidor. Cada solicitud puede tener su propio objeto de libro, ejecutar la escritura de valores calculados de la primera sección y transmitir la exportación, totalmente en paralelo con sus vecinas. El único recurso a vigilar es la memoria: el modelo de libro se mantiene en RAM durante la exportación, así que un servicio que abre libros muy grandes solo para reemitirlos como CSV debe limitar trabajos concurrentes o encolar los sobredimensionados en vez de dejar que el tráfico pico decida el working set

Establezcan IncludeBOM en las opciones HTML cuando el fragmento se vaya a guardar como archivo independiente consumido por herramientas que detectan codificación, y dejen la declaración charset del content-type a la capa HTTP cuando sirvan directamente

Cuando los bytes igual salen mal

La consulta de soporte más frecuente sobre la exportación a CSV es el problema inicial con otro disfraz: Excel muestra mojibake en lugar de caracteres acentuados. El instinto es culpar al escritor, pero este emite un BOM UTF-8 justamente por eso, y el archivo casi siempre es correcto cuando sale de su código. Algo entre ese punto y Excel se comió el BOM. Una transferencia FTP en modo texto, una copia de stream que saltea los primeros tres bytes, un proxy que recodifica al pasar: cualquiera de esos quita el marcador y deja que Excel adivine la codificación, cosa que hace mal. Diagnostiquen esto en la frontera, no en la llamada de exportación. Abran el archivo entregado en un visor hexadecimal y confirmen que EF BB BF sigue siendo lo primero que aparece

Diagrama que rastrea cómo un BOM UTF-8 correcto escrito por la exportación CSV de HotXLS en Delphi es eliminado por una transferencia FTP en modo texto o un proxy de recodificación, dejando que Excel muestre mojibake
El escritor emite EF BB BF correctamente — el mojibake aparece solo después de que un transporte quita el marcador, así que diagnostique los bytes entregados en un visor hexadecimal

Las exportaciones son donde la semántica del libro se encuentra con los parsers de otros sistemas, así que los detalles de contrato anteriores pertenecen a su documentación de integración, no solo a su código. La referencia completa de métodos está en la página de producto HotXLS Delphi Component