Как правильно прочитать XML-файл с вложенными узлами в 1С?

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

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

Анализ исходных данных и синтаксическая ошибка в XML

Для начала проанализируем структуру XML-файла, которую пытается прочитать автор темы:


<doc Контрагент="Вася Пупкин" Водитель="Иванов И.В." АдресДоставки="623780, Свердловская обл, ">
    <tovar Номенклатура="Гвозди "Количество="42"/>
    <tovar Номенклатура="Гвозди" Количество="20"/>
    <tovar Номенклатура="Шурупы" Количество="6"/>
    <tovar Номенклатура="Болты" Количество="6"/>
</doc>

При детальном рассмотрении первой строки с товаром мы сразу обнаруживаем критическую синтаксическую ошибку (так называемый «битый XML»):

<tovar Номенклатура="Гвозди "Количество="42"/>

Обратите внимание на кавычку после слова «Гвозди». Она закрывает значение атрибута Номенклатура, после чего сразу идет слово Количество без пробела-разделителя, либо парсер воспримет это как некорректное объявление нового атрибута. При попытке прочитать такой файл стандартный механизм 1С ЧтениеXML немедленно вызовет исключение «Ошибка разбора XML» (XML parsing error) и аварийно прекратит работу.

Как это исправить: Данная проблема должна решаться на стороне системы-отправителя, которая формирует файл. Символы кавычек внутри значений атрибутов должны быть экранированы (например, заменены на специальную сущность &quot;) или просто удалены:

<tovar Номенклатура="Гвозди &quot;" Количество="42"/> или <tovar Номенклатура="Гвозди" Количество="42"/>.

---

Разбор логической ошибки с вложенным циклом

В первом сообщении автор пытается читать файл последовательно, используя объект ЧтениеXML, и совершает ошибку, создавая внутренний цикл для чтения товаров:


Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "doc" Тогда
    // Чтение атрибутов документа...
    Пока ЧтениеXML.Имя = "tovar" Цикл
        // Чтение товаров...
    КонецЦикла;
КонецЕсли;

Проанализируем, почему этот код не работает. Объект ЧтениеXML работает по принципу последовательного однопроходного сканера (StAX-парсер). Метод Прочитать() сдвигает указатель на следующий узел XML-документа.

Когда парсер заходит в условие ЧтениеXML.Имя = "doc", текущим узлом является сам элемент <doc>. Сразу после этого программа заходит во внутренний цикл Пока ЧтениеXML.Имя = "tovar". Но в этот момент имя текущего узла все еще "doc"! Условие цикла сразу же возвращает значение Ложь, и этот цикл завершается, даже не успев начаться. Если же принудительно вызвать ЧтениеXML.Прочитать() внутри этого цикла, мы собьем шаг основного цикла обхода файла.

Правило работы с ЧтениеXML: Обход документа всегда должен выполняться в одном-единственном цикле Пока ЧтениеXML.Прочитать() Цикл. Внутри этого цикла мы просто проверяем тип узла и его имя с помощью условных операторов Если ... ИначеЕсли, как справедливо заметили участники форума в сообщениях 3 и 4.

---

Критическая проблема финального кода автора (Потеря связи «Шапка — Табличная часть»)

В сообщении 5 автор радуется, что избавился от внутреннего цикла и код начал «читать» все узлы. Однако его финальный алгоритм содержит скрытую архитектурную ошибку:


Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "doc" Тогда
    ст = Результат.Добавить();
    ст.Контрагент = ЧтениеXML.ЗначениеАтрибута("Контрагент");
    // ...
ИначеЕсли ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "tovar" Тогда
    строк = Результат.Добавить();
    строк.Номенклатура = ЧтениеXML.ЗначениеАтрибута("Номенклатура");
    // ...
КонецЕсли;

Если Результат — это плоская таблица значений, то этот код запишет данные следующим образом:

  1. При чтении узла doc создается первая строка таблицы. В ней заполняются поля Контрагент, Водитель и Адрес. Поля Номенклатура и Количество остаются пустыми.
  2. При переходе к узлам tovar для каждого товара создается новая отдельная строка таблицы. В ней заполняются поля товара, но поля контрагента и водителя остаются пустыми!

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

---

Вариант 1. Исправление последовательного чтения (Сохранение контекста)

Чтобы решить проблему потери связи при последовательном чтении, мы должны сохранять контекст «шапки» документа во временных переменных. При нахождении каждого нового элемента tovar мы создаем строку таблицы и записываем в нее как данные самого товара, так и сохраненные данные «шапки».

Рассмотрим по шагам правильную реализацию этого подхода:


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

ПутьКФайлу = "C:\temp\data.xml";
ЧтениеXML = Новый ЧтениеXML;
Попытка
    ЧтениеXML.ОткрытьФайл(ПутьКФайлу);
Исключение
    Сообщить("Не удалось открыть файл: " + ОписаниеОшибки());
    Возврат;
КонецПопытки;

// Инициализируем переменные для хранения контекста текущего документа
ТекущийКонтрагент = "";
ТекущийВодитель   = "";
ТекущийАдрес      = "";

// Основной цикл последовательного чтения файла
Пока ЧтениеXML.Прочитать() Цикл
    
    // Проверяем, что парсер находится на начале элемента
    Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        
        Если ЧтениеXML.Имя = "doc" Тогда
            // Запоминаем контекст шапки документа во временные переменные
            ТекущийКонтрагент = ЧтениеXML.ЗначениеАтрибута("Контрагент");
            ТекущийВодитель   = ЧтениеXML.ЗначениеАтрибута("Водитель");
            ТекущийАдрес      = ЧтениеXML.ЗначениеАтрибута("АдресДоставки");
            
        ИначеЕсли ЧтениеXML.Имя = "tovar" Тогда
            // Создаем строку таблицы и заполняем ее данными товара и контекстом документа
            НоваяСтрока = Результат.Добавить();
            НоваяСтрока.Контрагент   = ТекущийКонтрагент;
            НоваяСтрока.Водитель     = ТекущийВодитель;
            НоваяСтрока.Адрес        = ТекущийАдрес;
            НоваяСтрока.Номенклатура = ЧтениеXML.ЗначениеАтрибута("Номенклатура");
            
            // Важно преобразовывать строковые значения атрибутов к нужным типам данных
            ЗначениеКоличества = ЧтениеXML.ЗначениеАтрибута("Количество");
            НоваяСтрока.Количество   = ?(ЗначениеЗаполнено(ЗначениеКоличества), Число(ЗначениеКоличества), 0);
        КонецЕсли;
        
    КонецЕсли;
    
КонецЦикла;

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

Этот метод работает максимально быстро и потребляет минимальное количество оперативной памяти. Мы рекомендуем использовать его для разбора огромных файлов (размером в десятки и сотни мегабайт).

---

Вариант 2. Использование объектной модели DOM (ПостроительDOM)

Для файлов небольшого и среднего размера (до 10–20 мегабайт) гораздо удобнее использовать объектную модель документов — DOM (Document Object Model). Этот подход считывает весь файл в оперативную память и представляет его в виде иерархического дерева объектов 1С. Нам больше не нужно вручную контролировать указатель чтения и сохранять контекст в переменных.

Проанализируем, как изящно решается наша задача с помощью механизма ПостроительDOM:


ПутьКФайлу = "C:\temp\data.xml";
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);

// Загружаем XML-документ в дерево DOM
ПостроительDOM = Новый ПостроительDOM;
ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);
ЧтениеXML.Закрыть();

// Получаем список всех узлов "doc"
ВсеДокументы = ДокументDOM.ПолучитьЭлементыПоИмени("doc");

Для Каждого УзелDoc Из ВсеДокументы Цикл
    
    // Извлекаем атрибуты шапки документа напрямую из узла
    Контрагент = УзелDoc.Атрибуты.ПолучитьИменованныйЭлемент("Контрагент").Значение;
    Водитель   = УзелDoc.Атрибуты.ПолучитьИменованныйЭлемент("Водитель").Значение;
    Адрес      = УзелDoc.Атрибуты.ПолучитьИменованныйЭлемент("АдресДоставки").Значение;
    
    // Получаем вложенные элементы "tovar", принадлежащие ТОЛЬКО текущему узлу "doc"
    ВсеТоварыДокумента = УзелDoc.ПолучитьЭлементыПоИмени("tovar");
    
    Для Каждого УзелTovar Из ВсеТоварыДокумента Цикл
        
        Номенклатура = УзелTovar.Атрибуты.ПолучитьИменованныйЭлемент("Номенклатура").Значение;
        Количество   = Число(УзелTovar.Атрибуты.ПолучитьИменованныйЭлемент("Количество").Значение);
        
        // Здесь мы можем сразу создавать документы в базе данных или заполнять ТаблицуЗначений
        // Данные гарантированно связаны между собой!
        Сообщить(СтрШаблон("Контрагент: %1, Товар: %2, Кол-во: %3", Контрагент, Номенклатура, Количество));
        
    КонецЦикла;
    
КонецЦикла;

Использование DOM избавляет разработчика от необходимости писать сложные условия и следить за открытием и закрытием тегов. Код получается чистым, понятным и легко сопровождаемым.

---

Вариант 3. Использование фабрики XDTO

Наиболее современным, технологичным и правильным с точки зрения стандартов 1С является чтение XML через XDTO (XML Data Transfer Objects). Если для вашего XML-файла есть схема (XSD) или структура файла простая и стабильная, вы можете прочитать файл буквально одной строкой кода, преобразовав его в полноценный объект 1С с типизированными свойствами.

Рассмотрим пример чтения, если в нашей конфигурации создана схема пакета XDTO, описывающая структуру файла:


ПутьКФайлу = "C:\temp\data.xml";
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);

// Читаем XML напрямую в объект XDTO
Попытка
    // ФабрикаXDTO автоматически преобразует типы данных (строки в строки, числа в числа)
    ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
Исключение
    Сообщить("Ошибка чтения XDTO: " + ОписаниеОшибки());
    ЧтениеXML.Закрыть();
    Возврат;
КонецПопытки;

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

// Теперь мы работаем с данными как с обычным объектом 1С
// Свойства "Контрагент", "Водитель" и список "tovar" доступны через точку
Сообщить("Контрагент: " + ОбъектXDTO.Контрагент);
Сообщить("Водитель: " + ОбъектXDTO.Водитель);

Для Каждого СтрокаТовара Из ОбъектXDTO.tovar Цикл
    // Количество автоматически имеет тип Число, если это было указано в схеме XSD
    Сообщить(СтрШаблон("Товар: %1, Количество: %2", СтрокаТовара.Номенклатура, СтрокаТовара.Количество));
КонецЦикла;

Преимущества XDTO:

Резюме

При работе с вложенными структурами XML всегда сопоставляйте объем файлов и требования к производительности. Для больших объемов используйте последовательное ЧтениеXML, не забывая сохранять переменные контекста родительских узлов. Для средних и небольших файлов отдавайте предпочтение ПостроителюDOM или технологии XDTO, чтобы писать лаконичный, надежный и современный код.

← На главную