Teknisk artikkel

PDF/A-3-utvidelsesskjemaer for Factur-X XMP i Delphi

Du har bygget en Factur-X-faktura, og hver eneste beholdersjekk går gjennom. Katalogen bærer et /AF-array, navnetreet EmbeddedFiles peker til riktig filspesifikasjon, den innebygde factur-x.xml har riktig /AFRelationship med verdien Alternative, og den innebygde ValidateFacturXInvoice returnerer 1. Så kjører du den samme filen gjennom veraPDF, referansekontrolløren skatteportalene bruker, og den slår fast at hele dokumentet ikke er en gyldig PDF/A-3. Strukturen er riktig. Metadataene er problemet, og feilen er en av de aller letteste å overse i hele arbeidsflyten for e-faktura

Grunnen er verdt å forstå fullt ut, for den forklarer en klasse PDF/A-defekter som ikke har noe med den synlige siden eller vedlegget å gjøre, og alt å gjøre med hvordan XMP beskriver seg selv. Dette er fellen som gjemmer seg bak en grønn beholdersjekk

De fire egenskapene som feller filen

En Factur-X-faktura skriver fire egendefinerte egenskaper inn i XMP-pakken sin slik at programvare lenger ned i kjeden kan lese fakturaprofilen uten å tolke den innebygde XML-en. De ligger i Factur-X-navnerommet under prefikset fx: fx:DocumentFileName, fx:DocumentType, fx:Version og fx:ConformanceLevel. Det er nøyaktig de metadataene en leser trenger for å vite at denne PDF-en bærer en EN 16931-faktura ved navn factur-x.xml i versjon 1.0

Ingen av de fire egenskapene er del av noe XMP-skjema som PDF/A definerer på forhånd. Skjemaene Dublin Core, XMP Basic, PDF og PDF/A-identifikasjon er kjent for en konform leser, men fx: er det ikke. Når veraPDF går gjennom XMP-en og kommer til en egenskap i et navnerom den ikke kjenner igjen, leter den etter en deklarasjon som forteller hva egenskapen betyr. Er den deklarasjonen fraværende, rapporterer den et avvik mot ISO 19005-3 punkt 6.6.2.3.1, som krever at enhver egenskap som ikke er hentet fra et forhåndsdefinert skjema, beskrives i et PDF/A-utvidelsesskjema. Fire udeklarerte egenskaper, fire måter å få filen avvist på, og ikke én av dem er synlig for en beholdersjekk

PDF Library for Delphi: En validator søker i de fire forhåndsdefinerte XMP-skjemaene og finner ikke noe PDF/A-utvidelsesskjema for fx-egenskapene, så Factur-X-fakturaen feiler
veraPDF jakter på en skjemadeklarasjon filen aldri skrev — fire fx-egenskaper, fire sjanser til å feile punkt 6.6.2.3.1

Hvorfor PDF/A avviser en naken egendefinert egenskap

Regelen virker pedantisk helt til du husker hva PDF/A er til for. Formatet finnes for at en fil skal kunne åpnes og forstås om flere tiår, av programvare som aldri fikk høre om konvensjonene fra 2026. En konform leser forventes å få mening ut av dokumentet ut fra dokumentet alene, uten noe eksternt register å slå opp i

Egendefinerte metadata bryter det løftet med mindre filen bærer sin egen beskrivelse. Gitt en naken fx:ConformanceLevel-egenskap kan en fremtidig leser ikke vite hvilken navneroms-URI prefikset fx binder seg til, om verdien er tekst, dato eller heltall, eller om egenskapen beskriver selve dokumentet eller en ekstern ressurs. Mekanismen med PDF/A-utvidelsesskjema tetter det gapet. Den lar filen deklarere, i en fast XMP-struktur, navnerommet, prefikset og for hver egenskap en verditype og en kategori som er enten internal eller external. Når den deklarasjonen er på plass, beskriver egenskapen seg selv, og punkt 6.6.2.3.1 er oppfylt. Uten den har validatoren ikke annet valg enn å behandle egenskapen som uforståelig og forkaste filen. Skillet mellom kategoriene betyr noe her: fakturaegenskaper som disse beskriver data som kommer utenfra PDF-prosessoren, så de deklareres som external og ikke internal

Hva utvidelsesskjema-deklarasjonen inneholder

Deklarasjonen er en rdf:Description i XMP-pakken som bruker de tre AIIM-definerte navnerommene pdfaExtension, pdfaSchema og pdfaProperty. Inne i en pdfaExtension:schemas-bag ligger én skjemaoppføring som navngir Factur-X-skjemaet, oppgir pdfaSchema:namespaceURI og pdfaSchema:prefix, og deretter lister de fire egenskapene i en pdfaSchema:property-sekvens. Hver egenskap bærer et navn, en pdfaProperty:valueType lik Text og en pdfaProperty:category lik external. Den illustrerende oppmerkingen nedenfor viser formen på den blokken

Anatomien til PDF/A-3-utvidelsesskjemaet i PDF Library for Delphi som deklarerer Factur-X-skjemaet med navneroms-URI, fx-prefiks og fire eksterne Text-egenskaper
Hver påstand deklarasjonen kommer med må stemme med den fx-blokken fakturaen faktisk skriver, ellers avviser validatoren filen likevel
<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 og ConformanceLevel deklareres på samme måte -->
          </rdf:Seq>
        </pdfaSchema:property>
      </rdf:li>
    </rdf:Bag>
  </pdfaExtension:schemas>
</rdf:Description>

Navneroms-URI-en og prefikset er ikke faste strenger. De følger profilen. Et Factur-X-dokument bruker urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# med prefikset fx, mens en ZUGFeRD 2.0-fil valgt gjennom zugferd-invoice.xml løses opp til en annen URI under sitt eget skjemanavn. Utvidelsesskjemaet må deklarere den samme navneroms-URI-en som egenskapsblokken faktisk bruker, ellers klarer validatoren fortsatt ikke å koble de to sammen. PDF Library for Delphi utleder begge verdiene fra filnavnet og versjonen du sender inn, så deklarasjonen og egenskapsblokken er alltid enige

Hvordan hjelperen skriver begge halvdelene samtidig

I PDF Library for Delphi setter du ikke sammen den XML-en for hånd. Du setter dokumentet i en PDF/A-3-modus og kaller én metode. Det første som må på plass er konformansflagget, for Factur-X krever PDF/A-3. Kallet SetPDFAMode(7) velger nivået PDF/A-3u, som setter pdfaid:part til 3 og pdfaid:conformance til U i identifikasjonsskjemaet. XMP-pakken bærer nå riktig del og konformans før noen fakturametadata legges til

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(7);            // PDF/A-3u: pdfaid:part=3, conformance=U
  PDF.NewDocument;
  // tegn den menneskelesbare fakturasiden her

  FileID := PDF.AddFacturXAssociatedFileFromString(
    InvoiceXML,                  // rå UTF-8 XML-byte
    'EN16931',                   // ConformanceLevel
    'factur-x.xml',              // navn på den innebygde filen
    'Factur-X invoice XML',      // /Desc-tekst
    'Alternative',               // /AFRelationship
    '1.0',                       // profilversjon
    '');                         // valgfri landkode
  if FileID = 0 then
    Exit;                        // ikke PDF/A-3, eller feil XML/profil

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

Ett enkelt kall til AddFacturXAssociatedFileFromString gjør den jobben den feilende filen manglet. Det bygger inn XML-en som en tilknyttet PDF/A-3-fil med relasjonen du navnga, og det registrerer de fire fx-egenskapene sammen med skjemanavnet, navneroms-URI-en og prefikset for den valgte profilen. Når dokumentet lagres, sprøyter et internt trinn ved navn ApplyFacturXMetadata både egenskapsblokken og den tilhørende pdfaExtension:schemas-deklarasjonen inn i XMP-pakken, slik at de egendefinerte egenskapene ankommer ferdig beskrevet. Metoden returnerer 0 hvis dokumentet ikke er i en PDF/A-3-modus, eller hvis XML-en ikke stemmer med den deklarerte profilen, som er den samme vakten som hindrer en feilformet faktura i å nå filen i det hele tatt

Blindsonen beholdersjekken ikke kan se

Dette er delen det er verdt å si rett ut, for det er grunnen til at feilen skjuler seg. ValidateFacturXInvoice sjekker beholderen. Den bekrefter at katalogen har en /AF-oppføring, at navnetreet EmbeddedFiles finnes, at faktura-XML-en er der, at navnet på den innebygde filen stemmer med profilen, at retningslinje-ID-en i XML-en er enig med konformansnivået, og at /AFRelationship er en av dem PDF/A-3 tillater. Det er reelle sjekker, og de fanger reelle defekter. GetFacturXValidationIssues rapporterer dem ved navn, med identifikatorer som MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship og InvalidFileNameProfile

Det den ikke sjekker, er om XMP-utvidelsesskjemaet finnes og er riktig. En fil med feilfri beholder, men med udeklarerte fx-egenskaper, går gjennom hver eneste avvikskontroll og returnerer 1, fordi ingenting i den listen inspiserer pdfaExtension:schemas-blokken. Det er nettopp derfor en håndbygd faktura, eller en som er laget av en løype som skrev egenskapsblokken uten deklarasjonen, kan seile gjennom den innebygde validatoren og likevel feile i veraPDF på punkt 6.6.2.3.1. Beholdervalidatoren og PDF/A-metadatavalidatoren svarer på ulike spørsmål, og bare den fullstendige PDF/A-kontrolløren svarer på det andre

PDF Library for Delphi: Den samme Factur-X-fakturaen går gjennom hver beholdersjekk i ValidateFacturXInvoice mens veraPDF avviser den fordi ingen lag leser pdfaExtension-skjemablokken
Beholdervalidatoren og PDF/A-validatoren svarer på ulike spørsmål om den samme filen

Lese avvik så du vet hvilket lag som brast

Fordi de to lagene feiler uavhengig av hverandre, er den riktige diagnosevanen å lese beholderavvikene først og behandle et rent resultat som en uttalelse om beholderen alene, aldri om PDF/A-metadata. Kjør den innebygde valideringen, samle avvikslisten, og handle på den før du griper til et eksternt verktøy

var
  Issues: WideString;
begin
  if PDF.ValidateFacturXInvoice = 0 then
  begin
    Issues := PDF.GetFacturXValidationIssues('|');
    // identifikatorer på beholdernivå, for eksempel:
    //   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 kallet returnerer et avviksnavn, ligger feilen i beholderen, og meldingen forteller deg hvilken del. Når det returnerer rent og veraPDF likevel avviser filen, ligger feilen nesten alltid i XMP-utvidelsesskjemaet, og løsningen er å la AddFacturXAssociatedFileFromString skrive metadataene i stedet for at du bygger egenskapsblokken selv. Å holde de to spørsmålene adskilt i eget hode er det som gjør en forvirrende avvisning om til en diagnose på én linje: beholderproblemer dukker opp i avvikslisten, problemer med skjemadeklarasjonen dukker bare opp gjennom en PDF/A-validator, og å blande de to er det som lar feilen gjemme seg

Det bredere bildet av PDF/A- og PDF/UA-konformans, inkludert hvordan du kjører en preflight-gjennomgang før en fil forlater bygget ditt, er dekket i gjennomgangen av PDF/A- og PDF/UA-preflight. Må fakturaen din også være tilgjengelig, er strukturtreet som PDF/A-3a og tagget PDF hviler på, temaet i artikkelen om tilgjengelighet med tagget PDF. Håndteringen av utvidelsesskjemaer som er beskrevet her, følger med PDF Library for Delphi ved siden av støtten for Factur-X-, ZUGFeRD- og XRechnung-profilene som er dokumentert over hele denne bloggen