Ви створили рахунок-фактуру Factur-X, і кожна перевірка контейнера проходить успішно. Каталог містить масив /AF, дерево імен EmbeddedFiles вказує на правильну специфікацію файлу, вбудований factur-x.xml має правильний /AFRelationship, що дорівнює Alternative, а вбудована функція ValidateFacturXInvoice повертає 1. Потім ви пропускаєте той самий файл через veraPDF, еталонний засіб перевірки, який використовують податкові портали, і він визначає, що весь документ не є дійсним PDF/A-3. Структура правильна. Проблема в метаданих, і цю помилку найлегше пропустити у всьому робочому процесі електронного рахунку
Цю причину варто зрозуміти повністю, оскільки вона пояснює клас дефектів PDF/A, який не має нічого спільного з видимою сторінкою або прикріпленням, а повністю пов'язаний з тим, як XMP описує сам себе. Це пастка, яка ховається за успішною перевіркою контейнера
Чотири властивості, через які файл не проходить перевірку
Рахунок Factur-X записує чотири користувацькі властивості у свій пакет XMP, щоб програмне забезпечення на наступних етапах могло прочитати профіль рахунку без розбору вбудованого XML. Вони знаходяться у просторі імен Factur-X під префіксом fx: fx:DocumentFileName, fx:DocumentType, fx:Version та fx:ConformanceLevel. Це саме ті метадані, які потрібні програмі для читання, щоб дізнатися, що цей PDF містить рахунок-фактуру EN 16931 з іменем factur-x.xml версії 1.0
Жодна з цих чотирьох властивостей не є частиною жодної схеми XMP, яку попередньо визначає PDF/A. Схеми ідентифікації Dublin Core, XMP Basic, PDF та PDF/A відомі відповідній програмі для читання, але fx: - ні. Коли veraPDF проходить по XMP і досягає властивості, простір імен якої він не розпізнає, він шукає оголошення, яке б пояснило, що означає ця властивість. Якщо такого оголошення немає, він повідомляє про помилку відповідно до пункту 6.6.2.3.1 стандарту ISO 19005-3, який вимагає, щоб кожна властивість, не взята з попередньо визначеної схеми, була описана в схемі розширення PDF/A. Чотири неоголошені властивості - це чотири причини для відхилення файлу, і жодна з них не помітна під час перевірки контейнера
Чому PDF/A відхиляє просту користувацьку властивість
Це правило здається педантичним, поки ви не згадаєте, для чого потрібен PDF/A. Формат існує для того, щоб файл можна було відкрити і зрозуміти через десятиліття за допомогою програмного забезпечення, якому ніколи не розповідали про конвенції 2026 року. Очікується, що відповідна програма для читання зрозуміє документ лише на основі самого документа, не звертаючись до жодного зовнішнього реєстру
Користувацькі метадані порушують цю обіцянку, якщо файл не містить власного опису. Маючи лише властивість fx:ConformanceLevel, майбутня програма для читання не зможе дізнатися URI простору імен, до якого прив'язаний префікс fx, чи є значення текстом, датою або цілим числом, і чи описує властивість сам документ або якийсь зовнішній ресурс. Механізм схеми розширення PDF/A усуває цю прогалину. Він дозволяє файлу оголосити у фіксованій структурі XMP простір імен, префікс, а для кожної властивості - тип значення та категорію internal або external. Після того, як це оголошення присутнє, властивість описує сама себе, і пункт 6.6.2.3.1 вважається виконаним. Без нього валідатор не має іншого вибору, окрім як розглядати властивість як незрозумілу і відхилити файл. Розрізнення категорій тут має значення: такі властивості рахунку-фактури описують дані, що надходять з-поза меж PDF-процесора, тому вони оголошуються як external, а не internal
Що містить оголошення схеми розширення
Оголошення - це rdf:Description у пакеті XMP, яке використовує три простори імен, визначені AIIM: pdfaExtension, pdfaSchema та pdfaProperty. Всередині набору pdfaExtension:schemas знаходиться один запис схеми, який називає схему Factur-X, надає її pdfaSchema:namespaceURI та pdfaSchema:prefix, а потім перелічує чотири властивості в послідовності pdfaSchema:property. Кожна властивість містить назву, pdfaProperty:valueType зі значенням Text та pdfaProperty:category зі значенням external. Ілюстративна розмітка нижче показує форму цього блоку
<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 простору імен та префікс не є фіксованими рядками. Вони відповідають профілю. Документ Factur-X використовує urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# з префіксом fx, тоді як файл ZUGFeRD 2.0, вибраний через zugferd-invoice.xml, вказує на інший URI під власною назвою схеми. Схема розширення має оголосити той самий URI простору імен, який фактично використовується в блоці властивостей, інакше валідатор все одно не зможе зв'язати їх разом. PDFlibPas отримує обидва значення з імені файлу та версії, які ви передаєте, тому оголошення та блок властивостей завжди збігаються
Як помічник записує обидві половини разом
У PDFlibPas ви не збираєте цей XML вручну. Ви переводите документ у режим PDF/A-3 і викликаєте один метод. Перше, що потрібно владнати - це прапорець відповідності, оскільки Factur-X вимагає PDF/A-3. Виклик SetPDFAMode(7) вибирає рівень PDF/A-3u, який встановлює pdfaid:part на 3 і pdfaid:conformance на U в схемі ідентифікації. Тепер пакет XMP містить правильну частину та відповідність ще до додавання будь-яких метаданих рахунку
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;
Один виклик AddFacturXAssociatedFileFromString виконує ту роботу, якої не вистачало файлу з помилкою. Він вбудовує XML як пов'язаний файл PDF/A-3 із зазначеним вами зв'язком і записує чотири властивості fx разом із назвою схеми, URI простору імен і префіксом для вибраного профілю. При збереженні документа внутрішній крок під назвою ApplyFacturXMetadata впроваджує як блок властивостей, так і відповідне оголошення pdfaExtension:schemas у пакет XMP, тому користувацькі властивості надходять уже описаними. Метод повертає 0, якщо документ не в режимі PDF/A-3 або якщо XML не відповідає заявленому профілю - це той самий захист, який від самого початку не дає неправильно сформованому рахунку потрапити у файл
Сліпа зона, яку не бачить перевірка контейнера
Це та частина, яку слід назвати прямо, оскільки саме через неї ховається помилка. ValidateFacturXInvoice перевіряє контейнер. Функція підтверджує, що в каталозі є запис /AF, присутнє дерево імен EmbeddedFiles, існує XML рахунку, ім'я вбудованого файлу відповідає профілю, ідентифікатор вказівки в XML збігається з рівнем відповідності, а /AFRelationship є одним із тих, що дозволяє PDF/A-3. Це реальні перевірки, і вони виявляють реальні дефекти. GetFacturXValidationIssues повідомляє про них за іменами за допомогою таких ідентифікаторів, як MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship та InvalidFileNameProfile
Чого вона не перевіряє, так це того, чи присутня і чи є правильною схема розширення XMP. Файл, контейнер якого бездоганний, але чиї властивості fx не оголошені, проходить кожну перевірку на наявність проблем і повертає 1, оскільки жоден пункт у цьому списку перевіряє блок pdfaExtension:schemas. Саме тому рахунок-фактура, створений вручну, або такий, що створений конвеєром, який записав блок властивостей без оголошення, може легко пройти через вбудований валідатор і все одно не пройти перевірку veraPDF за пунктом 6.6.2.3.1. Валідатор контейнера та валідатор метаданих PDF/A відповідають на різні запитання, і лише повний засіб перевірки PDF/A відповідає на друге
Читання помилок, щоб дізнатися, який рівень зламався
Оскільки два рівні дають збої незалежно один від одного, правильною діагностичною звичкою є спочатку прочитати помилки контейнера і розглядати чистий результат лише як твердження про контейнер, а не про метадані PDF/A. Запустіть вбудовану валідацію, зберіть список проблем і відреагуйте на нього, перш ніж звертатися до зовнішнього інструменту
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;
Коли цей виклик повертає назву проблеми, помилка знаходиться в контейнері, і повідомлення підкаже вам, у якій саме частині. Коли він повертає чистий результат, а veraPDF все ще відхиляє файл, помилка майже завжди полягає в схемі розширення XMP, і виправлення полягає в тому, щоб дозволити AddFacturXAssociatedFileFromString записати метадані, а не створювати блок властивостей самостійно. Чітке розмежування цих двох запитань у вашій голові - це те, що перетворює незрозумілу відмову на однорядковий діагноз: проблеми з контейнером спливають через список проблем, проблеми з оголошенням схеми спливають лише через валідатор PDF/A, і саме плутанина між ними дозволяє помилці ховатися
Більш широка картина відповідності PDF/A та PDF/UA, включаючи те, як запустити попередню перевірку (preflight) перед тим, як файл покине вашу збірку, розглядається в нашому посібнику з попередньої перевірки PDF/A та PDF/UA у Delphi. Якщо ваш рахунок-фактура також має бути доступним, дерево структури, від якого залежать PDF/A-3a та тегований PDF, є темою статті про доступність тегованого PDF. Описана тут обробка схеми розширення постачається як частина PDFlibPas Delphi PDF Library разом із підтримкою профілів Factur-X, ZUGFeRD та XRechnung, задокументованою в цьому блозі