Разработчикам 1С часто приходится иметь дело со строковыми данными, которые необходимо разобрать на отдельные компоненты. Одной из распространённых задач является парсинг строк, где данные разделены определённым символом, например, символом пайпа |. Особую сложность представляют случаи, когда поля могут быть пустыми или содержать избыточные пробелы, а также когда необходимо избежать дополнительных операций по очистке, таких как СокрЛП. Для комплексной обработки текста и выделения подстрок без регулярных выражений можно использовать набор функций для работы со строками в 1С.
В этой статье мы подробно разберём, как эффективно использовать метод СтрНайтиВсеПоРегулярномуВыражению в 1С для решения такой задачи. Мы рассмотрим основные принципы составления надёжных регулярных выражений, которые самостоятельно справляются с пробелами, пустыми полями и поддерживают кириллицу.
Представьте, что у нас есть строка данных, которая выглядит следующим образом:
Строка = "|Финансовые расходы | Расходы от прочих операций| |Ложь| Истина |{d3dac491-f264-4a67-8040-45ada6ef7e0e} |";
Задача состоит в том, чтобы извлечь каждое поле как отдельный элемент массива, при этом:
| в результат.||).СокрЛП() для каждого элемента.Первоначальные попытки могут привести к проблемам. Например, регулярное выражение "(?<=(^|\|)\s{0,10})((\w|\{)[\w\s\.\-\d]*(\w|\}))?(?=\s{0,10}\|)", как было отмечено в сообщении 1, либо пропускает пустые поля, либо создаёт лишние нулевые элементы. Другой вариант, "(?<=\|)\s*((\w|\{)[\w\s\.\-\d]*(\w|\}))?\s*(?=\|)", почти работает, но захваченный контент всё равно может потребовать СокрЛП(), что не соответствует нашей цели.
Давайте выясним, как построить регулярное выражение, которое справится со всеми этими условиями.
Для создания универсальных регулярных выражений, способных корректно обрабатывать разнообразные строковые данные в 1С, необходимо освоить несколько ключевых приёмов:
\s* для необязательных пробеловСпецификатор \s* означает "ноль или более пробельных символов" (пробелы, табы, переводы строк). Это критически важно для игнорирования пробелов, которые могут окружать содержимое поля или разделители. В вашем случае, если поле выглядит как " Финансовые расходы ", \s* поможет пропустить пробелы, сосредоточившись на полезном содержимом.
Пример: Вместо того чтобы пытаться захватить все пробелы фиксированным количеством (например, \s{0,10}), используйте \s* для максимальной гибкости.
*?Квантификатор * сам по себе является "жадным" — он пытается захватить максимально возможное количество символов. Если у вас есть строка "|Поле1 | Поле2|" и вы используете .* для захвата содержимого, то он может "проглотить" всё до самого последнего разделителя. Добавление знака вопроса ? делает квантификатор "нежадным" (*?) — он захватывает минимально возможное количество символов, необходимое для совпадения.
Пример: (.*?) будет захватывать любые символы (кроме перевода строки по умолчанию) до ближайшего следующего элемента шаблона, что идеально подходит для извлечения содержимого поля между разделителями.
(...)?Если поле может быть полностью пустым (т.е. ||), использование опциональной захватывающей группы (...)? позволяет регулярному выражению найти совпадение, даже если содержимое группы отсутствует. В результатах поиска соответствующий элемент массива будет либо пустым, либо содержать Undefined, в зависимости от конкретной реализации движка регулярных выражений.
В нашем случае, при использовании (.*?) для содержимого поля, если между разделителями нет символов (только пробелы или ничего), .*? успешно захватит пустую строку, что и требуется.
(?<=...) и (?=...)Эти конструкции позволяют проверять наличие определённого шаблона до ((?<=...), lookbehind) или после ((?=...), lookahead) текущей позиции, не включая сам шаблон в окончательное совпадение. Это очень полезно для определения границ поля, как в вашем случае с разделителем |.
Пример:
(?<=\|): "позиция должна быть сразу после символа |".(?=\|): "позиция должна быть сразу перед символом |".Эти утверждения позволяют нам определить границы поля, не захватывая сами разделители в результат.
\w в 1СРанее в 1С могли возникать сложности с использованием \w для кириллицы. Однако, начиная с версии платформы 8.3.23, 1С поддерживает регулярные выражения, основанные на диалекте ICU, который обладает расширенными возможностями работы со строками Unicode. Это означает, что \w в большинстве случаев будет корректно работать с символами кириллицы, а также с цифрами и знаком подчёркивания.
Если же вы столкнётесь с проблемами, всегда можно использовать явные символьные классы, такие как [а-яА-ЯЁё], или же более общий нежадный шаблон .*?, который захватывает любые символы (кроме перевода строки) и не зависит от языковой принадлежности.
Важно отметить, что движок регулярных выражений в 1С не поддерживает именованные группы (например, (?<name>...)). Это означает, что при работе с захваченными группами вам придётся обращаться к ним по числовым индексам (Результат.ПолучитьГруппы()[Индекс]), а не по именам.
СокрЛПНаша цель — создать регулярное выражение, которое при использовании с СтрНайтиВсеПоРегулярномуВыражению вернёт коллекцию совпадений, где каждое совпадение будет уже очищенным (без ведущих/завершающих пробелов) значением поля. Использование утверждений нулевой ширины позволяет нам получить именно это.
Рассмотрим предложенную в обсуждении строку:
Строка = "|Финансовые расходы | Расходы от прочих операций| |Ложь| Истина |{d3dac491-f264-4a67-8040-45ada6ef7e0e} |";
Для извлечения каждого поля как отдельного значения, при этом обрезая пробелы и корректно обрабатывая пустые поля, мы можем использовать следующее регулярное выражение:
РегВырДляПоляБезСокрЛП = "(?<=\|)\s*(.*?)\s*(?=\|)";
Давайте разберём его по частям:
(?<=\|): Утверждение нулевой ширины, которое требует, чтобы перед текущей позицией находился символ |. Сам символ | при этом не включается в совпадение. Экранируем | с помощью \, так как | является служебным символом в регулярных выражениях (означает "ИЛИ").\s*: Захватываем (и фактически игнорируем) ноль или более пробельных символов, которые могут находиться сразу после начального разделителя. Это обеспечивает обрезку пробелов в начале поля.(.*?): Это самая важная часть. Мы используем . для совпадения с любым символом (кроме перевода строки по умолчанию) и *? для нежадного захвата ноля или более таких символов. Это гарантирует, что мы захватим всё содержимое поля, включая пробелы внутри него, цифры, специальные символы (как в GUID) и кириллицу, до следующего разделителя |, при этом не захватывая его. Если поле пустое (||), .*? успешно захватит пустую строку.\s*: Захватываем (и фактически игнорируем) ноль или более пробельных символов, которые могут находиться перед конечным разделителем. Это обеспечивает обрезку пробелов в конце поля.(?=\|): Утверждение нулевой ширины, которое требует, чтобы после текущей позиции находился символ |. Как и в случае с (?<=\|), сам символ | не включается в совпадение.Когда СтрНайтиВсеПоРегулярномуВыражению применяется с этим регулярным выражением к нашей строке, оно найдёт каждое поле по отдельности. Каждое совпадение, возвращаемое методом, будет содержать очищенное от ведущих и завершающих пробелов значение поля, без необходимости дополнительного вызова СокрЛП().
Теперь давайте применим наше регулярное выражение для разбора более сложной структуры, предложенной автором темы: таблицы данных, где есть строка заголовка и несколько строк с данными.
// Пример входных данных из обсуждения
СтрокаДанныхЗаполнения = " Наименование | Родитель |ЭтоГруппа|Доходы|Расходы| ПКО | Идентификатор |" + Символы.ПС +
"|Финансовые расходы |Расходы от прочих операций |Ложь |Ложь |Истина |Справочник_ГруппыФинансовогоУчетаДоходовРасходов|{d3dac491-f264-4a67-8040-45ada6ef7e0e}|" + Символы.ПС +
"|Расходы от прочих операций | |Истина | | |Справочник_ГруппыФинансовогоУчетаДоходовРасходов|{d9420a8e-94a8-480e-a708-03ca61bca502}|";
// Итоговое регулярное выражение для извлечения содержимого полей,
// включая пустые поля и автоматически обрезая пробелы.
// (?:\|\s*) - не захватывает начальный разделитель с пробелами
// (.*?) - захватывает само содержимое поля (нежадно, любые символы)
// (?=\|) - не захватывает конечный разделитель, но проверяет его наличие
РегВырДляПоляБезСокрЛП = "(?<=\|)\s*(.*?)\s*(?=\|)";
// Разделяем общую строку на отдельные строки таблицы
МассивСтрок = СтрРазделить(СтрокаДанныхЗаполнения, Символы.ПС);
// Инициализируем ТаблицуЗначений и массив для хранения имен колонок
ТаблицаДанных = Новый ТаблицаЗначений;
ИменаКолонок = Новый Массив;
// Обрабатываем каждую строку данных
Для Каждого ТекущаяСтрока Из МассивСтрок Цикл
// Пропускаем пустые строки или строки, содержащие только пробелы
Если ПустаяСтрока(СокрЛП(ТекущаяСтрока)) Тогда
Продолжить;
КонецЕсли;
// Ищем все совпадения полей в текущей строке
РезультатПоиска = СтрНайтиВсеПоРегулярномуВыражению(ТекущаяСтрока, РегВырДляПоляБезСокрЛП);
Если РезультатПоиска.Количество() = 0 Тогда
// Если в строке не найдено ни одного поля, пропускаем ее
Продолжить;
КонецЕсли;
Если ИменаКолонок.Количество() = 0 Тогда
// Это первая обработанная строка, считаем ее заголовком
// Создаем колонки в ТаблицеЗначений
Для Каждого ЭлементСовпадения Из РезультатПоиска Цикл
ИмяКолонки = ЭлементСовпадения.Значение; // Получаем очищенное имя колонки
// Проверяем, что имя колонки не пустое и не содержит только пробелы.
// Хотя наше regex уже обрезает, дополнительная проверка не помешает
Если Не ПустаяСтрока(ИмяКолонки) Тогда
Попытка
ТаблицаДанных.Колонки.Добавить(ИмяКолонки);
ИменаКолонок.Добавить(ИмяКолонки);
Исключение
// Обработка случая, если колонка с таким именем уже существует (дубликат в заголовке)
Сообщить("Ошибка добавления колонки '" + ИмяКолонки + "': " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
Иначе
// Это строка данных, заполняем новую строку ТаблицыЗначений
НоваяСтрокаТЗ = ТаблицаДанных.Добавить();
Для Счетчик = 0 По РезультатПоиска.Количество() - 1 Цикл
Если Счетчик < ИменаКолонок.Количество() Тогда
ИмяКолонки = ИменаКолонок[Счетчик];
ЗначениеПоля = РезультатПоиска[Счетчик].Значение; // Получаем очищенное значение поля
Попытка
НоваяСтрокаТЗ[ИмяКолонки] = ЗначениеПоля;
Исключение
// Обработка случая, если колонка не найдена (например, из-за ошибки в заголовке или данных)
Сообщить("Ошибка присвоения значения '" + ЗначениеПоля + "' колонке '" + ИмяКолонки + "' по индексу " + Счетчик + ": " + ОписаниеОшибки());
КонецПопытки;
Иначе
// Если количество полей в строке данных превышает количество колонок заголовка
Сообщить("Предупреждение: Количество полей в строке данных превышает количество колонок в заголовке. Лишнее поле: '" + РезультатПоиска[Счетчик].Значение + "'");
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
// Выведем результат для проверки
Сообщить("--- Результат парсинга в ТаблицуЗначений ---");
Если ТаблицаДанных.Количество() > 0 Тогда
Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
СтрокаДляСообщения = "";
Для Каждого КолонкаТЗ Из ТаблицаДанных.Колонки Цикл
СтрокаДляСообщения = СтрокаДляСообщения + КолонкаТЗ.Имя + ": """ + СтрокаТЗ[КолонкаТЗ.Имя] + """; ";
КонецЦикла;
Сообщить(СтрокаДляСообщения);
КонецЦикла;
Иначе
Сообщить("Таблица данных пуста или не удалось распарсить.");
КонецЕсли;
В этом примере мы делаем следующее:
СтрРазделить."(?<=\|)\s*(.*?)\s*(?=\|)" для каждой строки, чтобы найти все поля.ТаблицаЗначений.ТаблицаЗначений и заполняем её значениями, извлечёнными регулярным выражением.Обратите внимание, что регулярное выражение само по себе не обрабатывает начальный и конечный разделители всей строки (например, | в самом начале и в самом конце строки). Оно ищет совпадения *между* разделителями. В нашем случае это приводит к желаемому результату, так как метод СтрНайтиВсеПоРегулярномуВыражению обнаружит все подстроки, соответствующие паттерну, даже если они расположены друг за другом.
Мы добились нашей основной цели: извлекать поля из строк с разделителями, автоматически удаляя пробелы и корректно обрабатывая пустые поля, без необходимости использования СокрЛП().
Однако, давайте рассмотрим несколько дополнительных моментов, которые могут быть полезны:
Если содержимое поля имеет очень специфичный формат (например, только числа, только GUIDы), вы можете заменить .*? на более строгий шаблон. Например, для GUID это может быть \{[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}\}. Однако, для общей задачи парсинга произвольных текстовых полей .*? является наиболее универсальным и подходящим решением.
Как справедливо было отмечено в обсуждении, регулярные выражения могут работать очень быстро. При правильном составлении они часто превосходят по производительности ручной разбор строк с помощью циклов и функций Найти() или Сред() для сложных паттернов. Для больших объёмов данных или часто повторяющихся операций парсинга, использование регулярок — это эффективный выбор.
В реальных проектах всегда следует добавлять более строгую обработку ошибок и валидацию данных. Например, проверять, что количество извлечённых полей соответствует ожидаемому количеству колонок, или проверять типы данных, если это необходимо.
Использование регулярных выражений в 1С с методом СтрНайтиВсеПоРегулярномуВыражению — это мощный инструмент для парсинга строковых данных. Понимая ключевые концепции, такие как нежадные квантификаторы, утверждения нулевой ширины и обработка пробелов, мы можем создавать надёжные и эффективные решения, которые обрабатывают сложные сценарии, включая пустые поля и избыточные пробелы, без необходимости дополнительных операций по очистке. Это не только упрощает код, но и делает его более производительным и устойчивым к разнообразным входным данным.
Мы рассмотрели, как построить регулярное выражение, которое извлекает очищенные значения полей из пайп-разделённой строки, демонстрируя его применение для заполнения ТаблицаЗначений. Этот подход позволяет вам глубже понять возможности регулярных выражений и эффективно применять их в вашей работе с 1С.