Article technique

Ouvrir et enregistrer des feuilles ODS dans Delphi avec HotXLS

Un backend de reporting Delphi qui produit des fichiers .xlsx depuis des années reçoit une nouvelle exigence : les règles d'achat d'un client du secteur public imposent une sortie OpenDocument Spreadsheet, et les analystes de ce client renvoient des révisions comme fichiers .ods enregistrés depuis LibreOffice. HotXLS, la bibliothèque native Object Pascal de losLab pour Delphi et C++Builder, couvre les deux sens de cet échange sans qu'Excel ni LibreOffice soient installés nulle part. Les deux sens ne sont toutefois pas symétriques, et faire comme s'ils l'étaient est la manière dont les données disparaissent silencieusement entre systèmes. Cet article cartographie exactement ce que le chemin ODS lit, ce qu'il écrit et comment concevoir un aller-retour dont les pertes sont connues à l'avance au lieu d'être découvertes dans un ticket support.

Le support ODS vit sur la façade XLSX, pas sur la façade XLS

HotXLS livre deux hiérarchies de classes indépendantes dans un même package : TXLSWorkbook dans l'unité lxHandle pour les fichiers binaires BIFF8 .xls, et TXLSXWorkbook dans l'unité lxHandleX pour les packages OOXML .xlsx. Tous les points d'entrée OpenDocument — OpenODS, SaveAsODS, GetODSSheetNames — sont sur TXLSXWorkbook. Ce placement n'est pas arbitraire. Un package 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 d'enregistrements binaires des années 1990 qui n'a rien en commun.

Conséquence pratique : un classeur .xls legacy ne devient pas .ods en un appel. La route supportée consiste à faire d'abord passer le contenu BIFF dans le modèle XLSX avec SaveXLSWorkbookAsXLSX depuis l'unité lxXlsxExport, à rouvrir le résultat via TXLSXWorkbook, puis à exporter depuis là. Budgétez les limites de fidélité du pont lors de la planification — il copie valeurs, formules, formats numériques, polices, remplissages et largeurs de colonnes, mais pas les bordures, plages fusionnées, commentaires, graphiques ni formats conditionnels.

La détection côté import est automatique. La méthode ordinaire Open reconnaît un package ODS par son membre mimetype, avec repli sur une vérification content.xml au niveau supérieur lorsque ce membre est absent ; un chemin générique « ouvrir ce que l'utilisateur a téléversé » n'a donc pas besoin de flairer lui-même l'extension. Après ouverture, la propriété SourceFormat indique quelle branche a été prise.

Exporter en ODS avec TODSExportOptions

L'appel d'export lui-même tient en une ligne ; l'objet d'options autour porte les décisions qu'un relecteur demandera plus tard :

var
  Book: TXLSXWorkbook;
  Opts: TODSExportOptions;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('quarterly-report.xlsx');
    Opts := TODSExportOptions.Create;        // caller owns and frees this
    try
      Opts.Generator := 'ReportService 4.2'; // meta:generator override
      Opts.IncludeCharts := True;
      Opts.IncludeImages := True;
      Book.SaveAsODS('quarterly-report.ods', Opts);
    finally
      Opts.Free;
    end;
  finally
    Book.Free;
  end;
end;

Trois détails justifient leur place dans ce listing. D'abord, l'objet d'options appartient à l'appelant — HotXLS ne le libère pas, donc le try..finally interne compte. Ensuite, IncludeCharts := False fait plus que masquer les graphiques : il retire entièrement les sous-documents de graphiques et leurs entrées de manifeste du package, forme correcte lorsque le consommateur est une chaîne de données qui s'y étoufferait. Enfin, Generator remplace la chaîne ODF meta:generator (la valeur par défaut est HotXLS/<version>) ; définissez-le lorsque les outils aval identifient les producteurs de fichiers pour router le support. Appeler SaveAs(FileName, xlsxOpenDocumentSpreadsheet) équivaut à SaveAsODS avec les options par défaut, et des surcharges stream existent pour livrer le package directement à une réponse HTTP sans fichier temporaire.

Ce que le chemin d'import lit — et ce qu'il ignore volontairement

C'est la section à lire deux fois avant de promettre une fidélité d'aller-retour à qui que ce soit. L'import ODS dans HotXLS est un chemin léger : il préserve les valeurs scalaires de cellules, les résultats mis en cache des formules, et étend les lignes et colonnes répétées dans la grille. Il n'importe pas les styles, il n'importe pas les expressions de formules ODS, et il n'importe pas les dessins.

La décision sur les formules mérite une explication plutôt qu'une excuse. Une cellule ODF stocke à la fois l'expression de formule — écrite dans le dialecte OpenFormula défini par ODF 1.3 Part 4 — et la dernière valeur calculée par l'application productrice. Traduire OpenFormula en syntaxe de formule Excel est un problème de conversion de dialecte avec de vrais cas limites : vocabulaires de fonctions différents, syntaxe de références différente, modèles d'erreurs différents. En lisant plutôt la valeur en cache, HotXLS évite toute la classe de bugs de traduction silencieuse et garantit que les nombres importés sont ceux que l'expéditeur a vus en dernier.

Le mode de panne à concevoir en découle directement : une feuille dont les totaux étaient corrects au dernier enregistrement LibreOffice 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 reste. Si le flux a besoin de formules vivantes après import, rétablissez-les par programme depuis vos propres règles métier via Cell.Formula, qui sur la façade XLSX prend l'expression sans signe égal initial.

Concevoir autour de l'aller-retour asymétrique

L'export rend depuis le modèle complet de classeur en mémoire — valeurs, styles, et éventuellement graphiques et images — tandis que l'import est valeurs seulement. Sous forme de matrice : .xlsx vers .ods est de haute fidélité ; .ods vers .xlsx récupère les valeurs et résultats en cache mais arrive sans styles ni formules vivantes ; et un cycle complet .xlsx vers .ods vers .xlsx perd donc styles et formules sur le second trajet, même si le premier les a écrits fidèlement.

Book := TXLSXWorkbook.Create;
try
  Book.Open('vendor-revision.ods');          // format auto-detected
  if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
  begin
    // Values and cached formula results are present after an ODS
    // import; styles and live formulas are not. Rebuild whatever
    // the downstream pipeline depends on before saving.
    Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
    Book.SaveAs('vendor-revision.xlsx');
  end;
finally
  Book.Free;
end;

Le modèle architectural qui en découle : traitez les fichiers .ods entrants comme des flux de données, pas comme des documents à éditer en place. Gardez le classeur canonique en .xlsx, lisez les valeurs des révisions client, et émettez de nouveaux ODS à la demande depuis la copie canonique. La vérification appartient aux deux camps — ouvrez les fichiers exportés dans LibreOffice Calc, consommateur ODF de référence, et dans Excel, qui lit ODS depuis des années mais diverge de LibreOffice aux limites du support de styles et de graphiques. Le nombre de feuilles, quelques cellules clés et la présence de graphiques suffisent comme smoke check par profil d'export.

Trier un fichier ODS avant de s'engager dans un import

Quand un endpoint accepte des uploads, lister les noms de feuilles coûte bien moins cher qu'une analyse complète et capture 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 piège facilement : les appels HotXLS renvoient généralement un décompte positif ou 1 en cas de succès et -1 en cas d'échec, en vidant la liste lorsqu'ils échouent ; testez donc <= 0 plutôt que de comparer à une valeur positive précise. GetODSSheetNames ne réinitialise ni ne peuple l'instance de classeur, donc un seul objet de sonde peut valider tout un répertoire de fichiers entrants. Des contrôles structurels comme celui-ci capturent la panne réelle la plus courante — un analyste qui renomme ou supprime une feuille avant de renvoyer la révision — à la porte, où le message d'erreur peut encore nommer le fichier et la feuille manquante au lieu de surgir comme référence nil trois couches plus bas.

Questions fréquentes

HotXLS peut-il convertir directement .xls en .ods ?

Pas en un appel. Faites passer le .xls vers un .xlsx avec SaveXLSWorkbookAsXLSX, rouvrez-le avec TXLSXWorkbook, puis appelez SaveAsODS — et tenez compte du fait que le pont laisse tomber bordures, fusions, commentaires et graphiques en chemin.

Les formules survivent-elles à un aller-retour ODS ?

L'écriture ODS préserve ce que contient le modèle de classeur. La lecture ODS garde le résultat en cache de chaque formule comme constante et abandonne l'expression, donc les formules vivantes ne survivent pas au trajet retour.

Excel ouvrira-t-il les fichiers ODS produits par HotXLS ?

Oui — Excel lit les feuilles OpenDocument — mais validez la sortie dans Excel et LibreOffice, car les deux applications supportent des sous-ensembles différents des styles et fonctionnalités graphiques ODF.

Si vous construisez une chaîne de conversion plus large autour de cela, le modèle de workbench 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 batch dans des limites mémoire raisonnables.

HotXLS est une bibliothèque native Delphi et C++Builder de tableur avec code source complet ; la liste complète des fonctionnalités et les détails de licence se trouvent sur la page produit HotXLS Component.