Чтение XML в 1С Предприятие: потоковый разбор, DOM и XDTO

· 9 мин

Содержание

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

Ниже по порядку: как устроен XML с точки зрения платформы, код для каждого способа, что делать с файлом на сотни мегабайт, как проверить структуру и не потерять кодировку. Все примеры рассчитаны на 1С 8.3, на клиенте и на сервере они пишутся одинаково, но разбирать крупные файлы лучше на сервере.

Из чего состоит XML и что видит платформа

Платформа читает XML как последовательность узлов. Для неё <Номер>15</Номер> — это три события: начало элемента Номер, текст 15, конец элемента Номер. Атрибуты (<Строка Код="001">) относятся к узлу начала элемента.

Что нужно знать до первой строки кода:

  • 📄 у корректного файла один корневой элемент, всё остальное вложено в него;
  • 🔤 имена элементов чувствительны к регистру: Товар и товар — разные узлы;
  • 🌐 пространства имён (xmlns) входят в имя узла, и при сравнении их учитывают отдельно;
  • 🔣 кодировка задаётся в первой строке (<?xml version="1.0" encoding="windows-1251"?>), и читать файл нужно в той же кодировке.

Все значения в XML — строки. Число, дату и булево из текста приводит либо ваш код, либо XDTO. Для ручного приведения есть функция XMLЗначение:

Количество = XMLЗначение(Тип("Число"), "12.5");
Дата = XMLЗначение(Тип("Дата"), "2024-05-17T00:00:00");
Флаг = XMLЗначение(Тип("Булево"), "true");

Обратная функция — XMLСтрока. Она превращает значение в строку в формате XML, поэтому дату, например, получите сразу в виде 2024-05-17T00:00:00.

Не разбирайте дату и число через Сред() и СтрНайти(). XMLЗначение и XMLСтрока стандартны для обмена данными, и десятичный разделитель в них всегда точка.

Чтение XML через объект ЧтениеXML

Это основной способ. Объект идёт по файлу сверху вниз и не держит в памяти весь документ. Минимальный цикл выглядит так:

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

Пока Чтение.Прочитать() Цикл
    Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        Сообщить("Элемент: " + Чтение.Имя);
    КонецЕсли;
КонецЦикла;

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

Прочитать() возвращает Истина, пока есть следующий узел. Тип текущего узла смотрите в ТипУзла, имя — в Имя, текст — в Значение.

Атрибуты и текст внутри элемента

Атрибуты читают, пока курсор стоит на начале элемента:

Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "Товар" Тогда
    Код = Чтение.ЗначениеАтрибута("Код");
    Для Индекс = 0 По Чтение.КоличествоАтрибутов() - 1 Цикл
        Сообщить(Чтение.ИмяАтрибута(Индекс) + " = " + Чтение.ЗначениеАтрибута(Индекс));
    КонецЦикла;
КонецЕсли;

Текст элемента лежит в следующем узле. После НачалоЭлемента вызовите Прочитать() ещё раз и проверьте, что пришёл узел типа ТипУзлаXML.Текст. У пустого элемента <Комментарий/> текстового узла нет, и если не проверить тип, вы прочитаете чужое значение.

Типовая связка для простых элементов:

Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "Номер" Тогда
    Чтение.Прочитать();
    Если Чтение.ТипУзла = ТипУзлаXML.Текст Тогда
        Номер = Чтение.Значение;
    КонецЕсли;
КонецЕсли;

Читать из строки, а не из файла

Если XML пришёл по сети или лежит в реквизите, файл создавать не надо:

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

Дальше цикл тот же. Для двоичных данных (вложение, ответ сервиса) сначала сохраните их во временный файл через ПолучитьИмяВременногоФайла() и откройте как обычный файл.

Пропуск ненужных веток

Если часть файла вам не нужна, вызовите Чтение.Пропустить() на начале элемента: курсор перейдёт сразу за его закрывающий тег вместе со всем вложенным. Это сильно ускоряет разбор файлов с большими служебными блоками.

Чтение XML в дерево DOM

DOM загружает документ целиком, и вы свободно ходите по нему в любом порядке. Для небольших файлов (до нескольких мегабайт) это самый короткий код:

Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл("C:\Обмен\import.xml");
Построитель = Новый ПостроительDOM;
Документ = Построитель.Прочитать(Чтение);
Чтение.Закрыть();

Корень = Документ.ДокументЭлемент;
Для Каждого Товар Из Корень.ПолучитьЭлементыПоИмени("Товар") Цикл
    Код = Товар.ПолучитьАтрибут("Код");
    Наименование = Товар.ПолучитьЭлементыПоИмени("Наименование")[0].ТекстовоеСодержимое;
КонецЦикла;

Здесь ПолучитьЭлементыПоИмени ищет потомков по всему поддереву, а ТекстовоеСодержимое отдаёт текст элемента одной строкой. Если нужны только прямые дети, перебирайте Товар.ДочерниеУзлы.

⚠️ DOM занимает в памяти заметно больше, чем сам файл. Файл на 200 МБ через ПостроительDOM легко положит рабочий процесс сервера. Для таких объёмов берите поток.

Типизированное чтение через XDTO

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

Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл("C:\Обмен\import.xml");
Чтение.Прочитать();
Объект = ФабрикаXDTO.ПрочитатьXML(Чтение);
Чтение.Закрыть();

Номер = Объект.Номер;

Если корневой элемент известен заранее, тип можно указать явно вторым параметром ФабрикаXDTO.ПрочитатьXML(Чтение, Тип). Это надёжнее, когда в файле нет атрибута xsi:type.

Когда XDTO оправдан:

СитуацияЧто выбрать
Есть схема XSD, формат строгий (обмены, веб-сервисы)XDTO
Нужно вытащить 2–3 значения из файлаЧтениеXML
Надо править структуру и записывать обратноDOM
Файл больше сотен мегабайтЧтениеXML

Пакет XDTO подгружается в конфигурации в разделе «Общие → Пакеты XDTO» в конфигураторе. Для разовой задачи городить пакет ради одного файла не нужно, хватит потока.

Пространства имён и префиксы

В файлах из учётных систем и сервисов имена часто идут с префиксом: <ns:Товар>. В Имя вы получите полное имя вместе с префиксом, а чистое локальное имя отдаёт свойство ЛокальноеИмя. Пространство имён лежит в URIПространстваИмён. Если искать узел по Имя = "Товар", а в файле стоит префикс, условие не сработает, и разбор молча пройдёт мимо данных.

Сравнивайте по ЛокальноеИмя и отдельно проверяйте пространство имён, когда в файле смешаны элементы разных форматов. Такая привычка избавляет от странных случаев, когда тот же код работает на файле одного контрагента и не работает на файле другого, хотя данные одинаковые.

Большие файлы: как не упереться в память и время

На больших файлах решают три вещи: способ чтения, место запуска и порядок обработки.

  1. Читайте потоком, а найденные данные складывайте не в один гигантский массив, а записывайте порциями (например, по 1000 записей в таблицу значений с последующей записью в базу).
  2. Запускайте разбор на сервере, а не на тонком клиенте: данные не гоняются по сети между клиентом и сервером.
  3. Используйте Пропустить() для веток, которые не нужны.
  4. Закрывайте объект Закрыть() сразу после разбора, а не в конце процедуры. Файл не должен оставаться заблокированным.
  5. Для длинной обработки запускайте фоновое задание, чтобы не держать сеанс пользователя.

Типовой приём — читать по одной «записи» и сразу её обрабатывать:

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

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

Свойство ИгнорироватьПробелы

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

Проверка структуры и обработка ошибок

Файл от контрагента или другой системы часто приходит битым. Ошибки разбора платформа выбрасывает исключением, поэтому весь цикл чтения оборачивайте в Попытка … Исключение:

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

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

Что чаще всего ломает разбор:

  • ❌ несколько корневых элементов или текст до <?xml;
  • ❌ неэкранированные символы <, & в тексте (должны быть &lt; и &amp;);
  • ❌ служебный символ BOM в начале файла при неверно заданной кодировке;
  • ❌ XML-декларация говорит одну кодировку, а байты записаны в другой.

Если кириллица читается кракозябрами, кодировка в декларации не совпадает с реальной. Укажите кодировку явно вторым параметром: Чтение.ОткрытьФайл(Путь, "windows-1251") или "UTF-8".

Проверка по схеме

Если у обмена есть XSD, структуру сверяют до разбора данных. Для XDTO проверка идёт сама: при несоответствии схеме ФабрикаXDTO.ПрочитатьXML выбросит исключение. Для потокового чтения структуру проверяйте в коде: убедитесь, что встретились обязательные элементы, и заведите флаг для каждого.

Проверка в коде часто выручает и без схемы:

Если Не ЗначениеЗаполнено(Номер) Тогда
    ВызватьИсключение "В файле нет обязательного элемента Номер";
КонецЕсли;
Диагностика: выберите симптом

Практические сценарии

Загрузка номенклатуры из выгрузки

Прайс-лист поставщика в XML: корень Каталог, внутри много элементов Товар с атрибутом Артикул и вложенными Наименование, Цена. Поток вычитывает Товар целиком, цену приводит через XMLЗначение(Тип("Число"), …), а запись в справочник идёт порциями. Дубли по артикулу ищите запросом до создания элемента, а не после.

Разбор ответа веб-сервиса

Ответ приходит строкой в теле HTTP-ответа. Читайте его через УстановитьСтроку, без временных файлов. Если сервис возвращает ошибки внутри XML с кодом 200, проверяйте наличие узла с описанием ошибки до обработки данных.

Чтение файла обмена между базами

Для стандартных обменов (конвертация данных, обмен по правилам) формат определён платформой и самой конфигурацией. Там разбор лучше не переписывать: используйте штатные механизмы обмена. Ручной код оправдан, когда формат нестандартный, например выгрузка из стороннего кассового ПО.

Файлы электронных документов

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

Сопоставление полей файла и 1С

Перед разбором удобно составить таблицу соответствий:

Сопоставление полей
Элемент Артикул→Реквизит «Артикул» справочника Номенклатура
Элемент Наименование→Наименование
Атрибут Код→Код
Элемент Цена→Регистр сведений «Цены номенклатуры»

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

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

Если не получилось выбрать, оцените три параметра: размер файла, повторяемость формата и то, нужны ли вам все данные или лишь несколько значений. Для разовой выборки нескольких значений хватит ЧтениеXML и 15 строк кода. Для регулярного обмена с жёсткой схемой стоит один раз описать пакет XDTO.

Частые вопросы

Как прочитать XML из строки, а не из файла?
Создайте ЧтениеXML и вызовите УстановитьСтроку(ТекстXML). Дальше разбор такой же, как для файла.
Можно ли читать XML на клиенте?
Да, но на больших файлах это медленно, и данные придётся передавать на сервер. Разбирайте на сервере, а клиенту отдавайте результат.
Чем ЧтениеXML отличается от ПостроительDOM?
ЧтениеXML идёт по файлу потоком и почти не занимает память. ПостроительDOM строит в памяти дерево всего документа и позволяет переходить по нему в любую сторону.
Как узнать, что файл прочитан до конца?
Прочитать() вернёт Ложь, когда узлы закончились. Не забывайте после цикла вызывать Закрыть().
Как получить число или дату из текста элемента?
Через XMLЗначение(Тип("Число"), Строка) и XMLЗначение(Тип("Дата"), Строка). Формат даты в XML — ГГГГ-ММ-ДДTЧЧ:ММ:СС.