Ano — PDFium podporuje formuláře XFA, a to s jedním důležitým rozdělením. Komponenta PDFium dokáže detekovat formulář XFA a extrahovat jeho balíčky `template`, `datasets` a `config` jako čisté XML pomocí standardní DLL, tedy se zcela nulovou závislostí na běhovém prostředí. Běh dynamického formuláře XFA — vykreslení jeho XML popisu do živých, interaktivních stránek — je samostatný problém, který navíc vyžaduje sestavení s povoleným V8. Komunita se neustále ptá, zda PDFium „umí XFA“, jako by šlo o jedinou schopnost typu ano/ne, a upřímná odpověď zní, že čtení dat XFA a vykreslování formuláře XFA jsou dvě velmi odlišné věci s velmi odlišnými nároky na závislosti
Tento článek popisuje obě poloviny s komponentou PDFium Component, což je nativní obal VCL kolem PDFium pro Delphi a C++Builder. Nejprve probereme cestu detekce a statické extrakce — `FormType`, vlastnost `XFA` a čtečky `GetXfaPacket*` —, poté dynamickou běhovou cestu s jejím omezením na V8 a nakonec testy schopností, které vám umožní elegantně selhat, když nasazená DLL nedokáže spustit příslušný engine
Podporuje PDFium formuláře XFA?
Vnímejte to jako dvě schopnosti, nikoli jako jednu. Statická extrakce XFA je k dispozici vždy: nízkoúrovňové exporty `FPDF_GetXFAPacket*`, které stojí za metodami `GetXfaPacketCount`, `GetXfaPacketName` a `GetXfaPacketContent`, jsou zkompilovány do každé rozumně moderní knihovny pdfium.dll, a to i do sestavení s vypnutým XFA. Proto vytažení čistého XML šablon `template`, `datasets` a `config` z formuláře nevyžaduje vůbec žádnou závislost za běhu — jedná se o čistě strukturální čtení, do kterého není zapojen žádný engine. Dynamické XFA je druhá schopnost: skutečné spuštění formuláře, vyhodnocení jeho výpočtů a umožnění engine rozvržení XFA jej stránkovat. Tato cesta existuje pouze v sestavení PDFium zkompilovaném s podporou XFA nad JavaScript enginem V8, a to je ta část, o které se většina vláken typu „podporuje PDFium XFA“ ve skutečnosti přou
Standardy toto rozdělení definují jasně. XFA je definováno mimo základní gramatiku PDF v Adobe XFA 3.3 (2012) a specifikace PDF 1.7 §8.6.7 popisuje, jak se formulář XFA přenáší uvnitř PDF: balíčky formuláře žijí pod položkou `/XFA` ve slovníku AcroForm a dokument, který musí být rozvržen procesorem XFA předtím, než začne cokoli znamenat, nastavuje `/NeedsRendering true` v katalogu. Tyto dva identifikátory jsou přesně tím, co komponenta PDFium vyhledává, a přesně tím, na co se můžete spolehnout, když uvažujete o souboru, který jste nikdy předtím neviděli
V čem se XFA liší od AcroForm?
AcroForm je klasický formulář PDF: ovládací prvky (widget annotations) — textová pole, zaškrtávací políčka, přepínače — ukotvené na pevných souřadnicích na běžných stránkách PDF. Stránka, kterou vidíte, je stránka v souboru a pole leží na ní. XFA k tomu přistupuje opačně. Interaktivní formulář je kompletně popsán v XML a v plnohodnotném (dynamickém) dokumentu XFA jsou stránky PDF v podstatě zástupným symbolem — skutečný obsah se generuje při otevření engine XFA, který načte XML šablonu a rozvrhne ji, přičemž se rozšiřuje nebo smršťuje podle dat. To je to, co oznamuje `/NeedsRendering true`: bez procesoru XFA mohou být statické stránky prázdné nebo nést pouze upozornění „otevřete prosím v kompatibilním prohlížeči“
Samotný obsah XFA je sada pojmenovaných balíčků a tři z nich nesou vše, co většina integrací potřebuje. Balíček `template` je definice formuláře — pole, rozvržení, výpočty a skripty. Balíček `datasets` obsahuje skutečná data instance, tedy vyplněné hodnoty polí jako strom XML. Balíček `config` nese pokyny pro zpracování pro engine XFA. Existují i další — localeSet, connectionSet, obal xdp —, ale pro audit formuláře nebo migraci dat z něj je šablona plus datové sady obvykle celou prací. Protože tato data jsou pouze XML ležící v proudu, jejich čtení nikdy nevyžaduje spuštění ničeho, což je hlavní důvod, proč je statická extrakce bez závislostí
Detekce formuláře XFA v Delphi
Detekce začíná tak, jak začíná každý úkol s komponentou PDFium Component: nastavte `FileName`, přepněte `Active := True` a zkontrolujte, zda se skutečně otevřel. `Active := True` nikdy nevyvolá výjimku — chybějící soubor, špatné heslo nebo chybějící DLL pouze ponechá `Active` na hodnotě `False` —, takže kontrola je povinná. Jakmile je dokument otevřen, `FormType` vám sdělí, který model formuláře používá. Dvě z hodnot `TPdfFormType` jsou XFA: `ftXfaFull` je plnohodnotný, dynamický formulář XFA, tedy ten, který vyžaduje vykreslení, zatímco `ftXfaForeground` je XFAF, tedy popředí, kde je obsah XFA vykreslen nad jinak normálními stránkami AcroForm, takže statické stránky zůstávají smysluplné. Logická hodnota `XFA` je zkratkou pro „jedná se o dokument XFA jakéhokoli typu“. Pokud pracujete také s poli na úrovni ovládacích prvků v dokumentu XFAF nebo AcroForm, mechanika je popsána v příručce pro navigaci v polích formuláře PDFium
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'application-form.pdf';
Pdf.Active := True;
if not Pdf.Active then
Exit; // poškozeno, šifrováno nebo chybí DLL
case Pdf.FormType of
ftXfaFull:
Log('Plný (dynamický) XFA formulář');
ftXfaForeground:
Log('XFAF: XFA navrstvené nad statickými stránkami AcroForm');
ftAcroForm:
Log('Klasický AcroForm');
else
Log('Žádný interaktivní formulář');
end;
if Pdf.XFA then
Log('Dokument nese obsah balíčku XFA');
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Extrakce balíčků XFA jako čistého XML
Extrakce je ta hodnotnější polovina bez závislostí. Opatřete ji kontrolou `XfaStaticReadable` — ta potvrzuje, že se v načtené DLL vyřešily exporty čtečky balíčků —, a poté procházejte balíčky podle indexu. `GetXfaPacketCount` vrací, kolik balíčků formulář nese, `GetXfaPacketName` dává každému z nich jeho název a `GetXfaPacketContent` vrací nezpracované bajty, které jsou u známých balíčků XML v kódování UTF-8. Pokud již víte, který balíček chcete, pojmenovaní pomocníci `GetXfaTemplate`, `GetXfaDatasets` a `GetXfaConfig` vyřeší název za vás a předají přímo `TBytes`. U úlohy migrace dat je tím, na čem záleží, `GetXfaDatasets`: dává vám vyplněné hodnoty polí jako XML dokument, který můžete analyzovat a mapovat do vlastního schématu, což je stejný typ následné práce, jakou byste dělali po extrahování textu z PDF
procedure ExtractXfaData(Pdf: TPdf; const Dir: string);
var
I, Count: Integer;
PacketName: string;
Content, Datasets: TBytes;
begin
if not Pdf.XfaStaticReadable then
Exit; // exporty čtečky balíčků nejsou přítomny
Count := Pdf.GetXfaPacketCount;
for I := 0 to Count - 1 do
begin
PacketName := Pdf.GetXfaPacketName(I);
Content := Pdf.GetXfaPacketContent(I); // nezpracované UTF-8 XML
TFile.WriteAllBytes(
TPath.Combine(Dir, PacketName + '.xml'), Content);
end;
// Nebo přejít přímo k vyplněným hodnotám polí podle názvu:
Datasets := Pdf.GetXfaDatasets;
if Length(Datasets) > 0 then
ParseAndMap(Datasets);
end;
Proč dynamické vykreslování XFA vyžaduje engine V8?
Závěr nejprve: protože dynamický formulář XFA je malý program, nikoli statický výkres. Balíček šablon obsahuje výpočty FormCalc a JavaScript, validace a skripty událostí a engine rozvržení XFA je musí spustit, aby vůbec rozhodl, jak stránky vypadají. PDFium implementuje tento engine pouze v sestavení zkompilovaném s podporou XFA nad V8, JavaScript engine Google — což je stejný důvod, proč je DLL s povoleným V8 (pdfium32v8.dll nebo pdfium64v8.dll) výrazně větší než standardní sestavení. Bez V8 nemá skripty kdo spustit, takže neexistují žádné vykreslené stránky k zobrazení, pouze nezpracované XML, které si již můžete přečíst staticky
Výběr tohoto sestavení má pevné omezení, které nelze obejít: je to jednorázové rozhodnutí pro každý proces. Jediný proces nemůže načíst standardní pdfium.dll i sestavení s podporou V8 — exportují stejné symboly a V8 udržuje globální stav izolátu —, takže komponenta PDFium Component musí vybrat sestavení V8 dříve, než se knihovna načte úplně poprvé. Aby to bylo automatické, `LoadDocument` předem prohledá soubor na přítomnost identifikátorů `/XFA` a `/NeedsRendering true` (stejná kontrola vystavená jako samostatná funkce `PdfFileLooksXfa`) a nastaví `EnableV8Engine := True` před načtením, když je najde. Jakmile je již v paměti přítomna obyčejná pdfium.dll, přepnutí již nemůže mít účinek, a to je případ, který hlásí chybějící cesta za běhu
begin
// Nahlédnout na XFA před prvním načtením PDFium, aby bylo možné
// vybrat sestavení s V8. PdfFileLooksXfa vyhledává
// /XFA a /NeedsRendering true bez analýzy celého souboru.
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 běží: dynamické rozvržení
else
ExtractXfaData(Pdf, 'C:\out'); // vrátit se ke statickým balíčkům
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Elegantní zpracování chybějícího běhového prostředí XFA
Tři testy vám přesně řeknou, jakou kapacitu XFA máte, a poctivý kód je kontroluje, místo aby předpokládal. `XfaFeaturesAvailable` je globální kontrola, zda jsou v načtené DLL přítomny exporty pomocníka XFA/V8. `XfaStaticReadable` potvrzuje pro každý dokument, že se vyřešily čtečky balíčků — tedy ochrana pro extrakci. `XfaRuntimeAvailable` je ten nejsilnější: je true pouze tehdy, když tento konkrétní dokument skutečně aktivoval engine XFA, což znamená, že se jedná o XFA, načetlo se sestavení s V8 a volání `FPDF_LoadXFA` uspělo. Dokument může hlásit `XFA = True` a přitom `XfaRuntimeAvailable = False` pokaždé, když nasazená DLL postrádá XFA nebo když již byla načtena standardní knihovna
Když se dokument XFA otevře, ale engine nemůže běžet, komponenta PDFium neriskuje. Připne formulář k bezpečnému statickému režimu, místo aby požadovala dynamický runtime, který nemůže splnit, a jednou vyvolá událost `OnXfaRuntimeMissing`, takže hostitel může reagovat — obvykle tím, že uživateli řekne, aby aplikaci restartoval se sestavením s povoleným V8, a mezitím mu nabídne staticky čitelná data. Zapojení této obsluhy představuje rozdíl mezi čistým fallbackem a formulářem, který tichým způsobem nic nezobrazí
procedure TForm1.PdfXfaRuntimeMissing(Sender: TObject);
begin
// Spustí se jednou, když se dokument XFA otevře, ale engine nemůže běžet:
// obyčejná pdfium.dll již byla načtena, nebo sestavení postrádá XFA/V8.
ShowMessage('Jedná se o dynamický formulář XFA. Restartujte aplikaci se sestavením s povoleným V8 pro jeho vykreslení; data jeho balíčků jsou stále čitelná.');
end;
Buďte upřímní ohledně této hranice i ve svém vlastním produktu. Pokud nikdy neexpedujete DLL s povoleným V8, dynamické formuláře XFA se vašim uživatelům nikdy nevykreslí — ale stále je můžete detekovat, extrahovat každý balíček a vytáhnout odeslaná data, což stačí pro velkou část úloh, které jsou ve skutečnosti o získávání dat ze starších vládních a bankovních formulářů, a nikoli o jejich interaktivní prezentaci. Těžší sestavení s V8 si rezervujte pro případy, které skutečně potřebují živý, vyplnitelný formulář, a nechte testy schopností nasměrovat každý dokument na cestu, kterou se může skutečně vydat. Pokud váš pipeline pro extrakci zpracovává také vložené soubory, stejná filozofie pouze pro čtení platí pro přílohy PDF v Delphi
Rozhraní API `FormType`, `XFA`, `GetXfaPacketCount`, `GetXfaPacketName`, `GetXfaPacketContent`, `GetXfaTemplate`, `GetXfaDatasets`, `GetXfaConfig` a testy schopností zde ukázané jsou součástí komponenty PDFium Component pro Delphi a C++Builder