Работа с XML-файлами — одна из базовых задач при интеграции систем или манипуляции современными офисными форматами, такими как .docx (OpenXML). Часто возникает необходимость не просто прочитать данные, а точечно модифицировать структуру: удалить конкретный узел, основываясь на значении его атрибута, сохранив при этом остальную структуру документа в неизменном виде. Проанализируем ситуацию, когда стандартные методы XDTO не подходят из-за особенностей именования атрибутов, и разберем несколько эффективных способов решения задачи.
Метод с использованием объектной модели документа (DOM) считается наиболее правильным с точки зрения архитектуры XML. Он позволяет загрузить весь файл в память, найти нужные узлы с помощью мощного языка запросов XPath и удалить их. Рассмотрим этот процесс по шагам. Разобраться в тонкостях составления запросов поможет руководство по работе с XPath в 1С.
Ключевой сложностью здесь является наличие пространств имен (Namespaces). В файлах связей Microsoft Word (.rels) всегда определено пространство имен по умолчанию. Если его не учесть, запрос XPath вернет пустой результат. Выясним, как правильно настроить поиск:
// 1. Читаем файл в ДокументDOM
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ПутьКФайлу);
ПостроительDOM = Новый ПостроительDOM;
ДокументDOM = ПостроительDOM.Прочитать(Чтение);
Чтение.Закрыть();
// 2. Создаем менеджер пространств имен для XPath
МенеджерПЗ = Новый МенеджерПространствИменDOM(ДокументDOM);
// Добавляем префикс для пространства имен OpenXML Relationships
МенеджерПЗ.ДобавитьСоответствие("rel", "http://schemas.openxmlformats.org/package/2006/relationships");
// 3. Выполняем XPath запрос для поиска узлов по атрибуту Type
ТипДляУдаления = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";
ЗапросXPath = "/rel:Relationships/rel:Relationship[@Type='" + ТипДляУдаления + "']";
РезультатXPath = ДокументDOM.ВычислитьВыражениеXPath(ЗапросXPath, ДокументDOM, МенеджерПЗ);
// 4. Удаляем найденные узлы
УзелДляУдаления = РезультатXPath.ПолучитьСледующий();
Пока УзелДляУдаления <> Неопределено Цикл
Родитель = УзелДляУдаления.РодительскийУзел;
Родитель.УдалитьДочерний(УзелДляУдаления);
УзелДляУдаления = РезультатXPath.ПолучитьСледующий();
КонецЦикла;
// 5. Записываем результат обратно
Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл(ПутьКФайлу);
ЗаписьDOM = Новый ЗаписьDOM;
ЗаписьDOM.Записать(ДокументDOM, Запись);
Запись.Закрыть();
Важный момент: использование DOM требует значительных ресурсов оперативной памяти, если XML-файл очень велик (десятки и сотни мегабайт). Однако для конфигурационных файлов и файлов связей .rels этот метод идеален.
Если файл слишком большой или необходимо обеспечить максимальную производительность, проанализируем потоковый метод. Мы будем одновременно читать исходный файл и записывать его копию, пропуская тот узел, который нам не нужен. Рассмотрим, как решить главную проблему этого метода — "склеивание" пробелов и превращение тегов с xml:space="preserve" в пустые самозакрывающиеся теги.
Чтобы сохранить исходное форматирование и пробелы (особенно важно для текстовых узлов w:t в Word), необходимо установить свойство ИгнорироватьПробелы в значение Ложь.
Чтение = Новый ЧтениеXML;
// Критически важная настройка для сохранения структуры пробелов!
Чтение.Параметры.ИгнорироватьПробелы = Ложь;
Чтение.ОткрытьФайл(ПутьИсходный);
Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл(ПутьНовый);
Пока Чтение.Прочитать() Цикл
// Проверяем, является ли текущий узел тем самым элементом Relationship
Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.ЛокальноеИмя = "Relationship" Тогда
// Проверяем значение атрибута Type
ЗначениеАтрибута = Чтение.ПолучитьАтрибут("Type");
Если ЗначениеАтрибута = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Тогда
// Пропускаем этот узел и все его содержимое
Чтение.Пропустить();
Продолжить;
КонецЕсли;
КонецЕсли;
// Записываем все остальные узлы без изменений
Запись.ЗаписатьТекущий(Чтение);
КонецЦикла;
Запись.Закрыть();
Чтение.Закрыть();
Этот способ гарантирует, что программа не "упадет" с ошибкой нехватки памяти, так как файл обрабатывается небольшими порциями (буферизированно). Для реализации подобного алгоритма можно применить обработку для замены данных в XML через запись во временный файл.
В некоторых случаях, когда структура XML проста и предсказуема (например, каждый узел Relationship занимает ровно одну строку), можно прибегнуть к текстовой обработке с помощью регулярных выражений. Это "быстрый и грязный" способ, который работает крайне оперативно, но требует осторожности.
Посмотрим на пример функции, использующей COM-объект для поиска и удаления строки по шаблону:
Функция УдалитьУзелТекстом(ПутьКФайлу, ПаттернПоиска)
ТекстДок = Новый ТекстовыйДокумент;
ТекстДок.Прочитать(ПутьКФайлу);
ВесьТекст = ТекстДок.ПолучитьТекст();
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина;
RegExp.Global = Истина;
RegExp.MultiLine = Истина;
// Шаблон ищет тег Relationship, содержащий внутри себя искомый тип
RegExp.Pattern = "<Relationship[^>]+Type=""[^""]*footer[^""]*""[^>]*\/>";
Результат = RegExp.Replace(ВесьТекст, "");
ТекстДок.УстановитьТекст(Результат);
ТекстДок.Записать(ПутьКФайлу);
КонецФункции
Внимание: данный метод привязан к ОС Windows из-за использования VBScript.RegExp. Если ваш сервер 1С работает под управлением Linux, этот код не будет функционировать. Кроме того, регулярные выражения могут некорректно сработать, если в атрибутах используются переносы строк или нестандартное экранирование.
Выясним причины выбора того или иного метода в зависимости от контекста задачи:
МенеджерПространствИменDOM.ИгнорироватьПробелы = Ложь..rels недостаточно. Вам также потребуется найти и удалить ссылки на эти rId в основном файле document.xml, иначе Word сообщит об ошибке при открытии файла.Использование связки ЧтениеXML и ЗаписьXML является наиболее универсальным решением для платформы 1С:Предприятие 8.3, обеспечивая баланс между производительностью и гибкостью управления форматом вывода.