Разбор XML из строковой переменной: ЧтениеXML, DOM и XDTO на примерах
Содержание
Чтобы прочитать строку как 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-подобный) читатель. Он идёт по тексту узел за узлом и не строит дерево в памяти. Поэтому он самый быстрый и экономный, но код получается длиннее: текущее положение в документе приходится отслеживать самому.
Как устроен цикл чтения
- Создайте объект:
Чтение = Новый ЧтениеXML; - Передайте текст:
Чтение.УстановитьСтроку(ТекстXML); - Крутите цикл
Пока Чтение.Прочитать() Цикл. Каждый вызов перемещает читатель на следующий узел. - Проверяйте
Чтение.ТипУзла:ТипУзлаXML.НачалоЭлемента,ТипУзлаXML.Текст,ТипУзлаXML.КонецЭлемента. - Имя элемента берите из
Чтение.Имя, текст — изЧтение.Значение, атрибут — изЧтение.ЗначениеАтрибута("имя"). - В конце вызовите
Чтение.Закрыть().
Полный пример разбора
&НаСервереБезКонтекста
Функция РазобратьЗаказ(Текст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, и разбор упадёт с непонятным сообщением.
<?xml стоит мусор: пробел, перенос строки или невидимый символ BOM, оставшийся от склейки строк. Отрежьте всё до первого < или используйте СокрЛП().ООО "Рога & Копыта". Корректный XML должен содержать &. Исправлять нужно на стороне, которая формирует XML; на своей стороне можно временно заменить & через СтрЗаменить(), если уверены, что сущностей в тексте нет.xmlns="..."). Поиск по локальному имени без учёта пространства имён ничего не найдёт. Для XPath создайте разыменователь с префиксом и обращайтесь как //ns:item.ТипЗнч() перед циклом.ПолучитьТелоКакСтроку("UTF-8") или ту, что указана в заголовке ответа.Пространства имён: короткий рецепт
Для документа вида <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-схеме?
ПрочитатьXML() вызовет исключение с описанием расхождения.