Article technique

Expressions de chemin SOM XFA en Delphi

losLab PDF Library lit et écrit les nœuds de données répétés des formulaires XFA dynamiques au moyen des expressions de chemin SOM de XFA 3.3 : GetXFAFormFieldValue et SetXFAFormFieldValue acceptent les racines $data, $record et !data, les jokers d'occurrence [*], les sélecteurs de descendant et de joker d'enfant, la propriété parent, les sélecteurs de classe #dataGroup/#dataValue et de simples prédicats de style FormCalc, si bien qu'un programme Delphi peut mettre à jour chaque ligne de facture d'un formulaire fiscal officiel en un seul appel. Cet article est la référence d'adressage ; pour déplacer des jeux de données entiers entre fichiers, voyez l'article compagnon 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 une déclaration en douane bâtie en formulaire XFA dynamique n'a pas douze champs nommés Total_Price_1 à Total_Price_12 ; elle a un sous-formulaire Detail déclaré une fois dans le DOM de modèle et instancié autant de fois que les données l'exigent. Les valeurs vivent dans un second arbre, le Data DOM à l'intérieur du paquet datasets, où chaque ligne est un élément <Detail> répété sous <Receipt>. Des noms de champs plats ne peuvent pas adresser "le prix de la troisième ligne" ni "toutes les lignes au-dessus de 200" ; c'est exactement la tâche que le chapitre Scripting Object Model de la spécification Adobe XFA 3.3 confie aux expressions SOM, et c'est la syntaxe que parlent les API de valeurs de champs XFA de losLab PDF Library

PDF Library for Delphi : ce diagramme oppose un DOM de modèle XFA qui déclare un sous-formulaire Detail une seule fois au Data DOM qui le répète pour chaque enregistrement de données arrivant
Un formulaire XFA dynamique déclare chaque sous-formulaire une fois dans le modèle et le répète dans le paquet datasets au fil des données

Comment adresser les nœuds de données XFA depuis Delphi ?

Tout chemin datasets part d'une racine, et losLab PDF Library en accepte indifféremment les écritures standard : $data est la forme courte XFA 3.3 de xfa.datasets.data, !data est la forme courte enracinée sur xfa.datasets, et dans les paquets qui n'utilisent pas le traitement par enregistrements, $record se résout vers l'enregistrement de données externe, le premier élément sous le nœud data. Les API côté modèle telles que SetXFAFormFieldAccess acceptent $template et xfa.template de la même façon. Les segments peuvent être délimités par des points ou par des barres obliques ($data.Receipt.Tax ou $data/Receipt/Tax), ce qui compte parce que GetXFAFormFieldNames énumère les champs sous forme de chemins à barres obliques qui se réinjectent directement dans les API de valeurs et de modèle. Les index d'occurrence partent de zéro conformément à la spécification, donc Detail[0] est la première ligne. Deux règles d'échappement gardent les noms inhabituels adressables : dans un chemin à points, \. désigne un point littéral (Line\.Item), tandis que les chemins à barres obliques traitent les points comme des caractères ordinaires. Les données métier qui portent leur propre préfixe d'espace de noms XML sont appariées par nom local, si bien que <m:Receipt> répond encore à Receipt

PDF Library for Delphi : ce diagramme dissèque une expression de chemin SOM sur les datasets XFA, étiquetant la racine de jeu de données, les segments délimités, un index d'occurrence à base zéro et la valeur feuille visée
Racines, délimiteurs et index d'occurrence à base zéro se composent en chemins SOM, plusieurs écritures se résolvant vers le même nœud
var
  Lib: TPDFlib;
  Tax: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('vat-return.pdf', '');
    // Adresses équivalentes du même nœud de données
    Tax := Lib.GetXFAFormFieldValue('Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('xfa.datasets.data.Receipt.Tax');
    Tax := Lib.GetXFAFormFieldValue('$record.Tax');
    // Forme à barres obliques avec un index d'occurrence à base zéro
    Lib.SetXFAFormFieldValue('$record/Detail[0]/Total_Price', '251.00');
    Lib.SaveToFile('vat-return-updated.pdf');
  finally
    Lib.Free;
  end;
end;

Comment remplir par programme les lignes répétées d'un formulaire XFA ?

Le joker d'occurrence [*] est l'outil de lot. Là où un index numérique sélectionne un frère, [*] sélectionne tous les frères de même nom, si bien que $data.Receipt.Detail[*].Total_Price adresse le champ de prix de chaque ligne d'un coup. En lecture, GetXFAFormFieldValue renvoie les valeurs de tous les nœuds correspondants jointes par un séparateur | ; en é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 se relit en chaîne vide, ce qui est le moyen bon marché de sonder l'existence d'une branche avant d'y écrire

var
  Lib: TPDFlib;
  Prices: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('invoice.pdf', '');
    // Toutes les lignes Detail d'un coup : '250.00|60.00'
    Prices := Lib.GetXFAFormFieldValue('$data.Receipt.Detail[*].Total_Price');
    // Réinitialiser une colonne sur chaque ligne répétée en un seul appel
    Lib.SetXFAFormFieldValue('$data.Receipt.Detail[*].Surcharge', '0.00');
    Lib.SaveToFile('invoice-updated.pdf');
  finally
    Lib.Free;
  end;
end;

Sélecteurs de descendant, de joker d'enfant et de parent

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 de données, et une écriture par chemin de descendant met à jour ce premier nœud correspondant. Notez la limite : un index d'occurrence après une correspondance de descendant ne traverse pas les branches frères, donc si $data..Total_Price[0] se résout dans la première ligne Detail, $data..Total_Price[1] renvoie du vide plutôt que de sauter à la ligne suivante ; utilisez Detail[*] quand vous les voulez toutes. Le joker d'enfant .* correspond à chaque enfant direct et laisse les segments restants filtrer : $data.Receipt.*.Total_Price atteint le Total_Price dans chaque branche enfant de Receipt sans sélectionner aussi un champ récapitulatif de même nom posé directement sur Receipt. 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 part de la deuxième ligne, remonte à Receipt et atterrit sur sa valeur frère Tax

Sélecteurs de classe et prédicats : #dataGroup, #dataValue, .[expression]

La syntaxe #class adresse les nœuds du Data DOM par classe d'objet plutôt que par nom, ce qui est la voie pratique quand les balises XML contiennent des caractères malaisés à écrire en noms de script. losLab PDF Library fait correspondre #dataGroup aux éléments qui ont des enfants éléments et #dataValue aux éléments feuilles sans enfants éléments, et les deux se combinent avec des occurrences numériques ou [*] : $data.Receipt.#dataGroup[0].#dataValue[0] sélectionne la première valeur dans le premier groupe sous Receipt. Le sélecteur de prédicat .[expression] filtre les frères de même nom par contenu. Les prédicats pris en charge sont de simples comparaisons d'une valeur enfant à un littéral, avec des opérateurs relationnels tels que >, < et <= ; quand un prédicat correspond à plusieurs lignes, les lectures reviennent jointes par | et les écritures mettent à jour chaque correspondance

PDF Library for Delphi : ce diagramme passe en revue six sélecteurs SOM structurels pour les données XFA répétées : le joker d'occurrence, la recherche de descendant, le joker d'enfant, le pas vers le parent, les sélecteurs de classe et les prédicats de contenu
Jokers, descendants, pas vers le parent, sélecteurs de classe et prédicats de contenu couvrent chacun un manque d'adressage différent
// Marquer chaque ligne dont Total_Price dépasse 200
Lib.SetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price > 200].Review_Flag', '1');

// Descriptions de toutes les lignes de faible valeur, jointes par '|' si plusieurs correspondent
Desc := Lib.GetXFAFormFieldValue(
  '$data.Receipt.Detail.[Total_Price <= 200].Description');

// Adressage par classe quand les noms de balises résistent à l'écriture en script
Total := Lib.GetXFAFormFieldValue(
  '$data.Receipt.#dataGroup[0].#dataValue[0]');

Quelles fonctionnalités SOM de XFA ne sont pas prises en charge ?

La prise en charge SOM de losLab PDF Library est limitée aux chemins de datasets et de champs de modèle, et les bords sont explicites plutôt que approximatifs. Les connaître d'avance évite de déboguer un chemin qui renvoie silencieusement une chaîne vide

  • Les prédicats n'exécutent pas de FormCalc ni de JavaScript arbitraire : pas d'appels de fonction (un prédicat contains(...) renvoie du vide), pas d'expressions booléennes composées, seulement une comparaison unique child op literal
  • $record ne fonctionne que dans les paquets sans traitement par enregistrements ; la pagination dataWindow et la rotation de groupes d'enregistrements ne sont pas implémentées
  • La résolution se fait directement contre le Data DOM et le DOM de modèle ; la résolution relative au Form DOM, la vue fusionnée qu'une visionneuse construit à l'exécution, n'est pas effectuée
  • Les sémantiques de chargement d'attributs du Data DOM ne sont pas appliquées ; la bibliothèque lit le paquet datasets comme du XML brut, les chemins adressent donc des éléments, pas des attributs promus en nœuds

Ces limites mordent rarement dans le travail de remplissage en lot, car un remplisseur adresse les données par structure plutôt que par logique scriptée. Quand elles mordent, la porte de sortie est le niveau du paquet : lisez tout le XML datasets, transformez-le en Delphi et réécrivez-le

Écrire le paquet entier avec SetXFAFromString

SetXFAFromString est ce point d'entrée au niveau du paquet : il installe une chaîne XDP complète, modèle et datasets ensemble, crée le conteneur AcroForm dans un document neuf quand il n'en existe pas encore, et il accepte les paquets encodés en UTF-16 avec marque d'ordre des octets aussi bien qu'en UTF-8. Une forme de production courante consiste à garder le XDP fourni par l'administration comme gabarit, à le charger avec SetXFAFromString, puis à lancer des appels SetXFAFormFieldValue adressés en SOM pour les valeurs volatiles propres à chaque facture avant l'enregistrement. Puisque XFA est l'un des deux modèles de formulaire qu'un PDF peut porter, le côté AcroForm classique a sa propre histoire d'automatisation, traitée dans l'article sur les actions de formulaire interactif et JavaScript

Les API de valeurs de champs XFA, d'énumération et de paquet présentées ici sont livrées dans losLab PDF Library pour Delphi, C# et VB.NET ; la page produit porte la référence complète de la gestion des formulaires