چگونه با داده‌های PDF در Python کار کنیم

چگونه با داده‌های PDF در Python کار کنیم

Aspose.PDF FOSS برای Python متادیتای XMP یک سند PDF را به عنوان یک مدل داده‌ای در حافظه که از اشیای XmpPacket، XmpField، XmpArray، XmpStruct و XmpProperty ساخته شده است، نمایان می‌کند، به‌همراه NamespaceProvider که پیشوندهای فضای نام را به URIها تبدیل می‌کند. این امکان را می‌دهد تا متادیتای ساختار یافته — عناوین، تاریخ‌ها، فهرست کلیدواژه‌ها، فیلدهای سفارشی — را بخوانید، بسازید و بازنویسی کنید بدون اینکه RDF/XML را به‌صورت دستی بنویسید. این کتابخانه کاملاً Python است و با فرمان زیر نصب می‌شود.

راهنمای گام‌به‌گام

مرحله ۱: نصب بسته

نصب بسته 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 .

نصب را با وارد کردن XmpPacket و چاپ پیام تأیید بررسی کنید:

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

مرحله ۲: وارد کردن کلاس‌های مورد نیاز

کلاس‌های مدل داده XMP و حل‌کننده فضای‌نام‌ها را وارد کنید:

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

مرحله ۳: ایجاد یک بسته و تنظیم ویژگی‌های ساده

XmpPacket() با یک فهرست فیلد خالی شروع می‌شود. set_value(prefix, name, value, uri=...) یک ویژگی ساده را تنظیم یا جایگزین می‌کند و get(prefix_or_uri, name) آن را به عنوان یک XmpField می‌خواند؛ get یا پیشوند ثبت‌شده یا URI کامل فضای‌نام را می‌پذیرد:

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"

مرحله ۴: ذخیره مقادیر تایپ‌شده

XmpPacket دسترسی‌گرهای راحتی تایپ‌شده‌ای را فراهم می‌کند که بین فرم متن‌ساده‌ای که XMP به‌صورت داخلی ذخیره می‌کند، تبدیل می‌شوند: set_date/get_date, set_int/get_int, set_real/get_real, و set_bool/get_bool. هر getter در صورت عدم وجود ویژگی، به جای ایجاد خطا، None برمی‌گرداند:

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

مرحله ۵: ذخیره متن بومی‌سازی‌شده و آرایه‌های مرتب

set_localized_text یک ویژگی زبان-جایگزین (rdf:Alt) مانند dc:title را در زبانی مشخص می‌نویسد که به‌طور پیش‌فرض "x-default" است. set_array یک آرایهٔ مرتب (Seq)، نامرتب (Bag) یا جایگزین (Alt) را از یک فهرست سادهٔ مقادیر می‌نویسد و get_array آن را به‌عنوان فهرستی از رشته‌ها می‌خواند:

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

مرحله ۶: ساخت مقادیر ساختار یافته با XmpStruct

برخی از ویژگی‌های XMP (رکوردهای ابعاد، ورودی‌های تاریخچه) مقادیر ساختار یافته هستند نه متن ساده. یکی را با XmpStruct بسازید، اشیای عضو XmpField را با add اضافه کنید، و یک عضو را با نام توسط get بخوانید، سپس ساختار را با 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))

مرحله ۷: ثبت فضاهای نام سفارشی XMP

NamespaceProvider پیش‌بارگذاری شده با فضاهای نام استاندارد XMP (Dublin Core، Adobe XMP، PDF و سایرین) است. با فراخوانی register(prefix, uri) یک نگاشت سفارشی اضافه کنید — این فراخوانی خود فراهم‌کننده را برمی‌گرداند تا بتوان زنجیره‌ای از فراخوانی‌ها داشت — سپس فراهم‌کننده را به بسته پیوست کنید تا هنگام سریال‌سازی پیشوند سفارشی به 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")

مرحله ۸: تجزیه و سریال‌سازی یک بسته

XmpPacket.parse(data, provider=...) بایت‌های خام بسته XMP یا متن را به یک XmpPacket می‌خواند. serialize() و to_bytes() هر دو بسته را به بایت‌های بسته XMP بازمی‌گردانند:

xmp_bytes = packet.to_bytes()

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

مشکلات رایج و راه‌حل‌ها

get() یا یک getter تایپ‌شده مقدار را برمی‌گرداند None اگرچه من همین‌اکنون property را تنظیم کرده‌ام

از مطابقت دقیق prefix (یا URI) که به get/get_int/get_date/و غیره ارسال می‌شود، با آنچه به فراخوانی متناظر set_* پاس داده شده است، اطمینان حاصل کنید — get بر روی پیشوند فیلد و URI فضای‌نامی آن هم‌زمان تطبیق می‌شود، اما اگر در یک فراخوانی پیشوند سفارشی متفاوتی و در دیگری URI باشد، تطبیق انجام نمی‌شود.

پیشوند فضای‌نام سفارشی هنگام سریال‌سازی خروجی ناقص تولید می‌کند

پیشوندی که جزو فضاهای‌نام استاندارد XMP (dc، xmp، pdf و مشابه آن‌ها) نیست، یا نیاز به یک آرگومان صریح uri= در هر فراخوانی set_* دارد، یا به یک NamespaceProvider که آن پیشوند در آن ثبت شده و از طریق XmpPacket(namespace_provider=provider) به بسته متصل شده باشد، پیش از فراخوانی serialize()/to_bytes().

get_bool, get_int، یا get_real returns None برای مقداری که می‌دانم تنظیم شده است

این getterهای تایپی هنگامیکه متن ذخیره‌شده به‌عنوان نوع مورد انتظار تجزیه نشود، None را برمی‌گردانند — برای مثال، get_int روی ویژگی‌ای که مقدار آن با set_value به‌صورت متن آزاد تنظیم شده است نه set_int. از setter تایپی متناظر (set_int، set_real، set_bool) استفاده کنید تا منطق تجزیه getter با مسیر نوشتن مطابقت داشته باشد.

XmpPacket.parse raises ValueError در بسته‌ای از منبع غیرقابل اعتماد

parse هر اعلامیه <!DOCTYPE یا <!ENTITY را به‌عنوان حفاظ ورودی مخرب در مقابل حملات XXE و «billion-laughs» رد می‌کند. یک بسته XMP معتبر هرگز به DTD نیاز ندارد؛ استثنا را به‌عنوان سیگنالی در نظر بگیرید که جریان منبع خراب یا ناامن است، نه اینکه دور حفاظ را بگیرید.

get_array returns None به‌جای یک لیست

get_array زمانی که ویژگی نام‌برده یا وجود نداشته باشد یا به‌عنوان XmpArray ذخیره نشده باشد (مثلاً اگر با set_value به‌جای set_array تنظیم شده باشد) None را برمی‌گرداند. هنگام نوشتن ویژگی از set_array استفاده کنید تا مقدار ذخیره‌شده با شکلی که get_array انتظار دارد مطابقت داشته باشد.

سوالات متداول

تفاوت بین set_value و تنظیم‌کننده‌های تایپی مانند set_int چیست؟

set_value هر آنچه value را به‌صورت مستقیم پاس می‌دهید، ذخیره می‌کند. تنظیم‌کننده‌های تایپی (set_date، set_int، set_real، set_bool) ورودی را به فرم متن ساده‌ای که XMP به‌صورت داخلی استفاده می‌کند، تبدیل می‌کنند و با یک گیرندهٔ تایپی متناظر که آن را دوباره تجزیه می‌کند، جفت می‌شوند، بنابراین زمانی که به ایمنی نوع در دورهٔ کامل نیاز دارید (به‌جای رشته‌های خام) از آن‌ها استفاده کنید.

انواع XmpArray (Bag، Seq، Alt) چگونه متفاوت هستند؟

Bag یک مجموعهٔ نامرتب از مقادیر است، Seq یک لیست مرتب است، و Alt مقادیر جایگزین (اغلب جایگزین‌های زبانی، همان‌طور که در set_localized_text استفاده می‌شود) را نگه می‌دارد. نوع موردنظر را به set_array(..., kind="Bag") پاس کنید یا به‌صورت مستقیم یک XmpArray(kind=...) بسازید.

آیا می‌توانم فهرست خام ویژگی‌ها را در یک بسته بدون دانستن نام‌های آن‌ها از پیش بخوانم؟

بله — packet.fields را تکرار کنید، که هر XmpField، XmpArray و XmpProperty افزوده‌شده به بسته را به ترتیب وارد شدن نگه می‌دارد.

آیا برای فضای‌نام‌های استاندارد XMP به یک NamespaceProvider نیاز دارم؟

نه. dc، xmp، pdf و سایر پیشوندهای استاندارد به‌صورت خودکار حل می‌شوند. یک NamespaceProvider فقط برای پیشوندهای سفارشی که خودتان می‌سازید لازم است.

محدودکننده‌های XmpProperty چگونه با یک XmpField ساده متفاوت هستند؟

XmpProperty یک XmpField پایه را به همراه فهرستی از فیلدهای محدودکننده (که با add_qualifier اضافه می‌شوند) می‌پیچد، برای مورد نادرتر که یک ویژگی خودش به متادیتای RDF اضافی متصل به مقدارش نیاز دارد، فراتر از محدودکننده xml:lang که XmpField.language قبلاً پوشش می‌دهد.

همچنین ببینید:

 فارسی