Articolo tecnico

HotXLS: conservare macro VBA e collegamenti esterni

Immaginate un lavoro che non fa quasi nulla: apre una cartella di lavoro mensile, scrive la data di oggi in una cella, la risalva. Fatelo girare in un servizio abbastanza spesso e un reclamo arriva comunque. Le macro sono sparite, oppure i tassi di cambio collegati ora riportano #REF!, e il gruppo operativo è convinto che il vostro codice le abbia cancellate. Non ha cancellato nulla. Quel che di solito è successo è che una cartella abilitata alle macro è uscita con un nome .xlsx semplice, ed Excel ha obbedito alle regole sui tipi di contenuto di ECMA-376: un pacchetto il cui tipo di contenuto non dichiara VBA non può caricare un progetto VBA, che i byte siano lì o no. Il file non si è rotto. È stato rinominato in uno stato in cui Excel è tenuto a ignorarne una parte

Le macro e i collegamenti a cartelle di lavoro esterne sono le due cose che l'automazione perde più regolarmente, per la stessa ragione di fondo. Entrambe vivono fuori dalla griglia di celle che il codice di modifica tocca davvero, quindi il codice che ragiona per righe e colonne le lascia cadere senza mai emettere una cancellazione. HotXLS è una libreria nativa per Delphi e C++Builder che legge e scrive XLS e XLSX senza Excel installato, e tratta entrambe le risorse come carichi che trasporta di proposito anziché come dati che capita di copiare. Quel che segue è ciò di cui ciascuna ha bisogno dal vostro percorso di salvataggio, e dove finiscono le garanzie

Perché queste due risorse si comportano diversamente durante una riscrittura

Un progetto VBA è un unico binario opaco. In un pacchetto OOXML è il file vbaProject.bin; in un file BIFF legacy è un archivio OLE. Ci sono esattamente due modi per perderlo: il writer non lo copia mai nel risultato, oppure il risultato riceve un tipo di file che lo vieta. In entrambi i casi il guasto è totale e silenzioso. Il progetto c'è oppure non c'è

Un collegamento esterno non è affatto un blob. È un piccolo grafo di relazioni: un percorso o un URL di destinazione che punta a un'altra cartella di lavoro, l'elenco dei nomi di foglio che quella destinazione espone e una cache facoltativa dei valori visti l'ultima volta in quei fogli, così che Excel possa mostrare qualcosa quando la destinazione è irraggiungibile. Quelle tre parti hanno durate diverse durante una riscrittura, e una libreria può conservarne fedelmente alcune mentre ne lascia cadere altre in silenzio. È su quell'asimmetria che vale la pena essere precisi, perché nulla nel codice che modifica le celle la farà emergere

Diagramma di confronto fra il blob di un progetto VBA e le tre parti di un collegamento a cartella di lavoro esterna che HotXLS trasporta attraverso una riscrittura in Delphi
Un progetto VBA sopravvive a una riscrittura come carico binario tutto o niente, mentre un collegamento esterno è un piccolo grafo la cui destinazione, i nomi di foglio e i valori in cache possono essere conservati o persi in modo indipendente

Portare un progetto VBA attraverso una riscrittura XLSX

Sul lato XLSX, TXLSXWorkbook conserva letteralmente il carico delle macro. La proprietà VbaProject contiene i byte grezzi di vbaProject.bin dentro una AnsiString, e una stringa vuota è il modo in cui il modello dice che non ci sono macro. Attorno a essa stanno tre operazioni: HasVbaProject risponde se un progetto sia presente, ClearVbaProject lo rimuove di proposito e LoadVbaProjectFromFile ne inietta uno estratto da un modello. Quest'ultima chiamata vale più di quanto sembri. Permette alle cartelle di lavoro generate di adottare un progetto di macro standard senza trascinare un intero file modello lungo la pipeline

Diagramma di flusso di una chiamata di salvataggio in Delphi dove l'estensione .xlsm seleziona il tipo di contenuto abilitato alle macro e .xlsx fa rifiutare le macro a Excel in silenzio
HotXLS trasporta i byte grezzi di vbaProject.bin attraverso il salvataggio, e l'estensione .xlsm è ciò che seleziona il tipo di contenuto abilitato alle macro che Excel richiede
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');

    // L'estensione .xlsm non è cosmetica: seleziona il
    // tipo di contenuto abilitato alle macro dentro il pacchetto.
    Book.SaveAs('monthly-report.xlsm');
  finally
    Book.Free;
  end;
end;

La riga del salvataggio è dove tutto il problema si gioca. Una cartella di lavoro che contiene un progetto VBA va scritta con semantica abilitata alle macro, e HotXLS la applica quando il nome di destinazione finisce in .xlsm. Passategli invece .xlsx ed Excel rifiuta le macro, anche se i byte sono fisicamente presenti nel pacchetto e si deserializzerebbero benissimo. L'estensione non è una decorazione; seleziona il tipo di contenuto che dice a Excel che un progetto VBA ha diritto di esistere. Il più delle volte vi basta trasportare il carico. Quando dovete leggerne dentro, poniamo per elencare i nomi dei moduli in un report di verifica, ParsedVBAProject espone un modello dei moduli analizzato mentre VbaProject resta i byte originali intatti

Riusare le macro da cartelle di lavoro XLS legacy

La facciata BIFF rispecchia quel corredo di strumenti con un passo in più. HasVBAProject sonda un file caricato, SaveVBAProjectToFile scrive su disco l'archivio del progetto e LoadVBAProjectFromFile ne rilegge uno dentro un'altra cartella di lavoro. La deviazione attraverso un file rende immediato un compito di ammodernamento comune: sollevate le macro da un modello del 2003 e piantatele nell'output XLS appena generato, senza bisogno del modello originale a runtime

var
  Src, Dst: IXLSWorkbook;   // riferimenti a interfacce: nessuna Free manuale
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;

Il modello di memoria è la trappola qui, e va nella direzione opposta rispetto alla classe XLSX. TXLSWorkbook viene tenuto attraverso l'interfaccia a conteggio di riferimenti IXLSWorkbook, quindi non lo liberate mai a mano; il TXLSXWorkbook di XLSX è un oggetto normale che dovete avvolgere in try..finally e liberare. Mescolate le due convenzioni in una sola unit e arrivano i crash da doppia liberazione. Un altro limite da rispettare: tenete estrazione e iniezione dentro un solo formato di file. L'archivio di progetto BIFF e il vbaProject.bin di OOXML sono cugini, non lo stesso contenitore, e una pipeline che deve emettere macro in entrambi i formati dovrebbe tenere un modello di macro separato per ciascuno

Collegamenti esterni: la mappa sopravvive, i valori in cache no

Per le cartelle di lavoro XLSX, HotXLS espone i collegamenti esterni attraverso la collezione ExternalLinks. Ogni TXLSXExternalLink porta un Target, il percorso o URL della cartella remota, più un elenco SheetNames che nomina i fogli richiamati. Entrambi sopravvivono intatti a un ciclo di apertura e salvataggio, e potete anche costruire un collegamento da zero:

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;

Il limite sta un livello più in profondità dell'elenco delle destinazioni. HotXLS fa il round-trip della mappa dei collegamenti, cioè della destinazione e dei nomi di foglio, ma non analizza né riscrive i valori di cella in cache che OOXML conserva nell'elemento sheetDataSet del collegamento. Quella cache è ciò che permette a Excel di mostrare un ultimo numero noto quando il file sorgente è irraggiungibile, e una cartella di lavoro generata viene consegnata senza. La conseguenza ricade sul destinatario, non su di voi. Aprite un file simile dove la destinazione è irraggiungibile, un portatile fuori dalla VPN o una condivisione rinominata, e le formule che dipendono dal collegamento si risolvono in #REF! o restano ferme dietro una richiesta di aggiornamento. Da qui discendono due regole. Non promettete che una cartella generata mostrerà offline i valori collegati dall'esterno. E leggete un ExternalLinks.Count diverso da zero come precondizione di consegna anziché come funzionalità: ogni destinazione deve essere raggiungibile da dove il file verrà davvero aperto

Diagramma di quali parti di un collegamento a cartella di lavoro esterna di HotXLS sopravvivano a una riscrittura e di cosa accada quando i valori in cache dietro sheetDataSet sono assenti offline
HotXLS fa il round-trip della destinazione del collegamento e dei suoi nomi di foglio, ma i valori di cella in cache dietro sheetDataSet non vengono portati nel file generato

Cosa conserva byte per byte il lettore XLS

Per le strutture che non modella, il lato BIFF ha una risposta diversa: lasciarle esattamente come le trova. Cache e viste di tabella pivot (la famiglia di record SX*), definizioni QueryTable, connessioni a dati esterni, viste personalizzate, immagini di intestazione e record di tema attraversano tutti un ciclo di apertura e salvataggio come blocchi di record grezzi, non analizzati e non modificati. I riferimenti esterni stessi fanno il round-trip attraverso i record sottostanti EXTERNSHEET e SupBook. Sul lato XLS non esiste una API tipizzata per crearli, ma un collegamento esistente sopravvive intatto alle modifiche

La conservazione byte per byte è una garanzia autentica con uno spigolo vivo. Poiché nulla legge una struttura conservata, le vostre modifiche non possono corromperla. Per la stessa ragione, nulla la aggiorna. Inserite righe in una regione a cui punta una cache pivot o una query table conservata, e la struttura mantiene le coordinate originali mentre i dati sottostanti si spostano. Il file è ancora XML o BIFF valido; il significato è andato in silenzio fuori allineamento, e nessun errore scatta per avvisarvi. La disposizione difendibile è tenere le modifiche generate su fogli che non contengono strutture conservate, che è la stessa disciplina che protegge i fogli bloccati e configurati per la stampa nel nostro articolo su protezione dei fogli e impostazione della pagina

Verificare il file che avete davvero scritto

Entrambe le modalità di guasto sono silenziose al momento della scrittura, quindi l'asserzione che conta si fa riaprendo il risultato anziché fidandosi del codice che lo ha prodotto. Tre controlli coprono quasi tutto. Riaprite il file e confermate che HasVbaProject restituisca ancora vero ogni volta che ci si aspettavano macro, il che intercetta in un solo test sia un carico perso sia un'estensione sbagliata. Leggete ExternalLinks.Count e confrontatelo con il conteggio precedente alla riscrittura. Poi aprite il file una volta in Excel con le macro disabilitate, perché la convalida dei tipi di contenuto di Excel è più severa di quella di qualsiasi libreria, ed Excel è il programma con cui i vostri clienti giudicheranno il file

Nulla di tutto ciò richiede un'analisi completa in ingresso. Quando le cartelle di lavoro arrivano in volume e vi serve solo smistare quali portino contenuto soggetto a controlli, il sondaggio leggero descritto nel nostro articolo sull'elenco dei fogli e l'ispezione leggera delle cartelle di lavoro vi permette di instradare i file con macro e con collegamenti in una pipeline più severa prima ancora che parta la prima riscrittura

Alcune domande ricorrono abbastanza spesso da meritare una risposta diretta. HotXLS non esegue mai le macro che conserva: nella libreria non c'è alcun runtime VBA, solo la meccanica per memorizzare, copiare, estrarre e iniettare il progetto come dati. Su un server è una proprietà di sicurezza che vale la pena dichiarare, dato che una macro ostile che attraversa la pipeline resta inerte finché un Excel desktop non apre il file e un utente non abilita il contenuto. Convertire un .xlsm in .xlsx mantenendo le macro non è possibile, ed è una regola del formato più che un limite della libreria: il tipo di contenuto .xlsx dichiara una cartella di lavoro priva di macro, quindi gli unici esiti onesti sono restare .xlsm oppure chiamare ClearVbaProject e consegnare un file che davvero non ne ha. La rinomina silenziosa è l'unica scelta che non soddisfa nessuno. E quando le celle collegate mostrano #REF! dopo una riscrittura, la causa è la cache di valori mancante di cui sopra: il nuovo file porta la destinazione ma non i numeri in cache, quindi Excel deve risolvere la sorgente all'apertura, e un percorso irraggiungibile o relativo all'ambiente lo sconfigge. O garantite che la destinazione sia raggiungibile, oppure scrivete i valori calcolati nelle celle prima della consegna ed eliminate del tutto la dipendenza

Modificare le cartelle di lavoro altrui è in gran parte il lavoro di conservare cose che non avete scritto e non comprendete del tutto. Le funzionalità di round-trip per VBA e collegamenti esterni descritte qui sono distribuite con HotXLS Delphi Component per Delphi e C++Builder, insieme alle proprietà di verifica che vi permettono di rilevare il contenuto soggetto a controlli nel momento in cui un file arriva