U heeft een Factur-X-factuur gebouwd en elke containercontrole slaagt. De catalogus bevat een /AF-array, de naamstructuur van EmbeddedFiles verwijst naar de juiste bestandsspecificatie, de ingesloten factur-x.xml heeft de juiste /AFRelationship van Alternative, en de ingebouwde ValidateFacturXInvoice retourneert 1. Vervolgens voert u hetzelfde bestand door veraPDF, de referentiechecker die belastingportals gebruiken, en deze oordeelt dat het hele document geen geldige PDF/A-3 is. De structuur klopt. De metadata is het probleem, en de fout is een van de gemakkelijkste in de hele e-factuurworkflow om over het hoofd te zien.
De reden is de moeite waard om volledig te begrijpen, omdat deze een klasse van PDF/A-defecten verklaart die niets te maken heeft met de zichtbare pagina of de bijlage, en alles met de manier waarop XMP zichzelf beschrijft. Dit is de valstrik die zich verbergt achter een groene containercontrole.
De vier eigenschappen die het bestand doen falen
Een Factur-X-factuur schrijft vier aangepaste eigenschappen in het XMP-pakket, zodat software verderop in de keten het factuurprofiel kan lezen zonder de ingesloten XML te hoeven verwerken. Ze leven in de Factur-X-namespace onder het fx-voorvoegsel: fx:DocumentFileName, fx:DocumentType, fx:Version en fx:ConformanceLevel. Het is precies de metadata die een lezer nodig heeft om te weten dat deze PDF een EN 16931-factuur met de naam factur-x.xml in versie 1.0 bevat.
Geen van die vier eigenschappen maakt deel uit van een XMP-schema dat door PDF/A vooraf is gedefinieerd. De Dublin Core, XMP Basic, PDF, en PDF/A identificatieschema's zijn bekend bij een conforme lezer, maar fx: is dat niet. Wanneer veraPDF door de XMP loopt en een eigenschap bereikt waarvan het de namespace niet herkent, zoekt het naar een declaratie die zou vertellen wat de eigenschap betekent. Als die declaratie afwezig is, meldt het een fout ten aanzien van ISO 19005-3 clausule 6.6.2.3.1, die vereist dat elke eigenschap die niet afkomstig is uit een vooraf gedefinieerd schema, wordt beschreven in een PDF/A-extensieschema. Vier niet-gedeclareerde eigenschappen, vier manieren waarop het bestand kan worden afgewezen, en geen enkele daarvan is zichtbaar voor een containercontrole.
Waarom PDF/A een kale aangepaste eigenschap weigert
De regel lijkt pedant totdat u zich herinnert waar PDF/A voor bedoeld is. Het formaat bestaat zodat een bestand tientallen jaren vanaf nu geopend en begrepen kan worden, door software die nooit iets is verteld over de conventies van 2026. Van een conforme lezer wordt verwacht dat hij het document begrijpt op basis van het document alleen, zonder een extern register te hoeven raadplegen.
Aangepaste metadata verbreekt die belofte, tenzij het bestand zijn eigen beschrijving met zich meedraagt. Gegeven een kale fx:ConformanceLevel-eigenschap, kan een toekomstige lezer niet weten aan welke namespace-URI het fx-voorvoegsel gebonden is, of de waarde tekst, een datum of een geheel getal is, of dat de eigenschap het document zelf of een externe bron beschrijft. Het mechanisme van het PDF/A-extensieschema dicht die kloof. Het laat het bestand in een vaste XMP-structuur de namespace, het voorvoegsel, en voor elke eigenschap een waardetype en een categorie van internal of external declareren. Zodra die declaratie aanwezig is, beschrijft de eigenschap zichzelf, en is voldaan aan clausule 6.6.2.3.1. Zonder dit heeft de validator geen andere keuze dan de eigenschap als onbegrijpelijk te behandelen en het bestand af te keuren. Het onderscheid in categorie is hier van belang: factuureigenschappen zoals deze beschrijven gegevens die van buiten de PDF-processor komen, dus ze worden als external gedeclareerd in plaats van internal.
Wat de extensieschemadeclaratie bevat
De declaratie is een rdf:Description in het XMP-pakket dat de drie door AIIM gedefinieerde namespaces pdfaExtension, pdfaSchema en pdfaProperty gebruikt. Binnen een pdfaExtension:schemas bag bevindt zich één schemavermelding die het Factur-X-schema benoemt, zijn pdfaSchema:namespaceURI en pdfaSchema:prefix geeft, en vervolgens de vier eigenschappen opsomt in een pdfaSchema:property-sequentie. Elke eigenschap bevat een naam, een pdfaProperty:valueType van Text en een pdfaProperty:category van external. De illustratieve opmaak hieronder toont de vorm van dat blok.
<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>naam van het ingesloten XML-factuurbestand</pdfaProperty:description>
</rdf:li>
<!-- DocumentType, Version, ConformanceLevel worden op dezelfde manier gedeclareerd -->
</rdf:Seq>
</pdfaSchema:property>
</rdf:li>
</rdf:Bag>
</pdfaExtension:schemas>
</rdf:Description>
De namespace-URI en het voorvoegsel zijn geen vaste tekenreeksen. Ze volgen het profiel. Een Factur-X-document gebruikt urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# met het fx-voorvoegsel, terwijl een ZUGFeRD 2.0-bestand dat is geselecteerd via zugferd-invoice.xml oplost naar een andere URI onder zijn eigen schemanaam. Het extensieschema moet dezelfde namespace-URI declareren als die het eigenschappenblok daadwerkelijk gebruikt, anders kan de validator de twee nog steeds niet met elkaar in verband brengen. PDFlibPas leidt beide waarden af van de bestandsnaam en de versie die u doorgeeft, zodat de declaratie en het eigenschappenblok altijd overeenkomen.
Hoe de helper beide helften samen schrijft
In PDFlibPas hoeft u die XML niet met de hand samen te stellen. U zet het document in een PDF/A-3-modus en roept één methode aan. Het eerste dat moet worden geregeld, is de conformiteitsvlag, omdat Factur-X PDF/A-3 vereist. Het aanroepen van SetPDFAMode(7) selecteert het PDF/A-3u-niveau, dat pdfaid:part instelt op 3 en pdfaid:conformance op U in het identificatieschema. Het XMP-pakket bevat nu het juiste onderdeel en conformiteit voordat er factuurmetadata wordt toegevoegd.
var
FileID: Integer;
begin
PDF.SetPDFAMode(7); // PDF/A-3u: pdfaid:part=3, conformance=U
PDF.NewDocument;
// teken de voor mensen leesbare factuurpagina hier
FileID := PDF.AddFacturXAssociatedFileFromString(
InvoiceXML, // ruwe UTF-8 XML bytes
'EN16931', // ConformanceLevel
'factur-x.xml', // naam van ingesloten bestand
'Factur-X invoice XML', // /Desc tekst
'Alternative', // /AFRelationship
'1.0', // profielversie
''); // optionele landcode
if FileID = 0 then
Exit; // geen PDF/A-3, of XML/profiel komen niet overeen
PDF.SaveToFile('factur-x.pdf');
end;
Een enkele aanroep naar AddFacturXAssociatedFileFromString doet het werk dat ontbrak in het falende bestand. Het sluit de XML in als een PDF/A-3 gekoppeld bestand met de relatie die u heeft opgegeven, en het registreert de vier fx-eigenschappen samen met de schemanaam, namespace-URI en het voorvoegsel voor het gekozen profiel. Wanneer het document wordt opgeslagen, injecteert een interne stap genaamd ApplyFacturXMetadata zowel het eigenschappenblok als de bijbehorende pdfaExtension:schemas declaratie in het XMP-pakket, zodat de aangepaste eigenschappen al beschreven arriveren. De methode retourneert 0 als het document zich niet in een PDF/A-3-modus bevindt of als de XML niet overeenkomt met het gedeclareerde profiel, wat dezelfde beveiliging is die voorkomt dat een onjuist gevormde factuur überhaupt in het bestand terechtkomt.
De blinde vlek die de containercontrole niet kan zien
Dit is het onderdeel dat duidelijk benoemd moet worden, omdat dit de reden is waarom de bug zich verbergt. ValidateFacturXInvoice controleert de container. Het bevestigt dat de catalogus een /AF-vermelding heeft, de naamstructuur van EmbeddedFiles aanwezig is, de XML van de factuur bestaat, de bestandsnaam van de bijlage overeenkomt met het profiel, de guideline-ID in de XML overeenkomt met het conformiteitsniveau, en of de /AFRelationship er één is die PDF/A-3 toestaat. Dat zijn echte controles en ze vangen echte defecten op. GetFacturXValidationIssues rapporteert ze bij naam, met identificatoren zoals MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship en InvalidFileNameProfile.
Wat het niet controleert, is of het XMP-extensieschema aanwezig en correct is. Een bestand waarvan de container foutloos is maar waarvan de fx-eigenschappen niet gedeclareerd zijn, doorstaat elke foutencontrole en retourneert 1, omdat niets in die lijst het pdfaExtension:schemas-blok inspecteert. Dat is precies de reden waarom een handmatig gemaakte factuur, of een factuur die is geproduceerd door een pijplijn die het eigenschappenblok zonder de declaratie heeft geschreven, moeiteloos door de ingebouwde validator kan komen en toch kan falen in veraPDF op clausule 6.6.2.3.1. De containervalidator en de PDF/A-metadatavalidator beantwoorden verschillende vragen, en alleen de volledige PDF/A-checker beantwoordt de tweede vraag.
Fouten lezen zodat u weet welke laag kapotging
Omdat de twee lagen onafhankelijk van elkaar falen, is de juiste diagnostische gewoonte om eerst de containerproblemen te lezen en een schoon resultaat alleen te behandelen als een uitspraak over de container, en nooit over PDF/A-metadata. Voer de ingebouwde validatie uit, verzamel de lijst met problemen, en onderneem actie voordat u naar een externe tool grijpt.
var
Issues: WideString;
begin
if PDF.ValidateFacturXInvoice = 0 then
begin
Issues := PDF.GetFacturXValidationIssues('|');
// identifiers op containerniveau, bijvoorbeeld:
// MissingCatalogAF, NotPDFA3, MissingEmbeddedFilesNameTree,
// ConformanceGuidelineMismatch, InvalidAFRelationship
WriteLn('Container issues: ', Issues);
end
else
WriteLn('Container OK; controleer het XMP-extensieschema met een PDF/A-checker.');
end;
Wanneer die aanroep een foutnaam retourneert, zit de fout in de container en het bericht vertelt u in welk onderdeel. Wanneer deze schoon retourneert en veraPDF het bestand nog steeds afwijst, is de fout bijna altijd het XMP-extensieschema, en de oplossing is om AddFacturXAssociatedFileFromString de metadata te laten schrijven in plaats van zelf het eigenschappenblok samen te stellen. De twee vragen gescheiden houden in uw eigen hoofd is wat een verbijsterende afwijzing in een diagnose van één regel verandert: containerproblemen komen aan de oppervlakte via de foutenlijst, schemadeclaratieproblemen komen alleen aan de oppervlakte via een PDF/A-validator, en het verwarren van de twee is wat de bug doet verbergen.
Het bredere plaatje voor PDF/A- en PDF/UA-conformiteit, inclusief hoe u een preflight-controle uitvoert voordat een bestand uw build verlaat, wordt behandeld in de walkthrough voor PDF/A- en PDF/UA-preflight. Als uw factuur ook toegankelijk moet zijn, is de structuurboom waarvan PDF/A-3a en getagde PDF afhankelijk zijn het onderwerp van het artikel over toegankelijkheid van getagde PDF's. De hier beschreven afhandeling van extensieschema's wordt meegeleverd als onderdeel van de PDFlibPas Delphi PDF Library naast de ondersteuning voor Factur-X-, ZUGFeRD- en XRechnung-profielen die op dit blog worden gedocumenteerd.