Imaginez une tâche qui ne fait presque rien : ouvrir un classeur mensuel, écrire la date du jour dans une cellule, l'enregistrer. Faites-la tourner assez souvent dans un service et une réclamation finit par arriver. Les macros ont disparu, ou les taux de change liés affichent désormais #REF!, et l'équipe d'exploitation est convaincue que votre code les a supprimés. Il n'a rien supprimé. Ce qui s'est passé, la plupart du temps, c'est qu'un classeur à macros est reparti sous un simple nom .xlsx, et Excel a appliqué les règles de type de contenu d'ECMA-376 : un paquet dont le type de contenu ne déclare aucun VBA ne peut pas charger de projet VBA, que les octets soient là ou non. Le fichier n'est pas cassé. Il a été renommé vers un état où Excel a l'obligation d'en ignorer une partie
Les macros et les liens vers des classeurs externes sont les deux éléments que l'automatisation perd le plus sûrement, pour la même raison de fond. Tous deux vivent en dehors de la grille de cellules que le code d'édition touche réellement, si bien qu'un code qui raisonne en lignes et en colonnes les laissera tomber sans jamais émettre la moindre suppression. HotXLS est une bibliothèque native pour Delphi et C++Builder qui lit et écrit les formats XLS et XLSX sans Excel installé, et elle traite ces deux ressources comme des charges utiles transportées délibérément plutôt que comme des données recopiées par hasard. Voici ce que chacune exige de votre chemin de sauvegarde, et où les garanties s'arrêtent
Pourquoi ces deux ressources se comportent différemment lors d'une réécriture
Un projet VBA est un binaire opaque unique. Dans un paquet OOXML, c'est le fichier vbaProject.bin ; dans un fichier BIFF hérité, c'est un stockage OLE. Il existe exactement deux façons de le perdre : le module d'écriture ne le recopie jamais dans la sortie, ou la sortie reçoit un type de fichier qui l'interdit. Dans les deux cas, l'échec est total et silencieux. Le projet est présent ou il ne l'est pas
Un lien externe n'est pas du tout un bloc binaire. C'est un petit graphe de relations : un chemin ou une URL cible pointant vers un autre classeur, la liste des noms de feuilles que cette cible expose, et un cache facultatif des dernières valeurs vues dans ces feuilles pour qu'Excel puisse afficher quelque chose lorsque la cible est hors ligne. Ces trois parties ont des durées de vie différentes lors d'une réécriture, et une bibliothèque peut en préserver fidèlement certaines tout en abandonnant les autres sans bruit. Cette asymétrie mérite d'être précisée, car rien dans le code d'édition des cellules ne la fera remonter
Transporter un projet VBA à travers une réécriture XLSX
Côté XLSX, TXLSXWorkbook conserve la charge utile des macros telle quelle. La propriété VbaProject contient les octets bruts de vbaProject.bin dans une AnsiString, et une chaîne vide est la façon dont le modèle indique qu'il n'y a pas de macros. Autour d'elle se placent trois opérations : HasVbaProject répond si un projet est présent, ClearVbaProject le supprime volontairement, et LoadVbaProjectFromFile en injecte un extrait d'un modèle. Ce dernier appel vaut plus qu'il n'y paraît. Il permet à des classeurs générés de reprendre un projet de macros standard sans traîner un fichier modèle complet dans toute la chaîne
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'extension .xlsm n'est pas cosmétique : elle sélectionne le
// type de contenu à macros activées à l'intérieur du paquet.
Book.SaveAs('monthly-report.xlsm');
finally
Book.Free;
end;
end;
La ligne de sauvegarde est le point où tout le problème bascule. Un classeur porteur d'un projet VBA doit être écrit avec la sémantique des macros activées, et HotXLS applique celle-ci lorsque le nom cible se termine par .xlsm. Donnez-lui .xlsx à la place et Excel refuse les macros, alors même que les octets sont physiquement présents dans le paquet et se désérialiseraient sans problème. L'extension n'est pas décorative ; elle sélectionne le type de contenu qui indique à Excel qu'un projet VBA a le droit d'exister. La plupart du temps, il vous suffit de transporter la charge utile. Lorsque vous devez y lire, par exemple pour lister les noms de modules dans un rapport d'audit, ParsedVBAProject expose un modèle de modules analysé tandis que VbaProject reste les octets d'origine intacts
Réutiliser les macros de classeurs XLS hérités
La façade BIFF reflète cette panoplie avec une étape supplémentaire. HasVBAProject sonde un fichier chargé, SaveVBAProjectToFile écrit le stockage du projet sur disque, et LoadVBAProjectFromFile le relit dans un autre classeur. Ce détour par un fichier rend simple une corvée de modernisation courante : extraire les macros d'un modèle de l'époque 2003 et les implanter dans une sortie XLS fraîchement générée, sans avoir besoin du modèle d'origine à l'exécution
var
Src, Dst: IXLSWorkbook; // références d'interface : pas de Free manuel
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;
Le modèle mémoire est le piège ici, et il fonctionne à l'inverse de la classe XLSX. TXLSWorkbook se manipule via l'interface à comptage de références IXLSWorkbook, si bien que vous ne le libérez jamais à la main ; le TXLSXWorkbook du côté XLSX est un objet ordinaire que vous devez encadrer d'un try..finally et libérer. Mélangez les deux conventions dans une même unité et les plantages par double libération suivent. Une autre frontière mérite le respect : gardez extraction et injection à l'intérieur d'un seul format de fichier. Le stockage de projet BIFF et le vbaProject.bin OOXML sont cousins, pas le même conteneur, et une chaîne qui doit émettre des macros dans les deux formats devrait conserver un modèle de macros distinct pour chacun
Liens externes : la carte survit, les valeurs en cache non
Pour les classeurs XLSX, HotXLS expose les liens externes par la collection ExternalLinks. Chaque TXLSXExternalLink porte un Target, le chemin ou l'URL du classeur distant, plus une liste SheetNames nommant les feuilles qu'il référence. Tous deux survivent intacts à un cycle ouverture-sauvegarde, et vous pouvez aussi construire un lien de toutes pièces :
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;
La frontière se situe un niveau plus bas que la liste des cibles. HotXLS fait circuler la carte des liens, c'est-à-dire la cible et les noms de feuilles, mais il n'analyse ni ne réécrit les valeurs de cellules en cache que OOXML garde dans l'élément sheetDataSet du lien. Ce cache est ce qui permet à Excel d'afficher un dernier nombre connu lorsque le fichier source est hors ligne, et un classeur généré part sans lui. La conséquence retombe sur le destinataire, pas sur vous. Ouvrez un tel fichier là où la cible est injoignable, un portable hors VPN ou un partage renommé, et les formules qui dépendent du lien se résolvent en #REF! ou restent bloquées derrière une invite de mise à jour. Deux règles en découlent. Ne promettez pas qu'un classeur généré affichera ses valeurs liées hors ligne. Et lisez un ExternalLinks.Count non nul comme une condition préalable de livraison plutôt que comme une fonctionnalité : chaque cible doit être joignable depuis l'endroit où le fichier sera réellement ouvert
Ce que le lecteur XLS préserve octet par octet
Pour les structures qu'il ne modélise pas, le côté BIFF apporte une réponse différente : les laisser exactement telles qu'elles ont été trouvées. Les caches et les vues de tableaux croisés dynamiques (la famille d'enregistrements SX*), les définitions de QueryTable, les connexions de données externes, les vues personnalisées, les images d'en-tête et les enregistrements de thème traversent tous un cycle ouverture-sauvegarde sous forme de blocs d'enregistrements bruts, non analysés et non modifiés. Les références externes elles-mêmes font l'aller-retour par les enregistrements EXTERNSHEET et SupBook sous-jacents. Il n'existe pas d'API typée pour les créer côté XLS, mais un lien existant survit intact à l'édition
La préservation octet par octet est une vraie garantie, avec un tranchant. Comme rien ne lit une structure préservée, vos modifications ne peuvent pas la corrompre. Pour la même raison, rien ne la met à jour non plus. Insérez des lignes en travers d'une zone que vise un cache de tableau croisé ou une table de requête préservée, et la structure garde ses coordonnées d'origine pendant que les données en dessous se décalent. Le fichier reste un XML ou un BIFF valide ; le sens, lui, a discrètement glissé hors alignement, et aucune erreur ne se déclenche pour vous le dire. La disposition défendable consiste à garder les modifications générées sur des feuilles qui ne portent aucune structure préservée, la même discipline que celle qui protège les feuilles verrouillées et configurées pour l'impression dans notre article sur la protection des feuilles et la mise en page
Vérifier le fichier que vous avez réellement écrit
Les deux modes de défaillance sont silencieux à l'écriture, si bien que l'assertion qui compte se fait en rouvrant la sortie plutôt qu'en faisant confiance au code qui l'a produite. Trois contrôles couvrent presque tout. Rouvrez le fichier et confirmez que HasVbaProject renvoie toujours true chaque fois que des macros étaient attendues, ce qui attrape en un seul test une charge utile perdue et une mauvaise extension. Lisez ExternalLinks.Count et comparez-le au décompte relevé avant la réécriture. Puis ouvrez une fois le fichier dans Excel, macros désactivées, car la validation du type de contenu par Excel est plus stricte que celle de n'importe quelle bibliothèque, et Excel est le programme sur lequel vos clients jugeront le fichier
Rien de tout cela n'exige une analyse complète à l'entrée. Lorsque les classeurs arrivent en volume et que vous devez seulement trier ceux qui portent du contenu réglementé, le sondage léger décrit dans notre article sur le listage des feuilles et l'inspection légère des classeurs vous permet d'aiguiller les fichiers porteurs de macros et de liens vers une chaîne plus stricte avant que la première réécriture ne démarre
Quelques questions reviennent assez souvent pour mériter une réponse directe. HotXLS n'exécute jamais les macros qu'il préserve : il n'y a pas de moteur VBA dans la bibliothèque, seulement la mécanique permettant de stocker, copier, extraire et injecter le projet en tant que données. Sur un serveur, c'est une propriété de sécurité qui mérite d'être énoncée, puisqu'une macro hostile traversant la chaîne reste inerte jusqu'à ce qu'un Excel de bureau ouvre le fichier et qu'un utilisateur active le contenu. Convertir un .xlsm en .xlsx en gardant les macros est impossible, et c'est la règle du format plutôt qu'une limite de la bibliothèque : le type de contenu .xlsx déclare un classeur sans macros, si bien que les seuls dénouements honnêtes sont de rester en .xlsm ou d'appeler ClearVbaProject et de livrer un fichier qui n'en a véritablement aucune. Le renommage silencieux est le seul choix qui ne satisfait personne. Et lorsque des cellules liées affichent #REF! après une réécriture, la cause est le cache de valeurs manquant évoqué plus haut : le nouveau fichier porte la cible mais pas les nombres mis en cache, si bien qu'Excel doit résoudre la source à l'ouverture, et un chemin injoignable ou dépendant de l'environnement le met en échec. Soit vous garantissez que la cible est joignable, soit vous écrivez les valeurs calculées dans les cellules avant la livraison et supprimez complètement la dépendance
Modifier les classeurs des autres consiste surtout à préserver des choses que vous n'avez pas écrites et que vous ne comprenez pas entièrement. Les fonctions d'aller-retour pour VBA et liens externes décrites ici sont livrées avec le HotXLS Delphi Component pour Delphi et C++Builder, ainsi que les propriétés d'audit qui vous permettent de détecter le contenu réglementé dès qu'un fichier arrive