Technisch artikel

FDF-, XFDF- en XFA-formuliergegevensuitwisseling in Delphi

losLab PDF Library verplaatst formuliergegevens in en uit een PDF op drie verschillende manieren: FDF en XFDF voor AcroForm-veldwaarden, dezelfde twee formaten voor annotatieopmerkingen, en een heel XFA XDP-pakket dat rechtstreeks in het formulier wordt geschreven. ExportFormDataToXFDF, ImportFormDataFromXFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF en SetXFAFromString zijn de toegangspunten, en elk daarvan heeft een bestandswariant en een stringwariant

De reden dat er zoveel methoden zijn, is dat PDF-formuliergegevens niet één ding zijn. Een ingevuld AcroForm heeft veldwaarden, kan ook beoordelingsopmerkingen bevatten, en een legacy XFA-formulier sluit een hele XML-toepassingsbeschrijving in die met geen van beide iets te maken heeft. losLab PDF Library houdt deze drie zorgen bewust gescheiden op verschillende API's, omdat het samenvoegen ervan een verkeerd datamodel zou afdwingen voor ten minste twee ervan. Beslissen welke familie u nodig hebt is de eerste ontwerpkeuze, en deze is meestal beslecht zodra u weet wat het ontvangende systeem aan de andere kant daadwerkelijk verbruikt

Wat is het verschil tussen FDF-, XFDF- en XFA-formuliergegevens?

FDF en XFDF dragen dezelfde informatie in twee verschillende syntactische vormen, en XFA is een aparte wereld. FDF is een miniatuur PDF-syntaxisdocument (ISO 32000-2 §12.7.8): een /Fields-array van << /T (naam) /V (waarde) >>-dictionaries, waarbij strings precies zo zijn ge-escaped als literal strings binnen een PDF. XFDF is de XML-vorm van dezelfde gegevens (ISO 19444), een <fields>-boomstructuur die Acrobat en de meeste formulier-backends native lezen en schrijven. XFA is geen van beide: het is een XML Forms Architecture-sjabloon plus de gegevens ervan, opgeslagen als een XML Data Package (XDP) waarnaar de PDF verwijst vanuit /AcroForm/XFA. Kies FDF of XFDF wanneer u samenwerkt op basis van veldwaarden, en grijp alleen naar XFA wanneer u een document onderhoudt dat in de eerste plaats als een XFA-formulier is ontworpen

Binnen FDF en XFDF trekt losLab PDF Library een tweede grens: veldwaarden versus annotatieopmerkingen. De formuliergegevensfamilie (ExportFormDataToFDF, ImportFormDataFromFDF, ExportFormDataToXFDF, ImportFormDataFromXFDF) leest en schrijft de /Fields-subboom en raakt niets anders aan. De annotatiefamilie (ExportAnnotationsToFDF, ImportAnnotationsFromFDF, ExportAnnotationsToXFDF, ImportAnnotationsFromXFDF) leest en schrijft in plaats daarvan de /Annots-subboom, wat Acrobat bedoelt met Opmerkingen Exporteren. De twee overlappen elkaar nooit, dus het exporteren van veldwaarden zal geen verdwaalde beoordelingsnotities verzamelen, en het importeren van opmerkingen zal de waarden die een gebruiker al heeft getypt niet verstoren. Wat een widget doet wanneer erop wordt geklikt of wanneer deze opnieuw wordt berekend is een derde aspect, dat wordt behandeld in de begeleidende notitie over interactieve formulieracties en JavaScript

Hoe vult u een PDF-formulier in vanuit FDF of XFDF in Delphi?

Laad het document, roep één importmethode aan en sla het op. ImportFormDataFromXFDF en ImportFormDataFromFDF parsen elk de inkomende formuliergegevens, koppelen elk item aan een AcroForm-veld via de volledig gekwalificeerde naam, stellen de waarde in en retourneren het aantal daadwerkelijk bijgewerkte velden. Beide methoden werken alleen velden bij die al in de doel-PDF bestaan; ze verzinnen geen veld voor een naam die het formulier niet definieert, wat voorkomt dat een verdwaald of kwaadaardig gegevensbestand uw formulier stilletjes uitbreidt

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;

Twee details bepalen of niet-triviale gegevens de reis overleven. Het eerste is escaping: FDF slaat waarden op als PDF-literal strings, dus een waarde met een haakje, een backslash of een niet-afdrukbare byte komt binnen verpakt in de octale escaping gedefinieerd in ISO 32000-2 §7.3.4.2, en losLab PDF Library keert die escaping om bij import, zodat de haakjes en slashes terugkeren als de letterlijke tekens die de gebruiker heeft getypt. Het tweede is codering: de op bestanden gebaseerde methoden schrijven en lezen XFDF en FDF als UTF-8, wat de XFDF-declaratie belooft en wat elke niet-ASCII veldwaarde (een naam met accenten, een valutasymbool, een CJK-adres) nodig heeft om zonder corruptie de ronde te maken. Als u de XFDF zelf bouwt, declareer dan UTF-8 en schrijf UTF-8, en the import zal hiermee overeenstemmen

Hiërarchische velden, keuzes met meerdere waarden en rich text

Hiërarchische veldennamen zijn de eerste plaats waar een naïeve exporteur faalt. AcroForm adresseert een genest veld met een gestippelde volledige titel zoals Applicant.FullName, maar XFDF plaatst die gestippelde string niet in één naamattribuut; ISO 19444 nest deze, als <field name="Applicant"><field name="FullName">. losLab PDF Library splitst de gestippelde titel in geneste <field>-elementen bij export en voegt de geneste elementen weer samen tot de volledige titel bij import, zodat de twee richtingen symmetrisch blijven. Bij export slaat het ook niet-terminale bovenliggende velden over, omdat een bovenliggende node in een AcroForm alleen het naamgevingsniveau draagt en geen eigen waarde heeft; het verzenden ervan zou een leeg <value></value> opleveren dat niet overeenkomt met het echte formulier. Keuzelijsten met meervoudige selectie zijn de tweede valkuil: een keuzeveld kan meerdere geselecteerde waarden tegelijk bevatten, wat XFDF uitdrukt als herhaalde <value>-elementen en FDF uitdrukt als een /V-array, en losLab PDF Library splitst een veld alleen op in meerdere waarden als het echt een meervoudige selectiekeuze is, zodat een gewoon tekstveld met meerdere regels zijn regeleinden behoudt in plaats van uiteen te vallen in valse waarden

Rich text is de derde, en degene die mensen het vaakst fout doen. Een veld met opmaak slaat de opmaak op in het RV-item als een XHTML-subboom, en XFDF draagt dit in <value-richtext>. losLab PDF Library schrijft die subboom als een live XML-fragment in plaats van het te escapen, zodat een stroomafwaarts hulpprogramma echte rich text leest in plaats van een string met zichtbare tags; bij import behoudt het de ruwe RV-subboom en past het nog steeds de gewone <value> toe op V. Waar een veld beide biedt, wint de gewone waarde voor V en reist de rich text mee in RV, wat de interoperabiliteitsregel is die voorkomt dat een rich-text import stilletjes een waarde overschrijft die een ander hulpprogramma al heeft ingesteld. Wanneer uw rich text bestaat om documentstructuur over te dragen aan ondersteunende technologie, behandel deze dan op dezelfde manier als de leesvolgorde die wordt besproken in de notitie over getagde PDF en toegankelijkheidsstructuur

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;

Hoe wisselt u annotatieopmerkingen als FDF of XFDF?

Opmerkingen reizen via de annotatiefamilie, en de ondersteunde subset is bewust klein. ExportAnnotationsToXFDF en ImportAnnotationsFromXFDF, met hun FDF-tegenhangers, dragen tekstachtige opmaak met de velden die daadwerkelijk schoon round-trippen: het annotatiesubtype, de rechthoek ervan, de 0-gebaseerde pagina-index, de auteur in T, het onderwerp in Subj, de platte Contents en de kleur, die het PDF /C DeviceRGB-trio koppelt van en naar een XFDF #RRGGBB-attribuut. Import accepteert alleen bekende elementnamen en slaat elk item over waarvan de pagina buiten bereik is of waarvan de rechthoek ontbreekt, zodat een handmatig bewerkte of vreemde XFDF geen misvormde annotatie in het document kan plaatsen. Wat de subset nog niet draagt is de moeite waard om duidelijk te vermelden: rich-text inhoud (RC), pop-ups, quadpoints, inktlijsten, hoekpunten en ingebakken weergavestromen vallen vooralsnog buiten het bereik, dus highlight-geometrie en aangepaste stempelweergaven zullen dit pad niet overleven. Om te bevestigen wat er daadwerkelijk is geland, doorloopt u de pagina-annotaties of de bredere elementenboom die wordt behandeld in tekst zoeken en pagina-elementenopsomming

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;

Een volledig XFA XDP-pakket schrijven met SetXFAFromString

XFA is de buitenbeentje, en SetXFAFromString is hoe u het hele ding in één keer schrijft. De methode neemt een volledig XML Data Package, het <xdp:xdp>-document met zijn sjabloon- en datasetspakketten, en slaat dit op als de stream waarnaar wordt verwezen vanuit /AcroForm/XFA. losLab PDF Library maakt de AcroForm-container op aanvraag aan wanneer het document er geen heeft, zodat u geen wegwerpveld hoeft toe te voegen om de XFA ergens aan op te hangen, en het gooit elke geparste XFA-status weg die het vasthield, zodat een latere lezing het pakket weerspiegelt dat u zojuist hebt geschreven in plaats van een verouderde cache. Omdat een XFA-pakket XML is en niet noodzakelijkerwijs UTF-8, de bibliotheek detecteert een UTF-16 byte-order mark en decodeert het pakket correct vóór het parsen, wat belangrijk is voor pakketten die zijn geproduceerd door tools die standaard UTF-16 gebruiken

Zoba het pakket op zijn plaats is, adresseren GetXFAFormFieldValue en SetXFAFormFieldValue afzonderlijke velden via hun Scripting Object Model-pad. losLab PDF Library accepteert de standaard SOM-roots en kale relatieve paden, dus form1.FullName, $data.form1.FullName en xfa.datasets.data.form1.FullName verwijzen allemaal naar dezelfde datanode, en de sjabloonkant accepteert $template en xfa.template op dezelfde manier. Die tolerantie is belangrijk wanneer de SOM-paden worden gegenereerd door een ander systeem dat altijd de volledig gekwalificeerde vorm verzendt

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;

De eerlijke samenvatting is dat veldwaarden volledig round-trippen via FDF en XFDF, inclusief hiërarchie, meervoudige selectie en rich text; annotatieopmerkingen round-trippen als een gedefinieerde subset van tekstmarkeringen met duidelijke hiaten; en XFA wordt geschreven en gelezen als een heel pakket met daarbovenop SOM-toegang per veld. Stem het formaat af op de afnemer, declareer UTF-8 voor alles wat u handmatig bouwt, en onthoud dat de importmethoden alleen velden en annotaties aanraken die al in het document passen. De formuliergegevens-, annotatie- en XFA-methoden die hier worden beschreven, maken deel uit van de losLab PDF Library voor Delphi en C++Builder, waarvan de referentie de volledige parameterlijst bevat voor elk export- en importtoegangspunt