При реализации интеграционных механизмов между системами 1С и внешними сайтами (например, интернет-магазинами на платформе 1С-Битрикс) разработчики регулярно сталкиваются с необходимостью выгрузки данных в формате CommerceML 2. Для решения этой задачи традиционно применяется встроенный механизм платформы — Фабрика XDTO и поставляемые схемы XSD (в частности, типовой пакет CML208).
Однако структура схемы таит в себе неочевидные элементы, такие как анонимные типы, которые в редакторе XDTO-пакета конфигуратора отображаются с пустым типом и описанием «Определение типа». Попытка заполнить такие свойства стандартными методами «в лоб» приводит к ошибке несоответствия типов. Проанализируем причину возникновения этой проблемы и разберем наиболее надежные и правильные способы ее решения.
Рассмотрим структуру объекта Адрес в схеме CommerceML. Данный объект содержит список АдресноеПоле, предназначенный для хранения составляющих адреса (таких как страна, регион, город, улица). Если открыть XDTO-пакет CML208 в конфигураторе 1С, мы увидим, что у свойства АдресноеПоле колонка «Тип» пуста. Вместо этого внутри свойства создается локальное «Определение типа» (так называемый анонимный inline-тип), содержащий свойства Тип и Значение.
Попытка получить тип этого поля через фабрику стандартным путем:
ТипАдресногоПоля = ФабрикаXDTO.Тип("urn:1C.ru:commerceml_2", "АдресноеПоле");
всегда будет возвращать значение Неопределено, поскольку данный тип не имеет собственного уникального имени в глобальном пространстве имен схемы. Платформа 1С не знает о существовании такого изолированного типа данных вне контекста родительского объекта, из-за чего разработчики часто получают ошибку «Несоответствие типов» при попытке сериализовать или добавить созданный элемент в список.
Выясним наиболее чистый и правильный с точки зрения архитектуры способ. Мы можем получить описание анонимного типа, обратившись напрямую к свойствам его родительского типа. Поскольку тип Адрес является глобальным и известным, мы можем прочитать его структуру и извлечь тип вложенного свойства АдресноеПоле без создания самого объекта.
Разберем этот процесс по шагам на примере кода:
// Шаг 1. Получаем глобальный тип "Адрес" из фабрики XDTO по URI пространства имен
ТипАдрес = ФабрикаXDTO.Тип("urn:1C.ru:commerceml_2", "Адрес");
// Шаг 2. Находим свойство "АдресноеПоле" в метаданных родителя и извлекаем его тип
ТипАдресногоПоля = ТипАдрес.Свойства.Получить("АдресноеПоле").Тип;
// Шаг 3. Теперь мы можем безопасно создавать объекты нужного анонимного типа
НовыйАдресXDTO = ФабрикаXDTO.Создать(ТипАдрес);
НовоеПолеXDTO = ФабрикаXDTO.Создать(ТипАдресногоПоля);
Данный способ является наиболее производительным и надежным, поскольку он исключает работу с «магическими» строками и не требует предварительного создания экземпляра родительского объекта для анализа его свойств.
Рассмотрим альтернативный способ, который основывается на анализе метаданных уже созданного и инициализированного экземпляра объекта XDTO. Если у нас уже создан целевой объект Адрес, мы можем динамически обратиться к его списку АдресноеПоле и получить тип составляющих его элементов через специальное системное свойство ВладеющееСвойство.Тип.
Посмотрим на пример практической реализации этого подхода:
// Создаем экземпляр адреса стандартным способом
АдресТип = ФабрикаXDTO.Тип("urn:1C.ru:commerceml_2", "Адрес");
АдресXDTO = ФабрикаXDTO.Создать(АдресТип);
// Получаем тип анонимного элемента через метаданные списка уже существующего объекта
ТипАдресногоПоля = АдресXDTO.АдресноеПоле.ВладеющееСвойство.Тип;
// Создаем объект адресного поля на основе полученного типа
АдресноеПолеXDTO = ФабрикаXDTO.Создать(ТипАдресногоПоля);
Хотя этот вариант полностью рабочий, он заставляет разработчика совершать лишние действия в памяти (сначала инициализировать родительский объект, чтобы затем вытащить из него описание вложенных типов), что делает его менее изящным по сравнению с первым решением.
При работе со списками XDTO начинающие разработчики часто совершают грубую ошибку, связанную с непониманием ссылочной модели данных в оперативной памяти 1С. Проанализируем типичный ошибочный код:
// ВНИМАНИЕ: Код содержит критическую логическую ошибку!
АдресноеПолеXDTO = ФабрикаXDTO.Создать(АдресXDTO.АдресноеПоле.ВладеющееСвойство.Тип);
// Пытаемся добавить Страну
АдресноеПолеXDTO.Тип = "Страна";
АдресноеПолеXDTO.Значение = "РОССИЯ";
АдресXDTO.АдресноеПоле.Добавить(АдресноеПолеXDTO);
// Пытаемся добавить Регион в тот же самый объект
АдресноеПолеXDTO.Тип = "Регион";
АдресноеПолеXDTO.Значение = "Краснодарский край";
АдресXDTO.АдресноеПоле.Добавить(АдресноеПолеXDTO);
Выясним причину некорректной работы этой конструкции. Переменная АдресноеПолеXDTO указывает на конкретную область в памяти, где был создан единственный экземпляр объекта. Когда мы присваиваем свойства «Страна» и добавляем элемент в список, в коллекцию помещается ссылка на этот объект. На следующем шаге мы меняем значение свойства Тип у того же самого объекта в памяти на «Регион» и снова добавляем его в список.
В результате при сериализации в XML-файл мы получим дублирующиеся данные — у нас дважды выгрузится «Регион», а «Страна» будет полностью стерта, так как коллекция содержит две ссылки на один и тот же измененный объект. Чтобы избежать этой проблемы, необходимо принудительно инициализировать новый экземпляр через ФабрикаXDTO.Создать() для каждого нового элемента списка.
Объединим полученные знания и напишем надежный, чистый алгоритм заполнения адреса контрагента для последующей выгрузки. Мы будем использовать правильный способ извлечения типов через метаданные и создавать новые объекты в цикле.
// 1. Инициализируем типы данных
ПространствоИмен = "urn:1C.ru:commerceml_2";
ТипАдрес = ФабрикаXDTO.Тип(ПространствоИмен, "Адрес");
ТипАдресногоПоля = ТипАдрес.Свойства.Получить("АдресноеПоле").Тип;
// 2. Создаем основной объект адреса
АдресXDTO = ФабрикаXDTO.Создать(ТипАдрес);
АдресXDTO.Представление = ТекСтрока.АдресПредставление;
// 3. Подготовим структуру данных для заполнения
АдресныеДанные = Новый Соответствие;
Если ЗначениеЗаполнено(ТекСтрока.АдресСтрана) Тогда
АдресныеДанные.Вставить("Страна", ТекСтрока.АдресСтрана);
КонецЕсли;
Если ЗначениеЗаполнено(ТекСтрока.АдресРегион) Тогда
АдресныеДанные.Вставить("Регион", ТекСтрока.АдресРегион);
КонецЕсли;
Если ЗначениеЗаполнено(ТекСтрока.АдресГород) Тогда
АдресныеДанные.Вставить("Город", ТекСтрока.АдресГород);
КонецЕсли;
// 4. Заполняем список анонимных объектов в цикле
Для Каждого ТекущееПоле Из АдресныеДанные Цикл
// КРИТИЧЕСКИ ВАЖНО: Создаем НОВЫЙ объект на каждой итерации цикла!
НовоеПоле = ФабрикаXDTO.Создать(ТипАдресногоПоля);
НовоеПоле.Тип = ТекущееПоле.Ключ;
НовоеПоле.Значение = ТекущееПоле.Значение;
// Добавляем созданный объект в список адреса
АдресXDTO.АдресноеПоле.Добавить(НовоеПоле);
КонецЦикла;
Такой подход полностью гарантирует корректную структуру выгружаемого XML-файла и исключает потерю или случайное дублирование данных в оперативной памяти.
При разработке сложных интеграционных механизмов на основе XDTO-пакетов рекомендуется придерживаться следующих правил:
ОбъектXDTO.Проверить(). Данный метод выполняет автоматическую валидацию данных на соответствие исходной схеме XSD (проверяет обязательность заполнения реквизитов, ограничения по типам и длине строк) и позволяет отловить ошибки еще на стороне 1С.ОбменДаннымиXDTOСервер. Использование встроенной функции ОбъектXDTOИзДанныхXDTO() позволяет автоматически конвертировать обычные структуры и соответствия 1С в сложные древовидные структуры XDTO без необходимости вручную описывать создание каждого вложенного анонимного объекта.