تخطَّ إلى المحتوى
getnextpdf.com

ملف PDF حاوية: الملفات المُضمَّنة والبيانات المرتبطة

Spec: ISO 32000-2, §7.11.4Spec: ISO 32000-2, §14.13Spec: ISO 19005-3, PDF/A-3

يتصور معظم الناس ملف ⁨PDF⁩ كومةً من الصفحات. هذا هو الجزء الذي تراه. لكن ملف ⁨PDF⁩ حاوية أيضاً، ويمكنه حمل ملفات كاملة أخرى بداخله — جدول بيانات، أو حمولة ⁨XML⁩، أو المستند المصدري الأصلي — مُحزَّمةً داخل الملف الواحد ذاته الذي تسلّمه إلى شخص آخر.

تشرح هذه الصفحة كيف يعمل ذلك: دفق الملف المُضمَّن الذي يخزّن البايتات، وشجرة الأسماء التي تسردها، والمفتاح الواحد الذي يقرر ما إذا كان المرفق مجرد موجود أم يعني شيئاً فعلاً.

يبدو المرفق غير المصنَّف والمرفق المصنَّف متطابقين للإنسان. كلاهما ملف يستقلّ داخل ملف ⁨PDF⁩، وكلاهما — في هذا المحرك — مرتبط بالمستند. الفرق أن أحدهما يخبر الآلة بالغرض منه، والآخر يترك العلاقة فارغة كي تخمّنها الآلة.

ذلك الفرق هو كل اللعبة في فاتورة إلكترونية هجينة. لا تقرأ منصة الضرائب صفحة فاتورتك؛ بل تقرأ ⁨XML⁩ الذي ضمَّنته. فإن أُرفِق ذلك الـ⁨XML⁩ كتلةً غير متمايزة بدلاً من كونه بيانات الفاتورة للمستند المرئي، فلن يكون لدى القارئ المطابق طريقة موثوقة لمعرفة أنه الحمولة الواجب معالجتها. تبدو الصفحة مثالية. فتُرفَض الفاتورة. ويصل الإخفاق بعد أيام، وخلفه دفعة محتجزة.

ضبط العلاقة على نحو صحيح، في الطبقة التي تنتج الملف، أرخص بكثير من اكتشافه فاتورةً مرفوضةً تلو الأخرى.

  • يستطيع ملف ⁨PDF⁩ تضمين بايتات أي ملف بوصفه دفق ملف مُضمَّن (Spec: ISO 32000-2, §7.11.4). يحمل الدفق البيانات إضافةً إلى قاموس معاملات صغير: الحجم الأصلي والتواريخ ومجموع تحقق.
  • تُفهرَس الملفات المُضمَّنة في شجرة الأسماء EmbeddedFiles، كي يستطيع القارئ تعدادها بالاسم دون مسح المستند كله.
  • يمضي الملف المرتبط خطوة أبعد: فيعلن AFRelationship (Spec: ISO 32000-2, §7.11.3) — واحدة من ثماني قيم معيارية (Source، Data، Alternative، Supplement، EncryptedPayload، FormData، Schema، Unspecified)، أو قيمة مخصَّصة — تذكر كيف يتصل الملف بالمحتوى المرفق به.
  • تلك العلاقة المصنَّفة هي الآلية وراء الفواتير الإلكترونية الهجينة (⁨ZUGFeRD⁩ / ⁨Factur-X⁩) ومرفقات ⁨PDF⁩/A-3 (Spec: ISO 19005-3, PDF/A-3).
  • يدعم ⁨NextPDF⁩ بدائيات الحاوية الخام في النواة: embedFile() وembedFileFromString() بعلاقة صريحة. تضيف الإصدارات المتقدمة مُضمِّن الفواتير الإلكترونية المخصَّص EN 16931 / ⁨ZUGFeRD⁩ / ⁨Factur-X⁩ فوق هذه البدائيات.

فكّر فيه طبقتين مرصوصتين إحداهما فوق الأخرى.

الطبقة الدنيا هي التخزين. دفق الملف المُضمَّن (Spec: ISO 32000-2, §7.11.4) هو بايتات الملف الأصلي ملفوفةً في كائن دفق ⁨PDF⁩، مع قاموس معاملات يسجّل الحجم الأصلي وتاريخ التعديل ومجموع تحقق للبيانات غير المضغوطة. يُبلَغ الدفق عبر قاموس مواصفة ملف يشير قاموس /EF فيه إلى دفق الملف المُضمَّن — فالدفق نفسه لا يحمل /EF. يستطيع القارئ سحب الملف خارجاً بايتاً ببايت. ولجعل هذه الملفات قابلة للعثور عليها، يحمل فهرس المستند شجرة أسماء EmbeddedFiles — خريطة مرتَّبة من اسم إلى كل مواصفة ملف — كي يستطيع العارض سرد “إليك الملفات الثلاثة داخل هذا الـ⁨PDF⁩” دون السير عبر كل صفحة.

الطبقة العليا هي المعنى. الملف المُضمَّن وحده مجرد موجود. تربط آلية الملفات المرتبطة (Spec: ISO 32000-2, §14.13) ملفاً بشيء ما — المستند بأكمله، أو صفحة، أو كائن رسومي — وتختمه بـAFRelationship. يعرّف ISO 32000-2 مفردات صغيرة من ثماني قيم معيارية (Spec: ISO 32000-2, §7.11.3)، ويسمح بقيم مخصَّصة أيضاً؛ وتجيب كل قيمة معيارية عن سؤال دقيق:

AFRelationshipما تؤكده عن الملف
Sourceهذه هي المادة المصدرية التي وُلِّد منها المحتوى المرئي (على سبيل المثال، مستند معالج النصوص الأصلي).
Dataهذه بيانات منظَّمة مرتبطة بالمحتوى المرئي — والحالة النموذجية هي ⁨XML⁩ الفاتورة خلف صفحة فاتورة مُعروضة.
Alternativeهذا تمثيل بديل للمحتوى نفسه (على سبيل المثال، نسخة صوتية أو مرئية).
Supplementهذه مادة تكميلية تمدّد المحتوى لكنها ليست جزءاً منه.
EncryptedPayloadالملف المُضمَّن حمولة مشفَّرة يلفّها ⁨PDF⁩ كتلةً معتمة.
FormDataالملف بيانات نموذج (FDF أو XFDF أو حمولة نموذج ⁨XML⁩).
Schemaالملف مخطَّط يصف بنية ملف Data (على سبيل المثال، XSD لبيانات ⁨XML⁩ أو JSON Schema).
Unspecifiedالعلاقة غير مذكورة عمداً. صادقة، لكنها لا تخبر الآلة بشيء.

وراء هذه الثماني، يسمح المعيار أيضاً بقيم علاقة مخصَّصة خاصة بالتطبيق، فالمفردات قابلة للتوسيع لا ثابتة.

يُعرَّف الملف المرتبط بشيئين يعملان معاً، لا بمفتاح واحد وحده. يقوم الارتباط /AF بـربط مواصفة الملف بجزء من المستند؛ ثم يقوم مفتاح AFRelationship في مواصفة الملف بـذكر العلاقة الدلالية. مدخل /AF على نقطة الارتباط (فهرس المستند، أو صفحة، أو كائن) هو مصفوفة — تحتوي تلك المصفوفة على مواصفة ملف واحدة أو أكثر، عادةً كمراجع غير مباشرة؛ فـ/AF ليس مرجعاً مفرداً. الملف المرتبط على مستوى المستند هو مواصفة الملف المُدرَجة في مصفوفة /AF بفهرس المستند، حاملةً AFRelationship خاصتها. ضع علامة Unspecified على جدول البيانات ذاك، وتكون قد ربطته بالمستند لكنك لم تخبر الآلة بشيء عن السبب. ضع علامة Data على جدول البيانات نفسه، وتكون قد أخبرت كل قارئ مطابق ما هو وما الغرض منه. البايتات هي نفسها. الدلالات ليست كذلك.

ولهذا فإن حالة الفاتورة الإلكترونية ليست “أرفق ملف ⁨XML⁩”. بل هي “ضمِّن هذا الـ⁨XML⁩ بوصفه الملف المرتبط Data لهذا المستند، داخل حامل ⁨PDF⁩/A-3 مطابق” — مع بقاء صلاحية الفاتورة والقبول القانوني فحصين منفصلين لا يؤديهما الحامل. للتدفق أربع مراحل، والترتيب هو ما يبقيه صحيحاً.

  1. Store the bytesThe file is wrapped in an embedded file stream with its size, dates, and a checksum (ISO 32000-2 §7.11.4).
  2. Register it by nameThe file specification is added to the EmbeddedFiles name tree so a reader can enumerate attachments without scanning the document.
  3. Declare the relationshipAn AFRelationship value (one of the eight standard values such as Source or Data) marks how the file relates to the content, associated at the document level (ISO 32000-2 §14.13.3).
  4. Make it archivalA PDF/A-3 carrier permits the embedded payload to ride inside one conforming archival PDF/A document; invoice validity and legal acceptance remain separate checks (ISO 19005-3).
كيف يصير المرفق المصنَّف ملفاً هجيناً من البداية إلى النهاية: يخزّن المحرك البايتات، ويسجّل الملف بالاسم، ويعلن العلاقة، ويسمح ملف الأرشفة بأن يستقلّ ذلك كله داخل مستند أرشفة مطابق واحد.

تلك المرحلة الرابعة هي سبب وجود ⁨PDF⁩/A-3 ملفاً تعريفياً متميزاً. قيّدت ملفات الأرشفة الأسبق ما يمكن تضمينه؛ أما ⁨PDF⁩/A-3 (Spec: ISO 19005-3, PDF/A-3) فهو الجزء الذي يسمح بأن تستقلّ ملفات من أي تنسيق داخل مستند أرشفة مطابق. إنه يسمح بالحمولة المُضمَّنة — لا يصادق عليها ولا يمنح حالة قانونية. بدونه، لا يمكن للفاتورة الهجينة — ملف واحد هو في آنٍ الصفحة التي يقرؤها شخص والبيانات التي يحلّلها نظام ضريبي — أن تكون مستند أرشفة ⁨PDF⁩/A مطابقاً على الإطلاق؛ أما كون الفاتورة صالحة ومقبولة قانونياً فيظل سؤالاً منفصلاً. مُضمِّن الفواتير الإلكترونية المخصَّص الذي تضيفه الإصدارات المتقدمة هو طبقة الراحة فوق هذا تماماً: فهو يضمّن الحمولة، ويضبط العلاقة إلى Data، ويسجّلها على نحو صحيح، كي لا تجمّع سباكة الحاوية يدوياً. تستقر آليات الفوترة والأرشفة الأعمق في الصفحتين المجاورتين المرتبطتين أدناه؛ أما هذه الصفحة فعن الحاوية التي تقومان عليها معاً.

برنامج صغير كامل. النداءان المهمان هما الفرق بين ملف مرتبط غير مصنَّف وآخر مصنَّف — والعلاقة وسيطة صريحة ينبغي أن تضبطها. في هذا المحرك، يُنتِج كلا النداءين ملفاً مرتبطاً: فـembedFile() وembedFileFromString() يسجّلان دائماً مواصفة الملف في مصفوفة /AF بفهرس المستند، فالشيء الوحيد الذي تغيّره العلاقة هو ما يعنيه الارتباط. تتخذ العلاقة قيمتها الافتراضية Unspecified، التي تربط الملف لكنها لا تخبر الآلة بشيء عن السبب؛ ولحمولة فاتورة إلكترونية تضبطها إلى Data كي يتمكن القارئ من العثور عليها.

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
use NextPDF\Navigation\AFRelationship;
$document = Document::createStandalone();
$document->addPage();
$document->setFont('helvetica', 'B', 16);
$document->cell(0, 12, 'Invoice INV-2026-0042', newLine: true);
// An UNTYPED associated file: the bytes are embedded AND the file spec is
// added to the document catalog's /AF array, but the relationship says
// nothing about why. A reader can open it; a machine cannot tell its role.
// The relationship is left Unspecified (its default); the second argument is
// the human-readable description. embedFile accepts the AFRelationship enum.
$document->embedFile(
'/srv/invoices/INV-2026-0042-source.docx',
'Original source document',
AFRelationship::Unspecified,
);
// A TYPED associated file: the invoice XML is declared as the DATA behind
// the visible page. This is the relationship a hybrid e-invoice reader
// looks for — the same intent the dedicated e-invoice embedder sets.
// embedFileFromString takes the data, a filename, a description, and a
// relationship as a PDF-name string ('/Data').
$invoiceXml = $generateCiiXml(); // your ERP authors this; the engine never does
$document->embedFileFromString(
$invoiceXml,
'factur-x.xml',
'Factur-X invoice data',
'/Data',
);
$bytes = $document->getPdfData();

علاقة '/Data' لا تُخطئ. أما المرفق الأول — المتروك Unspecified — فمرتبط رغم ذلك، لكن من دون معنى مذكور. لأجل كلا النداءين، يكتب المحرك دفق الملف المُضمَّن، ويضيف الملف إلى شجرة الأسماء EmbeddedFiles، ويسرد مواصفة ملفه في مصفوفة /AF بفهرس المستند، ويسجّل العلاقة التي ذكرتها — لا يختار واحدة نيابةً عنك. لا يملك هذا المحرك وضع شجرة-أسماء-فقط: فكل ملف تضمّنه بهذه الطريقة هو ملف مرتبط بالمستند، فالعلاقة هي الرافعة الوحيدة التي تتحكم فيها.

الافتراض المتكرر هو أن “مُضمَّن” و”مرتبط” كلمتان للشيء نفسه. وهما ليستا كذلك. مُضمَّن عن التخزين — البايتات داخل الـ⁨PDF⁩. مرتبط عن الربط — مواصفة الملف مسرودة في مصفوفة /AF على جزء من المستند، وهي تحمل AFRelationship. في نموذج ⁨PDF⁩ المجرَّد يمكن تضمين ملف في شجرة الأسماء دون أن يُربَط أبداً؛ لكن مسار embedFile() في ⁨NextPDF⁩ لا يتركه هناك — بل يكتب دائماً الارتباط /AF — لذا فالسؤال المفتوح في هذا المحرك ليس أبداً ما إذا كان ملف مرتبطاً بل ما الذي تذكره العلاقة.

فخ ثانٍ: افتراض أن العارض سيستنتج أي مرفق هو الفاتورة. لا يُفترَض في القارئ المطابق أن يخمّن. بل يبحث عن الملف الذي تذكر علاقته Data. اترك العلاقة Unspecified وتكون قد ربطت الحمولة بينما لم تخبر الآلة بشيء مفيد عن دورها.

آلية الحاوية قوية على نحو يستحق الصدق بشأنه: يقرأ embedFile() أي مسار تستطيع عملية ⁨PHP⁩ قراءته. هذه هي الميزة — وهي أيضاً الحدّ. يرفق المحرك البايتات التي يُعطاها؛ ولا يقرر، ولا يستطيع أن يقرر نيابةً عنك، ما إذا كان مسار ما هو ما قصدت كشفه.

Embedding a file from a caller-supplied path — edition availability
EditionAvailability
Core

يقرأ embedFile() أي مسار تملك عملية ⁨PHP⁩ صلاحية الوصول إليه ويضمّن بايتاته حرفياً. التحقق من أن المسار آمن ومقصود — لا قيمة يتحكم فيها المستخدم، ولا اجتياز، ولا سرّ خارج نطاق المستند — مسؤولية المُدمِج. هذا عقد أمان موثَّق، لا سهو: لن يخمّن المحرك بصمت أي المسارات مشروعة، لأن ذلك التخمين يخص تطبيقك، الذي يعرف حدّ الثقة الذي لا يستطيع المحرك رؤيته. مرّر البايتات المتأثرة بمهاجِم عبر سلسلة بـembedFileFromString() كي لا تكون طبقة المسار في اللعب أبداً.

ProNot in this edition
EnterpriseNot in this edition

حدّان إضافيان يستحقان الذكر صراحةً:

  • التضمين ليس تصديقاً. يحمل المحرك البايتات التي تعطيه إياها. أما ما إذا كان ⁨XML⁩ المُضمَّن حمولة فاتورة مطابقة فسؤال منفصل، يجيب عنه مدقِّق — راجع صفحة الفوترة.
  • المرفق المصنَّف ليس وحده ملف أرشفة مطابقاً. جعل الملف الهجين مستند ⁨PDF⁩/A-3 قانونياً يتطلب وضع الأرشفة وفحص مطابقة مستقلاً — راجع صفحة الأرشفة.
  • دفق الملف المُضمَّن — كائن دفق ⁨PDF⁩ يحمل بايتات ملف خارجي، مع قاموس معاملات يسجّل حجمه الأصلي وتواريخه ومجموع تحقق (⁨ISO 32000-2⁩ §⁨7.11.4⁩).
  • شجرة الأسماء EmbeddedFiles — الخريطة المرتَّبة في فهرس المستند التي تسرد الملفات المُضمَّنة بالاسم، كي يستطيع القارئ تعداد المرفقات دون مسح المستند كله.
  • الملف المرتبط — ملف مُضمَّن مربوط بجزء من المستند بارتباط /AF (على فهرس المستند، أو صفحة، أو كائن) وحاملاً AFRelationship تذكر كيف يتصل بذلك المحتوى؛ والحالة على مستوى المستند — مواصفة الملف في مصفوفة /AF بالفهرس — هي ما تتمحور حوله هذه الصفحة (⁨ISO 32000-2⁩ §⁨14.13.3⁩).
  • AFRelationship — مفتاح مواصفة الملف الذي تسمّي قيمته العلاقة (⁨ISO 32000-2⁩ §⁨7.11.3⁩). يتخذ واحدة من ثماني قيم معيارية (Source، Data، Alternative، Supplement، EncryptedPayload، FormData، Schema، Unspecified) أو قيمة مخصَّصة؛ وData هي القيمة التي تستخدمها حمولة فاتورة إلكترونية هجينة.
  • ⁨PDF⁩/A-3 — ملف أرشفة ISO 19005-3 الذي يسمح بتضمين ملفات من أي تنسيق، مُمكِّناً مستنداً هجيناً مطابقاً.
  • الفاتورة الهجينة — ملف ⁨PDF⁩ واحد هو في آنٍ صفحة قابلة للقراءة بشرياً وحمولة فاتورة مُضمَّنة قابلة للقراءة آلياً.