При формировании печатных форм договоров, коммерческих предложений или актов часто возникает необходимость заполнить готовый шаблон Microsoft Word данными из информационной базы 1С. Разберем основные способы реализации поиска и замены текстовых меток (тегов) через COM-соединение, рассмотрим особенности работы с колонтитулами и проанализируем частые ошибки при взаимодействии с офисными приложениями.
Для стабильной работы программного кода рекомендуется выполнять поиск и замену не через курсор пользователя (Selection), а напрямую через диапазон документа (Range или Content). Это предотвращает сбои при потере документом фокуса или при работе в скрытом режиме.
Рассмотрим пример кода, открывающего внешний файл Word, выполняющего пакетную замену реквизитов и сохраняющего результат:
ИмяФайла = "C:\Work\Договор.docx";
НаименованиеКонтрагента = "ООО ""Торгуем всем""";
НомерДоговора = "№ 125/2026";
Попытка
MSWord = Новый COMОбъект("Word.Application");
MSWord.Visible = Ложь;
MSWord.DisplayAlerts = 0; // Отключаем всплывающие предупреждения
Документ = MSWord.Documents.Open(ИмяФайла);
// Выполняем замену первого маркера в основном тексте
Поиск = Документ.Content.Find;
Поиск.ClearFormatting();
Поиск.Replacement.ClearFormatting();
// Параметры метода Execute:
// Execute(FindText, MatchCase, MatchWholeWord, MatchWildcards, MatchSoundsLike, MatchAllWordForms, Forward, Wrap, Format, ReplaceWith, Replace)
// Значение 2 в последнем параметре означает замену всех вхождений (wdReplaceAll)
Поиск.Execute("[Контрагент]", Ложь, Истина, Ложь, , , Истина, 1, Ложь, НаименованиеКонтрагента, 2);
// Замена следующего маркера
Поиск.Execute("[НомерДоговора]", Ложь, Истина, Ложь, , , Истина, 1, Ложь, НомерДоговора, 2);
Документ.Save();
MSWord.Visible = Истина;
MSWord.Activate();
Исключение
Сообщить("Ошибка обработки документа Word: " + ОписаниеОшибки());
Если Не MSWord = Неопределено Тогда
MSWord.Quit();
КонецЕсли;
КонецПопытки;
Если шаблон хранится в конфигурации как макет типа ActiveDocument или в виде файла шаблона (.dotx), алгоритм выглядит аналогично: мы создаем новый документ на основе шаблона методом Documents.Add и заполняем метки.
В некоторых сценариях разработчики используют объект Selection с явным заданием каждого флага поиска. Посмотрим, как устроен этот подход:
wrdApp = Новый COMОбъект("Word.Application");
wrdDoc = wrdApp.Documents.Open("C:\Work\Шаблон.docx");
wrdDoc.Activate();
wrdApp.Visible = Истина;
wrdApp.Selection.Find.ClearFormatting();
wrdApp.Selection.Find.Replacement.ClearFormatting();
wrdApp.Selection.Find.Text = "Contoso";
wrdApp.Selection.Find.Replacement.Text = "ООО ""Торгуем всем""";
wrdApp.Selection.Find.Forward = Истина;
wrdApp.Selection.Find.Wrap = 1; // wdFindContinue
wrdApp.Selection.Find.Format = Ложь;
wrdApp.Selection.Find.MatchCase = Ложь;
wrdApp.Selection.Find.MatchWholeWord = Ложь;
wrdApp.Selection.Find.MatchWildcards = Ложь;
wrdApp.Selection.Find.MatchSoundsLike = Ложь;
wrdApp.Selection.Find.MatchAllWordForms = Ложь;
// Вызов Execute без строковых параметров, так как они уже заполнены в свойствах
wrdApp.Selection.Find.Execute( , , , , , , , , , , 2);
wrdDoc.Save();
Свойство Документ.Content охватывает только основное тело документа. Если метки расположены в верхнем или нижнем колонтитуле, стандартный поиск их пропустит. Разберем, как обратиться к колонтитулам напрямую:
// Обращение к нижнему колонтитулу первой секции документа
Колонтитул = Документ.Sections(1).Footers(1); // 1 = wdHeaderFooterPrimary
ПоискВКолонтитуле = Колонтитул.Range.Find;
ПоискВКолонтитуле.ClearFormatting();
ПоискВКолонтитуле.Execute("[КолонтитулИнфо]", Ложь, Истина, Ложь, , , Истина, 1, Ложь, "Документ согласован", 2);
Для гарантированной замены текста во всех разделах, сносках и колонтитулах сложного документа проанализируем обход коллекции историй StoryRanges:
Для Каждого ДиапазонИстории Из Документ.StoryRanges Цикл
Пока ДиапазонИстории <> Неопределено Цикл
ДиапазонИстории.Find.Execute("[Метка]", Ложь, Истина, Ложь, , , Истина, 1, Ложь, "Значение", 2);
// Переход к следующему связанному блоку (например, следующему колонтитулу)
Попытка
ДиапазонИстории = ДиапазонИстории.NextStoryRange;
Исключение
ДиапазонИстории = Неопределено;
КонецПопытки;
КонецЦикла;
КонецЦикла;
Selection.Find в новых версиях MS Office (2016, 2019, 2021), если окно Word свернуто, не активировано или находится в фоновом режиме. Для исправления переходите на использование диапазона: Документ.Range().Find или Документ.Content.Find. Также ошибка появляется, если файл заблокирован режимом «Защищенный просмотр» (Protected View) или при открытии появилось модальное системное окно.
Find.Execute и свойство Replacement.Text не принимают значения длиннее 255 символов. Если необходимо вставить длинный фрагмент текста (например, подробные реквизиты, список номенклатуры или условия договора), разберем правильный алгоритм:
// Ищем маркер без указания текста замены
Поиск = Документ.Content.Find;
Поиск.ClearFormatting();
Если Поиск.Execute("[ДлинныйТекст]") Тогда
// При успешном поиске сам диапазон схлопывается до найденного текста
Поиск.Parent.Text = ТекстБолее255Символов;
КонецЕсли;
WINWORD.EXE могут зависать в памяти и блокировать работу пользователей. Кроме того, COM-объекты не работают на серверах под Linux. При необходимости серверного формирования документов рекомендуется формировать файлы на клиенте либо использовать альтернативные механизмы (распаковка архива .docx через объекты ЧтениеZIPФайла и прямая модификация файла word/document.xml).