Artykuł techniczny

Schematy rozszerzeń PDF/A-3 dla XMP Factur-X w Delphi

Zbudowałeś fakturę Factur-X i wszystkie sprawdzenia kontenera przechodzą. Katalog zawiera tablicę /AF, drzewo nazw EmbeddedFiles wskazuje na właściwą specyfikację pliku, osadzony factur-x.xml ma prawidłowy /AFRelationship wynoszący Alternative, a wbudowane ValidateFacturXInvoice zwraca 1. Następnie uruchamiasz ten sam plik przez veraPDF - narzędzie referencyjne, z którego korzystają portale podatkowe - i stwierdza ono, że cały dokument nie jest prawidłowym PDF/A-3. Struktura jest poprawna. Problem leży w metadanych, a błąd ten jest jednym z najłatwiejszych do przeoczenia w całym przepływie pracy z fakturami elektronicznymi

Przyczyna jest warta pełnego zrozumienia, ponieważ wyjaśnia klasę defektów PDF/A, która nie ma nic wspólnego z widoczną stroną ani załącznikiem, lecz wszystko ze sposobem, w jaki XMP opisuje siebie. To jest pułapka ukryta za zielonym sprawdzeniem kontenera

Cztery właściwości, które powodują odrzucenie pliku

Faktura Factur-X zapisuje cztery niestandardowe właściwości do pakietu XMP, aby oprogramowanie pośredniczące mogło odczytać profil faktury bez parsowania osadzonego XML. Żyją w przestrzeni nazw Factur-X pod prefiksem fx: fx:DocumentFileName, fx:DocumentType, fx:Version i fx:ConformanceLevel. To dokładnie te metadane, których czytnik potrzebuje, aby wiedzieć, że ten PDF zawiera fakturę EN 16931 o nazwie factur-x.xml w wersji 1.0

Żadna z tych czterech właściwości nie jest częścią żadnego schematu XMP, który PDF/A predefiniuje. Schematy Dublin Core, XMP Basic, PDF i identyfikacja PDF/A są znane zgodnemu czytnikowi, ale fx: nie jest. Gdy veraPDF przechodzi przez XMP i trafia na właściwość, której przestrzeni nazw nie rozpoznaje, szuka deklaracji, która powiedziałaby mu, co ta właściwość oznacza. Jeśli ta deklaracja jest nieobecna, zgłasza błąd względem klauzuli 6.6.2.3.1 ISO 19005-3, która wymaga, aby każda właściwość nieczerpana z predefiniowanego schematu była opisana w schemacie rozszerzenia PDF/A. Cztery niezadeklarowane właściwości - cztery powody do odrzucenia pliku, a żaden z nich nie jest widoczny podczas sprawdzania kontenera

Dlaczego PDF/A odmawia gołej właściwości niestandardowej

Reguła wydaje się pedantyczna, dopóki nie przypomnimy sobie, do czego służy PDF/A. Format ten istnieje po to, aby plik mógł być otwarty i zrozumiany za kilkadziesiąt lat przez oprogramowanie, które nigdy nie było informowane o konwencjach z 2026 roku. Zgodny czytnik powinien rozumieć dokument z dokumentu samego, bez konsultowania zewnętrznych rejestrów

Niestandardowe metadane łamią tę obietnicę, chyba że plik zawiera własny opis. Mając gołą właściwość fx:ConformanceLevel, przyszły czytnik nie może wiedzieć, do jakiego URI przestrzeni nazw wiąże się prefiks fx, czy wartość jest tekstem, datą czy liczbą całkowitą, ani czy właściwość opisuje sam dokument czy jakiś zasób zewnętrzny. Mechanizm schematu rozszerzeń PDF/A wypełnia tę lukę. Pozwala plikowi zadeklarować w stałej strukturze XMP przestrzeń nazw, prefiks i dla każdej właściwości typ wartości oraz kategorię internal lub external. Gdy ta deklaracja jest obecna, właściwość jest samoopisująca i klauzula 6.6.2.3.1 jest spełniona. Bez niej walidator nie ma wyboru i musi potraktować właściwość jako niezrozumiałą i odrzucić plik. Rozróżnienie kategorii ma tu znaczenie: właściwości faktury, takie jak te, opisują dane pochodzące spoza procesora PDF, więc są deklarowane jako external, a nie internal

Co zawiera deklaracja schematu rozszerzenia

Deklaracja to rdf:Description w pakiecie XMP używające trzech przestrzeni nazw zdefiniowanych przez AIIM: pdfaExtension, pdfaSchema i pdfaProperty. Wewnątrz zbioru pdfaExtension:schemas znajduje się jeden wpis schematu, który nazywa schemat Factur-X, podaje jego pdfaSchema:namespaceURI i pdfaSchema:prefix, a następnie wymienia cztery właściwości w sekwencji pdfaSchema:property. Każda właściwość zawiera nazwę, pdfaProperty:valueType wynoszący Text i pdfaProperty:category wynoszący external. Poniższy ilustracyjny znacznik pokazuje kształt tego bloku

<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>

URI przestrzeni nazw i prefiks nie są stałymi ciągami. Są zależne od profilu. Dokument Factur-X używa urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# z prefiksem fx, podczas gdy plik ZUGFeRD 2.0 wybrany przez zugferd-invoice.xml wskazuje na inny URI pod własną nazwą schematu. Schemat rozszerzenia musi deklarować ten sam URI przestrzeni nazw, który faktycznie używa blok właściwości, w przeciwnym razie walidator nadal nie może połączyć obu. PDFlibPas wyprowadza obie wartości z nazwy pliku i wersji, które przekazujesz, więc deklaracja i blok właściwości zawsze się zgadzają

Jak pomocnik zapisuje obie połowy razem

W PDFlibPas nie składasz tego XML ręcznie. Umieszczasz dokument w trybie PDF/A-3 i wywołujesz jedną metodę. Pierwszą rzeczą do ustalenia jest flaga zgodności, ponieważ Factur-X wymaga PDF/A-3. Wywołanie SetPDFAMode(7) wybiera poziom PDF/A-3u, który ustawia pdfaid:part na 3 i pdfaid:conformance na U w schemacie identyfikacji. Pakiet XMP zawiera teraz właściwą część i zgodność przed dodaniem jakichkolwiek metadanych faktury

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;

Pojedyncze wywołanie AddFacturXAssociatedFileFromString wykonuje pracę, której brakowało w wadliwym pliku. Osadza XML jako plik powiązany PDF/A-3 z podaną relacją i zapisuje cztery właściwości fx wraz z nazwą schematu, URI przestrzeni nazw i prefiksem dla wybranego profilu. Gdy dokument jest zapisywany, wewnętrzny krok o nazwie ApplyFacturXMetadata wstrzykuje zarówno blok właściwości, jak i pasującą deklarację pdfaExtension:schemas do pakietu XMP, więc niestandardowe właściwości przybyją już opisane. Metoda zwraca 0, jeśli dokument nie jest w trybie PDF/A-3 lub XML nie pasuje do zadeklarowanego profilu - to ta sama ochrona, która zapobiega trafieniu wadliwej faktury do pliku

Martwy punkt, którego sprawdzenie kontenera nie widzi

To jest część, którą należy nazwać wprost, bo to właśnie dlatego błąd się ukrywa. ValidateFacturXInvoice sprawdza kontener. Potwierdza, że katalog ma wpis /AF, drzewo nazw EmbeddedFiles jest obecne, XML faktury istnieje, nazwa osadzonego pliku pasuje do profilu, GuidelineID w XML zgadza się z poziomem zgodności i /AFRelationship jest jednym z dozwolonych przez PDF/A-3. To są prawdziwe sprawdzenia i wychwytują prawdziwe defekty. GetFacturXValidationIssues zgłasza je według nazwy, z identyfikatorami takimi jak MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship i InvalidFileNameProfile

Czego nie sprawdza, to czy schemat rozszerzenia XMP jest obecny i poprawny. Plik, którego kontener jest bez zarzutu, ale właściwości fx są niezadeklarowane, przechodzi każde sprawdzenie problemu i zwraca 1, ponieważ nic na tej liście nie sprawdza bloku pdfaExtension:schemas. Właśnie dlatego ręcznie zbudowana faktura lub taka wyprodukowana przez potok, który zapisał blok właściwości bez deklaracji, może przejść wbudowany walidator i nadal nie przejść veraPDF na klauzuli 6.6.2.3.1. Walidator kontenera i walidator metadanych PDF/A odpowiadają na różne pytania, a tylko pełny sprawdzacz PDF/A odpowiada na to drugie

Odczytywanie problemów, aby wiedzieć, która warstwa się zepsuła

Ponieważ dwie warstwy nie działają niezależnie, właściwym nawykiem diagnostycznym jest najpierw odczytać problemy kontenera i traktować czysty wynik jako stwierdzenie dotyczące wyłącznie kontenera, nigdy metadanych PDF/A. Uruchom wbudowaną walidację, zbierz listę problemów i działaj na niej przed sięgnięciem po zewnętrzne narzędzie

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;

Gdy to wywołanie zwraca nazwę problemu, błąd leży w kontenerze, a komunikat mówi, w której części. Gdy zwraca czysty wynik, a veraPDF nadal odrzuca plik, błąd prawie zawsze dotyczy schematu rozszerzenia XMP, a poprawką jest pozwolenie AddFacturXAssociatedFileFromString na zapisanie metadanych zamiast samodzielnego konstruowania bloku właściwości. Rozdzielanie obu pytań w umyśle jest tym, co zamienia dezorientujące odrzucenie w jednoliniową diagnozę: problemy kontenera ujawniają się przez listę problemów, problemy deklaracji schematu ujawniają się tylko przez walidator PDF/A, a mylenie obu jest tym, co pozwala błędowi się ukrywać

Szerszy obraz zgodności PDF/A i PDF/UA, w tym jak uruchomić wstępną inspekcję przed opuszczeniem pliku przez build, jest omówiony w przewodniku inspekcji wstępnej PDF/A i PDF/UA. Jeśli twoja faktura musi być też dostępna, drzewo struktury, od którego zależy PDF/A-3a i otagowany PDF, jest tematem artykułu o dostępności otagowanego PDF. Obsługa schematu rozszerzenia opisana tutaj jest dostarczana jako część PDFlibPas Delphi PDF Library wraz z obsługą profili Factur-X, ZUGFeRD i XRechnung udokumentowaną w tym blogu