При формировании печатных форм и отчетов на основе шаблонов Microsoft Word через COM-соединение разработчики 1С часто сталкиваются с ситуацией, когда метод автозамены отрабатывает только для первого тега (например, #ФИО#), а все последующие вызовы игнорируются, оставляя шаблон незаполненным. Разберем по шагам, почему возникает такое поведение объекта Find в модели MS Word, как правильно настроить параметры поиска и какие альтернативные способы заполнения шаблонов существуют.
Проанализируем механику работы COM-объекта Word. Когда мы обращаемся к Документ.Content.Find, метод поиска связывается с конкретным диапазоном текста (Range). При успешном поиске и выполнении замены границы этого диапазона схлопываются (сжимаются) до позиции только что замененного фрагмента текста. В результате следующий вызов Замена.Execute() ищет новую подстроку не по всему документу, а исключительно внутри сжатой области или от текущей позиции до конца диапазона без циклического возврата в начало.
Самое простое и быстрое решение — заново получать диапазон всего документа Content.Find перед каждой операцией подстановки. В этом случае область поиска каждый раз сбрасывается на весь документ целиком.
Посмотрим на пример исправленного кода:
Справка = ПолучитьМакет("ПФ_DOC_СправкаСМестаРаботы");
MSWord = Справка.Получить();
Попытка
Документ = MSWord.Application.Documents(1);
Документ.Activate();
// 1. Замена ФИО
Замена = Документ.Content.Find;
Замена.Execute("#ФИОПолные#", Ложь, Истина, Ложь, , , Истина, , Ложь, "Иванова И.И.");
// 2. Заново получаем область поиска для следующей метки
Замена = Документ.Content.Find;
Замена.Execute("#ЖенскийПол#", Ложь, Истина, Ложь, , , Истина, , Ложь, "а");
// 3. Замена даты (обязательно преобразуем в строку)
Замена = Документ.Content.Find;
Замена.Execute("#ДатаДок#", Ложь, Истина, Ложь, , , Истина, , Ложь, Формат(ТекущаяДата(), "ДЛФ=ДД"));
MSWord.Application.Visible = Истина;
MSWord.Activate();
Исключение
Сообщить(ОписаниеОшибки());
MSWord.Application.Quit();
MSWord = Неопределено;
КонецПопытки;
Рассмотрим вызов метода Execute с явной передачей управляющих констант MS Word. Для замены всех совпадений по всему тексту документа используются параметры:
Разберем универсальный пример выполнения глобальной замены с правильной передачей параметров:
// Константы Word WdFindWrap и WdReplace
wdFindContinue = 1;
wdReplaceAll = 2;
ТекстПоиска = "#Должность#";
ТекстЗамены = "Главный бухгалтер";
Документ.Content.Find.Execute(
ТекстПоиска, // FindText
Ложь, // MatchCase (учитывать регистр)
Истина, // MatchWholeWord (только слово целиком)
Ложь, // MatchWildcards (подстановочные знаки)
Ложь, // MatchSoundsLike
Ложь, // MatchAllWordForms
Истина, // Forward (поиск вперед)
wdFindContinue, // Wrap (продолжать поиск по всему документу)
Ложь, // Format
ТекстЗамены, // ReplaceWith (строка замены)
wdReplaceAll // Replace (заменить все)
);
Важно помнить, что свойство Документ.Content охватывает только основной текст документа. Если метки расположены в верхних/нижних колонтитулах или внутри графических блоков (текстовых рамок / надписей), поиск Content.Find их не найдет. Для полной обработки документа нужно обойти все секции и коллекции фигур.
Рассмотрим подробнее полный обход элементов документа:
wdFindContinue = 1;
wdReplaceAll = 2;
// 1. Поиск в основном тексте
Документ.Content.Find.Execute(ИскомыйТекст, Истина, Истина, Ложь, Ложь, Ложь, Истина, wdFindContinue, Ложь, ТекстВставки, wdReplaceAll);
// 2. Поиск в колонтитулах каждой секции
Для НомерСекции = 1 По Документ.Sections.Count Цикл
Секция = Документ.Sections(НомерСекции);
// Верхний колонтитул
Секция.Headers(1).Range.Find.Execute(ИскомыйТекст, Истина, Истина, Ложь, Ложь, Ложь, Истина, wdFindContinue, Ложь, ТекстВставки, wdReplaceAll);
// Нижний колонтитул
Секция.Footers(1).Range.Find.Execute(ИскомыйТекст, Истина, Истина, Ложь, Ложь, Ложь, Истина, wdFindContinue, Ложь, ТекстВставки, wdReplaceAll);
КонецЦикла;
// 3. Поиск в текстовых блоках (надписях)
Для НомерФигуры = 1 По Документ.Shapes.Count Цикл
Фигура = Документ.Shapes.Item(НомерФигуры);
// Тип 17 соответствует msoTextBox (текстовая надпись)
Если Число(Фигура.Type) = 17 Тогда
Фигура.TextFrame.TextRange.Find.Execute(ИскомыйТекст, Истина, Истина, Ложь, Ложь, Ложь, Истина, wdFindContinue, Ложь, ТекстВставки, wdReplaceAll);
КонецЕсли;
КонецЦикла;
Вместо поиска строковых меток в шаблоне Word можно заранее расставить закладки (Bookmarks). Это исключает случайную порчу разметки при автозамене и ускоряет работу.
Посмотрим на пример заполнения через соответствие имен закладок и значений:
ДанныеДляЗаполнения = Новый Соответствие;
ДанныеДляЗаполнения.Вставить("ФИОПолные", "Иванова И.И.");
ДанныеДляЗаполнения.Вставить("Подразделение", "Бухгалтерия");
Для Каждого КлючИЗначение Из ДанныеДляЗаполнения Цикл
ИмяЗакладки = КлючИЗначение.Ключ;
Значение = КлючИЗначение.Значение;
Если Документ.Bookmarks.Exists(ИмяЗакладки) Тогда
ОбластьЗакладки = Документ.Bookmarks(ИмяЗакладки).Range;
ОбластьЗакладки.Text = Строка(Значение);
КонецЕсли;
КонецЦикла;
Execute ожидает в параметре ReplaceWith исключительно строковый тип данных. Передача числа, даты или значения Неопределено приведет к ошибке COM. Все значения обязательно форматируйте через Строка() или Формат().Find.Execute не может вставить строку длиннее 255 символов. Если необходимо вставить длинный текст, найдите фрагмент через Find.Execute(ИскомыйТекст) без указания ReplaceWith, после чего присвойте значение напрямую через свойство диапазона: Документ.Content.Find.Parent.Text = ДлиннаяСтрока.MSWord = Неопределено;) в блоке Исключение или в конце процедуры, чтобы не перегружать память сервера и клиентских рабочих станций.