При интеграции 1С с внешними системами разработчики часто сталкиваются с необходимостью разбора XML-файлов сторонних поставщиков. Одним из наиболее удобных и современных инструментов для этого является механизм XDTO (XML Data Transfer Objects). Однако при работе без жестко заданной схемы (XSD) стандартный импорт может преподнести сюрпризы.
Типичная проблема возникает при попытке прочитать элементы со смешанным содержимым (Mixed Content). Например, когда тег содержит одновременно и атрибуты, и текстовое значение внутри:
<param name="Ширина">235</param>
В этой статье мы подробно разберем, как прочитать такие данные с помощью XDTO, как решить проблему неопределенности типов (когда элемент может быть как одиночным объектом, так и списком), и как оптимизировать чтение больших файлов, чтобы избежать перегрузки оперативной памяти.
Выясним причину, почему при чтении XML без схемы XSD напрямую через фабрику XDTO текстовое содержимое тега <param> не попадает в явные свойства полученного объекта. Когда 1С анализирует узел XML, не имея описания типов, она считывает атрибуты (в нашем примере это name) и создает под них соответствующие свойства в ОбъектXDTO. Но текстовое наполнение самого тега (значение "235") не может быть сопоставлено с конкретным свойством, так как у него нет имени.
В терминологии XML и XDTO такой узел считается открытым типом со смешанным содержимым. Платформа 1С помещает текстовые фрагменты такого элемента в специальную коллекцию — Последовательность XDTO (XDTOSequence).
Рассмотрим, как извлечь скрытый текст из объекта XDTO. Для этого используется метод Последовательность(), который возвращает итерируемый объект последовательности. Мы можем получить текст по индексу. Если внутри тега нет других вложенных элементов, текст всегда будет находиться под индексом 0.
Посмотрим на пример кода, который решает эту задачу:
// Предположим, ТекущийПараметр — это ОбъектXDTO, полученный при чтении узла <param>
ИмяПараметра = ТекущийПараметр.name; // Получаем значение атрибута "name"
// Извлекаем текстовое значение тега через последовательность
Последовательность = ТекущийПараметр.Последовательность();
Если Последовательность.Количество() > 0 Тогда
ЗначениеПараметра = Последовательность.ПолучитьТекст(0);
Иначе
ЗначениеПараметра = "";
КонецЕсли;
Сообщить("Параметр: " + ИмяПараметра + ", Значение: " + ЗначениеПараметра);
Если структура XML более сложная и внутри тега могут чередоваться текст и другие элементы, последовательность необходимо обходить в цикле, проверяя тип каждого элемента последовательности:
Последовательность = ТекущийПараметр.Последовательность();
Для Индекс = 0 По Последовательность.Количество() - 1 Цикл
Свойство = Последовательность.ПолучитьСвойство(Индекс);
Если Свойство = Неопределено Тогда
// Свойство Неопределено означает, что в данной позиции находится чистый текст
ТекстУзла = Последовательность.ПолучитьТекст(Индекс);
// Обрабатываем текст...
Иначе
// Здесь находится вложенный элемент (объект XDTO)
ВложенныйОбъект = Последовательность.ПолучитьЗначение(Индекс);
КонецЕсли;
КонецЦикла;
Проанализируем еще одну классическую проблему динамического чтения XML через XDTO. Если во входящем файле узел <param> встречается несколько раз подряд, фабрика XDTO автоматически преобразует его в коллекцию — СписокXDTO. Мы можем легко обойти его циклом Для Каждого.
Однако, если у товара будет заполнен всего один параметр, 1С прочитает его не как список из одного элемента, а как одиночный ОбъектXDTO. Попытка применить к нему цикл вызовет критическую ошибку выполнения программы.
Чтобы избежать написания громоздких проверок типов в основном коде, разработаем универсальную функцию-помощник, которая всегда возвращает коллекцию (массив или исходный список), готовую к безопасному обходу в цикле:
// Функция приводит переданное свойство XDTO к итерируемой коллекции
Функция ПолучитьКакКоллекцию(ЗначениеXDTO) Экспорт
Результат = Новый Массив;
Если ЗначениеXDTO = Неопределено Тогда
Возврат Результат;
ИначеЕсли ТипЗнч(ЗначениеXDTO) = Тип("СписокXDTO") Тогда
// Если это уже список, возвращаем его как есть
Возврат ЗначениеXDTO;
ИначеЕсли ТипЗнч(ЗначениеXDTO) = Тип("ОбъектXDTO") Тогда
// Одиночный объект оборачиваем в массив
Результат.Добавить(ЗначениеXDTO);
Возврат Результат;
Иначе
// Простые типы (строка, число и т.д.) тоже оборачиваем
Результат.Добавить(ЗначениеXDTO);
Возврат Результат;
КонецЕсли;
КонецФункции
Теперь мы можем безопасно написать обход любых параметров, совершенно не беспокоясь о том, сколько их содержится в XML-файле — один, несколько или нет вообще:
// Безопасный обход коллекции параметров
ПараметрыОффера = ПолучитьКакКоллекцию(ТекущийОффер.param);
Для Каждого ТекущийПараметр Из ПараметрыОффера Цикл
ИмяПараметра = ТекущийПараметр.name;
ЗначениеПараметра = ТекущийПараметр.Последовательность().ПолучитьТекст(0);
// Выполняем запись в базу данных или обработку
КонецЦикла;
Обратим внимание на то, как именно автор темы считывает файл в первом сообщении. Он загружает весь XML-файл в память в виде гигантской строки с помощью ЧтениеТекста, а затем передает эту строку в ЧтениеXML. Это неоптимальный подход, особенно для больших XML-каталогов (например, YML-файлов Яндекс.Маркета), размер которых может превышать сотни мегабайт. При таком подходе велика вероятность получить ошибку нехватки памяти (Out of Memory).
Правильный способ — использовать потоковое чтение файла с диска с помощью метода ОткрытьФайл() объекта ЧтениеXML. Рассмотрим, как переписать этот участок кода:
ЧтениеXML = Новый ЧтениеXML;
// Открываем файл напрямую в потоке чтения, минуя загрузку в строку
ЧтениеXML.ОткрытьФайл(ПолноеИмяФайла);
// Выполняем чтение структуры через Фабрику XDTO
ДанныеФайла = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
ЧтениеXML.Закрыть();
Если ваш XML-файл действительно огромный, то даже метод ФабрикаXDTO.ПрочитатьXML(ЧтениеXML) может вызвать перегрузку памяти, так как он попытается построить в оперативной памяти полное дерево объектов всего файла.
Профессиональные разработчики 1С используют комбинированный подход. Мы последовательно перебираем элементы файла с помощью быстрого потокового объекта ЧтениеXML, а как только доходим до нужного нам узла (например, товара <offer>), мы позиционируем на нем чтение и десериализуем в объект XDTO только этот конкретный небольшой узел. Это позволяет сочетать высокую скорость разработки через XDTO с минимальным потреблением ресурсов сервера.
Разберем по шагам реализацию этого механизма:
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПолноеИмяФайла);
// Читаем файл последовательно в цикле
Пока ЧтениеXML.Прочитать() Цикл
// Находим начало элемента "offer"
Если ЧтениеXML.ЛокальноеИмя = "offer"
И ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
// Читаем в XDTO только один узел товара
ТекущийОффер = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
// Обрабатываем прочитанные данные
ИмяТовара = ТекущийОффер.name;
Сообщить("Обработка товара: " + ИмяТовара);
// Разбираем смешанные параметры товара
Для Каждого ТекущийПараметр Из ПолучитьКакКоллекцию(ТекущийОффер.param) Цикл
ИмяПараметра = ТекущийПараметр.name;
ЗначениеПараметра = "";
Если ТекущийПараметр.Последовательность().Количество() > 0 Тогда
ЗначениеПараметра = ТекущийПараметр.Последовательность().ПолучитьТекст(0);
КонецЕсли;
Сообщить(" - " + ИмяПараметра + ": " + ЗначениеПараметра);
КонецЦикла;
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
Этот подход гарантирует стабильную работу вашей обработки импорта на файлах абсолютно любого размера — от нескольких килобайт до гигабайтных выгрузок.