Skip to content

Formularios XFA — Detectar y Leer Datos XML en Python, Rust, Node.js, Go y C#

Detección y análisis de formularios 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) es un formato legado de formularios utilizado por muchas agencias gubernamentales, instituciones financieras y sistemas empresariales. La mayoría de las bibliotecas PDF para Python no pueden manejar formularios XFA en absoluto. PDF Oxide puede detectarlos, analizarlos y extraer datos de ellos.

¿Qué es XFA?

Los formularios XFA usan plantillas basadas en XML incrustadas dentro de un PDF, en lugar de los campos AcroForm estándar. Fueron creados por Adobe y son comunes en:

  • Formularios gubernamentales — IRS, inmigración, documentos de agencias estatales
  • Formularios financieros — solicitudes de préstamo, reclamaciones de seguros
  • Formularios empresariales — incorporación de RRHH, adquisiciones, cumplimiento normativo

XFA quedó obsoleto en PDF 2.0 (ISO 32000-2:2020), pero millones de documentos XFA existentes siguen en circulación.

XFA vs. AcroForm

Característica AcroForm XFA
Formato Objetos PDF Plantillas XML
Compatible con Todas las bibliotecas PDF Pocas bibliotecas PDF
Diseños dinámicos No
Estado en PDF 2.0 Compatible Obsoleto
Fuente típica La mayoría de las herramientas de creación de formularios Adobe LiveCycle, Adobe Designer

Por Qué PyMuPDF y pypdf No Pueden Manejar Formularios XFA

Si has intentado leer formularios XFA con las bibliotecas Python más populares para PDF, probablemente hayas obtenido resultados vacíos sin ningún error ni aviso. Esto se debe a que PyMuPDF, pypdf, pdfplumber y pdfminer no tienen soporte para XFA.

PyMuPDF (fitz) — devuelve vacío en silencio

Los métodos doc.get_form_fields() y .widgets() de PyMuPDF solo leen campos AcroForm. Cuando un PDF usa formularios exclusivamente XFA (habitual en documentos del IRS, inmigración y agencias estatales), PyMuPDF devuelve resultados vacíos sin ninguna advertencia:

# 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

Si el formulario XFA incluye una capa de reserva AcroForm, PyMuPDF puede devolver un subconjunto parcial de campos, pero los datos XFA reales (diseños dinámicos, valores calculados, subformularios anidados) permanecen invisibles.

pypdf — también devuelve vacío en formularios XFA

La lectura de campos de formulario en pypdf choca con la misma limitación. Solo puede acceder a campos AcroForm y no tiene soporte para 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 y pdfminer — sin ningún soporte para XFA

pdfplumber y pdfminer no intentan leer campos de formularios XFA. No disponen de API para detectar ni extraer XFA.

PDF Oxide — lee XFA de forma nativa

PDF Oxide parsea las plantillas XML de XFA directamente, extrayendo todos los campos, valores y la estructura del formulario:

# 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

Funciona con formularios gubernamentales, documentos del IRS, solicitudes de seguros y cualquier PDF basado en XFA, incluyendo formularios sin capa de reserva AcroForm.

Instalación

pip install pdf_oxide

Detectar Formularios XFA

Comprobar si un PDF contiene contenido XFA:

Python

from pdf_oxide import PdfDocument

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

if xfa:
    print("Este PDF usa formularios XFA")
    print(f"  Campos: {len(xfa.fields)}")
    print(f"  Tiene plantilla: {xfa.has_template}")
    print(f"  Tiene datasets: {xfa.has_datasets}")
else:
    print("AcroForm estándar (o sin formularios)")

WASM

En WASM, puedes detectar formularios XFA y usar como alternativa la lectura de campos AcroForm:

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

const doc = new WasmPdfDocument(bytes);
if (doc.hasXfa()) {
  console.log("Este PDF usa formularios XFA");
  // Leer los campos de reserva AcroForm
  const fields = doc.getFormFields();
  console.log(`Campos de reserva 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

Analizar Campos XFA

Obtener detalles de cada campo en el formulario 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()

Leer Datos XFA

Extraer los valores actuales de los campos desde los datasets 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)

Procesamiento en Lote de Formularios XFA

Escanear un directorio para identificar qué PDFs usan 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)}")

API de Rust

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

El binding para Node.js expone la detección de XFA y un XfaManager de alto nivel para operaciones a nivel de campo cuando el gestor opcional del lado de Node está instalado. Para lógica simple de enrutamiento, la detección es una sola llamada:

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

El binding para Go expone la detección de XFA. Úsalo para marcar documentos XFA en pipelines y redirigir esos PDFs a un paso en Python o Rust para la extracción completa de campos:

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");
}

Nota sobre la cobertura de bindings. La detección de XFA (hasXFA / HasXfa) está disponible en los cinco bindings. La enumeración completa de campos y extracción de valores XFA (nombres, tipos, valores, XML de datasets) está actualmente disponible solo en Python y Rust; los bindings de Node.js, Go y C# ofrecen detección y lectura del fallback AcroForm. En flujos de trabajo que necesiten leer valores de campos XFA desde Go o C#, añade un paso intermedio en Python o Rust.

Por Qué Importa XFA

La mayoría de las bibliotecas PDF para Python ignoran silenciosamente el contenido XFA — extract_text() y las APIs de campos de formulario solo ven la capa de reserva AcroForm (si existe). Muchos formularios exclusivamente XFA no tienen reserva AcroForm, lo que los hace invisibles para otras herramientas:

  • Formularios XFA de PyMuPDF (pymupdf)get_form_fields() y .widgets() devuelven vacío en PDFs con solo XFA. PyMuPDF no tiene soporte para XFA y no tiene planes de añadirlo.
  • Soporte XFA de pypdfget_form_text_fields() de pypdf no puede leer contenido XFA. Solo son visibles los campos de reserva AcroForm, si es que existen.
  • pdfplumber — sin soporte para XFA. La extracción de formularios se limita a campos AcroForm.
  • pdfminer — sin soporte para XFA. No puede detectar ni extraer datos de formularios XFA.

PDF Oxide es la única biblioteca PDF para Python que lee plantillas XML de XFA directamente, dándote acceso a la estructura del formulario y a los datos que PyMuPDF, pypdf, pdfplumber y pdfminer no pueden ver.

Páginas Relacionadas