مقال تقني

مخططات توسيع PDF/A-3 لبيانات Factur-X XMP الوصفية في Delphi

لقد قمت بإنشاء فاتورة Factur-X وتجاوزت جميع فحوصات الحاوية (container). يحتوي الكتالوج على مصفوفة /AF، وتحل شجرة أسماء EmbeddedFiles إلى مواصفة الملف الصحيحة، ويحتوي factur-x.xml المضمن على /AFRelationship صحيح بقيمة Alternative، وتُرجع الدالة المضمنة ValidateFacturXInvoice القيمة 1. ثم تقوم بتشغيل الملف نفسه عبر veraPDF، وهو المدقق المرجعي الذي تستخدمه البوابات الضريبية، فيحكم بأن المستند بأكمله ليس PDF/A-3 صالحاً. البنية صحيحة، لكن البيانات الوصفية هي المشكلة، وهذا الفشل هو من أسهل الأخطاء التي يمكن التغاضي عنها في مسار عمل الفواتير الإلكترونية بأكمله

يستحق السبب الفهم الكامل، لأنه يشرح فئة من عيوب PDF/A لا علاقة لها بالصفحة المرئية أو المرفق، بل تتعلق كلياً بكيفية وصف XMP لنفسه. هذا هو الفخ الذي يختبئ وراء فحص الحاوية الأخضر (الناجح)

الخصائص الأربع التي تتسبب في فشل الملف

تكتب فاتورة Factur-X أربع خصائص مخصصة في حزمة XMP الخاصة بها حتى تتمكن البرامج اللاحقة من قراءة ملف تعريف الفاتورة دون تحليل XML المضمن. توجد هذه الخصائص في مساحة أسماء Factur-X تحت البادئة fx وهي: fx:DocumentFileName و fx:DocumentType و fx:Version و fx:ConformanceLevel. إنها بالضبط البيانات الوصفية التي يحتاجها القارئ لمعرفة أن ملف PDF هذا يحمل فاتورة EN 16931 باسم factur-x.xml بالإصدار 1.0

لا تُعد أي من هذه الخصائص الأربع جزءاً من أي مخطط XMP يحدده PDF/A مسبقاً. تُعرف مخططات تعريف Dublin Core و XMP Basic و PDF و PDF/A للقارئ المتوافق، لكن fx: غير معروفة. عندما يتنقل veraPDF عبر XMP ويصل إلى خاصية لا يتعرف على مساحة أسمائها، فإنه يبحث عن تصريح يخبره بمعنى الخاصية. في حال غياب هذا التصريح، فإنه يُبلغ عن فشل مقابل البند 6.6.2.3.1 من معيار ISO 19005-3، والذي يتطلب أن يتم وصف كل خاصية لم يتم سحبها من مخطط محدد مسبقاً في مخطط توسيع PDF/A. أربع خصائص غير مصرح عنها تعني أربع طرق لرفض الملف، ولا تظهر أي منها في فحص الحاوية

لماذا يرفض PDF/A أي خاصية مخصصة مجردة

تبدو القاعدة متحذلقة حتى تتذكر الغرض من PDF/A. يوجد هذا التنسيق بحيث يمكن فتح ملف وفهمه بعد عقود من الآن، بواسطة برامج لم يتم إخبارها مطلقاً باتفاقيات عام 2026. يُتوقع من القارئ المتوافق أن يفهم المستند من المستند نفسه فقط، دون وجود أي سجل خارجي للرجوع إليه

تكسر البيانات الوصفية المخصصة هذا الوعد ما لم يحمل الملف وصفه الخاص. بالنظر إلى خاصية fx:ConformanceLevel مجردة، لا يمكن لقارئ مستقبلي معرفة عنوان URI لمساحة الأسماء الذي ترتبط به البادئة fx، أو ما إذا كانت القيمة نصاً أو تاريخاً أو عدداً صحيحاً، أو ما إذا كانت الخاصية تصف المستند نفسه أو مورداً خارجياً. تُغلق آلية مخطط توسيع PDF/A هذه الفجوة. إنها تتيح للملف أن يصرح، في بنية XMP ثابتة، عن مساحة الأسماء والبادئة، ولكل خاصية نوع قيمة وفئة من internal (داخلي) أو external (خارجي). بمجرد وجود هذا التصريح، تصبح الخاصية واصفة لنفسها، ويتم استيفاء البند 6.6.2.3.1. وبدونه، لا يملك المدقق خياراً سوى التعامل مع الخاصية على أنها غير مفهومة وإفشال الملف. يهم التمييز بين الفئات هنا: تصف خصائص الفاتورة مثل هذه البيانات التي تأتي من خارج معالج PDF، لذلك يُصرح عنها كـ external بدلاً من internal

ما يحتويه تصريح مخطط التوسيع

التصريح عبارة عن rdf:Description في حزمة XMP يستخدم مساحات الأسماء الثلاثة المعرفة بواسطة AIIM وهي pdfaExtension و pdfaSchema و pdfaProperty. داخل حقيبة (bag) pdfaExtension:schemas يوجد إدخال مخطط واحد يُسمي مخطط Factur-X، ويعطي pdfaSchema:namespaceURI و pdfaSchema:prefix الخاصين به، ثم يُدرج الخصائص الأربع في تسلسل (sequence) pdfaSchema:property. تحمل كل خاصية اسماً، و pdfaProperty:valueType من نوع Text، و pdfaProperty:category من نوع external. يُظهر الترميز التوضيحي أدناه شكل تلك الكتلة

<rdf:Description rdf:about=""
    xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
    xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
    xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">
  <pdfaExtension:schemas>
    <rdf:Bag>
      <rdf:li rdf:parseType="Resource">
        <pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema>
        <pdfaSchema:namespaceURI>urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#</pdfaSchema:namespaceURI>
        <pdfaSchema:prefix>fx</pdfaSchema:prefix>
        <pdfaSchema:property>
          <rdf:Seq>
            <rdf:li rdf:parseType="Resource">
              <pdfaProperty:name>DocumentFileName</pdfaProperty:name>
              <pdfaProperty:valueType>Text</pdfaProperty:valueType>
              <pdfaProperty:category>external</pdfaProperty:category>
              <pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>
            </rdf:li>
            <!-- DocumentType, Version, ConformanceLevel declared the same way -->
          </rdf:Seq>
        </pdfaSchema:property>
      </rdf:li>
    </rdf:Bag>
  </pdfaExtension:schemas>
</rdf:Description>

عنوان URI لمساحة الأسماء والبادئة ليسا سلسلتين ثابتتين، بل يتبعان ملف التعريف (profile). يستخدم مستند Factur-X urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# مع البادئة fx، بينما يحل ملف ZUGFeRD 2.0 المحدد عبر zugferd-invoice.xml إلى عنوان URI مختلف تحت اسم المخطط الخاص به. يجب أن يصرح مخطط التوسيع عن نفس عنوان URI لمساحة الأسماء الذي تستخدمه كتلة الخصائص فعلياً، وإلا فإن المدقق سيظل غير قادر على الربط بينهما. يستخرج PDFlibPas كلا القيمتين من اسم الملف والإصدار الذي تمرره، لذلك يتوافق التصريح وكتلة الخصائص دائماً

كيف يكتب المُساعِد كلا النصفين معاً

في PDFlibPas، لا تقوم بتجميع ملف XML هذا يدوياً. بل تضع المستند في وضع PDF/A-3 وتستدعي طريقة (method) واحدة. أول شيء يجب تسويته هو علامة التوافق، لأن Factur-X يتطلب PDF/A-3. يؤدي استدعاء SetPDFAMode(7) إلى تحديد مستوى PDF/A-3u، مما يعين pdfaid:part إلى 3 و pdfaid:conformance إلى U في مخطط التعريف. تحمل حزمة XMP الآن الجزء والتوافق الصحيحين قبل إضافة أي بيانات وصفية للفاتورة

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(7);            // PDF/A-3u: pdfaid:part=3, conformance=U
  PDF.NewDocument;
  // draw the human-readable invoice page here

  FileID := PDF.AddFacturXAssociatedFileFromString(
    InvoiceXML,                  // raw UTF-8 XML bytes
    'EN16931',                   // ConformanceLevel
    'factur-x.xml',              // embedded file name
    'Factur-X invoice XML',      // /Desc text
    'Alternative',               // /AFRelationship
    '1.0',                       // profile version
    '');                         // optional country code
  if FileID = 0 then
    Exit;                        // not PDF/A-3, or XML/profile mismatch

  PDF.SaveToFile('factur-x.pdf');
end;

استدعاء واحد لـ AddFacturXAssociatedFileFromString يقوم بالعمل الذي كان ينقص الملف الفاشل. إنه يضمن XML كملف مرتبط بـ PDF/A-3 مع العلاقة التي حددتها، ويُسجل خصائص fx الأربع جنباً إلى جنب مع اسم المخطط وعنوان URI لمساحة الأسماء والبادئة لملف التعريف المختار. عند حفظ المستند، تقوم خطوة داخلية تسمى ApplyFacturXMetadata بحقن كل من كتلة الخصائص وتصريح pdfaExtension:schemas المطابق في حزمة XMP، لذلك تصل الخصائص المخصصة وهي موصوفة بالفعل. تُرجع الطريقة القيمة 0 إذا لم يكن المستند في وضع PDF/A-3 أو إذا كان XML لا يتطابق مع ملف التعريف المصرح عنه، وهو نفس الحارس الذي يمنع فاتورة مشوهة من الوصول إلى الملف في المقام الأول

النقطة العمياء التي لا يمكن لفحص الحاوية رؤيتها

هذا هو الجزء الذي يجب تسميته بوضوح، لأنه السبب وراء اختباء الخطأ. تقوم ValidateFacturXInvoice بفحص الحاوية. فهي تؤكد أن الكتالوج يحتوي على إدخال /AF، وأن شجرة أسماء EmbeddedFiles موجودة، وأن XML الخاص بالفاتورة موجود، وأن اسم الملف المضمن يتطابق مع ملف التعريف، وأن معرف الدليل الإرشادي في XML يتوافق مع مستوى التوافق، وأن /AFRelationship هو مما يسمح به PDF/A-3. هذه فحوصات حقيقية وتلتقط عيوباً حقيقية. تقوم GetFacturXValidationIssues بالإبلاغ عنها بالاسم، بمعرفات مثل MissingCatalogAF و NotPDFA3 و ConformanceGuidelineMismatch و InvalidAFRelationship و InvalidFileNameProfile

ما لا يتم فحصه هو ما إذا كان مخطط توسيع XMP موجوداً وصحيحاً. فالملف الذي تكون حاويته خالية من العيوب ولكن خصائص fx الخاصة به غير مصرح عنها يتجاوز كل فحص للمشكلات ويُرجع 1، لأنه لا يوجد شيء في تلك القائمة يفتش كتلة pdfaExtension:schemas. هذا هو بالضبط سبب قدرة فاتورة مبنية يدوياً، أو منتجة بواسطة مسار كتب كتلة الخصائص بدون التصريح، على المرور عبر المدقق المضمن والاستمرار في الفشل في veraPDF بشأن البند 6.6.2.3.1. يجيب مدقق الحاوية ومدقق البيانات الوصفية في PDF/A على أسئلة مختلفة، ومدقق PDF/A الكامل وحده هو الذي يجيب على السؤال الثاني

قراءة المشكلات حتى تعرف أي طبقة تعطلت

نظراً لأن الطبقتين تفشلان بشكل مستقل، فإن العادة التشخيصية الصحيحة هي قراءة مشكلات الحاوية أولاً والتعامل مع النتيجة النظيفة كبيان حول الحاوية فقط، وليس مطلقاً حول البيانات الوصفية لـ PDF/A. قم بتشغيل التحقق المضمن، واجمع قائمة المشكلات، وتصرف بناءً عليها قبل اللجوء إلى أداة خارجية

var
  Issues: WideString;
begin
  if PDF.ValidateFacturXInvoice = 0 then
  begin
    Issues := PDF.GetFacturXValidationIssues('|');
    // container-level identifiers, for example:
    //   MissingCatalogAF, NotPDFA3, MissingEmbeddedFilesNameTree,
    //   ConformanceGuidelineMismatch, InvalidAFRelationship
    WriteLn('Container issues: ', Issues);
  end
  else
    WriteLn('Container OK; verify XMP extension schema with a PDF/A checker.');
end;

عندما يُرجع هذا الاستدعاء اسم مشكلة، يكون الخطأ في الحاوية وتخبرك الرسالة بأي جزء. وعندما يُرجع نتيجة نظيفة ويظل veraPDF يرفض الملف، يكون الخطأ بشكل شبه دائم في مخطط توسيع XMP، والحل هو ترك AddFacturXAssociatedFileFromString يكتب البيانات الوصفية بدلاً من بناء كتلة الخصائص بنفسك. إن إبقاء السؤالين منفصلين في ذهنك هو ما يحول الرفض المحير إلى تشخيص من سطر واحد: تظهر مشكلات الحاوية من خلال قائمة المشكلات، بينما لا تظهر مشكلات تصريح المخطط إلا من خلال مدقق PDF/A، والخلط بين الاثنين هو ما يسمح للخطأ بالاختباء

يُغطى السياق الأوسع لتوافق PDF/A و PDF/UA، بما في ذلك كيفية تشغيل تمريرة الفحص المسبق (preflight) قبل أن يغادر الملف عملية البناء، في جولة الفحص المسبق لـ PDF/A و PDF/UA. وإذا كان يجب أن تكون فاتورتك قابلة للوصول أيضاً، فإن شجرة البنية التي يعتمد عليها PDF/A-3a و PDF ذو العلامات (tagged) هي موضوع مقال إمكانية الوصول إلى ملفات PDF ذات العلامات. تُشحن معالجة مخطط التوسيع الموصوفة هنا كجزء من مكتبة PDFlibPas Delphi PDF جنباً إلى جنب مع دعم ملفات تعريف Factur-X و ZUGFeRD و XRechnung الموثقة عبر هذه المدونة