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 и распространён в следующих областях:

  • Государственные формы — налоговые декларации, иммиграционные документы, формы госведомств
  • Финансовые формы — заявки на кредит, страховые требования
  • Корпоративные формы — адаптация новых сотрудников, закупки, соответствие нормативам

XFA был признан устаревшим в PDF 2.0 (ISO 32000-2:2020), однако миллионы XFA-документов по-прежнему в обороте.

XFA vs 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 — silently misses XFA data
import fitz
doc = fitz.open("government-form.pdf")
fields = doc[0].widgets()  # Returns [] on XFA-only forms
form_data = doc.get_form_fields()  # Returns {} on XFA-only forms

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

pypdf — тоже возвращает пустой результат для XFA-форм

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

# pypdf — cannot read XFA content
from pypdf import PdfReader
reader = PdfReader("government-form.pdf")
fields = reader.get_form_text_fields()  # Returns {} on XFA-only forms

pdfplumber и pdfminer — поддержки XFA нет совсем

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

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

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

# PDF Oxide — reads XFA natively
from pdf_oxide import PdfDocument
doc = PdfDocument("government-form.pdf")
xfa = doc.analyze_xfa()
print(f"{len(xfa.fields)} fields found")  # All XFA fields extracted

Это работает с государственными формами, документами 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("This PDF uses XFA forms")
    print(f"  Fields: {len(xfa.fields)}")
    print(f"  Has template: {xfa.has_template}")
    print(f"  Has datasets: {xfa.has_datasets}")
else:
    print("Standard AcroForm (or no forms)")

WASM

В WASM можно обнаружить XFA-форму и при необходимости вернуться к чтению полей AcroForm:

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

const doc = new WasmPdfDocument(bytes);
if (doc.hasXfa()) {
  console.log("This PDF uses XFA forms");
  // Read any AcroForm fallback fields
  const fields = doc.getFormFields();
  console.log(`AcroForm fallback fields: ${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-документы в конвейерах и перенаправлять эти PDF в шаг 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-резерва, что делает их полностью невидимыми для других инструментов:

  • PyMuPDF (pymupdf) и XFAget_form_fields() и .widgets() возвращают пустой результат для PDF с только XFA-формами. У PyMuPDF нет поддержки XFA, и добавлять её не планируется.
  • pypdf и XFAget_form_text_fields() в pypdf не может читать XFA-контент. Видны только резервные поля AcroForm — если они вообще существуют.
  • pdfplumber — нет поддержки XFA. Извлечение форм ограничено полями AcroForm.
  • pdfminer — нет поддержки XFA. Не может обнаруживать или извлекать данные XFA-форм.

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

Связанные страницы