Du har byggt en Factur-X-faktura och varje containerkontroll är godkänd. Katalogen bär en /AF-array, namnträdet för EmbeddedFiles löser upp till rätt filspecifikation, den inbäddade factur-x.xml har korrekt /AFRelationship som Alternative, och den inbyggda ValidateFacturXInvoice returnerar 1. Sedan kör du samma fil genom veraPDF, referenskontrollanten som skatteportaler använder, och den bedömer att hela dokumentet inte är en giltig PDF/A-3. Strukturen är rätt. Metadatan är problemet, och felet är ett av de enklaste att missa i hela e-fakturaarbetsflödet
Orsaken är värd att förstå fullt ut, eftersom den förklarar en klass av PDF/A-fel som inte har något att göra med den synliga sidan eller bilagan, utan enbart handlar om hur XMP beskriver sig självt. Det här är fällan som gömmer sig bakom en grön containerkontroll
De fyra egenskaperna som fäller filen
En Factur-X-faktura skriver in fyra anpassade egenskaper i sitt XMP-paket så att nedströms programvara kan läsa fakturaprofilen utan att tolka (parse) den inbäddade XML:en. De bor i Factur-X-namnrymden under fx-prefixet: fx:DocumentFileName, fx:DocumentType, fx:Version och fx:ConformanceLevel. De utgör exakt den metadata som en läsare behöver för att veta att denna PDF bär en EN 16931-faktura som heter factur-x.xml på version 1.0
Ingen av de fyra egenskaperna ingår i något XMP-schema som PDF/A fördefinierar. Schemaidentifieringarna för Dublin Core, XMP Basic, PDF och PDF/A är kända för en konform läsare, men fx: är det inte. När veraPDF går igenom XMP:n och når en egenskap vars namnrymd den inte känner igen, letar den efter en deklaration som talar om vad egenskapen betyder. Om den deklarationen saknas, rapporterar den ett fel mot ISO 19005-3 paragraf 6.6.2.3.1, som kräver att varje egenskap som inte hämtats från ett fördefinierat schema måste beskrivas i ett PDF/A-tilläggsschema (extension schema). Fyra odeklarerade egenskaper innebär fyra sätt för filen att bli avvisad på, och inte ett enda av dem är synligt för en containerkontroll
Varför PDF/A vägrar en naken anpassad egenskap
Regeln framstår som pedantisk tills man minns vad PDF/A är till för. Formatet existerar så att en fil kan öppnas och förstås om årtionden, av programvara som aldrig informerades om konventionerna från 2026. En konform läsare förväntas bli klok på dokumentet utifrån dokumentet i sig, utan att rådfråga något externt register
Anpassad metadata bryter det löftet om inte filen bär med sig sin egen beskrivning. Med en naken fx:ConformanceLevel-egenskap kan en framtida läsare inte veta vilken namnrymds-URI som fx-prefixet binder till, huruvida värdet är text, ett datum eller ett heltal, eller om egenskapen beskriver dokumentet självt eller någon extern resurs. Mekanismen för PDF/A-tilläggsscheman stänger den klyftan. Den låter filen deklarera, i en fast XMP-struktur, namnrymd, prefix och för varje egenskap en värdetyp och en kategori av internal eller external. När den deklarationen väl finns på plats är egenskapen självbeskrivande, och paragraf 6.6.2.3.1 är uppfylld. Utan den har valideraren inget annat val än att behandla egenskapen som obegriplig och fälla filen. Kategoriseringen är viktig här: fakturaegenskaper som dessa beskriver data som kommer utifrån PDF-processorn, så de deklareras som external snarare än internal
Vad deklarationen för tilläggsschemat innehåller
Deklarationen är en rdf:Description i XMP-paketet som använder de tre AIIM-definierade namnrymderna pdfaExtension, pdfaSchema och pdfaProperty. Inuti en pdfaExtension:schemas-påse (bag) sitter en schemapost som namnger Factur-X-schemat, anger dess pdfaSchema:namespaceURI och pdfaSchema:prefix, och sedan listar de fyra egenskaperna i en pdfaSchema:property-sekvens. Varje egenskap bär ett namn, en pdfaProperty:valueType av Text, och en pdfaProperty:category av external. Den illustrativa koden (markup) nedan visar formen på det blocket
<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>
Namnrymds-URI:n och prefixet är inga fasta strängar. De följer profilen. Ett Factur-X-dokument använder urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# med fx-prefixet, medan en ZUGFeRD 2.0-fil vald genom zugferd-invoice.xml löser upp till en annan URI under sitt eget schemanamn. Tilläggsschemat måste deklarera samma namnrymds-URI som egenskapsblocket faktiskt använder, annars kan valideraren fortfarande inte koppla ihop de två. PDFlibPas härleder båda värdena från filnamnet och versionen du skickar med, så deklarationen och egenskapsblocket stämmer alltid överens
Hur hjälparen skriver båda halvorna tillsammans
I PDFlibPas sätter du inte ihop den XML:en för hand. Du sätter dokumentet i ett PDF/A-3-läge och anropar en metod. Det första att ordna är konformitetsflaggan, eftersom Factur-X kräver PDF/A-3. Att anropa SetPDFAMode(7) väljer PDF/A-3u-nivån, vilket sätter pdfaid:part till 3 och pdfaid:conformance till U i identifieringsschemat. XMP-paketet bär nu rätt del och efterlevnad innan någon fakturameta läggs till
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;
Ett enda anrop till AddFacturXAssociatedFileFromString gör det arbete som saknades i den fällda filen. Den bäddar in XML:en som en tillhörande PDF/A-3-fil med den relation du namngav, och den registrerar de fyra fx-egenskaperna tillsammans med schemanamn, namnrymds-URI och prefix för den valda profilen. När dokumentet sparas, injicerar ett internt steg benämnt ApplyFacturXMetadata både egenskapsblocket och den matchande pdfaExtension:schemas-deklarationen in i XMP-paketet, så de anpassade egenskaperna anländer redan beskrivna. Metoden returnerar 0 om dokumentet inte befinner sig i ett PDF/A-3-läge eller om XML:en inte matchar den deklarerade profilen, vilket är samma skydd som hindrar en felformulerad faktura från att nå filen från allra första början
Den döda vinkeln som containerkontrollen inte kan se
Det här är delen att benämna tydligt, för det är anledningen till att buggen gömmer sig. ValidateFacturXInvoice kontrollerar containern. Den bekräftar att katalogen har en /AF-post, att namnträdet för EmbeddedFiles är närvarande, att fakturans XML existerar, att det inbäddade filnamnet matchar profilen, att guideline-ID:t i XML:en stämmer överens med efterlevnadsnivån, och att /AFRelationship är en som PDF/A-3 tillåter. Det är verkliga kontroller och de fångar verkliga brister. GetFacturXValidationIssues rapporterar dem vid namn, med identifierare som MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship och InvalidFileNameProfile
Vad den inte kontrollerar är huruvida XMP-tilläggsschemat är närvarande och korrekt. En fil vars container är fläckfri men vars fx-egenskaper är odeklarerade passerar varje problemkontroll och returnerar 1, eftersom inget i den listan inspekterar pdfaExtension:schemas-blocket. Det är exakt därför en handbyggd faktura, eller en som producerats av en pipeline som skrev egenskapsblocket utan deklarationen, kan segla igenom den inbyggda valideraren och ändå fällas av veraPDF på paragraf 6.6.2.3.1. Containervalideraren och PDF/A-metadatavalideraren svarar på olika frågor, och endast den fullständiga PDF/A-kontrollanten svarar på den andra
Att läsa problem (issues) så att du vet vilket lager som gick sönder
Eftersom de två lagren misslyckas oberoende av varandra, är den rätta diagnostiska vanan att läsa containerproblemen först och behandla ett rent resultat som ett uttalande enbart om containern, aldrig om PDF/A-metadatan. Kör den inbyggda valideringen, samla in problemlistan och agera på den innan du sträcker dig efter ett externt verktyg
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;
När det anropet returnerar ett problemnamn (issue name) ligger felet i containern, och meddelandet talar om vilken del. När det returnerar rent och veraPDF ändå avvisar filen, är felet nästan alltid XMP-tilläggsschemat, och lösningen är att låta AddFacturXAssociatedFileFromString skriva metadatan i stället för att konstruera egenskapsblocket själv. Att hålla de två frågorna separerade i ditt eget sinne är det som förvandlar en förbryllande avvisning till en enradsdiagnos: containerproblem kommer upp till ytan genom problemlistan, schema-deklarationsproblem dyker bara upp via en PDF/A-validerare, och det är sammanblandningen av de två som låter buggen gömma sig
Den bredare bilden av PDF/A- och PDF/UA-efterlevnad, inklusive hur man kör ett preflight-pass innan en fil lämnar ditt bygge (build), täcks i genomgången av PDF/A- och PDF/UA-preflight. Om din faktura också måste vara tillgänglig, är strukturträdet som PDF/A-3a och taggad PDF förlitar sig på ämnet för artikeln om taggad PDF för tillgänglighet. Hanteringen av tilläggsscheman som beskrivs här levereras som en del av PDFlibPas Delphi PDF Library vid sidan av stödet för Factur-X-, ZUGFeRD- och XRechnung-profiler som dokumenteras runtom på den här bloggen