Preimenovanje trdo kodiranega sklica na delovni list v tisoč predlogah poročil z omogočenimi makri izključuje ročno odpiranje vsake datoteke v urejevalniku VBA. HotXLS, izvorna komponenta Excel za Delphi in C++Builder, ta primer obravnava tako, da izvorno kodo modula VBA izpostavi kot urejajočo lastnost SourceCode in vsako spremembo znova stisne z algoritmom stiskanja MS-OVBA, ki ga Microsoft določa za shranjevanje VBA, nato pa rezultat zapiše nazaj v klasično shrambo VBA XLS, samostojno datoteko projekta VBA ali delovni zvezek XLSM z omogočenimi makri. Na tej poti ni vključen niti Excelov primerek niti urejevalnik VBA niti snemalnik makrov
Zakaj tok modula VBA ni besedilna datoteka
Modul VBA v delovnem zvezku XLS ali samostojni datoteki projekta VBA ni izvorno besedilo v toku, ki čaka na branje — je majhen binarni vsebnik. Najprej je predpomnilnik za zmogljivost, bajti, ki jih Office uporabi za preskok ponovnega prevajanja modula ob nalaganju, ko se predpomnilnik še ujema z različico gostitelja, nato sledi dejansko izvorno besedilo, obdelano z lastniško shemo stiskanja, ki jo MS-OVBA določa posebej za shranjevanje VBA. Ta shema ni zip, ni deflate in ni nič, kar bi izvorno ustvarili Windowsovi API-ji za stiskanje, zato lahko večina Excelovih knjižnic tretjih oseb prebere izvorno kodo modula — dekompresija je lažji del težave — vendar je ne zapiše nazaj, saj pri ponovnem stiskanju že subtilno napačen bit povzroči datoteko, ki je Excel noče odpreti. Javni opisi bralne strani obstajajo, izvedbe zapisovalne strani, ki dejansko izvajajo ponovno stiskanje in ne le razpakirajo obstoječega modula za pregled, pa so dovolj redke, da to ostaja eden najmanj dokumentiranih kotičkov Excelovih datotečnih formatov
Kaj lastnost SourceCode v HotXLS dejansko spremeni
HotXLS vsak modul VBA predstavi kot objekt TXLSVBAModule z navadno lastnostjo SourceCode: WideString, dodelitev nove vrednosti pa je natanko tako preprosta, kot je videti: modul je v pomnilniku označen kot spremenjen, osnovni tok OLE pa se ne dotakne, dokler projekt ni shranjen. Projekt sam izhaja iz IXLSWorkbook.VBAProject v klasičnem mehanizmu XLS ali iz TXLSXWorkbook.ParsedVBAProject v mehanizmu OOXML z omogočenimi makri; oba vrneta TXLSVBAProject, katerega moduli so dostopni prek indekserja Item[] z indeksom od 1 in lastnosti Count, zato je paketna sprememba vseh modulov delovnega zvezka le zanka čez celoštevilski obseg
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;
Ta zanka je tudi oblika revizijskega prehoda. Preden se dotaknejo tisočih predlog, večina ekip najprej želi vedeti, koliko jih dejansko vsebuje makre in na kaj se ti makri sklicujejo; to je primer v ozadju delovnega okolja za revizijo in pretvorbo delovnih zvezkov — isti Project.Count, ki tukaj poganja prepisovalno zanko, tam postane štetje makrov po datotekah
V notranjosti vsebnika stiskanja MS-OVBA
Format stiskanja MS-OVBA pakira izvorne bajte v to, kar specifikacija imenuje CompressedContainer: en sam podpisni bajt, ki mora biti enak 0x01, nato pa zaporedje blokov CompressedChunk, od katerih vsak pokriva največ 4096 bajtov dekompresiranih podatkov. Glava bloka dolžine 16 bitov vsebuje tri polja — 3-bitni podpis, ki mora biti enak 3, 12-bitno polje velikosti in bit CompressedChunkFlag, ki označuje, ali so podatki bloka dobesedni bajti ali zaporedje stisnjeno z žetoni. Ko je zastavica nastavljena, so podatki zaporedje skupin po osmih žetonov s predpono zastavičnega bajta, vsak žeton pa je bodisi en sam dobesedni bajt bodisi CopyToken: povratna referenca odmika in dolžine v bajte, ki so bili dekompresirani prej v istem bloku, pri čemer se širina bitov, razdeljena med odmik in dolžino, spreminja glede na to, kako daleč v bloku je trenutno dekompresor. Ta del MS-OVBA (§2.4.1, Compression and Decompression) je mesto, kjer ročno napisana izvedba najpogosteje izgubi dan zaradi napake za ena pri izračunu širine bitov
Zakaj HotXLS zapisuje surove bloke namesto ujemanja žetonov
HotXLS se zapisovalni poti v celoti izogne delu algoritma, ki išče ujemanje žetonov. Pri ponovnem stiskanju spremenjenega modula se vsak blok zapiše z izbrisanim CompressedChunkFlag, kar pomeni, da vsebuje dobesedne bajte namesto žetonov povratnih referenc — to je dovoljeno v MS-OVBA, saj je lahko vsebnik stiskanja v celoti sestavljen iz nestisnjenih blokov, hkrati pa odstrani prav tisti del algoritma, ki ga je najtežje pravilno izvesti ročno: iskanje veljavnih povratnih referenc in pakiranje para odmika ter dolžine v širino bitov, odvisno od trenutnega položaja znotraj bloka. Kompromis se pokaže v velikosti datoteke, ne v pravilnosti — tok prepisanega modula je približno tako velik kot izvorno besedilo skupaj z dvobajtno glavo na vsak blok velikosti 4096 bajtov, ne pa manjši, kot bi bil v celoti z žetoni stisnjen blok. Vsak bralnik, ki izvaja dekompresijsko stran specifikacije, vključno z Excelom, rezultat še vedno pravilno odpre, ker je surovi blok prav tako veljaven CompressedChunk kot blok, stisnjen z žetoni
Kaj HotXLS pusti nedotaknjeno pri prepisovanju modula
Ponovno stiskanje vedno zamenja le del toka modula. Vsak tok modula najprej vsebuje predpomnilnik za zmogljivost in nato stisnjeno izvorno kodo, tok dir projekta pa v vnosu MODULEOFFSET natančno zabeleži, kje je ta ločnica za vsak modul; HotXLS prebere ta odmik, vse bajte pred njim ohrani natanko takšne, kot jih je našel, in znova sestavi le vsebnik stiskanja od odmika naprej
Samo izvorno besedilo se pretvori nazaj prek lastne kodne strani projekta VBA in ne prek UTF-8 — iste podedovane kodne strani, s katero je Office prvotno zapisal projekt. Pri urejanju SourceCode, ki uvede znake zunaj nabora te kodne strani, HotXLS pri ponovnem kodiranju niza v bajte tiho uporabi nadomestne znake z najboljšo približno ustreznostjo in spremembe ne zavrne, zato je nenavaden regionalni znak v komentarju ali znakovnem literalu najverjetnejše mesto, kjer boste opazili izgubo. Zunanji sklici in vezave knjižnic v istem projektu sledijo sorodni, vendar ločeni poti ohranjanja, opisani v spremljevalnem članku o ohranjanju zunanjih povezav VBA, ki ga je vredno prebrati, preden prepisovalni prehod obdela projekt s povezavami na druge delovne zvezke ali tipske knjižnice
Kako prepisane makre vrnete v delovni zvezek
Nič izrecno ne pokliče koraka ponovnega stiskanja — zažene se samodejno v trenutku, ko se delovni zvezek ali samostojni projekt VBA shrani. TXLSVBAProject.ApplyChanges pregleda vsak modul, znova stisne tiste, katerih SourceCode se je od zadnjega shranjevanja spremenil, in prepiše samo tok tega modula; klasični TXLSWorkbook.SaveAs, kadar cilj shranjevanja ohrani izvirni format datoteke, in OOXML TXLSXWorkbook.SaveAs za paket XLSM z omogočenimi makri oba interno pokličeta to metodo, preden se karkoli zapiše na disk, SaveVBAProjectToFile pa pokliče isto metodo, ko je cilj ločena datoteka projekta VBA in ne celoten delovni zvezek
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;
Vsi trije cilji si pod površjem delijo enako mehaniko SourceCode in ApplyChanges; edina prava razlika med njimi je, kateri klic shranjevanja na koncu sproži ponovno stiskanje
Kje se to še vedno lahko zalomi
Dva načina odpovedi sta dovolj pogosta, da ju je treba upoštevati, preden prepisovalni prehod zaženete nad produkcijskimi datotekami. Digitalno podpisan projekt VBA v trenutku spremembe izvorne kode ni več veljavno podpisan, saj podpis zajema vsebino projekta; HotXLS projekta ne more znova podpisati namesto vas, Excel pa ob naslednjem odpiranju datoteke podpis odstrani ali označi, zato podpisani projekt makrov potrebuje nadaljnji korak ponovnega podpisovanja, če vaš potek dela ta podpis dejansko preverja. Drugi način odpovedi zadeva vsakogar, ki bi želel to obliko stiskanja znova izvesti od začetka, namesto da bi uporabil knjižnico, ki jo že obvladuje: en sam napačen bit v glavi bloka, podpisnem niblu, polju velikosti ali zastavici stiskanja ustvari datoteko, ki je Excel noče odpreti, običajno za splošnim opozorilom o poškodbi, ki ne nakaže, kateri bajt je bil napačen — prav tak razred napak se strategija zapisovanja surovih blokov, opisana prej, skuša izogniti
Za uporabo ni potrebno povratno inženirstvo formata. Razvijalci Delphi in C++Builder dobijo dostop za branje in pisanje SourceCode, ponovno stiskanje, skladno z MS-OVBA, ter vse tri tukaj opisane cilje zapisovanja kot del standardne komponente HotXLS Component, skupaj s preostalim API-jem delovnega zvezka XLS in OOXML