Как обойти XML со сложной структурой в 1С: разбираем XDTO, DOM/XPath и быстрое последовательное чтение

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

При интеграции систем или переносе данных программисты 1С часто сталкиваются с необходимостью разбора XML-файлов. Особое удивление и трудности вызывают файлы, выгруженные с помощью стандартных механизмов обмена (например, через конфигурацию «Конвертация данных 2.1» или обработку «Выгрузка и загрузка данных XML»). Пытаясь прочитать такой файл «в лоб» через XDTO, разработчики обнаруживают, что часть ключевых полей (таких как уникальный идентификатор или код) просто не считывается стандартным циклом обхода свойств.

В этой статье мы подробно проанализируем, почему возникает эта проблема, разберем устройство структуры файлов обмена 1С и рассмотрим три практических способа решения задачи: правильный обход через ФабрикаXDTO, гибкий поиск элементов с помощью DOM/XPath, а также наиболее производительный и безопасный по памяти способ потокового чтения.

---

Выясняем причину: особенности структуры XML в обменах 1С

Для начала проанализируем структуру типичного XML-файла, сформированного «Конвертацией данных». Если мы откроем его, то увидим примерно следующую картину:


<Объект Тип="СправочникСсылка.ЯзыкиНародовМира">
    <Ссылка>
        <Свойство Имя="{УникальныйИдентификатор}">
            <Значение>31b9efb0-e910-11dc-b11e-0019d1e0b62b</Значение>
        </Свойство>
        <Свойство Имя="Код">
            <Значение>000000001</Значение>
        </Свойство>
    </Ссылка>
    <Свойство Имя="Наименование">
        <Значение>Украинский</Значение>
    </Свойство>
    <Свойство Имя="ПометкаУдаления">
        <Значение>false</Значение>
    </Свойство>
</Объект>

Обратите внимание на важную деталь: свойства объекта разделены на две группы:

  1. Вложенный узел Ссылка: содержит так называемые «поля поиска» (идентификации), по которым база-приемник определяет, существует ли уже такой элемент. Сюда помещаются {УникальныйИдентификатор} (GUID) и ключевые поля (например, Код или Артикул).
  2. Свойства первого уровня внутри узла Объект: содержат обычные реквизиты, которые подлежат переносу и записи (например, Наименование, ПометкаУдаления).

Именно поэтому простая попытка перебрать все свойства в цикле Для Каждого Свойство Из Объект.Свойство приводит к тому, что 1С видит только Наименование и ПометкаУдаления, полностью игнорируя Код и УИД, скрытые внутри контейнера Ссылка.

---

Способ 1. Исправление обхода XDTO (работа с вложенной структурой)

Если вы хотите продолжить использовать удобный механизм работы с XDTO-пакетами, вам необходимо явно учесть иерархию XML-документа. Нам потребуется сначала обойти свойства, находящиеся внутри объекта Ссылка, а затем перейти к свойствам самого объекта.

Рассмотрим по шагам рабочий код, который решает эту задачу:


Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ПутьКФайлу);

// Читаем XML в объектную модель XDTO
ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение);

Для Каждого Объект Из ОбъектXDTO.Объект Цикл
    
    УИД = "";
    Код = "";
    Наименование = "";
    ПометкаУдаления = Ложь;
    
    // Шаг 1: Обходим свойства идентификации (внутри узла Ссылка)
    Если Объект.Свойство("Ссылка") Тогда
        Для Каждого СвойствоСсылки Из Объект.Ссылка.Свойство Цикл
            Если СвойствоСсылки.Имя = "{УникальныйИдентификатор}" Тогда
                УИД = СвойствоСсылки.Значение;
            ИначеЕсли СвойствоСсылки.Имя = "Код" Тогда
                Код = СвойствоСсылки.Значение;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    
    // Шаг 2: Обходим основные реквизиты объекта
    Для Каждого СвойствоОбъекта Из Объект.Свойство Цикл
        Если СвойствоОбъекта.Имя = "Наименование" Тогда
            Наименование = СвойствоОбъекта.Значение;
        ИначеЕсли СвойствоОбъекта.Имя = "ПометкаУдаления" Тогда
            // Преобразуем текстовое представление булева значения в тип Булево
            ПометкаУдаления = XMLЗначение(Тип("Булево"), СвойствоОбъекта.Значение);
        КонецЕсли;
    КонецЦикла;
    
    // Здесь полученные данные можно записать в Табличную Часть или Таблицу Значений
    Сообщить(СтрШаблон("УИД: %1 | Код: %2 | Имя: %3 | Удален: %4", УИД, Код, Наименование, ПометкаУдаления));
    
КонецЦикла;

Чтение.Закрыть();

Преимущества: Строгая типизация 1С, отсутствие необходимости вручную парсить строки и теги.

Недостатки: Код жестко привязан к структуре XML. Если в правилах обмена поле Код вдруг перенесут из полей поиска в обычные свойства, алгоритм перестанет его считывать.

---

Способ 2. Использование модели DOM и XPath-запросов

Для поиска элементов в сложных XML-документах отлично подходит связка ПостроительDOM и языка запросов XPath. Этот способ позволяет абстрагироваться от точной структуры вложенности и выбирать нужные значения по их атрибутам.

Проанализируем пример кода, с помощью которого можно адресовать конкретные узлы:


ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);

ПостроительDOM = Новый ПостроительDOM;
ДанныеDOM = ПостроительDOM.Прочитать(ЧтениеXML);
ЧтениеXML.Закрыть();

УзелКонтекста = ДанныеDOM.ПервыйДочерний;
Разыменователь = Новый РазыменовательПространствИменDOM(УзелКонтекста);

// Находим все узлы "Объект" нужного типа
ЗапросXPath = "Объект[@Тип=""СправочникСсылка.ЯзыкиНародовМира""]";
РезультатXPath = ДанныеDOM.ВычислитьВыражениеXPath(ЗапросXPath, УзелКонтекста, Разыменователь);

ОбъектDOM = РезультатXPath.ПолучитьСледующий();

Пока НЕ ОбъектDOM = Неопределено Цикл
    
    // Прямой поиск значений по путям и атрибутам XML
    УИД = ДанныеDOM.ВычислитьВыражениеXPath("Ссылка/Свойство[@Имя = ""{УникальныйИдентификатор}""]/Значение", ОбъектDOM, Разыменователь).ПолучитьСледующий().ТекстовоеСодержимое;
    Код = ДанныеDOM.ВычислитьВыражениеXPath("Ссылка/Свойство[@Имя = ""Код""]/Значение", ОбъектDOM, Разыменователь).ПолучитьСледующий().ТекстовоеСодержимое;
    Наименование = ДанныеDOM.ВычислитьВыражениеXPath("Свойство[@Имя = ""Наименование""]/Значение", ОбъектDOM, Разыменователь).ПолучитьСледующий().ТекстовоеСодержимое;
    ПометкаУдаления = ДанныеDOM.ВычислитьВыражениеXPath("Свойство[@Имя = ""ПометкаУдаления""]/Значение", ОбъектDOM, Разыменователь).ПолучитьСледующий().ТекстовоеСодержимое;
    
    Сообщить(СтрШаблон("%1 - %2 - %3 - %4", УИД, Код, Наименование, ПометкаУдаления));
    
    ОбъектDOM = РезультатXPath.ПолучитьСледующий();
КонецЦикла;

Преимущества: Потрясающая гибкость. С помощью XPath можно легко вытаскивать данные любой степени вложенности без написания каскадов циклов Для каждого.

Недостатки: Низкая производительность на больших объемах данных. Механизм DOM полностью считывает весь XML-файл в оперативную память сервера или клиента. Если размер файла превышает 50-100 МБ, система может аварийно завершить работу по ошибке «Нехватка памяти» (Out of memory).

---

Способ 3. Самый надежный: Последовательное потоковое чтение (ЧтениеXML)

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

Этот метод считывает файл «потоком», элемент за элементом, расходуя минимальный объем оперативной памяти. Кроме того, мы можем написать алгоритм так, чтобы он полностью игнорировал узел Ссылка и обрабатывал все свойства линейно.

Посмотрим на пример такой реализации:


ТаблицаОбъектов = Новый ТаблицаЗначений;
ТаблицаОбъектов.Колонки.Добавить("УИД", Новый ОписаниеТипов("Строка"));
ТаблицаОбъектов.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка"));
ТаблицаОбъектов.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ТаблицаОбъектов.Колонки.Добавить("ПометкаУдаления", Новый ОписаниеТипов("Булево"));

ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);

ТекущийОбъект = Неопределено;
ИмяСвойства    = "";

Пока ЧтениеXML.Прочитать() Цикл
    
    ТипУзла = ЧтениеXML.ТипУзла;
    
    Если ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        
        ИмяЭлемента = ЧтениеXML.Имя;
        
        Если ИмяЭлемента = "Объект" Тогда
            // Создаем новую строку в таблице при начале каждого объекта
            ТекущийОбъект = ТаблицаОбъектов.Добавить();
            
        ИначеЕсли ИмяЭлемента = "Свойство" Тогда
            // Запоминаем имя текущего свойства (из атрибута "Имя")
            ИмяСвойства = ЧтениеXML.ПолучитьАтрибут("Имя");
            
        ИначеЕсли ИмяЭлемента = "Значение" И ТекущийОбъект <> Неопределено Тогда
            // Читаем значение свойства внутри тега <Значение>
            ЧтениеXML.Прочитать(); 
            Если ЧтениеXML.ТипУзла = ТипУзлаXML.Текст Тогда
                ТекстовоеЗначение = ЧтениеXML.Значение;
                
                // Заполняем соответствующую колонку таблицы
                Если ИмяСвойства = "{УникальныйИдентификатор}" Тогда
                    ТекущийОбъект.УИД = ТекстовоеЗначение;
                ИначеЕсли ИмяСвойства = "Код" Тогда
                    ТекущийОбъект.Код = ТекстовоеЗначение;
                ИначеЕсли ИмяСвойства = "Наименование" Тогда
                    ТекущийОбъект.Наименование = ТекстовоеЗначение;
                ИначеЕсли ИмяСвойства = "ПометкаУдаления" Тогда
                    ТекущийОбъект.ПометкаУдаления = (ВРег(ТекстовоеЗначение) = "TRUE");
                КонецЕсли;
            КонецЕсли;
        КонецЕсли;
        
    ИначеЕсли ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
        Если ЧтениеXML.Имя = "Объект" Тогда
            ТекущийОбъект = Неопределено; // Сбрасываем ссылку на объект по окончании тега </Объект>
        КонецЕсли;
    КонецЕсли;
    
КонецЦикла;

ЧтениеXML.Закрыть();

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

---

Резюме и рекомендации

Выбор оптимального способа зависит от объема обрабатываемых файлов и требований к коду:

← На главную