Skip to content

XFA-форми — Виявлення та зчитування XML-даних у Python, Rust, Node.js, Go і C#

Виявлення та аналіз XFA-форм:

from pdf_oxide import PdfDocument

doc = PdfDocument("government-form.pdf")
xfa = doc.analyze_xfa()
if xfa:
    print(f"XFA form with {len(xfa.fields)} fields")
    for field in xfa.fields:
        print(f"  {field.name}: {field.field_type}")

XFA (XML Forms Architecture) — застарілий формат форм, що використовується багатьма державними установами, фінансовими організаціями та корпоративними системами. Більшість Python-бібліотек для PDF не можуть обробляти XFA-форми взагалі. PDF Oxide вміє їх виявляти, аналізувати та витягувати з них дані.

Що таке XFA?

XFA-форми використовують XML-шаблони, вбудовані безпосередньо у PDF, замість стандартних полів AcroForm. Формат розроблений компанією Adobe і поширений у таких сферах:

  • Державні форми — документи IRS, імміграційні служби, державні відомства
  • Фінансові форми — заявки на кредит, страхові вимоги
  • Корпоративні форми — адаптація нових співробітників, закупівлі, відповідність вимогам

XFA був визнаний застарілим у PDF 2.0 (ISO 32000-2:2020), проте мільйони XFA-документів досі перебувають в обігу.

XFA проти AcroForm

Характеристика AcroForm XFA
Формат Об’єкти PDF XML-шаблони
Підтримка Всі PDF-бібліотеки Одиниці PDF-бібліотек
Динамічні макети Ні Так
Статус у PDF 2.0 Підтримується Застарілий
Типове джерело Більшість інструментів для форм Adobe LiveCycle, Adobe Designer

Чому PyMuPDF і pypdf не справляються з XFA-формами

Якщо ви пробували читати XFA-форми за допомогою популярних Python-бібліотек для PDF, ви напевне бачили порожні результати — без помилок і без попереджень. Причина проста: PyMuPDF, pypdf, pdfplumber і pdfminer не підтримують XFA.

PyMuPDF (fitz) — мовчки повертає порожній результат

Методи doc.get_form_fields() та .widgets() у PyMuPDF читають виключно поля AcroForm. Коли PDF містить форми лише у форматі XFA (типово для документів IRS, міграційних служб та державних відомств), PyMuPDF повертає порожній результат без жодного попередження:

# PyMuPDF — мовчки пропускає дані XFA
import fitz
doc = fitz.open("government-form.pdf")
fields = doc[0].widgets()  # Повертає [] для форм лише з XFA
form_data = doc.get_form_fields()  # Повертає {} для форм лише з XFA

Якщо XFA-форма містить резервний шар AcroForm, PyMuPDF може повернути частину полів — але справжні XFA-дані (динамічні макети, обчислені значення, вкладені підформи) залишаться невидимими.

pypdf — також повертає порожнє для XFA-форм

Читання полів форм у pypdf натикається на те саме обмеження. Бібліотека має доступ лише до полів AcroForm і не підтримує XFA:

# pypdf — не може читати вміст XFA
from pypdf import PdfReader
reader = PdfReader("government-form.pdf")
fields = reader.get_form_text_fields()  # Повертає {} для форм лише з XFA

pdfplumber і pdfminer — підтримки XFA немає взагалі

pdfplumber і pdfminer не намагаються зчитувати поля з XFA-форм. API для виявлення чи витягування XFA у них відсутні.

PDF Oxide — читає XFA нативно

PDF Oxide безпосередньо парсить XML-шаблони XFA, витягуючи всі поля, значення та структуру форми:

# PDF Oxide — читає XFA нативно
from pdf_oxide import PdfDocument
doc = PdfDocument("government-form.pdf")
xfa = doc.analyze_xfa()
print(f"{len(xfa.fields)} fields found")  # Всі поля XFA витягнуто

Це працює з державними формами, документами IRS, страховими заявками та будь-якими іншими PDF на основі XFA — включно з формами без резервного шару AcroForm.

Встановлення

pip install pdf_oxide

Виявлення XFA-форм

Перевірити, чи PDF містить вміст XFA:

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("form.pdf")
xfa = doc.analyze_xfa()

if xfa:
    print("Цей PDF використовує XFA-форми")
    print(f"  Поля: {len(xfa.fields)}")
    print(f"  Є шаблон: {xfa.has_template}")
    print(f"  Є набори даних: {xfa.has_datasets}")
else:
    print("Стандартна AcroForm (або без форм)")

WASM

У WASM можна виявити XFA-форми і перейти до читання полів AcroForm як запасного варіанту:

import { WasmPdfDocument } from "pdf-oxide-wasm";

const doc = new WasmPdfDocument(bytes);
if (doc.hasXfa()) {
  console.log("Цей PDF використовує XFA-форми");
  // Читання резервних полів AcroForm
  const fields = doc.getFormFields();
  console.log(`Резервні поля AcroForm: ${fields.length}`);
}
doc.free();

C++

#include <pdf_oxide/pdf_oxide.hpp>
#include <iostream>

auto doc = pdf_oxide::Document::open("government-form.pdf");
if (doc.has_xfa()) {
    std::cout << "This PDF uses XFA forms\n";
    // Read any AcroForm fallback fields
    auto fields = doc.get_form_fields();
    std::cout << "AcroForm fallback fields: " << fields.size() << "\n";
}

Swift

import PdfOxide

let doc = try Document.open("government-form.pdf")
if try doc.hasXfa() {
    print("This PDF uses XFA forms")
    // Read any AcroForm fallback fields
    let fields = try doc.formFields()
    print("AcroForm fallback fields: \(fields.count)")
}

Dart

import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('government-form.pdf');
if (doc.hasXfa()) {
  print('This PDF uses XFA forms');
  // Read any AcroForm fallback fields
  final fields = doc.getFormFields();
  print('AcroForm fallback fields: ${fields.length}');
}
doc.close();

R

library(pdfoxide)

doc <- pdf_open("government-form.pdf")
if (pdf_has_xfa(doc)) {
  cat("This PDF uses XFA forms\n")
  # Read any AcroForm fallback fields
  fields <- pdf_get_form_fields(doc)
  cat("AcroForm fallback fields:", length(fields), "\n")
}

Julia

using PdfOxide

doc = open_document("government-form.pdf")
if has_xfa(doc)
    println("This PDF uses XFA forms")
    # Read any AcroForm fallback fields
    fields = get_form_fields(doc)
    println("AcroForm fallback fields: ", length(fields))
end

Zig

const pdf_oxide = @import("pdf_oxide");

var doc = try pdf_oxide.Document.open("government-form.pdf");
defer doc.deinit();
if (doc.hasXfa()) {
    std.debug.print("This PDF uses XFA forms\n", .{});
    // Read any AcroForm fallback fields
    var fields = try doc.formFields();
    defer fields.deinit();
    std.debug.print("AcroForm fallback fields: {d}\n", .{try fields.count()});
}

Objective-C

#import "POXPdfOxide.h"
NSError *err = nil;

POXDocument *doc = [POXDocument openPath:@"government-form.pdf" error:&err];
if ([doc hasXfa]) {
    NSLog(@"This PDF uses XFA forms");
    // Read any AcroForm fallback fields
    NSArray<POXFormField*> *fields = [doc formFieldsWithError:&err];
    NSLog(@"AcroForm fallback fields: %lu", (unsigned long)fields.count);
}

Elixir

{:ok, doc} = PdfOxide.open("government-form.pdf")

if PdfOxide.has_xfa?(doc) do
  IO.puts("This PDF uses XFA forms")
  # Read any AcroForm fallback fields
  {:ok, fields} = PdfOxide.form_fields(doc)
  IO.puts("AcroForm fallback fields: #{length(fields)}")
end

Аналіз полів XFA

Отримати детальну інформацію про кожне поле у XFA-формі:

from pdf_oxide import PdfDocument

doc = PdfDocument("tax-form.pdf")
xfa = doc.analyze_xfa()

if xfa:
    for field in xfa.fields:
        print(f"Name: {field.name}")
        print(f"  Type: {field.field_type}")
        print(f"  Value: {field.value}")
        print()

Зчитування даних XFA

Витягти поточні значення полів із наборів даних XFA:

from pdf_oxide import PdfDocument

doc = PdfDocument("filled-xfa.pdf")
xfa = doc.analyze_xfa()

if xfa and xfa.has_datasets:
    data = {}
    for field in xfa.fields:
        if field.value:
            data[field.name] = field.value
    print(data)

Пакетна обробка XFA-форм

Сканування директорії для виявлення PDF, що використовують XFA:

from pdf_oxide import PdfDocument, PdfError
from pathlib import Path

pdf_dir = Path("government-forms/")
xfa_files = []
acroform_files = []

for pdf_path in pdf_dir.glob("*.pdf"):
    try:
        doc = PdfDocument(str(pdf_path))
        xfa = doc.analyze_xfa()
        if xfa:
            xfa_files.append(pdf_path.name)
        else:
            acroform_files.append(pdf_path.name)
    except PdfError as e:
        print(f"Error: {pdf_path.name}: {e}")

print(f"XFA forms: {len(xfa_files)}")
print(f"Standard forms: {len(acroform_files)}")

Rust API

use pdf_oxide::PdfDocument;
use pdf_oxide::xfa::analyze_xfa_document;

let mut doc = PdfDocument::open("xfa-form.pdf")?;
let analysis = analyze_xfa_document(&mut doc)?;

println!("XFA form detected: {} fields", analysis.fields.len());
for field in &analysis.fields {
    println!("  {} ({:?}): {:?}", field.name, field.field_type, field.value);
}

Node.js / TypeScript

Прив’язка до Node.js надає виявлення XFA та, за наявності встановленого опційного менеджера на стороні Node, XfaManager вищого рівня для операцій на рівні полів. Для простої логіки маршрутизації достатньо одного виклику:

const { PdfDocument } = require("pdf-oxide");

const doc = new PdfDocument("government-form.pdf");
if (doc.hasXFA()) {
  console.log("XFA form — route to specialized handler");
  // AcroForm fallback fields (if any) via doc.getFormFields()
  const fallback = doc.getFormFields();
  console.log(`AcroForm fallback fields: ${fallback.length}`);
} else {
  console.log("Standard AcroForm or no forms");
}
doc.close();
import { PdfDocument } from "pdf-oxide";

const doc = new PdfDocument("government-form.pdf");
if (doc.hasXFA()) {
  const fallback = doc.getFormFields();
  console.log(`XFA detected; ${fallback.length} AcroForm fallback fields`);
}
doc.close();

Go

Прив’язка до Go підтримує виявлення XFA. Використовуйте її, щоб позначати XFA-документи у конвеєрах і перенаправляти їх до кроку Python або Rust для повного витягування полів:

package main

import (
    "fmt"
    "log"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    doc, err := pdfoxide.Open("government-form.pdf")
    if err != nil { log.Fatal(err) }
    defer doc.Close()

    if doc.HasXfa() {
        fmt.Println("XFA form detected — route to Python/Rust extractor")
    } else {
        fmt.Println("Standard AcroForm or no forms")
    }
}

C#

using PdfOxide;

using var doc = PdfDocument.Open("government-form.pdf");
if (doc.HasXfa)
{
    Console.WriteLine("XFA form detected — route to specialized extractor");
}
else
{
    Console.WriteLine("Standard AcroForm or no forms");
}

Примітка щодо охоплення прив’язок. Виявлення XFA (hasXFA / HasXfa) доступне у всіх п’яти прив’язках. Повна перелік полів і витягування значень XFA (імена, типи, значення, XML наборів даних) наразі реалізовані лише у Python і Rust; прив’язки Node.js, Go і C# підтримують виявлення та читання резервного AcroForm. Для робочих процесів, яким потрібні значення XFA-полів з Go або C#, використовуйте проміжний крок Python або Rust.

Чому XFA має значення

Більшість Python-бібліотек для PDF мовчки ігнорують вміст XFA — extract_text() та API полів форм бачать лише резервний шар AcroForm (якщо він існує). Багато форм лише з XFA не мають резервного шару AcroForm, що робить їх повністю невидимими для інших інструментів:

  • XFA-форми у PyMuPDF (pymupdf)get_form_fields() та .widgets() повертають порожнє для PDF лише з XFA. PyMuPDF не підтримує XFA і не планує цього робити.
  • Підтримка XFA у pypdfget_form_text_fields() pypdf не може читати вміст XFA. Видимі лише резервні поля AcroForm — якщо вони взагалі є.
  • pdfplumber — без підтримки XFA. Витягування форм обмежене полями AcroForm.
  • pdfminer — без підтримки XFA. Не може виявляти чи витягувати дані XFA-форм.

PDF Oxide — єдина Python-бібліотека для PDF, яка читає XML-шаблони XFA безпосередньо, надаючи доступ до структури та даних форм, невидимих для PyMuPDF, pypdf, pdfplumber і pdfminer.

Пов’язані сторінки