Как в 1С распарсить строку и найти параметры в фигурных скобках?

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

При разработке механизмов печати, генерации сообщений или интеграции часто возникает задача обработки шаблонов. Подобные задачи часто встречаются, когда вы пишете парсер шаблонов конфигурации .st или другие инструменты автоматизации. Представим ситуацию: у нас есть строка с формулой или текстом, в которую вставлены параметры, обернутые в специальные символы-разделители, например, в двойные фигурные скобки {{Пример}}.

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

Постановка задачи

Допустим, на вход поступает строка следующего вида:


ИсходнаяСтрока = "10+{{TAB}} * RATE(Строка,PARENT({{KILL}},,1) ,2) /100";

Нам необходимо получить массив или список значений, содержащий: TAB и KILL. Рассмотрим, какие инструменты для этого предоставляет платформа 1С:Предприятие и Библиотека Стандартных Подсистем (БСП).

Способ 1. Использование регулярных выражений (Платформа 8.3.23 и новее)

Начиная с версии платформы 8.3.23, разработчикам доступны нативные средства работы с регулярными выражениями. Это самый правильный, быстрый и лаконичный способ решения задачи парсинга. Для быстрой проверки и отладки ваших шаблонов удобно использовать тестирование регулярных выражений на 1С.

Для поиска всех вхождений используем метод СтрНайтиВсеПоРегулярномуВыражению. Основная сложность здесь — составить правильный шаблон поиска.

Разберем шаблон \{\{(.+?)\}\}:

  1. \{\{ — мы экранируем фигурные скобки обратным слэшем, так как в RegEx они являются служебными символами. Мы ищем именно текстовые символы {{.
  2. (.+?) — это группа захвата. Точка . означает любой символ. Плюс + означает "один или более раз". Знак вопроса ? делает квантификатор "ленивым" (non-greedy). Это критически важно: без знака вопроса поиск захватит текст от самых первых {{ до самых последних }} в строке, проглотив все промежуточные закрывающие скобки. "Ленивый" поиск остановится на первой же встреченной закрывающей последовательности.
  3. \}\} — закрывающие экранированные скобки.

Посмотрим на пример кода:


ИсходнаяСтрока = "10+{{TAB}} * RATE(Строка,PARENT({{KILL}},,1) ,2) /100";
РегулярноеВыражение = "\{\{(.+?)\}\}"; 

// Ищем все вхождения
Результаты = СтрНайтиВсеПоРегулярномуВыражению(ИсходнаяСтрока, РегулярноеВыражение);

Для Каждого Вхождение Из Результаты Цикл
    // Вхождение.Значение вернет полную подстроку, например "{{TAB}}"
    // Чтобы получить чистое значение, можно использовать группы (если платформа поддерживает доступ к ним)
    // Либо просто обрезать лишние символы:
    
    НайденныйПараметр = Вхождение.Значение;
    ЧистоеИмя = Сред(НайденныйПараметр, 3, СтрДлина(НайденныйПараметр) - 4);
    
    Сообщить("Найдено: " + ЧистоеИмя); 
КонецЦикла;

Если ваша конфигурация работает в режиме совместимости с версиями ниже 8.3.23, этот метод будет недоступен. В таком случае переходим к следующим решениям.

Способ 2. Использование COM-объекта RegExp (Для Windows)

Если платформа старая, но сервер работает на ОС Windows, можно использовать классический VBScript.RegExp. Логика остается той же, меняется только вызов объекта.


RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.Global = Истина; // Искать все вхождения
RegExp.IgnoreCase = Истина;
RegExp.Pattern = "\{\{(.+?)\}\}";

Matches = RegExp.Execute(ИсходнаяСтрока);

Для Каждого Match Из Matches Цикл
    // Match.SubMatches(0) вернет содержимое первой группы захвата (то, что внутри скобок)
    Сообщить("Найдено: " + Match.SubMatches(0));
КонецЦикла;

Способ 3. Использование функций БСП (Универсально)

Если в вашей конфигурации внедрена Библиотека Стандартных Подсистем (БСП), можно воспользоваться функцией СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок. Чтобы лучше ориентироваться в доступных инструментах библиотеки, рекомендуем изучить справочник по методам БСП с примерами использования.

Обратите внимание, что мы не используем стандартную функцию платформы СтрРазделить, так как она делит строку по каждому символу из строки-разделителя, а функция БСП умеет делить по целой подстроке.

Алгоритм действий следующий:

  1. Разбиваем исходную строку на массив, используя открывающий тег {{ как разделитель.
  2. Обходим полученный массив. Каждый элемент (кроме, возможно, первого) будет начинаться с искомого параметра, за которым следует закрывающий тег }} и остальной текст.
  3. Внутри цикла разбиваем полученный элемент по закрывающему тегу }}.
  4. Первая часть полученного разбиения и будет нашим параметром.

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


МассивТокенов = Новый Массив;
// Делим строку по открывающим скобкам
// Параметр Истина означает, что мы не обрезаем пробелы, чтобы сохранить структуру, если нужно
МассивБлоков = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ИсходнаяСтрока, "{{", Истина);

Для Индекс = 0 По МассивБлоков.ВГраница() Цикл
    ТекБлок = МассивБлоков[Индекс];
    
    // Пропускаем первый блок, если строка не начинается с {{, так как в нем нет параметра
    Если Индекс = 0 И Лев(ИсходнаяСтрока, 2) <> "{{" Тогда
        Продолжить;
    КонецЕсли;

    // Теперь ищем закрывающую скобку в текущем блоке
    МассивЧастей = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ТекБлок, "}}", Истина);
    
    // Если массив частей больше 1, значит закрывающая скобка была, и то что до нее - параметр
    Если МассивЧастей.Количество() > 1 Тогда
        ИмяПараметра = МассивЧастей[0];
        МассивТокенов.Добавить(ИмяПараметра);
    КонецЕсли;
КонецЦикла;

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

Способ 4. Классический алгоритм (СтрНайти в цикле)

Это самый надежный и быстрый способ, не зависящий от версий платформы и наличия БСП. Такие подходы часто применяются, если вы разрабатываете статический анализатор кода проектов или другие инструменты низкоуровневой обработки текста. Мы просто бежим по строке, находя позиции открывающих и закрывающих скобок.

Разберем логику:

  1. Находим позицию первых {{.
  2. От этой позиции ищем ближайшие }}.
  3. Вырезаем текст между ними функцией Сред.
  4. Смещаем позицию поиска дальше найденного фрагмента.

Текст = ИсходнаяСтрока;
Результат = Новый Массив;
НачальнаяПозиция = 1;

Пока Истина Цикл
    // Ищем начало параметра
    ПозНачала = СтрНайти(Текст, "{{", НаправлениеПоиска.СНачала, НачальнаяПозиция);
    
    Если ПозНачала = 0 Тогда
        Прервать; // Больше нет параметров
    КонецЕсли;
    
    // Сдвигаем позицию на длину открывающих скобок (2 символа)
    ПозТекста = ПозНачала + 2;
    
    // Ищем конец параметра, начиная сразу после открывающих скобок
    ПозКонца = СтрНайти(Текст, "}}", НаправлениеПоиска.СНачала, ПозТекста);
    
    Если ПозКонца > 0 Тогда
        // Вычисляем длину параметра
        ДлинаПараметра = ПозКонца - ПозТекста;
        
        Если ДлинаПараметра > 0 Тогда
            ИмяПараметра = Сред(Текст, ПозТекста, ДлинаПараметра);
            Результат.Добавить(ИмяПараметра);
        КонецЕсли;
        
        // Смещаем указатель поиска за закрывающие скобки
        НачальнаяПозиция = ПозКонца + 2;
    Иначе
        // Если открывающая скобка есть, а закрывающей нет - строка некорректна или парсинг окончен
        Прервать;
    КонецЕсли;
КонецЦикла;

Преимущества этого метода:

Ошибки, которых следует избегать

В процессе обсуждения часто предлагается использовать функцию СтрРазделить с разделителем "{{". Давайте проанализируем, почему СтрРазделить не сработает так, как ожидается.

Функция СтрРазделить(<Строка>, <Разделитель>) воспринимает второй параметр не как единую подстроку, а как набор символов. Если вы передадите туда "{{", система будет разбивать строку каждый раз, когда встретит хотя бы одну фигурную скобку.

Пример неправильного кода:


// НЕПРАВИЛЬНО
Массив = СтрРазделить("Текст {{Параметр}}", "{{"); 
// Результат будет содержать пустые строки между двумя скобками,
// так как каждая скобка сработает как разделитель.

Альтернативный метод "Замена и разделение"

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


СпецСимволНачало = Символ(1); // Непечатный символ
СпецСимволКонец = Символ(2);

ВременнаяСтрока = СтрЗаменить(ИсходнаяСтрока, "{{", СпецСимволНачало);
ВременнаяСтрока = СтрЗаменить(ВременнаяСтрока, "}}", СпецСимволКонец);

// Теперь можно делить, так как разделитель состоит из 1 символа
МассивГрязный = СтрРазделить(ВременнаяСтрока, СпецСимволНачало);

Для Каждого Фрагмент Из МассивГрязный Цикл
    ПозКонца = СтрНайти(Фрагмент, СпецСимволКонец);
    Если ПозКонца > 0 Тогда
        Параметр = Лев(Фрагмент, ПозКонца - 1);
        Сообщить(Параметр);
    КонецЕсли;
КонецЦикла;

Заключение

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

← На главную