При интеграции с внешними системами через XML и схему XSD часто используется стандартный механизм десериализации платформы 1С: ФабрикаXDTO.ПрочитатьXML(). Этот метод выполняет строгую проверку входящих данных на соответствие типам схемы. Однако если во входящем файле передано некорректное значение (например, строка в целочисленном поле), разработчик сталкивается со специфичным поведением платформы.
Посмотрим на типичный фрагмент кода чтения XML:
Попытка
нОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(лЧтение, лТип);
Исключение
ЗаписатьЛог(ОписаниеОшибки());
КонецПопытки;
При наличии ошибки платформа прерывает чтение и формирует исключение следующего вида: Ошибка проверки данных XDTO: Значение: 'тест1' не соответствует простому типу: {http://www.w3.org/2001/XMLSchema}integer. В тексте ошибки и объекте ИнформацияОбОшибке() выводится само ошибочное значение и целевой тип XSD, однако имя некорректного тега или свойства XDTO в явном виде отсутствует. Разберем причины такого поведения и способы точной локализации сбойного поля.
Проанализируем, почему платформа не возвращает имя свойства при падении метода ПрочитатьXML:
ФабрикаXDTO.ПрочитатьXML() работает в потоковом режиме поверх объекта ЧтениеXML. Как только парсер натыкается на лексему с неподходящим типом данных, процесс чтения немедленно прерывается. Полноценный объект XDTO в памяти еще не сформирован.Проверить() предназначен для валидации уже созданного в памяти объекта ОбъектXDTO (например, для контроля количества вхождений элементов или заполненности обязательных полей). Применить его к невалидному XML невозможно, так как сам объект не создается из-за сбоя на этапе чтения типов.В момент генерации исключения внутри метода ПрочитатьXML указатель связанного объекта ЧтениеXML останавливается ровно на том узле, где произошла ошибка. Мы можем извлечь координаты и имя элемента непосредственно в блоке Исключение до закрытия потока чтения.
Рассмотрим реализацию этого подхода:
лЧтение = Новый ЧтениеXML;
лЧтение.ОткрытьФайл(ПутьКФайлу);
Попытка
нОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(лЧтение, лТип);
Исключение
Инфо = ИнформацияОбОшибке();
// Получаем текущие координаты чтения из объекта ЧтениеXML
НомерСтрокиОшибки = лЧтение.НомерСтроки;
НомерПозицииОшибки = лЧтение.НомерПозиции;
ТекущееИмяУзла = лЧтение.Имя;
ЛокальноеИмяУзла = лЧтение.ЛокальноеИмя;
СообщениеОбОшибке = СтрШаблон(
"Ошибка XDTO в строке %1 (позиция %2), узел: '%3'. Описание: %4",
НомерСтрокиОшибки,
НомерПозицииОшибки,
?(ЗначениеЗаполнено(ЛокальноеИмяУзла), ЛокальноеИмяУзла, ТекущееИмяУзла),
Инфо.Описание
);
ЗаписатьЛог(СообщениеОбОшибке);
лЧтение.Закрыть();
ВызватьИсключение СообщениеОбОшибке;
КонецПопытки;
лЧтение.Закрыть();
Обратите внимание: если курсор находится на узле типа ТипУзлаXML.Текст, свойство лЧтение.Имя может быть пустым. В этом случае точные координаты (НомерСтроки и НомерПозиции) позволяют однозначно локализовать проблемный тег в исходном файле.
Если входящий XML-документ уже загружен в память в виде строки или анализируется по тексту, мы можем распарсить текст исключения, извлечь ошибочное значение и найти тег, внутри которого оно находится.
Разберем логику работы:
Инфо.Описание с помощью строковых функций или регулярных выражений извлекаем фрагмент между Значение: ' и ' не соответствует.Данный вариант прост в реализации, однако имеет ограничение: если в файле содержится несколько одинаковых строковых значений в разных тегах, поиск найдет первое совпадение, которое и послужило причиной остановки парсера.
Для гарантированного получения полного пути к сбойному узлу (XPath) при сложной вложенной структуре XML мы можем реализовать комбинированный разбор: осуществлять итерацию по узлам через ЧтениеXML, сохраняя стек открытых тегов, и десериализовать в XDTO отдельные фрагменты документа.
Посмотрим на пример алгоритма:
СтекТегов = Новый Массив;
Пока лЧтение.Прочитать() Цикл
Если лЧтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
СтекТегов.Добавить(лЧтение.ЛокальноеИмя);
ИначеЕсли лЧтение.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
Если СтекТегов.Количество() > 0 Тогда
СтекТегов.Удалить(СтекТегов.Количество() - 1);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если внутри итерации вызывается десериализация конкретного составного узла через ФабрикаXDTO.ПрочитатьXML(лЧтение), то при возникновении исключения текущее состояние массива СтекТегов дает полную иерархическую цепочку родительских элементов.
Нередко ошибки несоответствия типов возникают из-за некорректно указанного пространства имен в корневом теге XML. Если фабрика XDTO ожидает типы из целевого пространства имен (URI), а в файле пространство имен отсутствует или указано с опечаткой, парсер может пытаться сопоставить данные с некорректными типами по умолчанию.
Убедимся, что корневой элемент документа содержит корректное объявление пространства имен:
// Пример добавления пространства имен в заголовок XML при необходимости
Если СтрНайти(ТекстXML, "xmlns=") = 0 Тогда
ТекстXML = СтрЗаменить(ТекстXML, "<PERS>", "<PERS xmlns=""uri:TFOMS_PK"">");
КонецЕсли;
Если требуется обрабатывать большой поток файлов в автоматическом режиме на сервере под управлением ОС Linux и получать полный список всех некорректных тегов (а не только первого сбойного), оптимально выполнять предварительную валидацию с помощью системной утилиты xmllint.
Разберем схему работы:
ЗапуститьПриложение() вызывается команда:
xmllint --noout --schema /path/to/schema.xsd /path/to/input.xml
xmllint выполняет полную проверку файла по XSD за один проход и возвращает в стандартный поток ошибок подробный отчет со всеми строками, именами тегов и несоответствиями типов.ФабрикаXDTO.ПрочитатьXML() без генерации исключений.