Techninis straipsnis

VBA makrokomandų ir išorinių nuorodų išsaugojimas, kai Delphi kodas perrašo darbaknygę

Įsivaizduokite užduotį, kuri beveik nieko nedaro: atidaryti mėnesio darbaknygę, įrašyti šios dienos datą į vieną langelį ir išsaugoti. Vykdant tai per paslaugą pakankamai dažnai, vis tiek sulaukiama skundų. Makrokomandos dingo arba susietieji valiutų kursai dabar rodo #REF!, o eksploatacijos komanda įsitikinusi, kad jūsų kodas juos ištrynė. Jis nieko neištrynė. Paprastai nutiko taip, kad makrokomandas palaikanti darbaknygė buvo išsaugota su paprastu .xlsx plėtiniu, o „Excel" laikėsi ECMA-376 turinio tipo taisyklių: paketas, kurio turinio tipas nedeklaruoja VBA, negali įkelti VBA projekto, nepriklausomai nuo to, ar baitai yra pat ten. Failas nesugedo. Jis buvo pervadintas į tokią būseną, kai „Excel" privalo ignoruoti jo dalį.

Makrokomandos ir išorinės darbaknygių nuorodos yra du dalykai, kuriuos automatizavimas patikimiausiai praranda dėl tos pačios pagrindinės priežasties. Abu gyvena už langelių tinklelio, kurį faktiškai paliečia redagavimo kodas, todėl kodas, operuojantis eilutėmis ir stulpeliais, juos pašalins niekada neišleisdamas jokio trynimo. HotXLS yra natūrali Delphi ir C++Builder biblioteka, skirta skaityti ir rašyti XLS ir XLSX be įdiegto „Excel", ir ji abi išteklius traktuoja kaip apkrovas, kurias sąmoningai perneša, o ne kaip duomenis, kuriuos tiesiog nukopijuoja. Toliau pateikiama, ko kiekvienam iš jų reikia iš jūsų išsaugojimo kelio ir kur garantijos baigiasi.

Kodėl šie du ištekliai skirtingai elgiasi perrašymo metu

VBA projektas yra vienas nepermatomas dvejetainis failas. OOXML pakete tai failas vbaProject.bin; senesnėje BIFF byloje tai OLE saugykla. Yra tiksliai du būdai jį prarasti: rašytojas niekada jo nukopijuoja į išvestį arba išvestis gauna failo tipą, kuris jo draudžia. Bet kokia nesėkmė yra visiška ir tylioji. Projektas arba yra, arba jo nėra.

Išorinė nuoroda iš viso nėra dvejetainė duomenų dalis. Tai mažas ryšių grafikas: tikslinis kelias arba URL, nurodantis į kitą darbaknygę, lapų pavadinimų sąrašas, kurį tas tikslas atskleidžia, ir neprivaloma paskutinių reikšmių, rodomų iš tų lapų, talpykla, kad „Excel" galėtų kažką parodyti, kai tikslas yra nepasiekiamas. Tos trys dalys turi skirtingą gyvavimo laiką perrašymo metu, ir biblioteka kai kurias gali ištikimai išsaugoti, o kitas tyliai praleisti. Tas asimetrija yra ta dalis, kurią verta tiksliai suprasti, nes langelių redagavimo kodas jos neatskleis.

VBA projekto pernešimas per XLSX perrašymą

XLSX pusėje, TXLSXWorkbook išlaiko makrokomandų apkrovą pažodžiui. Ypatybė VbaProject laiko neapdorotus vbaProject.bin baitus AnsiString, o tuščia eilutė reiškia, kad makrokomandų nėra. Aplink ją yra trys operacijos: HasVbaProject atsako, ar projektas yra, ClearVbaProject tyčia jį pašalina, o LoadVbaProjectFromFile injektuoja vieną, ištrauktą iš šablono. Tas paskutinis iškvietimas yra verčiau nei atrodo. Jis leidžia sugeneruotoms darbaknygėms paimti standartinį makrokomandų projektą netempdamos viso šablono failo per konvejerį.

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Data');
    Sheet.Cells[1, 1].Value := 'Refreshed ' + DateTimeToStr(Now);

    Book.LoadVbaProjectFromFile('macros\vbaProject.bin');
    if not Book.HasVbaProject then
      raise Exception.Create('VBA payload failed to load');

    // The .xlsm extension is not cosmetic: it selects the
    // macro-enabled content type inside the package.
    Book.SaveAs('monthly-report.xlsm');
  finally
    Book.Free;
  end;
end;

Išsaugojimo eilutė yra ta vieta, kur visa problema susiveda. Darbaknygė, kurioje yra VBA projektas, turi būti įrašyta su makrokomandas palaikančia semantika, o HotXLS jas taiko, kai tikslinio failo pavadinimas baigiasi .xlsm. Paduokite jai .xlsx ir „Excel" atmeta makrokomandas, net jei baitai fiziškai yra pakete ir puikiai deserializuotųsi. Plėtinys nėra dekoracija; jis parenka turinio tipą, kuris informuoja „Excel", kad VBA projektas gali egzistuoti. Dažniausiai tereikia apkrovą tik pernešti. Kai reikia į ją gilintis, pavyzdžiui, siekiant išvardyti modulių pavadinimus audito ataskaitai, ParsedVBAProject atskleidžia išanalizuotą modulių modelį, o VbaProject lieka originalūs nepakeisti baitai.

Makrokomandų pakartotinis naudojimas iš senų XLS darbaknygių

BIFF fasadas atspindi tą įrankių rinkinį su vienu papildomu žingsniu. HasVBAProject tikrina įkeltą failą, SaveVBAProjectToFile įrašo projekto saugyklą į diską, o LoadVBAProjectFromFile įskaito vieną atgal į kitą darbaknygę. Apsuka per failą padaro įprastą modernizavimo darbą paprastą: iškelkite makrokomandas iš 2003 m. modelio ir įrašykite jas į naujai sugeneruotą XLS išvestį, paleidimo metu nereikalaujant originalaus šablono.

var
  Src, Dst: IXLSWorkbook;   // interface references: no manual Free
begin
  Src := TXLSWorkbook.Create;
  if Src.Open('legacy-model.xls') <= 0 then
    raise Exception.Create('Cannot open legacy model');
  if Src.HasVBAProject then
    Src.SaveVBAProjectToFile('extracted-vba.bin');

  Dst := TXLSWorkbook.Create;
  Dst.Sheets.Add.Name := 'Report2026';
  Dst.LoadVBAProjectFromFile('extracted-vba.bin');
  Dst.SaveAs('report-with-macros.xls');
end;

Atminties modelis čia yra spąstai ir jis veikia priešingai nei XLSX klasė. TXLSWorkbook laikomas per suskaičiuotų nuorodų sąsają IXLSWorkbook, todėl jo niekada nepaleidžiate rankiniu būdu; XLSX klasė TXLSXWorkbook yra paprastas objektas, kurį turite apvynioti try..finally ir atlaisvinti. Sumaišykite abi konvencijas viename vienete ir seksis dvigubas atlaisvinimas su gedimais. Dar viena vertybė, kurią verta gerbti: laikykite ištraukimą ir injekciją vieno failo formato viduje. BIFF projekto saugykla ir OOXML vbaProject.bin yra giminaičiai, o ne tas pats konteineris, ir konvejeris, kuriam reikia išskleisti makrokomandas abiem formatais, turi turėti atskirą makrokomandų šabloną kiekvienam.

Išorinės nuorodos: žemėlapis išgyvena, talpyklos reikšmės ne

XLSX darbaknygėms HotXLS atskleidžia išorines nuorodas per ExternalLinks kolekciją. Kiekvienas TXLSXExternalLink turi Target, t. y. nuotolinio failo kelią arba URL, bei SheetNames sąrašą, įvardijantį lapus, į kuriuos jis nurodo. Abu išlieka nepažeisti atidarymo ir išsaugojimo cikle, ir taip pat galite sukurti nuorodą nuo nulio:

var
  Link: TXLSXExternalLink;
begin
  Link := Book.ExternalLinks.Add('\\fileserver\finance\fx-rates-2026.xlsx');
  Link.SheetNames.Add('FX');

  if Book.ExternalLinks.Count > 0 then
    Writeln(Format('%d external link(s): delivery requires reachable targets',
      [Book.ExternalLinks.Count]));
end;

Riba yra vienu lygiu giliau nei tikslų sąrašas. HotXLS perduoda nuorodų žemėlapį, t. y. tikslą ir lapų pavadinimus, tačiau neišanalizuoja ir neperrašo talpyklos langelių reikšmių, kurias OOXML laiko nuorodos sheetDataSet elemente. Ši talpykla leidžia „Excel" rodyti paskutinį žinomą skaičių, kai šaltinio failas nepasiekiamas, o sugeneruota darbaknygė siunčiama be jos. Pasekmė tenka gavėjui, o ne jums. Atidarykite tokį failą, kur tikslas nepasiekiamas, pvz., nešiojamuoju kompiuteriu be VPN arba pervardinta bendrinama vieta, ir formulės, priklausančios nuo nuorodos, rodo #REF! arba stringa už atnaujinimo raginimo. Todėl kyla dvi taisyklės. Nepažadėkite, kad sugeneruota darbaknygė rodys išoriškai susietas reikšmes neprisijungus. Ir skaitykite nenulinį ExternalLinks.Count kaip pristatymo išankstinę sąlygą, o ne funkciją: kiekvienas tikslas turi būti pasiekiamas iš ten, kur failas iš tikrųjų bus atidarytas.

Ką XLS skaitytuvas išsaugo baitas po baito

Struktūroms, kurių jis nemodelizuoja, BIFF pusė turi kitą atsakymą: palikti jas lygiai tokias, kokios buvo rastos. Sukamojo lentelės talpyklos ir sukamojo lentelės rodiniai (SX* įrašų šeima), „QueryTable" apibrėžtys, išoriniai duomenų ryšiai, pasirinktiniai rodiniai, antraščių paveikslėliai ir temų įrašai visi praeina atidarymo ir išsaugojimo ciklą kaip neapdoroti įrašų blokai, neišanalizuoti ir nepakeisti. Išorinės nuorodos pačios cirkuliuoja per esminius EXTERNSHEET ir SupBook įrašus. XLS pusėje nėra tipizuoto kūrimo API jiems, tačiau esama nuoroda išlieka nepakeista.

Baitas po baito išsaugojimas yra tikra garantija su aštriu kraštu. Kadangi niekas neskaito išsaugotos struktūros, jūsų redagavimai jos negali sugadinti. Dėl tos pačios priežasties niekas jos ir neatnaujina. Įdėkite eilutes per sritį, į kurią nurodo išsaugota sukamojo lentelės talpykla ar užklausos lentelė, ir struktūra laiko savo pradinias koordinates, o duomenys po ja keičiasi. Failas vis dar yra galiojantis XML arba BIFF; reikšmė tyliai nuklydo nuo suderinimo ir jokia klaida jums to nepraneš. Tinkamiausia išdėstymas yra laikyti sugeneruotus redagavimus lapuose, kuriuose nėra išsaugotų struktūrų, tai yra ta pati drausmė, kuri apsaugo užrakintus ir spausdintus lapus mūsų straipsnyje apie darbalapio apsaugą ir puslapio sąranką.

Tikrojo įrašyto failo patikrinimas

Abu gedimo režimai yra tylūs įrašymo metu, todėl svarbus patikrinimas atliekamas pakartotinai atidarius išvestį, o ne pasitikint ją sukūrusiu kodu. Trys patikrinimai apima beveik viską. Atidarykite failą iš naujo ir patvirtinkite, kad HasVbaProject vis dar grąžina true, kai buvo tikimasi makrokomandų, nes tai vienu bandymu aptinka prarastą apkrovą ir neteisingą plėtinį. Perskaitykite ExternalLinks.Count ir palyginkite su skaičiumi prieš perrašymą. Tada atidarykite failą vieną kartą „Excel" su išjungtomis makrokomandomis, nes „Excel" turinio tipo patvirtinimas yra griežtesnis nei bet kurios bibliotekos, o „Excel" yra programa, kuria jūsų klientai vertins failą.

Visa tai nereikalauja visiško analizavimo pakeliui. Kai darbaknygės ateina masiškai ir tereikia sugrupuoti, kurios iš jų turi valdomo turinio, lengvasis tikrinimas, aprašytas mūsų straipsnyje apie lapų išvardinimą ir lengvąjį darbaknygės tikrinimą, leidžia nukreipti makrokomandų turinčius ir susietus failus į griežtesnį konvejerį prieš pradedant pirmą perrašymą.

Keli klausimai kyla pakankamai dažnai, kad į juos tiesiogiai atsakytume. HotXLS niekada nevykdo makrokomandų, kurias išsaugo: bibliotekoje nėra VBA vykdymo laiko aplinkos, tik mechanizmas, skirtas saugoti, kopijuoti, ištraukti ir injektuoti projektą kaip duomenis. Serveryje tai yra saugos savybė, kurią verta nurodyti, nes priešiška makrokomanda, praeinanti per konvejerį, lieka neveikli, kol staliniame kompiuteryje „Excel" atidaro failą ir vartotojas įgalina turinį. Konvertuoti .xlsm į .xlsx ir išlaikyti makrokomandas neįmanoma, ir tai yra formato taisyklė, o ne bibliotekos apribojimas: .xlsx turinio tipas deklaruoja darbaknygę be makrokomandų, todėl vieninteliai sąžiningi rezultatai yra likti .xlsm arba iškviesti ClearVbaProject ir išsiųsti failą, kuriame jų tikrai nėra. Tylus pervadinimas yra vienintelis pasirinkimas, kuris nieko netenkina. Ir kai susietieji langeliai rodo #REF! po perrašymo, priežastis yra trūkstama reikšmių talpykla, aptarta aukščiau: naujas failas turi tikslą, bet ne talpyklos skaičius, todėl „Excel" turi išspręsti šaltinį atidarymo metu, o nepasiekiamas arba aplinkai santykinis kelias tai sužlugdo. Arba garantuokite, kad tikslas yra pasiekiamas, arba prieš pristatymą įrašykite apskaičiuotas reikšmes į langelius ir visiškai atsisakykite priklausomybės.

Kitų žmonių darbaknygių redagavimas iš esmės yra dalykų, kurių jūs neparašėte ir visiškai nesuprantate, išsaugojimas. Čia aprašytos VBA ir išorinių nuorodų cirkuliavimo priemonės pateikiamos su HotXLS Component Delphi ir C++Builder kūrėjams, kartu su audito savybėmis, kurios leidžia aptikti valdomą turinį tą akimirką, kai failas ateina.