Ja — PDFium ondersteunt XFA-formulieren, met één belangrijke splitsing. De PDFium-component kan een XFA-formulier detecteren en de template-, datasets- en config-pakketten ervan extraheren als ruwe XML met behulp van de standaard DLL, met nul afhankelijkheden in runtime. Het uitvoeren van een dynamisch XFA-formulier — de XML-beschrijving ervan indelen in live, interactieve pagina's — is een apart probleem dat bovendien de V8-ingeschakelde build vereist. De community blijft zich afvragen of PDFium "XFA doet" alsof het een simpele ja-of-nee-mogelijkheid is, en het eerlijke antwoord is dat het lezen van XFA-gegevens en het renderen van een XFA-formulier twee heel verschillende dingen zijn met twee heel verschillende afhankelijkheidsvoetafdrukken
Dit artikel behandelt beide helften met de PDFium-component, de systeemeigen VCL-wrapper rond PDFium voor Delphi en C++Builder. Eerst het detectie- en statische extractiepad — FormType, de XFA-eigenschap en de GetXfaPacket*-lezers — daarna het dynamische runtimepad met zijn V8-beperking, en tot slot de tests voor mogelijkheden die u in staat stellen om elegant op te vangen wanneer een geïmplementeerde DLL de engine niet kan uitvoeren
Ondersteunt PDFium XFA-formulieren?
Zie het als twee mogelijkheden, niet één. Statische XFA-extractie is altijd beschikbaar: de low-level exports FPDF_GetXFAPacket* die GetXfaPacketCount, GetXfaPacketName en GetXfaPacketContent ondersteunen, zijn gecompileerd in elke redelijk moderne pdfium.dll, zelfs in een build waarin XFA is uitgeschakeld. Dat is de reden waarom het ophalen van de ruwe XML van template, datasets en config uit een formulier helemaal geen runtime-afhankelijkheid met zich meebrengt — het is puur structureel lezen, er komt geen engine aan te pas. Dynamische XFA is de andere mogelijkheid: het daadwerkelijk uitvoeren van het formulier, het evalueren van de berekeningen ervan en het laten pagineren door de XFA-lay-outengine. Dat pad bestaat alleen in een PDFium-build die is gecompileerd met XFA-ondersteuning bovenop de V8 JavaScript-engine, en dat is het deel waar de meeste discussies over "ondersteunt PDFium XFA" werkelijk over gaan
De standaarden kaderen de splitsing helder in. XFA is buiten de kern-PDF-grammatica gedefinieerd in Adobe XFA 3.3 (2012), en PDF 1.7 §8.6.7 beschrijft hoe een XFA-formulier in een PDF wordt gedragen: de formulierpakketten bevinden zich onder de ingang /XFA van het AcroForm-dictionary, en een document dat door een XFA-processor moet worden ingedeeld voordat het betekenis heeft, stelt /NeedsRendering true in de catalogus in. Die twee markeringen zijn precies waar de PDFium-component naar zoekt, en precies waar u op kunt vertrouwen wanneer u redeneert over een bestand dat u nog nooit hebt gezien
Hoe verschilt XFA van een AcroForm?
Een AcroForm is het klassieke PDF-formulier: widget-annotaties — tekstvelden, selectievakjes, keuzerondjes — verankerd op vaste coördinaten op gewone PDF-pagina's. De pagina die u ziet is the pagina in het bestand, en de velden liggen er bovenop. XFA kiest de tegenovergestelde benadering. Het interactieve formulier wordt volledig in XML beschreven, en in een volledig (dynamisch) XFA-document zijn de PDF-pagina's in feite een tijdelijke aanduiding — de werkelijke inhoud wordt tijdens het openen gegenereerd door een XFA-engine die de XML-template leest en indeelt, waarbij deze groeit of krimpt om aan de gegevens te voldoen. Dat is wat /NeedsRendering true aankondigt: zonder een XFA-processor kunnen de statische pagina's leeg zijn of alleen een melding dragen van "open a.u.b. in een compatibele viewer"
De XFA-lading zelf is een set van benoemde pakketten, en drie daarvan bevatten alles wat de meeste integraties nodig hebben. Het template-pakket is de formulierdefinitie — velden, lay-out, berekeningen en scripts. Het datasets-pakket bevat de werkelijke instantiegegevens, de ingevulde veldwaarden als een XML-boomstructuur. Het config-pakket bevat verwerkingsinstructies voor de XFA-engine. Er bestaan er nog meer — localeSet, connectionSet, de xdp-wrapper — maar voor het controleren van een formulier of het migreren van de gegevens eruit is template plus datasets meestal de hele taak. Omdat die gegevens gewoon XML in een stream zijn, vereist het lezen ervan nooit het uitvoeren van wat dan ook, wat de fundamentele reden is waarom statische extractie vrij is van afhankelijkheden
Een XFA-formulier detecteren in Delphi
Detectie begint zoals elke taak van de PDFium-component: stel FileName in, zet Active := True, en controleer of het daadwerkelijk is geopend. Active := True werpt nooit fouten op — een ontbrekend bestand, een verkeerd wachtwoord of een ontbrekende DLL laat Active gewoon op False staan — dus de controle is verplicht. Zodra het document is geopend, FormType vertelt u welk formuliermodel het gebruikt. Twee van de TPdfFormType-waarden zijn XFA: ftXfaFull is een volledig, dynamisch XFA-formulier, het soort dat rendering nodig heeft, terwijl ftXfaForeground is XFAF, de voorgrond-subset waarbij XFA-inhoud over overigens normale AcroForm-pagina's wordt getekend, zodat de statische pagina's betekenisvol blijven. De boolean XFA is een kortere weg voor "dit is een XFA-document van een van beide typen". Als u ook met de velden op widgetniveau in een XFAF- of AcroForm-document werkt, worden de mechanismen behandeld in de handleiding over navigatie door formuliervelden in PDFium
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'application-form.pdf';
Pdf.Active := True;
if not Pdf.Active then
Exit; // damaged, encrypted, or DLL missing
case Pdf.FormType of
ftXfaFull:
Log('Full (dynamic) XFA form');
ftXfaForeground:
Log('XFAF: XFA layered over static AcroForm pages');
ftAcroForm:
Log('Classic AcroForm');
else
Log('No interactive form');
end;
if Pdf.XFA then
Log('Document carries an XFA packet payload');
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
XFA-pakketten extraheren als ruwe XML
Extractie is de waardevolle helft zonder afhankelijkheden. Beveilig dit met XfaStaticReadable — dit bevestigt dat de exports van de pakketlezer zijn opgelost in de geladen DLL — doorloop vervolgens de pakketten op index. GetXfaPacketCount retourneert hoeveel pakketten het formulier bevat, GetXfaPacketName geeft elk pakket zijn naam en GetXfaPacketContent retourneert de ruwe bytes, wat voor de bekende pakketten UTF-8 XML is. Wanneer u al weet welk pakket u wilt, lossen de benoemde helpers GetXfaTemplate, GetXfaDatasets en GetXfaConfig de naam voor u op en geven ze direct TBytes terug. Voor een datamigratietaak is degene die er echt toe doet GetXfaDatasets: deze geeft u de ingediende veldwaarden als een XML-document dat u kunt parsen en in uw eigen schema kunt mappen, hetzelfde soort downstreamwerk dat u zou doen na het extraheren van tekst uit een PDF
procedure ExtractXfaData(Pdf: TPdf; const Dir: string);
var
I, Count: Integer;
PacketName: string;
Content, Datasets: TBytes;
begin
if not Pdf.XfaStaticReadable then
Exit; // packet reader exports not present
Count := Pdf.GetXfaPacketCount;
for I := 0 to Count - 1 do
begin
PacketName := Pdf.GetXfaPacketName(I);
Content := Pdf.GetXfaPacketContent(I); // raw UTF-8 XML
TFile.WriteAllBytes(
TPath.Combine(Dir, PacketName + '.xml'), Content);
end;
// Or jump straight to the filled-in field values by name:
Datasets := Pdf.GetXfaDatasets;
if Length(Datasets) > 0 then
ParseAndMap(Datasets);
end;
Waarom vereist dynamische XFA-rendering de V8-engine?
Conclusie vooraf: omdat een dynamisch XFA-formulier een klein programma is, geen statische tekening. Het template-pakket bevat FormCalc- en JavaScript-berekeningen, validaties en gebeurtenisscripts, en de XFA-lay-outengine moet deze uitvoeren om te beslissen hoe de pagina's er überhaupt uitzien. PDFium implementeert die engine alleen in een build die is gecompileerd met XFA-ondersteuning bovenop V8, Google's JavaScript-engine — dezelfde reden waarom de V8-ingeschakelde DLL (pdfium32v8.dll of pdfium64v8.dll) aanzienlijk groter is dan de standaardbuild. Zonder V8 is er niemand om de scripts uit te voeren, dus zijn er geen gerenderde pagina's om te tonen, alleen de ruwe XML die u al statisch kunt lezen
Het selecteren van die build heeft een harde beperking waar u niet omheen kunt ontwerpen: het is een beslissing per proces, die eenmalig is. Een enkel proces kan niet zowel de standaard pdfium.dll als de V8-ingeschakelde build laden — ze exporteren dezelfde symbolen en V8 behoudt de globale isolaatstatus — dus de PDFium-component moet de V8-build kiezen voordat de bibliotheek voor de allereerste keer wordt geladen. Om dat automatisch te maken, LoadDocument scant het bestand vooraf op de markeringen /XFA en /NeedsRendering true (dezelfde controle die is blootgelegd als de standalone-functie PdfFileLooksXfa) en stelt EnableV8Engine := True in vóór het laden wanneer het deze vindt. Zodra er al een gewone pdfium.dll aanwezig is, kan de omschakeling niet meer plaatsvinden, en dat is wat het runtime-ontbrekende pad rapporteert
begin
// Peek for XFA before PDFium is loaded for the first time, so the
// V8-enabled build can still be selected. PdfFileLooksXfa scans for
// /XFA and /NeedsRendering true without parsing the whole file.
if PdfFileLooksXfa('dynamic-form.pdf') then
EnableV8Engine := True;
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'dynamic-form.pdf';
Pdf.Active := True;
if Pdf.Active and Pdf.XFA then
begin
if Pdf.XfaRuntimeAvailable then
RenderXfaPages(Pdf) // engine is live: dynamic layout
else
ExtractXfaData(Pdf, 'C:\out'); // fall back to static packets
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Een ontbrekende XFA-runtime elegant opvangen
Drie tests vertellen u precies hoeveel XFA-capaciteit u heeft, en betrouwbare code controleert deze in plaats van aannames te doen. XfaFeaturesAvailable is een globale controle of de XFA/V8-hulpexports aanwezig zijn in de geladen DLL. XfaStaticReadable bevestigt, per document, dat the pakketlezers zijn opgelost — de beveiliging voor extractie. XfaRuntimeAvailable is de sterke: deze is alleen true wanneer dit specifieke document daadwerkelijk de XFA-engine heeft geactiveerd, wat betekent dat het XFA is, de V8-build is geladen en FPDF_LoadXFA is geslaagd. Een document kan XFA = True rapporteren, maar toch XfaRuntimeAvailable = False wanneer de geïmplementeerde DLL geen XFA heeft of de standaardbuild al was geladen
Wanneer een XFA-document opent maar de engine niet kan draaien, gokt de PDFium-component niet. Ze zet het formulier vast in de veilige statische modus in plaats van een dynamische runtime te vragen die ze niet kan honoreren, en ze roept OnXfaRuntimeMissing eenmalig aan zodat de host kan reageren — doorgaans door de gebruiker te vertellen opnieuw op te starten met de V8-ingeschakelde build, terwijl in de tussentijd nog steeds de statisch leesbare gegevens worden aangeboden. Het koppelen van die handler is het verschil tussen een schone fallback en een formulier dat geruisloos niets toont
procedure TForm1.PdfXfaRuntimeMissing(Sender: TObject);
begin
// Fires once when an XFA document opens but the engine cannot run:
// a plain pdfium.dll was already loaded, or the build lacks XFA/V8.
ShowMessage('This is a dynamic XFA form. Restart with the ' +
'V8-enabled build to render it; its packet data is still readable.');
end;
Wees ook eerlijk over de grens in uw eigen product. Als u de V8-ingeschakelde DLL nooit meelevert, zullen dynamische XFA-formulieren nooit worden gerenderd voor uw gebruikers — maar u kunt ze nog steeds detecteren, elk pakket extraheren en de ingediende gegevens eruit halen. Dit is voldoende voor de grote klasse taken die er in feite om gaan gegevens uit legacy-overheids- en bankformulieren te halen, in plaats van ze interactief te presenteren. Reserveer de zwaardere V8-build voor de gevallen die echt een live, invulbaar formulier nodig hebben, en laat de testmogelijkheden elk document leiden naar het pad dat het daadwerkelijk kan nemen. Als uw extractiepijplijn ook ingebedde bestanden verwerkt, geldt dezelfde alleen-lezen filosofie voor PDF-bijlagen in Delphi
De FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig en de tests voor mogelijkheden die hier worden getoond, maken deel uit van de PDFium-component voor Delphi en C++Builder