Как заменить текст в фигурах и надписях MS Word из 1С?

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

При автоматизации документооборота в 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 = "Ж";
        КонецЕсли;
    КонецПопытки;
КонецЦикла;

Этот способ действительно решает базовую проблему: текст в фигуре заменяется. Однако в реальных условиях эксплуатации данный код имеет ряд критических недостатков, которые могут испортить верстку документа или привести к потере данных:

  1. Полное уничтожение форматирования. Прямое присвоение значения свойству TextRange.Text полностью перезаписывает все содержимое текстового фрейма. Если в фигуре использовались разные шрифты, цвета, жирное выделение или подчеркивание — все оформление сотрется, и новый текст примет стандартный вид.
  2. Уничтожение соседнего текста. Если внутри фигуры помимо заменяемого маркера (например, [ФИО]) находился статический текст (например, «Директор: [ФИО]»), прямое присвоение затрет всю фразу целиком, оставив только новое значение.
  3. Игнорирование группировок. Если дизайнер макета сгруппировал текстовое поле с другими объектами (линиями, логотипами), тип такой фигуры изменится на «группу». Код не сможет напрямую прочитать ее TextFrame и выдаст ошибку, которую просто подавит пустой блок Исключение.
  4. Пропуск колонтитулов. Довольно часто автофигуры со штампами или рамками согласования размещают в верхних или нижних колонтитулах. Коллекция НовыйДокумент.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();

Используя данный метод, вы гарантируете корректную замену всех параметров в файлах любой сложности, включая сложные дизайнерские бланки, договоры со штампами и многостраничные акты с колонтитулами, сохраняя при этом исходное форматирование документа.

← На главную