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 należy do żadnego schematu XMP, który PDF/A predefiniuje. Schematy Dublin Core, XMP Basic, PDF oraz identyfikacyjny PDF/A są znane zgodnemu czytnikowi, ale fx: już nie. Gdy veraPDF przechodzi przez XMP i trafia na właściwość o przestrzeni nazw, której nie rozpoznaje, szuka deklaracji, która powiedziałaby mu, co ta właściwość znaczy. Jeśli tej deklaracji brak, zgłasza niepowodzenie wobec klauzuli 6.6.2.3.1 ISO 19005-3, która wymaga, by każda właściwość niepochodząca z predefiniowanego schematu była opisana w schemacie rozszerzenia PDF/A. Cztery niezadeklarowane właściwości, cztery sposoby na odrzucenie pliku i ani jednego widocznego dla kontroli kontenera
Dlaczego PDF/A odmawia gołej właściwości niestandardowej
Reguła wygląda pedancko, dopóki nie przypomnisz sobie, po co jest PDF/A. Ten format istnieje po to, by plik dało się otworzyć i zrozumieć za dziesięciolecia, oprogramowaniem, które nigdy nie słyszało o konwencjach z 2026 roku. Od zgodnego czytnika oczekuje się, że zrozumie dokument z samego dokumentu, bez zewnętrznego rejestru do konsultacji
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, korzystające z trzech przestrzeni nazw zdefiniowanych przez AIIM: pdfaExtension, pdfaSchema i pdfaProperty. Wewnątrz worka pdfaExtension:schemas siedzi jeden wpis schematu, który nazywa schemat Factur-X, podaje jego pdfaSchema:namespaceURI i pdfaSchema:prefix, a potem wylicza cztery właściwości w sekwencji pdfaSchema:property. Każda właściwość niesie nazwę, pdfaProperty:valueType równy Text i pdfaProperty:category równą external. Przykładowy markup poniżej 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. PDF Library for Delphi 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 PDF Library for Delphi 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;
// tutaj narysuj stronę faktury czytelną dla człowieka
FileID := PDF.AddFacturXAssociatedFileFromString(
InvoiceXML, // raw UTF-8 XML bytes
'EN16931', // ConformanceLevel
'factur-x.xml', // nazwa osadzonego pliku
'Factur-X invoice XML', // /Desc text
'Alternative', // /AFRelationship
'1.0', // profile version
''); // opcjonalny kod kraju
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
Co pokrywa kontrola kontenera
Ten fragment warto trzymać pojętego, bo właśnie dlatego problem się ukrywa. ValidateFacturXInvoice sprawdza kontener. Potwierdza, że katalog ma wpis /AF, że drzewo nazw EmbeddedFiles istnieje, że XML faktury jest obecny, że nazwa osadzonego pliku pasuje do profilu, że ID wytycznych w XML zgadza się z poziomem zgodności i że /AFRelationship jest jedną z dozwolonych przez PDF/A-3. To są prawdziwe kontrole i łapią prawdziwe defekty. GetFacturXValidationIssues raportuje je z nazwiska, identyfikatorami takimi jak MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship i InvalidFileNameProfile
Schemat rozszerzenia XMP należy do innej warstwy. Plik, którego kontener jest bez skazy, ale właściwości fx niezadeklarowane, przechodzi każdą kontrolę kontenera i zwraca 1, bo deklarowanie właściwości XMP to pytanie o metadane PDF/A, a nie o kontener. Dokładnie dlatego faktura zbudowana ręcznie albo przez potok, który zapisał blok właściwości bez deklaracji, może przejść walidację kontenera i nadal paść w veraPDF na klauzuli 6.6.2.3.1. Walidator kontenera i walidator metadanych PDF/A odpowiadają na różne pytania, a na drugie odpowiada tylko pełny checker PDF/A
Odczytywanie problemów, aby wiedzieć, która warstwa się zepsuła
Bo obie warstwy zawodzą niezależnie, właściwym nawykiem diagnostycznym jest najpierw przeczytać problemy kontenera i traktować czysty wynik jako oświadczenie wyłącznie o kontenerze, nigdy o metadanych PDF/A. Uruchom wbudowaną walidację, zbierz listę problemów i zareaguj na nią, zanim sięgniesz po narzędzie zewnętrzne
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, usterka siedzi w kontenerze, a komunikat mówi, której części. Gdy zwraca czysty wynik, a veraPDF nadal odrzuca plik, usterką jest prawie zawsze schemat rozszerzenia XMP, a lekarstwo to pozwolić AddFacturXAssociatedFileFromString zapisać metadane, zamiast składać blok właściwości samodzielnie. Rozdzielenie tych dwóch pytań w głowie zamienia zagadkowe odrzucenie w diagnozę jednej linijki: problemy kontenera wychodzą przez listę problemów, problemy deklaracji schematu wychodzą tylko przez walidator PDF/A, a mylenie ich jest tym, co pozwala problemowi 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ęść PDF Library for Delphi Delphi PDF Library wraz z obsługą profili Factur-X, ZUGFeRD i XRechnung udokumentowaną w tym blogu