Technisch artikel

PDF/A-3-extensieschemas voor Factur-X XMP in Delphi

U hebt een Factur-X-factuur gebouwd en elke controle op de container slaagt. De catalog draagt een /AF-array, de naamboom EmbeddedFiles verwijst naar de juiste bestandsspecificatie, de ingesloten factur-x.xml heeft de correcte /AFRelationship van Alternative, en de ingebouwde ValidateFacturXInvoice retourneert 1. Vervolgens haalt u datzelfde bestand door veraPDF, de referentiecontroleur die belastingportalen gebruiken, en die oordeelt dat het hele document geen geldige PDF/A-3 is. De structuur klopt. De metadata is het probleem, en die fout is een van de makkelijkst te missen in de hele e-factureringsworkflow

De reden is het waard om volledig te begrijpen, want zij verklaart een klasse van PDF/A-defecten die niets met de zichtbare pagina of de bijlage te maken heeft en alles met de manier waarop XMP zichzelf beschrijft. Dit is de valkuil die zich achter een groene containercontrole verbergt

De vier eigenschappen die het bestand laten zakken

Een Factur-X-factuur schrijft vier aangepaste eigenschappen in haar XMP-pakket zodat software verderop het factuurprofiel kan lezen zonder de ingesloten XML te parsen. Ze wonen in de Factur-X-namespace onder het voorvoegsel fx: fx:DocumentFileName, fx:DocumentType, fx:Version en fx:ConformanceLevel. Ze zijn precies de metadata die een lezer nodig heeft om te weten dat deze PDF een EN 16931-factuur met de naam factur-x.xml op versie 1.0 draagt

Geen van die vier eigenschappen maakt deel uit van enig XMP-schema dat PDF/A vooraf definieert. De identificatieschemas Dublin Core, XMP Basic, PDF en PDF/A zijn bekend bij een conforme lezer, maar fx: niet. Wanneer veraPDF de XMP doorloopt en bij een eigenschap komt waarvan het de namespace niet herkent, zoekt het naar een declaratie die zou vertellen wat de eigenschap betekent. Ontbreekt die declaratie, dan meldt het een fout tegen ISO 19005-3 clausule 6.6.2.3.1, die vereist dat elke eigenschap die niet uit een vooraf gedefinieerd schema komt, in een PDF/A-extensieschema wordt beschreven. Vier niet-gedeclareerde eigenschappen, vier manieren waarop het bestand kan worden afgewezen, en geen enkele daarvan is zichtbaar voor een containercontrole

PDF Library for Delphi: een validator doorzoekt de vier vooraf gedefinieerde XMP-schemas en vindt geen PDF/A-extensieschema voor de fx-eigenschappen, dus de Factur-X-factuur zakt
veraPDF jaagt op een schemadeclaratie die het bestand nooit heeft geschreven — vier fx-eigenschappen, vier kansen om te zakken voor clausule 6.6.2.3.1

Waarom PDF/A een kale aangepaste eigenschap weigert

De regel oogt muggenzifterig totdat u zich herinnert waar PDF/A voor bestaat. Het formaat bestaat opdat een bestand over tientallen jaren kan worden geopend en begrepen, door software die nooit iets over de conventies van 2026 is verteld. Van een conforme lezer wordt verwacht dat hij het document uit het document alleen begrijpt, zonder een extern register te raadplegen

Aangepaste metadata breekt die belofte tenzij het bestand zijn eigen beschrijving meedraagt. Gegeven een kale fx:ConformanceLevel-eigenschap kan een toekomstige lezer niet weten aan welke namespace-URI het voorvoegsel fx is gebonden, of de waarde tekst, een datum of een geheel getal is, of dat de eigenschap het document zelf beschrijft dan wel een externe bron. Het mechanisme van het PDF/A-extensieschema dicht dat gat. Het laat het bestand in een vaste XMP-structuur de namespace, het voorvoegsel en per eigenschap een waardetype en een categorie internal of external declareren. Zodra die declaratie aanwezig is, is de eigenschap zelfbeschrijvend en is aan clausule 6.6.2.3.1 voldaan. Zonder die declaratie heeft de validator geen keuze dan de eigenschap als onbegrijpelijk te behandelen en het bestand te laten zakken. Het onderscheid in categorie telt hier: factuureigenschappen zoals deze beschrijven data die van buiten de PDF-processor komt, dus worden ze als external gedeclareerd in plaats van internal

Wat de declaratie van het extensieschema bevat

De declaratie is een rdf:Description in het XMP-pakket die de drie door AIIM gedefinieerde namespaces pdfaExtension, pdfaSchema en pdfaProperty gebruikt. Binnen een pdfaExtension:schemas-bag zit één schemavermelding die het Factur-X-schema benoemt, haar pdfaSchema:namespaceURI en pdfaSchema:prefix geeft, en vervolgens de vier eigenschappen opsomt in een pdfaSchema:property-reeks. Elke eigenschap draagt een naam, een pdfaProperty:valueType van Text en een pdfaProperty:category van external. De illustratieve markup hieronder toont de vorm van dat blok

PDF Library for Delphi: anatomie van het PDF/A-3-extensieschema dat het Factur-X-schema declareert met zijn namespace-URI, fx-voorvoegsel en vier externe Text-eigenschappen
Elke bewering die de declaratie doet moet overeenkomen met het fx-blok dat de factuur werkelijk schrijft, anders weigert de validator het bestand alsnog
<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 en 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 voorvoegsel fx, terwijl een ZUGFeRD 2.0-bestand dat via zugferd-invoice.xml is geselecteerd naar een andere URI onder zijn eigen schemanaam verwijst. Het extensieschema moet dezelfde namespace-URI declareren die het eigenschappenblok werkelijk gebruikt, anders kan de validator de twee nog steeds niet verbinden. PDF Library for Delphi leidt beide waarden af uit de bestandsnaam en versie die u doorgeeft, zodat de declaratie en het eigenschappenblok altijd overeenstemmen

Hoe de helper beide helften samen schrijft

In PDF Library for Delphi zet u die XML niet met de hand in elkaar. U brengt het document in een PDF/A-3-modus en roept één methode aan. Het eerste wat moet worden vastgelegd is de conformiteitsvlag, want Factur-X vereist PDF/A-3. Het aanroepen van SetPDFAMode(7) selecteert het niveau PDF/A-3u, wat pdfaid:part op 3 en pdfaid:conformance op U zet in het identificatieschema. Het XMP-pakket draagt nu het juiste part en de juiste conformiteit voordat er ook maar één stuk factuurmetadata wordt toegevoegd

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(7);            // PDF/A-3u: pdfaid:part=3, conformance=U
  PDF.NewDocument;
  // teken hier de door mensen leesbare factuurpagina

  FileID := PDF.AddFacturXAssociatedFileFromString(
    InvoiceXML,                  // ruwe UTF-8 XML-bytes
    'EN16931',                   // ConformanceLevel
    'factur-x.xml',              // naam van het 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 komt niet overeen

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

Eén aanroep van AddFacturXAssociatedFileFromString doet het werk dat het zakkende bestand miste. Zij sluit de XML in als een PDF/A-3-geassocieerd bestand met de relatie die u hebt genoemd, en zij legt de vier fx-eigenschappen vast samen met de schemanaam, de namespace-URI en het voorvoegsel voor het gekozen profiel. Wanneer het document wordt opgeslagen, injecteert een interne stap met de naam ApplyFacturXMetadata zowel het eigenschappenblok als de bijpassende pdfaExtension:schemas-declaratie in het XMP-pakket, zodat de aangepaste eigenschappen al beschreven aankomen. De methode retourneert 0 als het document niet in een PDF/A-3-modus staat of als de XML niet overeenkomt met het gedeclareerde profiel, wat dezelfde bescherming is die voorkomt dat een misvormde factuur überhaupt het bestand bereikt

De blinde vlek die de containercontrole niet kan zien

Dit is het deel dat ronduit benoemd moet worden, want het is de reden dat de bug zich verbergt. ValidateFacturXInvoice controleert de container. Het bevestigt dat de catalog een /AF-vermelding heeft, dat de naamboom EmbeddedFiles aanwezig is, dat de factuur-XML bestaat, dat de naam van het ingesloten bestand bij het profiel past, dat de guideline-ID in de XML strookt met het conformiteitsniveau, en dat de /AFRelationship er een is die PDF/A-3 toestaat. Dat zijn echte controles en ze vangen echte defecten. GetFacturXValidationIssues meldt 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 vlekkeloos is maar waarvan de fx-eigenschappen niet zijn gedeclareerd, doorstaat elke issuecontrole en retourneert 1, omdat niets in die lijst het pdfaExtension:schemas-blok inspecteert. Precies daarom kan een met de hand gebouwde factuur, of een die is geproduceerd door een pijplijn die het eigenschappenblok zonder de declaratie schreef, moeiteloos door de ingebouwde validator komen en toch zakken voor veraPDF op clausule 6.6.2.3.1. De containervalidator en de PDF/A-metadatavalidator beantwoorden verschillende vragen, en alleen de volledige PDF/A-controleur beantwoordt de tweede

PDF Library for Delphi: dezelfde Factur-X-factuur doorstaat elke containercontrole in ValidateFacturXInvoice terwijl veraPDF haar afwijst omdat geen enkele laag het pdfaExtension-schemablok leest
De containervalidator en de PDF/A-validator beantwoorden verschillende vragen over hetzelfde bestand

Issues lezen zodat u weet welke laag brak

Omdat de twee lagen onafhankelijk van elkaar falen, is de juiste diagnostische gewoonte om eerst de containerissues te lezen en een schoon resultaat te behandelen als een uitspraak over uitsluitend de container, nooit over PDF/A-metadata. Draai de ingebouwde validatie, verzamel de issuelijst en handel daarnaar voordat u naar een extern gereedschap grijpt

var
  Issues: WideString;
begin
  if PDF.ValidateFacturXInvoice = 0 then
  begin
    Issues := PDF.GetFacturXValidationIssues('|');
    // identificatoren op containerniveau, bijvoorbeeld:
    //   MissingCatalogAF, NotPDFA3, MissingEmbeddedFilesNameTree,
    //   ConformanceGuidelineMismatch, InvalidAFRelationship
    WriteLn('Container issues: ', Issues);
  end
  else
    WriteLn('Container OK; verify XMP extension schema with a PDF/A checker.');
end;

Wanneer die aanroep een issuenaam retourneert, ligt de fout in de container en vertelt het bericht u welk deel. Wanneer hij schoon terugkomt en veraPDF het bestand toch afwijst, is de fout vrijwel altijd het XMP-extensieschema, en de oplossing is om AddFacturXAssociatedFileFromString de metadata te laten schrijven in plaats van het eigenschappenblok zelf te construeren. De twee vragen in uw eigen hoofd gescheiden houden is wat een raadselachtige afwijzing verandert in een diagnose van één regel: containerproblemen komen boven via de issuelijst, problemen met de schemadeclaratie komen alleen boven via een PDF/A-validator, en die twee door elkaar halen is wat de bug laat schuilen

Het bredere plaatje van PDF/A- en PDF/UA-conformiteit, inclusief hoe u een preflightpassage draait voordat een bestand uw build verlaat, wordt behandeld in de doorloop van PDF/A- en PDF/UA-preflight. Als uw factuur ook toegankelijk moet zijn, is de structuurboom waarvan PDF/A-3a en getagde PDF afhangen het onderwerp van het artikel over toegankelijkheid met getagde PDF. De hier beschreven verwerking van extensieschemas wordt geleverd als onderdeel van PDF Library for Delphi, de Delphi PDF Library, naast de ondersteuning voor de profielen Factur-X, ZUGFeRD en XRechnung die elders op deze blog is gedocumenteerd