Как прочитать XML с одной строкой в табличной части через XDTO в 1С без ошибок итератора?

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

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

В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем внутреннее поведение платформы 1С при работе с XDTO без схемы и рассмотрим несколько надежных способов обхода этой проблемы.

Выясняем причину: почему фабрика XDTO спотыкается на одной строке?

Когда мы читаем XML-документ "на лету" без предварительно загруженной XSD-схемы (то есть динамически, используя тип anyType), фабрика XDTO не знает правил структуры документа. Она не догадывается, должен ли конкретный тег быть списком, или это одиночное свойство. Платформа принимает решение о типе данных динамически, основываясь исключительно на содержимом текущего файла:

  1. Если одинаковых вложенных тегов два или более: платформа понимает, что перед ней коллекция, и автоматически создает объект типа СписокXDTO. Этот тип поддерживает обход в цикле Для каждого ... Из ... Цикл.
  2. Если вложенный тег всего один: платформа считает его одиночным уникальным свойством и создает ОбъектXDTO (или даже простое значение, например, строку или число, если у тега нет атрибутов). Попытка перебрать такой одиночный объект в цикле и приводит к системной ошибке итератора.
  3. Если тега в файле нет вообще: свойство принимает значение Неопределено, что также делает цикл неработоспособным.

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

Способ 1. Универсальная функция-обертка для XDTO (Решение на лету)

Самый быстрый и частый способ решения проблемы в коде — это создание специальной функции-конвертера, которая будет принудительно приводить одиночные элементы к массиву. На форуме в сообщении №6 был предложен рабочий вариант, однако мы его доработаем и сделаем максимально безопасным.

Дело в том, что одиночный тег может прочитаться не только как ОбъектXDTO. Если у тега нет атрибутов, а есть только текстовое содержимое, 1С вернет обычную строку или число. Также элемент может отсутствовать, вернув Неопределено. Наша функция должна обрабатывать все эти случаи.

Создадим и рассмотрим подробнее следующую функцию-обертку:


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

Теперь применим эту функцию в нашем коде разбора XML. Обратите внимание, насколько лаконичным и безопасным становится обход элементов:


ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение);
Contr = ОбъектXDTO.усПриемкаФакт;

// Оборачиваем обращение к свойству в нашу функцию
Для каждого СтрокаДанных Из ПолучитьМассивИзXDTO(Contr.Строка) Цикл  
    НоменклатураКод = СтрокаДанных.Номенклатура;
    // Здесь выполняем дальнейшую обработку данных
КонецЦикла;

Этот метод хорош тем, что не требует изменения структуры конфигурации или создания дополнительных метаданных. Он работает "здесь и сейчас".

Способ 2. Использование пакетов XDTO и XSD-схем (Правильный архитектурный подход)

Если мы хотим решить проблему раз и навсегда на системном уровне, нам следует использовать XSD-схему документа. В схеме для каждого элемента явно указываются правила его появления. Например, свойство maxOccurs="unbounded" сообщает платформе, что данный элемент является списком, даже если в конкретном файле он присутствует в единственном экземпляре (или отсутствует вовсе).

Разберем по шагам, как настроить чтение со схемой:

  1. Получите или создайте файл схемы .xsd для вашего XML-документа.
  2. В конфигураторе 1С перейдите в ветку Общие -> Пакеты XDTO и добавьте новый пакет, импортировав в него вашу схему.
  3. При чтении файла фабрикой XDTO укажите целевой тип из импортированного пакета.

Посмотрим на пример кода при использовании схемы:


// Предположим, у нас в конфигурации создан пакет XDTO с пространством имен "http://www.example.org/priemka"
ТипОбъекта = ФабрикаXDTO.Тип("http://www.example.org/priemka", "усПриемкаФакт");

// Читаем XML с явным указанием типа
ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение, ТипОбъекта);

// Теперь свойство "Строка" гарантированно вернет СписокXDTO, даже если в файле одна строка!
Для каждого СтрокаДанных Из ОбъектXDTO.Строка Цикл  
    НоменклатураКод = СтрокаДанных.Номенклатура;
КонецЦикла;

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

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

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

Главный плюс DOM-модели в том, что метод ПолучитьЭлементыПоИмени() всегда возвращает коллекцию (объект СписокУзловDOM). Неважно, сколько элементов найдено в файле — ноль, один или тысяча — мы никогда не получим ошибку итератора.

Давайте проанализируем пример кода с использованием DOM:


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

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

// Метод гарантированно возвращает коллекцию узлов
СтрокиXML = ДокументDOM.ПолучитьЭлементыПоИмени("Строка"); 

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

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

Важное примечание: DOM-модель полностью загружает XML-файл в оперативную память. Этот способ отлично подходит для файлов небольшого и среднего размера (примерно до 50–100 МБ). Для гигабайтных файлов лучше использовать последовательное чтение.

Способ 4. Последовательное чтение через ЧтениеXML (Для больших файлов)

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

Этот способ также полностью застрахован от проблемы "одна или много строк", поскольку мы обходим файл шаг за шагом, событие за событием:


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

Пока ЧтениеXML.Прочитать() Цикл
    // Проверяем, что парсер зашел в начало тега "Строка"
    Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "Строка" Тогда
        
        // Читаем атрибут напрямую из текущего позиционирования парсера
        НоменклатураКод = ЧтениеXML.ПолучитьАтрибут("Номенклатура");
        
        Если ЗначениеЗаполнено(НоменклатураКод) Тогда
            // Обрабатываем данные
        КонецЕсли;
        
    КонецЕсли;
КонецЦикла;

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

Данный подход является самым производительным и потребляет фиксированное, минимальное количество оперативной памяти независимо от объема входящего файла.

Важный нюанс: Имена свойств с дефисами в XDTO

В завершение разберем еще одну неочевидную особенность фабрики XDTO при работе без схемы. Если в исходном XML-файле названия тегов или атрибутов содержат дефисы (например, <ус-Приемка-Факт>), то при чтении платформа 1С автоматически заменит все дефисы на символы подчеркивания (ус_Приемка_Факт).

Если вы попытаетесь обратиться к свойству по его оригинальному имени из XML-файла через точку, система выдаст ошибку отсутствия свойства. Всегда помните об этом правиле автозамены символов при написании кода динамического обхода XDTO.

Резюме

Мы рассмотрели четыре отличных способа решения проблемы с чтением табличных частей XML в 1С. Выбор конкретного метода зависит от архитектуры вашего решения:

← На главную