Формат загружаемого файла должен соответствовать ПакетуXDTO
<?xml version="1.0" encoding="UTF-8"?>
<My-Object xmlns="http://www.MyURL" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<header>
<num>2019-02-10</num> <!-------тут должно быть число----->
<sender-id>00001</sender-id>
<reciever-id>00000</reciever-id>
<created-date>2019-02-11T08:09:09</created-date>
</header>
<docs>
<doc>
<doc-date>100</doc-date> <!-------тут должна быть дата----->
<doc-num>1</doc-num>
<doc-gds>
<doc-gd>
<line-num>1</line-num>
<gd-code>000001</gd-code>
<qnty>10</qnty>
<sum>1000</sum>
<price>100</price>
</doc-gd>
</doc-gds>
</doc>
</docs>
</My-Object>
Но сторонняя система ничего не знает об XDTO и XSD. Она формирует по сути текстовый файл определенного формата. Поэтому при его создании валидность не проверяется.
При больших объемах XML-файлов поиск секции содержащей некорректное значение становится проблемой. Ошибки выдаваемые платформой не очень информативны
Мы можем отсюда понять, что некая секция вместо даты содержит значение "100". Нам нужно найти секцию, которая согласно ПакетуXDTO имеет тип "Дата", но при этом содержит значение "100". Значение "100" может встречаться в файле многократно, а типы секций в XML-файле не обозначены.
Со значением "2019-02-10" и типом Integer ситуация аналогичная.
Если читать XML-файл построчно, то сопоставить получаемые данные с ПакетомXDTO будет проблематично. Поэтому реализация выполнена через ДокументDOM.
Алгоритм следующий:
- Читаем XML-файл в ДокументDOM
- Получаем корневой узел.
- Затем последовательно перебираем соседние узлы первого уровня вложенности
- Читаем полученный узел ФабрикойXDTO с указанием типа
- Если чтение произошло без ошибок переходим к следующему соседнему узлу и переходим к пункту 4
- Если чтение узла выбросило исключение, то проверяем тип и состав вложенного узла
- Если дочерний узел содержит простое значение, значит оно и является некорректным. Получаем следующий соседний узел и переходим к пункту 4
- Если вложенный узел содержит дочерние узлы, то проверяем должен ли он их содержать согласно ПакетуXDTO. Если нет, значит этот узел и является некорректным. Получаем следующий соседний узел и переходим к пункту 4
- Если вложенный узел содержит дочерние узлы, то последовательно получаем каждый из них. Проверяем есть ли такой узел в ПакетеXDO. Если нет, значит этот узел и является некорректным. Получаем следующий соседний узел и переходим к пункту 4
- После перебора всех вложенных узлов проверяем, что все обязательные узлы присутствую.
Таким образом мы получим все некорректные узлы. Но остается другая проблема. Как узнать в какой строке XML-файла находится некорректный узел?
Для этого необходимо некорректному узлу добавить атрибут, в качестве значения подставить UID и затем сохранить ДокументDOM во временный файл. Список подставленных UIDов и описание причины некорректности узла сохраняются в ТЗ. Полученный файл можно прочитать построчно и в случае обнаружения в строке ранее сохраненного UIDа можно понять в какой строке содержится некорректный узел и в чем именно ошибка.Так же во время чтения можно удалить ранее созданный атрибут и его значение, после чего файл примет первоначальный вид.
Ниже приведен пример кода, который выполняет все эти функции.
&НаСервере
Процедура ПроверитьНаСервере(АдресПроверяемогоФайла)
#Область ЧтениеДокументаДОМ
//Читаем файл из временного хранилища
Поток = ПолучитьИзВременногоХранилища(АдресПроверяемогоФайла).ОткрытьПотокДляЧтения();
ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьПоток(Поток);
ПостроительДОМ = Новый ПостроительDOM;
ДокументДОМ = ПостроительДОМ.Прочитать(ЧтениеХМЛ);
ЧтениеХМЛ.Закрыть();
Поток.Закрыть();
#КонецОбласти
#Область ОбходДереваДОМ
//Обходим дерево ДОМ
ОбходДереваDOM = Новый ОбходДереваDOM(ДокументДОМ);
//Получаем корневой узел
ОбходДереваDOM.СледующийУзел();
//Получаем тип корневого узла
ТипОбъектаXDTO = ФабрикаXDTO.Тип("http://www.MyURL", "MyObject");
//Получаем первый вложенный узел
УзелDOM = ОбходДереваDOM.СледующийУзел();
Пока УзелDOM <> Неопределено Цикл
#Область ПоискСвойстваОбъектаXDTOПоЛокальномуИмениУзла
СвойствоОбъектаXDTO = Неопределено;
Для Каждого СвойствоXDTO Из ТипОбъектаXDTO.Свойства Цикл
Если СвойствоXDTO.ЛокальноеИмя = УзелDOM.ИмяУзла Тогда
//СвойствоТипОбъектаXDTO = ФабрикаXDTO.Тип(СвойствоXDTO.Тип.URIПространстваИмен, СвойствоXDTO.Тип.Имя);
СвойствоОбъектаXDTO = СвойствоXDTO;
Прервать;
КонецЕсли;
КонецЦикла;
Если СвойствоОбъектаXDTO = Неопределено Тогда
Сообщить("Объект " + УзелDOM.РодительскийУзел.ИмяУзла + " неивестное свойство " + УзелDOM.ИмяУзла);
УзелDOM = ОбходДереваDOM.СледующийСоседний();
Продолжить;
КонецЕсли;
#КонецОбласти
ПроверитьУзел(УзелDOM, СвойствоОбъектаXDTO.Тип);
//Получаем следующий узел на этом же уровне вложенности
УзелDOM = ОбходДереваDOM.СледующийСоседний();
КонецЦикла;
#КонецОбласти
#Область ЗаписьДокументаДОМ
//Записываем документ ДОМ в поток
Поток = Новый ПотокВПамяти ;
ЗаписьХМЛ = Новый ЗаписьXML;
ЗаписьХМЛ.Отступ = Истина;
ЧетыреПробела = " ";
ПараметрыЗаписи = Новый ПараметрыЗаписиXML("UTF-8", , Истина, Ложь, ЧетыреПробела);
ЗаписьХМЛ.ОткрытьПоток(Поток, ПараметрыЗаписи);
ЗаписьДОМ = Новый ЗаписьDOM;
ЗаписьДОМ.Записать(ДокументДОМ, ЗаписьХМЛ);
ЗаписьХМЛ.Закрыть();
#КонецОбласти
ПозицияКурсора = 0;
Смещение = 0;
//Закрываем Поток для записи и открываем для чтения
Поток = Поток.ЗакрытьИПолучитьДвоичныеДанные().ОткрытьПотокДляЧтения();
//Читаем текст документа ДОМ из потока
ТекстовыйДокумент.Прочитать(Поток);
Поток.Закрыть();
Для НомерСтроки = 1 По ТекстовыйДокумент.КоличествоСтрок() Цикл
ТекстСтроки = ТекстовыйДокумент.ПолучитьСтроку(НомерСтроки);
Для Каждого СтрокаТаблицыОшибок Из ТаблицаОшибок Цикл
Если СтрокаТаблицыОшибок.Обработана Или СтрНайти(ТекстСтроки, СтрокаТаблицыОшибок.ИД) = 0 Тогда
Продолжить;
КонецЕсли;
СтрокаТаблицыОшибок.Номер = НомерСтроки;
СтрокаТаблицыОшибок.Обработана = Истина;
СтрокаТаблицыОшибок.Закладка = ПозицияКурсора + 1 + СтрДлина(ТекстСтроки) - СтрДлина(СокрЛП(ТекстСтроки)) - Смещение;
Смещение = Смещение + СтрДлина("Error=""""00000000-0000-0000-0000-000000000000""");
ТекстовыйДокумент.ЗаменитьСтроку(НомерСтроки, СтрЗаменить(ТекстСтроки, " Error=""" + СтрокаТаблицыОшибок.ИД + """", ""));
КонецЦикла;
ПозицияКурсора = ПозицияКурсора + СтрДлина(ТекстСтроки) + 1;
КонецЦикла;
КонецПроцедуры
&НаСервере
Процедура ПроверитьУзел(УзелDOM, ТипXDTO)
//Пытаемся прочитать УзелDOM ФабрикойXDTO
УзелКорректный = Истина;
#Область ЧтениеУзловДОМ
ЧтениеУзловDOM = Новый ЧтениеУзловDOM;
ЧтениеУзловDOM.Открыть(УзелDOM);
Попытка
Фабрика = ФабрикаXDTO.ПрочитатьXML(ЧтениеУзловDOM, ТипXDTO);
Исключение
УзелКорректный = Ложь;
КонецПопытки;
ЧтениеУзловDOM.Закрыть();
#КонецОбласти
//Если узел корректный, выходим из рекурсии
Если УзелКорректный Тогда
Возврат;
КонецЕсли;
//Если это свойство содержащее простое значение, значит оно некорректное
Если ТипЗнч(УзелDOM.ПервыйДочерний) = Тип("ТекстDOM") Или ТипЗнч(ТипXDTO) = Тип("ТипЗначенияXDTO") Тогда
ТекстСообщения = "Узел """ + УзелDOM.ИмяУзла + """ (тип """ + ТипXDTO.Имя + """) содержит некорректное значение """ + УзелDOM.ТекстовоеСодержимое + """";
ЗарегистрироватьОшибку(УзелDOM, ТекстСообщения);
//Есть вложенные, а их быть не должно
ИначеЕсли УзелDOM.ЕстьДочерниеУзлы() И ТипXDTO.Свойства.Количество() = 0 Тогда
ТекстСообщения = "Узел """ + УзелDOM.ИмяУзла + """ не должен содержать вложенных секций.";
ЗарегистрироватьОшибку(УзелDOM, ТекстСообщения);
//Не вложенных, а они должны быть
ИначеЕсли Не УзелDOM.ЕстьДочерниеУзлы() И ТипXDTO.Свойства.Количество() <> 0 Тогда
ТекстСообщения = "Узел """ + УзелDOM.РодительскийУзел.ИмяУзла + """ содержит пустую секцию """ + УзелDOM.ИмяУзла + """";
ЗарегистрироватьОшибку(УзелDOM, ТекстСообщения);
Иначе
//Получаем свойства соответствующего ТипаXDTO
УзелDOMСвойстваXDTO = Новый ТаблицаЗначений;
УзелDOMСвойстваXDTO.Колонки.Добавить("ТипXDTO");
УзелDOMСвойстваXDTO.Колонки.Добавить("ЛокальноеИмя", Новый ОписаниеТипов("Строка"));
УзелDOMСвойстваXDTO.Колонки.Добавить("Существует", Новый ОписаниеТипов("Булево"));
УзелDOMСвойстваXDTO.Колонки.Добавить("НеОбязательное", Новый ОписаниеТипов("Булево"));
Для Каждого СвойствоТипаXDTO Из ТипXDTO.Свойства Цикл
НоваяСтрока = УзелDOMСвойстваXDTO.Добавить();
НоваяСтрока.ТипXDTO = СвойствоТипаXDTO.Тип;
НоваяСтрока.ЛокальноеИмя = СвойствоТипаXDTO.ЛокальноеИмя;
НоваяСтрока.Существует = Ложь;
НоваяСтрока.НеОбязательное = СвойствоТипаXDTO.НижняяГраница = 0;
КонецЦикла;
//Проверяем наличие свойств
Для Каждого Дочерний Из УзелDOM.ДочерниеУзлы Цикл
СвойствоТипаXDTO = УзелDOMСвойстваXDTO.Найти(Дочерний.ИмяУзла, "ЛокальноеИмя");
Если СвойствоТипаXDTO <> Неопределено Тогда
СвойствоТипаXDTO.Существует = Истина;
ПроверитьУзел(Дочерний, СвойствоТипаXDTO.ТипXDTO);
Иначе
ТекстСообщения = "Узел """ + УзелDOM.ИмяУзла + """ содержит недопустимую вложенную секцию """ + Дочерний.ИмяУзла + """";
ЗарегистрироватьОшибку(Дочерний, ТекстСообщения);
КонецЕсли;
КонецЦикла;
ОтсутствующиеСвойства = УзелDOMСвойстваXDTO.НайтиСтроки(Новый Структура("Существует, НеОбязательное", Ложь, Ложь));
Для Каждого ОтсутствующееСвойство Из ОтсутствующиеСвойства Цикл
ТекстСообщения = "Отсутствует обязательная вложенная секция";
ЗарегистрироватьОшибку(УзелDOM, ТекстСообщения);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ЗарегистрироватьОшибку(УзелДОМ, ОписаниеОшибки)
ПолноеИмяУзла = УзелДОМ.ИмяУзла;
РодительскийУзел = УзелДОМ.РодительскийУзел;
Пока РодительскийУзел.ИмяУзла <> "#document" Цикл
МассивСтрок = Новый Массив;
МассивСтрок.Добавить(РодительскийУзел.ИмяУзла);
МассивСтрок.Добавить("\");
МассивСтрок.Добавить(ПолноеИмяУзла);
ПолноеИмяУзла = СтрСоединить(МассивСтрок);
РодительскийУзел = РодительскийУзел.РодительскийУзел;
КонецЦикла;
ГУИД = Строка(Новый УникальныйИдентификатор);
Атрибут = УзелДОМ.ДокументВладелец.СоздатьАтрибут("Error");
Атрибут.Значение = СокрЛП(ГУИД);
УзелДОМ.Атрибуты.УстановитьИменованныйЭлемент(Атрибут);
СтрокаТаблицы = ТаблицаОшибок.Добавить();
СтрокаТаблицы.ИД = ГУИД;
СтрокаТаблицы.Реквизит = ПолноеИмяУзла;
СтрокаТаблицы.Значение = УзелДОМ.ИмяУзла;
СтрокаТаблицы.Описание = ОписаниеОшибки;
КонецПроцедуры
Во вложении обработка визуально отображающая строки XML-файла содержащие некорректные секции. Переход к строке осуществляется по двойному клику на строку таблицы ошибок. Конфигурация должна содержать XDTO-пакет с пространством имен проверяемого XML-файла.
Платформа 8.3.14.