مقاله فنی

طرحواره‌های افزونه PDF/A-3 برای Factur-X XMP در دلفی

شما یک فاکتور Factur-X ساخته‌اید و تمام بررسی‌های کانتینر با موفقیت عبور کرده است. کاتالوگ حامل یک آرایه /AF است، درخت نام EmbeddedFiles در مشخصات فایل درست حل شده است، factur-x.xml جاسازی شده، /AFRelationship را روی Alternative به درستی تنظیم کرده است و در درون‌ساخت ValidateFacturXInvoice عدد ۱ را برمی‌گرداند. سپس همان فایل را از طریق 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 ایزو 19005-3 گزارش می‌کند، که می‌طلبد هر ویژگی‌ای که از یک طرحواره از پیش‌تعریف‌شده گرفته نشده است، در یک طرحواره افزونه PDF/A شرح داده شود. چهار ویژگیِ اعلام‌نشده، چهار راه برای رد شدن فایل است و هیچ یک از آن‌ها برای بررسی کانتینر قابل‌مشاهده نیست

چرا PDF/A یک ویژگی سفارشی برهنه را نمی‌پذیرد

این قانون خیلی وسواسی و سخت‌گیرانه به نظر می‌رسد تا زمانی که به یاد بیاورید هدف PDF/A چیست. این فرمت به این دلیل وجود دارد که بتوان یک فایل را ده‌ها سال بعد باز کرد و فهمید، توسط نرم‌افزاری که هرگز در مورد قراردادهای سال 2026 چیزی به آن گفته نشده است. از یک خواننده منطبق انتظار می‌رود که از خود سند، بدون هیچ رجیستری خارجی برای مشاوره، مفهوم سند را استخراج کند

متادیتای سفارشی این وعده را می‌شکند مگر اینکه فایل توصیف خود را داشته باشد. با داشتن یک ویژگی خام مانند fx:ConformanceLevel، خواننده آینده نمی‌تواند بداند که URI فضای‌نامی که پیشوند fx به آن مقید شده است، مقدار متن، تاریخ یا یک عدد صحیح است، یا اینکه آیا ویژگی، خودِ سند را توصیف می‌کند یا یک منبع خارجی را. مکانیزم طرحواره افزونه PDF/A این شکاف را برطرف می‌کند. این ویژگی به فایل اجازه می‌دهد در یک ساختار ثابت XMP، فضای نام، پیشوند و برای هر ویژگی یک نوع مقدار و دسته‌بندی internal (داخلی) یا external (خارجی) را اعلام کند. پس از وجود آن اعلامیه، ویژگی خود-توصیفی (self-describing) است و بند 6.6.2.3.1 برآورده می‌شود. بدون آن، اعتبارسنج (validator) هیچ چاره‌ای جز این ندارد که این ویژگی را غیرقابل‌فهم در نظر بگیرد و فایل را مردود کند. در اینجا تمایزِ دسته‌بندی مهم است: ویژگی‌های فاکتور از این دست، داده‌هایی را توصیف می‌کنند که از خارج از پردازنده PDF می‌آیند، بنابراین آن‌ها به‌جای internal، به عنوان external اعلام می‌شوند

آنچه در اعلامیه طرحواره افزونه قرار دارد

این اعلامیه یک rdf:Description در بسته XMP است که از سه فضای نام تعریف‌شده توسط AIIM استفاده می‌کند: pdfaExtension، pdfaSchema، و pdfaProperty. در داخل یک pdfaExtension:schemas، یک ورودی طرحواره وجود دارد که نام طرحواره Factur-X را مشخص می‌کند، pdfaSchema:namespaceURI و pdfaSchema:prefix آن را ارائه می‌دهد، و سپس این چهار ویژگی را در یک دنباله pdfaSchema:property لیست می‌کند. هر ویژگی دارای یک نام، یک pdfaProperty:valueType از نوع Text و pdfaProperty:category از نوع external است. کدهای نشانه‌گذاری (markup) زیر شکل آن بلوک را نشان می‌دهد

<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 فضای نام و پیشوند رشته‌های ثابتی نیستند. آن‌ها از پروفایل پیروی می‌کنند. یک سند Factur-X از urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# با پیشوند fx استفاده می‌کند، در حالی که یک فایل ZUGFeRD 2.0 انتخاب‌شده از طریق zugferd-invoice.xml به یک URI متفاوت با نام طرحواره خودش حل می‌شود. طرحواره افزونه باید همان URI فضای نامی را اعلام کند که بلوکِ ویژگی واقعاً استفاده می‌کند، در غیر این صورت اعتبارسنج همچنان نمی‌تواند آن دو را به هم متصل کند. PDFlibPas هر دو مقدار را از روی نام فایل و نسخه‌ای که پاس می‌دهید استخراج می‌کند، در نتیجه اعلامیه و بلوکِ ویژگی همیشه با هم همخوانی دارند

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

در PDFlibPas شما آن XML را دستی سرهم نمی‌کنید. سند را در حالت PDF/A-3 قرار می‌دهید و یک متد را فراخوانی می‌کنید. اولین چیزی که باید حل شود پرچم انطباق است، زیرا Factur-X به PDF/A-3 نیاز دارد. فراخوانی SetPDFAMode(7) سطح PDF/A-3u را انتخاب می‌کند، که pdfaid:part را روی ۳ و pdfaid:conformance را روی U در طرحواره شناسایی (identification schema) تنظیم می‌کند. اکنون بسته 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 تزریق می‌کند، بنابراین ویژگی‌های سفارشی از قبل توصیف‌شده وارد می‌شوند. اگر سند در حالت PDF/A-3 نباشد یا اگر XML با پروفایل اعلام‌شده تطابق نداشته باشد، این متد 0 را برمی‌گرداند، که این همان محافظی است که مانع از رسیدن یک فاکتور معیوب به فایل در وهله اول می‌شود

نقطه کوری که بررسی کانتینر نمی‌تواند ببیند

این قسمتی است که به وضوح نام‌گذاری می‌شود، زیرا دلیلی است که باگ در آن پنهان می‌شود. ValidateFacturXInvoice کانتینر را بررسی می‌کند. این ویژگی تأیید می‌کند که کاتالوگ دارای ورودی /AF است، درخت نام EmbeddedFiles موجود است، XML فاکتور وجود دارد، نام فایل جاسازی شده با پروفایل مطابقت دارد، شناسه راهنما (guideline ID) در 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 برچسب‌دار به آن تکیه دارند موضوعِ مقاله دسترسی‌پذیریِ PDF برچسب‌دار است. مدیریتِ طرحواره افزونه که در اینجا توضیح داده شد به عنوان بخشی از کتابخانه PDFlibPas Delphi PDF در کنار پروفایل‌های Factur-X، ZUGFeRD، و XRechnung که در سراسر این وبلاگ مستند شده‌اند، عرضه می‌شود