Teknisk artikel

PDF/A-3-udvidelsesskemaer til Factur-X XMP i Delphi

Du har bygget en Factur-X-faktura, og alle containerkontroller består. Kataloget bærer et /AF-array, navnetræet EmbeddedFiles opløses til den rigtige filspecifikation, den indlejrede factur-x.xml har den korrekte /AFRelationship på Alternative, og den indbyggede ValidateFacturXInvoice returnerer 1. Så kører du den samme fil gennem veraPDF, den referencechecker skatteportaler bruger, og den afgør, at hele dokumentet ikke er en gyldig PDF/A-3. Strukturen er rigtig. Metadataene er problemet, og fejlen er en af de letteste i hele e-fakturaarbejdsgangen at overse

Grunden er værd at forstå til bunds, for den forklarer en klasse af PDF/A-defekter, der intet har at gøre med den synlige side eller vedhæftningen og alt at gøre med, hvordan XMP beskriver sig selv. Det er den fælde, der gemmer sig bag en grøn containerkontrol

De fire egenskaber, der får filen til at dumpe

En Factur-X-faktura skriver fire brugerdefinerede egenskaber ind i sin XMP-pakke, så software længere nede i kæden kan læse fakturaprofilen uden at parse den indlejrede XML. De ligger i Factur-X-namespacet under præfikset fx: fx:DocumentFileName, fx:DocumentType, fx:Version og fx:ConformanceLevel. De er præcis de metadata, en læser skal bruge for at vide, at denne PDF bærer en EN 16931-faktura ved navn factur-x.xml i version 1.0

Ingen af de fire egenskaber er en del af noget XMP-skema, som PDF/A foruddefinerer. Dublin Core-, XMP Basic-, PDF- og PDF/A-identifikationsskemaerne er kendt af en konform læser, men det er fx: ikke. Når veraPDF gennemgår XMP'en og når en egenskab, hvis namespace den ikke genkender, leder den efter en deklaration, der ville fortælle den, hvad egenskaben betyder. Hvis den deklaration mangler, rapporterer den en fejl mod ISO 19005-3 klausul 6.6.2.3.1, som kræver, at enhver egenskab, der ikke er hentet fra et foruddefineret skema, beskrives i et PDF/A-udvidelsesskema. Fire udeklarerede egenskaber, fire måder at få filen afvist på, og ikke én af dem er synlig for en containerkontrol

PDF Library for Delphi: En validator gennemsøger de fire foruddefinerede XMP-skemaer og finder intet PDF/A-udvidelsesskema for fx-egenskaberne, så Factur-X-fakturaen dumper
veraPDF jagter en skemadeklaration, filen aldrig skrev — fire fx-egenskaber, fire chancer for at dumpe klausul 6.6.2.3.1

Hvorfor PDF/A afviser en nøgen brugerdefineret egenskab

Reglen virker pedantisk, indtil du husker, hvad PDF/A er til for. Formatet findes, for at en fil kan åbnes og forstås om årtier af software, der aldrig har fået noget at vide om konventionerne i 2026. En konform læser forventes at kunne forstå dokumentet ud fra dokumentet alene, uden noget eksternt register at konsultere

Brugerdefinerede metadata bryder det løfte, medmindre filen bærer sin egen beskrivelse. Stillet over for en nøgen fx:ConformanceLevel-egenskab kan en fremtidig læser ikke vide, hvilken namespace-URI præfikset fx er bundet til, om værdien er tekst, en dato eller et heltal, eller om egenskaben beskriver selve dokumentet eller en ekstern ressource. PDF/A-udvidelsesskemamekanismen lukker det hul. Den lader filen deklarere, i en fast XMP-struktur, namespacet, præfikset og for hver egenskab en værditype og en kategori på internal eller external. Når den deklaration er til stede, er egenskaben selvbeskrivende, og klausul 6.6.2.3.1 er opfyldt. Uden den har validatoren intet andet valg end at behandle egenskaben som uforståelig og lade filen dumpe. Kategoriskellet betyder noget her: fakturaegenskaber som disse beskriver data, der kommer udefra PDF-processoren, så de deklareres som external frem for internal

Hvad udvidelsesskema-deklarationen indeholder

Deklarationen er en rdf:Description i XMP-pakken, der bruger de tre AIIM-definerede namespaces pdfaExtension, pdfaSchema og pdfaProperty. Inde i en pdfaExtension:schemas-bag sidder én skemapost, der navngiver Factur-X-skemaet, angiver dets pdfaSchema:namespaceURI og pdfaSchema:prefix og derefter opregner de fire egenskaber i en pdfaSchema:property-sekvens. Hver egenskab bærer et navn, en pdfaProperty:valueType på Text og en pdfaProperty:category på external. Den illustrative markup nedenfor viser formen på den blok

PDF Library for Delphi anatomi af PDF/A-3-udvidelsesskemaet, der deklarerer Factur-X-skemaet med dets namespace-URI, fx-præfiks og fire eksterne Text-egenskaber
Hver påstand, deklarationen fremsætter, skal matche den fx-blok, fakturaen faktisk skriver, ellers afviser validatoren stadig filen
<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 deklareres på samme måde -->
          </rdf:Seq>
        </pdfaSchema:property>
      </rdf:li>
    </rdf:Bag>
  </pdfaExtension:schemas>
</rdf:Description>

Namespace-URI'en og præfikset er ikke faste strenge. De følger profilen. Et Factur-X-dokument bruger urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# med præfikset fx, mens en ZUGFeRD 2.0-fil valgt via zugferd-invoice.xml opløses til en anden URI under sit eget skemanavn. Udvidelsesskemaet skal deklarere den samme namespace-URI, som egenskabsblokken faktisk bruger, ellers kan validatoren stadig ikke forbinde de to. PDF Library for Delphi udleder begge værdier af det filnavn og den version, du sender ind, så deklarationen og egenskabsblokken altid stemmer overens

Sådan skriver hjælperen begge halvdele sammen

I PDF Library for Delphi samler du ikke den XML i hånden. Du sætter dokumentet i en PDF/A-3-tilstand og kalder én metode. Det første, der skal afklares, er konformitetsflaget, fordi Factur-X kræver PDF/A-3. Et kald til SetPDFAMode(7) vælger niveauet PDF/A-3u, som sætter pdfaid:part til 3 og pdfaid:conformance til U i identifikationsskemaet. XMP-pakken bærer nu den rigtige del og konformitet, før der tilføjes nogen fakturametadata

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

  FileID := PDF.AddFacturXAssociatedFileFromString(
    InvoiceXML,                  // rå UTF-8-XML-bytes
    'EN16931',                   // ConformanceLevel
    'factur-x.xml',              // indlejret filnavn
    'Factur-X invoice XML',      // /Desc-tekst
    'Alternative',               // /AFRelationship
    '1.0',                       // profilversion
    '');                         // valgfri landekode
  if FileID = 0 then
    Exit;                        // ikke PDF/A-3, eller XML/profil stemmer ikke

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

Et enkelt kald til AddFacturXAssociatedFileFromString udfører det arbejde, den dumpede fil manglede. Det indlejrer XML'en som en PDF/A-3 associated file med den relation, du navngav, og det registrerer de fire fx-egenskaber sammen med skemanavnet, namespace-URI'en og præfikset for den valgte profil. Når dokumentet gemmes, injicerer et internt trin ved navn ApplyFacturXMetadata både egenskabsblokken og den matchende pdfaExtension:schemas-deklaration i XMP-pakken, så de brugerdefinerede egenskaber ankommer allerede beskrevet. Metoden returnerer 0, hvis dokumentet ikke er i en PDF/A-3-tilstand, eller hvis XML'en ikke matcher den deklarerede profil, hvilket er den samme vagt, der i første omgang forhindrer en misdannet faktura i at nå filen

Den blinde vinkel, containerkontrollen ikke kan se

Dette er den del, der skal siges ligeud, for det er grunden til, at fejlen gemmer sig. ValidateFacturXInvoice kontrollerer containeren. Den bekræfter, at kataloget har en /AF-post, at navnetræet EmbeddedFiles er til stede, at faktura-XML'en findes, at det indlejrede filnavn matcher profilen, at guideline-id'et i XML'en stemmer overens med konformitetsniveauet, og at /AFRelationship er en, PDF/A-3 tillader. Det er reelle kontroller, og de fanger reelle defekter. GetFacturXValidationIssues rapporterer dem ved navn med identifikatorer som MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship og InvalidFileNameProfile

Det, den ikke kontrollerer, er, om XMP-udvidelsesskemaet er til stede og korrekt. En fil, hvis container er fejlfri, men hvis fx-egenskaber er udeklarerede, består hver eneste issue-kontrol og returnerer 1, fordi intet på den liste inspicerer pdfaExtension:schemas-blokken. Det er præcis derfor, en håndbygget faktura, eller en produceret af en pipeline, der skrev egenskabsblokken uden deklarationen, kan glide gennem den indbyggede validator og stadig dumpe veraPDF på klausul 6.6.2.3.1. Containervalidatoren og PDF/A-metadatavalidatoren besvarer forskellige spørgsmål, og kun den fulde PDF/A-checker besvarer det andet

PDF Library for Delphi: Den samme Factur-X-faktura består hver containerkontrol i ValidateFacturXInvoice, mens veraPDF afviser den, fordi intet lag læser pdfaExtension-schemas-blokken
Containervalidatoren og PDF/A-validatoren besvarer forskellige spørgsmål om den samme fil

Læsning af issues, så du ved, hvilket lag der gik i stykker

Fordi de to lag fejler uafhængigt af hinanden, er den rigtige diagnostiske vane at læse container-issues først og behandle et rent resultat som et udsagn om containeren alene, aldrig om PDF/A-metadata. Kør den indbyggede validering, indsaml issue-listen, og handl på den, før du griber til et eksternt værktøj

var
  Issues: WideString;
begin
  if PDF.ValidateFacturXInvoice = 0 then
  begin
    Issues := PDF.GetFacturXValidationIssues('|');
    // identifikatorer på containerniveau, 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 kald returnerer et issue-navn, ligger fejlen i containeren, og meddelelsen fortæller dig, hvilken del. Når det returnerer rent, og veraPDF stadig afviser filen, er fejlen næsten altid XMP-udvidelsesskemaet, og rettelsen er at lade AddFacturXAssociatedFileFromString skrive metadataene i stedet for selv at konstruere egenskabsblokken. At holde de to spørgsmål adskilt i dit eget hoved er det, der forvandler en forvirrende afvisning til en diagnose på én linje: containerproblemer dukker op gennem issue-listen, skemadeklarationsproblemer dukker kun op gennem en PDF/A-validator, og at forveksle de to er det, der lader fejlen gemme sig

Det bredere billede af PDF/A- og PDF/UA-konformitet, herunder hvordan du kører en preflight-gennemgang, før en fil forlader dit build, er dækket i gennemgangen af PDF/A- og PDF/UA-preflight. Hvis din faktura også skal være tilgængelig, er det strukturtræ, som PDF/A-3a og tagged PDF afhænger af, emnet for artiklen om tilgængelighed med tagged PDF. Den udvidelsesskema-håndtering, der er beskrevet her, leveres som en del af PDF Library for Delphi Delphi PDF Library sammen med den Factur-X-, ZUGFeRD- og XRechnung-profilunderstøttelse, der er dokumenteret på tværs af denne blog