Technical Article

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

Factur-X đặt siêu dữ liệu hóa đơn vào XMP. Siêu dữ liệu đó sống trong namespace urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#, với tiền tố thông thường là fx, và nó mang bốn thuộc tính: fx:ConformanceLevel, fx:DocumentFileName, fx:DocumentType, và fx:Version. Vấn đề là PDF/A từ chối bất kỳ namespace XMP nào mà nó không nhận ra, và nó không nhận ra namespace Factur-X theo mặc định. Để vượt qua xác thực, tài liệu phải khai báo namespace đó trong một extension schema. Điều đó là bắt buộc, không phải tùy chọn, và extension schema phải mô tả từng thuộc tính bằng cách sử dụng một bộ từ vựng siêu dữ liệu rất cụ thể mà PDF/A hiểu để biết loại thuộc tính là gì

Bốn thuộc tính làm tệp thất bại

veraPDF kiểm tra siêu dữ liệu XMP đối với một quy tắc gọi là xác thực extension schema. Khi một tài liệu sử dụng một thuộc tính từ namespace tùy chỉnh, namespace đó phải được khai báo trong phần extension schema của XMP packet, và khai báo phải mô tả từng thuộc tính bao gồm tên, loại giá trị và danh mục. Nếu bất kỳ điều nào trong số đó bị thiếu, veraPDF báo cáo lỗi xác thực extension schema cho từng thuộc tính không được khai báo đúng cách. Đối với một tệp Factur-X sử dụng cả bốn thuộc tính fx: mà không có extension schema, đó là bốn lỗi, một lỗi cho mỗi thuộc tính

Bốn thuộc tính Factur-X là:

  • fx:ConformanceLevel - chuỗi xác định hồ sơ hóa đơn, chẳng hạn như EN16931 hay BASIC
  • fx:DocumentFileName - tên tệp của XML nhúng, thường là factur-x.xml
  • fx:DocumentType - giá trị cố định INVOICE cho hóa đơn
  • fx:Version - số phiên bản của đặc điểm kỹ thuật Factur-X, chẳng hạn như 1.0

Tất cả bốn đều là chuỗi văn bản đơn giản, loại Text trong từ vựng XMP. Cả bốn đều thuộc danh mục external, có nghĩa là giá trị của chúng được đặt bởi người viết tài liệu và không tự động phát sinh từ nội dung

Tại sao PDF/A từ chối thuộc tính tùy chỉnh đơn thuần

Mục đích của PDF/A là khả năng tự mô tả lâu dài. Một tài liệu PDF/A phải có thể đọc được mà không cần phần mềm bên ngoài, và siêu dữ liệu của nó phải có thể giải thích được mà không cần tài liệu tham khảo bên ngoài. Nếu một tài liệu sử dụng thuộc tính từ namespace tùy chỉnh mà không giải thích các thuộc tính đó là gì, tài liệu đó tự mô tả một phần: nó nói "giá trị này là X" nhưng không giải thích "X là gì." Extension schema cung cấp phần giải thích đó. Nó nói rằng namespace này tồn tại, đây là thuộc tính của nó, đây là loại của mỗi thuộc tính, và đây là cách mỗi thuộc tính nên được giải thích. Với khai báo đó trong tệp, một đầu đọc gặp thuộc tính fx:ConformanceLevel trong vài thập kỷ có thể hiểu loại và danh mục của nó mà không cần kết nối mạng hay phần mềm bổ sung

Cấu trúc khai báo extension schema

Khai báo đi vào XMP packet trong một phần tử container cụ thể. Cấu trúc là một mô hình RDF: container là một phần tử pdfaExtension:schemas, chứa một rdf:Bag, chứa các phần tử rdf:li, mỗi phần tử là một khai báo schema. Bên trong mỗi khai báo schema là namespace, tiền tố, tên hiển thị, và một danh sách các thuộc tính của nó. Mỗi thuộc tính được mô tả bằng tên, loại giá trị và danh mục của nó

<pdfaExtension:schemas>
  <rdf:Bag>
    <rdf:li rdf:parseType="Resource">
      <pdfaSchema:namespaceURI>urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#</pdfaSchema:namespaceURI>
      <pdfaSchema:prefix>fx</pdfaSchema:prefix>
      <pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema>
      <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>The name of the embedded XML invoice file</pdfaProperty:description>
          </rdf:li>
          <rdf:li rdf:parseType="Resource">
            <pdfaProperty:name>DocumentType</pdfaProperty:name>
            <pdfaProperty:valueType>Text</pdfaProperty:valueType>
            <pdfaProperty:category>external</pdfaProperty:category>
            <pdfaProperty:description>The type of the hybrid document, always INVOICE</pdfaProperty:description>
          </rdf:li>
          <rdf:li rdf:parseType="Resource">
            <pdfaProperty:name>Version</pdfaProperty:name>
            <pdfaProperty:valueType>Text</pdfaProperty:valueType>
            <pdfaProperty:category>external</pdfaProperty:category>
            <pdfaProperty:description>The version of the Factur-X specification</pdfaProperty:description>
          </rdf:li>
          <rdf:li rdf:parseType="Resource">
            <pdfaProperty:name>ConformanceLevel</pdfaProperty:name>
            <pdfaProperty:valueType>Text</pdfaProperty:valueType>
            <pdfaProperty:category>external</pdfaProperty:category>
            <pdfaProperty:description>The Factur-X conformance level, e.g. EN16931 or BASIC</pdfaProperty:description>
          </rdf:li>
        </rdf:Seq>
      </pdfaSchema:property>
    </rdf:li>
  </rdf:Bag>
</pdfaExtension:schemas>

Phần khai báo này là một phần cố định của XMP packet của hóa đơn. Nó không thay đổi giữa các hóa đơn hay giữa các hồ sơ vì namespace và các thuộc tính của nó là bất biến trên toàn đặc điểm kỹ thuật Factur-X. Điều thay đổi là các giá trị thuộc tính, được đặt trong phần riêng biệt của XMP packet cùng với các siêu dữ liệu hóa đơn thực tế

Cách helper ghi cả hai nửa

Để giữ phần tạo XMP ở một nơi và đảm bảo extension schema luôn đi kèm với các thuộc tính Factur-X, thư viện xử lý toàn bộ XMP packet thông qua một lời gọi duy nhất. Gọi AddFacturXAssociatedFileFromString và phương thức viết hai thứ vào XMP: các giá trị thuộc tính fx: cho hóa đơn cụ thể, và khai báo extension schema mô tả chúng. Cả hai đều được ghi cùng lúc, vì vậy không có cách nào để viết thuộc tính mà không có khai báo đi kèm của nó

// After SetPDFAMode and drawing the visible page:
FileID := PDF.AddFacturXAssociatedFileFromString(
  InvoiceXML,       // UTF-8 XML bytes
  'EN16931',        // fx:ConformanceLevel
  'factur-x.xml',   // fx:DocumentFileName - also the embedded file name
  'Factur-X EN16931 invoice',
  'Alternative',    // /AFRelationship
  '1.0',            // fx:Version
  '');              // country code, empty for non-country-specific profiles

Bên trong lời gọi đó, thư viện tạo XMP packet hoàn chỉnh bao gồm đầy đủ khai báo extension schema. Phương thức không tách bước "ghi extension schema" ra thành một lời gọi riêng vì luôn cần thiết và luôn giống nhau. Tất cả những gì thay đổi giữa các hóa đơn là các giá trị bốn thuộc tính đó, và chúng đến từ các tham số của phương thức

Điểm mù mà kiểm tra container không thể thấy

Có một loại lỗi mà extension schema không thể phát hiện: các giá trị thuộc tính không nhất quán. Extension schema khai báo rằng fx:ConformanceLevel là một thuộc tính loại Text trong namespace đó. Nó không xác nhận rằng giá trị là một trong năm giá trị Factur-X được phép, hay rằng giá trị khớp với hồ sơ được khai báo trong XML nhúng, hay rằng fx:DocumentFileName khớp với tên tệp của tệp nhúng thực tế. Những điều đó là kiểm tra ngữ nghĩa và extension schema là khai báo kiểu. Một tài liệu có thể có extension schema hoàn toàn hợp lệ và vượt qua xác thực veraPDF trong khi vẫn có fx:ConformanceLevel đặt thành giá trị tùy ý không khớp với bất cứ điều gì

Kiểm tra tính nhất quán đó xảy ra ở cấp ứng dụng khi thư viện đính kèm XML. AddFacturXAssociatedFileFromString kiểm tra rằng GuidelineID bên trong XML khớp với mức tuân thủ bạn truyền vào và rằng tên tệp khớp với tên tệp nhúng. Nếu chúng không khớp, lời gọi thất bại và trả về 0 thay vì ghi tệp bất hợp lệ vào tài liệu. Kiểm tra đó là những gì veraPDF không thể làm vì nó không phân tích cú pháp XML nhúng; nó chỉ xem container

Đọc lại các vấn đề

Khi đọc một tệp Factur-X, điều đầu tiên để kiểm tra là liệu tệp có được nhận ra hay không. DetectFacturXInvoice trả về 1 nếu tài liệu có cấu trúc tệp liên kết bắt buộc và siêu dữ liệu XMP Factur-X, 0 nếu không. Nếu trả về 1, GetFacturXInvoiceInfo đọc các trường siêu dữ liệu theo số thẻ: thẻ 2 là fx:DocumentFileName, thẻ 3 là fx:DocumentType, thẻ 5 là fx:ConformanceLevel, thẻ 6 là GuidelineID từ bên trong XML, và thẻ 7 là /AFRelationship từ tệp liên kết

Đọc thẻ 5 và thẻ 6 và so sánh chúng là cách nhanh nhất để phát hiện hóa đơn được tạo ra với mức tuân thủ không khớp. Mức tuân thủ trong XMP mô tả tệp nói nó là gì. GuidelineID bên trong XML mô tả những gì XML thực sự là. Nếu hai thứ không đồng ý, hóa đơn có thể vượt qua xác thực container nhưng sẽ thất bại xác thực quy tắc kinh doanh khi bên nhận cố gắng xử lý nó

Tất cả những điều này, tạo extension schema, ghi XMP, đính kèm tệp liên kết, đọc lại và xác thực, đều được xây dựng vào PDFlibPas Delphi PDF Library. Bối cảnh rộng hơn về cách hóa đơn được lắp ráp ở cấp container được đề cập trong hướng dẫn hóa đơn lai Factur-X và ZUGFeRD, và các kiểm tra xác thực hai tầng mà một quy trình sản xuất nên chạy được đề cập trong hướng dẫn xác thực veraPDF và Mustang