При интеграции систем 1С с внешними источниками разработчики часто сталкиваются с механизмами XDTO. Типичный сценарий: у нас есть файл XML и схема XSD, описывающая его структуру. Мы пытаемся прочитать файл через СериализаторXDTO, но метод ВозможностьЧтенияXML(ЧтениеХМЛ) упорно возвращает Ложь, хотя при использовании метода ФабрикаXDTO.ПрочитатьXML() данные считываются без проблем. В этой статье мы подробно разберем, почему возникает такая ситуация, проанализируем разницу в назначении этих механизмов и научимся правильно писать код для импорта данных.
Для начала выясним главную архитектурную причину проблемы. Несмотря на то что оба объекта работают с технологией XDTO, они решают абсолютно разные задачи:
ОбъектXDTO на основе предоставленной схемы XSD. Фабрика абсолютно ничего не знает о метаданных вашей информационной базы (справочниках, документах, регистрах). Ей не важно, существуют ли такие объекты в конфигурации 1С.ДокументОбъект.РасходТовара или СправочникОбъект.Номенклатура). Он осуществляет двустороннее отображение между типами XDTO и типами встроенного языка 1С.Если вы создаете изолированную фабрику динамически из файла схемы с помощью метода СоздатьФабрикуXDTO(ПутьКФабрике), то связанный с ней СериализаторXDTO не будет иметь связей с метаданными конфигурации. Он просто «не понимает», в какой именно справочник или документ 1С нужно преобразовать прочитанную ветку XML. Именно поэтому метод ВозможностьЧтенияXML() всегда будет возвращать Ложь.
Проанализируем фрагмент кода, который часто приводит к ошибкам при попытке использовать сериализатор:
ФабрикаПолучателя = СоздатьФабрикуXDTO(ПутьКФабрике);
ТекСериализатор = Новый СериализаторXDTO(ФабрикаПолучателя);
ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьФайл(ИмяФайлаЗагрузки);
ЧтениеХМЛ.Прочитать(); // Чтение корневого узла
Пока ТекСериализатор.ВозможностьЧтенияXML(ЧтениеХМЛ) Цикл
Тип_РасходТовара = ФабрикаXDTO.Тип("http://www.example.org", "РасходТовара");
ТекОбъект = ТекСериализатор.ПрочитатьXML(ЧтениеХМЛ, Тип_РасходТовара);
КонецЦикла;
В этом коде кроется критическая ошибка совместимости типов. Обратите внимание:
ТекСериализатор инициализируется на основе локальной фабрики ФабрикаПолучателя.Тип_РасходТовара получается из глобальной фабрики конфигурации ФабрикаXDTO.В платформе 1С:Предприятие типы, полученные из разных фабрик, считаются несовместимыми, даже если они имеют одинаковые имена и пространства имен. Передавать тип одной фабрики в сериализатор, созданный на основе другой фабрики, категорически нельзя. Это гарантированно вызовет отказ при проверке возможности чтения.
Для успешной десериализации объекта в прикладной тип 1С структура XML-файла должна строго соответствовать пространству имен типов платформы 1С. По умолчанию типы конфигурации сопоставляются с пространством имен:
http://v8.1c.ru/8.1/data/enterprise/current-config
Если ваш XML-файл содержит произвольные пространства имен или не содержит их вовсе, то СериализаторXDTO не сможет автоматически сопоставить узел XML с объектом базы данных 1С, поскольку он ищет системные типы вида DocumentObject.РасходТовара. Для произвольных XML-схем использование сериализатора теряет смысл.
---
Если вы загружаете данные из сторонней системы, структура которой не совпадает один в один с метаданными 1С, забудьте про СериализаторXDTO. Самым надежным и правильным способом является чтение XML в универсальный ОбъектXDTO с последующим созданием документа вручную.
Рассмотрим по шагам этот алгоритм:
// 1. Инициализируем чтение файла
ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьФайл(ИмяФайлаЗагрузки);
// 2. Создаем фабрику на основе схемы XSD
ФабрикаПолучателя = СоздатьФабрикуXDTO(ПутьКФабрике);
Тип_РасходТовара = ФабрикаПолучателя.Тип("http://www.example.org", "РасходТовара");
// 3. Читаем данные XML напрямую в объект XDTO
ДанныеXDTO = ФабрикаПолучателя.ПрочитатьXML(ЧтениеХМЛ, Тип_РасходТовара);
// 4. Переносим данные из объекта XDTO в реальный документ 1С
НовыйДокумент = Документы.РасходТовара.СоздатьДокумент();
НовыйДокумент.Дата = ДанныеXDTO.Дата;
НовыйДокумент.Номер = ДанныеXDTO.Номер;
// Если имена реквизитов совпадают, можно использовать автоматическое заполнение:
ЗаполнитьЗначенияСвойств(НовыйДокумент, ДанныеXDTO);
// Заполняем табличную часть, если она есть
Для Каждого СтрокаТовара Из ДанныеXDTO.Товары Цикл
НоваяСтрока = НовыйДокумент.Товары.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрокаТовара.Наименование);
НоваяСтрока.Количество = СтрокаТовара.Количество;
НоваяСтрока.Цена = СтрокаТовара.Цена;
КонецЦикла;
НовыйДокумент.Записать();
ЧтениеХМЛ.Закрыть();
---
Если вам все же крайне необходимо использовать именно СериализаторXDTO для автоматического создания объектов 1С, вам придется связать загружаемую схему с типами вашей конфигурации. Метод СоздатьФабрикуXDTO умеет принимать вторым параметром коллекцию пакетов платформы.
Проанализируем, как правильно инициализировать такую фабрику:
// 1. Помещаем путь к вашей схеме в массив
МассивСхем = Новый Массив;
МассивСхем.Добавить(ПутьКФабрике);
// 2. Создаем фабрику, объединяя вашу схему с пакетами глобальной фабрики конфигурации
ФабрикаПолучателя = СоздатьФабрикуXDTO(МассивСхем, ФабрикаXDTO.Пакеты);
// 3. Создаем сериализатор на базе объединенной фабрики
ТекСериализатор = Новый СериализаторXDTO(ФабрикаПолучателя);
ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьФайл(ИмяФайлаЗагрузки);
// Теперь важно получить тип именно из НАШЕЙ новой соединенной фабрики!
Тип_РасходТовара = ФабрикаПолучателя.Тип("http://v8.1c.ru/8.1/data/enterprise/current-config", "DocumentObject.РасходТовара");
Если ТекСериализатор.ВозможностьЧтенияXML(ЧтениеХМЛ) Тогда
// Объект будет создан автоматически с правильным типом ДокументОбъект.РасходТовара
СозданныйДокумент = ТекСериализатор.ПрочитатьXML(ЧтениеХМЛ, Тип_РасходТовара);
СозданныйДокумент.Записать();
КонецЕсли;
ЧтениеХМЛ.Закрыть();
Важное примечание: Чтобы этот вариант сработал, ваш XML-файл должен быть сформирован по правилам XML-сериализации 1С. В структуре XML должны присутствовать пространства имен платформы 1С, а имена узлов должны точно соответствовать именам объектов метаданных с префиксами видов объектов (например, DocumentObject.РасходТовара, а не просто РасходТовара).