При работе с внешними печатными формами (ВПФ) в 1С, которые взаимодействуют с документом Word через COM-объекты, часто возникает задача автоматической замены текстовых "плейсхолдеров" на реальные значения из базы данных. Однако иногда мы сталкиваемся с ситуацией, когда, несмотря на видимую корректность кода и даже подтверждение успешного поиска отладчиком (метод Find возвращает «Истина»), замена не происходит. Вместо ожидаемых значений в документе остаются исходные параметры. Давайте разберем эту проблему подробнее и выясним, почему так происходит, а также как ее эффективно устранить.
Мы работаем с ВПФ, которая генерирует документ Word, используя макет. В коде 1С происходит обращение к объекту Word.Application, и для замены параметров используются методы объекта Find. Изначальный код, демонстрирующий проблему, может выглядеть следующим образом:
Замена = Док.Content.Find;
Замена.Forward = -1; // Устанавливаем направление поиска (можно установить Истина/Ложь или опустить, если ищем по всему документу)
// Пример первой замены, которая, как сообщается, работает
Замена.Execute("",,,,,,,,,"" + ДанныеОрг.Наим + "",2); // Заменяем что-то на Наименование организации
Если ДанныеОрг.ИННЮл = "123" Тогда
// Пример замены, которая не работает для параметра "Прем"
Замена.Execute("",,,,,,,,,"" + ?(ЗначениеЗаполнено(Прем), Формат(Прем, "ЧГ=")+ "%", "") + "",2);
Иначе
// Вариант, где замена также не происходит
Замена.Execute("",,,,,,,,,"" + "" + "",2);
КонецЕсли;
Word.Selection.EndKey(6); // Перемещаем курсор в конец документа
Word.Visible = Истина; // Делаем Word видимым для отладки
Основная сложность заключалась в том, что в отладчике объект Find возвращал Истина при вызове Execute, но сам текст параметра, например, "Прем", оставался в документе без изменений. При этом некоторые другие параметры, например, "Наименование организации" (предположительно, заменяемые аналогичным образом), успешно выводились.
Когда мы видим, что "выводится сам параметр, без замены", это означает, что объект Find нашел что-то, но это "что-то" не было нашим целевым текстовым плейсхолдером. Отладчик может показывать Истина, если найдено, например, определенное форматирование или просто некий диапазон, но не конкретный текст, который мы хотим заменить.
Как выяснилось, причина кроется в неправильной передаче параметров методу Execute объекта Find. Метод Execute имеет множество параметров, и их корректное использование критически важно для успешного поиска и замены.
Метод Execute имеет следующую сигнатуру (в упрощенном виде для 1С, с указанием позиций наиболее важных параметров):
Find.Execute(FindText, MatchCase, MatchWholeWord, MatchWildcards, MatchSoundsLike, MatchAllWordForms, Forward, Wrap, Format, ReplaceWith, Replace)
Давайте сфокусируемся на наиболее важных для нашей проблемы параметрах:
FindText (первый аргумент): Это тот самый текст, который мы хотим найти в документе. В приведенном выше коде этот аргумент передан как пустая строка (""). Если FindText передан как пустая строка, Word не будет искать конкретный текст, а будет искать только форматирование, либо использовать последнее значение, заданное через свойство Find.Text. В таком случае, даже если Execute возвращает Истина, это означает, что найдено какое-то форматирование, соответствующее предыдущим настройкам объекта Find, но не наш целевой текстовый плейсхолдер. Это ключевой момент, который объясняет, почему поиск "вроде бы" срабатывал, но замены не происходило. Мы должны явно указывать, какой текст ищем.ReplaceWith (десятый аргумент): Это текст, на который будет заменен найденный фрагмент. В исходном коде он передается корректно, содержа значения для замены. Если этот параметр пуст (""), то найденный текст будет удален.Replace (одиннадцатый аргумент): Этот параметр определяет, что делать с найденным текстом. Обычно используются следующие значения (числовые константы):
wdReplaceNone (0): Только найти, не заменять.wdReplaceOne (1): Заменить только первое найденное вхождение.wdReplaceAll (2): Заменить все найденные вхождения. Именно это значение (2) мы видим в исходном коде, что говорит о попытке заменить все плейсхолдеры.В исходном коде мы видим, что для замены как ДанныеОрг.Наим ("Организация"), так и Прем использовался вызов Замена.Execute("",,,,,,,,,"" + ЗначениеДляЗамены + "",2);. Это означает, что FindText был пустой строкой в обоих случаях. Если "Организация" при этом заменялась, то это могло произойти по нескольким причинам:
Find.Text: Возможно, перед вызовом для "Организации" где-то выше по коду, не показанному в сообщении, было явно установлено свойство Замена.Text = "Организация". В этом случае пустой FindText в Execute будет использовать значение из Замена.Text.Find: Объект Find сохраняет свое состояние между вызовами. Если до проблемного места происходил какой-то другой поиск, который корректно устанавливал Find.Text, то последующие вызовы Execute("",...) будут использовать это последнее сохраненное значение. Если же для "Прем" до этого не было корректного поиска, либо Find.Text было сброшено или установлено в некорректное значение, то замена не произойдет.В любом случае, использование пустой строки для FindText является потенциальной ошибкой и лучшей практикой будет явное указание искомого текста.
Для корректной замены параметров нам необходимо явно указывать текст, который мы хотим найти, в первом аргументе метода Execute. Также мы можем установить свойства объекта Find перед вызовом Execute. Рассмотрим оба подхода.
Наиболее прямолинейный способ — передавать искомый текст и текст для замены непосредственно в метод Execute. Для этого необходимо заполнить все параметры метода. Давайте посмотрим, как это будет выглядеть:
// Для замены "Организация" на ДанныеОрг.Наим
// FindText (1), MatchCase (2), MatchWholeWord (3), MatchWildcards (4), MatchSoundsLike (5), MatchAllWordForms (6), Forward (7), Wrap (8), Format (9), ReplaceWith (10), Replace (11)
Замена.Execute("Организация", Ложь, Ложь, Ложь, Ложь, Ложь, Истина, 1, Ложь, ДанныеОрг.Наим, 2);
Если ДанныеОрг.ИННЮл = "123" Тогда
// Для замены "Прем" на вычисленное значение
Замена.Execute("Прем", Ложь, Ложь, Ложь, Ложь, Ложь, Истина, 1, Ложь, ?(ЗначениеЗаполнено(Прем), Формат(Прем, "ЧГ=")+ "%", ""), 2);
Иначе
// Если нужно удалить "Прем" при другом ИНН
Замена.Execute("Прем", Ложь, Ложь, Ложь, Ложь, Ложь, Истина, 1, Ложь, "", 2); // Замена на пустую строку
КонецЕсли;
Здесь мы видим:
"Организация" или "Прем" — это искомый текст. Это критическое изменение.MatchCase) — Ложь (не учитывать регистр символов при поиске).MatchWholeWord) — Ложь (искать текст как часть слова). Если вы хотите заменять только целое слово, установите Истина.Forward) — Истина (искать вперед по документу).Wrap) — 1 (wdFindContinue — продолжить поиск с начала/конца документа, если достигнут его конец/начало).2 (wdReplaceAll — заменить все найденные вхождения).Внимательно просмотрите все параметры и адаптируйте их под свои нужды. Часто достаточно указать FindText, ReplaceWith и Replace, оставляя остальные по умолчанию, но иногда требуется более тонкая настройка, как мы рассмотрим далее.
Более читаемым и контролируемым подходом является предварительная установка свойств объекта Find, а затем вызов Execute с меньшим количеством аргументов, так как многие из них уже будут заданы через свойства. Мы рекомендуем этот подход для лучшей поддерживаемости кода.
Замена = Док.Content.Find;
// Сбросим все настройки предыдущего поиска для чистоты. Это очень важно!
Замена.ClearFormatting();
Замена.Replacement.ClearFormatting();
// Устанавливаем общие параметры поиска
Замена.MatchCase = Ложь; // Не учитывать регистр
Замена.MatchWholeWord = Ложь; // Искать как часть слова (для плейсхолдеров обычно Ложь, если они не окружены пробелами)
Замена.Forward = Истина; // Искать вперед по документу
Замена.Wrap = 1; // wdFindContinue - продолжить поиск по всему документу
// Для замены "Организация" на ДанныеОрг.Наим
Замена.Text = "Организация"; // Явно указываем текст для поиска
Замена.Replacement.Text = ДанныеОрг.Наим; // Явно указываем текст для замены
Замена.Execute(,,,,,,,,,,2); // Вызываем Execute, указывая только Replace = wdReplaceAll
Если ДанныеОрг.ИННЮл = "123" Тогда
// Для замены "Прем"
Замена.Text = "Прем"; // Явно указываем текст для поиска
Замена.Replacement.Text = ?(ЗначениеЗаполнено(Прем), Формат(Прем, "ЧГ=")+ "%", "");
Замена.Execute(,,,,,,,,,,2);
Иначе
// Если нужно удалить "Прем" при другом ИНН
Замена.Text = "Прем"; // Явно указываем текст для поиска
Замена.Replacement.Text = ""; // Заменяем на пустую строку
Замена.Execute(,,,,,,,,,,2);
КонецЕсли;
Этот подход предпочтительнее, так как он делает код более понятным, снижает вероятность ошибок при передаче большого количества необязательных параметров и позволяет более гибко управлять настройками поиска.
Давайте рассмотрим другие моменты, которые могут повлиять на успешность операции поиска и замены в Word из 1С, и которые мы можем учитывать для более надежной работы:
Find и Replacement перед поиском, чтобы избежать таких проблем:
Замена.ClearFormatting();
Замена.Replacement.ClearFormatting();
MatchWholeWord): Если ваш плейсхолдер <МойПараметр>, а в тексте есть текстМойПараметрТекст, то при установке Замена.MatchWholeWord = Истина он не будет найден. Убедитесь, что эта настройка соответствует вашим ожиданиям. В большинстве случаев для замены плейсхолдеров, которые не являются частью других слов (например, [Организация]), MatchWholeWord = Истина будет полезнее, чтобы избежать замены части слова.MatchCase): Если в шаблоне плейсхолдер написан как [ПаРаМеТр], а вы ищете [параметр], то при установке Замена.MatchCase = Истина он не будет найден. Обычно для плейсхолдеров лучше устанавливать Замена.MatchCase = Ложь, если регистр не имеет значения для поиска.MatchWildcards): Если вы используете подстановочные знаки (например, *, ?) в тексте для поиска, необходимо установить Замена.MatchWildcards = Истина. Это позволяет реализовать более сложные сценарии поиска, но требует внимательности к синтаксису.ReplaceWith параметра (обычно около 255 символов). Если вам нужно вставить очень длинный текст, рассмотрите альтернативный подход:
Find.Execute(FindText, ..., wdReplaceNone), где wdReplaceNone означает "только найти").Selection).Word.Selection.Delete()).Word.Selection.TypeText(ДлинныйТекст)).Word.Visible = Истина;. Это позволяет визуально отслеживать процесс замены, видеть, где находится курсор, и понимать, на каком этапе происходит сбой или почему текст не заменяется. После отладки не забудьте вернуть Word.Visible = Ложь; для фоновой работы.Особое внимание стоит уделить тому факту, что метод Execute, когда FindText передается как пустая строка (""), будет использовать значение из свойства Find.Text. Если это свойство ранее не было установлено или было установлено некорректно, то поиск будет неудачным для конкретного текстового плейсхолдера, даже если Execute вернет Истина (например, найдя какое-либо форматирование). Таким образом, мы выяснили, что основная причина большинства подобных проблем — это недопонимание работы первого параметра FindText в методе Execute.
Всегда явно указывайте, что вы ищете, и тогда ваши печатные формы Word из 1С будут работать надежно и предсказуемо.