Teknisk artikel

Bevaring af VBA-makroer og eksterne links, når Delphi-kode omskriver en arbejdsbog

Overvej en opgave, der næsten intet gør: Åbn en månedlig arbejdsbog, skriv dagens dato i en enkelt celle, gem den igen. Kør dette gennem en importtjeneste tilstrækkelig mange gange, og der vil alligevel komme en klage. Makroerne er væk, eller de linkede valutakurser viser nu #REF!, og driftsteamet er overbevist om, at din kode slettede dem. Den slettede intet. Hvad der normalt skete, var, at en makroaktiveret arbejdsbog blev sendt afsted under et almindeligt .xlsx-navn, og Excel adlød ECMA-376-reglerne for indholdstype: En pakke, hvis indholdstype ikke erklærer VBA, kan ikke indlæse et VBA-projekt, uanset om bytes rent faktisk ligger i filen. Filen gik ikke i stykker. Den blev blot omdøbt til en tilstand, hvor Excel er forpligtet til at ignorere en del af den

Makroer og eksterne arbejdsbogslinks er de to ting, som automatisering mister mest pålideligt, af samme underliggende årsag. Begge lever uden for cellegitteret, som redigeringskoden rent faktisk rører ved, så kode, der ræsonnerer i rækker og kolonner, vil kassere dem uden nogensinde at have slettet dem aktivt. HotXLS is et indfødt Delphi- og C++Builder-bibliotek, der læser og skriver XLS og XLSX uden at have Excel installeret, og det behandler begge aktiver som nyttelast, det bevidst bærer med, frem for data, det tilfældigvis kopierer. Det følgende beskriver, hvad hver især kræver af din lagringssti, og hvor garantierne ophører

Hvorfor disse to aktiver opfører sig forskelligt under en omskrivning

Et VBA-projekt er en enkelt uigennemsigtig (opaque) binær fil. I en OOXML-pakke er det filen vbaProject.bin; i en ældre BIFF-fil er det et OLE-lager. Der er præcis to måder at miste det på: Skriveren kopierer det aldrig til outputtet, eller outputtet får en filtype, der forbyder det. Begge fejltyper sker stiltiende og er totale. Projektet er til stede, eller også er det ikke

Et eksternt link er slet ikke en blob. Det er a lille graf af relationer: En målsti eller en URL, der peger på en anden arbejdsbog, listen over de arknavne, som målet eksponerer, og en valgfri cache af de værdier, der sidst blev set i disse ark, så Excel kan vise noget, når målet er offline. Disse tre dele har forskellige levetider under en omskrivning, og et bibliotek kan trofast bevare nogle af dem, mens det stiltiende kasserer andre. Denne asymmetri er værd at være præcis omkring, da intet i celleredigeringskoden vil bringe den til overfladen

Medbringelse af et VBA-projekt gennem en XLSX-omskrivning

På XLSX-siden bevarer TXLSXWorkbook makro-nyttelasten ordret. Egenskaben VbaProject indeholder de rå bytes fra vbaProject.bin i en AnsiString, og en tom streng er måden, hvorpå modellen angiver, at der ikke er nogen makroer. Omkring den ligger tre funktioner: HasVbaProject svarer på, om et projekt er til stede, ClearVbaProject fjerner det bevidst, og LoadVbaProjectFromFile indsætter et projekt, der er udtaget fra en skabelon. Det sidstnævnte kald er mere værd, end det ser ud til. Det lader genererede arbejdsbøger opsamle et standardmakroprojekt uden at trække en hel skabelonfil gennem import-pipelinen

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;

Lagringslinjen er der, hvor hele problemet opstår. En arbejdsbog, der indeholder et VBA-projekt, skal skrives med makroaktiveret semantik, og HotXLS anvender denne, når målnavnet ender på .xlsm. Giver du den i stedet .xlsx, afviser Excel makroerne, selvom bytes fysisk er til stede i pakken og ville kunne deserialiseres fint. Filtypen er ikke til pynt; den vælger den indholdstype, der fortæller Excel, at et VBA-projekt må eksistere. Det meste af tiden behøver du kun at overføre nyttelasten. Når du har brug for at læse i den, f.eks. for at liste modulnavne til en revisionsrapport, eksponerer ParsedVBAProject en fortolket modulmodel, mens VbaProject forbliver de oprindelige, uberørte bytes

Genbrug af makroer fra ældre XLS-arbejdsbøger

BIFF-facaden afspejler dette værktøjssæt med et enkelt ekstra trin. HasVBAProject undersøger en indlæst fil, SaveVBAProjectToFile skriver projektlageret til disken, og LoadVBAProjectFromFile læser et ind i en anden arbejdsbog. Omvejen via en fil gør en almindelig moderniseringsopgave ligetil: Løft makroerne ud af en model fra 2003-æraen og placer dem i et frisk genereret XLS-output uden krav om en oprindelig skabelon under afviklingen

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;

Hukommelsesmodellen er fælden her, og den kører modsat af XLSX-klassen. TXLSWorkbook holdes via den referencetællede IXLSWorkbook-grænseflade, så du aldrig frigør den manuelt; XLSX-klassen TXLSXWorkbook er et almindeligt objekt, som du skal pakke ind i try..finally og frigøre (free). Blandes de to konventioner i samme kodeenhed, kan det medføre nedbrud pga. dobbelt frigørelse (double-free). Endnu en grænse, der er værd at respektere: Hold ekstraktion og injektion inden for ét enkelt filformat. BIFF-projektlageret og OOXML-filen vbaProject.bin er beslægtede, men ikke samme beholder, og en importpipeline, der skal levere makroer i begge formater, bør vedligeholde en separat makroskabelon til hvert format

Eksterne links: Kortet overlever, de cachede værdier gør ikke

For XLSX-arbejdsbøger eksponerer HotXLS eksterne links via samlingen ExternalLinks. Hver TXLSXExternalLink bærer et Target — stien eller URL'en til den eksterne arbejdsbog — samt listen SheetNames, der navngiver de ark, den refererer til. Begge overlever en åbne-og-gemme-cyklus intakt, og du kan også opbygge et link fra bunden:

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;

Grænsen ligger et niveau dybere end mållisten. HotXLS foretager en rundtur af linkkortet — dvs. målet og arknavnene — men det fortolker eller omskriver ikke de cachede celleværdier, som OOXML opbevarer i linkets sheetDataSet-element. Denne cache er det, der lader Excel vise et sidst kendt tal, når kildefilen er offline, og en genereret arbejdsbog leveres uden denne. Konsekvensen rammer modtageren, ikke dig. Åbnes en sådan fil, hvor målet er utilgængeligt — f.eks. en bærbar computer uden for VPN'et eller et netværksdrev, der er blevet omdøbt — vil formlerne, der afhænger af linket, resultere i #REF! eller gå i stå bag en opdateringsprompt. Derfor falder to regler ud af dette: Lov ikke, at en genereret arbejdsbog kan vise sine eksternt linkede værdier offline. Og læs et ExternalLinks.Count, der er forskelligt fra nul, som en leveringsforudsætning frem for en funktion: Hvert enkelt mål skal være tilgængeligt fra det sted, hvor filen rent faktisk åbnes

Hvad XLS-læseren bevarer byte-for-byte

For strukturer, som den ikke modellerer, har BIFF-siden et andet svar: Efterlad dem nøjagtigt som de blev fundet. Pivot-caches og pivot-visninger (SX*-postfamilien), QueryTable-definitioner, eksterne dataforbindelser, brugerdefinerede visninger, sidehovedbilleder og temaposter passerer alle gennem en åbne-og-gemme-cyklus som rå postblokke (record blocks), ufortolket og uændret. Eksterne referencer foretager selv en rundtur gennem de underliggende EXTERNSHEET- og SupBook-poster. Der er intet typedefineret API til at oprette dem på XLS-siden, men et eksisterende link overlever redigering uberørt

Byte-for-byte-bevaring er en reel garanti med en skarp kant. Fordi intet læser en bevaret struktur, kan dine redigeringer ikke beskadige den. Af samme grund er der heller intet, der opdaterer den. Indsætter du rækker i et område, som een bevaret pivot-cache eller query table peger på, beholder strukturen sine oprindelige koordinater, mens de underliggende data flytter sig. Filen er stadig gyldig XML eller BIFF; indholdstypen er blot lydløst drevet ud af justering, og ingen fejl udløses for at advare dig. Det forsvarlige layout er at holde genererede redigeringer på ark, der ikke indeholder bevarede strukturer, hvilket er den samme disciplin, der beskytter låste og printkonfigurerede ark i vores artikel om beskyttelse af regneark og sideopsætning

Verificering af den fil, du rent faktisk skrev

Begge fejltyper sker stiltiende ved skrivetidspunktet, så den konstatering, der betyder noget, gøres ved at genåbne outputtet frem for at stole på den kode, der producerede det. Tre kontroller dækker næsten alt: Genåbn filen og bekræft, at HasVbaProject still returnerer true, når der forventes makroer, hvilket fanger en tabt nyttelast og en forkert filtype i en enkelt test. Læs ExternalLinks.Count og sammenlign det med antallet før omskrivningen. Åbn derefter filen én gang i Excel med makroer deaktiveret, da Excels indholdstypevalidering er strengere end noget biblioteks, og Excel is det program, dine kunder vil bedømme filen ud fra

Intet af det kræver en fuld fortolkning ved indlæsningen. Når arbejdsbøger ankommer i store mængder, og du blot skal sortere, hvilke der indeholder styret indhold, lader den lette undersøgelse i vores artikel om arklister og let arbejdsbogsinspektion dig dirigere makroholdige og linkede filer til en strengere pipeline, før den første omskrivning overhovedet kører

Nogle få spørgsmål opstår ofte nok til at blive besvaret direkte. HotXLS afvikler aldrig de makroer, det bevarer: Der er intet VBA-kørselsmiljø i biblioteket, kun maskineriet til at gemme, kopiere, udtage og indsætte projektet som data. På en server er dette en sikkerhedsegenskab, der er værd at nævne, da en fjendtlig makro, der passerer gennem import-pipelinen, forbliver inaktiv, indtil en desktop-Excel åbner filen, og en bruger aktiverer indholdet. Konvertering af en .xlsm til .xlsx med bevarelse af makroerne er ikke muligt, og det er formatets regel frem for en biblioteksbegrænsning: Indholdstypen .xlsx erklærer en makrofri arbejdsbog, så de eneste ærlige resultater er at forblive .xlsm eller kalde ClearVbaProject og levere en fil, der reelt ingen har. Den stiltiende omdøbning er det eneste valg, der ikke tilfredsstiller nogen. Og når linkede celler viser #REF! efter en omskrivning, er årsagen den manglende værdicache diskuteret ovenfor: Den nye fil bærer målet, men ikke de cachede tal, så Excel skal opløse kilden ved åbningstidspunktet, og en utilgængelig eller miljørelativ sti forhindrer dette. Garanter enten, at målet er tilgængeligt, eller skriv beregnede værdier ind i cellerne før levering, og drop afhængigheden helt

At redigere andres arbejdsbøger handler for det meste om at bevare ting, du ikke selv har skrevet og ikke fuldt ud forstår. Rundtursfunktionerne til VBA og eksterne links, der er beskrevet her, leveres med HotXLS Component til Delphi og C++Builder sammen med revisions-egenskaberne, der lader dig detektere styret indhold, i samme øjeblik en fil ankommer