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