Articolo tecnico

Interscambio di dati modulo FDF, XFDF e XFA in Delphi

losLab PDF Library sposta i dati dei moduli dentro e fuori da un PDF in tre modi diversi: FDF e XFDF per i valori dei campi AcroForm, gli stessi due formati per i commenti delle annotazioni e un intero pacchetto XFA XDP scritto direttamente nel modulo. ExportFormDataToXFDF, ImportFormDataFromXFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF e SetXFAFromString sono i punti di ingresso, e ognuno di essi ha una variante di file e una variante di stringa

Il motivo per cui ci sono così tanti metodi è che i dati dei moduli PDF non sono un'unica cosa. Un AcroForm compilato contiene valori dei campi, può anche contenere commenti di revisione e un modulo XFA legacy incorpora un'intera descrizione dell'applicazione XML che non ha nulla a che fare con nessuno dei due. losLab PDF Library mantiene queste tre preoccupazioni su API separate di proposito, perché unirle costringerebbe ad adottare un modello di dati errato su almeno due di esse. Decidere di quale famiglia si ha bisogno è la prima scelta di progettazione, e di solito viene stabilita nel momento in cui si sa cosa consuma effettivamente il sistema ricevente dall'altra parte

Qual è la differenza tra i dati dei moduli FDF, XFDF e XFA?

FDF e XFDF trasportano le stesse informazioni in due sintassi diverse, e XFA è un mondo a parte. FDF è un documento in miniatura con sintassi PDF (ISO 32000-2 §12.7.8): un array /Fields di dizionari << /T (name) /V (value) >>, con stringhe di cui è stato fatto l'escape esattamente come per le stringhe letterali all'interno di un PDF. XFDF è la forma XML degli stessi dati (ISO 19444), un albero <fields> che Acrobat e la maggior parte dei backend dei moduli leggono e scrivono nativamente. XFA non è nessuno dei due: è un modello XML Forms Architecture più i suoi dati, memorizzato come XML Data Package (XDP) a cui il PDF fa riferimento da /AcroForm/XFA. Scegli FDF or XFDF quando interoperi sui valori dei campi, e ricorri a XFA solo quando stai mantenendo un documento creato originariamente come modulo XFA

All'interno di FDF e XFDF, losLab PDF Library traccia una seconda linea: valori dei campi contro commenti delle annotazioni. La famiglia dei dati modulo (ExportFormDataToFDF, ImportFormDataFromFDF, ExportFormDataToXFDF, ImportFormDataFromXFDF) legge e scrive il sottoalbero /Fields e non tocca altro. La famiglia delle annotazioni (ExportAnnotationsToFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF, ImportAnnotationsFromXFDF) legge e scrive invece il sottoalbero /Annots, che è ciò che Acrobat intende per Esporta commenti. I due non si sovrappongono mai, quindi l'esportazione dei valori dei campi non raccoglierà note di revisione vaganti e l'importazione dei commenti non disturberà i valori già digitati da un utente. Ciò che un widget fa quando viene cliccato o ricalcolato è una terza preoccupazione, trattata nella nota di accompagnamento sulle azioni interattive dei moduli e JavaScript

Come si popola un modulo PDF da FDF o XFDF in Delphi?

Carica il documento, chiama un metodo di importazione e salva. ImportFormDataFromXFDF e ImportFormDataFromFDF analizzano ciascuno i dati del modulo in entrata, associano ogni voce a un campo AcroForm tramite il suo nome completo, impostano il valore e restituiscono il numero di campi effettivamente aggiornati. Entrambi i metodi aggiornano solo i campi che già esistono nel PDF di destinazione; nessuno dei due crea un campo per un nome che il modulo non definisce, il che impedisce a un file di dati vagante o ostile di far crescere silenziosamente il tuo modulo

var
  Lib: TPDFlib;
  FieldsSet: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('application-blank.pdf', '');
    // XFDF prodotto da a case-management system, già UTF-8 on disk
    FieldsSet := Lib.ImportFormDataFromXFDF('applicant-1042.xfdf');
    if FieldsSet > 0 then
      Lib.SaveToFile('application-filled.pdf');
  finally
    Lib.Free;
  end;
end;

Due dettagli decidono se i dati complessi sopravvivono al viaggio. Il primo è l'escaping: FDF memorizza i valori come stringhe letterali PDF, quindi un valore contenente una parentesi, un backslash o un byte non stampabile arriva avvolto nell'escaping ottale definito in ISO 32000-2 §7.3.4.2, e losLab PDF Library inverte tale escaping all'importazione in modo che le parentesi e le barre tornino come i caratteri letterali digitati dall'utente. Il secondo è la codifica: i metodi basati su file scrivono e leggono XFDF e FDF como UTF-8, che è ciò che la dichiarazione XFDF promette e ciò di cui ha bisogno qualsiasi valore di campo non ASCII (un nome accentato, un simbolo di valuta, un indirizzo CJK) per completare il percorso senza corruzioni. Se crei tu stesso l'XFDF, dichiara UTF-8 e scrivi UTF-8, e l'importazione sarà d'accordo con te

Campi gerarchici, scelte multivalore e rich text

I nomi dei campi gerarchici sono il primo punto in cui un esportatore ingenuo si rompe. AcroForm indirizza un campo nidificato con un titolo completo puntato come Applicant.FullName, ma XFDF non inserisce quella stringa puntata in un singolo attributo name; ISO 19444 la nidifica, come <field name="Applicant"><field name="FullName">. losLab PDF Library divide il titolo puntato in elementi <field> nidificati all'esportazione e riassembla gli elementi nidificati nel titolo completo all'importazione, in modo che le due direzioni rimangano simmetriche. All'esportazione salta anche i campi padre non terminali, perché un nodo padre in un AcroForm porta solo il livello di denominazione e non ha alcun valore proprio; emetterlo produrrebbe un <value></value> vuoto che non corrisponde al modulo reale. Le caselle di riepilogo a selezione multipla sono la seconda trappola: un campo di scelta può contenere più valori selezionati contemporaneamente, che XFDF esprime come elementi <value> ripetuti e FDF esprime come un array /V, e losLab PDF Library divide un campo in più valori solo quando si tratta di una scelta a selezione multipla effettiva, in modo che un normale campo di testo multilinea mantenga le sue interruzioni di riga invece di frantumarsi in valori fittizi

Il rich text è il terzo elemento, ed è quello in cui si sbaglia più spesso. Un campo con formattazione memorizza il suo markup nella voce RV come un sottoalbero XHTML, e XFDF lo trasporta in <value-richtext>. losLab PDF Library scrive quel sottoalbero come un frammento XML live anziché farne l'escape, in modo che uno strumento a valle legga vero rich text invece di una stringa di tag visibili; all'importazione preserva il sottoalbero RV grezzo e applica comunque il valore semplice <value> a V. Laddove un campo offre entrambi, il valore semplice vince per V e il rich text lo accompagna in RV, che è la regola di interoperabilità che impedisce a un'importazione rich-text di riscrivere silenziosamente un valore già impostato da un altro strumento. Quando il tuo rich text esiste per trasmettere la struttura del documento alle tecnologie assistive, trattalo nello stesso modo in cui tratteresti l'ordine di lettura discusso nella nota sul PDF taggato e struttura di accessibilità

var
  Lib: TPDFlib;
const
  XFDF =
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">' +
    '<fields>' +
    '  <field name="Applicant">' +
    '    <field name="FullName"><value>Alice Example</value></field>' +
    '  </field>' +
    '  <field name="Skills">' +
    '    <value>Delphi</value><value>PDF</value>' +
    '  </field>' +
    '  <field name="Notes">' +
    '    <value>See attachment</value>' +
    '    <value-richtext><body><p>See <b>attachment</b></p></body></value-richtext>' +
    '  </field>' +
    '</fields></xfdf>';
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('intake.pdf', '');
    // Applicant.FullName si riassembla; Skills riempie gli array /V + /I;
    // Notes ottiene V da <value> e RV da <value-richtext>
    Lib.ImportFormDataFromXFDFString(XFDF);
    Lib.SaveToFile('intake-filled.pdf');
  finally
    Lib.Free;
  end;
end;

Come si fa il round-trip dei commenti delle annotazioni come FDF o XFDF?

I commenti viaggiano sulla famiglia delle annotazioni, e il sottoinsieme supportato è deliberatamente piccolo. ExportAnnotationsToXFDF e ImportAnnotationsFromXFDF, con le loro controparti FDF, trasportano il markup in stile testo con i campi che effettivamente completano il ciclo in modo pulito: il sottotipo di annotazione, il suo rettangolo, il suo indice di pagina a base 0, l'autore in T, l'oggetto in Subj, il semplice Contents e il colore, che mappa la tripla DeviceRGB /C del PDF da e verso un attributo XFDF #RRGGBB. L'importazione accetta solo nomi di elementi noti e salta qualsiasi voce la cui pagina sia fuori intervallo o il cui rettangolo sia mancante, in modo che un XFDF modificato a mano o estraneo non possa rilasciare un'annotazione malformata nel documento. Ciò che il sottoinsieme non trasporta ancora vale la pena dichiararlo chiaramente: i contenuti rich-text (RC), i popup, i punti quadrangolari (quadpoint), gli elenchi di inchiostro, i vertici e i flussi di aspetto pronti (baked appearance streams) sono attualmente fuori portata, quindi la geometria dell'evidenziazione e gli aspetti personalizzati dei timbri non sopravvivranno a questo percorso. Per confermare ciò che è effettivamente arrivato, esamina le annotazioni della pagina o l'albero degli elementi più ampio trattato nella sezione ricerca di testo ed enumerazione degli elementi della pagina

var
  Src, Dest: TPDFlib;
  Xfdf: WideString;
begin
  Src := TPDFlib.Create;
  Dest := TPDFlib.Create;
  try
    Src.LoadFromFile('reviewed.pdf', '');
    Xfdf := Src.ExportAnnotationsToXFDFString;   // solo sottoinsieme <annots>
 
    Dest.LoadFromFile('clean-copy.pdf', '');     // le pagine devono allinearsi per indice
    Dest.ImportAnnotationsFromXFDFString(Xfdf);
    Dest.SaveToFile('clean-copy-commented.pdf');
  finally
    Dest.Free;
    Src.Free;
  end;
end;

Scrivere un intero pacchetto XFA XDP con SetXFAFromString

XFA è l'anomalia, e SetXFAFromString è il modo in cui scrivi l'intera cosa in una volta sola. Il metodo prende un XML Data Package completo, il documento <xdp:xdp> con i suoi pacchetti template e dataset, e lo memorizza come lo stream referenziato da /AcroForm/XFA. losLab PDF Library crea il contenitore AcroForm su richiesta quando il documento non ne ha alcuno, so you do not have to add a throwaway field just to give the XFA somewhere to hang, e scarta qualsiasi stato XFA analizzato che conteneva, in modo che una lettura successiva rifletta il pacchetto appena scritto anziché una cache obsoleta. Poiché un pacchetto XFA è in formato XML e non necessariamente in UTF-8, la libreria rileva un byte-order mark UTF-16 e decodifica correttamente il pacchetto prima dell'analisi, il che è importante per i pacchetti prodotti da strumenti che utilizzano per impostazione predefinita UTF-16

Una volta posizionato il pacchetto, GetXFAFormFieldValue e SetXFAFormFieldValue indirizzano i singoli campi attraverso il loro percorso Scripting Object Model. losLab PDF Library accetta le radici SOM standard così come percorsi relativi semplici, quindi form1.FullName, $data.form1.FullName e xfa.datasets.data.form1.FullName si risolvono tutti nello stesso nodo dati, e il lato template accetta $template e xfa.template nello stesso modo. Questa tolleranza è importante quando i percorsi SOM sono generati da un altro sistema che emette sempre il modulo con il nome completo

var
  Lib: TPDFlib;
const
  Xdp =
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">' +
    '  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">' +
    '    <subform name="form1">' +
    '      <field name="FullName"><ui><textEdit/></ui></field>' +
    '    </subform>' +
    '  </template>' +
    '  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">' +
    '    <xfa:data><form1><FullName>Alice Example</FullName></form1></xfa:data>' +
    '  </xfa:datasets>' +
    '</xdp:xdp>';
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetXFAFromString(Xdp, 0);      // crea il contenitore AcroForm se non esiste
    // rilegge attraverso il data DOM con un percorso SOM
    if Lib.GetXFAFormFieldValue('form1.FullName') = 'Alice Example' then
      Lib.SetXFAFormFieldValue('form1.FullName', 'Bob Example');
    Lib.SaveToFile('xfa-packet.pdf');
  finally
    Lib.Free;
  end;
end;

Il riepilogo onesto è che i valori dei campi completano pienamente il ciclo attraverso FDF e XFDF, inclusi gerarchia, selezione multipla e rich text; i commenti delle annotazioni completano il ciclo come un sottoinsieme di markup del testo definito con evidenti lacune; e XFA viene scritto e letto come un pacchetto intero con accesso SOM per campo in aggiunta. Associa il formato al consumatore, dichiara UTF-8 per qualsiasi cosa crei a mano, e ricorda che i metodi di importazione toccano solo i campi e le annotazioni che già si adattano al documento. I metodi per i dati dei moduli, le annotazioni e XFA qui descritti fanno parte di losLab PDF Library per Delphi e C++Builder, il cui riferimento riporta l'elenco completo dei parametri per ogni punto di ingresso di esportazione e importazione