Artículo técnico

Propiedades de documento de Excel en Delphi con HotXLS

Una hoja de cálculo lleva dos capas de identidad. Está la cuadrícula de celdas, y están los metadatos del documento que viajan junto a ella: título, autor, empresa, palabras clave, las marcas de tiempo. Excel nunca muestra esa segunda capa en la cuadrícula, y sin embargo es la capa que Windows Search indexa, la que SharePoint lee para titular un documento, y por la que un sistema de gestión de registros archiva. Cuando un libro de trabajo generado hereda su Autor y su Título de la plantilla a partir de la cual se construyó, cada sistema posterior registra al diseñador de la plantilla como autor de cuatro mil estados de cuenta de clientes. Los metadatos no son correctos en ninguna parte y se consultan en todas

HotXLS expone esta capa como propiedades ordinarias a nivel de libro de trabajo en sus dos motores: la fachada BIFF para .xls y la fachada OOXML para .xlsx. Lees un campo después de abrir un archivo y escribes un campo antes de guardarlo. La biblioteca decide en qué contenedor físico aterriza el valor. Lo que vale la pena entender antes de escribir un generador es qué campos soporta realmente cada formato, dónde viven físicamente esos campos, y la única regla de compuerta que gobierna si un .xlsx registra algún metadato en absoluto

Dos formatos, dos modelos de almacenamiento

La razón por la que una biblioteca de hojas de cálculo necesita dos implementaciones de metadatos, y la razón por la que las herramientas a medio terminar estampan correctamente un formato y olvidan el otro, es que .xls y .xlsx guardan sus propiedades en lugares sin relación. Un libro de trabajo BIFF las escribe en flujos de archivo compuesto OLE, principalmente el conjunto de propiedades SummaryInformation que es anterior al propio Excel, junto al registro WRITEACCESS dentro del flujo que nombra a quien guardó el archivo por última vez. Un libro de trabajo OOXML las guarda como partes XML dentro del paquete zip, divididas por propósito: docProps/core.xml contiene los campos Dublin Core (título, creador, asunto, palabras clave, fechas) y docProps/app.xml contiene los campos a nivel de aplicación como la empresa y la aplicación generadora, según ECMA-376 Parte 1

HotXLS aplana ambos modelos de almacenamiento en propiedades directas del objeto de libro de trabajo. Nunca abres un flujo de conjunto de propiedades ni editas una parte XML a mano. Asignas cadenas y fechas al libro de trabajo, y el contenedor correcto se materializa para el formato en que guardes

Diagrama de HotXLS para Delphi que compara el almacenamiento SummaryInformation de BIFF con las partes docProps de OOXML para las propiedades de documento de Excel
HotXLS aplana dos modelos de almacenamiento sin relación en una sola superficie de propiedades del libro de trabajo — el motor elige el contenedor físico cuando se guarda el archivo

Estampar libros de trabajo generados desde el registro de negocio

Del lado XLSX, TXLSXWorkbook expone Title, Subject, Author, Keywords, Description, Category, LastModifiedBy, Company, Application y AppVersion como cadenas, más Created y Modified como valores TDateTime donde cero significa sin establecer. La regla que cierra el agujero de la herencia cabe en una oración: asigna cada campo en cada ejecución, tomando los valores del registro de negocio en lugar de confiar en lo que la plantilla llevara por casualidad

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('statement-template.xlsx') <> 1 then
      raise Exception.Create('Template not available');

    // Sobrescribe cada campo: todo lo que quede sin tocar se
    // hereda de quien haya diseñado la plantilla.
    Book.Title := 'Account Statement 2026-06 / ACME Corp';
    Book.Subject := 'Monthly account statement';
    Book.Author := 'Billing Service 4.2';
    Book.LastModifiedBy := 'Billing Service 4.2';
    Book.Company := 'Northwind Financial';
    Book.Category := 'Customer Delivery';
    Book.Keywords := 'statement;billing;2026-06;acct-10024';
    Book.Description := 'Generated document - manual edits are not retained';
    Book.Created := Now;
    Book.Modified := Now;

    Book.SaveAs('statement-10024.xlsx');
  finally
    Book.Free;
  end;
end;

El campo Keywords recompensa más reflexión de la que suele recibir. La infraestructura de búsqueda lo indexa literalmente, tanto Windows Search como SharePoint y la mayoría de los productos DMS, así que una convención separada por punto y coma que lleve el número de cuenta y el período convierte cada libro de trabajo entregado en un registro localizable sin ningún viaje a la base de datos. Ese mismo alcance es la trampa. Las propiedades viajan con cada copia del archivo, mucho más allá de los controles de acceso del sistema que las escribió, así que los datos personales no tienen lugar ahí

El par de marcas de tiempo tiene una semántica que vale la pena fijar en una política en lugar de dejarla al hábito. Created debería marcar el momento en que tu pipeline generó el documento y luego permanecer congelado. Modified es el campo que Excel actualiza cada vez que un destinatario guarda el archivo, así que una divergencia entre los dos después de la entrega es evidencia positiva de que alguien editó el libro de trabajo más adelante, lo que zanja más de una disputa sobre de quién son realmente los números de una hoja de cálculo reenviada. Una trampa se esconde en el estado sin establecer: es el valor literal cero, no una excepción ni un null, así que el código de auditoría tiene que comprobar el cero explícitamente. Formatea un TDateTime sin establecer sin esa protección y tus registros se llenan de una fecha de diciembre de 1899 confiadamente incorrecta

DocPropsTouched: el libro de trabajo que se envía sin docProps

Un indicador de solo lectura, DocPropsTouched, controla el escritor de propiedades de XLSX. Un libro de trabajo en el que nunca se asignó ninguna propiedad no produce ninguna parte docProps en absoluto; HotXLS se niega a escribir un esqueleto de metadatos vacío. El comportamiento es ordenado, y tiene dos consecuencias alrededor de las cuales vale la pena diseñar

El código de recepción del lado consumidor no debe suponer que core.xml existe en cada paquete. Una herramienta que lo exija de forma estricta rechazará archivos mínimos perfectamente válidos. Y si tu postura de cumplimiento exige que cada documento saliente lleve al menos una identidad de generador, esa exigencia se convierte en código en lugar de una propiedad del formato: asigna Application y Author incondicionalmente en la ruta de guardado, ya que un libro de trabajo sin tocar es completamente legal según la especificación mientras viola silenciosamente tu política

Diagrama de flujo de HotXLS para Delphi que muestra el indicador DocPropsTouched controlando la salida de docProps en libros XLSX guardados
DocPropsTouched controla el escritor de docProps de XLSX — asigna Application y Author incondicionalmente cuando la política exige identidad de generador

La superficie XLS heredada y la trampa de Comments

La fachada BIFF lleva el conjunto de campos más antiguo y pequeño: Title, Subject, Author, Keywords, Comments, Company y Manager, más LastSavedBy, un alias de UserName, que escribe el registro WRITEACCESS que Excel muestra cuando otro usuario tiene el archivo bloqueado

var
  Legacy: IXLSWorkbook;     // interfaz con conteo de referencias: sin Free manual
begin
  Legacy := TXLSWorkbook.Create;
  if Legacy.Open('archive-1999.xls') <= 0 then
    raise Exception.Create('Cannot open archive file');

  Legacy.Title := 'FY1999 ledger (migrated copy)';
  Legacy.Author := 'Archive Migration Batch';
  Legacy.Company := 'Northwind Financial';
  Legacy.Comments := 'Migrated 2026-06-11; source retained in cold storage';
  Legacy.LastSavedBy := 'migration-svc';   // registro WRITEACCESS de BIFF

  Legacy.SaveAs('archive-1999-stamped.xls');
end;

Una colisión de nombres causa confusión recurrente. La propiedad Comments a nivel de documento aquí es la observación de texto libre que se muestra en el diálogo de propiedades del archivo. No tiene nada que ver con los comentarios de celda, que son objetos de la capa de dibujo adjuntos a rangos a través de una API completamente separada. Una revisión de código que acepta "ya escribimos Comments" sin comprobar a cuál se refiere ha aceptado una afirmación sobre la función equivocada, y eso ocurre más a menudo de lo que el nombre compartido sugeriría. Los dos comparten el nombre y ni un solo byte de almacenamiento

Leer metadatos en la recepción, y la brecha del sondeo

La lectura es simétrica. Después de Open, las mismas propiedades regresan pobladas desde el archivo, lo que convierte una auditoría de metadatos de libros de trabajo entrantes en un bucle corto

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open(FileName) = 1 then
    begin
      Writeln(Format('%s | title="%s" author="%s" created=%s',
        [ExtractFileName(FileName), Book.Title, Book.Author,
         FormatDateTime('yyyy-mm-dd', Book.Created)]));
      if Book.Created = 0 then
        Writeln('  no creation date recorded');
    end;
  finally
    Book.Free;
  end;
end;

Mientras lo haces, planifica en torno a una limitación. No hay un sondeo de solo propiedades. GetSheetNames puede listar hojas sin cargar un libro de trabajo, pero leer Title o Author implica un Open completo, así que la clasificación de metadatos a lo largo de un archivo histórico grande paga el costo de análisis completo en cada archivo. Del lado BIFF puedes recortar ese costo para auditorías de solo lectura estableciendo _DisableGraphics en true antes de abrir, lo que omite directamente la capa de dibujo. Encaja en un bucle que solo lee propiedades y estadísticas de celdas, y es exactamente lo incorrecto en el momento en que la misma instancia pudiera guardar, porque el contenido de dibujo omitido se descartaría. Cuando la estructura de hojas por sí sola puede prefiltrar el conjunto, siendo las exportaciones de una sola hoja lo obvio a omitir, las técnicas económicas de nuestro artículo sobre listado de hojas e inspección ligera reducen cuántos archivos llegan al pase costoso. Y en trabajos de estampado masivo, donde se escriben miles de salidas en lugar de inspeccionarlas, los patrones de rendimiento del lado de escritura de nuestro artículo sobre escrituras en streaming para trabajos por lotes se trasladan sin cambios, ya que la asignación de propiedades no agrega nada medible al tiempo de guardado

Cruzar formatos y contener la fuga

Las propiedades hacen el viaje de ida y vuelta limpiamente dentro de una sola fachada: abre un .xlsx, edítalo, guárdalo, y el conjunto regresa intacto. Cruzar formatos es donde la suposición de paridad se rompe, porque los conjuntos de campos de BIFF y OOXML no se alinean uno a uno. BIFF tiene Manager y ninguna marca de tiempo; OOXML tiene Category, Description y el par Created/Modified. Un convertidor que copia a ciegas pierde todo lo que el formato de destino no puede contener, así que mapea los campos explícitamente y pon el mapeo en tu lista de verificación de conversión junto a todo lo demás que no sobrevive al viaje

Mapa de campos de HotXLS para Delphi que muestra qué propiedades de documento de Excel sobreviven a una conversión entre formatos XLS y XLSX
Una copia a ciegas entre formatos descarta cada campo que el destino no puede contener — mapea explícitamente los conjuntos de propiedades BIFF y OOXML en la lista de verificación de conversión

La fuga que abre la herencia de plantillas corre en la otra dirección: información que nunca quisiste enviar. Nombres de autor, etiquetas internas de proyecto aparcadas en las palabras clave, un título de borrador que nadie aprobó. La disciplina de sobrescribirlo todo del generador anterior es toda la defensa, y vale la pena verificarla como lo haría un externo, abriendo el diálogo de Propiedades al que cualquier cliente puede llegar o descomprimiendo el .xlsx y leyendo docProps/core.xml directamente desde el paquete. Lo que ves ahí es exactamente lo que ve cada indexador posterior

Esa visibilidad posterior es también la razón por la que unos pocos campos merecen más cuidado que el resto. Título, Autor, Palabras clave (que aparecen como Etiquetas) y Comentarios o Descripción cargan la mayor parte del peso de indexación en SharePoint y Windows Search. Un Título genuinamente distinto por documento, que lleve el período y la cuenta, hace más por la localizabilidad que cualquier esquema de nombres de carpeta apilado encima, y cuesta una asignación por guardado

Las propiedades de documento son el pulido profesional más barato que un libro de trabajo generado puede llevar, y el defecto más comúnmente enviado cuando nadie se hace cargo de ellas. Ambas superficies de propiedades descritas aquí pertenecen al componente HotXLS para Delphi, que las escribe de forma nativa para XLS y XLSX sin automatización de Excel