Techninis straipsnis

XFA formų aptikimas ir išgavimas Delphi aplinkoje naudojant PDFium

Taip — PDFium palaiko XFA formas, turėdama vieną svarbų pasidalijimą. PDFium komponentas gali aptikti XFA formą bei išgauti jos template, datasets bei config paketus kaip neapdorotą XML naudodamas standartinę DLL, be jokių vykdymo laiko priklausomybių. Vykdyti dinaminę XFA formą — išdėstant jos XML aprašymą į gyvus, interaktyvius puslapius — yra atskira problema, kuriai papildomai reikalinga V8 palaikanti bibliotekos versija. Bendruomenė nuolat klausia, ar PDFium „palaiko XFA“, tarsi tai būtų viena „taip arba ne“ galimybė, ir sąžiningas atsakymas yra tas, kad XFA duomenų nuskaitymas bei XFA formos atvaizdavimas yra du labai skirtingi dalykai su labai skirtingais priklausomybių pėdsakais

Šiame straipsnyje apžvelgiamos abi dalys naudojant PDFium komponentą — vietinį VCL susiejimą aplink PDFium, skirtą Delphi bei C++Builder. Pirmiausia aptikimo bei statinio išgavimo kelias — FormType, XFA savybė bei GetXfaPacket* skaitytuvai —, tada dinaminio vykdymo laiko kelias su jo V8 apribojimu, ir galiausiai galimybių patikros, kurios leidžia grakščiai susitvarkyti su nesėkme, kai įdiegta DLL negali paleisti variklio

Ar PDFium palaiko XFA formas?

Vertinkite tai kaip dvi galimybes, o ne vieną. Statinis XFA išgavimas yra prieinamas visada: žemo lygio FPDF_GetXFAPacket* eksportai, palaikantys GetXfaPacketCount, GetXfaPacketName bei GetXfaPacketContent funkcijas, yra sukompiliuoti kiekvienoje šiuolaikinėje pdfium.dll versijoje, net ir toje, kurioje XFA yra išjungta. Būtent todėl neapdorotų template, datasets bei config XML duomenų ištraukimas iš formos neturi jokių vykdymo laiko priklausomybių — tai grynas struktūrinis nuskaitymas, nedalyvaujant jokiam varikliui. Dinaminė XFA yra kita galimybė: tikras formos vykdymas, jos skaičiavimų įvertinimas bei leidimas XFA išdėstymo varikliui ją sunumeruoti. Šis kelias egzistuoja tik PDFium versijoje, sukompiliuotoje su XFA palaikymu virš V8 JavaScript variklio, ir tai yra būtent ta dalis, dėl kurios dažniausiai ginčijamasi forumuose

Standartai šį pasidalijimą apibrėžia aiškiai. XFA apibrėžta už pagrindinės PDF gramatikos ribų Adobe XFA 3.3 (2012) specifikacijoje, o PDF 1.7 §8.6.7 aprašo, kaip XFA forma pernešama PDF viduje: formos paketai randasi po AcroForm žodyno /XFA įrašu, o dokumentas, kurį prieš naudojimą turi išdėstyti XFA procesorius, kataloge nustato /NeedsRendering true. Šie du žymekliai yra būtent tai, ko ieško PDFium komponentas, bei tai, kuo galite pasikliauti vertindami failą

Kuo XFA skiriasi nuo AcroForm?

AcroForm yra klasikinė PDF forma: valdymo elementų anotacijos (valdikliai) — tekstiniai laukai, žymimieji langeliai, radijo grupės —, prikabintos fiksuotose koordinatėse įprastuose PDF puslapiuose. Puslapis, kurį matote, yra faile esantis puslapis, o laukai randasi ant jo. XFA naudoja priešingą metodą. Interaktyvi forma yra visiškai aprašyta XML, o pilname (dinaminiame) XFA dokumente PDF puslapiai iš tikrųjų yra tik vietos rezervuaras — tikrasis turinys generuojamas atidarymo metu XFA variklio, kuris nuskaito XML šabloną bei jį išdėsto, padidėdamas arba sumažėdamas, kad tilptų duomenys. Štai ką praneša /NeedsRendering true: be XFA procesoriaus statiniai puslapiai gali būti tušti arba turėti tik užrašą „atidarykite suderinamoje skaityklėje“

Pats XFA duomenų paketas yra pavadintų paketų rinkinys, ir trys iš jų perneša viską, ko reikia daugumai integracijų. template paketas yra formos apibrėžimas — laukai, išdėstymas, skaičiavimai bei scenarijai. datasets paketas saugo tikruosius duomenis, užpildytas laukų reikšmes kaip XML medį. config paketas perneša apdorojimo instrukcijas, skirtas XFA varikliui. Yra ir kitų — localeSet, connectionSet, xdp apvalkalas —, tačiau formos auditui ar duomenų perkėlimui template bei datasets paprastai yra visas darbas. Kadangi šie duomenys yra tik XML sraute, jų nuskaitymui niekada nereikia nieko vykdyti; tai ir yra pagrindinė priežastis, kodėl statinis išgavimas neturi jokių priklausomybių

XFA formos aptikimas Delphi aplinkoje

Aptikimas prasideda taip, kaip ir kiekviena PDFium komponento užduotis: nustatykite FileName, perjunkite Active := True bei patikrinkite, ar jis tikrai atsidarė. Active := True niekada nesukelia klaidų — trūkstamas failas, neteisingas slaptažodis ar nesanti DLL tiesiog palieka Active reikšmę False —, todėl ši patikra yra privaloma. Atidarius dokumentą, FormType praneša, kurį formos modelį jis naudoja. Dvi iš TPdfFormType reikšmių yra XFA: ftXfaFull yra pilna, dinaminė XFA forma, kuriai reikalingas atvaizdavimas, o ftXfaForeground is XFAF — priekinio plano poaibis, kur XFA turinys nupiešiamas ant įprastų AcroForm puslapių, kad statiniai puslapiai išlaikytų prasmę. Loginė reikšmė XFA yra trumpinys, reiškiantis „tai yra bet kurio tipo XFA dokumentas“. Jei taip pat dirbate su valdiklių lygio laukais XFAF ar AcroForm dokumentuose, mechanika aprašyta straipsnyje PDFium formos laukų naršymo vadovas

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 paketų išgavimas kaip neapdoroto XML

Išgavimas yra vertingoji pusė, neturinti jokių priklausomybių. Apsaugokite patikrinimu XfaStaticReadable — jis patvirtina, kad paketo skaitytuvo eksportai yra išspręsti įkeltoje DLL —, tada pereikite paketus pagal indeksą. GetXfaPacketCount grąžina paketų skaičių formoje, GetXfaPacketName suteikia kiekvienam pavadinimą, o GetXfaPacketContent grąžina neapdorotus baitus, kurie žinomiems paketams yra UTF-8 XML. Kai jau žinote, kurio paketo norite, pavadintos pagalbinės funkcijos GetXfaTemplate, GetXfaDatasets bei GetXfaConfig suranda pavadinimą už jus bei grąžina TBytes tiesiogiai. Duomenų migravimo darbui svarbiausias yra GetXfaDatasets: jis pateikia pateiktas laukų reikšmes kaip XML dokumentą, kurį galite analizuoti bei susieti su savo schema; tai panašus darbas, kokį darytumėte po teksto išgavimo iš 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;

Kodėl dinaminiam XFA atvaizdavimui reikalingas V8 variklis?

Išvada pirmiausia: todėl, kad dinaminė XFA forma yra maža programa, o ne statinis piešinys. Šablono pakete yra FormCalc bei JavaScript skaičiavimai, patvirtinimai bei įvykių scenarijai, o XFA išdėstymo variklis turi juos įvykdyti, kad nuspręstų, kaip puslapiai išvis atrodo. PDFium įdiegia šį variklį tik toje versijoje, kuri sukompiliuota su XFA palaikymu virš V8 — „Google“ JavaScript variklio; tai yra ta pati priežastis, kokybės sumetimais, kodėl V8 palaikanti DLL (pdfium32v8.dll arba pdfium64v8.dll) yra žymiai didesnė už standartinę versiją. Be V8 nėra kam vykdyti scenarijų, todėl nėra rodomų atvaizduotų puslapių, tik neapdorotas XML, kurį jau galite nuskaityti statiškai

Šios versijos pasirinkimas turi griežtą apribojimą, kurio negalite apeiti: tai yra vienkartinis sprendimas vienam procesui. Vienas procesas negali įkelti tiek standartinės pdfium.dll, tiek V8 palaikančios versijos — jos eksportuoja tuos pačius simbolius, o V8 išlaiko globalią izoliato būseną —, todėl PDFium komponentas turi pasirinkti V8 versiją prieš įkeliant biblioteką pačiu pirmasis kartas. Kad tai būtų atliekama automatiškai, LoadDocument iš anksto patikrina failą, ieškodama /XFA bei /NeedsRendering true žymeklių (tas pats tikrinimas pateikiamas kaip savarankiška PdfFileLooksXfa funkcija) bei prieš įkėlimą nustato EnableV8Engine := True, jei juos randa. Kai įprasta pdfium.dll jau yra atmintyje, jungiklis nebegali suveikti, ir apie šį atvejį praneša vykdymo laiko trūkumo kelias

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;

Grakštus trūkstamos XFA vykdymo aplinkos apdorojimas

Trys patikros praneša, kokį XFA palaikymą turite, ir teisingas kodas jas tikrina, užuot spėliojęs. XfaFeaturesAvailable yra globalus patikrinimas, ar įkeltoje DLL yra XFA/V8 pagalbiniai eksportai. XfaStaticReadable patvirtina konkrečiam dokumentui, kad paketų skaitytuvai yra išspręsti — tai apsauga išgavimui. XfaRuntimeAvailable yra stiprioji patikra: ji yra True tik tada, kai šis konkretus dokumentas iš tikrųjų aktyvavo XFA variklį, t. y. jis yra XFA, įkelta V8 versija bei FPDF_LoadXFA pavyko. Dokumentas gali pranešti XFA = True, tačiau XfaRuntimeAvailable = False, kai įdiegtoje DLL trūksta XFA arba standartinė versija jau buvo įkelta

Kai XFA dokumentas atsidaro, tačiau variklis negali veikti, PDFium komponentas nerizikuoja. Jis pririša formą prie saugaus statinio režimo, užuot reikalavęs dinaminio vykdymo laiko, kurio negali suteikti, bei vieną kartą suaktyvina OnXfaRuntimeMissing įvykį, kad programa galėtų sureaguoti — paprastai pranešdama vartotojui apie būtinybę perkrauti programą su V8 versija, tuo pat metu leisdama pasiekti statiškai skaitomus duomenis. Šio įvykio apdorojimas užkerta kelią situacijai, kai forma byla tyliai nieko nerodo

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;

Būkite sąžingi dėl šių ribų ir savo pačių kuriamoje programoje. Jei niekada neplatinsite V8 palaikančios DLL, dinaminės XFA formos jūsų vartotojams niekada nebus atvaizduojamos — tačiau vis tiek galite jas aptikti, išgauti kiekvieną paketą bei ištraukti pateiktus duomenis; to pakanka didelei užduočių daliai, kai reikia tiesiog ištraukti duomenis iš senų vyriausybės bei bankų formų, užuot jas rodžius interaktyviai. Pasilikite sunkesnę V8 versiją tais atvejais, kai tikrai reikia gyvos, pildomos formos, ir leiskite galimybių patikroms nukreipti kiekvieną dokumentą jam tinkamu keliu. Jei jūsų išgavimo konvejeris taip pat apdoroja įterptuosius failus, tas pats skaitomas principas galioja ir straipsnyje PDF priedai Delphi aplinkoje

Čia parodyti FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig bei galimybių patikrų API platinami kaip PDFium komponento, skirto Delphi ir C++Builder, dalis