При интеграции систем или выгрузке отчетности часто возникает необходимость внедрить дополнительные данные в уже сформированный XML-документ. Разработчики сталкиваются с дилеммой: обязательно ли полностью считывать файл, десериализовать его в структуры 1С (соответствия, структуры, таблицы значений), дополнять своими данными и затем заново сериализовать в XML, либо существуют более лаконичные и производительные способы?
Разберем специфику работы с форматом XML и проанализируем основные архитектурные варианты решения этой задачи средствами платформы 1С:Предприятие.
В отличие от обычных текстовых логов или CSV-файлов, к XML-документу нельзя применить стандартный файловый режим дозаписи в конец (append). Это обусловлено строгими требованиями спецификации формата:
Следовательно, задача всегда сводится к изменению структуры документа с его последующей полной перезаписью. Рассмотрим два основных способа выполнения этой операции в платформе 1С.
Для файлов небольшого и среднего объема (до нескольких десятков мегабайт) наиболее удобным и наглядным решением является работа с объектной моделью документа — DOM (Document Object Model). Платформа предоставляет для этого объекты ПостроительDOM, ДокументDOM и ЗаписьDOM.
При таком подходе файл загружается в оперативную память целиком в виде иерархического дерева, что позволяет легко перемещаться по элементам, находить нужные ветки и внедрять новые узлы.
Разберем по шагам алгоритм работы с DOM:
ЧтениеXML.ПостроительDOM считываем поток во внутреннее дерево ДокументDOM.СоздатьЭлемент() и текстовые узлы через СоздатьТекстовыйУзел().ДобавитьДочерний() или ВставитьПеред().ЗаписьXML и сохраняем измененное дерево посредством объекта ЗаписьDOM.Посмотрим на практический пример реализации:
ПутьКФайлу = "C:\Exchange\data.xml";
// Читаем документ в дерево DOM
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ПутьКФайлу);
Построитель = Новый ПостроительDOM;
Документ = Построитель.Прочитать(Чтение);
Чтение.Закрыть();
// Получаем корневой узел или ищем нужную ветку
КорневойУзел = Документ.ЭлементДокумента;
// Создаем новый узел и текстовое значение
НовыйЭлемент = Документ.СоздатьЭлемент("CustomData");
НовыйЭлемент.УстановитьАтрибут("id", "1001");
ТекстовоеЗначение = Документ.СоздатьТекстовыйУзел("Дополнительные сведения");
НовыйЭлемент.ДобавитьДочерний(ТекстовоеЗначение);
// Прикрепляем элемент в структуру дерева
КорневойУзел.ДобавитьДочерний(НовыйЭлемент);
// Сохраняем обновленный документ обратно в файл
Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл(ПутьКФайлу);
ЗаписьDOMДерева = Новый ЗаписьDOM;
ЗаписьDOMДерева.Записать(Документ, Запись);
Запись.Закрыть();
Важное ограничение: данный метод требует значительного объема оперативной памяти, так как DOM-представление XML в памяти может превышать физический размер файла в несколько раз. Если файл занимает сотни мегабайт, попытка загрузить его через ПостроительDOM приведет к ошибке нехватки памяти.
Если файл имеет большой объем или процесс обработки критичен к потреблению памяти, DOM-модель не подходит. В этом случае правильнее применить потоковое чтение и запись с одновременным копированием элементов во временный файл.
Рассмотрим подробнее механизм работы: мы последовательно читаем исходный файл с помощью ЧтениеXML, передаем считываемые узлы в целевой файл через ЗаписьXML.ЗаписатьТекущий(), а при попадании на требуемую позицию временно перехватываем поток и выводим собственные теги.
Разберем по шагам реализацию потокового добавления:
ЧтениеXML.ЗаписьXML.Прочитать().ЗаписатьНачалоЭлемента(), ЗаписатьТекст() и ЗаписатьКонецЭлемента().ЗаписьXML.ЗаписатьТекущий(ЧтениеXML), что обеспечивает максимальную скорость копирования без ручного разбора атрибутов и текстов.ПереместитьФайл().Посмотрим на пример потокового внедрения узла:
ПутьИсходный = "C:\Exchange\large_data.xml";
ПутьВременный = "C:\Exchange\large_data_temp.xml";
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ПутьИсходный);
Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл(ПутьВременный, "UTF-8");
Пока Чтение.Прочитать() Цикл
// Ловим момент перед закрытием корневого элемента
Если Чтение.ТипУзла = ТипУзлаXML.КонецЭлемента И Чтение.Имя = "Root" Тогда
// Вставляем свои узлы
Запись.ЗаписатьНачалоЭлемента("AppendedNode");
Запись.ЗаписатьАтрибут("status", "Active");
Запись.ЗаписатьТекст("Потоковая запись значения");
Запись.ЗаписатьКонецЭлемента();
КонецЕсли;
// Переносим текущий узел в результирующий файл
Запись.ЗаписатьТекущий(Чтение);
КонецЦикла;
Чтение.Закрыть();
Запись.Закрыть();
УдалитьФайлы(ПутьИсходный);
ПереместитьФайл(ПутьВременный, ПутьИсходный);
Еще один альтернативный метод — использование платформенного объекта ПреобразованиеXSL. Он удобен в случаях, когда XML-файл стандартизирован, а логику модификации хочется вынести из программного кода в отдельный декларативный шаблон (identity transform).
В шаблоне XSL описывается правило копирования всех имеющихся тегов как есть, а для определенного пути XPath задается внедрение нового блока. Метод ПреобразованиеXSL.Преобразовать() принимает исходный и результирующий файлы, производя замену без ручного перебора структуры в коде 1С.
Выбор оптимального инструмента зависит от конкретной задачи:
Для небольших XML-документов (файлы настроек, небольшие пакеты обмена) идеальным выбором является объектная модель DOM, так как она предоставляет удобную навигацию по узлам и минимизирует количество кода.
Для объемных выгрузок и регулярных фоновых обменов следует использовать потоковую связку ЧтениеXML и ЗаписьXML с вызовом метода ЗаписатьТекущий(), что гарантирует стабильность работы системы без перегрузки оперативной памяти.