При разработке механизмов печати, генерации сообщений или интеграции часто возникает задача обработки шаблонов. Подобные задачи часто встречаются, когда вы пишете парсер шаблонов конфигурации .st или другие инструменты автоматизации. Представим ситуацию: у нас есть строка с формулой или текстом, в которую вставлены параметры, обернутые в специальные символы-разделители, например, в двойные фигурные скобки {{Пример}}.
Наша задача — найти все такие вхождения и получить список имен параметров. Рассмотрим подробно несколько способов решения этой задачи: от современных возможностей платформы до универсальных алгоритмов, работающих в любых конфигурациях.
Допустим, на вход поступает строка следующего вида:
ИсходнаяСтрока = "10+{{TAB}} * RATE(Строка,PARENT({{KILL}},,1) ,2) /100";
Нам необходимо получить массив или список значений, содержащий: TAB и KILL. Рассмотрим, какие инструменты для этого предоставляет платформа 1С:Предприятие и Библиотека Стандартных Подсистем (БСП).
Начиная с версии платформы 8.3.23, разработчикам доступны нативные средства работы с регулярными выражениями. Это самый правильный, быстрый и лаконичный способ решения задачи парсинга. Для быстрой проверки и отладки ваших шаблонов удобно использовать тестирование регулярных выражений на 1С.
Для поиска всех вхождений используем метод СтрНайтиВсеПоРегулярномуВыражению. Основная сложность здесь — составить правильный шаблон поиска.
Разберем шаблон \{\{(.+?)\}\}:
\{\{ — мы экранируем фигурные скобки обратным слэшем, так как в RegEx они являются служебными символами. Мы ищем именно текстовые символы {{.(.+?) — это группа захвата. Точка . означает любой символ. Плюс + означает "один или более раз". Знак вопроса ? делает квантификатор "ленивым" (non-greedy). Это критически важно: без знака вопроса поиск захватит текст от самых первых {{ до самых последних }} в строке, проглотив все промежуточные закрывающие скобки. "Ленивый" поиск остановится на первой же встреченной закрывающей последовательности.\}\} — закрывающие экранированные скобки.Посмотрим на пример кода:
ИсходнаяСтрока = "10+{{TAB}} * RATE(Строка,PARENT({{KILL}},,1) ,2) /100";
РегулярноеВыражение = "\{\{(.+?)\}\}";
// Ищем все вхождения
Результаты = СтрНайтиВсеПоРегулярномуВыражению(ИсходнаяСтрока, РегулярноеВыражение);
Для Каждого Вхождение Из Результаты Цикл
// Вхождение.Значение вернет полную подстроку, например "{{TAB}}"
// Чтобы получить чистое значение, можно использовать группы (если платформа поддерживает доступ к ним)
// Либо просто обрезать лишние символы:
НайденныйПараметр = Вхождение.Значение;
ЧистоеИмя = Сред(НайденныйПараметр, 3, СтрДлина(НайденныйПараметр) - 4);
Сообщить("Найдено: " + ЧистоеИмя);
КонецЦикла;
Если ваша конфигурация работает в режиме совместимости с версиями ниже 8.3.23, этот метод будет недоступен. В таком случае переходим к следующим решениям.
Если платформа старая, но сервер работает на ОС Windows, можно использовать классический VBScript.RegExp. Логика остается той же, меняется только вызов объекта.
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.Global = Истина; // Искать все вхождения
RegExp.IgnoreCase = Истина;
RegExp.Pattern = "\{\{(.+?)\}\}";
Matches = RegExp.Execute(ИсходнаяСтрока);
Для Каждого Match Из Matches Цикл
// Match.SubMatches(0) вернет содержимое первой группы захвата (то, что внутри скобок)
Сообщить("Найдено: " + Match.SubMatches(0));
КонецЦикла;
Если в вашей конфигурации внедрена Библиотека Стандартных Подсистем (БСП), можно воспользоваться функцией СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок. Чтобы лучше ориентироваться в доступных инструментах библиотеки, рекомендуем изучить справочник по методам БСП с примерами использования.
Обратите внимание, что мы не используем стандартную функцию платформы СтрРазделить, так как она делит строку по каждому символу из строки-разделителя, а функция БСП умеет делить по целой подстроке.
Алгоритм действий следующий:
{{ как разделитель.}} и остальной текст.}}.Рассмотрим реализацию:
МассивТокенов = Новый Массив;
// Делим строку по открывающим скобкам
// Параметр Истина означает, что мы не обрезаем пробелы, чтобы сохранить структуру, если нужно
МассивБлоков = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ИсходнаяСтрока, "{{", Истина);
Для Индекс = 0 По МассивБлоков.ВГраница() Цикл
ТекБлок = МассивБлоков[Индекс];
// Пропускаем первый блок, если строка не начинается с {{, так как в нем нет параметра
Если Индекс = 0 И Лев(ИсходнаяСтрока, 2) <> "{{" Тогда
Продолжить;
КонецЕсли;
// Теперь ищем закрывающую скобку в текущем блоке
МассивЧастей = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ТекБлок, "}}", Истина);
// Если массив частей больше 1, значит закрывающая скобка была, и то что до нее - параметр
Если МассивЧастей.Количество() > 1 Тогда
ИмяПараметра = МассивЧастей[0];
МассивТокенов.Добавить(ИмяПараметра);
КонецЕсли;
КонецЦикла;
Этот метод хорош тем, что не требует написания сложного посимвольного анализатора, однако он создает много промежуточных массивов, что может быть неоптимально для очень больших текстов.
Это самый надежный и быстрый способ, не зависящий от версий платформы и наличия БСП. Такие подходы часто применяются, если вы разрабатываете статический анализатор кода проектов или другие инструменты низкоуровневой обработки текста. Мы просто бежим по строке, находя позиции открывающих и закрывающих скобок.
Разберем логику:
{{.}}.Сред.
Текст = ИсходнаяСтрока;
Результат = Новый Массив;
НачальнаяПозиция = 1;
Пока Истина Цикл
// Ищем начало параметра
ПозНачала = СтрНайти(Текст, "{{", НаправлениеПоиска.СНачала, НачальнаяПозиция);
Если ПозНачала = 0 Тогда
Прервать; // Больше нет параметров
КонецЕсли;
// Сдвигаем позицию на длину открывающих скобок (2 символа)
ПозТекста = ПозНачала + 2;
// Ищем конец параметра, начиная сразу после открывающих скобок
ПозКонца = СтрНайти(Текст, "}}", НаправлениеПоиска.СНачала, ПозТекста);
Если ПозКонца > 0 Тогда
// Вычисляем длину параметра
ДлинаПараметра = ПозКонца - ПозТекста;
Если ДлинаПараметра > 0 Тогда
ИмяПараметра = Сред(Текст, ПозТекста, ДлинаПараметра);
Результат.Добавить(ИмяПараметра);
КонецЕсли;
// Смещаем указатель поиска за закрывающие скобки
НачальнаяПозиция = ПозКонца + 2;
Иначе
// Если открывающая скобка есть, а закрывающей нет - строка некорректна или парсинг окончен
Прервать;
КонецЕсли;
КонецЦикла;
Преимущества этого метода:
В процессе обсуждения часто предлагается использовать функцию СтрРазделить с разделителем "{{". Давайте проанализируем, почему СтрРазделить не сработает так, как ожидается.
Функция СтрРазделить(<Строка>, <Разделитель>) воспринимает второй параметр не как единую подстроку, а как набор символов. Если вы передадите туда "{{", система будет разбивать строку каждый раз, когда встретит хотя бы одну фигурную скобку.
Пример неправильного кода:
// НЕПРАВИЛЬНО
Массив = СтрРазделить("Текст {{Параметр}}", "{{");
// Результат будет содержать пустые строки между двумя скобками,
// так как каждая скобка сработает как разделитель.
Если вам все же очень хочется использовать СтрРазделить, можно применить хитрость: заменить двойные скобки на уникальный символ, который гарантированно не встречается в тексте. Подобные алгоритмы "трансформации" текста часто встречаются в задачах, где требуется подсчет количества слов в тексте и их вывод или сложный лингвистический разбор.
СпецСимволНачало = Символ(1); // Непечатный символ
СпецСимволКонец = Символ(2);
ВременнаяСтрока = СтрЗаменить(ИсходнаяСтрока, "{{", СпецСимволНачало);
ВременнаяСтрока = СтрЗаменить(ВременнаяСтрока, "}}", СпецСимволКонец);
// Теперь можно делить, так как разделитель состоит из 1 символа
МассивГрязный = СтрРазделить(ВременнаяСтрока, СпецСимволНачало);
Для Каждого Фрагмент Из МассивГрязный Цикл
ПозКонца = СтрНайти(Фрагмент, СпецСимволКонец);
Если ПозКонца > 0 Тогда
Параметр = Лев(Фрагмент, ПозКонца - 1);
Сообщить(Параметр);
КонецЕсли;
КонецЦикла;
Для простых задач парсинга в современных конфигурациях лучше всего подходят регулярные выражения. Если же вы пишете универсальный код для тиражируемого решения или поддерживаете старую конфигурацию, используйте цикл с СтрНайти — это "золотой стандарт" производительности и совместимости.