При интеграции 1С с внешними информационными системами (сайтами, государственными порталами, логистическими сервисами) разработчики часто сталкиваются с необходимостью выгрузки данных в формат XML. На первый взгляд задача кажется простой, однако на практике начинающие программисты часто путают логическую структуру XML-документа, смешивая понятия элементов (узлов), атрибутов и текстового содержимого.
В этой статье мы подробно разберем, как уйти от записи данных в виде атрибутов и сформировать красивую, иерархически правильную структуру XML с вложенными тегами. Также мы рассмотрим решение распространенной проблемы с дублированием одинаковых тегов (например, нескольких номеров телефонов) и альтернативные, более продвинутые методы генерации XML в 1С.
---
Рассмотрим ситуацию, с которой столкнулся автор темы. Исходный код выглядел следующим образом:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ПутьКФайлуДляЗаписи);
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("complexes");
ЗаписьXML.ЗаписатьНачалоЭлемента("complex");
ЗаписьXML.ЗаписатьАтрибут("id", "1122");
ЗаписьXML.ЗаписатьАтрибут("name", "Комплекс №1");
ЗаписьXML.ЗаписатьАтрибут("address", "город, улица, дом");
ЗаписьXML.ЗаписатьКонецЭлемента(); // complex
ЗаписьXML.ЗаписатьКонецЭлемента(); // complexes
В результате выполнения этого кода формируется XML-структура, где все параметры объекта complex записаны внутри самого тега в виде атрибутов:
<?xml version="1.0" encoding="UTF-8"?>
<complexes>
<complex id="1122" name="Комплекс №1" address="город, улица, дом">
</complex>
</complexes>
Однако внешняя система-приемник (загрузчик) ожидает совершенно иной формат, где каждый параметр является полноценным вложенным XML-элементом (тегом) со своим текстовым значением:
<complexes>
<complex>
<id>1122</id>
<name>Комплекс №1</name>
<address>город, улица, дом</address>
</complex>
</complexes>
Выясним причину, почему так произошло. Автор кода использовал метод ЗаписатьАтрибут(), который добавляет свойства внутрь открывающего тега. Для того чтобы получить вложенную структуру, необходимо открывать новый элемент для каждого свойства, записывать в него текст и закрывать его.
---
Для того чтобы преобразовать атрибуты во вложенные элементы, нам понадобится метод ЗаписатьТекст(). Этот метод записывает текстовое содержимое в текущий активный узел XML-документа.
Разберем по шагам правильный алгоритм формирования вложенной структуры:
ЗаписатьНачалоЭлемента("complexes").ЗаписатьНачалоЭлемента("complex").id) открываем свой подчиненный элемент: ЗаписатьНачалоЭлемента("id").ЗаписатьТекст("1122").ЗаписатьКонецЭлемента().Посмотрим на пример реализации правильного кода на языке 1С:
ПутьКФайлуДляЗаписи = "C:\temp\complexes.xml";
ЗаписьXML = Новый ЗаписьXML;
// Настройки записи для создания красивых отступов (форматирования)
ПараметрыЗаписиXML = Новый ПараметрыЗаписиXML("UTF-8", "1.0", Истина, Символы.Таб);
ЗаписьXML.ОткрытьФайл(ПутьКФайлуДляЗаписи, ПараметрыЗаписиXML);
ЗаписьXML.ЗаписатьОбъявлениеXML();
// Открываем корневой элемент
ЗаписьXML.ЗаписатьНачалоЭлемента("complexes");
// Начало выгрузки конкретного комплекса
ЗаписьXML.ЗаписатьНачалоЭлемента("complex");
// Запись элемента "id"
ЗаписьXML.ЗаписатьНачалоЭлемента("id");
ЗаписьXML.ЗаписатьТекст("1122");
ЗаписьXML.ЗаписатьКонецЭлемента(); // id
// Запись элемента "name"
ЗаписьXML.ЗаписатьНачалоЭлемента("name");
ЗаписьXML.ЗаписатьТекст("Комплекс №1");
ЗаписьXML.ЗаписатьКонецЭлемента(); // name
// Запись элемента "address"
ЗаписьXML.ЗаписатьНачалоЭлемента("address");
ЗаписьXML.ЗаписатьТекст("город, улица, дом");
ЗаписьXML.ЗаписатьКонецЭлемента(); // address
ЗаписьXML.ЗаписатьКонецЭлемента(); // complex (закрываем карточку)
ЗаписьXML.ЗаписатьКонецЭлемента(); // complexes (закрываем корень)
ЗаписьXML.Закрыть();
Важный совет: Обратите внимание на использование объекта ПараметрыЗаписиXML. Передав в метод ОткрытьФайл() параметры с третьим аргументом Истина и символом табуляции, мы заставляем платформу автоматически расставлять переносы строк и отступы. Это делает XML-файл легко читаемым человеком.
---
В сообщении №15 автор столкнулся с дополнительным требованием: для одного комплекса необходимо выгрузить список телефонных номеров, причем теги должны называться одинаково и идти друг за другом:
<complex>
<id>1122</id>
<tel>городской</tel>
<tel>сотовый</tel>
</complex>
Объект ЗаписьXML работает по принципу последовательного потока (SAX-like writer). Ему абсолютно все равно, сколько одинаковых тегов вы запишете подряд. Главное — соблюдать строгую вложенность и своевременно закрывать каждый элемент перед открытием следующего.
Ошибки у разработчиков обычно возникают из-за неправильной подготовки данных в коде 1С. Например, если попытаться упаковать данные выгрузки в объект Структура:
// ТАК ДЕЛАТЬ НЕЛЬЗЯ для повторяющихся полей!
ДанныеКомплекса = Новый Структура;
ДанныеКомплекса.Вставить("id", "1122");
ДанныеКомплекса.Вставить("tel", "городской");
ДанныеКомплекса.Вставить("tel", "сотовый"); // Предыдущее значение перезапишется!
Структура 1С не может содержать ключи с одинаковыми именами. Чтобы обойти это ограничение, списки однотипных данных необходимо хранить в объектах Массив или ТаблицаЗначений. Проанализируем правильный шаблон кода для выгрузки структуры с повторяющимися тегами:
// Подготовим демонстрационные данные
ДанныеКомплекса = Новый Структура;
ДанныеКомплекса.Вставить("Идентификатор", "1122");
ДанныеКомплекса.Вставить("Наименование", "Комплекс №1");
СписокТелефонов = Новый Массив;
СписокТелефонов.Добавить("8-495-000-00-00"); // городской
СписокТелефонов.Добавить("8-900-000-00-00"); // сотовый
ДанныеКомплекса.Вставить("Телефоны", СписокТелефонов);
// Алгоритм выгрузки в XML
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\temp\complexes.xml", Новый ПараметрыЗаписиXML("UTF-8", "1.0", Истина));
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("complexes");
ЗаписьXML.ЗаписатьНачалоЭлемента("complex");
// Записываем простые реквизиты
ЗаписьXML.ЗаписатьНачалоЭлемента("id");
ЗаписьXML.ЗаписатьТекст(ДанныеКомплекса.Идентификатор);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("name");
ЗаписьXML.ЗаписатьТекст(ДанныеКомплекса.Наименование);
ЗаписьXML.ЗаписатьКонецЭлемента();
// Записываем массив телефонов через цикл
Для Каждого НомерТелефона Из ДанныеКомплекса.Телефоны Цикл
ЗаписьXML.ЗаписатьНачалоЭлемента("tel");
ЗаписьXML.ЗаписатьТекст(НомерТелефона);
ЗаписьXML.ЗаписатьКонецЭлемента(); // tel
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента(); // complex
ЗаписьXML.ЗаписатьКонецЭлемента(); // complexes
ЗаписьXML.Закрыть();
---
В финальных сообщениях форума автору справедливо советуют обратить внимание на механизм XDTO (XML Data Transfer Objects). При работе с большими и сложными структурами данных ручная сборка через ЗаписьXML становится трудоемкой и подверженной ошибкам (легко забыть закрыть тег или перепутать их последовательность).
XDTO решает эти проблемы, предоставляя разработчику объектную модель данных. Вы работаете с привычными свойствами объектов 1С, а платформа сама преобразует их в валидный XML.
Рассмотрим, как решить нашу задачу с помощью фабрики XDTO, если у нас нет готовой схемы (XSD):
// 1. Создаем модель типов динамически (или используем готовую XSD-схему из метаданных)
// Для простых задач можно использовать глобальную фабрику XDTO, если типы стандартные.
// Покажем пример работы с динамическим созданием объектов через фабрику.
// Допустим, у нас в конфигурации импортирована схема данных.
// Создадим объект по типу из схемы:
ТипКомплекса = ФабрикаXDTO.Тип("http://www.example.org/complexes", "ComplexType");
НовыйКомплекс = ФабрикаXDTO.Создать(ТипКомплекса);
НовыйКомплекс.id = "1122";
НовыйКомплекс.name = "Комплекс №1";
НовыйКомплекс.address = "город, улица, дом";
// Если свойство "tel" в схеме описано как список (MaxOccurs = unbounded),
// мы можем просто добавлять в него элементы:
НовыйКомплекс.tel.Добавить("8-495-000-00-00");
НовыйКомплекс.tel.Добавить("8-900-000-00-00");
// 2. Сериализация объекта в файл XML
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\temp\complex_xdto.xml", Новый ПараметрыЗаписиXML("UTF-8", "1.0", Истина));
ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, НовыйКомплекс, "complex", "http://www.example.org/complexes");
ЗаписьXML.Закрыть();
Преимущества XDTO:
---
Если вам необходимо выгружать документы, структура которых постоянно меняется, или требуется дописывать новые узлы в уже существующие ветки готового файла в произвольном порядке, потоковый объект ЗаписьXML не подойдет, так как он умеет писать только строго вперед.
В этом случае на помощь приходит DOM-модель. Она позволяет построить дерево документа прямо в оперативной памяти компьютера.
Рассмотрим пример генерации нашей структуры через DOM:
// Создаем документ DOM
ДокументDOM = Новый ДокументDOM;
// Создаем корневой элемент
КорневойЭлемент = ДокументDOM.СоздатьЭлемент("complexes");
ДокументDOM.ЗаписатьОбластьДанных(КорневойЭлемент); // Делаем его корневым
// Создаем элемент "complex"
ЭлементComplex = ДокументDOM.СоздатьЭлемент("complex");
КорневойЭлемент.ДобавитьДочерний(ЭлементComplex);
// Функция-помощник для быстрого добавления вложенного текстового узла
ЭлементID = ДокументDOM.СоздатьЭлемент("id");
ТекстID = ДокументDOM.СоздатьТекст(Строка("1122"));
ЭлементID.ДобавитьДочерний(ТекстID);
ЭлементComplex.ДобавитьДочерний(ЭлементID);
// Аналогично добавляем имя
ЭлементName = ДокументDOM.СоздатьЭлемент("name");
ТекстName = ДокументDOM.СоздатьТекст("Комплекс №1");
ЭлементName.ДобавитьДочерний(ТекстName);
ЭлементComplex.ДобавитьДочерний(ЭлементName);
// Запись всего DOM-дерева в файл
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\temp\complex_dom.xml");
ЗаписьDOM = Новый ЗаписьDOM;
ЗаписьDOM.Записать(ДокументDOM, ЗаписьXML);
ЗаписьXML.Закрыть();
Использование DOM требует написания большего объема кода и потребляет значительно больше оперативной памяти, так как вся структура файла держится в памяти до момента выгрузки. Этот способ рекомендуется использовать только для небольших XML-документов со сложной нелинейной структурой.
---
Для решения классической задачи выгрузки данных во внешние системы программисту 1С важно четко понимать различия в структуре XML-документа. Если принимающей стороне нужны вложенные теги с текстовым содержимым, используйте последовательную связку методов ЗаписатьНачалоЭлемента(), ЗаписатьТекст() и ЗаписатьКонецЭлемента().
Если формат выгрузки часто меняется или строго регламентирован внешней схемой XSD — делайте выбор в пользу XDTO. Это сэкономит ваше время на ручном написании кода и убережет от ошибок форматирования.