Article technique

Exporter des classeurs Excel en CSV, TSV, HTML et RTF depuis Delphi avec HotXLS

L'ERP en aval a rejeté le flux nocturne avec une erreur d'analyse dans la colonne des totaux. Le CSV semblait correct dans un éditeur de texte jusqu'à la ligne 42, où le champ montant contenait =SUM(D2:D41) — la formule elle-même, sous forme de texte, et non le nombre qu'elle calcule. Rien n'était cassé : c'est le comportement documenté. L'export CSV dans HotXLS sérialise le modèle de cellules tel qu'il existe à cet instant, et une cellule de formule dont la valeur n'a jamais été calculée n'a rien d'autre à fournir que le texte de la formule. Toute équipe qui branche les appels d'export de la bibliothèque sur une chaîne d'intégration rencontre une variante de ce problème dès la première semaine, c'est donc le bon point de départ.

Pourquoi votre CSV contient des formules au lieu de nombres

HotXLS conserve le texte de formule et la valeur calculée comme deux éléments séparés, et SaveAsCSV ne lance volontairement pas le moteur de calcul — un export ne doit pas modifier le classeur ni se bloquer sur une chaîne de formules pathologique. Les fichiers enregistrés par Excel transportent des résultats mis en cache à côté des formules, ce qui rend leur export conforme aux attentes ; le piège apparaît avec les classeurs générés par votre propre code, où les formules ont été écrites mais jamais évaluées. La correction consiste à matérialiser les valeurs avant l'export, avec le même moteur Calculate qui gère les références interfeuilles et les fonctions personnalisées :

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  R: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('invoice-run.xlsx');
    Sheet := Book.Sheets[0];

    // Materialize formula results so the CSV carries numbers, not '=...' text
    for R := 2 to 41 do
      if Sheet.Cells[R, 4].Formula <> '' then
        Sheet.Cells[R, 4].Value := Book.Calculate(Sheet.Cells[R, 4].Formula);

    Book.SaveAsCSV('feed.csv', 0, ',');    // sheet 0, comma
    Book.SaveAsCSV('feed.tsv', 0, #9);     // same sheet as TSV
  finally
    Book.Free;
  end;
end;

Remarquez ce que fait la boucle : elle remplace les cellules de formule par leurs valeurs calculées, ce qui est exactement ce qu'il faut pour une passe d'export jetable, mais pas si vous comptez réenregistrer ensuite le classeur en .xlsx. Exportez depuis une copie, ou limitez cette réécriture au run d'export. Les capacités plus profondes du moteur derrière Calculate — y compris l'enregistrement de vos propres fonctions — sont traitées dans le moteur de formules HotXLS et les fonctions personnalisées.

Ce que garantit le writer délimité

Le chemin CSV produit de l'UTF-8 avec byte order mark, des fins de ligne CRLF et des guillemets conformes à RFC 4180 — les champs contenant le séparateur, des guillemets ou des sauts de ligne sont encadrés, et les guillemets internes sont doublés. Les dates sont rendues sous la forme yyyy-mm-dd hh:nn:ss, indépendamment du format d'affichage de la cellule, ce qui est le choix raisonnable pour des consommateurs machines mais surprend ceux qui attendent le format visible à l'écran. Les cellules en texte riche sont aplaties en concaténant leurs segments.

Ces valeurs par défaut règlent la plupart des discussions avec les importeurs avant même qu'elles commencent, avec deux réserves à inscrire dans votre contrat d'interface. D'abord, le BOM : c'est lui qui permet à Excel lui-même d'ouvrir le fichier avec les accents corrects, mais quelques analyseurs stricts traitent ces trois octets comme des données — si le vôtre le fait, supprimez-les au passage de relais. Ensuite, TSV n'est pas une fonctionnalité séparée ; c'est le même writer avec #9 comme paramètre de séparateur, donc tout ce qui précède s'applique à l'identique. La feuille est choisie par index 0-based dans la surcharge à plusieurs arguments, tandis que le raccourci à argument unique SaveAsCSV(FileName) prend la feuille active.

L'export HTML est un instantané, pas un format d'échange

Là où CSV jette tout sauf les valeurs, SaveAsHTML tente de préserver l'apparence : un <table> par feuille, des régions fusionnées exprimées avec colspan et rowspan, et une mise en forme de cellule de base intégrée en CSS. Les couleurs relatives au thème sont ignorées plutôt que résolues, donc un modèle d'entreprise qui dépend fortement des emplacements de thème ressortira plus sobre qu'il n'apparaît dans Excel — définissez des couleurs RGB explicites pour tout élément qui doit survivre. L'objet d'options contrôle l'enveloppe :

var
  Opts: TXLSXHtmlExportOptions;
begin
  Opts := TXLSXHtmlExportOptions.Create;
  try
    Opts.Title := 'Weekly settlement';
    Opts.TableClass := 'report-grid';     // hook for the host page stylesheet
    Opts.WriteDocument := True;           // full page, not a fragment
    if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
      raise Exception.Create('Sheet index out of range');
  finally
    Opts.Free;
  end;
end;

Deux détails de cet extrait méritent l'attention. WriteDocument := False bascule la sortie vers un fragment de table nu, ce qu'il faut lorsque vous injectez un aperçu dans une page existante — définissez TableClass et laissez la feuille de style environnante appliquer le thème. Et la convention de retour est l'inverse de la plupart des appels HotXLS : SaveAsHTML renvoie 0 en cas de succès et -1 pour un mauvais index de feuille, donc un test de succès par habitude avec = 1 signalera à tort chaque export. Pour envoyer par e-mail ou intégrer seulement une région plutôt qu'une feuille, TXLSXRange.SaveAsHTML exporte n'importe quel bloc rectangulaire avec les mêmes règles de rendu.

Sortie RTF et cas où elle garde son intérêt

La quatrième cible d'export écrit des tables RTF 1.6, une feuille par appel via SaveAsRTF. Les largeurs de colonnes sont approximées à environ 96 twips par caractère de largeur de colonne, et — limitation structurelle — les cellules fusionnées ne s'étendent pas dans la sortie : seule la cellule d'ancrage porte le contenu, les cellules couvertes étant émises comme vides. Cela rend RTF inadapté aux modèles à forte mise en page, mais il reste le chemin de moindre résistance pour coller des résultats tabulaires dans des traitements de texte et des systèmes de gestion documentaire anciens qui précèdent l'ingestion HTML.

Aller-retour : importer un CSV est destructeur par conception

Le sens import a son propre contrat. OpenCSV vide tout le classeur et le reconstruit comme une seule feuille nommée Sheet1 — c'est un constructeur dans l'esprit, pas une fusion, donc ne l'appelez jamais sur un classeur contenant du contenu non enregistré. Passer #0 comme séparateur déclenche la détection automatique du délimiteur, et l'indicateur ADetectTypes contrôle la promotion de types : activé, les chaînes numériques deviennent des nombres, les chaînes ISO-8601 deviennent des dates, et true/false deviennent des booléens. Désactivez-le lorsque le flux contient des identifiants avec zéros initiaux ou des codes postaux, que la promotion de types corrompt silencieusement en nombres. Les deux façades de classeur exposent le même import ; associez-le aux appels d'export ci-dessus et vous obtenez un pont de formats qui ne requiert aucune installation d'Excel dans la chaîne, scénario approfondi dans la génération de rapports base de données vers Excel avec HotXLS.

Exporter directement dans un stream

Tous les writers évoqués ici — CSV, HTML, RTF et les formats de classeur eux-mêmes — disposent de surcharges stream en plus des versions à nom de fichier, et dans du code serveur ce sont celles qu'il faut choisir. Un endpoint web qui produit un téléchargement CSV peut écrire dans un TMemoryStream et le transmettre directement à l'objet réponse : pas de fichier temporaire, pas de tâche de nettoyage, pas de collision entre deux requêtes exportant sous le même nom généré. Il en va de même pour déposer des exports dans un stockage blob ou les joindre à des e-mails sortants — le système de fichiers sort complètement de la chaîne.

Ce modèle se combine avec la propriété centrale de déploiement de la bibliothèque : les deux façades sont des lecteurs et writers natifs Object Pascal, donc il n'y a ni installation d'Excel, ni automatisation COM, ni goulot d'étranglement mono-thread par processus sur le serveur. Chaque requête peut posséder son objet classeur, exécuter la réécriture de calcul de la première section et streamer l'export, entièrement en parallèle avec ses voisines. La ressource à surveiller est la mémoire : le modèle du classeur est conservé en RAM pendant l'export, donc un service qui ouvre de très grands classeurs seulement pour les réémettre en CSV doit plafonner les jobs concurrents ou mettre en file les plus gros au lieu de laisser le trafic de pointe décider de la mémoire active.

Définissez IncludeBOM sur les options HTML lorsque le fragment sera enregistré comme fichier autonome consommé par des outils qui devinent l'encodage, et laissez la déclaration charset du content-type à la couche HTTP lorsque vous servez directement.

FAQ : export de tableur depuis Delphi

Comment exporter une feuille précise plutôt que la feuille active ? Utilisez les surcharges qui prennent un index de feuille — SaveAsCSV(FileName, SheetIndex, Delimiter), SaveAsHTML(FileName, SheetIndex, Options), SaveAsRTF(FileName, SheetIndex). L'index est 0-based sur la façade XLSX.

Excel affiche des caractères accentués cassés quand il ouvre mon CSV exporté. Pourquoi ? Ce n'est presque certainement pas le fichier — le writer émet précisément un BOM UTF-8 pour qu'Excel le décode correctement. Vérifiez si une étape intermédiaire, comme un transfert FTP en mode texte ou une copie de stream qui retire les premiers octets, a endommagé le BOM.

Puis-je exporter seulement une plage de cellules en CSV ? Sur la façade XLS, oui — IXLSRange possède ses propres SaveAsCSV, SaveAsHTML et SaveAsRTF. Sur la façade XLSX, l'export au niveau plage est disponible pour HTML via TXLSXRange.SaveAsHTML.

Les exports sont l'endroit où la sémantique du classeur rencontre les parseurs d'autres systèmes ; les détails de contrat ci-dessus doivent donc figurer dans votre documentation d'intégration, pas seulement dans votre code. La référence complète des méthodes se trouve sur la page produit HotXLS Component.