Igen – a PDFium támogatja az XFA űrlapokat, egy fontos megosztással. A PDFium Component képes észlelni az XFA űrlapokat, és kinyerni azok template, datasets és config csomagjait nyers XML-ként a standard DLL használatával, nulla futásidejű függőséggel. Egy *dinamikus* XFA űrlap futtatása – XML-leírásának elrendezése élő, interaktív oldalakká – egy különálló probléma, amelyhez ráadásul a V8-kompatibilis build szükséges. A közösség folyamatosan azt kérdezi, hogy a PDFium „tudja-e az XFA-t”, mintha ez egyetlen igen-nem képesség lenne, és az őszinte válasz az, hogy az XFA adatok olvasása és az XFA űrlap renderelése két nagyon különböző dolog, két nagyon különböző függőségi lábnyommal
Ez a cikk mindkét felet végigjárja a PDFium Component segítségével, amely a PDFium natív VCL burkolója Delphi és C++Builder rendszerekhez. Először az észlelési és statikus kinyerési útvonalat – a FormType, az XFA tulajdonság és a GetXfaPacket* olvasók –, majd a dinamikus futásidejű útvonalat annak V8-as korlátjával, végül pedig azokat a képesség-szondákat (capability probes), amelyek lehetővé teszik a méltóságteljes hibakezelést, ha a telepített DLL nem tudja futtatni a motort
Támogatja a PDFium az XFA űrlapokat?
Tekintsen rá két képességként, nem pedig egyként. A statikus XFA kinyerés mindig elérhető: az alacsony szintű FPDF_GetXFAPacket* exportálások, amelyek mögött a GetXfaPacketCount, GetXfaPacketName és GetXfaPacketContent áll, minden ésszerűen modern pdfium.dll-be be vannak fordítva, még az XFA kikapcsolásával készült buildekbe is. Ezért van az, hogy a nyers template, datasets és config XML kinyerése egy űrlapból egyáltalán nem hordoz futásidejű függőséget – ez tiszta strukturális olvasás, motor bevonása nélkül. A dinamikus XFA a másik képesség: az űrlap tényleges futtatása, a számítások kiértékelése és a tördelése az XFA elrendezési motorral. Ez az útvonal csak egy olyan PDFium buildben létezik, amelyet XFA támogatással és a V8 JavaScript motorral építettek fel, és ez az a rész, amiről a legtöbb „támogatja-e a PDFium az XFA-t” témájú fórumszál valójában vitatkozik
A szabványok tisztán keretezik ezt a megosztást. Az XFA a PDF alaptantárgyán kívül van definiálva az Adobe XFA 3.3 (2012) szabványban, a PDF 1.7 §8.6.7 pedig leírja, hogyan hordozódik az XFA űrlap a PDF-en belül: az űrlapcsomagok az AcroForm szótár /XFA bejegyzése alatt élnek, és az a dokumentum, amelyet egy XFA processzornak kell elrendeznie, mielőtt bármit is jelentene, a /NeedsRendering true értéket állítja be a katalógusban. Ez a két jelölő pontosan az, amit a PDFium Component keres, és pontosan az, amire támaszkodhat, amikor egy még nem látott fájlról gondolkodik
Miben különbözik az XFA az AcroForm-tól?
Az AcroForm a klasszikus PDF-űrlap: widget-megjegyzések – szövegmezők, jelölőnégyzetek, rádiógombok –, amelyek rögzített koordinátákhoz vannak kötve a szokásos PDF-oldalakon. Az oldal, amit lát, a fájlban lévő oldal, és a mezők annak tetején ülnek. Az XFA az ellenkező megközelítést alkalmazza. Az interaktív űrlap teljes egészében XML-ben van leírva, és egy teljes (dinamikus) XFA dokumentumban a PDF oldalak gyakorlatilag helyőrzők – a valódi tartalmat megnyitáskor generálja egy XFA motor, amely beolvassa az XML sablont és elrendezi azt, növekedve vagy zsugorodva az adatokhoz igazodva. Ezt jelenti be a /NeedsRendering true: XFA processzor nélkül a statikus oldalak üresek lehetnek, vagy csak egy „kérjük, nyissa meg egy kompatibilis megjelenítőben” figyelmeztetést hordozhatnak
Maga az XFA hasznos teher (payload) elnevezett csomagok készlete, és ezek közül három hordoz szinte mindent, amire a legtöbb integrációnak szüksége van. A template csomag az űrlap definíciója – mezők, elrendezés, számítások és szkriptek. A datasets csomag a tényleges példányadatokat tartalmazza, a kitöltött mezőértékeket XML-faként. A config csomag feldolgozási utasításokat hordoz az XFA motor számára. Léteznek mások is – localeSet, connectionSet, az xdp burkoló –, de egy űrlap auditálásához vagy az adatok kinyeréséhez általában a template és a datasets elegendő. Mivel ezek az adatok csak egy streamben ülő XML-ek, olvasásuk soha nem igényel semmilyen kód végrehajtását, ami a statikus kinyerés függőségmentességének a gyökere
XFA űrlap észlelése Delphiben
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 csomagok kinyerése nyers XML-ként
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;
Miért igényel a dinamikus XFA renderelés V8 motort?
Konklúzió először: mert egy dinamikus XFA űrlap egy kis program, nem pedig statikus rajz. A template csomag FormCalc és JavaScript számításokat, ellenőrzéseket és eseményszkripteket hordoz, az XFA elrendezési motornak pedig végre kell hajtania őket, hogy eldöntse, egyáltalán hogyan nézzenek ki az oldalak. A PDFium ezt a motort csak a Google JavaScript motorja, a V8 feletti XFA támogatással lefordított buildben valósítja meg – ugyanezen okból a V8-kompatibilis build (pdfium32v8.dll vagy pdfium64v8.dll) jelentősen nagyobb, mint a standard build. V8 nélkül nincs aki futtassa a szkripteket, így nincsenek renderelt oldalak, amelyeket meg lehetne mutatni, csak a nyers XML, amelyet statikusan már el tud olvasni
Ezen build kiválasztásának van egy kemény korlátja, amelyet nem lehet megkerülni: ez egy folyamatonkénti, egyszeri döntés. Egyetlen folyamat nem töltheti be egyszerre a standard pdfium.dll-t és a V8-kompatibilis buildet – ugyanazokat a szimbólumokat exportálják, és a V8 globális izolátumállapotot (global isolate state) tart fenn –, így a PDFium Component-nek ki kell választania a V8 buildet, mielőtt a könyvtárat legelső alkalommal betöltenék. Hogy ez automatikus legyen, a LoadDocument előzetesen átvizsgálja a fájlt a /XFA és a /NeedsRendering true jelölők után (ugyanaz az ellenőrzés, amely különálló PdfFileLooksXfa függvényként is elérhető) és a betöltés előtt EnableV8Engine := True-ra állítja a konfigurációt, ha megtalálja őket. Miután egy egyszerű pdfium.dll már rezidens a memóriában, az átkapcsolás már nem léphet életbe, és a futási környezet hiányának elérési útja pontosan ezt jelenti
A hiányzó XFA futtatókörnyezet méltóságteljes kezelése
Három szonda pontosan megmondja, mekkora XFA képességgel rendelkezik, és a korrekt kód ellenőrzi őket a feltételezés helyett. Az XfaFeaturesAvailable egy globális ellenőrzés arra vonatkozóan, hogy az XFA/V8 segédexportok jelen vannak-e a betöltött DLL-ben. Az XfaStaticReadable dokumentumonként igazolja, hogy a csomagolvasók feloldódtak – ez a kinyerés védelme. Az XfaRuntimeAvailable a legerősebb: csak akkor igaz, ha ez a konkrét dokumentum valóban aktiválta az XFA motort, ami azt jelenti, hogy XFA, a V8 build betöltődött, és az FPDF_LoadXFA sikeres volt. Egy dokumentum jelenthet XFA = True értéket, miközben az XfaRuntimeAvailable = False, ha a telepített DLL-ből hiányzik az XFA, vagy a standard build már betöltődött
Amikor egy XFA dokumentum megnyílik, de a motor nem tud futni, a PDFium Component nem kockáztat. Biztonságos statikus módba rögzíti az űrlapot ahelyett, hogy olyan dinamikus futási környezetet kérne, amelyet nem tud teljesíteni, és egyszer kiváltja az OnXfaRuntimeMissing eseményt, hogy a gazdagép reagálhasson – jellemzően úgy, hogy felszólítja a felhasználót a V8-kompatibilis builddel való újraindításra, miközben időközben felajánlja a statikusan olvasható adatokat. Ezen kezelő behuzalozása a különség a tiszta visszalépési útvonal és a csendben semmit sem mutató űrlap között
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;
Legyen őszinte ezzel a határral a saját termékében is. Ha soha nem szállítja le a V8-kompatibilis DLL-t, a dinamikus XFA űrlapok soha nem fognak renderelődni a felhasználóinak – de továbbra is észlelheti őket, kinyerhet minden csomagot, és kiveheti a beküldött adatokat, ami elegendő a feladatok azon nagy osztályához, amelyek valójában a régi kormányzati és banki űrlapokból történő adatkinyerésről szólnak, nem pedig az interaktív megjelenítésükről. Tartsa fenn a nehezebb V8 buildet azokhoz az esetekhez, amelyek valóban élő, kitölthető űrlapot igényelnek, és hagyja, hogy a képesség-szondák minden dokumentumot arra az útvonalra irányítsanak, amelyet valóban képes bejárni. Ha a kinyerési csővezetéke a beágyazott fájlokat is kezeli, ugyanez az írásvédett filozófia vonatkozik a PDF mellékletekre Delphiben is
A FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig és képesség-szonda API-k a Delphihez és C++Builderhez készült PDFium Component részei