Разбор XML из строковой переменной: ЧтениеXML, DOM и XDTO на примерах

· 9 мин

Содержание

Чтобы прочитать строку как XML в 1С, создайте объект ЧтениеXML и передайте ему текст методом УстановитьСтроку(). Дальше этот объект читают напрямую в цикле Прочитать(), отдают ПостроительDOM или ФабрикаXDTO. Записывать строку во временный файл не нужно.

Частая ошибка новичков — искать у строки метод вроде «ЗагрузитьXML» или пытаться разобрать текст через СтрНайти() и Сред(). Первого метода в платформе нет. Второй способ ломается на первом же переносе строки, атрибуте в другом порядке или экранированном &. Ниже три рабочих способа, их отличия и готовый код.

Зачем превращать строку в XML-документ

XML почти никогда не приходит в 1С файлом на диске. Чаще это строка:

  • 📡 тело ответа веб-сервиса или HTTP-запроса — Ответ.ПолучитьТелоКакСтроку();
  • 🗄️ значение реквизита типа «Строка неограниченной длины», куда раньше сохранили настройки или пакет обмена;
  • 📋 текст, который пользователь вставил в поле формы из письма или личного кабинета;
  • 🔄 сообщение из очереди или результат выполнения внешней компоненты.

Со строкой можно работать только как с текстом. После разбора у вас появляется дерево: элементы, атрибуты, вложенность, пространства имён. По нему можно найти нужный узел по имени, проверить, есть ли атрибут, и корректно получить значения — без ручной возни с кавычками и спецсимволами.

Для всех примеров ниже возьмём одну и ту же строку:

<?xml version="1.0" encoding="UTF-8"?>
<order number="125" date="2026-10-01">
  <item sku="A-17">
    <name>Бумага А4</name>
    <qty>10</qty>
    <price>350.50</price>
  </item>
  <item sku="B-03">
    <name>Ручка синяя</name>
    <qty>25</qty>
    <price>18.00</price>
  </item>
</order>

Задача: получить номер заказа и массив позиций с артикулом, наименованием, количеством и ценой.

Способ 1. Потоковое чтение строки через ЧтениеXML

ЧтениеXML — последовательный (SAX-подобный) читатель. Он идёт по тексту узел за узлом и не строит дерево в памяти. Поэтому он самый быстрый и экономный, но код получается длиннее: текущее положение в документе приходится отслеживать самому.

Как устроен цикл чтения

  1. Создайте объект: Чтение = Новый ЧтениеXML;
  2. Передайте текст: Чтение.УстановитьСтроку(ТекстXML);
  3. Крутите цикл Пока Чтение.Прочитать() Цикл. Каждый вызов перемещает читатель на следующий узел.
  4. Проверяйте Чтение.ТипУзла: ТипУзлаXML.НачалоЭлемента, ТипУзлаXML.Текст, ТипУзлаXML.КонецЭлемента.
  5. Имя элемента берите из Чтение.Имя, текст — из Чтение.Значение, атрибут — из Чтение.ЗначениеАтрибута("имя").
  6. В конце вызовите Чтение.Закрыть().

Полный пример разбора

&НаСервереБезКонтекста
Функция РазобратьЗаказ(ТекстXML)

    Результат = Новый Структура("Номер, Позиции", "", Новый Массив);
    ТекущаяПозиция = Неопределено;
    ИмяПоля = "";

    Чтение = Новый ЧтениеXML;
    Чтение.УстановитьСтроку(ТекстXML);

    Пока Чтение.Прочитать() Цикл

        Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда

            Если Чтение.Имя = "order" Тогда
                Результат.Номер = Чтение.ЗначениеАтрибута("number");
            ИначеЕсли Чтение.Имя = "item" Тогда
                ТекущаяПозиция = Новый Структура("Артикул, Наименование, Количество, Цена",
                    Чтение.ЗначениеАтрибута("sku"), "", 0, 0);
            ИначеЕсли ТекущаяПозиция <> Неопределено Тогда
                ИмяПоля = Чтение.Имя;
            КонецЕсли;

        ИначеЕсли Чтение.ТипУзла = ТипУзлаXML.Текст И ТекущаяПозиция <> Неопределено Тогда

            Если ИмяПоля = "name" Тогда
                ТекущаяПозиция.Наименование = Чтение.Значение;
            ИначеЕсли ИмяПоля = "qty" Тогда
                ТекущаяПозиция.Количество = XMLЗначение(Тип("Число"), Чтение.Значение);
            ИначеЕсли ИмяПоля = "price" Тогда
                ТекущаяПозиция.Цена = XMLЗначение(Тип("Число"), Чтение.Значение);
            КонецЕсли;

        ИначеЕсли Чтение.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда

            Если Чтение.Имя = "item" Тогда
                Результат.Позиции.Добавить(ТекущаяПозиция);
                ТекущаяПозиция = Неопределено;
            КонецЕсли;
            ИмяПоля = "";

        КонецЕсли;

    КонецЦикла;

    Чтение.Закрыть();
    Возврат Результат;

КонецФункции

Для строки из примера функция вернёт номер 125 и две позиции: 10 × 350,50 и 25 × 18,00.

Числа и даты из XML превращайте через XMLЗначение(Тип("Число"), ...) и XMLЗначение(Тип("Дата"), ...). Функция понимает XML-формат: точку как разделитель дробной части и даты вида 2026-10-01. Обычное Число() на строке 350.50 может повести себя не так, как вы ждёте.

Когда нужный элемент найден, а его вложенное содержимое не интересно, вызовите Чтение.Пропустить() — читатель перескочит весь узел целиком до его закрывающего тега.

Способ 2. ПостроительDOM: всё дерево в памяти

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

Получаем ДокументDOM из строки

Строку всё равно сначала передают в ЧтениеXML, а уже его — построителю:

&НаСервереБезКонтекста
Функция РазобратьЗаказDOM(ТекстXML)

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

    Результат = Новый Структура("Номер, Позиции", "", Новый Массив);
    Результат.Номер = Документ.ЭлементДокумента.ПолучитьАтрибут("number");

    Узлы = Документ.ПолучитьЭлементыПоИмени("item");
    Для Индекс = 0 По Узлы.Количество() - 1 Цикл
        Узел = Узлы.Элемент(Индекс);
        Позиция = Новый Структура;
        Позиция.Вставить("Артикул", Узел.ПолучитьАтрибут("sku"));
        Позиция.Вставить("Наименование", ТекстПодэлемента(Узел, "name"));
        Позиция.Вставить("Количество", XMLЗначение(Тип("Число"), ТекстПодэлемента(Узел, "qty", "0")));
        Позиция.Вставить("Цена", XMLЗначение(Тип("Число"), ТекстПодэлемента(Узел, "price", "0")));
        Результат.Позиции.Добавить(Позиция);
    КонецЦикла;

    Возврат Результат;

КонецФункции

&НаСервереБезКонтекста
Функция ТекстПодэлемента(Узел, Имя, ПоУмолчанию = "")
    Найденные = Узел.ПолучитьЭлементыПоИмени(Имя);
    Если Найденные.Количество() = 0 Тогда
        Возврат ПоУмолчанию;
    КонецЕсли;
    Возврат Найденные.Элемент(0).ТекстовоеСодержимое;
КонецФункции

Вспомогательная функция нужна не для красоты. Если в одной из позиций нет тега <price>, обращение Элемент(0) к пустому списку вызовет исключение. Проверка Количество() избавляет от падения на «кривых» данных.

Выборка по условию через XPath

XPath избавляет от ручных циклов, когда нужны не все узлы, а часть. Например, позиции с количеством больше 20:

Разыменователь = Новый РазыменовательПространствИменDOM(Документ);
РезультатXPath = Документ.ВычислитьВыражениеXPath("/order/item[qty > 20]", Документ, Разыменователь);

Узел = РезультатXPath.ПолучитьСледующий();
Пока Узел <> Неопределено Цикл
    Сообщить(Узел.ПолучитьАтрибут("sku"));
    Узел = РезультатXPath.ПолучитьСледующий();
КонецЦикла;

Для нашего XML выведется только B-03.

Способ 3. ФабрикаXDTO и СериализаторXDTO: когда XML описан схемой

Третий способ подходит, если структура XML заранее известна. Например, пакет сформирован другой базой 1С или под него в конфигурацию загружен XDTO-пакет из XSD-схемы.

Нетипизированное чтение

Без схемы фабрика тоже читает документ и возвращает ОбъектXDTO, где атрибуты и вложенные элементы доступны как свойства:

Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(ТекстXML);
Заказ = ФабрикаXDTO.ПрочитатьXML(Чтение);
Чтение.Закрыть();

Номер = Заказ.number;
Позиции = Заказ.item;
Если ТипЗнч(Позиции) <> Тип("СписокXDTO") Тогда
    // одна позиция пришла объектом, а не списком
КонецЕсли;

Ловушка здесь одна, но частая: если элемент item встретился один раз, свойство вернёт сам объект. Если два и больше — СписокXDTO. Код, рассчитанный на список, упадёт на заказе из одной строки. Поэтому всегда проверяйте тип, как в примере.

Значения, сериализованные платформой

Если строку получили методом ЗаписатьXML() через СериализаторXDTO — например, выгрузили структуру, массив или таблицу значений, — обратно её читают тем же сериализатором:

Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(ТекстXML);
Значение = СериализаторXDTO.ПрочитатьXML(Чтение);
Чтение.Закрыть();

На выходе будет исходное значение 1С с типами, а не дерево узлов.

Какой способ выбрать

СпособКогда братьПамятьМинусы
ЧтениеXML в циклеБольшие строки, простая плоская структура, нужна скоростьМинимумСостояние разбора отслеживаете вручную
ПостроительDOM + XPathПроизвольный доступ, выборки по условию, документ до нескольких мегабайтВсё дерево целикомТяжело на очень больших документах
ФабрикаXDTOЕсть XSD-схема или XDTO-пакет, обмен между базамиОбъектная модель целикомНет на тонком клиенте, ловушка «один элемент / список»
СериализаторXDTOСтроку записала сама 1С через ЗаписатьXML()Зависит от значенияЧитает только свой формат сериализации

Ошибки при разборе XML из строки и как их ловить

Любой из способов бросает исключение, если текст не является корректным XML. Значит, разбор всегда оборачивают в Попытка:

Если ПустаяСтрока(ТекстXML) Тогда
    ВызватьИсключение "Получена пустая строка вместо XML";
КонецЕсли;

Чтение = Новый ЧтениеXML;
Попытка
    Чтение.УстановитьСтроку(ТекстXML);
    Построитель = Новый ПостроительDOM;
    Документ = Построитель.Прочитать(Чтение);
    Чтение.Закрыть();
Исключение
    Чтение.Закрыть();
    ЗаписьЖурналаРегистрации("Обмен.РазборXML", УровеньЖурналаРегистрации.Ошибка, , ,
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    ВызватьИсключение "Не удалось разобрать XML: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;

Не глушите исключение пустой веткой Исключение — иначе обработка молча вернёт пустой результат, и пустой документ или незагруженный заказ всплывут только через неделю.

Если строка пришла из HTTP-ответа, сначала проверьте Ответ.КодСостояния. При коде 500 или 404 в теле обычно лежит HTML-страница ошибки, а не XML, и разбор упадёт с непонятным сообщением.

Диагностика: выберите симптом

Пространства имён: короткий рецепт

Для документа вида <order xmlns="http://example.org/orders"> XPath без префикса не работает. Задайте префикс явно:

Разыменователь = Новый РазыменовательПространствИменDOM("ns", "http://example.org/orders");
РезультатXPath = Документ.ВычислитьВыражениеXPath("/ns:order/ns:item", Документ, Разыменователь);

В потоковом чтении то же самое решается сравнением Чтение.ЛокальноеИмя и Чтение.URIПространстваИмен вместо Чтение.Имя: в Имя попадает префикс, который в разных выгрузках бывает разным.

Чтение строки как XML в управляемой и обычной форме

Управляемая форма

Разбор выносите на сервер. Причин две: ФабрикаXDTO на тонком клиенте недоступна, а лишние серверные вызовы с передачей большого текста туда-обратно замедляют форму.

Типичная схема для формы, где пользователь вставляет XML в многострочное поле ТекстXML и жмёт кнопку «Загрузить»:

&НаКлиенте
Процедура Загрузить(Команда)
    Если ПустаяСтрока(ТекстXML) Тогда
        Сообщить("Вставьте текст XML в поле");
        Возврат;
    КонецЕсли;
    ЗагрузитьНаСервере();
КонецПроцедуры

&НаСервере
Процедура ЗагрузитьНаСервере()
    Данные = РазобратьЗаказ(ТекстXML);
    Объект.Товары.Очистить();
    Для Каждого Позиция Из Данные.Позиции Цикл
        НоваяСтрока = Объект.Товары.Добавить();
        НоваяСтрока.Артикул = Позиция.Артикул;
        НоваяСтрока.Количество = Позиция.Количество;
        НоваяСтрока.Цена = Позиция.Цена;
    КонецЦикла;
КонецПроцедуры

Сама функция разбора объявлена с директивой &НаСервереБезКонтекста: ей не нужны данные формы, только строка. Имена табличной части и реквизитов в примере условные — подставьте свои.

Обычная форма (толстый клиент)

В обычных формах директив компиляции нет, а ЧтениеXML, ПостроительDOM и ФабрикаXDTO доступны прямо в модуле формы. Функцию РазобратьЗаказ можно вставить без &НаСервереБезКонтекста, остальной код не меняется.

Если тот же разбор нужен и в обычной, и в управляемой форме, вынесите его в общий модуль с флажком «Сервер» и вызывайте оттуда. Тогда исправление ошибки разбора делается в одном месте.

Производительность при разборе больших XML-строк

На строках в десятки килобайт разницы между способами не заметно. Она появляется на больших пакетах обмена.

  • 🧠 DOM держит всё дерево в памяти. Дерево занимает заметно больше места, чем исходный текст. Для пакетов в десятки мегабайт берите ЧтениеXML.
  • 🔁 Не ищите элементы в цикле по всему документу. Вызов Документ.ПолучитьЭлементыПоИмени() внутри цикла по позициям каждый раз обходит всё дерево. Ищите от текущего узла: Узел.ПолучитьЭлементыПоИмени().
  • ⏭️ Пропускайте ненужное. В потоковом чтении Чтение.Пропустить() перескакивает целые ветви, которые вам не нужны.
  • 📂 Большой текст — не строка. Если XML пришёл файлом, читайте его через Чтение.ОткрытьФайл(), а не загружайте сначала в переменную. Для HTTP-ответа большого размера можно сохранить тело в файл через ПолучитьТелоКакДвоичныеДанные() и Записать().
  • 🧹 Закрывайте читатель. Чтение.Закрыть() освобождает ресурсы сразу, не дожидаясь выхода из процедуры.

Если тот же XML нужно разбирать регулярно (например, раз в час из регламентного задания), сравните время двух вариантов на реальном пакете. Замер ТекущаяУниверсальнаяДатаВМиллисекундах() до и после разбора даёт честную цифру за минуту.

Частые вопросы по разбору XML-строки

Можно ли разобрать XML-строку на клиенте?
ЧтениеXML и DOM на клиенте можно использовать, но разбор всё равно лучше делать на сервере: там доступна ФабрикаXDTO, проще писать в журнал регистрации и не нужно гонять данные между клиентом и сервером.
Как получить строку обратно из ДокументDOM?
Через ЗаписьXML и ЗаписьDOM: создайте ЗаписьXML, вызовите УстановитьСтроку(), запишите документ методом ЗаписьDOM.Записать(Документ, Запись) и заберите текст через Запись.Закрыть().
Чем `Чтение.Имя` отличается от `Чтение.ЛокальноеИмя`?
Имя содержит префикс пространства имён, например ns:item. ЛокальноеИмя — только item. Если у источника префикс может меняться, сравнивайте локальное имя вместе с URI.
Как проверить, что строка вообще является XML, не разбирая её до конца?
Пройдите её циклом Пока Чтение.Прочитать() Цикл КонецЦикла внутри Попытка. Потоковое чтение без обработки узлов работает быстро и найдёт ошибку синтаксиса в любом месте текста.
Как проверить XML на соответствие XSD-схеме?
Загрузите схему в XDTO-пакет конфигурации или создайте фабрику из схемы, затем читайте документ типизированно с указанием типа. Если структура не совпадает со схемой, ПрочитатьXML() вызовет исключение с описанием расхождения.