Artículo técnico

Crear un entorno de auditoría y conversión de libros en Delphi con HotXLS

Una migración documental le entrega cuarenta mil archivos de hojas de cálculo acumulados desde 2003: BIFF de la era .xls, .xlsx modernos, algunos .ods de un piloto de LibreOffice y unos cuantos archivos que nadie puede abrir porque la contraseña se fue con un empleado. El mandato es normalizar todo a XLSX y CSV. El plan ingenuo, recorrer, abrir y guardar como, producirá cuarenta mil archivos de salida y ningún registro de cuáles perdieron sus gráficos, descartaron sus macros o nunca pudieron abrirse. Lo que el trabajo realmente necesita es un entorno de trabajo: inventariar primero, convertir después, verificar al final.

HotXLS le da a un proceso Delphi o C++Builder todo lo necesario para eso, sin ninguna instalación de Excel en el circuito: dos motores nativos, una fachada BIFF8 y una fachada OOXML, llamadas de sondeo económicas que evitan análisis completos, contadores de auditoría por hoja y una matriz de conversión documentada. La salvedad es que cada una de esas piezas tiene bordes que conviene conocer antes de que el lote corra toda la noche.

Sondee antes de cargar: nombres de hojas y detección de cifrado

Abrir un libro de 200 MB solo para descubrir que está cifrado desperdicia minutos por archivo; multiplicado por un archivo histórico completo, desperdicia días. Ambas fachadas exponen GetSheetNames, que lee metadatos de hojas sin poblar el libro: la implementación BIFF escanea solo los registros BoundSheet al inicio del flujo, y la implementación OOXML lee solo workbook.xml dentro del zip. CanReadEncrypted detecta un contenedor de cifrado sin intentar descifrarlo:

var
  Probe: TXLSXWorkbook;
  Names: TStringList;
begin
  Names := TStringList.Create;
  Probe := TXLSXWorkbook.Create;
  try
    if Probe.CanReadEncrypted(FileName) then
    begin
      Writeln(FileName + ': encrypted container - route to manual handling');
      Exit;
    end;
    if Probe.GetSheetNames(FileName, Names) <= 0 then
      Writeln(FileName + ': unreadable - quarantine')
    else
      Writeln(Format('%s: %d sheet(s), first "%s"',
        [FileName, Names.Count, Names[0]]));
  finally
    Probe.Free;
    Names.Free;
  end;
end;

Dos notas operativas. GetSheetNames no reinicia ni puebla la instancia del libro, así que un solo objeto de sondeo puede clasificar miles de archivos en un bucle ajustado. Y la versión de la fachada XLS de la misma llamada también entiende paquetes .xlsx, lo que la convierte en un sondeo único conveniente cuando las extensiones no son confiables, como siempre ocurre en archivos históricos antiguos. Para un tratamiento más profundo de la clasificación antes de cargar, vea nuestro artículo sobre listado de hojas e inspección ligera de libros.

Contar lo que realmente contiene un libro

Cuando un archivo supera la clasificación, la pasada de auditoría decide su ruta de conversión. La fachada XLSX expone un contador para cada familia de funciones que importa en las decisiones de fidelidad: celdas combinadas, gráficos, imágenes, formatos condicionales, validaciones de datos, tablas, hipervínculos y comentarios, además de indicadores a nivel de libro para macros, protección y formato de origen:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  I: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open(FileName) <> 1 then Exit;
    for I := 0 to Book.Sheets.Count - 1 do
    begin
      Sheet := Book.Sheets[I];
      Writeln(Format('%s: cells=%d merges=%d charts=%d cf=%d dv=%d protected=%s',
        [Sheet.Name, Sheet.Cells.Count, Sheet.MergedCells.Count,
         Sheet.Charts.Count, Sheet.ConditionalFormats.Count,
         Sheet.DataValidations.Count, BoolToStr(Sheet.IsProtected, True)]));
    end;
    if Book.HasVbaProject then
      Writeln('  contains VBA project - macro policy applies');
    if Book.ExternalLinks.Count > 0 then
      Writeln(Format('  %d external link(s)', [Book.ExternalLinks.Count]));
  finally
    Book.Free;
  end;
end;

Interprete Cells.Count con cuidado: el almacén de celdas es disperso, así que el número refleja celdas instanciadas, no el área rectangular del rango usado. Una hoja con dos valores en A1 y ZZ9999 cuenta dos celdas. En el lado BIFF, el escaneo equivalente usa los límites de UsedRange y ForEachCell, y arrastra el clásico desplazamiento de uno de HotXLS: UsedRange.FirstRow y sus pares son base 0, mientras que Cells.Item[Row, Col] es base 1, de modo que un recorrido debe sumar uno a cada límite o auditará el rectángulo equivocado.

Dos palancas aceleran las pasadas de solo auditoría sobre archivos heredados grandes. Establecer _DisableGraphics en true antes de abrir un .xls omite por completo el análisis de la capa de dibujos OfficeArt, lo que ahorra bastante en libros densos en formas; pero es estrictamente una optimización de solo lectura, porque guardar desde una instancia así descartaría los dibujos no analizados. Y cuando la auditoría necesita contenido por celda en vez de contadores, el callback ForEachCell recorre directamente las celdas pobladas, evitando la sobrecarga de Variant por acceso de las propiedades indexadas de celdas en millones de lecturas.

Normalice temprano los códigos de retorno inconsistentes

Las llamadas de E/S de HotXLS reportan errores mediante resultados enteros en vez de excepciones, y las convenciones no son uniformes en toda la superficie de API. La mayoría de las llamadas de apertura y guardado devuelven 1 en éxito y -1 en falla; GetSheetNames devuelve el conteo o -1 y limpia la lista; SaveAsHTML de XLSX devuelve 0 para éxito y -1 si el índice de hoja está fuera de rango. Un entorno que pruebe = 1 en todas partes clasificará mal algunos éxitos, y uno que pruebe <> -1 perderá otros.

La regla pragmática que se sostiene en toda la superficie es: trate <= 0 como falla en llamadas que devuelven conteos, compruebe el valor de éxito documentado para cada rutina de guardado que realmente use y envuelva ambos casos en una pequeña función de comprobación de resultados para que la convención viva en un solo lugar. Las canalizaciones por lotes mueren por la acumulación de mil archivos con códigos de retorno sin revisar, no por errores exóticos de parser.

La matriz de conversión y dónde cada camino pierde información

Las dos fachadas se reparten el trabajo de conversión. TXLSXWorkbook abre XLSX, ODS y CSV, y guarda XLSX, ODS, CSV, HTML, RTF y XLSX cifrado con AES. TXLSWorkbook abre y guarda BIFF y exporta HTML, RTF y CSV. Cada ruta tiene un perfil de fidelidad documentado, no una promesa vaga:

La exportación CSV escribe UTF-8 con BOM, finales de línea CRLF y comillas según RFC 4180, pero las fórmulas se emiten como texto literal, nunca se evalúan, así que una hoja de celdas =SUM(...) se exporta como cadenas de fórmula a menos que usted calcule valores primero. La exportación HTML produce una sola tabla con colspan y rowspan para combinaciones y estilos base en línea. La exportación RTF no puede extender celdas combinadas entre columnas; las celdas de continuación salen vacías. La importación ODS es deliberadamente ligera según la propia documentación de la biblioteca: entran valores escalares y resultados de fórmulas en caché, pero no estilos, expresiones ODF vivas ni dibujos; esto importa cuando el archivo histórico contiene archivos OpenDocument gobernados por OASIS ODF 1.3, donde una conversión visualmente fiel requiere más de lo que proporciona la ruta de importación.

SaveXLSWorkbookAsXLSX es un puente de datos, no de diseño

La fachada BIFF no puede escribir OOXML directamente; el cruce es la función SaveXLSWorkbookAsXLSX de la unidad lxXlsxExport. Es importante dimensionar su fidelidad con honestidad: el puente copia valores, fórmulas, formatos numéricos, colores de relleno, atributos centrales de fuente, anchos de columnas y configuraciones de vista como líneas de cuadrícula; no copia bordes, rangos combinados, comentarios, gráficos ni formatos condicionales. Para normalización de datos de un archivo histórico que leerán sistemas posteriores, eso es exactamente suficiente. Para la conversión de grado presentación de un informe de directorio con formato, no lo es, y el entorno honesto envía esos archivos a una cola manual en vez de entregar una copia degradada.

var
  Legacy: IXLSWorkbook;        // interface reference: do not Free
  Modern: TXLSXWorkbook;
begin
  if SameText(ExtractFileExt(FileName), '.xls') then
  begin
    Legacy := TXLSWorkbook.Create;
    if Legacy.Open(FileName) <= 0 then Exit;
    if SaveXLSWorkbookAsXLSX(Legacy,
         ChangeFileExt(FileName, '.xlsx')) <= 0 then
      Writeln('bridge failed: ' + FileName);
  end
  else
  begin
    Modern := TXLSXWorkbook.Create;
    try
      Modern.StreamingWrite := True;     // stream sheet XML into the zip
      if Modern.Open(FileName) = 1 then
        Modern.SaveAsCSV(ChangeFileExt(FileName, '.csv'), 0, ',');
    finally
      Modern.Free;
    end;
  end;
end;

El bucle anterior también muestra la palanca de rendimiento del lado OOXML: StreamingWrite transmite el XML de las hojas directamente al paquete de salida en vez de prepararlo como una sola cadena gigante, lo que importa cuando los archivos alcanzan cientos de miles de filas. El dimensionamiento y el comportamiento de memoria de ese modo se cubren en nuestro artículo sobre escritura en streaming para trabajos por lotes de servidor. Ninguna fachada es segura para hilos, pero ninguna comparte estado global; la conversión paralela con una instancia de libro por hilo trabajador es el patrón admitido.

Preguntas frecuentes

¿El entorno puede convertir archivos protegidos con contraseña?

Divida la respuesta por formato. El cifrado heredado de .xls, RC4, RC4 CryptoAPI y XOR, es legible: pase la contraseña a Open y convierta normalmente. Los paquetes .xlsx cifrados se detectan con CanReadEncrypted, pero HotXLS no puede descifrarlos; envíelos a una cola donde alguien los abra y vuelva a guardar primero en Excel.

¿Las fórmulas se recalculan durante la conversión?

Ninguna ruta de guardado evalúa fórmulas. Excel recalcula al abrir, así que la conversión XLSX a XLSX es segura; los destinos CSV reciben texto de fórmula a menos que su canalización evalúe las celdas con Calculate y escriba los resultados antes de exportar.

¿Cómo debe verificar su propia salida el entorno?

Vuelva a abrir cada archivo convertido con la misma biblioteca y ejecute otra vez los contadores de auditoría, luego revise una muestra en Excel o LibreOffice. Comparar conteos de hojas y de celdas antes y después de la conversión detecta la gran mayoría de las pérdidas silenciosas con un costo insignificante.

Un entorno que audita primero convierte una conversión masiva riesgosa en un proceso medible con carril de cuarentena. Todas las llamadas de sondeo, conteo y conversión mostradas aquí son parte de HotXLS Component, que las ejecuta de forma nativa en el proceso sin automatización de Excel.