При интеграции систем или переносе данных программисты 1С часто сталкиваются с необходимостью разбора XML-файлов. Особое удивление и трудности вызывают файлы, выгруженные с помощью стандартных механизмов обмена (например, через конфигурацию «Конвертация данных 2.1» или обработку «Выгрузка и загрузка данных XML»). Пытаясь прочитать такой файл «в лоб» через XDTO, разработчики обнаруживают, что часть ключевых полей (таких как уникальный идентификатор или код) просто не считывается стандартным циклом обхода свойств.
В этой статье мы подробно проанализируем, почему возникает эта проблема, разберем устройство структуры файлов обмена 1С и рассмотрим три практических способа решения задачи: правильный обход через ФабрикаXDTO, гибкий поиск элементов с помощью DOM/XPath, а также наиболее производительный и безопасный по памяти способ потокового чтения.
---
Для начала проанализируем структуру типичного XML-файла, сформированного «Конвертацией данных». Если мы откроем его, то увидим примерно следующую картину:
<Объект Тип="СправочникСсылка.ЯзыкиНародовМира">
<Ссылка>
<Свойство Имя="{УникальныйИдентификатор}">
<Значение>31b9efb0-e910-11dc-b11e-0019d1e0b62b</Значение>
</Свойство>
<Свойство Имя="Код">
<Значение>000000001</Значение>
</Свойство>
</Ссылка>
<Свойство Имя="Наименование">
<Значение>Украинский</Значение>
</Свойство>
<Свойство Имя="ПометкаУдаления">
<Значение>false</Значение>
</Свойство>
</Объект>
Обратите внимание на важную деталь: свойства объекта разделены на две группы:
Ссылка: содержит так называемые «поля поиска» (идентификации), по которым база-приемник определяет, существует ли уже такой элемент. Сюда помещаются {УникальныйИдентификатор} (GUID) и ключевые поля (например, Код или Артикул).Объект: содержат обычные реквизиты, которые подлежат переносу и записи (например, Наименование, ПометкаУдаления).Именно поэтому простая попытка перебрать все свойства в цикле Для Каждого Свойство Из Объект.Свойство приводит к тому, что 1С видит только Наименование и ПометкаУдаления, полностью игнорируя Код и УИД, скрытые внутри контейнера Ссылка.
---
Если вы хотите продолжить использовать удобный механизм работы с XDTO-пакетами, вам необходимо явно учесть иерархию XML-документа. Нам потребуется сначала обойти свойства, находящиеся внутри объекта Ссылка, а затем перейти к свойствам самого объекта.
Рассмотрим по шагам рабочий код, который решает эту задачу:
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ПутьКФайлу);
// Читаем XML в объектную модель XDTO
ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение);
Для Каждого Объект Из ОбъектXDTO.Объект Цикл
УИД = "";
Код = "";
Наименование = "";
ПометкаУдаления = Ложь;
// Шаг 1: Обходим свойства идентификации (внутри узла Ссылка)
Если Объект.Свойство("Ссылка") Тогда
Для Каждого СвойствоСсылки Из Объект.Ссылка.Свойство Цикл
Если СвойствоСсылки.Имя = "{УникальныйИдентификатор}" Тогда
УИД = СвойствоСсылки.Значение;
ИначеЕсли СвойствоСсылки.Имя = "Код" Тогда
Код = СвойствоСсылки.Значение;
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Шаг 2: Обходим основные реквизиты объекта
Для Каждого СвойствоОбъекта Из Объект.Свойство Цикл
Если СвойствоОбъекта.Имя = "Наименование" Тогда
Наименование = СвойствоОбъекта.Значение;
ИначеЕсли СвойствоОбъекта.Имя = "ПометкаУдаления" Тогда
// Преобразуем текстовое представление булева значения в тип Булево
ПометкаУдаления = XMLЗначение(Тип("Булево"), СвойствоОбъекта.Значение);
КонецЕсли;
КонецЦикла;
// Здесь полученные данные можно записать в Табличную Часть или Таблицу Значений
Сообщить(СтрШаблон("УИД: %1 | Код: %2 | Имя: %3 | Удален: %4", УИД, Код, Наименование, ПометкаУдаления));
КонецЦикла;
Чтение.Закрыть();
Преимущества: Строгая типизация 1С, отсутствие необходимости вручную парсить строки и теги.
Недостатки: Код жестко привязан к структуре XML. Если в правилах обмена поле Код вдруг перенесут из полей поиска в обычные свойства, алгоритм перестанет его считывать.
---
Для поиска элементов в сложных 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).
---
Что делать, если файл обмена весит гигабайты, а данные нужно прочитать быстро и гарантированно не уронить рабочий процесс сервера 1С? В этом случае лучшим архитектурным решением будет использование последовательного чтения с помощью объекта ЧтениеXML.
Этот метод считывает файл «потоком», элемент за элементом, расходуя минимальный объем оперативной памяти. Кроме того, мы можем написать алгоритм так, чтобы он полностью игнорировал узел Ссылка и обрабатывал все свойства линейно.
Посмотрим на пример такой реализации:
ТаблицаОбъектов = Новый ТаблицаЗначений;
ТаблицаОбъектов.Колонки.Добавить("УИД", Новый ОписаниеТипов("Строка"));
ТаблицаОбъектов.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка"));
ТаблицаОбъектов.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ТаблицаОбъектов.Колонки.Добавить("ПометкаУдаления", Новый ОписаниеТипов("Булево"));
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);
ТекущийОбъект = Неопределено;
ИмяСвойства = "";
Пока ЧтениеXML.Прочитать() Цикл
ТипУзла = ЧтениеXML.ТипУзла;
Если ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
ИмяЭлемента = ЧтениеXML.Имя;
Если ИмяЭлемента = "Объект" Тогда
// Создаем новую строку в таблице при начале каждого объекта
ТекущийОбъект = ТаблицаОбъектов.Добавить();
ИначеЕсли ИмяЭлемента = "Свойство" Тогда
// Запоминаем имя текущего свойства (из атрибута "Имя")
ИмяСвойства = ЧтениеXML.ПолучитьАтрибут("Имя");
ИначеЕсли ИмяЭлемента = "Значение" И ТекущийОбъект <> Неопределено Тогда
// Читаем значение свойства внутри тега <Значение>
ЧтениеXML.Прочитать();
Если ЧтениеXML.ТипУзла = ТипУзлаXML.Текст Тогда
ТекстовоеЗначение = ЧтениеXML.Значение;
// Заполняем соответствующую колонку таблицы
Если ИмяСвойства = "{УникальныйИдентификатор}" Тогда
ТекущийОбъект.УИД = ТекстовоеЗначение;
ИначеЕсли ИмяСвойства = "Код" Тогда
ТекущийОбъект.Код = ТекстовоеЗначение;
ИначеЕсли ИмяСвойства = "Наименование" Тогда
ТекущийОбъект.Наименование = ТекстовоеЗначение;
ИначеЕсли ИмяСвойства = "ПометкаУдаления" Тогда
ТекущийОбъект.ПометкаУдаления = (ВРег(ТекстовоеЗначение) = "TRUE");
КонецЕсли;
КонецЕсли;
КонецЕсли;
ИначеЕсли ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
Если ЧтениеXML.Имя = "Объект" Тогда
ТекущийОбъект = Неопределено; // Сбрасываем ссылку на объект по окончании тега </Объект>
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
Преимущества: Максимально возможная скорость работы в 1С. Потребление памяти стремится к нулю, так как файл не загружается в память целиком. Полное безразличие к тому, находится ли свойство внутри тега Ссылка или лежит на верхнем уровне.
---
Выбор оптимального способа зависит от объема обрабатываемых файлов и требований к коду:
Ссылка.Свойство.