Как искать данные с помощью регулярных выражений до определенного слова-маркера

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

При работе с большими текстовыми данными (логами, выгрузками в формате JSON или XML, сложными текстовыми отчетами) часто возникает задача извлечь информацию не из всего документа, а только из его определенной части. Например, нам нужно найти все идентификаторы ГУИД (GUID), которые перечисляются в заголовке файла, но проигнорировать их появление в основной части документа после слова-разделителя shopItems.

В этой статье мы подробно разберем, как решить эту задачу в среде 1С, используя объект VBScript.RegExp. Мы проанализируем как простые и надежные способы манипуляции строками, так и продвинутые техники построения самих регулярных выражений.

Метод 1: Предварительная обрезка строки (самый надежный способ)

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

Разберем алгоритм по шагам:

  1. Находим позицию искомого слова-маркера (например, "shopItems") в исходном тексте.
  2. Обрезаем исходную строку, оставляя только ту часть, которая идет до этого слова.
  3. Применяем стандартное регулярное выражение для поиска ГУИДов к полученному фрагменту.

Посмотрим на пример кода, реализующий этот подход:


// Исходный текст
ТекстДляПоиска = "GUID1-..., GUID2-..., shopItems, GUID1-..., GUID3-...";
Маркер = "shopItems";

// Определяем позицию маркера
ПозицияМаркера = Найти(ТекстДляПоиска, Маркер);

Если ПозицияМаркера > 0 Тогда
    // Обрезаем строку до маркера
    РабочийТекст = Лев(ТекстДляПоиска, ПозицияМаркера - 1);
Иначе
    // Если маркер не найден, решаем по логике задачи (искать везде или не искать совсем)
    РабочийТекст = ТекстДляПоиска;
КонецЕсли;

// Настраиваем регулярное выражение
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина;
RegExp.Global = Истина;
RegExp.MultiLine = Истина;
RegExp.Pattern = "[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}";

// Получаем коллекцию совпадений
Matches = RegExp.Execute(РабочийТекст);
Для Каждого Match Из Matches Цикл
    Сообщить("Найден ГУИД: " + Match.Value);
КонецЦикла;

Метод 2: Использование «Просмотра вперед» (Positive Lookahead)

Если мы хотим решить задачу исключительно средствами регулярных выражений, нам потребуется конструкция Positive Lookahead (просмотр вперед). Это специальная «незахватывающая» проверка. Она говорит движку: «Найди мне шаблон А, но только если где-то дальше по тексту встречается шаблон Б».

Синтаксис этой конструкции: (?=...). Выясним причину, почему обычная точка . может не сработать в VBScript.RegExp. В этом движке точка по умолчанию не включает в себя символы переноса строки. Чтобы поиск работал через несколько строк, мы заменим точку на универсальный класс [\s\S] (любой пробельный или непробельный символ).

Рассмотрим структуру такого шаблона:

[0-9a-fA-F-]{36}(?=[\s\S]*shopItems)

Разберем по частям:

Важное ограничение: Движок VBScript.RegExp, используемый в 1С через COM, является довольно старым. Он поддерживает «просмотр вперед» (Lookahead), но не поддерживает «просмотр назад» (Lookbehind). Поэтому искать «после слова» таким способом не получится, а «до слова» — вполне.

Метод 3: Ограничение до первого вхождения (Tempered Greedy Token)

Представим ситуацию, когда слово shopItems встречается в тексте несколько раз. Если мы используем обычный просмотр вперед (?=[\s\S]*shopItems), то первый блок ГУИДов будет найден правильно, но если между первым и вторым shopItems тоже есть ГУИДы, они тоже попадут в результат, так как «где-то впереди» (у второго маркера) условие выполнится.

Чтобы ограничить поиск строго первым упоминанием слова, используется техника «закаленного жадного токена». Мы проверяем, что на всем пути от найденного ГУИДа до маркера само слово-маркер больше не встречается.

Пример сложного шаблона для такой задачи:

[0-9a-fA-F-]{36}(?=(?:(?!shopItems)[\s\S])*shopItems)

Этот шаблон гарантирует, что мы смотрим только до ближайшего shopItems.

Метод 4: Использование захватывающих групп

Еще один профессиональный подход — захватить весь текст от начала файла до маркера в одну группу, а затем обработать эту группу. Это более читаемо и легче отлаживается в коде 1С.

Рассмотрим пример реализации:


RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.Global = Ложь; // Нам нужно только одно первое совпадение всего блока
RegExp.Pattern = "^([\s\S]*?)shopItems"; 

Matches = RegExp.Execute(ВесьТекст);
Если Matches.Count > 0 Тогда
    // Текст до первого shopItems находится в первой подгруппе
    ТекстДоМаркера = Matches.Item(0).SubMatches.Item(0);
    
    // Теперь во вложенном цикле или вторым RegExp ищем сами ГУИДы
    RegExpGUID = Новый COMОбъект("VBScript.RegExp");
    RegExpGUID.Global = Истина;
    RegExpGUID.Pattern = "[0-9a-fA-F-]{36}";
    
    Результаты = RegExpGUID.Execute(ТекстДоМаркера);
    // ... обработка результатов
КонецЕсли;

Сравнение производительности и рекомендации

Проанализируем ситуацию с точки зрения ресурсов системы. Регулярные выражения с использованием «просмотра вперед» на очень длинных текстах могут приводить к так называемому «катастрофическому возврату» (catastrophic backtracking). Это происходит потому, что для каждого найденного потенциального ГУИДа движок вынужден просматривать весь остаток текста до конца, чтобы подтвердить наличие слова shopItems.

Рекомендации по выбору метода:

Помните, что при работе с VBScript.RegExp в 1С важно правильно выставлять флаги IgnoreCase и Global, так как их значения по умолчанию могут отличаться от ожидаемых (по умолчанию они часто False).

← На главную