Ja — PDFium stöder XFA-formulär, med en viktig uppdelning. PDFium Component kan identifiera ett XFA-formulär och extrahera dess template, datasets och config-paket som rå XML med hjälp av standard-DLL-filen, helt utan körtidsberoenden. Att köra ett dynamiskt XFA-formulär — vilket innebär och dess XML-beskrivning läggs ut på levande, interaktiva sidor — är ett separat problem som dessutom kräver det V8-aktiverade bygget. Användare frågar ofta om PDFium "stöder XFA" som om det vore en enkel ja-eller-nej-funktion, och det ärliga svaret är att läsning av XFA-data och rendering av ett XFA-formulär är två mycket olika saker med två mycket olika beroendestruktur
Den här artikeln går igenom båda halvorna med PDFium Component, den inbyggda VCL-omslutningen runt PDFium för Delphi och C++Builder. Först går vi igenom identifiering och statisk extrahering — FormType, egenskapen XFA samt läsarna GetXfaPacket* — därefter den dynamiska körtidsvägen med dess V8-begränsning, och slutligen kapacitetskontrollerna som låter dig hantera fel snyggt när en distribuerad DLL inte kan köra motorn
Stöder PDFium XFA-formulär?
Se det som två förmågor, inte en. Statisk XFA-extrahering är alltid tillgänglig: de lågnivå-exporter FPDF_GetXFAPacket* som ligger bakom GetXfaPacketCount, GetXfaPacketName och GetXfaPacketContent finns kompilerade i varje rimligt modern pdfium.dll, även i ett bygge där XFA är inaktiverat. Det är därför extrahering av rå XML för template, datasets och config ur ett formulär inte medför något körtidsberoende alls — det är ren strukturell läsning utan att motorn är inblandad. Dynamisk XFA är den andra förmågan: att faktiskt köra formuläret, utvärdera dess beräkningar och låta XFA-layoutmotorn sidnumrera det. Den vägen finns endast i ett PDFium-bygge kompilerat med XFA-stöd ovanpå V8 JavaScript-motorn, och det är den del som de flesta trådar om "stöder PDFium XFA" egentligen handlar om
Standarderna ramar in uppdelningen snyggt. XFA definieras utanför den grundläggande PDF-grammatiken i Adobe XFA 3.3 (2012), och PDF 1.7 §8.6.7 beskriver hur ett XFA-formulär bärs inuti en PDF: formulärpaketen lever under AcroForm-ordbokens /XFA-post, och ett dokument som måste läggas ut av en XFA-processor innan det betyder något anger /NeedsRendering true i katalogen. Dessa två markörer är exakt vad PDFium Component letar efter, och exakt vad du kan lita på när du analyserar en fil du aldrig har sett tidigare
Hur skiljer sig XFA från ett AcroForm?
Ett AcroForm är det klassiska PDF-formuläret: widget-anteckningar — textfält, kryssrutor, radiogrupper — förankrade vid fasta koordinater på vanliga PDF-sidor. Sidan du ser är sidan i filen, och fälten sitter ovanpå den. XFA tar motsatt väg. Det interaktiva formuläret beskrivs helt i XML, och i ett fullständigt (dynamiskt) XFA-dokument är PDF-sidorna i praktiken en platshållare — det verkliga innehållet genereras när filen öppnas av en XFA-motor som läser XML-mallen och lägger ut den, varvid den växer eller krymper för att passa datamängden. Det är vad /NeedsRendering true meddelar: utan en XFA-processor kan de statiska sidorna vara tomma eller bara innehålla ett meddelande om att "öppna i en kompatibel visare"
Själva XFA-innehållet är en uppsättning namngivna paket, och tre av dem bär på allt som de flesta integrationer behöver. Paketet template är formulärdefinitionen — fält, layout, beräkningar och skript. Paketet datasets innehåller de faktiska instansdata, de ifyllda fältvärdena som ett XML-träd. Paketet config innehåller bearbetningsinstruktioner för XFA-motorn. Det finns andra — localeSet, connectionSet, xdp-omslutningen — men för att granska ett formulär eller migrera data från det är template plus datasets vanligtvis hela jobbet. Eftersom dessa data bara är XML som ligger i en ström kräver läsning av dem aldrig att något körs, vilket är den grundläggande anledningen till att statisk extrahering är fri från beroenden
Identifiera ett XFA-formulär i Delphi
Identifieringen börjar på samma sätt som alla uppgifter i PDFium Component: ställ in FileName, slå om till Active := True och kontrollera att filen faktiskt öppnades. Active := True utlöser aldrig fel — en saknad fil, felaktigt lösenord eller saknad DLL lämnar bara Active som False — så kontrollen är obligatorisk. När dokumentet är öppet berättar FormType vilken formulärmodell som används. Två av TPdfFormType-värdena är XFA: ftXfaFull är ett fullständigt, dynamiskt XFA-formulär, den typ som kräver rendering, medan ftXfaForeground is XFAF, förgrunds-delmängden där XFA-innehåll ritas över i övrigt normala AcroForm-sidor så att de statiska sidorna förblir meningsfulla. Den booleska XFA är en genväg för "detta är ett XFA-dokument av någon av typerna". Om du också arbetar med fält på widgetnivå i ett XFAF- eller AcroForm-dokument täcks mekaniken i guiden till navigering av PDFium-formulärfält
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;
Extrahera XFA-paket som rå XML
Extraheringen är den värdefulla halvan med noll beroenden. Skydda med XfaStaticReadable — det bekräftar att paketläsar-exporterna löstes i den laddade DLL-filen — och gå sedan igenom paketen efter index. GetXfaPacketCount returnerar hur många paket formuläret innehåller, GetXfaPacketName ger varje paket dess namn och GetXfaPacketContent returnerar råa byte, vilket för de välkända paketen är UTF-8 XML. När du redan vet vilket paket du vill ha löser de namngivna hjälparna GetXfaTemplate, GetXfaDatasets och GetXfaConfig namnet åt dig och lämnar tillbaka TBytes direkt. För ett datamigreringsjobb är det GetXfaDatasets som spelar roll: det ger dig de inskickade fältvärdena som ett XML-dokument som du kan tolka och mappa till ditt eget schema, samma typ av efterföljande arbete som du skulle göra efter att ha extraherat text från en 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;
Varför kräver dynamisk XFA-rendering V8-motorn?
Slutsatsen först: eftersom ett dynamiskt XFA-formulär är ett litet program, inte en statisk ritning. Mallpaketet innehåller FormCalc- och JavaScript-beräkningar, valideringar och händelseskript, och XFA-layoutmotorn måste köra dem för att avgöra hur sidorna överhuvbtaget ska se ut. PDFium implementerar den motorn endast i ett bygge kompilerat med XFA-stöd ovanpå V8, Googles JavaScript-motor — samma anledning till att den V8-aktiverade DLL-filen (pdfium32v8.dll eller pdfium64v8.dll) är markant större än standardbygget. Utan V8 finns det ingen som kör skripten, så det finns inga renderade sidor att visa, bara de råa XML-data som du redan kan läsa statiskt
Att välja det bygget hat en hård begränsning som du inte kan designa runt: det är ett beslut per process som tas en enda gång. En process kan inte ladda både standard-pdfium.dll och det V8-aktiverade bygget — de exporterar samma symboler och V8 har ett globalt isolattillstånd — så PDFium Component måste välja V8-bygget innan biblioteket laddas för allra första gången. För att göra det automatiskt förhandsskannar LoadDocument filen efter markörerna /XFA och /NeedsRendering true (samma kontroll som exponeras via den fristående funktionen PdfFileLooksXfa) och ställer in EnableV8Engine := True före laddningen när den hittar dem. När en vanlig pdfium.dll väl har laddats i minnet kan växlingen inte längre träda i kraft, och det fallet är vad den saknade körtidsvägen rapporterar
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;
Hantera en saknad XFA-körtidsmiljö snyggt
Tre kontroller talar om exakt hur mycket XFA-kapacitet du har, och ärlig kod kontrollerar dem istället för att anta saker. XfaFeaturesAvailable is en global kontroll av att XFA/V8-hjälpexporterna finns i den laddade DLL-filen. XfaStaticReadable bekräftar, per dokument, att paketläsarna löstes — skyddet för extrahering. XfaRuntimeAvailable är den starka kontrollen: den är sann endast när detta specifika dokument faktiskt aktiverade XFA-motorn, vilket innebär att det är XFA, att V8-bygget laddades och att FPDF_LoadXFA lyckades. Ett dokument kan rapportera XFA = True men ändå XfaRuntimeAvailable = False närhelst den distribuerade DLL-filen saknar XFA eller standardbygget redan var laddat
När ett XFA-dokument öppnas men motorn inte kan köras chanserar inte PDFium Component. Den låser formuläret till det säkra statiska läget snarare än att begära en dynamisk körtid som den inte kan uppfylla, och den utlöser OnXfaRuntimeMissing en gång så att värden kan reagera — vanligtvis genom att tala om för användaren att starta om med det V8-aktiverade bygget, samtidigt som den fortfarande erbjuder de statiskt läsbara data under tiden. Att koppla den hanteraren är skillnaden mellan ett snyggt reservläge och ett formulär som tyst inte visar någonting
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;
Var ärlig med gränsen även i din egen produkt. Om du aldrig levererar den V8-aktiverade DLL-filen kommer dynamiska XFA-formulär aldrig att renderas för dina användare — men du kan fortfarande upptäcka dem, extrahera varje paket och hämta ut de inskickade data, vilket är tillräckligt för den stora klass av jobb som egentligen handlar om att hämta data ur äldre myndighets- och bankformulär snarare än att presentera dem interaktivt. Spara det tyngre V8-bygget för de fall som verkligen behöver ett levande, ifyllbart formulär, och låt kapacitetskontrollerna styra varje dokument till den väg det faktiskt kan ta. Om din extraheringspipeline även hanterar inbäddade filer gäller samma skrivskyddade filosofi för PDF-bilagor i Delphi
API:erna för FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig och kapacitetskontroller som visas här är en del av PDFium Component för Delphi och C++Builder