losLab PDF Library lit et écrit des nœuds de données répétitifs dans les formulaires XFA dynamiques à l'aide des expressions de chemin SOM XFA 3.3 : GetXFAFormFieldValue et SetXFAFormFieldValue acceptent les racines $data, $record et !data, les jokers d'occurrence [*], les sélecteurs de descendants et de jokers enfants, la propriété parent, les sélecteurs de classe #dataGroup/#dataValue, ainsi que des prédicats simples de style FormCalc. Ainsi, un programme Delphi peut mettre à jour chaque ligne de facture dans un formulaire fiscal gouvernemental en un seul appel. Cet article sert de référence d'adressage ; pour le transfert de jeux de données entiers entre fichiers, consultez l'article complémentaire sur l'échange de données de formulaire FDF, XFDF et XFA
Le problème surgit dès qu'un formulaire cesse d'être plat. Une déclaration de TVA ou de douane conçue sous forme de formulaire XFA dynamique ne possède pas douze champs nommés Total_Price_1 à Total_Price_12 ; elle possède un sous-formulaire Detail déclaré une seule fois dans le DOM du modèle (Template DOM) et instancié autant de fois que les données le requièrent. Les valeurs résident dans un second arbre, le DOM de données (Data DOM) à l'intérieur du paquet de jeux de données (datasets), où chaque élément de ligne est un élément <Detail> répété sous <Receipt>. Les noms de champs plats ne peuvent cibler « le prix de la troisième ligne » ou « chaque ligne supérieure à 200 » ; c'est précisément le rôle que le chapitre Scripting Object Model de la spécification Adobe XFA 3.3 attribue aux expressions SOM, et c'est la syntaxe que comprennent les API de valeurs de champs XFA de losLab PDF Library
Comment cibler les nœuds de données XFA depuis Delphi ?
Chaque chemin de jeu de données commence par une racine, et losLab PDF Library accepte les notations standards de manière interchangeable : $data est la forme courte XFA 3.3 de xfa.datasets.data, !data est la forme courte enracinée à xfa.datasets, et dans les paquets n'utilisant pas le traitement par enregistrement, $record résout le nœud d'enregistrement externe (data record), le premier élément sous le nœud de données. Les API côté modèle comme SetXFAFormFieldAccess acceptent $template et xfa.template de la même manière. Les segments peuvent être séparés par un point ou par une barre oblique ($data.Receipt.Tax ou $data/Receipt/Tax). Ce détail importe car GetXFAFormFieldNames énumère les champs sous forme de chemins séparés par des barres obliques qui alimentent directement les API de valeurs et de modèles. Les index d'occurrence commencent à zéro selon la spécification, de sorte que Detail[0] désigne la première ligne. Deux règles d'échappement permettent de cibler les noms inhabituels : dans un chemin séparé par des points, \. indique un point littéral (Line\.Item), tandis que les chemins séparés par des barres obliques traitent les points comme des caractères ordinaires. Les données métier portant leur propre préfixe d'espace de noms XML sont filtrées par leur nom local, de sorte que <m:Receipt> répond toujours à Receipt
var
Lib: TPDFlib;
Tax: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('vat-return.pdf', '');
// Equivalent addresses for the same data node
Tax := Lib.GetXFAFormFieldValue('Receipt.Tax');
Tax := Lib.GetXFAFormFieldValue('$data.Receipt.Tax');
Tax := Lib.GetXFAFormFieldValue('xfa.datasets.data.Receipt.Tax');
Tax := Lib.GetXFAFormFieldValue('$record.Tax');
// Slash form with a zero-based occurrence index
Lib.SetXFAFormFieldValue('$record/Detail[0]/Total_Price', '251.00');
Lib.SaveToFile('vat-return-updated.pdf');
finally
Lib.Free;
end;
end;
Comment remplir des lignes répétitives dans un formulaire XFA par programmation ?
Le joker d'occurrence [*] est l'outil de traitement par lots. Là où un index numérique sélectionne un seul élément frère, [*] sélectionne tous les éléments frères de même nom, de sorte que $data.Receipt.Detail[*].Total_Price cible le champ de prix sur chaque ligne d'article à la fois. Lors d'une lecture, GetXFAFormFieldValue renvoie les valeurs de tous les nœuds correspondants jointes par le séparateur | ; lors d'une écriture, SetXFAFormFieldValue met à jour chaque nœud correspondant avec la même valeur et renvoie 1 en cas de succès. Un chemin qui ne correspond à rien renvoie une chaîne vide, ce qui constitue un moyen simple de vérifier si une branche existe avant d'y écrire
var
Lib: TPDFlib;
Prices: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('invoice.pdf', '');
// All Detail rows at once: '250.00|60.00'
Prices := Lib.GetXFAFormFieldValue('$data.Receipt.Detail[*].Total_Price');
// Reset one column across every repeated row in a single call
Lib.SetXFAFormFieldValue('$data.Receipt.Detail[*].Surcharge', '0.00');
Lib.SaveToFile('invoice-updated.pdf');
finally
Lib.Free;
end;
end;
Sélecteurs de descendants, de jokers enfants et de parents
Trois sélecteurs structurels couvrent les cas où vous connaissez le nom du champ mais pas sa profondeur exacte. Le sélecteur de descendant .. correspond à n'importe quelle profondeur sous le nœud courant : $data..Total_Price trouve le premier Total_Price n'importe où sous la racine des données, et une écriture via un chemin descendant met à jour ce premier nœud correspondant. Notez la limite : un index d'occurrence après une correspondance de descendant ne franchit pas les branches sœurs. Ainsi, si $data..Total_Price[0] se résout à l'intérieur de la première ligne Detail, $data..Total_Price[1] renverra un résultat vide plutôt que de passer à la ligne suivante ; utilisez Detail[*] si vous souhaitez les obtenir toutes. Le joker enfant .* correspond à chaque enfant direct et laisse les segments restants filtrer : $data.Receipt.*.Total_Price atteint le Total_Price à l'intérieur de chaque branche enfant de Receipt sans sélectionner un champ de résumé de même nom situé directement sur Receipt lui-même. Enfin, la propriété parent remonte d'un niveau, ce que XFA 3.3 sépare délibérément de .. : $data.Receipt.Detail[1].parent.Tax commence au deuxième élément de ligne, remonte à Receipt et atterrit sur sa valeur sœur Tax
Sélecteurs de classe et prédicats : #dataGroup, #dataValue, .[expression]
La syntaxe #class cible les nœuds du DOM de données par leur classe d'objet au lieu de leur nom, ce qui s'avère pratique lorsque les balises XML contiennent des caractères difficiles à utiliser comme noms de script. losLab PDF Library associe #dataGroup aux éléments ayant des éléments enfants et #dataValue aux éléments terminaux (feuilles) sans élément enfant, et les deux se combinent avec des occurrences numériques ou [*] : $data.Receipt.#dataGroup[0].#dataValue[0] sélectionne la première valeur à l'intérieur du premier groupe sous Receipt. Le sélecteur de prédicat .[expression] filtre les éléments frères de même nom par leur contenu. Les prédicats pris en charge sont des comparaisons simples d'une valeur enfant par rapport à un littéral, avec des opérateurs relationnels comme >, < et <=. Lorsqu'un prédicat correspond à plusieurs lignes, les lectures sont renvoyées jointes par le caractère | et les écritures mettent à jour chaque correspondance
// Flag every line whose Total_Price exceeds 200
Lib.SetXFAFormFieldValue(
'$data.Receipt.Detail.[Total_Price > 200].Review_Flag', '1');
// Descriptions of all low-value lines, '|'-joined when several match
Desc := Lib.GetXFAFormFieldValue(
'$data.Receipt.Detail.[Total_Price <= 200].Description');
// Class-based addressing when tag names resist script spelling
Total := Lib.GetXFAFormFieldValue(
'$data.Receipt.#dataGroup[0].#dataValue[0]');
Quelles fonctionnalités SOM XFA ne sont pas prises en charge ?
La prise en charge de SOM dans losLab PDF Library est limitée aux jeux de données et aux chemins de champs de modèles, et ces limites sont strictes plutôt que gérées au mieux. Les connaître permet d'éviter de déboguer un chemin qui renvoie silencieusement une chaîne vide
- Les prédicats n'exécutent pas de FormCalc ou de JavaScript arbitraire : pas d'appels de fonctions (un prédicat
contains(...)renverra un résultat vide), pas d'expressions booléennes complexes, uniquement une comparaison simple de typechild op literal $recordfonctionne uniquement dans les paquets sans traitement par enregistrement ; la pagination des enregistrementsdataWindowet la rotation des groupes d'enregistrements ne sont pas implémentées- La résolution s'effectue directement par rapport au DOM de données et au DOM de modèle ; la résolution relative du DOM de formulaire, la vue fusionnée qu'un visualiseur construit à l'exécution, n'est pas effectuée
- La sémantique de chargement des attributs du DOM de données n'est pas appliquée ; la bibliothèque lit le paquet de jeux de données comme du XML brut, de sorte que les chemins ciblent des éléments, et non des attributs promus en nœuds
Ces limites sont rarement gênantes dans le cadre d'un travail de remplissage par lots, car un outil de remplissage cible les données par leur structure plutôt que par une logique de script. Si nécessaire, la solution de secours réside au niveau du paquet : lisez l'intégralité du XML des jeux de données, transformez-le dans Delphi et écrivez-le à nouveau
Écriture de l'intégralité du paquet avec SetXFAFromString
SetXFAFromString constitue ce point d'entrée au niveau du paquet : il installe une chaîne XDP complète, modèle et jeux de données ensemble, créant le conteneur AcroForm dans un nouveau document s'il n'existe pas encore, et il accepte les paquets encodés en UTF-16 avec indicateurs d'ordre des octets (BOM) ainsi qu'en UTF-8. Une structure de production courante consiste à conserver le fichier XDP fourni par l'administration comme modèle fixe, à le charger avec SetXFAFromString, puis à exécuter des appels SetXFAFormFieldValue ciblés par SOM pour les valeurs variables de chaque facture avant d'enregistrer. Le format XFA étant l'un des deux modèles de formulaires qu'un PDF peut contenir, la partie AcroForm classique possède sa propre méthode d'automatisation, traitée dans l'article sur les actions de formulaire interactif et JavaScript
Les API de valeurs de champs, d'énumération et de paquets XFA présentées ici font partie de la bibliothèque losLab PDF Library pour Delphi, C# et VB.NET ; la page du produit contient la référence complète de gestion des formulaires