Как выполнить точный поиск и замену целых слов в строке 1С, избегая частичных совпадений?

Программист 1С v8.3 (Управляемые формы) Бухгалтерский учет
← На главную

При разработке решений на платформе 1С мы часто сталкиваемся с необходимостью поиска и замены текста в строках. Стандартные функции Найти и СтрЗаменить прекрасно справляются с базовыми задачами, но могут вызвать сложности, когда требуется найти только целое слово, а не его вхождение в составе другого слова. Рассмотрим эту проблему подробнее и выясним, как ее эффективно решить.

Предположим, у нас есть строка назначения платежа, например: "оплата за выполненные работы в [дата] дог n07/18 от [дата] по негарантированному заказу сч n____ от [дата] вт ч ндс 20%". Наша задача — заменить предлог "по" на другое значение, но так, чтобы не затронуть слово "выполненные", в котором также встречается "по".

В чем заключается проблема стандартного поиска?

Используя следующий код:


Для Сч = 1 по ТабДок.ВысотаТаблицы Цикл 
    НазначениеПлатежа = ТабДок.Область(Сч, 2).Текст;
    Для Каждого Элемент Из СловарьФраз Цикл
        Если Найти(НазначениеПлатежа, Элемент.Ключ) Тогда
            НазначениеПлатежа = СтрЗаменить(НазначениеПлатежа, Элемент.Ключ, Элемент.Значение);
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Если Элемент.Ключ содержит "по", функция Найти обнаружит его как в предлоге "по", так и в слове "выполненные". Это приведет к некорректной замене, и вместо "выполненные" мы можем получить, например, "вы[новое значение]лненные". Такой результат, безусловно, не является желаемым.

Первое простое решение: Добавление пробелов

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

Модифицируем условие поиска:


Если Найти(НазначениеПлатежа, " " + Элемент.Ключ + " ") Тогда
    НазначениеПлатежа = СтрЗаменить(НазначениеПлатежа, " " + Элемент.Ключ + " ", " " + Элемент.Значение + " ");
КонецЕсли;

Этот подход успешно решает проблему с такими словами, как "выполненные". Теперь "по" будет найдено только как отдельное слово, окруженное пробелами.

Ограничения простого решения и усложнение задачи

Однако, у простого добавления пробелов есть свои ограничения, которые становятся очевидны при рассмотрении более сложных сценариев:

  1. Слова в начале или конце строки: Если слово "по" находится в самом начале или конце строки ("По договору..." или "...по оплате."), оно не будет найдено, так как перед ним или после него нет пробела.
  2. Пунктуация: Слова, окруженные знаками препинания (например, "по," "по." "по!"), также не будут найдены, так как функция ищет именно пробелы.
  3. Ключи, содержащие пробелы: Если сам Элемент.Ключ включает пробелы (например, "по заказу"), то простое добавление внешних пробелов может не сработать или потребует более сложной логики.

Для решения этих усложненных задач нам потребуется более мощный инструмент.

Наиболее эффективное решение: Регулярные выражения

В современных версиях платформы 1С (начиная с 8.3.23) появилась встроенная поддержка регулярных выражений, что является наиболее гибким и надежным способом для поиска и замены текста по сложным шаблонам. Регулярные выражения позволяют точно определить "границы слова", чтобы найти только целые слова, избегая частичных совпадений.

Принцип работы с регулярными выражениями

Ключевым элементом здесь является границы слова, обозначаемые метасимволом \b. Этот метасимвол соответствует позиции, которая является границей слова. Граница слова — это позиция между символом слова (буква, цифра, подчеркивание) и символом, не являющимся словом (пробел, знак препинания), или началом/концом строки.

Таким образом, для поиска целого слова "по" мы используем шаблон \bпо\b.

Использование регулярных выражений в 1С

В 1С есть встроенные функции для работы с регулярными выражениями:

Рассмотрим пример использования:


// Предположим, у нас есть Соответствие:
// СловарьФраз = Новый Соответствие();
// СловарьФраз.Вставить("по", "согласно");
// СловарьФраз.Вставить("дог n07/18", "Договор №07/18");
// СловарьФраз.Вставить("сч n", "счет №");

Для Сч = 1 по ТабДок.ВысотаТаблицы Цикл 
    НазначениеПлатежа = ТабДок.Область(Сч, 2).Текст;
    
    Для Каждого Элемент Из СловарьФраз Цикл
        // Шаблон для поиска целого слова
        // Важно экранировать специальные символы в ключе, если они могут быть частью регулярного выражения
        // Например, если Элемент.Ключ содержит ".", он будет интерпретирован как любой символ.
        // В 1С нет встроенной функции экранирования для регулярных выражений, ее можно реализовать вручную
        // или использовать общую функцию (например, из БСП).
        
        // Пример упрощенного экранирования (для демонстрации)
        // В реальных условиях требуется более полная функция экранирования.
        ШаблонПоиска = "\b" + СтрЗаменить(Элемент.Ключ, ".", "\.") + "\b"; 
        
        Если СтрНайтиПоРегулярномуВыражению(НазначениеПлатежа, ШаблонПоиска, 1, Истина) Тогда
            // Заменяем все вхождения, соответствующие шаблону
            НазначениеПлатежа = СтрЗаменитьПоРегулярномуВыражению(НазначениеПлатежа, ШаблонПоиска, Элемент.Значение, Истина);
        КонецЕсли;
    КонецЦикла;
    
    // Если нужно обновить текст в ТабДок
    ТабДок.Область(Сч, 2).Текст = НазначениеПлатежа;
КонецЦикла;

Важный момент: экранирование специальных символов. Если ключи в вашем Соответствии могут содержать символы, которые являются специальными в регулярных выражениях (например, ., *, +, ?, (, ), [, ], {, }, \, |, ^, $), их необходимо экранировать, то есть ставить перед ними обратный слеш \. В противном случае регулярное выражение может интерпретировать их как часть шаблона, а не как обычный текст. В 1С нет встроенной функции для экранирования регулярных выражений, поэтому ее приходится реализовывать самостоятельно или использовать готовую из типовых библиотек.

Регулярные выражения для старых версий 1С (до 8.3.23)

Если вы работаете на более старой версии платформы, где встроенных регулярных выражений нет, вы можете использовать компонент VBScript.RegExp через COM-объекты.


// Создаем объект RegExp
Попытка
    РегВыр = Новый COMОбъект("VBScript.RegExp");
Исключение
    Сообщить("Ошибка инициализации объекта VBScript.RegExp. Возможно, он не зарегистрирован в системе.");
    Возврат;
КонецПопытки;

РегВыр.Global = Истина; // Разрешаем глобальный поиск (все вхождения)
РегВыр.IgnoreCase = Истина; // Поиск без учета регистра (опционально)

Для Сч = 1 по ТабДок.ВысотаТаблицы Цикл 
    НазначениеПлатежа = ТабДок.Область(Сч, 2).Текст;
    
    Для Каждого Элемент Из СловарьФраз Цикл
        // Экранирование ключа для RegExp
        // Пример простой функции экранирования (для демонстрации)
        Функция ЭкранироватьДляРегВыр(Строка)
            Возврат СтрЗаменить(Строка, "\", "\\");
        КонецФункции;
        
        ШаблонПоиска = "\b" + ЭкранироватьДляРегВыр(Элемент.Ключ) + "\b";
        РегВыр.Pattern = ШаблонПоиска;
        
        Если РегВыр.Test(НазначениеПлатежа) Тогда
            // Выполняем замену
            НазначениеПлатежа = РегВыр.Replace(НазначениеПлатежа, Элемент.Значение);
        КонецЕсли;
    КонецЦикла;
    
    // Если нужно обновить текст в ТабДок
    ТабДок.Область(Сч, 2).Текст = НазначениеПлатежа;
КонецЦикла;

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

Альтернативные подходы без регулярных выражений (при невозможности их использования)

Если по каким-то причинам (очень старая платформа, отсутствие COM-объектов) регулярные выражения недоступны, а простое добавление пробелов не решает всех проблем, нам придется реализовать более сложную логику, используя комбинацию стандартных строковых функций. Этот подход менее элегантен и более трудоемок, но может быть единственно возможным в определенных условиях.

  1. Расширенный поиск с пробелами и пунктуацией:

    Нам придется проверять не только " пробел + ключ + пробел ", но и другие варианты:

    • Найти(НазначениеПлатежа, Элемент.Ключ + " ") — для слов в начале строки.
    • Найти(НазначениеПлатежа, " " + Элемент.Ключ) — для слов в конце строки.
    • Найти(НазначениеПлатежа, Элемент.Ключ + ","), Найти(НазначениеПлатежа, Элемент.Ключ + ".") и так далее для различных знаков препинания.
    • Отдельная проверка на точное соответствие всей строки, если она может состоять только из одного слова.

    Такой подход быстро усложняет код, становится громоздким и подвержен ошибкам, так как легко упустить какой-либо вариант.

  2. Поиск и обработка по частям:

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

    Пример (упрощенный, без учета пунктуации):

    
    Для Сч = 1 по ТабДок.ВысотаТаблицы Цикл 
        НазначениеПлатежа = ТабДок.Область(Сч, 2).Текст;
        НовоеНазначениеПлатежа = "";
        МассивСлов = СтрРазделить(НазначениеПлатежа, " ", Ложь);
                
        Для Каждого Слово Из МассивСлов Цикл
            НайденоИЗаменено = Ложь;
            Для Каждого Элемент Из СловарьФраз Цикл
                Если ВРег(Слово) = ВРег(Элемент.Ключ) Тогда // Сравнение без учета регистра
                    НовоеНазначениеПлатежа = НовоеНазначениеПлатежа + Элемент.Значение + " ";
                    НайденоИЗаменено = Истина;
                    Прервать;
                КонецЕсли;
            КонецЦикла;
            Если Не НайденоИЗаменено Тогда
                НовоеНазначениеПлатежа = НовоеНазначениеПлатежа + Слово + " ";
            КонецЕсли;
        КонецЦикла;
                
        ТабДок.Область(Сч, 2).Текст = СтрЗаменить(НовоеНазначениеПлатежа, "  ", " "); // Убираем лишние пробелы
    КонецЦикла;
    

    Этот подход также имеет свои недостатки, включая возможные проблемы с многословными ключами в СловарьФраз и необходимостью сложной обработки пунктуации, которая может быть частью слова.

  3. Метод НайтиТекст() для Табличного Документа:

    Если мы говорим о поиске в ТабличномДокументе, существует метод НайтиТекст(). У этого метода есть параметр ЯчейкаЦеликом (Булево). Если установить ЯчейкаЦеликом = Истина, то будет найдено только полное соответствие искомой строки всему тексту ячейки. Однако этот метод применим только для поиска по всей ячейке, а не внутри текста ячейки для отдельных слов, и, следовательно, не решает нашу текущую задачу.

Заключение

Мы выяснили, что для точного поиска и замены целых слов в строке в 1С наиболее надежным и эффективным решением является использование регулярных выражений. Они предоставляют мощный механизм для определения границ слов и сложных шаблонов поиска. В зависимости от версии платформы мы можем использовать встроенные функции СтрНайтиПоРегулярномуВыражению и СтрЗаменитьПоРегулярномуВыражению или COM-объект VBScript.RegExp. Если же использование регулярных выражений невозможно, нам придется прибегнуть к более сложным и менее производительным комбинациям стандартных строковых функций, тщательно обрабатывая все возможные варианты расположения слова в строке и его окружение.

← На главную