При автоматизации документооборота в 1С разработчики часто сталкиваются с необходимостью автоматического заполнения шаблонов Microsoft Word. Традиционный подход, основанный на поиске и замене текстовых маркеров (например, [НомерДоговора] или {Контрагент}), отлично работает для основного тела документа. Однако, как только маркер попадает внутрь графического объекта — автофигуры, текстовой рамки (надписи) или штампа — стандартный алгоритм поиска его игнорирует. Рассмотрим подробнее, почему это происходит, разберем причины такого поведения объектной модели MS Word и выработаем надежный, отказоустойчивый алгоритм для решения этой задачи.
При использовании интеграции через COM-соединение стандартный код замены текста обычно выглядит следующим образом:
Попытка
COMОбъект.Documents.Open2000(ИмяФайлаДоговора);
Исключение
COMОбъект.Quit(0);
COMОбъект = 0;
УдалитьФайлы(ИмяФайлаДоговора);
КонецПопытки;
Попытка
НовыйДокумент = COMОбъект.Application.Documents(1);
НовыйДокумент.Activate();
Замена = НовыйДокумент.Content.Find;
Замена.Execute("фыва", Ложь, Истина, Ложь, , , Истина, , Ложь, "Ж");
Исключение
COMОбъект.Application.Quit();
Сообщить(ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
Проанализируем этот фрагмент кода. Объект НовыйДокумент.Content возвращает текстовый диапазон, который охватывает только основную историю документа (Main Story). Сюда входит обычный текст абзацев и таблиц. Но текстовые поля, надписи и автофигуры находятся на другом логическом уровне разметки Word. Они принадлежат коллекции Shapes (фигуры). По этой причине метод Find.Execute, вызванный от лица Content, физически «не видит» то, что написано внутри графических рамок.
Для того чтобы добраться до текста внутри автофигур, нам необходимо явным образом обратиться к коллекции Shapes активного документа. Простой вариант решения заключается в переборе всех элементов коллекции и прямой перезаписи свойства текста. Рассмотрим пример такого кода:
МаксИндекс = НовыйДокумент.Shapes.Count;
Для Индекс = 1 По МаксИндекс Цикл
Попытка
Найден = НовыйДокумент.Shapes.Item(Индекс).Select();
Если СокрЛП(НовыйДокумент.Shapes.Item(Индекс).TextFrame.TextRange.Text) = "фыва" Тогда
НовыйДокумент.Shapes.Item(Индекс).TextFrame.TextRange.Text = "Ж";
КонецЕсли;
КонецПопытки;
КонецЦикла;
Этот способ действительно решает базовую проблему: текст в фигуре заменяется. Однако в реальных условиях эксплуатации данный код имеет ряд критических недостатков, которые могут испортить верстку документа или привести к потере данных:
TextRange.Text полностью перезаписывает все содержимое текстового фрейма. Если в фигуре использовались разные шрифты, цвета, жирное выделение или подчеркивание — все оформление сотрется, и новый текст примет стандартный вид.[ФИО]) находился статический текст (например, «Директор: [ФИО]»), прямое присвоение затрет всю фразу целиком, оставив только новое значение.TextFrame и выдаст ошибку, которую просто подавит пустой блок Исключение.НовыйДокумент.Shapes их не содержит, и замена там не произойдет.Для создания промышленного решения нам необходимо написать рекурсивную процедуру, которая будет учитывать структуру документа Word. Проанализируем ключевые доработки, которые мы внедрим в наш алгоритм:
1. Точечная замена через метод Find.Execute
Вместо жесткой перезаписи текста мы будем вызывать метод поиска Find.Execute непосредственно у диапазона TextRange конкретной фигуры. Это позволит заменить только искомую подстроку, полностью сохранив исходное форматирование окружающего текста.
2. Проверка наличия текста (HasText)
Чтобы избежать лишних исключений при обращении к объектам, не поддерживающим текст (например, к обычным линиям или картинкам), мы будем проверять свойство TextFrame.HasText. Значение -1 в COM-модели Word соответствует логическому значению Истина.
3. Рекурсивный обход сгруппированных объектов
Если фигура является группой (свойство Type возвращает системную константу 6, соответствующую типу msoGroup), мы должны обойти внутреннюю коллекцию элементов группы GroupItems, вызывая нашу процедуру рекурсивно.
4. Обработка всех секций и колонтитулов
Мы обойдем коллекцию секций документа (Sections) и обработаем фигуры, привязанные к верхним (Headers) и нижним (Footers) колонтитулам каждой секции.
Ниже представлен готовый к использованию программный код на языке 1С, реализующий описанный подход:
// Рекурсивная процедура для поиска и точечной замены текста внутри фигуры
Процедура ЗаменитьТекстВФигуре(Фигура, ЧтоИскать, НаЧтоЗаменить) Экспорт
// 1. Если это группа объектов (тип msoGroup = 6) - рекурсивно обрабатываем элементы
Если Фигура.Type = 6 Тогда
Для ИндексГруппы = 1 По Фигура.GroupItems.Count Цикл
ПодФигура = Фигура.GroupItems.Item(ИндексГруппы);
ЗаменитьТекстВФигуре(ПодФигура, ЧтоИскать, НаЧтоЗаменить);
КонецЦикла;
Возврат;
КонецЕсли;
// 2. Обрабатываем только те фигуры, которые содержат текст
Попытка
Если Фигура.TextFrame.HasText = -1 Тогда
ДиапазонТекста = Фигура.TextFrame.TextRange;
// Используем метод Find непосредственно для диапазона фигуры.
// Параметр Replace = 2 означает "wdReplaceAll" (заменить все вхождения).
ДиапазонТекста.Find.Execute(
ЧтоИскать, // FindText
Ложь, // MatchCase
Истина, // MatchWholeWord
Ложь, // MatchWildcards
Ложь, // MatchSoundsLike
Ложь, // MatchAllWordForms
Истина, // Forward
, // Wrap
Ложь, // Format
НаЧтоЗаменить, // ReplaceWith
2 // Replace
);
КонецЕсли;
Исключение
// Исключение предотвращает аварийное завершение при специфических ограничениях COM-объекта
КонецПопытки;
КонецПроцедуры
// Основная процедура, управляющая процессом замены во всем документе
Процедура ВыполнитьЗаменуВДокументе(НовыйДокумент, ИскомыйТекст, ТекстЗамены) Экспорт
// Шаг А. Замена в фигурах основного тела документа
Для Индекс = 1 По НовыйДокумент.Shapes.Count Цикл
Фигура = НовыйДокумент.Shapes.Item(Индекс);
ЗаменитьТекстВФигуре(Фигура, ИскомыйТекст, ТекстЗамены);
КонецЦикла;
// Шаг Б. Замена в фигурах, находящихся в колонтитулах всех секций
Для Каждого Секция Из НовыйДокумент.Sections Цикл
// Обрабатываем верхние колонтитулы
Для Каждого Колонтитул Из Секция.Headers Цикл
Для Индекс = 1 По Колонтитул.Shapes.Count Цикл
ФигураКолонтитула = Колонтитул.Shapes.Item(Индекс);
ЗаменитьТекстВФигуре(ФигураКолонтитула, ИскомыйТекст, ТекстЗамены);
КонецЦикла;
КонецЦикла;
// Обрабатываем нижние колонтитулы
Для Каждого Колонтитул Из Секция.Footers Цикл
Для Индекс = 1 По Колонтитул.Shapes.Count Цикл
ФигураКолонтитула = Колонтитул.Shapes.Item(Индекс);
ЗаменитьТекстВФигуре(ФигураКолонтитула, ИскомыйТекст, ТекстЗамены);
КонецЦикла;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Для интеграции этого алгоритма в вашу рабочую обработку достаточно передать в процедуру ВыполнитьЗаменуВДокументе ссылку на COM-объект открытого документа MS Word, строку поиска и новое значение.
Рассмотрим пример вызова функции при формировании печатной формы договора:
// Инициализация COM-соединения
Попытка
WordПриложение = Новый COMОбъект("Word.Application");
Исключение
ВызватьИсключение "Не удалось запустить MS Word. Проверьте установку приложения.";
КонецПопытки;
WordПриложение.Visible = Ложь; // Скрываем окно программы для ускорения работы
Попытка
Документ = WordПриложение.Documents.Open(ПутьКФайлуШаблона);
// Выполняем замену стандартных реквизитов в теле документа
ЗаменаВТеле = Документ.Content.Find;
ЗаменаВТеле.Execute("[НомерДоговора]", Ложь, Истина, , , , Истина, , Ложь, "123-ФЗ", 2);
// Выполняем точечную замену в надписях, автофигурах и колонтитулах
ВыполнитьЗаменуВДокументе(Документ, "[Исполнитель_Подпись]", "Иванов И.И.");
ВыполнитьЗаменуВДокументе(Документ, "[Согласовано_Дата]", "25.10.2023");
Документ.SaveAs(ПутьКНовомуФайлу);
Документ.Close();
Исключение
Сообщить("Произошла ошибка при заполнении шаблона: " + ОписаниеОшибки());
КонецПопытки;
WordПриложение.Quit();
Используя данный метод, вы гарантируете корректную замену всех параметров в файлах любой сложности, включая сложные дизайнерские бланки, договоры со штампами и многостраничные акты с колонтитулами, сохраняя при этом исходное форматирование документа.