Renombrar una referencia de hoja de cálculo fija a través de mil plantillas de informe habilitadas para macros descarta abrir cada archivo a mano en el editor de VBA. HotXLS, el componente Excel nativo para Delphi y C++Builder, resuelve ese caso exponiendo el código fuente de un módulo VBA como una propiedad SourceCode editable y recomprimiendo cada edición con el algoritmo de compresión MS-OVBA que Microsoft define para el almacenamiento de VBA, escribiendo el resultado de vuelta en el almacenamiento VBA de un XLS clásico, en un archivo de proyecto VBA independiente o en un libro XLSM habilitado para macros. Ninguna instancia de Excel, ningún editor de VBA y ninguna grabadora de macros interviene en ningún punto de esa vía
Por qué un flujo de módulo VBA no es un archivo de texto
Un módulo VBA dentro de un libro XLS o de un archivo de proyecto VBA independiente no es texto fuente alojado en un flujo esperando a ser leído, es un pequeño contenedor binario. Primero va una caché de rendimiento compilada, los bytes que Office usa para saltarse la recompilación del módulo al cargar cuando la caché todavía coincide con la versión del host, y a continuación va el texto fuente real, pasado por un esquema de compresión propietario que MS-OVBA define específicamente para el almacenamiento de VBA. Ese esquema no es zip, no es deflate, y no es nada que las API de compresión de Windows produzcan de forma nativa, que es exactamente por qué la mayoría de las bibliotecas de Excel de terceros pueden leer el código fuente de un módulo, la descompresión es la mitad más fácil del problema, pero se quedan cortas a la hora de volver a escribirlo, ya que la recompresión es donde un bit sutilmente equivocado produce un archivo que Excel se niega a abrir. Existen artículos públicos sobre el lado de lectura; las implementaciones del lado de escritura que realmente ejercitan la recompresión, en lugar de limitarse a desempaquetar un módulo existente para inspección, son lo bastante escasas como para que esto siga siendo uno de los rincones peor documentados de los formatos de archivo de Excel
¿Qué cambia realmente la propiedad SourceCode de HotXLS?
HotXLS representa cada módulo VBA como un objeto TXLSVBAModule con una propiedad sencilla SourceCode: WideString, y asignarle un valor nuevo es exactamente tan simple como parece: el módulo se marca como modificado en memoria, y nada toca el flujo OLE subyacente hasta que se guarda el proyecto. El propio proyecto procede de IXLSWorkbook.VBAProject en el motor XLS clásico o de TXLSXWorkbook.ParsedVBAProject en el motor OOXML habilitado para macros, ambos devolviendo un TXLSVBAProject cuyos módulos se sitúan detrás de un indexador Item[] basado en 1 y una propiedad Count, así que una edición por lotes a través de cada módulo de un libro es simplemente un bucle sobre un rango de enteros
var
Wb: TXLSWorkbook;
Project: TXLSVBAProject;
I: Integer;
Updated: WideString;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Open('MonthlyReport.xls');
if Wb.HasVBAProject then
begin
Project := Wb.VBAProject;
for I := 1 to Project.Count do
begin
Updated := StringReplace(Project[I].SourceCode,
'ReportSheet2025', 'ReportSheet2026', [rfReplaceAll]);
if Updated <> Project[I].SourceCode then
Project[I].SourceCode := Updated; // marks the module dirty
end;
Wb.SaveAs('MonthlyReport.xls'); // recompresses on write
end;
finally
Wb.Free;
end;
end;
Ese bucle es también la forma de una pasada de auditoría. Antes de tocar mil plantillas, la mayoría de los equipos primero quieren saber cuántas de ellas realmente llevan macros y a qué hacen referencia esas macros, que es el escenario que hay detrás del banco de auditoría y conversión de libros, el mismo Project.Count que gobierna un bucle de reescritura aquí se convierte allí en un recuento de macros por archivo
Dentro del contenedor de compresión MS-OVBA
El formato de compresión de MS-OVBA empaqueta los bytes de fuente en lo que la especificación llama un CompressedContainer: un único byte de firma, que debe ser igual a 0x01, seguido de una secuencia de bloques CompressedChunk, cada uno cubriendo hasta 4096 bytes de datos descomprimidos. Una cabecera de bloque de 16 bits lleva tres campos, una firma de 3 bits que debe ser igual a 3, un campo de tamaño de 12 bits, y un bit CompressedChunkFlag que marca si la carga del bloque es bytes literales o una secuencia comprimida por tokens. Cuando el indicador está activado, la carga es una tirada de grupos de ocho tokens prefijados por byte de indicador, y cada token es o bien un único byte literal o bien un CopyToken: una referencia hacia atrás de desplazamiento/longitud a bytes ya descomprimidos anteriormente en el mismo bloque, con el ancho de bits repartido entre desplazamiento y longitud cambiando según cuán adentro del bloque se encuentre en ese momento el descompresor. Esta parte de MS-OVBA (§2.4.1, Compression and Decompression) es donde una implementación hecha a mano pierde con más frecuencia un día entero por un error de uno en ese cálculo de ancho de bits
Por qué HotXLS escribe bloques en bruto en lugar de emparejar tokens
La vía de escritura de HotXLS esquiva por completo la mitad de emparejamiento de tokens de ese algoritmo. Cuando recomprime un módulo editado, cada bloque sale con el CompressedChunkFlag desactivado, lo que significa que el bloque contiene bytes literales en lugar de tokens de referencia hacia atrás, legal según MS-OVBA, ya que un contenedor comprimido puede consistir enteramente en bloques sin comprimir, y esto elimina precisamente la parte del algoritmo más difícil de acertar a mano: encontrar referencias hacia atrás válidas y empaquetar un par desplazamiento/longitud en un ancho de bits que depende de la posición actual dentro del bloque. La compensación aparece en el tamaño del archivo, no en la corrección: un flujo de módulo reescrito acaba cerca del tamaño de su texto fuente más una cabecera de dos bytes por cada bloque de 4096 bytes, no más pequeño como lo sería un bloque totalmente comprimido por tokens. Cada lector que implemente el lado de descompresión de la especificación, Excel incluido, sigue abriendo el resultado correctamente, porque un bloque en bruto es un CompressedChunk tan válido como uno comprimido por tokens
Qué deja intacto HotXLS cuando reescribe un módulo
La recompresión solo sustituye jamás parte del flujo del módulo. Cada flujo de módulo almacena primero su caché de rendimiento y después su fuente comprimida, y el flujo dir del proyecto registra exactamente dónde cae esa división para cada módulo en una entrada MODULEOFFSET; HotXLS lee ese desplazamiento, conserva cada byte anterior a él exactamente como lo encontró, y reconstruye solo el contenedor comprimido a partir de ese desplazamiento en adelante
El propio texto fuente hace el viaje de ida y vuelta a través de la página de códigos propia del proyecto VBA en lugar de UTF-8, la misma página de códigos heredada con la que Office escribió el proyecto en primer lugar. Una edición de SourceCode que introduce caracteres fuera del repertorio de esa página de códigos se sustituye silenciosamente con caracteres de reemplazo de mejor ajuste cuando HotXLS vuelve a codificar la cadena a bytes, no se rechaza, así que un carácter regional inusual colado en un comentario o en un literal de cadena es el lugar más probable donde notar la pérdida. Las referencias externas y los enlaces a bibliotecas dentro del mismo proyecto siguen una vía de preservación relacionada pero independiente, cubierta en el artículo complementario sobre la preservación de enlaces externos de VBA, y merece la pena leerlo antes de que una pasada de reescritura toque un proyecto que enlaza con otros libros o bibliotecas de tipos
¿Cómo se devuelven las macros reescritas a un libro?
Nada llama al paso de recompresión explícitamente, se ejecuta automáticamente en el momento en que se guarda un libro o un proyecto VBA independiente. TXLSVBAProject.ApplyChanges recorre cada módulo, recomprime aquellos cuyo SourceCode haya cambiado desde el último guardado, y reescribe solo el flujo de ese módulo; tanto el TXLSWorkbook.SaveAs clásico, cuando el destino de guardado conserva el formato original del archivo, como el TXLSXWorkbook.SaveAs de OOXML para un paquete XLSM habilitado para macros llaman a este método internamente antes de escribir nada en disco, y SaveVBAProjectToFile llama al mismo método cuando el destino es un archivo de proyecto VBA independiente en lugar de un libro completo
var
Wb: TXLSWorkbook;
begin
Wb := TXLSWorkbook.Create;
try
if Wb.LoadVBAProjectFromFile('LegacyMacros.ole') = 1 then
begin
Wb.VBAProject[1].SourceCode :=
StringReplace(Wb.VBAProject[1].SourceCode, 'OldServer', 'NewServer', [rfReplaceAll]);
Wb.SaveVBAProjectToFile('LegacyMacros_Patched.ole'); // ApplyChanges runs internally
end;
finally
Wb.Free;
end;
end;
var
Xlsx: TXLSXWorkbook;
Project: TXLSVBAProject;
begin
Xlsx := TXLSXWorkbook.Create;
try
Xlsx.Open('Dashboard.xlsm');
Project := Xlsx.ParsedVBAProject;
if Assigned(Project) then
begin
Project[1].SourceCode := StringReplace(Project[1].SourceCode,
'ConnStringV1', 'ConnStringV2', [rfReplaceAll]);
Xlsx.SaveAs('Dashboard.xlsm'); // SyncParsedVBAProject recompresses before the part is written
end;
finally
Xlsx.Free;
end;
end;
Los tres destinos comparten por debajo la misma mecánica de SourceCode y ApplyChanges; la única diferencia real entre ellos es qué llamada de guardado acaba disparando la recompresión
Dónde esto todavía se rompe
Dos modos de fallo son lo bastante comunes como para planificarlos antes de que una pasada de reescritura se ejecute contra archivos de producción. Un proyecto VBA firmado digitalmente deja de estar válidamente firmado en el momento en que cambia su fuente, ya que la firma cubre el contenido del proyecto; HotXLS no tiene forma de volver a firmar un proyecto en vuestro nombre, y Excel elimina o marca la firma la próxima vez que se abra el archivo, así que un proyecto de macros firmado necesita un paso de refirmado posterior si esa firma es algo que vuestro flujo de trabajo realmente comprueba. El segundo modo de fallo pertenece a cualquiera tentado de reimplementar este formato de compresión desde cero en lugar de usar una biblioteca que ya lo gestiona: un único bit equivocado en una cabecera de bloque, en el nibble de firma, en el campo de tamaño o en el indicador de compresión, produce un archivo que Excel se niega a abrir, normalmente detrás de una advertencia genérica de corrupción que no da ninguna pista de qué byte estaba mal, precisamente la clase de fallo que la estrategia de escritura en bloques en bruto descrita antes existe para evitar
Nada de esto requiere hacer ingeniería inversa del formato para usarlo. Los desarrolladores Delphi y C++Builder obtienen acceso de lectura y escritura a SourceCode, recompresión conforme a MS-OVBA y los tres destinos de reescritura descritos aquí como parte del componente HotXLS estándar, junto con el resto de su API de libros XLS clásico y OOXML