При интеграции 1С с внешними сайтами или сервисами разработчики нередко сталкиваются с ситуацией, когда выгрузка формируется с нарушением стандарта XML. Наиболее частый случай — отсутствие общего корневого тега, когда в файле друг за другом идет несколько повторяющихся узлов верхнего уровня (например, несколько блоков NewRequest).
При попытке прочитать такой файл стандартным объектом ЧтениеXML платформа 1С на втором блоке вызывает исключение:
Ошибка разбора XML: - [12,2]
Фатальная ошибка:
Extra content at the end of the document
Выясним причину такого поведения платформы и подробно разберем несколько надежных способов обхода этой проблемы.
Спецификация стандарта XML строго регламентирует наличие одного и только одного корневого элемента (root element), который оборачивает все остальные узлы документа. Файл с несколькими тегами верхнего уровня считается не полноценным XML-документом, а так называемым фрагментом XML (XML Fragment).
Объект ЧтениеXML в платформе 1С опирается на строгий синтаксический анализатор. Как только парсер доходит до закрывающего тега первого блока NewRequest, он считает документ завершенным. Обнаружив следующий тег NewRequest, парсер фиксирует наличие лишних данных после конца документа (Extra content at the end of the document) и аварийно завершает чтение потока.
Попытка обернуть чтение каждого элемента в конструкцию Попытка ... Исключение не поможет: после возникновения фатальной ошибки парсер не может продолжить чтение дальше, и все последующие заказы будут потеряны. Для успешной загрузки необходимо привести структуру к стандарту XML перед передачей в объект ЧтениеXML.
Если размер загружаемого файла невелик (до нескольких десятков мегабайт), проще и быстрее всего прочитать его в строковую переменную, добавить обрамляющий корневой тег и передать готовую строку в парсер без перезаписи файла на диске.
Разберем по шагам реализацию этого метода:
ТекстовыйДокумент или ЧтениеТекста.<?xml ... ?> (если она присутствует в начале), чтобы не нарушить валидность новой структуры.<root>...</root>.ЧтениеXML через метод УстановитьСтроку().Посмотрим на пример программного кода:
ПутьКФайлу = "C:\111233\webto1c.xml";
// 1. Читаем исходный файл в строку
ЧтениеТ = Новый ЧтениеТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ИсходныйТекст = ЧтениеТ.Прочитать();
ЧтениеТ.Закрыть();
// 2. Убираем заголовок XML при наличии
ПозицияЗакрытияЗаголовка = СтрНайти(ИсходныйТекст, "?>");
Если ПозицияЗакрытияЗаголовка > 0 Тогда
ИсходныйТекст = Сред(ИсходныйТекст, ПозицияЗакрытияЗаголовка + 2);
КонецЕсли;
// 3. Добавляем искусственный корневой тег
КорректныйXML = "<root>" + ИсходныйТекст + "</root>";
// 4. Разбираем исправленный XML стандартным потоком
Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(КорректныйXML);
НовыйДокумент = Неопределено;
Адрес = Новый Структура;
Пока Чтение.Прочитать() Цикл
Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "NewRequest" Тогда
НовыйДокумент = Документы.cleanЗаказССайта.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();
Адрес.Очистить();
ИначеЕсли Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "Addr" Тогда
Пока Чтение.ПрочитатьАтрибут() Цикл
Адрес.Вставить(Чтение.Имя, Чтение.Значение);
КонецЦикла;
ИначеЕсли Чтение.ТипУзла = ТипУзлаXML.Текст И НовыйДокумент <> Неопределено Тогда
Если ИмяПредыдущегоУзла = "Date" Тогда
НовыйДокумент.ДатаССайта = XMLЗначение(Тип("Дата"), Чтение.Значение);
ИначеЕсли ИмяПредыдущегоУзла = "Number" Тогда
НовыйДокумент.НомерЗаказа = Чтение.Значение;
ИначеЕсли ИмяПредыдущегоУзла = "RequestDate" Тогда
НовыйДокумент.ДатаВыезда = XMLЗначение(Тип("Дата"), Чтение.Значение);
ИначеЕсли ИмяПредыдущегоУзла = "PhNumber" Тогда
НовыйДокумент.НомерТелефона = Прав(Чтение.Значение, СтрДлина(Чтение.Значение) - 1);
ИначеЕсли ИмяПредыдущегоУзла = "Customer" Тогда
НовыйДокумент.Клиент = Чтение.Значение;
ИначеЕсли ИмяПредыдущегоУзла = "Comments" Тогда
НовыйДокумент.Комментарий = Чтение.Значение;
КонецЕсли;
ИначеЕсли Чтение.ТипУзла = ТипУзлаXML.КонецЭлемента И Чтение.Имя = "NewRequest" Тогда
Если Адрес.Свойство("Town") Тогда
НовыйДокумент.Адрес = Адрес.Town + " " + Адрес.Street + " " + Адрес.House + " " + Адрес.Flat;
КонецЕсли;
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
НовыйДокумент = Неопределено;
КонецЕсли;
Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
ИмяПредыдущегоУзла = Чтение.Имя;
КонецЕсли;
КонецЦикла;
Чтение.Закрыть();
Если файл выгрузки имеет значительный объем (сотни мегабайт), считывание его целиком в оперативную память может привести к ошибкам нехватки памяти. В такой ситуации применим потоковую построчную перезапись во временный файл.
Рассмотрим алгоритм потокового формирования валидного XML-файла:
ПолучитьИмяВременногоФайла("xml").ЗаписьТекста и записываем в начало открывающий тег <root>.ЧтениеТекста построчно считываем исходный файл методом ПрочитатьСтроку().<?xml, а все остальные строки без изменений переносим в поток записи.</root> и закрываем оба файла.ЧтениеXML.ОткрытьФайл(), а после обработки удаляем его вызовом УдалитьФайлы().Посмотрим на пример реализации потоковой нормализации:
ИсходныйФайл = "C:\111233\webto1c.xml";
ВременныйФайл = ПолучитьИмяВременногоФайла("xml");
ЧтениеТ = Новый ЧтениеТекста(ИсходныйФайл, КодировкаТекста.UTF8);
ЗаписьТ = Новый ЗаписьТекста(ВременныйФайл, КодировкаТекста.UTF8);
ЗаписьТ.ЗаписатьСтроку("<root>");
Стр = ЧтениеТ.ПрочитатьСтроку();
Пока Стр <> Неопределено Цикл
Если Не СтрНачинаетсяС(СокрЛП(Стр), "<?xml") Тогда
ЗаписьТ.ЗаписатьСтроку(Стр);
КонецЕсли;
Стр = ЧтениеТ.ПрочитатьСтроку();
КонецЦикла;
ЗаписьТ.ЗаписатьСтроку("</root>");
ЧтениеТ.Закрыть();
ЗаписьТ.Закрыть();
// Читаем нормализованный файл
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ВременныйФайл);
Пока Чтение.Прочитать() Цикл
// Разбор элементов документа
КонецЦикла;
Чтение.Закрыть();
// Удаляем временный файл после разбора
УдалитьФайлы(ВременныйФайл);
Проанализируем архитектурную сторону задачи: наиболее правильным решением является исправление скрипта выгрузки на стороне веб-сервера. Разработчикам сайта следует настроить оборачивание массива заказов в общий корневой узел, например <Requests>...</Requests>. Это исключит необходимость вспомогательной предобработки на стороне 1С и гарантирует соответствие выгрузки международному стандарту XML 1.0.