Cómo trabajar con datos PDF en Python

Cómo trabajar con datos PDF en Python

Aspose.PDF FOSS para Python representa los metadatos XMP de un documento PDF como un modelo de datos en memoria construido a partir de objetos XmpPacket, XmpField, XmpArray, XmpStruct y XmpProperty, con NamespaceProvider resolviendo los prefijos de espacio de nombres a URIs. Esto le permite leer, crear y reescribir metadatos estructurados — títulos, fechas, listas de palabras clave, campos personalizados — sin escribir manualmente RDF/XML. La biblioteca es pura Python y se instala con el siguiente comando.

Guía paso a paso

Paso 1: Instalar el paquete

Instale el paquete FOSS Aspose.PDF:

git clone https://github.com/aspose-pdf-foss/Aspose-PDF-FOSS-for-Python.git
cd Aspose-PDF-FOSS-for-Python
pip install -e .

Verifique la instalación importando XmpPacket y mostrando un mensaje de confirmación:

from aspose_pdf import XmpPacket
print("aspose-pdf-foss-for-python is ready.")

Paso 2: Importar clases requeridas

Importa las clases del modelo de datos XMP y el resolvedor de espacios de nombres:

from aspose_pdf import (
    NamespaceProvider,
    XmpArray,
    XmpField,
    XmpPacket,
    XmpProperty,
    XmpStruct,
)

Paso 3: Crear un paquete y establecer propiedades simples

XmpPacket() comienza con una lista de campos vacía. set_value(prefix, name, value, uri=...) establece o reemplaza una propiedad simple, y get(prefix_or_uri, name) la lee de nuevo como un XmpField; get acepta tanto el prefijo registrado como el URI completo del espacio de nombres:

from aspose_pdf import XmpPacket

packet = XmpPacket()
packet.set_value("dc", "title", "Quarterly Report")
packet.set_value(
    "dc", "creator", "Automation Pipeline",
    uri="http://purl.org/dc/elements/1.1/",
)

title_field = packet.get("dc", "title")
print(title_field.value)  # "Quarterly Report"

Paso 4: Almacenar valores tipados

XmpPacket proporciona accesores de conveniencia tipados que convierten hacia y desde la forma de texto plano que XMP almacena internamente: set_date/get_date, set_int/get_int, set_real/get_real, y set_bool/get_bool. Cada getter devuelve None cuando la propiedad está ausente en lugar de lanzar:

from datetime import datetime

packet.set_date("xmp", "CreateDate", datetime(2026, 7, 29, 9, 30))
packet.set_int("pdf", "PageCount", 42)
packet.set_real("custom", "ConfidenceScore", 0.97, uri="https://example.com/ns/custom/1.0/")
packet.set_bool("custom", "IsFinal", True, uri="https://example.com/ns/custom/1.0/")

print(packet.get_date("xmp", "CreateDate"))
print(packet.get_int("pdf", "PageCount"))
print(packet.get_real("custom", "ConfidenceScore"))
print(packet.get_bool("custom", "IsFinal"))

Paso 5: Almacenar texto localizado y matrices ordenadas

set_localized_text escribe una propiedad alternativa de idioma (rdf:Alt) como dc:title en un idioma específico, por defecto "x-default". set_array escribe una matriz ordenada (Seq), desordenada (Bag) o alternativa (Alt) a partir de una lista simple de valores, y get_array la lee de nuevo como una lista de cadenas:

packet.set_localized_text("dc", "description", "Quarterly summary", lang="en")
packet.set_localized_text(
    "dc", "description", "Resumen trimestral",
    uri="http://purl.org/dc/elements/1.1/", lang="es",
)
print(packet.get_localized_text("dc", "description", lang="es"))

packet.set_array("dc", "subject", ["finance", "quarterly", "internal"], kind="Bag")
print(packet.get_array("dc", "subject"))

Paso 6: Construir valores estructurados con XmpStruct

Algunas propiedades XMP (registros de dimensiones, entradas de historial) son valores estructurados en lugar de texto simple. Construye uno con XmpStruct, agrega XmpField objetos miembro con add, y lee un miembro de nuevo por nombre con get, luego adjunta la estructura al paquete con add:

dimensions = XmpStruct()
dimensions.add(XmpField(prefix="stDim", name="w", value="612"))
dimensions.add(XmpField(prefix="stDim", name="h", value="792"))
dimensions.add(XmpField(prefix="stDim", name="unit", value="pt"))

print(dimensions.get("w").value)  # "612"

packet.add(XmpField(prefix="xmpTPg", name="MaxPageSize", value=dimensions))

Paso 7: Registrar espacios de nombres XMP personalizados

NamespaceProvider viene precargado con los espacios de nombres XMP estándar (Dublin Core, Adobe XMP, PDF y otros). Llama a register(prefix, uri) para añadir una asignación personalizada — devuelve el propio proveedor para que las llamadas puedan encadenarse — y luego adjunta el proveedor a un paquete para que la serialización pueda resolver el prefijo personalizado a su URI:

from aspose_pdf import NamespaceProvider, XmpPacket

provider = NamespaceProvider()
provider.register("custom", "https://example.com/ns/custom/1.0/")

packet = XmpPacket(namespace_provider=provider)
packet.set_value("custom", "batch_id", "run-2026-07-29")

Paso 8: Analizar y serializar un paquete

XmpPacket.parse(data, provider=...) lee los bytes o texto sin procesar de un paquete XMP en un XmpPacket. serialize() y to_bytes() renderizan el paquete de nuevo a bytes de paquete XMP:

xmp_bytes = packet.to_bytes()

restored = XmpPacket.parse(xmp_bytes)
print(restored.get("dc", "title").value)

Problemas comunes y soluciones

get() o un getter tipado devuelve None aunque acabo de establecer la propiedad

Confirme que el argumento prefix (o URI) para get/get_int/get_date/etc. coincida exactamente con lo que se pasó a la llamada correspondiente de set_* — get coincide tanto con el prefijo del campo como con su URI de espacio de nombres, pero un prefijo personalizado no coincidente en una llamada y un URI en la otra no coincidirán.

El prefijo de espacio de nombres personalizado produce una salida incompleta al serializarse

Un prefijo que no sea uno de los espacios de nombres XMP estándar (dc, xmp, pdf y similares) necesita ya sea un argumento uri= explícito en cada llamada a set_*, o un NamespaceProvider con ese prefijo registrado y adjunto al paquete mediante XmpPacket(namespace_provider=provider) antes de llamar a serialize()/to_bytes().

get_bool, get_int, o get_real returns None para un valor que sé que está establecido

Estos getters tipados devuelven None cuando el texto almacenado no se analiza como el tipo esperado — por ejemplo, get_int en una propiedad cuyo valor se estableció con set_value como texto libre en lugar de set_int. Utilice el setter tipado correspondiente (set_int, set_real, set_bool) para que la lógica de análisis del getter coincida con la ruta de escritura.

XmpPacket.parse raises ValueError en un paquete de una fuente no confiable

parse rechaza cualquier declaración de <!DOCTYPE o <!ENTITY como una defensa contra entradas hostiles contra ataques XXE y de mil millones de risas. Un paquete XMP legítimo nunca necesita un DTD; trate la excepción como una señal de que el flujo de origen está malformado o no es seguro, en lugar de intentar eludir la defensa.

get_array returns None en lugar de una lista

get_array devuelve None cuando la propiedad nombrada no existe o no se almacenó como un XmpArray (por ejemplo, si se estableció con set_value en lugar de set_array). Use set_array al escribir la propiedad para que el valor almacenado coincida con la forma que get_array espera.

Preguntas frecuentes

¿Cuál es la diferencia entre set_value y los setters tipados como set_int?

set_value almacena lo que sea que value pases tal cual. Los setters tipados (set_date, set_int, set_real, set_bool) convierten su entrada a la forma de texto plano que XMP usa internamente y se emparejan con un getter tipado correspondiente que lo vuelve a analizar, así que úsalos cuando necesites seguridad de tipo de ida y vuelta en lugar de cadenas crudas.

¿En qué se diferencian los tipos de XmpArray (Bag, Seq, Alt)?

Bag es un conjunto no ordenado de valores, Seq es una lista ordenada, y Alt contiene valores alternativos (con mayor frecuencia alternativas de idioma, como las usadas por set_localized_text). Pase el tipo deseado a set_array(..., kind="Bag") o construya un XmpArray(kind=...) directamente.

¿Puedo leer la lista sin procesar de propiedades de un paquete sin conocer sus nombres de antemano?

Sí — itere packet.fields, que contiene cada XmpField, XmpArray y XmpProperty añadidos al paquete en orden de inserción.

¿Necesito un NamespaceProvider para los espacios de nombres estándar de XMP?

No. dc, xmp, pdf y los demás prefijos estándar se resuelven automáticamente. Sólo se necesita un NamespaceProvider para los prefijos personalizados que inventes tú mismo.

¿En qué se diferencian los calificadores XmpProperty de un XmpField simple?

XmpProperty envuelve un XmpField base junto con una lista de campos calificador (añadidos con add_qualifier), utilizado para el caso más raro en el que una propiedad necesita metadatos RDF adicionales adjuntos a su valor, más allá del calificador xml:lang que XmpField.language ya cubre.

Ver también

 Español