В практике разработчика 1С часто возникает задача парсинга неструктурированного текста. Типичный пример — работа с превью кассового чека, где необходимо извлечь конкретные числовые показатели (суммы), стоящие после определенных символов, например, после знака «равно» (=). В данной статье мы подробно разберем, как решить эту задачу с помощью регулярных выражений, рассмотрим классический подход через VBScript.RegExp, возможности новых версий платформы 1С и академический интерес к методу Replace.
Представим многострочный текст чека, в котором информация о товаре и его стоимости перемешана с техническими данными:
Болт с шест.гол. (DIN 933)
0.111 х 5555.55=333.33
НДС 20%
ТОВАР
ПЕРЕДАЧА В КРЕДИТ
КОД СТРАНЫ:555
ДЕКЛАРАЦИЯ:111111/22222/33333
ЕД.ИЗМЕРЕНИЯ:тыс. шт
Болт с шест.гол 2 (DIN 933)
2.222 х 3333.33=7777.77
Наша цель — получить на выходе только значения 333.33 и 7777.77, отсеяв всё лишнее. Рассмотрим несколько способов достижения этой цели.
Наиболее логичный и производительный способ в 1С — это поиск всех вхождений по шаблону. Если мы работаем в среде Windows, мы используем COM-объект VBScript.RegExp. Разберем код по шагам:
1. Создадим объект регулярного выражения и настроим его свойства. Нам важен флаг Global, чтобы найти все суммы в тексте, а не только первую.
2. Используем шаблон =([\d\.]+). Здесь знак «равно» служит ориентиром, а скобки создают группу захвата для цифр и точек.
// Создание объекта
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина;
RegExp.Global = Истина;
RegExp.MultiLine = Истина;
// Шаблон: ищем знак =, затем цифры и точки
RegExp.Pattern = "=([\d\.]+)";
ТекстЧека = "Ваш текст чека...";
Совпадения = RegExp.Execute(ТекстЧека);
Для Каждого ТекущееСовпадение Из Совпадения Цикл
// Мы берем первую группу захвата (SubMatches), в которой лежит само число без знака =
Сумма = ТекущееСовпадение.SubMatches.Item(0);
Сообщить("Найдена сумма: " + Сумма);
КонецЦикла;
Иногда, ради «спортивного интереса» или специфических требований алгоритма, разработчики хотят использовать метод Replace, чтобы «грохнуть» всё, что не является суммой, оставив только нужные данные. Это сложнее, так как стандартный VBScript.RegExp не поддерживает некоторые современные конструкции (например, ретроспективную проверку).
Для решения задачи через Replace нам нужно составить шаблон так, чтобы он захватывал либо «нужный кусок», либо «весь остальной мусор». Рассмотрим конструкцию, к которой пришли в ходе обсуждения:
Шаблон: [\S\s]+?=(\d+\.\d+)|[\S\s]+
Разберем составные части шаблона:
[\S\s]+? — ленивый поиск любых символов (включая переносы строк) до первого знака равенства.=(\d+\.\d+) — захват знака равенства и группы цифр (наша целевая сумма).| — оператор «ИЛИ».[\S\s]+ — поглощение остатка текста, если суммы не найдены.При замене на $1\n (где $1 — это содержимое первой группы захвата), движок заменит каждый найденный блок текста на найденную сумму и символ переноса строки. Тот текст, который попал во вторую часть условия («мусор»), заменится на пустоту (так как для него группа захвата $1 будет пустой).
Начиная с версии 8.3.23, в 1С появились встроенные регулярные выражения, работающие на движке ICU. Это избавляет от необходимости использовать COMОбъект и делает код кроссплатформенным (работающим в том числе на Linux).
Проанализируем, как изменится решение. В новом движке доступна положительная ретроспективная проверка (lookbehind). Это позволяет сказать движку: «найди цифры, ПЕРЕД которыми стоит знак равно, но сам знак равно в результат не включай».
Шаблон: (?<==)\d+\.\d+
// Пример для платформы 8.3.23 и выше
ТекстЧека = "..."; // Ваш текст
Результат = РегулярныеВыражения.НайтиСовпадения(ТекстЧека, "(?<==)\d+\.\d+");
Для Каждого Совпадение Из Результат Цикл
Сообщить(Совпадение.Значение);
КонецЦикла;
При анализе превью чеков следует учитывать несколько технических особенностей, которые могут «сломать» ваше регулярное выражение:
\s в регулярках обычно успешно обрабатывает оба варианта, в то время как простой пробел в шаблоне может не сработать.. в регулярных выражениях не включает в себя символ переноса строки. Чтобы искать по всему тексту чека, используйте флаг MultiLine или универсальный класс [\s\S] (любой символ, включая пробелы и не-пробелы).* или + является «жадным» и пытается захватить как можно больше текста. При использовании сложных замен (как в способе 2) всегда проверяйте, не «съедает» ли выражение лишние данные, добавляя знак вопроса для переключения в ленивый режим (например, .*?).Если ваше регулярное выражение «оставляет лишнее», проанализируйте структуру строк. В тексте чека после суммы часто идут пробелы или служебные слова (например, «НДС»). Если ваш шаблон заканчивается просто на \d+, он может остановиться на первой же цифре. Всегда четко ограничивайте конец искомой последовательности (например, требуя, чтобы за цифрами шел пробел, конец строки или другой нецифровой символ).
Выбор метода зависит от ваших задач: для промышленной разработки лучше использовать метод Execute (или встроенные функции 8.3.23), а для разовых манипуляций в консоли разработчика можно оттачивать мастерство владения Replace.