Почему ЧтениеXML и ПрочитатьXML пропускают узлы и как правильно разобрать XML в 1С

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

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

Анализ причин пропуска узлов при чтении

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


Если ЧтениеХМЛ.ТипУзла <> ТипУзлаXML.НачалоЭлемента Тогда
    ВызватьИсключение "Ошибка чтения ХМЛ";    
КонецЕсли;

ЧтениеХМЛ.Прочитать();

НоменклатураСсылка = ПрочитатьXML(ЧтениеХМЛ);
// Курсор неожиданно смещается дальше ожидаемой позиции

Выясним основные причины некорректного поведения:

  1. Сдвиг курсора потока функцией ПрочитатьXML(). Метод ПрочитатьXML() предназначен для десериализации типа данных XDTO или объекта целиком. При вызове этой функции платформа считывает текущий элемент вместе с его содержимым и закрывающим тегом, после чего автоматически переводит внутренний курсор ЧтениеXML на следующий узел. Если разработчик дополнительно вызывает ЧтениеХМЛ.Прочитать(), происходит двойное смещение указателя, и следующий тег пропускается.
  2. Сброс пространства имен атрибутом xmlns="". Если в XML узел содержит пустой атрибут пространства имен xmlns="", но при этом тип указан как xsi:type="CatalogRef.Номенклатура", платформенный сериализатор не может сопоставить тип со схемой конфигурации без явно заданного префикса пространства имен (например, http://v8.1c.ru/8.1/data/enterprise/current-config). В результате функция либо завершается ошибкой, либо считывает пустое значение.
  3. Смешивание потокового и объектного подходов. Попытка читать структуру построчно, вызывая на каждом шаге ПрочитатьXML(), требует строгого совпадения порядка тегов, типов схемы и позиции курсора. Малейшее отклонение в структуре XML нарушает весь алгоритм загрузки.

Способ 1. Использование объектной модели DOM

Рассмотрим наиболее удобный и устойчивый к порядку тегов способ — объектную модель ДокументDOM. Этот вариант идеально подходит для файлов небольшого и среднего объема, когда необходимо обращаться к произвольным элементам структуры по их именам.

Разберем пример чтения узлов номенклатуры с использованием DOM:


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

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

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

Для Каждого УзелОбъекта Из СписокЭлементов Цикл
    
    // Получаем значение ссылки (GUID)
    УзлыRef = УзелОбъекта.ПолучитьЭлементыПоИмени("Ref");
    Если УзлыRef.Количество() > 0 Тогда
        СтрокаGUID = УзлыRef[0].ТекстовоеСодержимое;
        СсылкаНоменклатуры = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(СтрокаGUID));
    КонецЕсли;
    
    // Получаем признак группы
    ЭтоГруппа = Ложь;
    УзлыIsFolder = УзелОбъекта.ПолучитьЭлементыПоИмени("IsFolder");
    Если УзлыIsFolder.Количество() > 0 Тогда
        ЭтоГруппа = (ВРег(СокрЛП(УзлыIsFolder[0].ТекстовоеСодержимое)) = "TRUE");
    КонецЕсли;
    
    // Создаем или получаем объект для записи
    ОбъектСправочника = СсылкаНоменклатуры.ПолучитьОбъект();
    Если ОбъектСправочника = Неопределено Тогда
        Если ЭтоГруппа Тогда
            ОбъектСправочника = Справочники.Номенклатура.СоздатьГруппу();
        Иначе
            ОбъектСправочника = Справочники.Номенклатура.СоздатьЭлемент();
        КонецЕсли;
        ОбъектСправочника.УстановитьСсылкуНового(СсылкаНоменклатуры);
    КонецЕсли;
    
    // Заполняем реквизиты
    УзлыКод = УзелОбъекта.ПолучитьЭлементыПоИмени("Code");
    Если УзлыКод.Количество() > 0 Тогда
        ОбъектСправочника.Код = УзлыКод[0].ТекстовоеСодержимое;
    КонецЕсли;
    
    УзлыНаименование = УзелОбъекта.ПолучитьЭлементыПоИмени("Description");
    Если УзлыНаименование.Количество() > 0 Тогда
        ОбъектСправочника.Наименование = УзлыНаименование[0].ТекстовоеСодержимое;
    КонецЕсли;
    
    ОбъектСправочника.Записать();
    
КонецЦикла;

Способ 2. Корректный потоковый обход через ЧтениеXML без XDTO

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

Посмотрим на реализацию пошагового разбора:


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

ТекущийОбъект = Неопределено;
ИмяТекущегоРеквизита = "";

Пока Чтение.Прочитать() Цикл
    
    Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        
        ИмяЭлемента = Чтение.Имя;
        
        Если ИмяЭлемента = "CatalogObject.Номенклатура.Вручную" Тогда
            ТекущийОбъект = Новый Структура("Ref, IsFolder, Code, Description");
        ИначеЕсли ТекущийОбъект <> Неопределено Тогда
            ИмяТекущегоРеквизита = ИмяЭлемента;
        КонецЕсли;
        
    ИначеЕсли Чтение.ТипУзла = ТипУзлаXML.Текст Тогда
        
        Если ТекущийОбъект <> Неопределено И ЗначениеЗаполнено(ИмяТекущегоРеквизита) Тогда
            ТекущийОбъект.Вставить(ИмяТекущегоРеквизита, Чтение.Значение);
        КонецЕсли;
        
    ИначеЕсли Чтение.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
        
        Если Чтение.Имя = "CatalogObject.Номенклатура.Вручную" Тогда
            // Обрабатываем и записываем собранный объект
            Если ТекущийОбъект <> Неопределено Тогда
                Ссылка = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(ТекущийОбъект.Ref));
                // Логика создания и заполнения объекта
            КонецЕсли;
            ТекущийОбъект = Неопределено;
        Иначе
            ИмяТекущегоРеквизита = "";
        КонецЕсли;
        
    КонецЕсли;
    
КонецЦикла;

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

Способ 3. Проверка схемы XSD и десериализация объектов целиком

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

Выгрузить схему типов конфигурации можно следующими способами:

  1. Через конфигуратор: Меню Конфигурация → Выгрузить XML-схему.
  2. Программно: Вызовом глобального метода ФабрикаXDTO.ЭкспортXMLСхемы(ПространствоИмен).

При наличии корректных пространств имен разбор выполняется без пошагового чтения каждого реквизита:


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

// Переходим к первому объекту
Пока Чтение.Прочитать() Цикл
    Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.ЛокальноеИмя = "CatalogObject.Номенклатура" Тогда
        // Десериализуем весь объект справочника одной командой
        ОбъектНоменклатура = ПрочитатьXML(Чтение);
        ОбъектНоменклатура.Записать();
    КонецЕсли;
КонецЦикла;

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

Рекомендации

Подведем итог: при разборе произвольных или модифицированных XML-структур не следует смешивать вызовы ЧтениеXML.Прочитать() и ПрочитатьXML() для отдельных дочерних тегов. Для небольших файлов наиболее простым решением является ПостроительDOM, а для больших объемов данных — построчный разбор состояний через ЧтениеXML с проверкой типа узла.

← На главную