Bài viết kỹ thuật

Extension schema PDF/A-3 cho XMP Factur-X trong Delphi

Bạn đã dựng xong một hóa đơn Factur-X và mọi kiểm tra vỏ chứa đều đạt. Catalog mang một mảng /AF, cây tên EmbeddedFiles phân giải đúng tới file specification cần thiết, tệp factur-x.xml nhúng có /AFRelationship đúng là Alternative, và hàm ValidateFacturXInvoice có sẵn trả về 1. Rồi bạn cho chính tệp đó chạy qua veraPDF, trình kiểm tra tham chiếu mà các cổng thuế dùng, và nó phán rằng cả tài liệu không phải là PDF/A-3 hợp lệ. Cấu trúc thì đúng. Vấn đề nằm ở metadata, và đây là một trong những lỗi dễ bỏ sót nhất trong toàn bộ quy trình hóa đơn điện tử

Lý do đáng hiểu cho trọn vẹn, bởi nó giải thích một lớp khuyết tật PDF/A chẳng liên quan gì tới trang nhìn thấy được hay tệp đính kèm, mà hoàn toàn liên quan tới cách XMP tự mô tả chính nó. Đây là cái bẫy nấp sau một kết quả kiểm tra vỏ chứa màu xanh

Bốn thuộc tính làm tệp trượt

Một hóa đơn Factur-X ghi bốn thuộc tính tùy biến vào gói XMP của nó để phần mềm phía sau đọc được hồ sơ hóa đơn mà không phải phân tích XML nhúng. Chúng nằm trong namespace Factur-X dưới tiền tố fx: fx:DocumentFileName, fx:DocumentType, fx:Version và fx:ConformanceLevel. Chúng chính là phần metadata mà trình đọc cần để biết rằng tệp PDF này mang một hóa đơn EN 16931 tên factur-x.xml ở phiên bản 1.0

Không thuộc tính nào trong bốn thuộc tính đó thuộc về bất kỳ XMP schema nào mà PDF/A định nghĩa sẵn. Các schema Dublin Core, XMP Basic, PDF và schema nhận dạng PDF/A đều được một trình đọc tuân thủ biết tới, nhưng fx: thì không. Khi veraPDF duyệt XMP và gặp một thuộc tính có namespace nó không nhận ra, nó tìm một khai báo cho biết thuộc tính đó nghĩa là gì. Nếu khai báo ấy vắng mặt, nó báo lỗi theo ISO 19005-3 điều 6.6.2.3.1, vốn yêu cầu mọi thuộc tính không lấy từ một schema định nghĩa sẵn phải được mô tả trong một extension schema PDF/A. Bốn thuộc tính chưa khai báo là bốn cách để tệp bị từ chối, và không cách nào trong số đó lộ ra với một kiểm tra vỏ chứa

PDF Library for Delphi: một trình kiểm tra dò tìm bốn XMP schema định nghĩa sẵn và không thấy extension schema PDF/A nào cho các thuộc tính fx, nên hóa đơn Factur-X bị trượt
veraPDF săn tìm một khai báo schema mà tệp chưa bao giờ ghi ra — bốn thuộc tính fx là bốn cơ hội để trượt điều 6.6.2.3.1

Vì sao PDF/A khước từ một thuộc tính tùy biến trần trụi

Quy tắc này trông có vẻ vụn vặt cho tới khi bạn nhớ ra PDF/A sinh ra để làm gì. Định dạng này tồn tại để một tệp có thể được mở và hiểu được sau nhiều thập niên, bởi phần mềm chưa từng được nghe kể về các quy ước của năm 2026. Một trình đọc tuân thủ được kỳ vọng hiểu tài liệu chỉ từ chính tài liệu, không cần tra cứu bất kỳ sổ đăng ký bên ngoài nào

Metadata tùy biến phá vỡ lời hứa đó trừ khi tệp mang theo phần mô tả của chính nó. Với một thuộc tính fx:ConformanceLevel trần trụi, một trình đọc trong tương lai không thể biết tiền tố fx gắn với URI namespace nào, giá trị là văn bản hay ngày tháng hay số nguyên, hay thuộc tính đó mô tả chính tài liệu hay một tài nguyên bên ngoài nào đó. Cơ chế extension schema của PDF/A lấp khoảng trống ấy. Nó cho phép tệp khai báo, trong một cấu trúc XMP cố định, namespace, tiền tố, và với từng thuộc tính là một kiểu giá trị cùng một hạng mục internal hoặc external. Khi khai báo đó có mặt, thuộc tính tự mô tả được và điều 6.6.2.3.1 được thỏa mãn. Không có nó, trình kiểm tra không còn lựa chọn nào ngoài việc coi thuộc tính là không hiểu nổi và đánh trượt tệp. Sự phân biệt hạng mục quan trọng ở đây: các thuộc tính hóa đơn như thế này mô tả dữ liệu đến từ bên ngoài bộ xử lý PDF, nên chúng được khai báo là external chứ không phải internal

Khai báo extension schema chứa những gì

Khai báo là một rdf:Description trong gói XMP, dùng ba namespace do AIIM định nghĩa là pdfaExtension, pdfaSchema và pdfaProperty. Bên trong một bag pdfaExtension:schemas là một mục schema đặt tên cho schema Factur-X, nêu pdfaSchema:namespaceURI và pdfaSchema:prefix của nó, rồi liệt kê bốn thuộc tính trong một chuỗi pdfaSchema:property. Mỗi thuộc tính mang một tên, một pdfaProperty:valueType là Text, và một pdfaProperty:category là external. Đoạn đánh dấu minh họa bên dưới cho thấy hình dáng của khối ấy

PDF Library for Delphi: giải phẫu extension schema PDF/A-3 khai báo schema Factur-X cùng URI namespace, tiền tố fx và bốn thuộc tính Text hạng mục external
Mọi điều khai báo tuyên bố phải khớp với khối fx mà hóa đơn thực sự ghi ra, nếu không trình kiểm tra vẫn từ chối tệp
<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 được khai báo theo cùng cách -->
          </rdf:Seq>
        </pdfaSchema:property>
      </rdf:li>
    </rdf:Bag>
  </pdfaExtension:schemas>
</rdf:Description>

URI namespace và tiền tố không phải là chuỗi cố định. Chúng đi theo hồ sơ. Một tài liệu Factur-X dùng urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# với tiền tố fx, còn một tệp ZUGFeRD 2.0 được chọn qua zugferd-invoice.xml phân giải sang một URI khác dưới tên schema riêng của nó. Extension schema phải khai báo đúng cái URI namespace mà khối thuộc tính thực sự dùng, nếu không trình kiểm tra vẫn không nối được hai bên với nhau. PDF Library for Delphi rút cả hai giá trị từ tên tệp và phiên bản bạn truyền vào, nên khai báo và khối thuộc tính luôn ăn khớp

Hàm trợ giúp ghi cả hai nửa cùng lúc ra sao

Trong PDF Library for Delphi bạn không phải tự ráp đoạn XML đó bằng tay. Bạn đưa tài liệu vào một chế độ PDF/A-3 rồi gọi một phương thức. Việc đầu tiên cần chốt là cờ tuân thủ, bởi Factur-X đòi hỏi PDF/A-3. Gọi SetPDFAMode(7) chọn mức PDF/A-3u, nó đặt pdfaid:part bằng 3 và pdfaid:conformance bằng U trong schema nhận dạng. Gói XMP giờ mang đúng part và conformance trước khi bất kỳ metadata hóa đơn nào được thêm vào

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(7);            // PDF/A-3u: pdfaid:part=3, conformance=U
  PDF.NewDocument;
  // vẽ trang hóa đơn dành cho người đọc ở đây

  FileID := PDF.AddFacturXAssociatedFileFromString(
    InvoiceXML,                  // byte XML UTF-8 thô
    'EN16931',                   // ConformanceLevel
    'factur-x.xml',              // tên tệp nhúng
    'Factur-X invoice XML',      // văn bản /Desc
    'Alternative',               // /AFRelationship
    '1.0',                       // phiên bản hồ sơ
    '');                         // mã quốc gia tùy chọn
  if FileID = 0 then
    Exit;                        // không phải PDF/A-3, hoặc XML lệch hồ sơ

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

Một lệnh gọi duy nhất tới AddFacturXAssociatedFileFromString làm đúng phần việc mà tệp bị trượt còn thiếu. Nó nhúng XML dưới dạng associated file của PDF/A-3 với quan hệ bạn nêu, và nó ghi lại bốn thuộc tính fx cùng tên schema, URI namespace và tiền tố cho hồ sơ đã chọn. Khi tài liệu được lưu, một bước nội bộ tên ApplyFacturXMetadata tiêm cả khối thuộc tính lẫn khai báo pdfaExtension:schemas tương ứng vào gói XMP, nhờ đó các thuộc tính tùy biến đến nơi là đã có mô tả sẵn. Phương thức trả về 0 nếu tài liệu không ở chế độ PDF/A-3 hoặc nếu XML không khớp hồ sơ đã khai báo, đây cũng chính là chốt chặn ngăn một hóa đơn dị dạng lọt vào tệp ngay từ đầu

Điểm mù mà kiểm tra vỏ chứa không nhìn thấy

Đây là phần cần gọi tên thẳng thắn, bởi nó là lý do con lỗi này nấp được. ValidateFacturXInvoice kiểm tra vỏ chứa. Nó xác nhận catalog có mục /AF, cây tên EmbeddedFiles có mặt, XML hóa đơn tồn tại, tên tệp nhúng khớp hồ sơ, ID hướng dẫn trong XML nhất quán với mức tuân thủ, và /AFRelationship nằm trong số PDF/A-3 cho phép. Đó đều là những kiểm tra thật và chúng bắt được những khuyết tật thật. GetFacturXValidationIssues báo cáo chúng theo tên, với các định danh như MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship và InvalidFileNameProfile

Thứ nó không kiểm tra là extension schema trong XMP có mặt và đúng đắn hay không. Một tệp có vỏ chứa hoàn hảo nhưng các thuộc tính fx chưa được khai báo sẽ qua mọi kiểm tra vấn đề và trả về 1, bởi chẳng có mục nào trong danh sách đó soi vào khối pdfaExtension:schemas. Đó chính xác là lý do một hóa đơn dựng tay, hoặc một hóa đơn do pipeline ghi khối thuộc tính mà quên khai báo, có thể lướt qua trình kiểm tra có sẵn mà vẫn trượt veraPDF ở điều 6.6.2.3.1. Trình kiểm tra vỏ chứa và trình kiểm tra metadata PDF/A trả lời hai câu hỏi khác nhau, và chỉ trình kiểm tra PDF/A đầy đủ mới trả lời câu thứ hai

PDF Library for Delphi: cùng một hóa đơn Factur-X qua được mọi kiểm tra vỏ chứa trong ValidateFacturXInvoice trong khi veraPDF từ chối nó vì không tầng nào đọc khối pdfaExtension schemas
Trình kiểm tra vỏ chứa và trình kiểm tra PDF/A trả lời những câu hỏi khác nhau về cùng một tệp

Đọc danh sách vấn đề để biết tầng nào hỏng

Vì hai tầng trượt độc lập với nhau, thói quen chẩn đoán đúng là đọc các vấn đề vỏ chứa trước và coi một kết quả sạch chỉ là phát biểu về vỏ chứa, không bao giờ là về metadata PDF/A. Hãy chạy phần kiểm tra có sẵn, thu danh sách vấn đề, và xử lý nó trước khi với tới một công cụ bên ngoài

var
  Issues: WideString;
begin
  if PDF.ValidateFacturXInvoice = 0 then
  begin
    Issues := PDF.GetFacturXValidationIssues('|');
    // các định danh ở mức vỏ chứa, ví dụ:
    //   MissingCatalogAF, NotPDFA3, MissingEmbeddedFilesNameTree,
    //   ConformanceGuidelineMismatch, InvalidAFRelationship
    WriteLn('Container issues: ', Issues);
  end
  else
    WriteLn('Container OK; verify XMP extension schema with a PDF/A checker.');
end;

Khi lệnh gọi đó trả về một tên vấn đề, lỗi nằm ở vỏ chứa và thông điệp cho bạn biết phần nào. Khi nó trả về sạch mà veraPDF vẫn từ chối tệp, lỗi gần như luôn là extension schema trong XMP, và cách sửa là để AddFacturXAssociatedFileFromString ghi metadata thay vì bạn tự dựng khối thuộc tính. Giữ hai câu hỏi tách bạch trong đầu chính là thứ biến một lần từ chối khó hiểu thành một chẩn đoán gọn một dòng: vấn đề vỏ chứa lộ ra qua danh sách vấn đề, vấn đề khai báo schema chỉ lộ ra qua một trình kiểm tra PDF/A, và lẫn lộn hai thứ là điều khiến con lỗi ẩn mình

Bức tranh tuân thủ PDF/A và PDF/UA rộng hơn, gồm cả cách chạy một lượt preflight trước khi tệp rời khỏi bản build, được trình bày trong bài hướng dẫn preflight PDF/A và PDF/UA. Nếu hóa đơn của bạn còn phải có tính trợ năng, cây cấu trúc mà PDF/A-3a và tagged PDF dựa vào là chủ đề của bài viết về trợ năng tagged PDF. Phần xử lý extension schema mô tả ở đây là một phần của PDF Library for Delphi Delphi PDF Library bên cạnh phần hỗ trợ hồ sơ Factur-X, ZUGFeRD và XRechnung được ghi lại trên khắp blog này