Как реализовать рекурсивную выгрузку объектов 1С в XML с вложенными ссылочными данными?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет IT и автоматизация бизнеса
← На главную

При обмене данными между существенно различающимися конфигурациями (например, при переходе с 1С:Управление торговлей 10.3 на 11.4) программисты часто сталкиваются с тем, что стандартные механизмы XDTO или СериализаторXDTO выдают ошибки из-за несовпадения описаний типов в метаданных. В таких случаях возникает необходимость ручного формирования XML-файла. Однако стандартный метод ЗаписатьXML для ссылочных реквизитов выгружает только их Уникальный Идентификатор (GUID). Если нам нужно передать объект "целиком" вместе со всеми данными связанных справочников внутри одного файла, нам потребуется реализовать механизм рекурсивной выгрузки.

В этой статье мы подробно разберем, как самостоятельно написать процедуру, которая будет обходить реквизиты объекта и, встречая ссылку, выгружать данные вложенного объекта непосредственно в структуру текущего XML-узла.

Почему стандартный метод не подходит?

Рассмотрим стандартное поведение платформы. Когда мы вызываем ЗаписатьXML(Запись, Ссылка), система генерирует XML-узел, содержащий строковое представление ссылки (ее GUID). При загрузке в другую информационную базу система попытается найти объект с таким идентификатором. Если база-приемник пуста, мы получим "Битую ссылку". Наша задача — сделать так, чтобы в XML попал не только GUID, но и наименование, ИНН, родитель и другие важные реквизиты вложенного справочника.

Алгоритм рекурсивной выгрузки

Для решения задачи мы проанализируем подход с использованием метаданных объекта. Нам необходимо создать универсальную процедуру, которая будет принимать объект (например, СправочникОбъект или ДокументОбъект) и записывать его свойства в поток XML. Если значение реквизита является ссылкой на другой объект, процедура должна вызвать саму себя для этого вложенного значения.

Рассмотрим программный код такой процедуры:


Процедура ВыгрузитьРекурсивно(ЗаписьXML, Объект, МетаданныеОбъекта)
    
    // Определяем типы, которые мы будем записывать как текст
    МассивПримитивныхТипов = Новый Массив;
    МассивПримитивныхТипов.Добавить(Тип("Число"));
    МассивПримитивныхТипов.Добавить(Тип("Строка"));
    МассивПримитивныхТипов.Добавить(Тип("Дата"));
    МассивПримитивныхТипов.Добавить(Тип("Булево"));

    // Открываем узел объекта в зависимости от его вида
    ИмяТипа = "";
    Если Метаданные.Справочники.Содержит(МетаданныеОбъекта) Тогда
        ИмяТипа = "CatalogObject." + МетаданныеОбъекта.Имя;
    ИначеЕсли Метаданные.Документы.Содержит(МетаданныеОбъекта) Тогда
        ИмяТипа = "DocumentObject." + МетаданныеОбъекта.Имя;
    КонецЕсли;

    ЗаписьXML.ЗаписатьНачалоЭлемента(ИмяТипа);

    // Обрабатываем стандартные реквизиты (Код, Наименование и т.д.)
    Для Каждого СтандартныйРеквизит Из МетаданныеОбъекта.СтандартныеРеквизиты Цикл
        ЗаписьXML.ЗаписатьНачалоЭлемента(СтандартныйРеквизит.Имя);
        ЗаписьXML.ЗаписатьТекст(Строка(Объект[СтандартныйРеквизит.Имя]));
        ЗаписьXML.ЗаписатьКонецЭлемента();
    КонецЦикла;

    // Перебираем основные реквизиты объекта
    Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
        Значение = Объект[Реквизит.Имя];
        
        // Если тип примитивный — просто записываем значение
        Если МассивПримитивныхТипов.Найти(ТипЗнч(Значение)) <> Неопределено Тогда
            ЗаписьXML.ЗаписатьНачалоЭлемента(Реквизит.Имя);
            ЗаписьXML.ЗаписатьТекст(Формат(Значение, "ЧГ=0"));
            ЗаписьXML.ЗаписатьКонецЭлемента();
        Иначе
            // Если это ссылка, получаем объект и уходим в рекурсию
            Если ЗначениеЗаполнено(Значение) Тогда
                ЗаписьXML.ЗаписатьНачалоЭлемента(Реквизит.Имя);
                
                ВложенныйОбъект = Значение.ПолучитьОбъект();
                Если ВложенныйОбъект <> Неопределено Тогда
                    ВыгрузитьРекурсивно(ЗаписьXML, ВложенныйОбъект, ВложенныйОбъект.Метаданные());
                КонецЕсли;
                
                ЗаписьXML.ЗаписатьКонецЭлемента();
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;

    ЗаписьXML.ЗаписатьКонецЭлемента(); // Закрываем ИмяТипа

КонецПроцедуры

Проблема бесконечной рекурсии (зацикливание)

Проанализируем важный нюанс: что произойдет, если справочник "Контрагенты" ссылается на "Договоры", а в "Договорах" есть ссылка обратно на того же "Контрагента"? Наш код уйдет в бесконечный цикл и вызовет переполнение стека. Чтобы этого избежать, необходимо контролировать уже выгруженные объекты.

Решение: создадим Соответствие или Массив, в который будем помещать уникальные идентификаторы (GUID) всех выгруженных в текущем сеансе объектов. Перед тем как выгружать вложенный объект, проверим, не выгружали ли мы его ранее. Если объект уже есть в списке — выгружаем только его ссылку (GUID), если нет — выгружаем его данные полностью и добавляем GUID в список.

Обработка табличных частей

Для полноты картины нам необходимо расширить алгоритм для работы с табличными частями. Табличная часть — это коллекция строк, каждая из которых также содержит реквизиты. Мы должны перебрать все табличные части метаданных, затем все строки в объекте и для каждой колонки применить ту же логику, что и для реквизитов шапки.

Разберем пример обхода табличных частей:


Для Каждого МетаТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
    ЗаписьXML.ЗаписатьНачалоЭлемента(МетаТЧ.Имя);
    Для Каждого СтрокаТЧ Из Объект[МетаТЧ.Имя] Цикл
        ЗаписьXML.ЗаписатьНачалоЭлемента("Row");
        Для Каждого РеквизитТЧ Из МетаТЧ.Реквизиты Цикл
            // Здесь повторяем логику проверки типа (примитивный или ссылочный)
            // и вызов рекурсии для ссылок
        КонецЦикла;
        ЗаписьXML.ЗаписатьКонецЭлемента(); // Конец Row
    КонецЦикла;
    ЗаписьXML.ЗаписатьКонецЭлемента(); // Конец ТЧ
КонецЦикла;

Особенности загрузки такого XML

При загрузке "матрешечного" XML-файла (где один объект вложен в другой) мы сталкиваемся с тем, что не можем записать родительский объект, пока не созданы все его дочерние объекты-ссылки. Рассмотрим две стратегии загрузки:

  1. Создание "пустышек": При чтении XML сначала создается пустая ссылка с нужным GUID (через метод УстановитьСсылкуНового()), а затем, когда парсер дойдет до данных этого объекта, реквизиты заполняются.
  2. Загрузка "снизу вверх": Рекурсия при чтении сначала доходит до самого глубокого уровня (объекта, не имеющего ссылок), записывает его в базу, возвращает ссылку на уровень выше и так далее до корневого объекта.

Проанализируем технический момент: использование ПостроительDOM удобно для небольших файлов, так как он загружает всё дерево XML в оперативную память. Однако, если вы выгружаете справочник "Номенклатура" со всеми вложенными единицами измерения и характеристиками, файл может стать огромным. В этом случае настоятельно рекомендуем использовать ЧтениеXML. Это потоковый метод, который читает файл последовательно, потребляя минимум памяти.

Выводы и рекомендации

Создание собственного механизма рекурсивной выгрузки — это мощный инструмент для решения нестандартных задач обмена. Однако помните о следующих правилах:

Такой подход позволит вам гибко настроить перенос данных между любыми конфигурациями 1С, не ограничиваясь жесткими рамками стандартных схем обмена.

← На главную