Artikel Teknis

Mendeteksi dan Mengekstrak Formulir XFA di Delphi dengan PDFium

Ya — PDFium mendukung formulir XFA, dengan satu pembagian penting. PDFium Component dapat mendeteksi formulir XFA dan mengekstrak paket template, datasets, dan config-nya sebagai XML mentah menggunakan DLL standar, dengan nol dependensi runtime. Menjalankan formulir XFA dinamis — menata deskripsi XML-nya menjadi halaman aktif yang interaktif — adalah masalah terpisah yang juga memerlukan build yang mengaktifkan V8. Komunitas terus bertanya apakah PDFium "melakukan XFA" seolah-olah itu adalah satu kemampuan ya-atau-tidak, dan jawaban jujurnya adalah bahwa membaca data XFA dan merender formulir XFA adalah dua hal yang sangat berbeda dengan jejak dependensi yang sangat berbeda pula

Artikel ini membahas kedua bagian tersebut dengan PDFium Component, pembungkus VCL asli di sekitar PDFium untuk Delphi dan C++Builder. Pertama, jalur deteksi dan ekstraksi statis — FormType, properti XFA, dan pembaca GetXfaPacket* — kemudian jalur runtime dinamis dengan batasan V8-nya, dan terakhir penyelidikan kemampuan (capability probes) yang memungkinkan Anda menangani kegagalan dengan anggun ketika DLL yang diterapkan tidak dapat menjalankan engine

Apakah PDFium mendukung formulir XFA?

Pahamilah ini sebagai dua kemampuan, bukan satu. Ekstraksi XFA statis selalu tersedia: ekspor tingkat rendah FPDF_GetXFAPacket* yang mendukung GetXfaPacketCount, GetXfaPacketName, dan GetXfaPacketContent dikompilasi ke dalam setiap pdfium.dll yang cukup modern, bahkan build yang menonaktifkan XFA. Itulah mengapa mengekstrak XML mentah dari template, datasets, dan config dari suatu formulir sama sekali tidak membawa dependensi runtime — ini adalah pembacaan struktural murni, tidak melibatkan engine. XFA Dinamis adalah kemampuan lainnya: menjalankan formulir secara nyata, mengevaluasi perhitungannya, dan membiarkan engine tata letak XFA membaginya ke dalam halaman. Jalur tersebut hanya ada di build PDFium yang dikompilasi dengan dukungan XFA di atas engine JavaScript V8, dan itu adalah bagian yang paling sering diperdebatkan di thread diskusi tentang "apakah PDFium mendukung XFA"

Standar tersebut membingkai pembagian ini dengan bersih. XFA didefinisikan di luar tata bahasa inti PDF dalam Adobe XFA 3.3 (2012), dan PDF 1.7 §8.6.7 menjelaskan bagaimana formulir XFA dibawa di dalam PDF: paket formulir berada di bawah entri kamus AcroForm /XFA entry, dan dokumen yang harus ditata oleh prosesor XFA sebelum dapat digunakan akan menyetel /NeedsRendering true dalam katalog. Kedua penanda tersebut adalah apa yang dicari oleh PDFium Component, dan merupakan hal yang dapat Anda andalkan saat menganalisis file yang belum pernah Anda lihat

Bagaimana perbedaan XFA dari AcroForm?

AcroForm adalah formulir PDF klasik: anotasi widget — bidang teks, kotak centang, grup radio — ditambatkan pada koordinat tetap pada halaman PDF biasa. Halaman yang Anda lihat adalah halaman yang ada di dalam file, dan bidang (fields) berada di atasnya. XFA mengambil pendekatan sebaliknya. Formulir interaktif dijelaskan seluruhnya dalam XML, dan dalam dokumen XFA (dinamis) lengkap, halaman PDF secara efektif menjadi placeholder — konten sebenarnya dibuat pada saat pembukaan oleh engine XFA yang membaca template XML dan menatanya, membesar atau mengecil agar sesuai dengan data. Itulah yang diumumkan oleh /NeedsRendering true: tanpa prosesor XFA, halaman statis mungkin kosong atau hanya membawa pemberitahuan "harap buka di penampil yang kompatibel"

Payload XFA itu sendiri adalah satu set paket bernama, dan tiga di antaranya membawa semua yang dibutuhkan oleh sebagian besar integrasi. Paket template adalah definisi formulir — bidang, tata letak, perhitungan, dan skrip. Paket datasets menampung data instans yang sebenarnya, yaitu nilai bidang yang diisi sebagai pohon XML. Paket config membawa instruksi pemrosesan untuk engine XFA. Paket lain juga ada — localeSet, connectionSet, pembungkus xdp — tetapi untuk mengaudit formulir atau memigrasikan data darinya, template ditambah datasets biasanya sudah mencakup seluruh pekerjaan. Karena data tersebut hanyalah XML yang berada dalam stream, membacanya tidak pernah memerlukan pengeksekusian apa pun, yang merupakan alasan mendasar mengapa ekstraksi statis bebas dari dependensi

Mendeteksi formulir XFA di Delphi

Deteksi dimulai dengan cara yang sama seperti tugas PDFium Component lainnya: atur FileName, ubah Active := True, dan periksa apakah file tersebut benar-benar terbuka. Active := True tidak pernah memunculkan pengecualian — file yang hilang, kata sandi yang salah, atau DLL yang tidak ada hanya akan membiarkan Active bernilai False — sehingga pemeriksaan ini wajib dilakukan. Setelah dokumen terbuka, FormType memberi tahu Anda model formulir mana yang digunakannya. Dua dari nilai TPdfFormType adalah XFA: ftXfaFull adalah formulir XFA dinamis lengkap, jenis yang memerlukan perenderan, sedangkan ftXfaForeground adalah XFAF, subset latar depan di mana konten XFA digambar di atas halaman AcroForm yang normal sehingga halaman statis tetap bermakna. Boolean XFA adalah pintasan untuk "ini adalah dokumen XFA dari salah satu jenis". Jika Anda juga bekerja dengan bidang tingkat widget pada dokumen XFAF atau AcroForm, mekanismenya dibahas dalam panduan untuk navigasi bidang formulir 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;

Mengekstrak paket XFA sebagai XML mentah

Ekstraksi adalah bagian yang bernilai tinggi dan bebas dependensi. Lindungi dengan pemeriksaan XfaStaticReadable — ini mengonfirmasi ekspor pembaca paket berhasil diselesaikan dalam DLL yang dimuat — lalu telusuri paket berdasarkan indeks. GetXfaPacketCount mengembalikan berapa banyak paket yang dibawa formulir, GetXfaPacketName memberikan nama untuk masing-masing paket, dan GetXfaPacketContent mengembalikan byte mentah, yang untuk paket terkenal berupa XML UTF-8. Ketika Anda sudah tahu paket mana yang Anda inginkan, pembantu bernama GetXfaTemplate, GetXfaDatasets, dan GetXfaConfig menyelesaikan nama tersebut untuk Anda dan mengembalikan TBytes secara langsung. Untuk pekerjaan migrasi data, yang penting adalah GetXfaDatasets: ini memberi Anda nilai bidang yang dikirimkan sebagai dokumen XML yang dapat Anda urai dan petakan ke dalam skema Anda sendiri, jenis pekerjaan hilir yang sama dengan yang akan Anda lakukan setelah mengekstrak teks dari 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;

Mengapa perenderan XFA dinamis memerlukan engine V8?

Kesimpulan pertama: karena formulir XFA dinamis adalah program kecil, bukan gambar statis. Paket template membawa perhitungan FormCalc dan JavaScript, validasi, dan skrip peristiwa (event scripts), dan engine tata letak XFA harus mengeksekusinya untuk memutuskan seperti apa tampilan halaman tersebut. PDFium mengimplementasikan engine tersebut hanya dalam build yang dikompilasi dengan dukungan XFA di atas V8, engine JavaScript Google — alasan yang sama mengapa DLL yang mengaktifkan V8 (pdfium32v8.dll atau pdfium64v8.dll) jauh lebih besar daripada build standar. Tanpa V8, tidak ada yang menjalankan skrip, sehingga tidak ada halaman yang dirender untuk ditampilkan, hanya XML mentah yang sudah dapat Anda baca secara statis

Memilih build tersebut memiliki batasan ketat yang tidak dapat Anda hindari: ini adalah keputusan sekali saja per proses. Satu proses tidak dapat memuat pdfium.dll standar dan build yang mengaktifkan V8 secara bersamaan — keduanya mengekspor simbol yang sama dan V8 mempertahankan status isolat global — sehingga PDFium Component harus memilih build V8 sebelum pustaka dimuat untuk pertama kalinya. Untuk membuatnya otomatis, LoadDocument memindai file terlebih dahulu untuk mencari penanda /XFA dan /NeedsRendering true (pemeriksaan yang sama yang diekspos sebagai fungsi mandiri PdfFileLooksXfa) dan menyetel EnableV8Engine := True sebelum pemuatan saat menemukannya. Begitu pdfium.dll biasa sudah dimuat (resident), sakelar tidak dapat lagi berlaku, dan kasus itulah yang dilaporkan oleh jalur runtime-missing

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;

Menangani runtime XFA yang hilang dengan anggun

Tiga penyelidikan memberi tahu Anda dengan tepat seberapa besar kemampuan XFA yang Anda miliki, dan kode yang jujur memeriksanya alih-alih berasumsi. XfaFeaturesAvailable adalah pemeriksaan global bahwa ekspor pembantu XFA/V8 ada di DLL yang dimuat. XfaStaticReadable mengonfirmasi, per dokumen, bahwa pembaca paket berhasil diselesaikan — pemeriksaan keamanan untuk ekstraksi. XfaRuntimeAvailable adalah pemeriksaan yang kuat: bernilai true hanya ketika dokumen spesifik ini benar-benar mengaktifkan engine XFA, yang berarti dokumen tersebut adalah XFA, build V8 dimuat, dan FPDF_LoadXFA berhasil. Sebuah dokumen dapat melaporkan XFA = True namun XfaRuntimeAvailable = False setiap kali DLL yang diterapkan tidak memiliki XFA atau build standar sudah dimuat

Ketika dokumen XFA terbuka tetapi engine tidak dapat berjalan, PDFium Component tidak berspekulasi. Ia mematok formulir ke mode statis yang aman daripada meminta runtime dinamis yang tidak dapat dipenuhinya, dan ia memunculkan OnXfaRuntimeMissing sekali sehingga host dapat bereaksi — biasanya dengan memberi tahu pengguna untuk memulai ulang dengan build yang mengaktifkan V8, sementara tetap menawarkan data yang dapat dibaca secara statis. Menghubungkan handler tersebut adalah perbedaan antara fallback yang bersih dan formulir yang secara diam-diam tidak menampilkan apa pun

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;

Bersikaplah jujur tentang batasan tersebut dalam produk Anda sendiri juga. Jika Anda tidak pernah mengirimkan DLL yang mengaktifkan V8, formulir XFA dinamis tidak akan pernah dirender untuk pengguna Anda — tetapi Anda masih dapat mendeteksinya, mengekstrak setiap paket, dan menarik keluar data yang dikirimkan, yang cukup untuk sebagian besar pekerjaan yang sebenarnya adalah tentang mendapatkan data dari formulir pemerintah dan perbankan warisan alih-alih menyajikannya secara interaktif. Cadangkan build V8 yang lebih berat untuk kasus yang benar-benar memerlukan formulir aktif yang dapat diisi, dan biarkan penyelidikan kemampuan mengarahkan setiap dokumen ke jalur yang sebenarnya dapat diambilnya. Jika Anda juga bekerja dengan bidang tingkat widget pada dokumen XFAF atau AcroForm, mekanismenya dibahas dalam panduan untuk navigasi bidang formulir PDFium

API FormType, XFA, GetXfaPacketCount, GetXfaPacketName, GetXfaPacketContent, GetXfaTemplate, GetXfaDatasets, GetXfaConfig, dan penyelidikan kemampuan yang ditunjukkan di sini adalah bagian dari PDFium Component untuk Delphi dan C++Builder