
La facturation électronique impose aux entreprises de produire des fichiers XML structurés, normalisés, échangés entre plateformes. Pour les équipes qui maintiennent un système de gestion sur IBM i, cela soulève une question concrète : comment générer proprement un fichier XML en RPG Free ILE, alors que le langage est historiquement tourné vers les bases de données DB2 ?
Cet article retrace l’approche mise en œuvre lors d’un projet réel de génération de factures au format Factur-X / CrossIndustryInvoice (norme EN 16931). Au-delà du cas de la facture, la technique décrite vaut pour n’importe quel fichier texte à écrire sur l’IFS : XML, CSV, JSON ou HTML.
Deux mondes à réconcilier
Un développeur RPG passe l’essentiel de son temps dans un environnement bien délimité : des tables DB2, un accès natif ou du SQL intégré, des données encodées en EBCDIC. C’est efficace, robuste et parfaitement adapté à la gestion transactionnelle.
Un fichier XML, lui, appartient à un autre monde. Il vit dans l’IFS (Integrated File System), l’arborescence de type Unix du système. C’est un fichier « stream », attendu par les plateformes de dématérialisation dans un encodage précis : UTF-8. Le défi consiste donc à construire, depuis du code RPG, un fichier texte UTF-8 bien formé et à le déposer au bon endroit de l’IFS.
La bonne nouvelle : le système fournit déjà tout ce qu’il faut. Inutile d’introduire un composant Java ou un utilitaire externe ; les API C standard du système suffisent, et le RPG sait les appeler.
La clé : les API C de l’IFS
Quatre fonctions C couvrent l’ensemble du besoin. On les déclare en prototypes RPG, puis on les utilise comme n’importe quelle procédure.
dcl-pr open int(10) extproc('open');
path pointer value options(*string); // chemin IFS
oflag int(10) value; // drapeaux d'ouverture
mode uns(10) value options(*nopass); // droits d'accès
codepage uns(10) value options(*nopass); // CCSID du fichier
end-pr;
dcl-pr write int(10) extproc('write');
fd int(10) value; // descripteur retourné par open
buffer pointer value; // adresse du texte à écrire
nbytes uns(10) value; // nombre d'octets à écrire
end-pr;
dcl-pr close int(10) extproc('close');
fd int(10) value;
end-pr;
dcl-pr unlink int(10) extproc('unlink'); // suppression du fichier
path pointer value options(*string);
end-pr;
Leur rôle se résume simplement : open crée ou ouvre le fichier et renvoie un identifiant, write y écrit des octets, close libère le fichier, et unlink le supprime si la génération échoue. La seule contrainte de compilation est de lier la directory de service QC2LE (paramètre BNDDIR('QC2LE')).
Trois concepts à maîtriser
Avant d’écrire la moindre balise, trois notions méritent d’être bien comprises, car elles conditionnent la validité du résultat.
- Le file descriptor. L’appel à
openrenvoie un entier, le descripteur de fichier (fd). Cet entier identifie le fichier ouvert ; on le transmet ensuite àwriteet àclose. Une valeur négative signale un échec d’ouverture : il faut systématiquement tester ce cas avant d’écrire quoi que ce soit.
- Le CCSID 1208. Le dernier paramètre de
openindique au système le jeu de caractères du fichier. La valeur 1208 correspond à l’UTF-8, l’encodage attendu pour un XML valide. C’est un point décisif : sans lui, les accents et les caractères spéciaux se retrouvent corrompus, et le fichier est rejeté par la plateforme destinataire.
- Le CRLF. Contrairement à l’écriture dans un fichier base de données, l’IFS ne gère pas les enregistrements pour nous. C’est au programme d’ajouter explicitement le retour chariot suivi du saut de ligne (
x'0D0A') à la fin de chaque ligne, afin d’obtenir un fichier lisible et correctement structuré.
Le cœur du dispositif : une procédure d’écriture
Plutôt que d’appeler write directement à chaque ligne du XML — ce qui serait verbeux et source d’erreurs — on encapsule toute la mécanique dans une procédure unique, appelée ici WriteLine. C’est elle qui sera invoquée des centaines de fois pour construire le document.
dcl-proc WriteLine;
dcl-pi *n int(10);
pLine char(2048) const ccsid(1208);
end-pi;
dcl-s ligne char(2048) ccsid(1208);
dcl-s lg int(10);
ligne = %trim(pLine) + CRLF; // ajout du séparateur de ligne
lg = %len(%trim(ligne));
return write(fd : %addr(ligne) : lg); // %addr = adresse du buffer
end-proc;
Deux détails méritent l’attention. D’abord, le tag ccsid(1208) sur le paramètre et sur la variable de travail garantit que le texte manipulé est bien en UTF-8, en cohérence avec le fichier ouvert. Ensuite, write ne raisonne pas en chaînes mais en octets bruts : on lui passe donc un pointeur vers le tampon, obtenu via %addr, accompagné d’une longueur.
Une fois cette procédure en place, le reste du programme devient remarquablement lisible : il ne s’agit plus que d’enchaîner des appels du type WriteLine('<balise>…</balise>').
Ne pas casser le fichier : l’échappement XML
Certains caractères ont une signification syntaxique en XML. Une raison sociale aussi banale que « Durand & Fils » suffit à rendre le fichier invalide si le & n’est pas échappé. Il faut donc remplacer ces caractères réservés par leurs entités correspondantes.
dcl-proc escapeXml;
dcl-pi *n varchar(2048);
pTexte varchar(2048) const;
end-pi;
dcl-s r varchar(2048);
r = pTexte;
r = %scanrpl('&' : '&' : r); // à traiter EN PREMIER
r = %scanrpl('<' : '<' : r);
r = %scanrpl('>' : '>' : r);
r = %scanrpl('"' : '"' : r);
r = %scanrpl('''' : ''' : r);
return r;
end-proc;
L’ordre des remplacements n’est pas anodin : le & doit impérativement être traité en premier. Dans le cas contraire, les & introduits par les substitutions suivantes (<, >…) seraient eux-mêmes ré-échappés, produisant des séquences erronées. Cette fonction s’applique à toute donnée de texte libre : libellés d’articles, raisons sociales, désignations, commentaires.
Un exemple complet et minimal
Le programme ci-dessous rassemble les éléments précédents en une démonstration autonome. Il ouvre un fichier, écrit une petite facture, puis ferme le descripteur. C’est le squelette exact que l’on retrouve, démultiplié, dans un programme de production.
**free
///
// @Program DEMOXML
// @Purpose Démonstration minimale : créer un fichier XML sur l'IFS en RPG Free
//
// Le principe :
// 1) open() -> on crée/ouvre un fichier "stream" sur l'IFS en UTF-8
// 2) write() -> on écrit le contenu ligne par ligne
// 3) close() -> on ferme le descripteur
//
///
ctl-opt dftactgrp(*no) actgrp(*new) bnddir('QC2LE');
// ---------------------------------------------------------------------------
// 1. PROTOTYPES DES API C DE L'IFS
// ---------------------------------------------------------------------------
dcl-pr open int(10) extproc('open');
path pointer value options(*string); // chemin IFS (ex: /tmp/fic.xml)
oflag int(10) value; // drapeaux d'ouverture
mode uns(10) value options(*nopass); // droits d'accès (rwx)
codepage uns(10) value options(*nopass); // CCSID du fichier (1208 = UTF-8)
end-pr;
dcl-pr write int(10) extproc('write');
fd int(10) value; // descripteur retourné par open
buffer pointer value; // adresse du texte à écrire
nbytes uns(10) value; // nombre d'octets à écrire
end-pr;
dcl-pr close int(10) extproc('close');
fd int(10) value;
end-pr;
dcl-pr unlink int(10) extproc('unlink'); // suppression du fichier
path pointer value options(*string);
end-pr;
// ---------------------------------------------------------------------------
// 2. CONSTANTES D'OUVERTURE (drapeaux open + droits + séparateur de ligne)
// ---------------------------------------------------------------------------
dcl-c O_WRONLY 2; // écriture seule
dcl-c O_CREAT 8; // créer le fichier s'il n'existe pas
dcl-c O_TRUNC 64; // remettre à 0 octet (écrasement)
dcl-c O_TEXTDATA 16777216; // mode texte (conversion CCSID)
dcl-c CCSID_UTF8 1208; // UTF-8 -> indispensable pour le XML
dcl-c MODE_RWX 448; // droits rwx pour le propriétaire
dcl-c CRLF x'0D0A'; // retour chariot + saut de ligne
// ---------------------------------------------------------------------------
// 3. VARIABLES
// ---------------------------------------------------------------------------
dcl-s fd int(10);
dcl-s rc int(10);
dcl-s chemin varchar(256);
// ===========================================================================
// PROGRAMME PRINCIPAL
// ===========================================================================
chemin = '/tmp/demo_facture.xml';
// --- ÉTAPE 1 : ouverture / création du fichier en UTF-8 -------------------
fd = open( %trim(chemin)
: O_WRONLY + O_CREAT + O_TRUNC + O_TEXTDATA // drapeaux combinés
: MODE_RWX // droits
: CCSID_UTF8 ); // 1208 = UTF-8
if fd < 0; // < 0 => échec de l'ouverture
*inlr = *on;
return;
endif;
// --- ÉTAPE 2 : écriture du XML ligne par ligne ----------------------------
WriteLine('<?xml version="1.0" encoding="UTF-8"?>');
WriteLine('<Facture>');
WriteLine(' <Numero>FA-2026-001</Numero>');
WriteLine(' <Client>' + escapeXml('Durand & Fils') + '</Client>');
WriteLine(' <Montant devise="EUR">1250.00</Montant>');
WriteLine('</Facture>');
// --- ÉTAPE 3 : fermeture du descripteur ------------------------------------
rc = close(fd);
*inlr = *on;
return;
// ===========================================================================
// PROCÉDURE RÉUTILISABLE : écrit UNE ligne de texte + saut de ligne
// Le buffer est tagué ccsid(1208) : le texte est donc bien en UTF-8.
// ===========================================================================
dcl-proc WriteLine;
dcl-pi *n int(10);
pLine char(2048) const ccsid(1208);
end-pi;
dcl-s ligne char(2048) ccsid(1208);
dcl-s lg int(10);
ligne = %trim(pLine) + CRLF; // on ajoute le séparateur de ligne
lg = %len(%trim(ligne));
return write(fd : %addr(ligne) : lg); // %addr = adresse du buffer
end-proc;
// ===========================================================================
// PROCÉDURE : échappement des caractères spéciaux du XML
// ATTENTION : remplacer '&' EN PREMIER, sinon double échappement.
// ===========================================================================
dcl-proc escapeXml;
dcl-pi *n varchar(2048);
pTexte varchar(2048) const;
end-pi;
dcl-s r varchar(2048);
r = pTexte;
r = %scanrpl('&' : '&' : r); // & EN PREMIER
r = %scanrpl('<' : '<' : r);
r = %scanrpl('>' : '>' : r);
r = %scanrpl('"' : '"' : r);
r = %scanrpl('''' : ''' : r);
return r;
end-proc;
Le fichier produit est un XML standard, où la raison sociale a bien été échappée :
<?xml version="1.0" encoding="UTF-8"?>
<Facture>
<Numero>FA-2026-001</Numero>
<Client>Durand & Fils</Client>
<Montant devise="EUR">1250.00</Montant>
</Facture>
Du squelette au cas réel
La génération d’une vraie facture électronique repose exactement sur ce principe, mais à une autre échelle. Une boucle SQL parcourt les factures à traiter ; pour chacune, les données sont chargées dans des structures externes (entête, parties prenantes, lignes d’articles, ventilation de TVA, pied de facture) ; puis des dizaines d’appels à WriteLine reconstituent l’arborescence CrossIndustryInvoice. Chaque valeur y est associée à son code métier normalisé — BT-1 pour le numéro de facture, BT-112 pour le montant total TTC, et ainsi de suite. La complexité réside alors dans la conformité à la norme, non dans la mécanique d’écriture, qui reste celle décrite ici.
Bonnes pratiques
Quelques principes, issus de l’expérience, font la différence entre une preuve de concept et un programme fiable en production :
- Tester le code retour de
open. Un descripteur négatif signale un échec ; il ne faut pas tenter d’écrire dans un fichier qui n’a pas pu être ouvert. - Toujours fermer le descripteur. Un
fdlaissé ouvert peut verrouiller le fichier ou produire un contenu incomplet. - Nettoyer en cas d’erreur. Lorsqu’une exception interrompt la génération,
unlinksupprime le fichier partiellement écrit, évitant de livrer un XML tronqué. - Assurer la cohérence UTF-8. Le tag
ccsid(1208)du tampon et le CCSID passé àopendoivent concorder. - Centraliser l’écriture. Une procédure
WriteLineunique rend le code lisible, réduit les risques d’erreur et facilite la maintenance. - Échapper tout texte libre. Les données saisies par les utilisateurs doivent systématiquement passer par la fonction d’échappement avant d’être insérées dans une balise.
Conclusion
Générer du XML sur IBM i en RPG Free ILE ne requiert ni bibliothèque tierce ni changement de paradigme. Trois idées suffisent à poser des fondations solides : les API C de l’IFS (open, write, close, unlink) assurent l’écriture de tout fichier texte ; le CCSID 1208 garantit un encodage UTF-8 conforme ; et une procédure d’écriture associée à une fonction d’échappement rendent le code clair et maintenable. Le reste relève du métier — remplir les bonnes balises avec les bonnes données.
Cette approche, éprouvée dans le cadre de la facturation électronique, constitue une brique réutilisable pour tout besoin d’interopérabilité par fichiers structurés sur la plateforme IBM i.

