Vous avez construit une facture Factur-X et chaque vérification de conteneur réussit. Le catalogue porte un tableau /AF, l'arborescence de noms EmbeddedFiles se résout à la bonne spécification de fichier, le factur-x.xml intégré a le bon /AFRelationship de Alternative, et le ValidateFacturXInvoice intégré renvoie 1. Ensuite, vous passez le même fichier dans veraPDF, le vérificateur de référence utilisé par les portails fiscaux, et il juge que l'ensemble du document n'est pas un PDF/A-3 valide. La structure est correcte. Les métadonnées posent problème, et cet échec est l'un des plus faciles à manquer dans tout le flux de travail de facturation électronique
La raison mérite d'être comprise dans son intégralité, car elle explique une classe de défauts PDF/A qui n'a rien à voir avec la page visible ou la pièce jointe et tout à voir avec la façon dont XMP se décrit. C'est le piège qui se cache derrière une vérification de conteneur au vert
Les quatre propriétés qui font échouer le fichier
Une facture Factur-X écrit quatre propriétés personnalisées dans son paquet XMP afin que les logiciels en aval puissent lire le profil de facture sans analyser le XML intégré. Elles résident dans l'espace de noms Factur-X sous le préfixe fx : fx:DocumentFileName, fx:DocumentType, fx:Version et fx:ConformanceLevel. Ce sont exactement les métadonnées dont un lecteur a besoin pour savoir que ce PDF porte une facture EN 16931 nommée factur-x.xml à la version 1.0
Aucune de ces quatre propriétés ne fait partie d'un schéma XMP que PDF/A prédéfinit. Les schémas Dublin Core, XMP Basic, PDF et d'identification PDF/A sont connus d'un lecteur conforme, mais fx: ne l'est pas. Lorsque veraPDF parcourt le XMP et atteint une propriété dont il ne reconnaît pas l'espace de noms, il cherche une déclaration qui lui indiquerait ce que signifie la propriété. Si cette déclaration est absente, il signale un échec par rapport à la clause 6.6.2.3.1 de l'ISO 19005-3, qui exige que chaque propriété non tirée d'un schéma prédéfini soit décrite dans un schéma d'extension PDF/A. Quatre propriétés non déclarées, quatre façons pour le fichier d'être rejeté, et aucune d'entre elles n'est visible pour une vérification de conteneur
Pourquoi PDF/A refuse une propriété personnalisée nue
La règle semble pédante jusqu'à ce que vous vous rappeliez à quoi sert PDF/A. Le format existe pour qu'un fichier puisse être ouvert et compris dans des décennies, par des logiciels qui n'ont jamais été informés des conventions de 2026. On attend d'un lecteur conforme qu'il comprenne le document à partir du document lui-même, sans aucun registre externe à consulter
Les métadonnées personnalisées rompent cette promesse à moins que le fichier ne porte sa propre description. Face à une propriété fx:ConformanceLevel nue, un futur lecteur ne peut pas connaître l'URI d'espace de noms auquel le préfixe fx est lié, si la valeur est du texte, une date ou un entier, ou si la propriété décrit le document lui-même ou une ressource externe. Le mécanisme de schéma d'extension PDF/A comble cette lacune. Il permet au fichier de déclarer, dans une structure XMP fixe, l'espace de noms, le préfixe, et pour chaque propriété un type de valeur et une catégorie internal ou external. Une fois cette déclaration présente, la propriété est auto-descriptive, et la clause 6.6.2.3.1 est satisfaite. Sans elle, le validateur n'a d'autre choix que de traiter la propriété comme inintelligible et de faire échouer le fichier. La distinction de catégorie a de l'importance ici : les propriétés de facture telles que celles-ci décrivent des données provenant de l'extérieur du processeur PDF, elles sont donc déclarées external plutôt que internal
Ce que contient la déclaration du schéma d'extension
La déclaration est une rdf:Description dans le paquet XMP qui utilise les trois espaces de noms définis par l'AIIM pdfaExtension, pdfaSchema et pdfaProperty. À l'intérieur d'un bag pdfaExtension:schemas se trouve une entrée de schéma qui nomme le schéma Factur-X, donne son pdfaSchema:namespaceURI et pdfaSchema:prefix, puis liste les quatre propriétés dans une séquence pdfaSchema:property. Chaque propriété porte un nom, un pdfaProperty:valueType de Text et une pdfaProperty:category de external. Le balisage illustratif ci-dessous montre la forme de ce bloc
<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>
L'URI d'espace de noms et le préfixe ne sont pas des chaînes fixes. Ils suivent le profil. Un document Factur-X utilise urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# avec le préfixe fx, tandis qu'un fichier ZUGFeRD 2.0 sélectionné via zugferd-invoice.xml se résout en une URI différente sous son propre nom de schéma. Le schéma d'extension doit déclarer la même URI d'espace de noms que le bloc de propriété utilise réellement, sinon le validateur ne peut toujours pas relier les deux. PDFlibPas dérive les deux valeurs à partir du nom de fichier et de la version que vous transmettez, de sorte que la déclaration et le bloc de propriété concordent toujours
Comment l'aide écrit les deux moitiés ensemble
Dans PDFlibPas, vous n'assemblez pas ce XML à la main. Vous mettez le document dans un mode PDF/A-3 et appelez une méthode. La première chose à régler est l'indicateur de conformité, car Factur-X requiert PDF/A-3. L'appel à SetPDFAMode(7) sélectionne le niveau PDF/A-3u, ce qui définit pdfaid:part à 3 et pdfaid:conformance à U dans le schéma d'identification. Le paquet XMP porte maintenant la bonne partie et la bonne conformité avant même que la moindre métadonnée de facture ne soit ajoutée
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;
Un seul appel à AddFacturXAssociatedFileFromString effectue le travail qui manquait au fichier défaillant. Il intègre le XML en tant que fichier associé PDF/A-3 avec la relation que vous avez nommée, et il enregistre les quatre propriétés fx avec le nom de schéma, l'URI d'espace de noms et le préfixe du profil choisi. Lors de l'enregistrement du document, une étape interne nommée ApplyFacturXMetadata injecte à la fois le bloc de propriété et la déclaration pdfaExtension:schemas correspondante dans le paquet XMP, de sorte que les propriétés personnalisées arrivent déjà décrites. La méthode renvoie 0 si le document n'est pas dans un mode PDF/A-3 ou si le XML ne correspond pas au profil déclaré, ce qui est la même garde qui empêche une facture malformée d'atteindre le fichier en premier lieu
Le point aveugle que la vérification de conteneur ne peut pas voir
C'est la partie à nommer clairement, car c'est la raison pour laquelle le bug se cache. ValidateFacturXInvoice vérifie le conteneur. Il confirme que le catalogue a une entrée /AF, que l'arborescence de noms EmbeddedFiles est présente, que le XML de facture existe, que le nom du fichier intégré correspond au profil, que l'ID de la directive dans le XML concorde avec le niveau de conformité, et que /AFRelationship est l'une de celles que PDF/A-3 autorise. Ce sont de véritables vérifications et elles détectent de vrais défauts. GetFacturXValidationIssues les signale par leur nom, avec des identifiants tels que MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship et InvalidFileNameProfile
Ce qu'il ne vérifie pas, c'est si le schéma d'extension XMP est présent et correct. Un fichier dont le conteneur est irréprochable mais dont les propriétés fx ne sont pas déclarées passe chaque vérification de problème et renvoie 1, car rien dans cette liste n'inspecte le bloc pdfaExtension:schemas. C'est précisément la raison pour laquelle une facture construite à la main, ou produite par un pipeline qui a écrit le bloc de propriété sans la déclaration, peut passer sans encombre le validateur intégré et tout de même échouer à veraPDF sur la clause 6.6.2.3.1. Le validateur de conteneur et le validateur de métadonnées PDF/A répondent à des questions différentes, et seul le vérificateur PDF/A complet répond à la seconde
Lecture des problèmes pour savoir quelle couche s'est rompue
Étant donné que les deux couches échouent indépendamment, la bonne habitude de diagnostic consiste à lire d'abord les problèmes de conteneur et à traiter un résultat propre comme une déclaration sur le conteneur uniquement, jamais sur les métadonnées PDF/A. Exécutez la validation intégrée, recueillez la liste des problèmes, et agissez en conséquence avant de faire appel à un outil externe
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;
Lorsque cet appel renvoie un nom de problème, la faute se trouve dans le conteneur et le message vous indique quelle partie. Lorsqu'il renvoie un résultat propre et que veraPDF rejette toujours le fichier, la faute est presque toujours le schéma d'extension XMP, et la solution consiste à laisser AddFacturXAssociatedFileFromString écrire les métadonnées plutôt que de construire le bloc de propriété vous-même. Garder les deux questions séparées dans votre propre esprit est ce qui transforme un rejet déroutant en un diagnostic d'une ligne : les problèmes de conteneur font surface via la liste des problèmes, les problèmes de déclaration de schéma ne font surface que via un validateur PDF/A, et confondre les deux est ce qui permet au bug de se cacher
Le tableau plus large de la conformité PDF/A et PDF/UA, y compris la façon d'exécuter un passage de contrôle en amont avant qu'un fichier ne quitte votre génération, est couvert dans le guide du contrôle en amont PDF/A et PDF/UA. Si votre facture doit également être accessible, l'arborescence de structure dont dépendent le PDF/A-3a et le PDF balisé fait l'objet de l'article sur l'accessibilité des PDF balisés. La gestion des schémas d'extension décrite ici est fournie dans le cadre de la Bibliothèque PDF Delphi PDFlibPas aux côtés de la prise en charge des profils Factur-X, ZUGFeRD et XRechnung documentée tout au long de ce blog