Работа с форматом XML в «1С:Предприятие 8» является одной из базовых задач при организации обмена данными между различными информационными системами. Однако даже опытные разработчики иногда сталкиваются с ошибками разбора структуры файла. В рамках данной статьи мы подробно разберем ситуацию, когда при загрузке данных возникает фатальная ошибка «Extra content at the end of the document», выясним причины ее появления и рассмотрим несколько способов реализации надежного обмена данными.
Ошибка «Extra content at the end of the document» (Лишнее содержимое в конце документа) чаще всего свидетельствует о нарушении иерархической структуры XML-файла. Согласно спецификации XML, документ должен иметь строго один корневой элемент, внутри которого располагаются все остальные узлы. Если парсер встречает данные там, где они не ожидаются (например, после закрытия корневого тега), или если структура тегов нарушена настолько, что парсер не может определить границы документа, возникает эта ошибка.
Давайте проанализируем фрагмент кода выгрузки, который привел к проблеме:
ЗаписьXML.ЗаписатьНачалоЭлемента("Выгрузка");
Для Каждого СТР Из Выборка Цикл
ЗаписьXML.ЗаписатьНачалоЭлемента("НовыйДокумент");
// ... запись реквизитов (Номер, Дата и т.д.)
//ЗаписьXML.ЗаписатьКонецЭлемента(); // ОШИБКА: Строка закомментирована!
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента();
В данном примере мы видим, что внутри цикла вызывается метод ЗаписатьНачалоЭлемента для узла НовыйДокумент, но соответствующий ему метод ЗаписатьКонецЭлемента закомментирован. Это приводит к тому, что каждый последующий документ в цикле открывается внутри предыдущего. В итоге вместо плоского списка документов получается огромная «матрешка». Когда цикл завершается, вызывается только один ЗаписатьКонецЭлемента, который закрывает самый первый узел Выгрузка, оставляя все внутренние узлы НовыйДокумент незакрытыми. При попытке прочитать такой файл объект ЧтениеXML выдает ошибку, так как структура файла не соответствует правилам формирования XML-документов.
Первый и самый очевидный способ решения — восстановить корректную последовательность открытия и закрытия тегов. Рассмотрим правильный алгоритм ручного формирования XML. Важно следить, чтобы каждому вызову ЗаписатьНачалоЭлемента строго соответствовал вызов ЗаписатьКонецЭлемента в той же области видимости или итерации цикла.
Проанализируем исправленный код выгрузки:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяВременногоФайла, "UTF-8");
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("ДанныеОбмена"); // Корневой узел
Для Каждого СтрокаДанных Из ТаблицаВыгрузки Цикл
ЗаписьXML.ЗаписатьНачалоЭлемента("ДокументРКО");
// Используем стандартную функцию для упрощения записи
ЗаписатьXML(ЗаписьXML, "Номер", СтрокаДанных.Номер);
ЗаписатьXML(ЗаписьXML, "Дата", СтрокаДанных.Дата);
ЗаписатьXML(ЗаписьXML, "Сумма", СтрокаДанных.Сумма);
ЗаписьXML.ЗаписатьКонецЭлемента(); // Закрываем ДокументРКО
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента(); // Закрываем Корневой узел
ЗаписьXML.Закрыть();
Обратите внимание на использование глобального метода ЗаписатьXML. Это значительно сокращает код, так как он автоматически выполняет три действия: открывает узел, записывает значение (приводя его к строковому формату XML) и закрывает узел. Рассмотрим этот метод подробнее в следующем блоке.
Вместо того чтобы вручную прописывать ЗаписатьНачалоЭлемента, ЗаписатьТекст и ЗаписатьКонецЭлемента для каждого реквизита, мы можем использовать встроенную процедуру ЗаписатьXML. Она принимает объект ЗаписьXML, имя узла и само значение.
Преимущества этого подхода:
Дата, Число или Булево в формат, понятный XML (например, даты записываются в формате ISO 8601).Пример оптимизированной записи реквизита:
ЗаписатьXML(ЗаписьXML, "Контрагент", СТР.Получатель);
Если нам необходимо выгрузить и загрузить сложные структуры данных, такие как ТаблицаЗначений или целые объекты метаданных, наиболее эффективным способом будет использование Сериализатора XDTO. Этот механизм позволяет превратить любой объект 1С в XML и обратно одной командой.
Разберем пример выгрузки таблицы значений через сериализатор:
// На сервере
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, РезультатЗапроса.Выгрузить());
СтрокаXML = ЗаписьXML.Закрыть();
И пример последующей загрузки:
// На сервере
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXML);
ТаблицаДанных = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
ЧтениеXML.Закрыть();
Этот метод полностью исключает ошибки «лишнего содержимого» или «незакрытых тегов», так как формирование структуры полностью берет на себя платформа. Однако помните, что при загрузке объектов через XDTO необходимо, чтобы типы данных в базе-приемнике совпадали с типами в базе-источнике.
В исходном коде автора использовался метод Парсер.УстановитьСтроку(). Давайте выясним, почему это может стать проблемой. Данный метод загружает весь текст XML-файла в оперативную память. Если ваш файл весит несколько сотен мегабайт, сервер 1С может аварийно завершить работу из-за нехватки памяти.
Для оптимизации процесса загрузки мы рекомендуем использовать потоковое чтение напрямую из файла:
Парсер = Новый ЧтениеXML;
Парсер.ОткрытьФайл(ПутьКФайлу); // Читаем данные порциями с диска
Пока Парсер.Прочитать() Цикл
Если Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента И Парсер.Имя = "НовыйДокумент" Тогда
// Логика создания документа
КонецЕсли;
КонецЦикла;
Парсер.Закрыть();
Метод ОткрытьФайл позволяет обрабатывать гигабайтные файлы, используя минимальное количество оперативной памяти, так как парсер считывает данные последовательно.
При загрузке документов крайне важно обеспечить целостность базы данных. Если в процессе чтения файла из 1000 документов на 500-м произойдет ошибка (например, из-за некорректной суммы), без использования транзакции у вас в базе останется 499 «полуфабрикатов».
Рассмотрим, как правильно организовать запись документов:
НачатьТранзакцию();
Попытка
Пока Парсер.Прочитать() Цикл
// ... разбор XML и заполнение документа
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("ЗагрузкаXML", УровеньЖурналаРегистрации.Ошибка, , , КраткоеОписаниеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Загрузка прервана из-за ошибки. Изменения не сохранены.";
КонецПопытки;
Использование НачатьТранзакцию() гарантирует, что либо все документы из файла будут успешно загружены, либо, в случае любой ошибки, база вернется в исходное состояние. Это критически важно для финансового учета.
При разработке загрузки часто забывают о том, что один и тот же файл может быть загружен дважды. Использование метода СоздатьДокумент() без предварительной проверки приведет к дублированию записей.
Проанализируем правильную стратегию:
UUID объекта.Пример поиска перед созданием:
ДокументСсылка = Документы.РасходныйКассовыйОрдер.НайтиПоНомеру(НомерИзXML, ДатаИзXML);
Если ДокументСсылка.Пустая() Тогда
ОбъектДокумент = Документы.РасходныйКассовыйОрдер.СоздатьДокумент();
Иначе
ОбъектДокумент = ДокументСсылка.ПолучитьОбъект();
КонецЕсли;
// Заполнение реквизитов и запись...
Применяя данные подходы, вы не только избавитесь от ошибок разбора XML, но и создадите надежный, производительный механизм обмена данными, устойчивый к сбоям и дублированию информации.