При организации обмена данными между базами 1С или интеграции с внешними информационными системами разработчики часто используют стандартный механизм сериализации. Метод ЗаписатьXML позволяет быстро выгрузить весь объект (например, ДокументОбъект) в файл буквально одной строкой кода. Однако на практике часто возникает задача: необходимо передать в файле обмена дополнительный реквизит, жестко привязанный к документу, которого физически нет в метаданных конфигурации (в СУБД). Создавать ради одной выгрузки реальный реквизит в базе данных не хочется, так как это перегружает структуру таблиц и усложняет поддержку конфигурации.
В этой статье мы подробно разберем, как решить эту задачу на платформах 1С v8.2 и v8.3. Мы проанализируем несколько различных подходов — от простых "хитростей" платформы до гибкого манипулирования XML-документом через модель DOM.
Рассмотрим стандартный сценарий выгрузки зарегистрированных изменений из плана обмена. Обычно код выглядит следующим образом:
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
Хмл = Новый ЗаписьXML();
ЗаписьСообщения.НачатьЗапись(ХМЛ, Узел);
Выборка = ПланыОбмена.ВыбратьИзменения(Узел, НомерСообщения);
Пока Выборка.Следующий() Цикл
Данные = Выборка.Получить();
ЗаписатьXML(хмл, Данные);
КонецЦикла;
ЗаписьСообщения.ЗакончитьЗапись();
Метод ЗаписатьXML(хмл, Данные) автоматически преобразует объект Данные (в нашем случае ДокументОбъект) в XML-структуру, соответствующую его метаданным. Наша цель — внедрить в этот XML-пакет свое кастомное поле с произвольным значением, не меняя структуру самого документа в конфигураторе.
Это один из самых простых и изящных способов, заложенных в саму платформу 1С. Мало кто знает, что стандартный сериализатор XML обращает внимание не только на реквизиты объекта, заданные в конфигураторе, но и на локальные переменные модуля объекта, если они объявлены как экспортные.
Выясним, как это реализовать по шагам:
Перем МойДополнительныйРеквизит Экспорт;
// Получаем объект для выгрузки
ДокументОбъект = Данные.ПолучитьОбъект();
ДокументОбъект.МойДополнительныйРеквизит = "Нужное нам значение";
ЗаписатьXML(хмл, ДокументОбъект);
При выполнении этого кода платформа автоматически добавит в XML-структуру документа новый тег МойДополнительныйРеквизит со значением "Нужное нам значение".
Важные особенности этого метода:
ПрочитатьXML, то в конфигурации-приемнике в модуле этого же документа обязательно должна быть объявлена аналогичная экспортная переменная Перем МойДополнительныйРеквизит Экспорт;. В противном случае чтение файла завершится ошибкой десериализации.Если нам нужно отправить данные во внешнюю систему (не 1С), которая строго валидирует входящий XML, или если мы не хотим вносить изменения в модуль объекта конфигурации-источника, можно использовать объектную модель документов — DOM. Этот механизм позволяет загрузить XML-представление документа в оперативную память, программно добавить туда любые теги и атрибуты, а затем записать результат в итоговый файл обмена.
Разберем по шагам комбинированный алгоритм работы с DOM:
ЗаписатьXML.ДокументDOM.Посмотрим на пример практической реализации этого алгоритма:
// 1. Сериализуем объект во временную строку XML
ЗаписьВременногоXML = Новый ЗаписьXML;
ЗаписьВременногоXML.УстановитьСтроку();
ЗаписатьXML(ЗаписьВременногоXML, Данные);
СтрокаXML = ЗаписьВременногоXML.Закрыть();
// 2. Читаем полученный XML в модель DOM
ЧтениеВременногоXML = Новый ЧтениеXML;
ЧтениеВременногоXML.УстановитьСтроку(СтрокаXML);
Построитель = Новый ПостроительDOM;
ДокументDOM = Построитель.Прочитать(ЧтениеВременногоXML);
ЧтениеВременногоXML.Закрыть();
// 3. Добавляем свой произвольный элемент в структуру DOM
КорневойЭлемент = ДокументDOM.ДокументЭлемент;
НовыйЭлемент = ДокументDOM.СоздатьЭлемент("ИмяМоегоРеквизита");
НовыйЭлемент.ТекстовоеСодержимое = "ЗначениеРеквизита";
КорневойЭлемент.ДобавитьДочерний(НовыйЭлемент);
// 4. Записываем измененный DOM в итоговый поток (хмл)
ЗаписьDOM = Новый ЗаписьDOM;
ЗаписьDOM.Записать(ДокументDOM, хмл);
Этот способ является максимально гибким. Мы получаем полную независимость от метаданных конфигурации, не меняем модули объектов и можем динамически формировать любую структуру XML "на лету". Единственным минусом является повышенное потребление оперативной памяти, так как всё дерево DOM строится в памяти, однако для поштучной обработки документов в цикле обмена это не критично.
В некоторых случаях мы можем отказаться от автоматической сериализации всего объекта целиком и применить комбинированный подход. Если у нас выгружается множество разнородных объектов, мы можем сделать "развилку" в цикле выгрузки. Для обычных объектов мы оставим стандартную сериализацию, а для нашего специфического документа напишем отдельную процедуру ручной записи.
Проанализируем пример кода:
Пока Выборка.Следующий() Цикл
Данные = Выборка.Получить();
Если ТипЗнч(Данные) = Тип("ДокументОбъект.РеализацияТоваровУслуг") Тогда
ВыгрузитьРеализациюСДопПолями(хмл, Данные);
Иначе
ЗаписатьXML(хмл, Данные);
КонецЕсли;
КонецЦикла;
Внутри процедуры ВыгрузитьРеализациюСДопПолями мы вручную открываем тег документа, записываем его стандартные реквизиты, добавляем свои кастомные узлы и закрываем тег. Это выглядит следующим образом:
Процедура ВыгрузитьРеализациюСДопПолями(ЗаписьXML, ДокументОбъект)
// Открываем основной тег документа
ЗаписьXML.ЗаписатьНачалоЭлемента("DocumentObject.РеализацияТоваровУслуг");
// Записываем стандартные реквизиты через ЗаписатьXML, указывая имя тега
ЗаписатьXML(ЗаписьXML, ДокументОбъект.Ссылка, "Ref");
ЗаписатьXML(ЗаписьXML, ДокументОбъект.ПометкаУдаления, "DeletionMark");
ЗаписатьXML(ЗаписьXML, ДокументОбъект.Дата, "Date");
ЗаписатьXML(ЗаписьXML, ДокументОбъект.Номер, "Number");
ЗаписатьXML(ЗаписьXML, ДокументОбъект.Проведен, "Posted");
ЗаписатьXML(ЗаписьXML, ДокументОбъект.Организация, "Организация");
ЗаписатьXML(ЗаписьXML, ДокументОбъект.Контрагент, "Контрагент");
// Внедряем наш произвольный реквизит
ЗаписьXML.ЗаписатьНачалоЭлемента("МойДополнительныйРеквизит");
ЗаписьXML.ЗаписатьТекст("ЗначениеРеквизита");
ЗаписьXML.ЗаписатьКонецЭлемента();
// Записываем табличные части (при необходимости)
// ... здесь может быть цикл по строкам табличных частей ...
// Закрываем основной тег документа
ЗаписьXML.ЗаписатьКонецЭлемента();
КонецПроцедуры
Этот метод гарантирует стопроцентный контроль над структурой результирующего XML-файла. Мы вольны располагать теги в любом удобном порядке, переименовывать их и добавлять сколько угодно кастомных данных. Основной минус — необходимость вручную прописывать выгрузку каждого реквизита, что при изменении метаданных конфигурации потребует ручной актуализации кода.
У начинающих разработчиков часто возникает соблазн использовать для этой задачи стандартное свойство объекта ДополнительныеСвойства, которое имеет тип Структура. Действительно, в эту структуру можно записать любые произвольные данные в оперативной памяти:
ДокументОбъект.ДополнительныеСвойства.Вставить("МойПараметр", "Значение");
Однако важно понимать: свойство ДополнительныеСвойства предназначено исключительно для передачи параметров между различными обработчиками событий (например, из формы в модуль объекта при записи) внутри одной сессии работы. При вызове стандартного метода ЗаписатьXML платформа 1С полностью игнорирует коллекцию ДополнительныеСвойства, и эти данные никогда не попадут в результирующий XML-файл.
Если ваша информационная база работает на современной платформе 1С v8.3 (и не использует режим совместимости с устаревшими версиями), то самым правильным, надежным и безопасным решением будет использование Расширений конфигурации.
В расширении вы можете добавить к любому типовому документу собственный реквизит. При этом основная конфигурация останется полностью типовой и будет без проблем обновляться. Платформа 1С v8.3 на уровне ядра умеет автоматически сериализовать реквизиты, добавленные через расширения. При вызове ЗаписатьXML добавленный реквизит автоматически попадет в выгрузку, а при чтении файла на стороне приемника (где установлено такое же расширение) автоматически запишется в базу данных без написания какого-либо дополнительного кода.
Мы рассмотрели несколько способов решения проблемы. Какой из них выбрать, зависит от конкретных условий вашей задачи: