Redenumirea unei referințe de foaie de calcul codificată în cod-sursă pe o mie de șabloane de raport cu macro-uri activate exclude deschiderea manuală a fiecărui fișier în editorul VBA. HotXLS, componenta Excel nativă pentru Delphi și C++Builder, gestionează acest caz expunând sursa unui modul VBA ca o proprietate SourceCode editabilă și recomprimând fiecare editare cu algoritmul de compresie MS-OVBA pe care Microsoft îl definește pentru stocarea VBA, scriind rezultatul înapoi în stocarea VBA din XLS clasic, un fișier de proiect VBA independent, sau un registru de lucru XLSM cu macro-uri activate. Nicio instanță Excel, niciun editor VBA, și niciun recorder de macro nu este implicat nicăieri pe acea cale
De ce un flux de modul VBA nu este un fișier text
Un modul VBA în interiorul unui registru de lucru XLS sau un fișier de proiect VBA independent nu este text sursă așezat într-un flux așteptând să fie citit — este un mic container binar. Un cache de performanță compilat vine primul, octeții pe care Office îi folosește pentru a sări peste recompilarea modulului la încărcare atunci când cache-ul încă se potrivește cu versiunea gazdă, iar textul sursă efectiv urmează, trecut printr-o schemă de compresie proprietară pe care MS-OVBA o definește specific pentru stocarea VBA. Acea schemă nu este zip, nu este deflate, și nu este nimic ce produc nativ API-urile de compresie Windows, ceea ce este exact motivul pentru care majoritatea bibliotecilor Excel terțe pot citi sursa unui modul — decompresia este jumătatea mai ușoară a problemei — dar se opresc înainte de a o scrie înapoi, întrucât recompresia este locul unde un bit subtil greșit produce un fișier pe care Excel refuză să îl deschidă. Există prezentări publice ale părții de citire; implementările pentru partea de scriere care chiar exercită recompresia, nu doar despachetează un modul existent pentru inspecție, sunt suficient de rare încât acesta rămâne unul din cele mai puțin documentate colțuri ale formatelor de fișier Excel
Ce schimbă de fapt proprietatea SourceCode a HotXLS?
HotXLS reprezintă fiecare modul VBA ca un obiect TXLSVBAModule cu o proprietate simplă SourceCode: WideString, iar atribuirea unei valori noi este exact la fel de simplă cum arată: modulul este marcat murdar (dirty) în memorie, iar nimic nu atinge fluxul OLE de bază până când proiectul este salvat. Proiectul însuși vine din IXLSWorkbook.VBAProject pe motorul XLS clasic sau TXLSXWorkbook.ParsedVBAProject pe motorul OOXML cu macro-uri activate, ambele returnând un TXLSVBAProject ale cărui module stau în spatele unui indexer Item[] pe bază de 1 și o proprietate Count, așa că o editare în lot pe fiecare modul dintr-un registru de lucru este doar o buclă peste un interval de întregi
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;
Acea buclă este de asemenea forma unei treceri de audit. Înainte ca o mie de șabloane să fie atinse, majoritatea echipelor vor mai întâi să știe câte dintre ele chiar poartă macro-uri și la ce fac referire acele macro-uri, ceea ce este scenariul din spatele băncii de lucru de audit și conversie a registrelor de lucru — același Project.Count care conduce o buclă de rescriere aici devine acolo o statistică de macro-uri per fișier
În interiorul containerului de compresie MS-OVBA
Formatul de compresie MS-OVBA împachetează octeții sursă în ceea ce specificația numește CompressedContainer: un singur octet de semnătură, obligat să fie egal cu 0x01, urmat de o secvență de blocuri CompressedChunk, fiecare acoperind până la 4096 de octeți de date decomprimate. Un antet de bloc pe 16 biți poartă trei câmpuri — o semnătură pe 3 biți care trebuie să fie egală cu 3, un câmp de dimensiune pe 12 biți, și un bit CompressedChunkFlag care marchează dacă încărcătura blocului este octeți literali sau o secvență comprimată prin token-uri. Când steagul este setat, încărcătura este o serie de grupuri prefixate de octet-steag de câte opt token-uri, iar fiecare token este fie un singur octet literal, fie un CopyToken: o referință înapoi offset/lungime în octeții deja decomprimați anterior în același bloc, cu lățimea de biți împărțită între offset și lungime schimbându-se în funcție de cât de departe în bloc se află decompresorul în acel moment. Această parte din MS-OVBA (§2.4.1, Compression and Decompression) este locul unde o implementare scrisă manual pierde cel mai adesea o zi la o eroare de-un-bit în acel calcul de lățime de biți
De ce scrie HotXLS blocuri brute în loc să potrivească token-uri
Calea de scriere a HotXLS ocolește complet jumătatea de potrivire de token-uri a acelui algoritm. Când recomprimă un modul editat, fiecare bloc iese cu CompressedChunkFlag șters, ceea ce înseamnă că blocul conține octeți literali, nu token-uri de referință înapoi — legal conform MS-OVBA, întrucât un container comprimat poate fi format în întregime din blocuri necomprimate, iar asta elimină exact partea algoritmului care este cea mai grea de obținut corect manual: găsirea referințelor înapoi valide și împachetarea unei perechi offset/lungime într-o lățime de biți care depinde de poziția curentă în interiorul blocului. Compromisul apare în dimensiunea fișierului, nu în corectitudine — un flux de modul rescris ajunge aproape de dimensiunea textului său sursă plus un antet de doi octeți per bloc de 4096 octeți, nu mai mic așa cum ar fi un bloc complet comprimat prin token-uri. Fiecare cititor care implementează partea de decompresie a specificației, Excel inclusiv, tot deschide corect rezultatul, pentru că un bloc brut este la fel de valid ca un CompressedChunk ca unul comprimat prin token-uri
Ce lasă HotXLS neatins când rescrie un modul
Recompresia înlocuiește întotdeauna doar o parte a fluxului de modul. Fiecare flux de modul își stochează cache-ul de performanță primul și sursa comprimată al doilea, iar fluxul dir al proiectului înregistrează exact unde cade acea diviziune pentru fiecare modul într-o intrare MODULEOFFSET; HotXLS citește acel offset, păstrează fiecare octet dinainte de el exact așa cum i-a găsit, și reconstruiește doar containerul comprimat de la offset înainte
Textul sursă însuși face drumul dus-întors prin propriul cod de pagină al proiectului VBA, nu UTF-8 — același cod de pagină moștenit cu care Office a scris proiectul în primul rând. O editare SourceCode care introduce caractere din afara repertoriului acelui cod de pagină este substituită silențios cu caractere de înlocuire cea-mai-bună-potrivire atunci când HotXLS re-codifică șirul înapoi la octeți, nu respinsă, așa că un caracter regional neobișnuit plasat într-un comentariu sau un literal de șir este locul cel mai probabil de a observa pierderea. Referințele externe și legăturile de bibliotecă din același proiect urmează o cale de conservare înrudită dar separată, acoperită în articolul complementar despre conservarea legăturilor externe VBA, și merită citit înainte ca o trecere de rescriere să atingă un proiect care leagă spre alte registre de lucru sau biblioteci de tip
Cum se întorc macro-urile rescrise înapoi într-un registru de lucru?
Nimic nu apelează explicit pasul de recompresie — rulează automat în momentul în care un registru de lucru sau un proiect VBA independent este salvat. TXLSVBAProject.ApplyChanges parcurge fiecare modul, recomprimă pe cele al căror SourceCode s-a schimbat de la ultima salvare, și rescrie doar fluxul acelui modul; TXLSWorkbook.SaveAs clasic, atunci când destinația de salvare păstrează formatul original al fișierului, și TXLSXWorkbook.SaveAs OOXML pentru un pachet XLSM cu macro-uri activate ambele îl apelează intern înainte ca ceva să fie scris pe disc, iar SaveVBAProjectToFile apelează aceeași metodă când ținta este un fișier de proiect VBA detașat, nu un registru de lucru complet
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;
Toate cele trei destinații împart aceeași mecanică SourceCode și ApplyChanges dedesubt; singura diferență reală între ele este care apel de salvare ajunge să declanșeze recompresia
Unde încă se rupe asta
Două moduri de eșec sunt suficient de comune încât să merite planificate înainte ca o trecere de rescriere să ruleze pe fișiere de producție. Un proiect VBA semnat digital încetează să mai fie semnat valid în momentul în care sursa sa se schimbă, întrucât semnătura acoperă conținutul proiectului; HotXLS nu are nicio modalitate de a re-semna un proiect în numele dvs., iar Excel elimină sau marchează semnătura data viitoare când fișierul se deschide, așa că un proiect de macro-uri semnat are nevoie de un pas de re-semnare în aval, dacă acea semnătură este ceva ce fluxul dvs. de lucru chiar verifică. Al doilea mod de eșec aparține oricui tentat să reimplementeze acest format de compresie de la zero, în loc să folosească o bibliotecă care îl gestionează deja: un singur bit greșit într-un antet de bloc, în nibble-ul de semnătură, câmpul de dimensiune, sau steagul de comprimare, produce un fișier pe care Excel refuză să îl deschidă, de obicei în spatele unui avertisment generic de corupție care nu dă niciun indiciu despre ce octet era greșit — exact clasa de bug pe care strategia de scriere cu bloc brut descrisă mai devreme există pentru a o evita
Nimic din toate acestea nu necesită inginerie inversă a formatului pentru a fi folosit. Dezvoltatorii Delphi și C++Builder primesc acces de citire și scriere SourceCode, recompresie conformă MS-OVBA, și toate cele trei destinații de scriere înapoi descrise aici ca parte a componentei HotXLS standard, alături de restul API-ului său de registru de lucru XLS clasic și OOXML