Technical Article

DelphiでのFactur-X XMP向けPDF/A-3拡張スキーマ

あなたはFactur-Xインボイスを構築し、すべてのコンテナチェックに合格しました。カタログには/AF配列があり、EmbeddedFiles名前ツリーは正しいファイル仕様に解決され、埋め込まれたfactur-x.xmlAlternativeという正しい/AFRelationshipを持ち、組み込みのValidateFacturXInvoiceは1を返します。次に、同じファイルを税務ポータルが使用するリファレンスチェッカーであるveraPDFにかけると、ドキュメント全体が有効なPDF/A-3ではないと判定されます。構造は正しいのです。メタデータが問題であり、この失敗は電子インボイスのワークフロー全体で最も見落としやすいものの1つです

その理由を完全に理解する価値があります。なぜなら、それは可視ページや添付ファイルとは何の関係もなく、XMPがそれ自身をどのように記述するかということとすべて関係している、PDF/Aの欠陥のクラスを説明しているからです。これは、緑色の(合格した)コンテナチェックの背後に隠れている罠です

ファイルを不合格にする4つのプロパティ

Factur-Xインボイスは、ダウンストリームのソフトウェアが埋め込みXMLを解析することなくインボイスプロファイルを読み取ることができるように、XMPパケットに4つのカスタムプロパティを書き込みます。これらは、fxプレフィックスの下のFactur-Xネームスペース内に存在します:fx:DocumentFileNamefx:DocumentTypefx:Version、およびfx:ConformanceLevelです。これらはまさに、このPDFがバージョン1.0のfactur-x.xmlという名前のEN 16931インボイスを保持していることをリーダーが知るために必要なメタデータです

これら4つのプロパティはどれも、PDF/Aが事前定義しているXMPスキーマの一部ではありません。Dublin Core、XMP Basic、PDF、およびPDF/A識別スキーマは準拠リーダーに知られていますが、fx:は知られていません。veraPDFがXMPを探索し、認識できないネームスペースを持つプロパティに到達すると、そのプロパティが何を意味するのかを教える宣言を探します。その宣言が存在しない場合、事前定義されたスキーマから抽出されたものではないすべてのプロパティはPDF/A拡張スキーマで記述されなければならないと要求している、ISO 19005-3条項6.6.2.3.1に対する失敗を報告します。4つの宣言されていないプロパティ、ファイルが拒否される4つの方法があり、そのどれ1つとしてコンテナチェックでは確認できません

なぜPDF/Aはむき出しのカスタムプロパティを拒否するのか

このルールは、PDF/Aが何のためのものかを思い出すまでは、衒学的に見えます。このフォーマットは、2026年の規則について教えられたことのないソフトウェアによって、数十年後でもファイルを開いて理解できるように存在しています。準拠リーダーは、参照する外部レジストリなしに、ドキュメントのみからドキュメントを理解することが期待されています

カスタムメタデータは、ファイルが独自の説明を保持していない限り、その約束を破ります。むき出しのfx:ConformanceLevelプロパティが与えられた場合、将来のリーダーは、fxプレフィックスがバインドされているネームスペースURI、値がテキストなのか日付なのか整数なのか、あるいはプロパティがドキュメント自体を記述しているのか外部リソースを記述しているのかを知ることができません。PDF/A拡張スキーマのメカニズムは、そのギャップを埋めます。これにより、ファイルは固定のXMP構造で、ネームスペース、プレフィックス、そして各プロパティに対して値の型とinternalまたはexternalのカテゴリを宣言できます。その宣言が存在すれば、プロパティは自己記述的になり、条項6.6.2.3.1は満たされます。それがなければ、バリデーターはプロパティを理解できないものとして扱い、ファイルを不合格にする以外に選択肢がありません。ここでのカテゴリの区別は重要です。これらのインボイスプロパティはPDFプロセッサの外部から来るデータを記述しているため、internalではなくexternalと宣言されます

拡張スキーマ宣言には何が含まれているか

この宣言は、3つのAIIM定義のネームスペースpdfaExtensionpdfaSchema、およびpdfaPropertyを使用するXMPパケット内のrdf:Descriptionです。pdfaExtension:schemasのbagの内部には、Factur-Xスキーマを命名し、そのpdfaSchema:namespaceURIpdfaSchema:prefixを提供し、次に4つのプロパティをpdfaSchema:propertyのシーケンスにリストする1つのスキーマエントリが存在します。各プロパティは、名前、TextpdfaProperty:valueType、およびexternalpdfaProperty:categoryを保持しています。以下の説明用のマークアップは、そのブロックの形状を示しています

<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も同じ方法で宣言される -->
          </rdf:Seq>
        </pdfaSchema:property>
      </rdf:li>
    </rdf:Bag>
  </pdfaExtension:schemas>
</rdf:Description>

ネームスペースURIとプレフィックスは固定文字列ではありません。それらはプロファイルに従います。Factur-Xドキュメントはfxプレフィックスを持つurn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#を使用しますが、zugferd-invoice.xmlを通じて選択されたZUGFeRD 2.0ファイルは、独自のスキーマ名の下にある異なるURIに解決されます。拡張スキーマは、プロパティブロックが実際に使用しているのと同じネームスペースURIを宣言しなければなりません。そうしないと、バリデーターは依然として2つを接続できません。PDFlibPasは、渡されたファイル名とバージョンから両方の値を導き出すため、宣言とプロパティブロックは常に一致します

ヘルパーが両方の半分を一緒に書き込む方法

PDFlibPasでは、そのXMLを手作業で組み立てることはありません。ドキュメントをPDF/A-3モードにして、1つのメソッドを呼び出します。最初に解決すべきことは適合性フラグです。なぜなら、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;
  // ここに人間が読めるインボイスページを描画する

  FileID := PDF.AddFacturXAssociatedFileFromString(
    InvoiceXML,                  // 生のUTF-8 XMLバイト
    'EN16931',                   // ConformanceLevel
    'factur-x.xml',              // 埋め込みファイル名
    'Factur-X invoice XML',      // /Desc テキスト
    'Alternative',               // /AFRelationship
    '1.0',                       // プロファイルのバージョン
    '');                         // オプションの国コード
  if FileID = 0 then
    Exit;                        // PDF/A-3ではない、またはXML/プロファイルの不一致

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

AddFacturXAssociatedFileFromStringの1回の呼び出しで、失敗したファイルに欠けていた作業が行われます。XMLを、名前を付けたリレーションシップを持つPDF/A-3関連ファイルとして埋め込み、選択されたプロファイルのスキーマ名、ネームスペースURI、プレフィックスとともに4つのfxプロパティを記録します。ドキュメントが保存されると、ApplyFacturXMetadataという内部ステップが、プロパティブロックとそれに一致するpdfaExtension:schemas宣言の両方をXMPパケットに挿入するため、カスタムプロパティはすでに記述された状態で到着します。ドキュメントがPDF/A-3モードではない場合、またはXMLが宣言されたプロファイルと一致しない場合、メソッドは0を返します。これは、不正な形式のインボイスがファイルに到達するのを最初に防ぐのと同じガードです

コンテナチェックには見えない死角

これがバグが隠れる理由であるため、率直に名指しすべき部分です。ValidateFacturXInvoiceはコンテナをチェックします。カタログに/AFエントリがあること、EmbeddedFiles名前ツリーが存在すること、インボイスXMLが存在すること、埋め込みファイル名がプロファイルと一致していること、XML内のガイドラインIDが適合性レベルと一致していること、そして/AFRelationshipがPDF/A-3で許可されているものであることを確認します。これらは実際のチェックであり、実際の欠陥を捉えます。GetFacturXValidationIssuesは、MissingCatalogAFNotPDFA3ConformanceGuidelineMismatchInvalidAFRelationship、およびInvalidFileNameProfileなどの識別子を使用して、それらを名前で報告します

チェックしないのは、XMP拡張スキーマが存在し、正しいかどうかです。コンテナに欠陥はないが、fxプロパティが宣言されていないファイルは、そのリストのどれもpdfaExtension:schemasブロックを検査しないため、すべての問題チェックに合格し1を返します。それこそまさに、手動で構築されたインボイスや、宣言なしにプロパティブロックを書き込んだパイプラインによって生成されたインボイスが、組み込みのバリデーターを通過しても、条項6.6.2.3.1でveraPDFに失敗する理由です。コンテナバリデーターとPDF/Aメタデータバリデーターは異なる質問に答えており、フルPDF/Aチェッカーだけが2番目の質問に答えます

どのレイヤーが壊れたかを知るための問題の読み取り

2つのレイヤーは独立して失敗するため、正しい診断の習慣は、最初にコンテナの問題を読み取り、クリーンな結果はコンテナに関するステートメントとしてのみ扱い、PDF/Aメタデータに関するステートメントとしては決して扱わないことです。組み込みの検証を実行し、問題のリストを収集し、それに基づいて行動してから外部ツールに手を伸ばしてください

var
  Issues: WideString;
begin
  if PDF.ValidateFacturXInvoice = 0 then
  begin
    Issues := PDF.GetFacturXValidationIssues('|');
    // コンテナレベルの識別子、例:
    //   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にメタデータを書き込ませることです。この2つの質問を自分の頭の中で切り離しておくことが、不可解な拒否を1行の診断に変えるものです。コンテナの問題は問題リストを通じて表面化し、スキーマ宣言の問題はPDF/Aバリデーターを通じてのみ表面化します。この2つを混同することが、バグを隠してしまう原因なのです

ファイルがビルドから出る前にプリフライトパスを実行する方法など、より広範なPDF/AおよびPDF/UAの適合性の全体像については、PDF/AおよびPDF/UAプリフライトのチュートリアルでカバーされています。インボイスをアクセシブルにする必要がある場合、PDF/A-3aとタグ付きPDFが依存する構造ツリーは、タグ付きPDFのアクセシビリティに関する記事の主題となります。ここで説明した拡張スキーマの処理は、このブログ全体で文書化されているFactur-X、ZUGFeRD、およびXRechnungプロファイルのサポートとともに、PDFlibPas Delphi PDF Libraryの一部として出荷されています