Como Trabalhar com Dados PDF em Python

Como Trabalhar com Dados PDF em Python

Aspose.PDF FOSS para Python representa os metadados XMP de um documento PDF como um modelo de dados em memória construído a partir dos objetos XmpPacket, XmpField, XmpArray, XmpStruct e XmpProperty, com NamespaceProvider resolvendo prefixos de namespace para URIs. Isso permite ler, criar e reescrever metadados estruturados — títulos, datas, listas de palavras-chave, campos personalizados — sem escrever manualmente RDF/XML. A biblioteca é pura Python e é instalada com o comando abaixo.

Guia passo a passo

Passo 1: Instalar o pacote

Instale o pacote Aspose.PDF FOSS:

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

Verifique a instalação importando XmpPacket e imprimindo uma mensagem de confirmação:

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

Etapa 2: Importar Classes Necessárias

Importe as classes do modelo de dados XMP e o resolvedor de namespace:

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

Etapa 3: Criar um Pacote e Definir Propriedades Simples

XmpPacket() começa com uma lista de campos vazia. set_value(prefix, name, value, uri=...) define ou substitui uma propriedade simples, e get(prefix_or_uri, name) a lê de volta como um XmpField; get aceita tanto o prefixo registrado quanto o URI completo do namespace:

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"

Etapa 4: Armazenar Valores Tipados

XmpPacket fornece acessores de conveniência tipados que convertem de e para a forma de texto simples que o XMP armazena internamente: set_date/get_date, set_int/get_int, set_real/get_real e set_bool/get_bool. Cada getter retorna None quando a propriedade está ausente ao invés de lançar:

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

Etapa 5: Armazenar Texto Localizado e Arrays Ordenados

set_localized_text grava uma propriedade de alternativa de idioma (rdf:Alt) como dc:title em um idioma específico, padrão para "x-default". set_array grava um array ordenado (Seq), não ordenado (Bag) ou alternativo (Alt) a partir de uma lista simples de valores, e get_array o lê de volta como uma lista de strings:

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

Etapa 6: Construir valores estruturados com XmpStruct

Algumas propriedades XMP (registros de dimensão, entradas de histórico) são valores estruturados em vez de texto simples. Construa um com XmpStruct, adicione objetos membro XmpField com add, e leia um membro de volta pelo nome com get, então anexe a estrutura ao pacote com 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))

Etapa 7: Registrar namespaces XMP personalizados

NamespaceProvider vem pré-carregado com os namespaces XMP padrão (Dublin Core, Adobe XMP, PDF e outros). Chame register(prefix, uri) para adicionar um mapeamento personalizado — ele retorna o próprio provedor para que as chamadas possam ser encadeadas — então anexe o provedor a um pacote para que a serialização possa resolver o prefixo personalizado para sua 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")

Etapa 8: Analisar e serializar um pacote

XmpPacket.parse(data, provider=...) lê bytes ou texto brutos de um pacote XMP para um XmpPacket. serialize() e to_bytes() ambos renderizam o pacote de volta para bytes de pacote XMP:

xmp_bytes = packet.to_bytes()

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

Problemas comuns e soluções

get() ou um getter tipado retorna None mesmo que eu tenha acabado de definir a propriedade

Confirme se o argumento prefix (ou URI) para get/get_int/get_date/etc. corresponde exatamente ao que foi passado para a chamada correspondente de set_* — get corresponde tanto ao prefixo do campo quanto ao seu URI de namespace, mas um prefixo personalizado incompatível em uma chamada e um URI na outra resultarão em falha.

Prefixo de namespace personalizado produz saída incompleta ao ser serializado

Um prefixo que não seja um dos namespaces XMP padrão (dc, xmp, pdf e similares) precisa de um argumento uri= explícito em cada chamada de set_*, ou de um NamespaceProvider com esse prefixo registrado e anexado ao pacote via XmpPacket(namespace_provider=provider) antes de chamar serialize()/to_bytes().

get_bool, get_int, ou get_real returns None para um valor que eu sei que está definido

Esses getters tipados retornam None quando o texto armazenado não é analisado como o tipo esperado — por exemplo, get_int em uma propriedade cujo valor foi definido com set_value como texto livre em vez de set_int. Use o setter tipado correspondente (set_int, set_real, set_bool) para que a lógica de análise do getter corresponda ao caminho de escrita.

XmpPacket.parse raises ValueError em um pacote de uma fonte não confiável

parse rejeita qualquer declaração de <!DOCTYPE ou <!ENTITY como uma proteção contra entrada hostil contra ataques XXE e de bilhões de risadas. Um pacote XMP legítimo nunca precisa de um DTD; trate a exceção como um sinal de que o fluxo de origem está malformado ou inseguro, em vez de contornar a proteção.

get_array returns None em vez de uma lista

get_array retorna None quando a propriedade nomeada não existe ou não foi armazenada como um XmpArray (por exemplo, se foi definida com set_value em vez de set_array). Use set_array ao escrever a propriedade para que o valor armazenado corresponda ao formato que get_array espera.

Perguntas Frequentes

Qual é a diferença entre set_value e os setters tipados como set_int?

set_value armazena qualquer value que você passar como está. Os setters tipados (set_date, set_int, set_real, set_bool) convertem sua entrada para a forma de texto simples que o XMP usa internamente e são combinados com um getter tipado correspondente que a analisa novamente, portanto use-os quando precisar de segurança de tipo em ida e volta em vez de strings brutas.

Como os tipos de XmpArray (Bag, Seq, Alt) diferem?

Bag é um conjunto não ordenado de valores, Seq é uma lista ordenada e Alt contém valores alternativos (mais frequentemente alternativas de idioma, como usado por set_localized_text). Passe o tipo desejado para set_array(..., kind="Bag") ou crie um XmpArray(kind=...) diretamente.

Posso ler a lista bruta de propriedades em um pacote sem conhecer seus nomes antecipadamente?

Sim — itere packet.fields, que contém cada XmpField, XmpArray e XmpProperty adicionados ao pacote na ordem de inserção.

Preciso de um NamespaceProvider para os namespaces XMP padrão?

Não. dc, xmp, pdf e os outros prefixos padrão são resolvidos automaticamente. Um NamespaceProvider só é necessário para prefixos personalizados que você inventa.

Como os qualificadores XmpProperty diferem de um XmpField simples?

XmpProperty envolve um XmpField base junto com uma lista de campos de qualificador (adicionados com add_qualifier), usado para o caso mais raro em que uma propriedade por si só precisa de metadados RDF adicionais anexados ao seu valor, além do qualificador xml:lang que XmpField.language já cobre.

Ver também

 Português