Articol tehnic

Detectarea și extragerea formularelor XFA în Delphi cu PDFium

Da — PDFium acceptă formulare XFA, dar există o delimitare importantă. Componenta PDFium poate detecta un formular XFA și poate extrage pachetele sale template, datasets și config sub formă de XML brut folosind DLL-ul standard, cu zero dependențe la rulare. Rularea unui formular XFA dinamic — aranjarea descrierii sale XML în pagini active și interactive — reprezintă o problemă separată care necesită suplimentar o versiune cu suport V8. Membrii comunității întreabă frecvent dacă PDFium „suportă XFA” ca și cum ar fi o singură funcționalitate de tip da-sau-nu, dar răspunsul corect este că citirea datelor XFA și randarea unui formular XFA sunt două lucruri complet diferite, cu seturi de dependențe distincte

Acest articol analizează ambele aspecte folosind componenta PDFium, legătura nativă VCL în jurul PDFium pentru Delphi și C++Builder. Mai întâi, calea de detectare și extragere statică — FormType, proprietatea XFA și cititoarele GetXfaPacket* —, apoi calea dinamică de execuție cu limitarea sa V8 și, în final, testele de compatibilitate care vă permit să gestionați erorile când un DLL utilizat nu poate rula motorul

Acceptă PDFium formulare XFA?

Trebuie privit ca două funcționalități separate, nu una singură. Extragerea statică XFA este întotdeauna disponibilă: exporturile de nivel scăzut FPDF_GetXFAPacket* care susțin GetXfaPacketCount, GetXfaPacketName și GetXfaPacketContent are compiled into every reasonably modern pdfium.dll, even a build with XFA switched off. De aceea, extragerea XML-ului brut din pachetele template, datasets și config dintr-un formular nu aduce nicio dependență la rulare — este o simplă citire structurală, fără implicarea motorului. XFA dinamic este cea de-a doua funcționalitate: rularea propriu-zisă a formularului, evaluarea calculelor și permiterea motorului de layout XFA să îl pagineze. Această cale este disponibilă doar într-o versiune de PDFium compilată cu suport XFA peste motorul JavaScript V8, aceasta fiind zona pe care se concentrează majoritatea discuțiilor despre suportul XFA

Standardele definesc clar această separare. XFA este definit în afara regulilor de bază ale PDF-ului în Adobe XFA 3.3 (2012), iar PDF 1.7 §8.6.7 descrie modul în care un formular XFA este inclus într-un PDF: pachetele formularului se află sub intrarea /XFA din dicționarul AcroForm, iar un document care trebuie structurat de un procesor XFA înainte de a putea fi utilizat setează /NeedsRendering true în catalog. Acești doi indicatori sunt exact elementele pe care le caută componenta PDFium și pe care vă puteți baza când analizați un fișier

Prin ce diferă XFA de un AcroForm?

Un AcroForm este formularul clasic PDF: adnotări de tip widget — câmpuri de text, casete de selectare, grupuri de opțiuni (radio buttons) — ancorate la coordonate fixe pe pagini PDF obișnuite. Pagina pe care o vedeți este pagina reală din fișier, iar câmpurile sunt plasate deasupra acesteia. XFA abordează lucrurile invers. Formularul interactiv este descris în întregime în XML, iar într-un document XFA complet (dinamic), paginile PDF sunt doar un substituent — conținutul real este generat la deschidere de un motor XFA care citește șablonul XML și îl aranjează în pagină, mărindu-l sau micșorându-l pentru a se potrivi cu datele. Asta anunță /NeedsRendering true: fără un procesor XFA, paginile statice pot fi goale sau pot afișa doar un mesaj de tipul „vă rugăm să deschideți într-un vizualizator compatibil”

Datele XFA reprezintă un set de pachete numite, iar trei dintre acestea conțin tot ce este necesar pentru majoritatea integrărilor. Pachetul template conține definiția formularului — câmpuri, așezare în pagină, calcule și scripturi. Pachetul datasets stochează datele reale ale instanței, adică valorile completate ale câmpurilor sub formă de arbore XML. Pachetul config transportă instrucțiunile de procesare pentru motorul XFA. Există și altele — localeSet, connectionSet, containerul xdp —, dar pentru verificarea unui formular sau extragerea datelor, șablonul plus seturile de date reprezintă întreaga activitate. De deoarece datele respective sunt doar XML stocate într-un flux, citirea lor nu necesită execuție de cod, acesta fiind motivul principal pentru care extragerea statică este lipsită de dependențe

Detectarea unui formular XFA în Delphi

Detectarea începe în modul obișnuit pentru orice sarcină a componentei PDFium: setați FileName, comutați Active := True și verificați dacă s-a deschis. Linia Active := True nu generează excepții — lipsa unui fișier, o parolă greșită sau un DLL absent lasă proprietatea Active pe False —, așa că această verificare este obligatorie. Odată ce documentul este deschis, FormType indică ce model de formular utilizează. Două dintre valorile TPdfFormType sunt specifice XFA: ftXfaFull reprezintă un formular XFA dinamic complet, cel care necesită randare, în timp ce ftXfaForeground reprezintă XFAF, subsetul în care conținutul XFA este desenat peste pagini AcroForm normale, astfel încât paginile statice să rămână vizibile. Valoarea booleană XFA este o scurtătură pentru „acesta este un document XFA de orice tip”. Dacă lucrați și cu câmpuri la nivel de widget pe un document XFAF sau AcroForm, mecanismele sunt descrise în ghidul pentru navigarea în câmpurile de formular 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;

Extragerea pachetelor XFA ca XML brut

Extragerea este partea valoroasă și lipsită de dependențe. Verificați compatibilitatea cu XfaStaticReadable — confirmă că exporturile cititorului de pachete sunt disponibile în DLL-ul încărcat —, apoi parcurgeți pachetele după index. GetXfaPacketCount returnează numărul de pachete conținute în formular, GetXfaPacketName oferă numele fiecăruia, iar GetXfaPacketContent returnează octeții bruți, care pentru pachetele cunoscute sunt XML în format UTF-8. Când știți deja ce pachet doriți, metodele dedicate GetXfaTemplate, GetXfaDatasets și GetXfaConfig identifică numele și returnează direct structura TBytes. Pentru procesele de migrare a datelor, cel care contează este GetXfaDatasets: vă oferă valorile trimise ale câmpurilor sub formă de document XML pe care îl puteți analiza și mapa în propria schemă, activitate similară cu cea efectuată după extragerea textului dintr-un 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;

De ce are nevoie randarea dinamică XFA de motorul V8?

Concluzia mai întâi: deoarece un formular XFA dinamic este de fapt un program de mici dimensiuni, nu un desen static. Pachetul template conține calcule în FormCalc și JavaScript, validări și scripturi de evenimente, iar motorul de layout XFA trebuie să le execute pentru a determina aspectul paginilor. PDFium integrează acest motor doar într-o versiune compilată cu suport XFA peste V8, motorul JavaScript al Google — acesta fiind motivul pentru care DLL-ul cu suport V8 (pdfium32v8.dll sau pdfium64v8.dll) este semnificativ mai mare decât versiunea standard. Fără V8, scripturile nu pot fi executate, deci nu vor fi generate pagini randate, fiind disponibil doar XML-ul brut pe care îl puteți citi static

Selectarea acelei versiuni vine cu o limitare strictă: este o decizie unică, realizată la nivel de proces. Un proces unic nu poate încărca simultan fișierul pdfium.dll standard și versiunea cu suport V8 — acestea exportă aceleași simboluri, iar V8 menține o stare globală izolată —, așa că componenta PDFium trebuie să aleagă binarul V8 înainte ca biblioteca să fie încărcată pentru prima dată. Pentru automatizarea acestui proces, LoadDocument verifică fișierul în prealabil căutând indicatorii /XFA și /NeedsRendering true (aceeași verificare fiind expusă ca funcția independentă PdfFileLooksXfa) și activează EnableV8Engine := True înainte de încărcare, dacă îi găsește. Odată ce un pdfium.dll simplu este deja încărcat, comutarea nu mai poate avea loc, acesta fiind cazul raportat la lipsa runtime-ului

Gestionarea corespunzătoare a lipsei runtime-ului XFA

Trei teste de verificare vă indică exact ce capabilități XFA aveți la dispoziție, iar codul corect le verifică în loc să facă presupuneri. XfaFeaturesAvailable este o verificare globală care atestă că exporturile ajutătoare XFA/V8 sunt prezente în DLL-ul încărcat. XfaStaticReadable confirmă, per document, că cititoarele de pachete sunt pregătite — verificarea necesară pentru extragere. XfaRuntimeAvailable este testul definitoriu: este adevărat doar când acest document specific a activat efectiv motorul XFA, adică este un document XFA, s-a încărcat versiunea V8 și apelul FPDF_LoadXFA a reușit. Un document poate raporta XFA = True, dar XfaRuntimeAvailable = False când DLL-ul utilizat nu are suport XFA sau când versiunea standard a fost deja încărcată

Când un document XFA se deschide, dar motorul nu poate rula, componenta PDFium nu își asumă riscuri. Blocează formularul în modul static sigur în loc să solicite un runtime dinamic pe care nu îl poate susține și generează o singură dată evenimentul OnXfaRuntimeMissing pentru ca gazda să poată reacționa — de obicei informând utilizatorul să repornească aplicația cu versiunea ce conține suport V8, oferind între timp datele ce pot fi citite static. Configurarea acestui handler face diferența între o soluție de rezervă corectă și un formular care nu afișează nimic în mod silențios

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;

Fii realist cu privire la aceste limite și în propriul produs. Dacă nu livrați niciodată DLL-ul cu suport V8, formularele XFA dinamice nu se vor randa pentru utilizatori — dar le puteți detecta în continuare, puteți extrage fiecare pachet și puteți prelua datele completate, ceea ce este suficient pentru o mare parte din activitățile care implică preluarea datelor din formulare guvernamentale sau bancare învechite, fără a le prezenta interactiv. Rezervați versiunea mai mare cu V8 pentru cazurile care au cu adevărat nevoie de un formular activ ce poate fi completat și lăsați testele de compatibilitate să direcționeze fiecare document pe calea pe care o poate parcurge. Dacă fluxul dvs. de extragere gestionează și fișiere atașate, aceeași abordare de tip read-only se aplică pentru atașamentele PDF în Delphi

Metodele FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig și API-urile de verificare a compatibilității prezentate aici fac parte din pachetul PDFium Component pentru Delphi și C++Builder