В процессе обмена данными между различными системами, например, при выгрузке из "Мой склад" и загрузке в "1С:Бухгалтерия", часто возникает необходимость предварительно обработать файл обмена. Задача усложняется, если целевая база находится в облачном сервисе "1С:Фреш", где возможности доработки ограничены. Рассмотрим, как можно модифицировать XML-файл формата Enterprise Data, не загружая его целиком в память, изменить нужные поля и сохранить результат для последующей загрузки.
Проблема заключается в том, что файл может быть очень большим, и стандартные методы, такие как полная загрузка в DOM-модель, могут привести к чрезмерному потреблению оперативной памяти. Требуется найти оптимальный способ, который позволит внести изменения (например, поправить номера документов или формат сумм) и при этом будет производительным.
Прежде чем перейти к практическому решению, проанализируем основные методы работы с XML, доступные на платформе 1С, и их применимость к нашей задаче.
ЧтениеXML, ЗаписьXML). Этот подход является самым эффективным с точки зрения использования памяти. Файл читается узел за узлом, что позволяет обрабатывать файлы практически неограниченного размера. Мы можем читать данные из одного файла, модифицировать их "на лету" и сразу записывать в другой. Основной недостаток — сложность кода и необходимость вручную контролировать структуру XML, чтобы не нарушить её валидность. Именно этот метод мы и разберем подробно.В нашей ситуации, когда нужно создать внешнюю обработку-прокладку, которая быстро исправляет данные в файле, метод последовательного чтения и записи является хорошим компромиссом между производительностью и сложностью реализации.
Рассмотрим реализацию внешней обработки, которая читает исходный XML-файл, разбирает его в древовидную структуру в памяти (массив структур), позволяет внести изменения, а затем собирает новый XML-файл из измененной структуры.
Создадим обработку с формой, на которой будет поле для выбора исходного файла и кнопка для запуска обработки. Основная логика будет выполняться на клиенте, так как работа с файлами там наиболее удобна.
Основная процедура-обработчик будет выглядеть следующим образом: она открывает файл для чтения, вызывает функцию разбора, а затем вызывает функцию сборки, записывая результат в новый файл с суффиксом `_res`.
&НаКлиенте
Процедура ОбработатьФайл(Команда)
Ч = Новый ЧтениеXML();
Ч.ОткрытьФайл(ВыбИмяФайла);
КодировкаИсточника = Ч.КодировкаИсточника;
// Разбираем XML в массив структур
Строки = РазобратьXML(Ч);
Ч.Закрыть();
// Здесь будет логика модификации данных в массиве "Строки"
Ф = Новый Файл(ВыбИмяФайла);
РезИмяФайла = Ф.Путь + Ф.ИмяБезРасширения + "_res" + Ф.Расширение;
З = Новый ЗаписьXML;
З.ОткрытьФайл(РезИмяФайла, КодировкаИсточника);
З.ЗаписатьОбъявлениеXML();
// Собираем XML из массива структур
СобратьXML(З, Строки);
З.Закрыть();
КонецПроцедуры
Для разбора XML в удобную структуру напишем рекурсивную функцию РазобратьXML. Она будет обходить все узлы и формировать массив структур. Каждая структура в массиве представляет собой XML-узел и содержит его имя (тег), атрибуты, текстовое содержимое и вложенные узлы (рекурсивный вызов).
&НаКлиенте
Функция РазобратьXML(Ч, Строки = Неопределено, ТекСтрока = Неопределено) Экспорт
Если Строки = Неопределено Тогда
Строки = Новый Массив();
КонецЕсли;
Пока Ч.Прочитать() Цикл
Если Ч.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
// Создаем структуру для нового элемента
ТекСтрока = Новый Структура("Тег, Тип, Тело", Ч.Имя);
ТекСтрока.Вставить("Атрибуты", Новый Соответствие());
ТекСтрока.Вставить("Строки", Новый Массив());
Строки.Добавить(ТекСтрока);
// Читаем все атрибуты элемента
Если Ч.КоличествоАтрибутов() > 0 Тогда
Пока Ч.ПрочитатьАтрибут() Цикл
ТекСтрока.Атрибуты.Вставить(Ч.Имя, Ч.Значение);
КонецЦикла
КонецЕсли;
// Рекурсивно вызываем разбор для дочерних элементов
РазобратьXML(Ч, ТекСтрока.Строки, ТекСтрока);
ИначеЕсли Ч.ТипУзла = ТипУзлаXML.Текст Тогда
// Записываем текстовое содержимое тега
ТекСтрока.Тело = Ч.Значение;
ИначеЕсли Ч.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
// При закрытии тега выходим из рекурсии на уровень выше
Возврат Неопределено;
ИначеЕсли Ч.ТипУзла = ТипУзлаXML.ИнструкцияОбработки Тогда
ТекСтрока.Тело = Ч.Значение;
КонецЕсли;
КонецЦикла;
Возврат Строки;
КонецФункции
Функция СобратьXML выполняет обратную операцию. Она рекурсивно обходит нашу структуру данных (массив структур) и с помощью объекта ЗаписьXML формирует итоговый файл.
&НаКлиенте
Функция СобратьXML(З, Строки)
Для Каждого ТекСтрока ИЗ Строки Цикл
Если ТекСтрока.Тип = Неопределено Тогда
// Записываем начало элемента
З.ЗаписатьНачалоЭлемента(ТекСтрока.Тег);
// Записываем все атрибуты
Для Каждого ЭлАтрибут ИЗ ТекСтрока.Атрибуты Цикл
З.ЗаписатьАтрибут(ЭлАтрибут.Ключ, ЭлАтрибут.Значение);
КонецЦикла;
// Записываем текстовое содержимое
Если ТекСтрока.Тело <> Неопределено Тогда
З.ЗаписатьТекст(ТекСтрока.Тело);
КонецЕсли;
// Рекурсивно собираем дочерние элементы
СобратьXML(З, ТекСтрока.Строки);
// Записываем конец элемента
З.ЗаписатьКонецЭлемента();
КонецЕсли;
КонецЦикла;
КонецФункции
Теперь, когда у нас есть функции для разбора и сборки, мы можем вставить логику обработки между ними. Например, нам нужно исправить формат сумм, которые выгружаются из "Мой склад" с избыточной точностью.
Создадим процедуру, которая будет обходить полученное дерево и вносить изменения. Вот пример кода, который форматирует значения в тегах, связанных с суммами, до двух знаков после запятой:
// Этот код нужно вставить в процедуру ОбработатьФайл
// после вызова РазобратьXML(Ч)
// ...
Строки = РазобратьXML(Ч);
Ч.Закрыть();
// Вызываем обработку данных
ОбработатьДанные(Строки);
Ф = Новый Файл(ВыбИмяФайла);
// ...
// ...
&НаКлиенте
Процедура ОбработатьДанные(Строки)
Для каждого Строка Из Строки Цикл
Тег = Строка.Тег;
// Обрабатываем неправильный формат сумм
Если Тег = "СуммаВозврата" ИЛИ Тег = "СуммаНДС" ИЛИ Тег = "Себестоимость" Тогда
Значение = Строка.Тело;
Если ЗначениеЗаполнено(Значение) Тогда
Поз = Найти(Значение, ".");
Если Поз <> 0 И Поз < СтрДлина(Значение) - 2 Тогда
НовоеЗначение = Формат(Число(Значение), "ЧДЦ=2; ЧРД=.; ЧН=0; ЧГ=");
Строка.Тело = НовоеЗначение;
Сообщить("Изменено значение тега: " + Тег + " с: " + Значение + " на: " + НовоеЗначение);
КонецЕсли;
КонецЕсли;
КонецЕсли;
// Рекурсивный вызов для дочерних элементов
Если Строка.Строки.Количество() > 0 Тогда
ОбработатьДанные(Строка.Строки);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Хотя предложенный метод работает, важно помнить о его "подводных камнях".
<ed:Контейнер>), их нужно обрабатывать отдельно, читая и записывая с помощью методов ЧтениеXML.Префикс и ЗаписьXML.ЗаписатьНачалоЭлемента(Имя, URIПространстваИмен). Механизм XDTO делает это автоматически.ЗаписатьОбъявлениеXML() может не записывать некоторые атрибуты, например, standalone="yes". Кроме того, 1С при записи в кодировке UTF-8 часто добавляет в начало файла маркер BOM, который может вызывать ошибки в некоторых системах (например, написанных на PHP). Если требуется полный контроль над заголовком, можно использовать метод ЗаписатьБезОбработки():
// Вместо З.ЗаписатьОбъявлениеXML();
З.ЗаписатьБезОбработки("<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>" + Символы.ВК + Символы.ПС);
В заключение, ручной разбор и сборка XML — это мощный инструмент для решения задач быстрой модификации файлов, особенно в условиях ограничений, как с "1С:Фреш". Он обеспечивает высокую производительность при работе с большими объемами данных. Однако для построения надежных и долгосрочных интеграционных решений всегда стоит в первую очередь рассматривать использование платформенного механизма XDTO, который обеспечивает строгий контроль над структурой и типами данных.