Почему при заполнении шаблона Word из 1С заменяется только первая метка и как это исправить

Программист 1С v8.3 (Управляемые формы) 1С:Зарплата и Управление Персоналом IT и автоматизация бизнеса
← На главную

При формировании печатных форм и отчетов на основе шаблонов Microsoft Word через COM-соединение разработчики 1С часто сталкиваются с ситуацией, когда метод автозамены отрабатывает только для первого тега (например, #ФИО#), а все последующие вызовы игнорируются, оставляя шаблон незаполненным. Разберем по шагам, почему возникает такое поведение объекта Find в модели MS Word, как правильно настроить параметры поиска и какие альтернативные способы заполнения шаблонов существуют.

Причина проблемы: сжатие диапазона Range

Проанализируем механику работы COM-объекта Word. Когда мы обращаемся к Документ.Content.Find, метод поиска связывается с конкретным диапазоном текста (Range). При успешном поиске и выполнении замены границы этого диапазона схлопываются (сжимаются) до позиции только что замененного фрагмента текста. В результате следующий вызов Замена.Execute() ищет новую подстроку не по всему документу, а исключительно внутри сжатой области или от текущей позиции до конца диапазона без циклического возврата в начало.

Способ 1. Переинициализация объекта Find перед каждой заменой

Самое простое и быстрое решение — заново получать диапазон всего документа 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 = Неопределено;
КонецПопытки;

Способ 2. Использование параметров wdFindContinue и wdReplaceAll

Рассмотрим вызов метода Execute с явной передачей управляющих констант MS Word. Для замены всех совпадений по всему тексту документа используются параметры:

  1. Wrap (wdFindContinue = 1): указывает Word продолжать поиск с начала документа при достижении конца диапазона.
  2. Replace (wdReplaceAll = 2): выполняет замену всех найденных вхождений, а не только первого попавшегося.

Разберем универсальный пример выполнения глобальной замены с правильной передачей параметров:


// Константы Word WdFindWrap и WdReplace
wdFindContinue = 1;
wdReplaceAll   = 2;

ТекстПоиска = "#Должность#";
ТекстЗамены = "Главный бухгалтер";

Документ.Content.Find.Execute(
    ТекстПоиска,     // FindText
    Ложь,            // MatchCase (учитывать регистр)
    Истина,          // MatchWholeWord (только слово целиком)
    Ложь,            // MatchWildcards (подстановочные знаки)
    Ложь,            // MatchSoundsLike
    Ложь,            // MatchAllWordForms
    Истина,          // Forward (поиск вперед)
    wdFindContinue,  // Wrap (продолжать поиск по всему документу)
    Ложь,            // Format
    ТекстЗамены,     // ReplaceWith (строка замены)
    wdReplaceAll     // Replace (заменить все)
);

Способ 3. Замена текста в колонтитулах и фигурах (Shapes)

Важно помнить, что свойство Документ.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);
    КонецЕсли;
КонецЦикла;

Способ 4. Использование закладок (Bookmarks)

Вместо поиска строковых меток в шаблоне Word можно заранее расставить закладки (Bookmarks). Это исключает случайную порчу разметки при автозамене и ускоряет работу.

Посмотрим на пример заполнения через соответствие имен закладок и значений:


ДанныеДляЗаполнения = Новый Соответствие;
ДанныеДляЗаполнения.Вставить("ФИОПолные", "Иванова И.И.");
ДанныеДляЗаполнения.Вставить("Подразделение", "Бухгалтерия");

Для Каждого КлючИЗначение Из ДанныеДляЗаполнения Цикл
    ИмяЗакладки = КлючИЗначение.Ключ;
    Значение    = КлючИЗначение.Значение;
    
    Если Документ.Bookmarks.Exists(ИмяЗакладки) Тогда
        ОбластьЗакладки = Документ.Bookmarks(ИмяЗакладки).Range;
        ОбластьЗакладки.Text = Строка(Значение);
    КонецЕсли;
КонецЦикла;

Критические нюансы при работе с Word через COM

  1. Строгая типизация данных: метод Execute ожидает в параметре ReplaceWith исключительно строковый тип данных. Передача числа, даты или значения Неопределено приведет к ошибке COM. Все значения обязательно форматируйте через Строка() или Формат().
  2. Ограничение длины строки замены (255 символов): метод Find.Execute не может вставить строку длиннее 255 символов. Если необходимо вставить длинный текст, найдите фрагмент через Find.Execute(ИскомыйТекст) без указания ReplaceWith, после чего присвойте значение напрямую через свойство диапазона: Документ.Content.Find.Parent.Text = ДлиннаяСтрока.
  3. Зависание процессов WINWORD.EXE: всегда закрывайте неиспользуемые объекты и очищайте переменные COM (MSWord = Неопределено;) в блоке Исключение или в конце процедуры, чтобы не перегружать память сервера и клиентских рабочих станций.
← На главную