Technical Article

FDF, XFDF and XFA Form Data Interchange in Delphi

Die losLab PDF Library verschiebt Formulardaten auf drei verschiedene Arten in ein PDF und aus diesem heraus: FDF und XFDF für AcroForm-Feldwerte, dieselben zwei Formate für Anmerkungskommentare und ein komplettes XFA XDP-Paket, das direkt in das Formular geschrieben wird. ExportFormDataToXFDF, ImportFormDataFromXFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF und SetXFAFromString sind die Einstiegspunkte, und jeder von ihnen verfügt über eine Datei- und eine String-Variante

Der Grund für die Vielzahl an Methoden liegt darin, dass PDF-Formulardaten nicht einheitlich strukturiert sind. Ein ausgefülltes AcroForm enthält Feldwerte, kann aber auch Review-Kommentare tragen, und ein veraltetes XFA-Formular bettet eine vollständige XML-Anwendungsbeschreibung ein, die mit beidem nichts zu tun hat. Die losLab PDF Library trennt diese drei Aufgaben bewusst in unterschiedlichen APIs, da deren Zusammenführung mindestens zwei davon ein falsches Datenmodell aufzwingen würde. Die Entscheidung, welche Familie Sie benötigen, ist die erste Entwurfsentscheidung und klärt sich meist in dem Moment, in dem Sie wissen, was das empfangende System auf der anderen Seite tatsächlich verarbeitet

Was ist der Unterschied zwischen FDF-, XFDF- und XFA-Formulardaten?

FDF und XFDF übertragen dieselben Informationen in zwei verschiedenen Syntaxen, während XFA eine eigene Welt darstellt. FDF ist ein Dokument im PDF-Kleinstformat (ISO 32000-2 §12.7.8): ein /Fields-Array aus << /T (Name) /V (Wert) >>-Dictionaries, wobei Zeichenketten genau so maskiert sind, wie Literal-Strings innerhalb einer PDF-Datei maskiert werden. XFDF ist die XML-Form derselben Daten (ISO 19444), ein <fields>-Baum, den Acrobat und die meisten Formular-Backends nativ lesen und schreiben. XFA ist keines von beiden: Es handelt sich um eine XML Forms Architecture-Vorlage mitsamt ihren Daten, gespeichert als XML Data Package (XDP), auf das die PDF-Datei von /AcroForm/XFA aus verweist. Wählen Sie FDF oder XFDF, wenn Sie mit Feldwerten interagieren, und greifen Sie nur dann zu XFA, wenn Sie ein Dokument pflegen, das ursprünglich als XFA-Formular erstellt wurde

Innerhalb von FDF und XFDF zieht die losLab PDF Library eine zweite Grenze: Feldwerte gegenüber Anmerkungskommentaren. Die Formulardaten-Familie (ExportFormDataToFDF, ImportFormDataFromFDF, ExportFormDataToXFDF, ImportFormDataFromXFDF) liest und schreibt den /Fields-Unterbaum und rührt nichts anderes an. Die Anmerkungs-Familie (ExportAnnotationsToFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF, ImportAnnotationsFromXFDF) liest und schreibt stattdessen den Unterbaum /Annots, was Acrobat unter „Kommentare exportieren“ versteht. Beide überschneiden sich nie, sodass der Export von Feldwerten keine verstreuten Review-Notizen erfasst und das Importieren von Kommentaren die bereits vom Benutzer eingegebenen Werte nicht stört. Was ein Widget tut, wenn es angeklickt oder neu berechnet wird, ist ein dritter Aspekt, der in der begleitenden Notiz über interaktive Formularaktionen und JavaScript behandelt wird

Wie füllt man ein PDF-Formular aus FDF oder XFDF in Delphi aus?

Laden Sie das Dokument, rufen Sie eine Importmethode auf und speichern Sie. ImportFormDataFromXFDF und ImportFormDataFromFDF parsen jeweils die eingehenden Formulardaten, gleichen jeden Eintrag mit einem AcroForm-Feld über seinen vollqualifizierten Namen ab, setzen den Wert und geben die Anzahl der tatsächlich aktualisierten Felder zurück. Beide Methoden aktualisieren nur Felder, die bereits im Ziel-PDF vorhanden sind; keine von beiden erzeugt ein Feld für einen Namen, den das Formular nicht definiert. Dies verhindert, dass eine fehlerhafte oder feindliche Datendatei Ihr Formular im Stillen vergrößert

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

Zwei Details entscheiden darüber, ob komplexe Daten den Weg überstehen. Das erste ist das Escaping: FDF speichert Werte als PDF-Literal-Strings, sodass ein Wert, der eine Klammer, einen Backslash oder ein nicht druckbares Byte enthält, in dem in ISO 32000-2 §7.3.4.2 definierten oktalen Escaping verpackt ankommt. Die losLab PDF Library macht dieses Escaping beim Import rückgängig, sodass die Klammern und Schrägstriche wieder als die vom Benutzer eingegebenen Literalzeichen erscheinen. Das zweite ist die Kodierung: Die dateibasierten Methoden schreiben und lesen XFDF und FDF als UTF-8, was in der XFDF-Deklaration versprochen wird und was jeder Nicht-ASCII-Feldwert (ein Name mit Umlauten, ein Währungssymbol, eine CJK-Adresse) erfordert, um ohne Beschädigung hin- und zurückübertragen zu werden. Wenn Sie das XFDF selbst erstellen, deklarieren Sie UTF-8 und schreiben Sie UTF-8, damit der Import damit übereinstimmt

Hierarchische Felder, Auswahlen mit mehreren Werten und Rich-Text

Hierarchische Feldnamen sind die erste Stelle, an der ein naiver Exporter scheitert. AcroForm spricht ein geschachteltes Feld mit einem durch Punkte getrennten vollständigen Namen an, wie etwa Applicant.FullName, aber XFDF speichert diesen Punkt-String nicht in einem einzelnen Name-Attribut; ISO 19444 schachtelt ihn als <field name="Applicant"><field name="FullName">. Die losLab PDF Library teilt den Punkt-Namen beim Export in geschachtelte <field>-Elemente auf und setzt die geschachtelten Elemente beim Import wieder zum vollständigen Namen zusammen, sodass beide Richtungen symmetrisch bleiben. Beim Export überspringt sie zudem nicht-terminale Elternfelder, da ein Elternknoten in einem AcroForm nur die Namensebene trägt und keinen eigenen Wert hat; ihn auszugeben würde ein leeres <value></value> erzeugen, das nicht dem echten Formular entspricht. Mehrfachauswahl-Listenboxen sind die zweite Falle: Ein Auswahlfeld kann mehrere ausgewählte Werte gleichzeitig enthalten, was XFDF als wiederholte <value>-Elemente und FDF als ein /V-Array ausdrückt. Die losLab PDF Library teilt ein Feld nur dann in mehrere Werte auf, wenn es sich tatsächlich um eine Mehrfachauswahl handelt, sodass ein gewöhnliches mehrzeiliges Textfeld seine Zeilenumbrüche behält, anstatt in Scheinelemente zerlegt zu werden

Rich-Text ist das dritte und am häufigsten missverstandene Thema. Ein Feld mit Formatierung speichert sein Markup im Eintrag RV als XHTML-Unterbaum, und XFDF überträgt dieses in <value-richtext>. Die losLab PDF Library schreibt diesen Unterbaum als echtes XML-Fragment, anstatt ihn zu maskieren, sodass ein nachgelagertes Tool echten Rich-Text anstelle einer Reihe sichtbarer Tags liest; beim Import bleibt der rohe RV-Unterbaum erhalten und der einfache <value> wird weiterhin auf V angewendet. Wenn ein Feld beides anbietet, gewinnt der einfache Wert für V und der Rich-Text wird in RV mitgeführt. Dies ist die Interoperabilitätsregel, die verhindert, dass ein Rich-Text-Import stillschweigend einen Wert überschreibt, den ein anderes Tool bereits gesetzt hat. Wenn Ihr Rich-Text dazu dient, die Dokumentenstruktur an assistive Technologien zu vermitteln, behandeln Sie ihn genauso wie die im Hinweis zu Tagged PDF und Barrierefreiheitsstruktur beschriebene Lesereihenfolge

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 reassembles; Skills fills the /V + /I arrays;
    // Notes gets V from <value> and RV from <value-richtext>
    Lib.ImportFormDataFromXFDFString(XFDF);
    Lib.SaveToFile('intake-filled.pdf');
  finally
    Lib.Free;
  end;
end;

Wie überträgt man Anmerkungskommentare als FDF oder XFDF hin und zurück?

Kommentare reisen über die Anmerkungs-Familie, und die unterstützte Teilmenge ist bewusst klein gehalten. ExportAnnotationsToXFDF und ImportAnnotationsFromXFDF übertragen zusammen mit ihren FDF-Gegenstücken textbasiertes Markup mit den Feldern, die sich sauber hin- und zurückübertragen lassen: der Anmerkungstyp, sein Rechteck, sein 0-basierter Seitenindex, der Autor in T, das Thema in Subj, der einfache Inhalt in Contents und die Farbe, die das PDF-Tripel /C DeviceRGB in ein XFDF-Attribut #RRGGBB und umgekehrt abbildet. Der Import akzeptiert nur bekannte Elementnamen und überspringt jeden Eintrag, dessen Seite außerhalb des gültigen Bereichs liegt oder dessen Rechteck fehlt. Auf diese Weise kann ein manuell bearbeitetes oder fremdes XFDF keine fehlerhafte Anmerkung in das Dokument einschleusen. Was die Teilmenge derzeit nicht überträgt, sollte deutlich gesagt werden: Rich-Text-Inhalte (RC), Popups, Quadpoints, Tintenlisten, Scheitelpunkte und eingebrannte Darstellungsströme liegen vorerst außerhalb des Leistungsumfangs, sodass Hervorhebungsgeometrien und benutzerdefinierte Stempelbilder diesen Pfad nicht überstehen. Um zu überprüfen, was tatsächlich angekommen ist, durchlaufen Sie die Seitenanmerkungen oder den größeren Elementbaum, der unter Textsuche und Aufzählung von Seitenelementen beschrieben ist

var
  Src, Dest: TPDFlib;
  Xfdf: WideString;
begin
  Src := TPDFlib.Create;
  Dest := TPDFlib.Create;
  try
    Src.LoadFromFile('reviewed.pdf', '');
    Xfdf := Src.ExportAnnotationsToXFDFString;   // <annots> subset only

    Dest.LoadFromFile('clean-copy.pdf', '');     // pages must line up by index
    Dest.ImportAnnotationsFromXFDFString(Xfdf);
    Dest.SaveToFile('clean-copy-commented.pdf');
  finally
    Dest.Free;
    Src.Free;
  end;
end;

Schreiben eines vollständigen XFA XDP-Pakets mit SetXFAFromString

XFA ist der Ausreißer, und SetXFAFromString ist der Weg, wie Sie das Ganze auf einmal schreiben. Die Methode nimmt ein vollständiges XML Data Package, das <xdp:xdp>-Dokument mit seinen Template- und Datasets-Paketen, und speichert es als den Stream, auf den von /AcroForm/XFA verwiesen wird. Die losLab PDF Library erstellt den AcroForm-Container bei Bedarf, wenn das Dokument noch keinen besitzt, sodass Sie kein temporäres Feld hinzufügen müssen, nur um dem XFA einen Einhängepunkt zu bieten. Zudem verwirft sie jeden zwischengespeicherten XFA-Zustand, sodass ein späterer Lesevorgang das soeben geschriebene Paket widerspiegelt und nicht einen veralteten Cache. Da ein XFA-Paket XML ist und nicht zwingend UTF-8 sein muss, erkennt die Bibliothek eine UTF-16-Byte-Reihenfolgemarkierung (BOM) und dekodiert das Paket vor dem Parsen korrekt. Dies ist wichtig für Pakete, die von Tools erstellt wurden, die standardmäßig UTF-16 verwenden

Sobald das Paket platziert ist, sprechen GetXFAFormFieldValue and SetXFAFormFieldValue einzelne Felder über ihren Scripting Object Model (SOM)-Pfad an. Die losLab PDF Library akzeptiert die standardmäßigen SOM-Wurzeln ebenso wie einfache relative Pfade, sodass form1.FullName, $data.form1.FullName und xfa.datasets.data.form1.FullName alle auf denselben Datenknoten verweisen. Auf der Vorlagenseite werden $template und xfa.template in gleicher Weise akzeptiert. Diese Toleranz ist wichtig, wenn die SOM-Pfade von einem anderen System generiert werden, das immer die vollqualifizierte Form ausgibt

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);      // creates the AcroForm container if none exists
    // read back through the data DOM with an SOM path
    if Lib.GetXFAFormFieldValue('form1.FullName') = 'Alice Example' then
      Lib.SetXFAFormFieldValue('form1.FullName', 'Bob Example');
    Lib.SaveToFile('xfa-packet.pdf');
  finally
    Lib.Free;
  end;
end;

Zusammenfassend lässt sich sagen, dass Feldwerte über FDF und XFDF vollständig übertragen werden, einschließlich Hierarchie, Mehrfachauswahl und Rich-Text; Anmerkungskommentare werden als definierte textbasierte Markup-Teilmenge mit klaren Lücken übertragen; und XFA wird als ganzes Paket mit SOM-Zugriff pro Feld geschrieben und gelesen. Passen Sie das Format an den Empfänger an, deklarieren Sie UTF-8 für alles, was Sie manuell erstellen, und denken Sie daran, dass die Importmethoden nur Felder und Anmerkungen berühren, die bereits in das Dokument passen. Die hier beschriebenen Formulardaten-, Anmerkungs- und XFA-Methoden sind Teil der losLab PDF Library für Delphi und C++Builder, deren Referenz die vollständige Parameterliste für jeden Export- und Importeinstiegspunkt enthält