При работе с текстовыми данными в 1С часто возникает задача поиска специфических идентификаторов — например, номеров заказов или артикулов, которые состоят из строго определенного количества цифр (в нашем случае 6 или 10). При этом важно исключить попадание в выборку чисел, перед которыми стоят буквы, но разрешить специальные символы, такие как # или №. Реализация такой задачи напрямую зависит от того, какой движок регулярных выражений используется в вашей конфигурации.
Разберем подробно все варианты решения, проанализируем ограничения популярных методов и выясним, как составить идеальный паттерн.
Рассмотрим условия: нам нужно найти в строке последовательности из 6 или 10 цифр. Перед числом не должно быть букв (ни латинских, ни кириллических). Однако, если перед числом стоит пробел, начало строки или символы #, №, то такое число должно попасть в результат.
Критически важный момент: при поиске альтернатив (6 или 10 цифр) всегда следует указывать сначала более длинный вариант. В регулярных выражениях квантификаторы работают по принципу первого совпадения в альтернативной группе. Если написать \d{6}|\d{10}, то в строке "1234567890" движок найдет первые 6 цифр и остановится, проигнорировав оставшиеся 4. Поэтому правильный порядок: \d{10}|\d{6}.
Объект VBScript.RegExp является самым доступным, так как он встроен в Windows. Однако у него есть существенный недостаток: он не поддерживает «ретроспективную проверку» (lookbehind), то есть конструкции вида (?<!...). Если вы попытаетесь использовать паттерн из тестов в интернете в этом объекте, 1С выдаст ошибку или не найдет совпадений.
Чтобы обойти это ограничение, мы будем использовать группы захвата. Вместо того чтобы проверять отсутствие букв «за спиной» у числа, мы опишем, что может стоять перед числом (либо начало строки, либо не-буква), и захватим само число во вторую группу.
Рассмотрим программный код:
// Создаем COM-объект для работы с регулярными выражениями
РегВыр = Новый COMОбъект("VBScript.RegExp");
РегВыр.MultiLine = Истина;
РегВыр.Global = Истина;
РегВыр.IgnoreCase = Истина;
// Паттерн: (^|[^a-zа-яё]) - первая группа: начало строки или любой символ НЕ буква
// (\d{10}|\d{6}) - вторая группа: искомые цифры
// (?!\d) - негативная опережающая проверка: после числа не должно быть цифр
РегВыр.Pattern = "(^|[^a-zа-яё])(\d{10}|\d{6})(?!\d)";
ВхСтрока = "Заказ #009931, а вот a009931 игнорируем. Новый №0123456789 и 209931qq";
Matches = РегВыр.Execute(ВхСтрока);
Для Каждого Match Из Matches Цикл
// Так как у нас есть группы захвата, само число находится в SubMatches(1)
// Индекс 0 — это вся найденная подстрока с символом-префиксом
// Индекс 1 — это только наши цифры
Если Match.SubMatches.Count > 1 Тогда
Результат = Match.SubMatches(1);
Сообщить("Найдено число: " + Результат);
КонецЕсли;
КонецЦикла;
В этом примере мы используем исключающий класс символов [^a-zа-яё]. Это гарантирует, что если перед числом стоит любая буква, совпадение не будет найдено. При этом символы # и № не являются буквами и корректно обрабатываются движком.
Многие разработчики используют внешние компоненты, такие как AddIn.Component.RegEx. Они обычно базируются на движке PCRE или ICU, которые гораздо мощнее стандартного VBScript. Здесь мы можем использовать более элегантные конструкции, включая проверку границ слова \b.
Проанализируем ситуацию с границами слова. Символ \b срабатывает на границе между "словесным" (буквы, цифры) и "несловесным" символом. Поскольку нам нужно разрешить спецсимволы перед цифрами, использование простого \b\d{6} может не сработать, если перед цифрой стоит #, так как переход от # к цифре не всегда считается границей слова в зависимости от реализации.
Посмотрим на пример кода с использованием внешней компоненты:
Попытка
Рег = Новый("AddIn.Component.RegEx");
Исключение
Сообщить("Компонента RegEx не установлена");
Возврат;
КонецПопытки;
Рег.ВсеСовпадения = Истина;
// Используем паттерн с необязательным не-буквенным символом впереди
// и условием отсутствия буквы перед последовательностью цифр
Шаблон = "(?<![a-zA-Zа-яА-ЯёЁ])(\d{10}|\d{6})(?!\d)";
Рег.НайтиСовпадения(Текст, Шаблон, Истина);
Пока Рег.Следующий() Цикл
// Получаем значение основной группы
Значение = Рег.ПолучитьПодгруппу(0);
Сообщить("Найдено: " + Значение);
КонецЦикла;
Если ваша компания использует современную версию платформы (начиная с 8.3.23), вам больше не нужны COM-объекты или внешние компоненты. Фирма 1С добавила штатную поддержку регулярных выражений на базе современного движка ICU.
Рассмотрим, как реализовать нашу задачу максимально эффективно в новых версиях:
СтрНайтиВсеПоРегулярномуВыражению().РезультатПоискаПоРегулярномуВыражению.
// В штатном движке ICU конструкции (?<!...) работают идеально
// Паттерн говорит: найти 10 или 6 цифр, перед которыми НЕТ буквы
// и после которых НЕТ цифры.
Шаблон = "(?<![a-zA-Zа-яА-ЯёЁ])(\d{10}|\d{6})(?!\d)";
МассивРезультатов = СтрНайтиВсеПоРегулярномуВыражению(ВхСтрока, Шаблон);
Для Каждого ТекущийРезультат Из МассивРезультатов Цикл
Сообщить("Найдено значение: " + ТекущийРезультат.Значение);
КонецЦикла;
Проанализируем ситуацию автора: (?<!\d)\d{6}(?!\d).
Эта регулярка говорит: "Найди 6 цифр, перед которыми нет цифры".
Проблема: Она разрешает буквы! Если в тексте встретится a009931, движок увидит, что перед 009931 стоит буква a. Буква — это не цифра, значит условие (?<!\d) выполнено, и число будет захвачено. Именно поэтому в результат попадали строки с буквами.
Чтобы исключить буквы, нужно явно указать их в негативной проверке: (?<![a-zA-Zа-яА-ЯёЁ]). Это означает: "перед числом не должно быть символа, входящего в алфавитный диапазон".
Подведем итог и выделим ключевые правила работы с регулярными выражениями в 1С для данной задачи:
(А|Б) всегда ставьте сначала более длинный шаблон (10 цифр перед 6-ю).(^|[^а-я]) или (?<![а-я]).SubMatches.Применяя эти подходы, вы сможете гибко настраивать поиск данных любой сложности, игнорируя ненужный контекст и точно выделяя целевые значения.