Artículo técnico

Parchear una hoja de cálculo en un XLSX grande desde Delphi

HotXLS puede reescribir una hoja de cálculo dentro de un paquete XLSX existente sin analizar ni volver a comprimir el resto del archivo. TXLSDirectWriter.BeginPatch abre un paquete de origen, copia cada entrada salvo la hoja objetivo con sus bytes comprimidos intactos, y permite volver a redactar esa única hoja mediante las llamadas habituales AddSheet, AddRow y Write*. Los gráficos, las cachés de tablas dinámicas, los temas, los estilos y las cadenas compartidas nunca llegan a descomprimirse

El flujo de trabajo que esto resuelve aparece en la generación de informes y la actualización de datos. Un libro llega de un equipo de negocio con tablas dinámicas, segmentaciones de datos, formatos condicionales y una década de formato acumulado. Cada noche hay que reemplazar una hoja de datos con cifras nuevas. Cargar y volver a guardar el libro completo cuesta minutos por archivo y, lo que es más importante, arriesga la fidelidad de funciones que el motor de carga tiene que reconstruir. El parcheo evita ambos problemas al no tocar lo que no necesita tocar

¿Por qué copiar bytes comprimidos es la parte interesante?

Una entrada zip copiada a nivel comprimido cuesta una copia de flujo. Esa misma entrada pasada por una vía de escritura normal cuesta una descompresión al entrar y una compresión al salir, y la compresión es la mitad cara. En un libro con una caché de tabla dinámica grande y unas cuantas docenas de imágenes incrustadas, esa diferencia es la diferencia entre un parche que termina en el tiempo que tarda en escribir la hoja nueva y uno que pasa la mayor parte del tiempo recomprimiendo bytes que nunca examinó

HotXLS usa CopyCompressedFrom para esto, que escribe los bytes comprimidos de la entrada de origen directamente en el archivo de destino. Cuando una entrada no se puede copiar de ese modo, porque usa un método de compresión distinto o un cifrado débil, el escritor recurre a una copia de flujo descomprimida en lugar de fallar. Las entradas marcadoras de directorio se omiten, ya que el escritor produce las suyas propias

Reemplazar in situ, o escribir en un archivo nuevo

Dos sobrecargas cubren las dos formas que adopta esta tarea. La forma in situ prepara el resultado en un archivo temporal junto al original, cierra el identificador de origen y luego elimina y renombra, de modo que un fallo a mitad de la escritura deja el original intacto. La forma con destino explícito deja el origen intacto y puede reemplazar una hoja o añadir una nueva:

var
  W: TXLSDirectWriter;
begin
  W := TXLSDirectWriter.Create;
  try
    W.BeginPatch('monthly-dashboard.xlsx', 'Data');   // in situ
    W.AddSheet('Data');
    W.AddRow(1);
    W.WriteString(1, 'Region');
    W.WriteString(2, 'Revenue');
    W.AddRow(2);
    W.WriteString(1, 'North');
    W.WriteNumber(2, 184320.55);
    W.AddRow(3);
    W.WriteFormula(1, '=SUM(B2:B2)');
    W.Close;
  finally
    W.Free;
  end;
end;

La variante de inserción recibe una ruta de origen y una de destino, más InsertSheet:

  // El origen permanece intacto; el destino recibe una hoja adicional llamada Extra
  W.BeginPatch('template.xlsx', 'output.xlsx', 'Extra', True);
  W.AddSheet('Extra');
  W.AddRow(1);
  W.WriteString(1, 'appended by the nightly job');
  W.Close;

La inserción es la parte que requiere una auténtica cirugía contable. El escritor analiza el registro de hojas en xl/workbook.xml y el mapa de relaciones que vincula cada hoja con su parte, y luego elige el siguiente número de parte, identificador de hoja e identificador de relación libres. Los tipos de relación siguen las convenciones del paquete de origen, así que parchear un libro strict de ISO 29500 emite tipos de relación strict y parchear uno transicional emite tipos transicionales

Qué descarta y restringe deliberadamente el parche

La cadena de cálculo se descarta en ambos modos. En modo de reemplazo, sus entradas describen celdas de una hoja que ya no existe en esa forma; en modo de inserción, el desplazamiento del índice de hoja la invalida directamente. Excel reconstruye la cadena en el siguiente recálculo, así que descartarla es correcto y no supone pérdida. La parte se deja fuera de la copia, y su entrada de relación y su anulación de tipo de contenido se eliminan quirúrgicamente

Dos semánticas de creación cambian dentro de un parche, y ambas se derivan del mismo principio: el parche no debe perturbar partes que no reescribió. Las cadenas se escriben en línea dentro de la hoja en lugar de añadirse a la tabla de cadenas compartidas, porque la tabla de origen pasa intacta. Y StyleIndex hace referencia a entradas del cellXfs del paquete de origen, no a una tabla de estilos que construya el escritor. Eso significa que se pueden referenciar formatos que el libro original ya define, que suele ser exactamente lo que quiere una actualización de datos, pero también significa que hay que saber qué índice lleva qué formato

// Dentro de un parche, StyleIndex indexa el cellXfs del paquete DE ORIGEN.
// Una fecha necesita un índice explícito que se corresponda allí con un formato de fecha:
W.WriteDateTime(3, EncodeDate(2026, 8, 22), DateStyleIndexFromTemplate);

// La sobrecarga de WriteDateTime sin estilo se rechaza en modo parche,
// porque asume la tabla de estilos propia del escritor, que un parche
// nunca crea

Seis puntos de entrada de creación están bloqueados: añadir tablas, gráficos, imágenes, comentarios, nombres definidos y estilos de celda genera todos ellos una excepción en modo parche, con una segunda red de seguridad en el momento del cierre que falla si alguno de sus contadores es distinto de cero. Cada una de esas funciones requeriría editar partes que el parche copia literalmente, y un paquete editado a medias es peor que una operación rechazada. Solo se puede parchear una hoja por operación

Cuándo parchear y cuándo cargar

Parchear es la herramienta adecuada cuando el libro es grande, el cambio se limita a una hoja y el resto del archivo debe sobrevivir bit a bit. Es la herramienta incorrecta cuando el cambio abarca varias hojas, cuando se necesita nuevo formato o nuevos objetos, o cuando el archivo es lo bastante pequeño como para que una carga y guardado normales no cuesten nada. Para la generación masiva desde cero, la vía en flujo descrita en el escritor directo en flujo sigue siendo la opción más adecuada, y comparte la misma API de AddRow y Write*, así que moverse entre ambas es mecánico

La manipulación a nivel de hoja dentro de un libro cargado, cuando sí se quiere el modelo de objetos completo, se trata en duplicar hojas de cálculo en paquetes XLSX. Y si el motivo por el que se está considerando un parche es que el procesamiento del libro completo se ha vuelto lento, vale la pena leer las mediciones y el comportamiento de memoria de el rendimiento con libros grandes antes de elegir un enfoque

Verificar que un parche realmente hizo lo que se cree

Tres comprobaciones detectan casi cualquier error. Confirme que las partes que esperaba que sobrevivieran siguen en el archivo, que xl/calcChain.xml ha desaparecido, y que volver a abrir el archivo con TXLSXWorkbook informa del número de hojas esperado, sin cambios para un reemplazo e incrementado en uno para una inserción. Releer la hoja parcheada y comparar algunos valores y fórmulas cierra el ciclo

Un detalle de implementación del desarrollo de esta función merece repetirse, porque puede afectar a cualquiera que escriba código a nivel de zip similar. Los nombres de las partes de hoja se comparan por prefijo, y un error de uno en la longitud del prefijo hace que el predicado nunca coincida, de modo que una parte recién escrita colisiona con un nombre existente y los lectores que toman la última entrada con un nombre dado eligen silenciosamente la hoja equivocada. Si un parche parece haber intercambiado el contenido de dos hojas, revise la coincidencia de nombres antes de revisar el XML

El parcheo in situ, la escritura en flujo y el modelo de objetos de libro completo se incluyen en la misma biblioteca para Delphi y C++Builder; la lista de funciones está en la página del componente de hojas de cálculo para Delphi de HotXLS