Un backend de reporting Delphi qui émet du .xlsx depuis des années hérite d'une nouvelle exigence : les règles de marchés publics d'un client du secteur public imposent une sortie OpenDocument Spreadsheet, et les analystes de ce compte renvoient leurs modifications sous forme de fichiers .ods enregistrés depuis LibreOffice. Le même code doit donc désormais écrire de l'ODS et le relire. HotXLS, la bibliothèque tableur native en Object Pascal de losLab pour Delphi et C++Builder, gère les deux sens sans qu'Excel ni LibreOffice soient installés nulle part. Ce qu'elle ne fait pas, c'est rendre les deux sens symétriques. L'export emporte bien plus que l'import ne récupère, et une équipe qui suppose le contraire verra formules et mise en forme s'évaporer quelque part entre la révision du client et le rapport suivant, sans aucune erreur à montrer du doigt
La prise en charge ODS vit sur la façade XLSX, pas sur celle de XLS
HotXLS livre deux hiérarchies de classes indépendantes dans un seul paquet : TXLSWorkbook dans l'unité lxHandle pour les fichiers .xls binaires BIFF8, et TXLSXWorkbook dans l'unité lxHandleX pour les paquets OOXML .xlsx. Chaque point d'entrée OpenDocument — OpenODS, SaveAsODS, GetODSSheetNames — pend de TXLSXWorkbook. Ce placement n'a rien d'arbitraire. Un paquet ODS, tel que spécifié dans OASIS ODF 1.3, est une archive zip portant un membre mimetype, un manifeste et un corps content.xml, ce qui en fait un cousin structurel du zip OOXML ; BIFF8 est un flux binaire d'enregistrements des années 1990 qui n'a rien de commun avec cela
Ce placement a une conséquence pratique : un classeur .xls hérité ne peut pas devenir .ods en un seul appel. Vous faites d'abord passer le contenu BIFF dans le modèle XLSX, avec SaveXLSWorkbookAsXLSX de l'unité lxXlsxExport, vous rouvrez le résultat par TXLSXWorkbook, puis vous exportez depuis là. La passerelle n'est pas sans perte, et il vaut mieux en connaître les manques avant de bâtir dessus. Elle copie les valeurs, les formules, les formats de nombre, les polices, les remplissages et les largeurs de colonnes. Elle abandonne les bordures, les plages fusionnées, les commentaires, les graphiques et la mise en forme conditionnelle. Une source .xls à la mise en forme chargée atteindra l'ODS plus dépouillée qu'elle en est partie, et c'est une propriété de la passerelle, pas de l'écrivain ODS
La détection est automatique côté import. La simple méthode Open reconnaît un paquet ODS à son membre mimetype, en se rabattant sur un contrôle du content.xml de premier niveau quand ce membre est absent, si bien qu'un chemin de code générique du type « ouvrir ce que l'utilisateur a déposé » n'a pas besoin de renifler lui-même les extensions. Après ouverture, la propriété SourceFormat indique quelle branche a joué
Exporter vers ODS avec TODSExportOptions
L'appel d'export tient en une ligne ; l'objet d'options qui l'entoure porte les décisions sur lesquelles un relecteur reviendra plus tard :
var
Book: TXLSXWorkbook;
Opts: TODSExportOptions;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-report.xlsx');
Opts := TODSExportOptions.Create; // l'appelant le possède et le libère
try
Opts.Generator := 'ReportService 4.2'; // remplacement de meta:generator
Opts.IncludeCharts := True;
Opts.IncludeImages := True;
Book.SaveAsODS('quarterly-report.ods', Opts);
finally
Opts.Free;
end;
finally
Book.Free;
end;
end;
L'objet d'options appartient à l'appelant. HotXLS ne le libérera pas, ce qui explique pourquoi le try..finally interne est là et n'est pas facultatif. Les deux propriétés qui changent la sortie, au lieu de simplement l'étiqueter, méritent un examen plus attentif. Régler IncludeCharts := False fait plus que masquer les graphiques : cela retire du paquet les sous-documents de graphiques et leurs entrées de manifeste, ce qui est exactement ce que vous voulez quand le consommateur est un pipeline de données qui trébucherait dessus. Generator remplace la chaîne ODF meta:generator, qui indique sinon HotXLS/<version> ; remplacez-la quand un outillage en aval identifie les producteurs de fichiers pour aiguiller le support. Si rien de tout cela ne s'applique, laissez complètement tomber l'objet d'options. Appeler SaveAs(FileName, xlsxOpenDocumentSpreadsheet) revient au même que SaveAsODS avec les valeurs par défaut, et les surcharges sur flux des deux méthodes vous laissent écrire le paquet droit dans une réponse HTTP sans fichier temporaire
Ce que le chemin d'import lit — et ce qu'il saute délibérément
Lisez cette partie attentivement avant de promettre à quiconque une fidélité en aller-retour. L'import ODS dans HotXLS est délibérément un chemin allégé. Il préserve les valeurs scalaires des cellules et le résultat mis en cache que chaque formule portait au moment de la sauvegarde, et il déploie dans la grille les lignes et colonnes répétées. Il n'apporte ni les styles, ni les expressions de formules ODS, ni les dessins
Le choix sur les formules est celui qui risque le plus de mordre, et il a été fait exprès. Une cellule ODF range deux choses côte à côte : l'expression de formule, écrite dans le dialecte OpenFormula défini en ODF 1.3 partie 4, et la dernière valeur que l'application productrice a calculée pour elle. Traduire OpenFormula en syntaxe de formules Excel est un problème de conversion de dialecte à part entière, avec de vrais cas limites autour du vocabulaire des fonctions, de la syntaxe des références et des modèles d'erreurs. Lire plutôt la valeur mise en cache contourne toute cette classe de traductions fautives silencieuses, si bien que les nombres importés sont exactement ceux que l'expéditeur a vus en dernier. Le prix à payer, c'est qu'ils arrivent en nombres, non en formules vivantes qui les ont produits
Le mode de défaillance autour duquel concevoir en découle directement : une feuille dont les totaux étaient corrects quand LibreOffice l'a enregistrée en dernier s'importe avec des nombres corrects, mais ces nombres sont désormais des constantes. Modifiez une cellule d'entrée, recalculez, et rien ne bouge — la formule a disparu, seul son résultat final demeure. Si le flux de travail a besoin de formules vivantes après l'import, rétablissez-les par programme à partir de vos propres règles métier via Cell.Formula, qui sur la façade XLSX prend l'expression sans signe égal initial
Concevoir autour d'un aller-retour asymétrique
L'export produit son rendu à partir du modèle de classeur complet en mémoire : valeurs, styles et, si vous les demandez, graphiques et images. L'import ne renvoie que des valeurs. La branche .xlsx vers .ods est donc de haute fidélité, tandis que la branche .ods vers .xlsx ramène les valeurs et les résultats mis en cache, mais aucun style ni formule vivante. Enchaînez les deux et l'asymétrie se cumule. Un cycle complet .xlsx vers .ods vers .xlsx écrit tout fidèlement à l'aller et perd les styles et les formules au retour, alors même que rien n'a échoué à aucune des deux étapes
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // format détecté automatiquement
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Les valeurs et les résultats de formules mis en cache sont présents
// après un import ODS ; les styles et les formules vivantes, non.
// Reconstruisez ce dont dépend le pipeline aval avant d'enregistrer.
Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
Book.SaveAs('vendor-revision.xlsx');
end;
finally
Book.Free;
end;
Le motif d'architecture qui en découle : traitez les fichiers .ods entrants comme des flux de données, non comme des documents à modifier sur place. Gardez le classeur canonique en .xlsx, lisez les valeurs dans les révisions clients, et émettez du ODS frais à la demande depuis la copie canonique. La vérification appartient aux deux camps — ouvrez les fichiers exportés dans LibreOffice Calc, le consommateur ODF de référence, et dans Excel, qui lit l'ODS depuis des années mais diverge de LibreOffice aux marges de la prise en charge des graphiques et des styles. Le nombre de feuilles, une poignée de cellules clés et la présence des graphiques suffisent comme contrôle rapide par profil d'export
Trier un fichier ODS avant de s'engager dans un import
Quand un point d'entrée accepte des dépôts, lister les noms de feuilles coûte bien moins cher qu'une analyse complète et attrape tôt les surprises structurelles :
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
raise Exception.Create('not a readable ODS package');
if Names.IndexOf('Data') < 0 then
raise Exception.Create('revision is missing the Data sheet');
finally
Book.Free;
Names.Free;
end;
La convention de retour fait trébucher : les appels HotXLS renvoient généralement un compte positif ou 1 en cas de succès et -1 en cas d'échec, en vidant la liste lorsqu'ils échouent, alors testez <= 0 plutôt que de comparer à une valeur positive précise. GetODSSheetNames ne réinitialise ni ne remplit l'instance de classeur, si bien qu'un seul objet sonde peut examiner tout un répertoire de fichiers entrants. Les contrôles structurels de ce genre attrapent la panne la plus courante en conditions réelles — un analyste qui renomme ou supprime une feuille avant de renvoyer la révision — au portail, là où le message d'erreur peut encore nommer le fichier et la feuille manquante au lieu de ressortir en référence nulle trois couches plus bas
Si vous bâtissez un pipeline de conversion plus large autour de cela, le motif de l'atelier d'audit et de conversion de classeurs montre comment inventorier les fonctionnalités d'un fichier avant de choisir un format cible, et le guide de performance des grands classeurs garde les exports par lots dans des bornes mémoire raisonnables
HotXLS est une bibliothèque tableur native pour Delphi et C++Builder livrée avec le code source complet ; la liste exhaustive des fonctionnalités et les modalités de licence figurent sur la page produit du HotXLS Delphi Component