Каждый разработчик 1С, занимающийся интеграцией и обменом данными, рано или поздно сталкивается с чтением XML-файлов. Использование встроенной фабрики XDTO — один из самых быстрых и удобных способов десериализации данных. Но здесь нас подстерегает классическая архитектурная ловушка платформы 1С. Когда мы тестируем выгрузку на тестовых файлах с несколькими строками в табличной части, код работает идеально. Однако стоит реальному контрагенту прислать файл, где в табличной части содержится всего одна строка, как наша программа падает с ошибкой: «Итератор для значения не определен».
В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем внутреннее поведение платформы 1С при работе с XDTO без схемы и рассмотрим несколько надежных способов обхода этой проблемы.
Когда мы читаем XML-документ "на лету" без предварительно загруженной XSD-схемы (то есть динамически, используя тип anyType), фабрика XDTO не знает правил структуры документа. Она не догадывается, должен ли конкретный тег быть списком, или это одиночное свойство. Платформа принимает решение о типе данных динамически, основываясь исключительно на содержимом текущего файла:
СписокXDTO. Этот тип поддерживает обход в цикле Для каждого ... Из ... Цикл.ОбъектXDTO (или даже простое значение, например, строку или число, если у тега нет атрибутов). Попытка перебрать такой одиночный объект в цикле и приводит к системной ошибке итератора.Неопределено, что также делает цикл неработоспособным.Давайте рассмотрим, как исправить эту ситуацию несколькими способами — от простых оберток до изменения архитектуры чтения данных.
Самый быстрый и частый способ решения проблемы в коде — это создание специальной функции-конвертера, которая будет принудительно приводить одиночные элементы к массиву. На форуме в сообщении №6 был предложен рабочий вариант, однако мы его доработаем и сделаем максимально безопасным.
Дело в том, что одиночный тег может прочитаться не только как ОбъектXDTO. Если у тега нет атрибутов, а есть только текстовое содержимое, 1С вернет обычную строку или число. Также элемент может отсутствовать, вернув Неопределено. Наша функция должна обрабатывать все эти случаи.
Создадим и рассмотрим подробнее следующую функцию-обертку:
Функция ПолучитьМассивИзXDTO(ЗначениеXDTO) Экспорт
Если ЗначениеXDTO = Неопределено Тогда
// Если тега не было в XML, возвращаем пустой массив
Возврат Новый Массив;
ИначеЕсли ТипЗнч(ЗначениеXDTO) = Тип("СписокXDTO") Тогда
// Если элементов несколько, платформа уже создала список.
// Возвращаем его напрямую, так как СписокXDTO можно обходить в цикле.
Возврат ЗначениеXDTO;
Иначе
// Сюда попадают одиночные ОбъектXDTO, а также примитивные типы (Строка, Число и др.)
Результат = Новый Массив;
Результат.Добавить(ЗначениеXDTO);
Возврат Результат;
КонецЕсли;
КонецФункции
Теперь применим эту функцию в нашем коде разбора XML. Обратите внимание, насколько лаконичным и безопасным становится обход элементов:
ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение);
Contr = ОбъектXDTO.усПриемкаФакт;
// Оборачиваем обращение к свойству в нашу функцию
Для каждого СтрокаДанных Из ПолучитьМассивИзXDTO(Contr.Строка) Цикл
НоменклатураКод = СтрокаДанных.Номенклатура;
// Здесь выполняем дальнейшую обработку данных
КонецЦикла;
Этот метод хорош тем, что не требует изменения структуры конфигурации или создания дополнительных метаданных. Он работает "здесь и сейчас".
Если мы хотим решить проблему раз и навсегда на системном уровне, нам следует использовать XSD-схему документа. В схеме для каждого элемента явно указываются правила его появления. Например, свойство maxOccurs="unbounded" сообщает платформе, что данный элемент является списком, даже если в конкретном файле он присутствует в единственном экземпляре (или отсутствует вовсе).
Разберем по шагам, как настроить чтение со схемой:
.xsd для вашего XML-документа.Посмотрим на пример кода при использовании схемы:
// Предположим, у нас в конфигурации создан пакет XDTO с пространством имен "http://www.example.org/priemka"
ТипОбъекта = ФабрикаXDTO.Тип("http://www.example.org/priemka", "усПриемкаФакт");
// Читаем XML с явным указанием типа
ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение, ТипОбъекта);
// Теперь свойство "Строка" гарантированно вернет СписокXDTO, даже если в файле одна строка!
Для каждого СтрокаДанных Из ОбъектXDTO.Строка Цикл
НоменклатураКод = СтрокаДанных.Номенклатура;
КонецЦикла;
Преимущества этого способа: автоматическая типизация данных (числа станут числами, даты — датами), строгая валидация файлов при загрузке, отсутствие необходимости писать "костыли" в коде 1С.
Если создавать схемы XDTO некогда или сложно, но хочется получить абсолютно надежное решение без проверок типов, мы можем отказаться от XDTO в пользу объектной модели документа (DOM).
Главный плюс DOM-модели в том, что метод ПолучитьЭлементыПоИмени() всегда возвращает коллекцию (объект СписокУзловDOM). Неважно, сколько элементов найдено в файле — ноль, один или тысяча — мы никогда не получим ошибку итератора.
Давайте проанализируем пример кода с использованием DOM:
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);
ПостроительDOM = Новый ПостроительDOM;
ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);
// Метод гарантированно возвращает коллекцию узлов
СтрокиXML = ДокументDOM.ПолучитьЭлементыПоИмени("Строка");
Для Каждого СтрокаDOM Из СтрокиXML Цикл
// Читаем атрибут "Номенклатура" у каждого узла
АтрибутНоменклатура = СтрокаDOM.Атрибуты.ПолучитьИменованныйЭлемент("Номенклатура");
Если АтрибутНоменклатура <> Неопределено Тогда
НоменклатураКод = АтрибутНоменклатура.Значение;
// Обработка полученного кода номенклатуры
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
Важное примечание: DOM-модель полностью загружает XML-файл в оперативную память. Этот способ отлично подходит для файлов небольшого и среднего размера (примерно до 50–100 МБ). Для гигабайтных файлов лучше использовать последовательное чтение.
Если ваши XML-файлы содержат сотни тысяч строк и весят сотни мегабайт, загрузка их через XDTO или DOM может привести к перегрузке оперативной памяти и падению рабочего процесса сервера 1С. В этом случае мы рекомендуем использовать потоковое последовательное чтение с помощью объекта ЧтениеXML.
Этот способ также полностью застрахован от проблемы "одна или много строк", поскольку мы обходим файл шаг за шагом, событие за событием:
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);
Пока ЧтениеXML.Прочитать() Цикл
// Проверяем, что парсер зашел в начало тега "Строка"
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "Строка" Тогда
// Читаем атрибут напрямую из текущего позиционирования парсера
НоменклатураКод = ЧтениеXML.ПолучитьАтрибут("Номенклатура");
Если ЗначениеЗаполнено(НоменклатураКод) Тогда
// Обрабатываем данные
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
Данный подход является самым производительным и потребляет фиксированное, минимальное количество оперативной памяти независимо от объема входящего файла.
В завершение разберем еще одну неочевидную особенность фабрики XDTO при работе без схемы. Если в исходном XML-файле названия тегов или атрибутов содержат дефисы (например, <ус-Приемка-Факт>), то при чтении платформа 1С автоматически заменит все дефисы на символы подчеркивания (ус_Приемка_Факт).
Если вы попытаетесь обратиться к свойству по его оригинальному имени из XML-файла через точку, система выдаст ошибку отсутствия свойства. Всегда помните об этом правиле автозамены символов при написании кода динамического обхода XDTO.
Мы рассмотрели четыре отличных способа решения проблемы с чтением табличных частей XML в 1С. Выбор конкретного метода зависит от архитектуры вашего решения: