Article technique

Fusionner des formulaires PDF dans Delphi : règles pour les champs en double

PDF Library for Delphi fusionne deux documents AcroForm avec une politique explicite pour les champs qui partagent un nom. MergeDocumentEx prend l'identifiant du document source et l'une des trois stratégies : dfsReject refuse la fusion, dfsMerge conserve le nom partagé et synchronise les valeurs, et dfsAutoNumber renomme les champs entrants de manière déterministe. Le balayage des noms a lieu avant que les numéros d'objet ne se décalent, de sorte qu'une fusion rejetée laisse les deux documents pleinement utilisables

Quiconque a assemblé un dossier de candidature PDF a rencontré cela. Trois formulaires, chacun avec un champ nommé Signature ou Date ou Total, sont fusionnés en un seul fichier. Dans un AcroForm, le nom de champ pleinement qualifié est l'identité du champ, donc deux champs portant le même nom ne sont pas deux champs du tout : remplir l'un remplit l'autre, et une signature apposée sur l'un couvre une portée que personne n'avait prévue

Pourquoi la collision de noms est-elle tranchée avant la fusion ?

L'ancien MergeDocument concatène les deux tableaux de champs racines AcroForm et n'offre aucun choix. Pire, lorsque le résultat est inutilisable, la découverte a lieu après que les numéros d'objet ont été renumérotés et les arbres de pages assemblés, ce qui laisse l'appelant avec un document dans un état où aucun des deux originaux ne se trouvait

MergeDocumentEx inverse l'ordre. Il collecte les noms de champs de premier niveau des deux documents, les compare, et applique la stratégie avant que quoi que ce soit ne bouge. Un rejet est donc une non-opération propre : le document cible reste intact, le document source reste intact, et les deux restent ouverts et utilisables, ce que le test de fusion vérifie en relisant une valeur de champ dans la source après une fusion refusée

La comparaison utilise un ensemble de noms ordonné et sensible à la casse, de sorte que le coût est proportionnel au nombre combiné de champs multiplié par un facteur logarithmique plutôt qu'au produit des deux nombres. La sensibilité à la casse est le bon choix ici car les noms de champs PDF sont sensibles à la casse ; les replier fusionnerait des champs que la spécification traite comme distincts

Les trois stratégies, et quand chacune convient

dfsReject est la stratégie pour les pipelines automatisés qui ne doivent pas produire de documents ambigus. La fusion renvoie zéro et LastErrorCode indique 705, un code dédié afin que les noms en double puissent être distingués de tout autre échec de fusion et orientés vers un remède spécifique, généralement le renommage des champs en amont

dfsMerge conserve délibérément le nom partagé et synchronise la valeur cible et la valeur par défaut dans le champ source, de sorte qu'une visionneuse conforme traite les différents widgets comme un seul champ nommé logiquement, ce qui est le comportement AcroForm standard pour un champ portant plusieurs annotations de widget. Ce qu'il ne fait pas, c'est fusionner différents dictionnaires de champs en un seul objet. Chaque champ conserve sa propre association de page, son apparence et ses actions, car les regrouper abandonnerait silencieusement la mise en forme et le comportement propres au document entrant

dfsAutoNumber renomme les doublons entrants en ajoutant un suffixe numérique commençant à _2 et en prenant le premier libre. Le résultat est reproductible : il ne dépend que des noms présents, jamais des numéros d'objet de champ, donc fusionner deux fois la même paire de documents produit les mêmes noms les deux fois. Cette propriété compte lorsqu'un code en aval, un import FDF ou une correspondance de base de données référence les champs par nom

uses
  PDFlibrary;

var
  Lib: TPDFlib;
  TargetDoc, SourceDoc: Integer;
begin
  Lib := TPDFlib.Create;
  try
    TargetDoc := Lib.SelectedDocument;
    Lib.LoadFromFile('application-part1.pdf', '');

    SourceDoc := Lib.NewDocument;
    Lib.LoadFromFile('application-part2.pdf', '');

    Lib.SelectDocument(TargetDoc);
    if Lib.MergeDocumentEx(SourceDoc, dfsReject) = 0 then
    begin
      if Lib.LastErrorCode = 705 then
      begin
        // Les deux documents sont toujours intacts - réessayez avec une politique
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

    Lib.SaveToFile('application-complete.pdf');
  finally
    Lib.Free;
  end;
end;

Notez le schéma en deux étapes dans ce code, qui n'est possible que parce que le rejet n'est pas destructeur. Essayez d'abord la politique stricte, examinez l'erreur, puis décidez. Avec une fusion qui échoue à mi-chemin, le repli devrait recommencer en rechargeant les deux fichiers

À quoi ressemble le formulaire fusionné ensuite

Sous dfsMerge, un champ cible nommé Shared portant « Target value » et un champ source portant le même nom produisent deux champs, tous deux nommés Shared, rapportant tous deux la valeur cible, car la valeur cible et la valeur par défaut sont synchronisées dans le champ entrant. C'est la sémantique voulue pour un nom partagé : un champ logique, plusieurs widgets, une valeur

Sous dfsAutoNumber, la même entrée produit Shared et Shared_2 comme champs distincts avec des valeurs indépendantes. Choisissez entre les deux en posant une seule question : remplir un contrôle doit-il remplir l'autre ? Pour un nom de signataire répété sur chaque partie d'un dossier, oui, et dfsMerge est le bon choix. Pour un total qui signifie quelque chose de différent sur chaque formulaire, non, et la numérotation automatique est le bon choix

// Après une fusion, énumérez ce que vous avez réellement obtenu
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Remarques pratiques pour assembler des dossiers de formulaires

Une fusion réussie consomme le document source : il est retiré de la liste de documents de la bibliothèque, c'est pourquoi DocumentCount passe de deux à un. Ne continuez pas à utiliser l'identifiant source ensuite. La version du document est portée à la plus élevée des deux, donc fusionner un formulaire PDF 2.0 dans un document 1.7 produit un fichier 2.0

L'ordre compte pour les noms. Fusionner A dans B et fusionner B dans A produisent des résultats de numérotation automatique différents, puisque le document qui effectue la fusion conserve ses noms inchangés. Lorsqu'un dossier possède un formulaire principal canonique, faites-en la cible

Les champs de signature méritent leur propre réflexion. Une signature appliquée avant une fusion ne couvre que la révision qu'elle a signée, donc la fusion l'invalide dans le sens pratique où le fichier a changé depuis la signature. Assemblez d'abord et signez le document assemblé, plutôt que de fusionner des parties déjà signées. Lorsque la fusion porte sur le contenu de page plutôt que sur des formulaires, le chemin plus rapide décrit dans la fusion PDF rapide par décalage de référence d'octets est le meilleur outil

Enfin, planifiez le volet données du dossier conjointement avec la fusion. Si les valeurs de champs proviennent d'un système externe, déterminez si ce système adresse les champs par nom avant de choisir la numérotation automatique, car Shared_2 ne correspondra pas à une correspondance qui attend Shared. Les formats d'import et d'export sont traités dans l'échange de données de formulaire FDF, XFDF et XFA, et le comportement de script au niveau des champs qui peut également être affecté par le renommage est traité dans les actions de formulaire interactives et JavaScript

La fusion de formulaires, l'échange de données et la signature s'exécutent dans la même bibliothèque pour Delphi, C++Builder et Free Pascal ; la liste complète des fonctionnalités se trouve sur la page PDF Library for Delphi