Artículo técnico

Listar nombres de hojas en Delphi con HotXLS GetSheetNames

A veces la única pregunta que una rutina de recepción necesita responder es estructural: ¿tiene este libro de trabajo una hoja llamada "Mapping", o cuántas pestañas trae? Responderla llamando a Open es la manera cara de hacerlo. Una apertura completa infla la tabla de cadenas compartidas, decodifica cada registro de estilo y recorre las celdas de cada hoja de cálculo, porque no tiene forma de saber que usted solo quería el índice. En un archivo grande eso son cientos de megabytes de asignaciones y varios segundos de CPU gastados en leer una lista que ocupa unos pocos kilobytes. HotXLS, la biblioteca nativa de hojas de cálculo para Delphi de losLab, le entrega esa lista por sí sola: GetSheetNames devuelve los nombres de las hojas de cálculo, en el orden del libro de trabajo, sin materializar una sola celda

Por qué el catálogo es barato de leer

Ambos formatos de hoja de cálculo ponen su índice cerca del principio, y eso es lo que vuelve rápida a una llamada de listado, no algún truco ingenioso. Un paquete OOXML guarda el catálogo de hojas en xl/workbook.xml, una parte que se mantiene pequeña ya sea que el libro de trabajo tenga diez filas o diez millones. Un .xls BIFF8 guarda sus registros BoundSheet al inicio del flujo de globales del libro de trabajo, antes de cualquier dato de celda. Así que el trabajo que evita una llamada de listado no es un error de redondeo frente a una apertura completa. Es casi todo el archivo. Leer el catálogo cuesta el mismo puñado de kilobytes sin importar el número de filas, mientras que una apertura completa escala con los datos, y en un libro de trabajo de varios megabytes esa brecha llega a varios órdenes de magnitud tanto en bytes tocados como en memoria asignada

HotXLS GetSheetNames en Delphi leyendo solo el catálogo de hojas de un archivo XLSX o XLS mientras una apertura completa recorre cada celda
El catálogo vive en workbook.xml o en los registros BoundSheet, así que listar cuesta unos pocos kilobytes mientras una apertura completa escala con los datos

Ese costo plano es la propiedad alrededor de la cual vale la pena diseñar. Una puerta de recepción construida sobre GetSheetNames se comporta igual con un archivo de 200 filas y con uno de 200 MB, así que el archivo más lento de un lote ya no marca el ritmo para decidir si un archivo siquiera vale la pena procesar

Una sola llamada para .xls, .xlsx y los formatos de plantilla

En la fachada XLS, TXLSWorkbook.GetSheetNames lee más que .xls. También acepta los formatos basados en zip .xlsx, .xlsm, .xltx y .xltm, y extrae del archivo comprimido únicamente workbook.xml. Para una entrada .xls genuina recorre los registros BoundSheet y se detiene en el primer registro EOF del subflujo de globales, así que un archivo binario grande sigue costando solo sus primeros kilobytes. La fachada XLSX trae una garantía que importa más de lo que parece en código de servicio de larga vida: TXLSXWorkbook.GetSheetNames no reinicia ni puebla la instancia del libro de trabajo, así que una instancia que ya tiene un documento abierto puede sondear otros archivos sin perturbar el que tiene en mano. GetODSSheetNames aplica el mismo enfoque a los paquetes OpenDocument, y cada una de estas llamadas tiene una sobrecarga con flujo, lo que le permite inspeccionar una carga que nunca aterriza en disco

var
  Book: TXLSXWorkbook;
  Names: TStringList;
  I: Integer;
begin
  Names := TStringList.Create;
  Book := TXLSXWorkbook.Create;
  try
    if Book.GetSheetNames('upload-7f3a.xlsx', Names) <= 0 then
      raise Exception.Create('unreadable workbook package');
    if Names.IndexOf('Mapping') < 0 then
      raise Exception.Create('required Mapping sheet is missing');
    for I := 0 to Names.Count - 1 do
      Writeln(Format('sheet %d: %s', [I, Names[I]]));
  finally
    Book.Free;
    Names.Free;
  end;
end;

La misma llamada da un buen diálogo de importación de escritorio. Liste las hojas, deje que el usuario elija una y pague la apertura completa solo después de que la elección esté hecha. Con un libro de trabajo de cincuenta hojas la diferencia se nota: un selector que aparece de inmediato frente a uno que se atasca mientras el archivo entero se carga detrás

Los archivos .xlsm con macros habilitadas y los formatos de plantilla se listan igual que un .xlsx sencillo, porque el catálogo vive en el mismo workbook.xml haya o no un vbaProject.bin viajando dentro del paquete. Por eso una tubería de recepción puede enumerar las hojas de un libro de trabajo con macros para enrutarlo, sin tocar nunca la carga de macros y sin hacer nada que la ejecute, y dejar la decisión de política sobre macros a la etapa que sí abre el archivo

Leer el valor de retorno sin engañarse

Las convenciones de retorno no son uniformes en todo HotXLS. Algunas llamadas devuelven 1 al tener éxito, otras devuelven un conteo, así que para las funciones de listado la única verificación que se sostiene es tratar cualquier valor de cero o menos como falla, con la lista de cadenas vaciada. Resista la tentación de leer una lista vacía como "un libro de trabajo sin hojas". Tanto ECMA-376 como la especificación BIFF8 exigen al menos una hoja en un libro de trabajo válido, así que cero nombres siempre significa que la lectura falló, nunca que el archivo esté legítimamente vacío

Un listado fallido es en sí mismo una señal que vale la pena conservar. Un archivo .xlsx que falla la llamada es una de unas pocas cosas concretas: está truncado, no es realmente un paquete OOXML (las exportaciones CSV mal etiquetadas de otros sistemas aparecen aquí todo el tiempo) o es un contenedor cifrado. Distinguirlas es tarea de la siguiente verificación. Registrar los primeros bytes del archivo rechazado junto con la falla suele convertir un hilo de soporte en un solo mensaje

Detectar contenedores cifrados antes de enrutar

Un .xlsx cifrado no es un zip. Es un archivo compuesto OLE que envuelve los flujos EncryptionInfo y EncryptedPackage, así que GetSheetNames no puede ver dentro y devuelve falla como cualquier otro archivo ilegible. CanReadEncrypted prueba esa forma de contenedor, lo que permite que la recepción enrute a propósito un archivo cifrado en lugar de tragarse un error de lectura genérico surgido de algún punto profundo de un trabajador:

Flujo de triaje de recepción en Delphi con HotXLS CanReadEncrypted y GetSheetNames enrutando las cargas a necesita contraseña, ilegible o normal
CanReadEncrypted corre primero porque un archivo OOXML cifrado es un contenedor OLE dentro del cual las llamadas de listado no pueden ver
type
  TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);

function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // El OOXML cifrado es un contenedor OLE, no un zip: verifíquelo primero,
    // porque las llamadas de listado no pueden mirar dentro de él.
    if Book.CanReadEncrypted(FileName) then
      Exit(irNeedsPassword);
    if SameText(ExtractFileExt(FileName), '.ods') then
    begin
      if Book.GetODSSheetNames(FileName, Names) <= 0 then
        Exit(irUnreadable);
    end
    else if Book.GetSheetNames(FileName, Names) <= 0 then
      Exit(irUnreadable);
    Result := irNormal;
  finally
    Book.Free;
  end;
end;

El cifrado es donde HotXLS es deliberadamente asimétrico, y el enrutamiento tiene que respetarlo. El cifrado heredado de .xls (RC4, RC4 CryptoAPI, XOR) sí se puede leer: TXLSWorkbook.Open(FileName, Password) descifra con una contraseña almacenada, y esos archivos pueden seguir por la vía automatizada. Los paquetes OOXML cifrados van en sentido contrario. HotXLS puede escribir uno con SaveAsEncrypted, pero no puede volver a leerlo. OpenEncrypted lanza EXlsxEncryptionNotImplemented cuando recibe un paquete cifrado, y por eso un diseño de recepción honesto envía los .xlsx cifrados a una persona con Excel y deja en código los .xls con contraseña

Para el trabajo por lotes este clasificador se gana su lugar corriendo sobre todo un directorio de entrada antes de que cualquier trabajador empiece el procesamiento real, ya que cada sondeo cuesta más o menos la apertura de un archivo y unos pocos kilobytes de lecturas. Adelantarlo cambia el modo de falla que a operaciones de verdad le importa. En lugar de un trabajo de las 3 de la mañana que muere en el archivo 412 de 600, usted obtiene 412 archivos en cola y 5 rechazados en la recepción con un motivo adjunto a cada uno. Las mismas llamadas de la biblioteca, una historia operativa mucho mejor

Las preguntas que una llamada de listado no puede responder

Los nombres y el orden son todo lo que obtiene. Las llamadas de listado no dicen nada sobre la visibilidad, así que las hojas ocultas y muy ocultas llegan a la lista con el mismo aspecto que cualquier otra. No informan dimensiones del rango usado, ni conteos de celdas, ni propiedades del documento. La parte docProps/core.xml también es pequeña, pero hoy no existe un sondeo solo de propiedades, así que los metadatos de autor y título siguen costando un Open completo. La manera limpia de convivir con eso es dejar que los datos baratos enruten cada archivo y reservar los caros para los archivos que sobreviven al enrutamiento. Para los archivos que sí avanzan a una lectura profunda, un escaneo de solo lectura de un .xls grande corre notablemente más rápido con _DisableGraphics := True, que se salta el análisis de OfficeArt. Eso sí, nunca guarde desde esa instancia: la capa de dibujo que se saltó desapareció del modelo, y guardarla la eliminaría del archivo

Los archivos que pasan el triaje suelen dirigirse a un análisis más profundo. El banco de auditoría y conversión de libros de trabajo cubre los contadores por hoja que vale la pena recolectar una vez que la apertura completa se justifica, y la guía de rendimiento con libros de trabajo grandes cubre cómo mantener rápida esa apertura completa

HotXLS es una biblioteca nativa de hojas de cálculo en Object Pascal para Delphi y C++Builder; toda la superficie de la API, incluidas las llamadas de inspección mostradas aquí, está documentada en la página de producto de HotXLS Delphi Component