Renombrar una referencia de hoja de cálculo codificada de forma fija a través de mil plantillas de informe habilitadas para macros descarta abrir cada archivo en el editor de VBA a mano. HotXLS, el componente Excel nativo para Delphi y C++Builder, maneja ese caso exponiendo el código fuente de un módulo VBA como una propiedad editable SourceCode y recomprimiendo cada edición con el algoritmo de compresión MS-OVBA que Microsoft define para el almacenamiento VBA, escribiendo el resultado de vuelta en el almacenamiento VBA del XLS clásico, un archivo de proyecto VBA independiente, o un libro XLSM habilitado para macros. No hay ninguna instancia de Excel, ningún editor de VBA, y ninguna grabadora de macros involucrada en ningún punto de esa ruta
Por qué un flujo de módulo VBA no es un archivo de texto
Un módulo VBA dentro de un libro XLS o un archivo de proyecto VBA independiente no es texto fuente sentado en un flujo esperando ser leído: es un pequeño contenedor binario. Primero viene 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 luego sigue el texto fuente real, pasado por un esquema de compresión propietario que MS-OVBA define específicamente para el almacenamiento 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— mientras se quedan cortas al escribirlo de vuelta, ya que la recompresión es donde un bit sutilmente equivocado produce un archivo que Excel se niega a abrir. Existen análisis públicos del lado de lectura; las implementaciones del lado de escritura que realmente ejercitan la recompresión, en lugar de simplemente desempaquetar un módulo existente para inspección, son lo bastante escasas como para que esto siga siendo uno de los rincones menos 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 simple SourceCode: WideString, y asignarle un nuevo valor es exactamente tan simple como parece: el módulo se marca como sucio en memoria, y nada toca el flujo OLE subyacente hasta que se guarda el proyecto. El propio proyecto viene de IXLSWorkbook.VBAProject en el motor XLS clásico o TXLSXWorkbook.ParsedVBAProject en el motor OOXML habilitado para macros, ambos devolviendo un TXLSVBAProject cuyos módulos están 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 en un libro es simplemente un bucle sobre un rango entero
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 también es la forma de una pasada de auditoría. Antes de que se toquen mil plantillas, la mayoría de los equipos primero quiere saber cuántas de ellas realmente llevan macros y a qué referencian esas macros, que es el escenario detrás del banco de trabajo de auditoría y conversión de libros —el mismo Project.Count que impulsa un bucle de reescritura aquí se convierte ahí en un conteo de macros por archivo
Dentro del contenedor de compresión MS-OVBA
El formato de compresión de MS-OVBA empaqueta los bytes 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. Un encabezado de fragmento 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 útil del fragmento son bytes literales o una secuencia comprimida por tokens. Cuando la bandera está establecida, la carga útil es una serie de grupos de ocho tokens prefijados por byte de bandera, y cada token es o bien un único byte literal o un CopyToken: una referencia hacia atrás de desplazamiento/longitud a bytes ya descomprimidos anteriormente en el mismo fragmento, con la división de ancho de bits entre desplazamiento y longitud cambiando según cuán adentro del fragmento se encuentre actualmente el descompresor. Esta parte de MS-OVBA (§2.4.1, Compression and Decompression) es donde una implementación hecha a mano más a menudo pierde un día por un error de uno de más o de menos en ese cálculo de ancho de bits
Por qué HotXLS escribe fragmentos crudos en lugar de emparejar tokens
La ruta de escritura de HotXLS evita por completo la mitad de emparejamiento de tokens de ese algoritmo. Cuando recomprime un módulo editado, cada fragmento sale con el CompressedChunkFlag desactivado, lo que significa que el fragmento contiene bytes literales en lugar de tokens de referencia hacia atrás —legal bajo MS-OVBA, ya que a un contenedor comprimido se le permite consistir enteramente de fragmentos sin comprimir, y elimina precisamente la parte del algoritmo que es 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 fragmento. La compensación aparece en el tamaño de archivo, no en la corrección —un flujo de módulo reescrito termina cerca del tamaño de su texto fuente más un encabezado de dos bytes por cada bloque de 4096 bytes, no más pequeño como lo sería un fragmento completamente comprimido por tokens. Cada lector que implementa el lado de descompresión de la especificación, Excel incluido, sigue abriendo el resultado correctamente, porque un fragmento crudo es un CompressedChunk tan válido como uno comprimido por tokens
Qué deja intacto HotXLS cuando reescribe un módulo
La recompresión solo reemplaza parte del flujo del módulo. Cada flujo de módulo almacena primero su caché de rendimiento y segundo 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 desde el desplazamiento en adelante
El texto fuente en sí hace el viaje de ida y vuelta a través de la propia página de códigos 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 recodifica la cadena de vuelta a bytes, no se rechaza, así que un carácter regional inusual dejado caer en un comentario o literal de cadena es el lugar más probable donde notar la pérdida. Las referencias externas y los enlaces de biblioteca dentro del mismo proyecto siguen una ruta de preservación relacionada pero separada, cubierta en el artículo complementario sobre la preservación de enlaces externos de VBA, y vale la pena leerlo antes de que una pasada de reescritura toque un proyecto que enlaza hacia otros libros o bibliotecas de tipos
¿Cómo se vuelven a incorporar 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 los que tienen SourceCode cambiado desde el último guardado, y reescribe solo el flujo de ese módulo; el clásico TXLSWorkbook.SaveAs, cuando el destino del guardado conserva el formato original del archivo, y el TXLSXWorkbook.SaveAs de OOXML para un paquete XLSM habilitado para macros ambos lo llaman internamente antes de que se escriba 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 la misma mecánica de SourceCode y ApplyChanges por debajo; la única diferencia real entre ellos es qué llamada de guardado termina disparando la recompresión
Dónde esto todavía se rompe
Dos modos de fallo son lo bastante comunes como para planificar en torno a ellos 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 código fuente, ya que la firma cubre el contenido del proyecto; HotXLS no tiene forma de volver a firmar un proyecto en su nombre, y Excel descarta o marca la firma la próxima vez que se abre el archivo, así que un proyecto de macros firmado necesita un paso de refirmado posterior si esa firma es algo que su flujo de trabajo realmente comprueba. El segundo modo de fallo pertenece a cualquiera tentado a reimplementar este formato de compresión desde cero en lugar de usar una biblioteca que ya lo maneja: un solo bit equivocado en un encabezado de fragmento, en el nibble de firma, el campo de tamaño, o la bandera comprimida, 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 bug que la estrategia de escritura de fragmento crudo 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 escritura de vuelta descritos aquí como parte del componente HotXLS estándar, junto con el resto de su API de libros XLS clásico y OOXML