Как корректно парсить строки с разделителями, пустыми полями и пробелами с помощью регулярных выражений в 1С?

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

Разработчикам 1С часто приходится иметь дело со строковыми данными, которые необходимо разобрать на отдельные компоненты. Одной из распространённых задач является парсинг строк, где данные разделены определённым символом, например, символом пайпа |. Особую сложность представляют случаи, когда поля могут быть пустыми или содержать избыточные пробелы, а также когда необходимо избежать дополнительных операций по очистке, таких как СокрЛП. Для комплексной обработки текста и выделения подстрок без регулярных выражений можно использовать набор функций для работы со строками в 1С.

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

Определяем основную проблему

Представьте, что у нас есть строка данных, которая выглядит следующим образом:


Строка = "|Финансовые расходы | Расходы от прочих операций| |Ложь| Истина |{d3dac491-f264-4a67-8040-45ada6ef7e0e} |";

Задача состоит в том, чтобы извлечь каждое поле как отдельный элемент массива, при этом:

  1. Избежать включения разделителей | в результат.
  2. Автоматически удалять пробелы в начале и конце каждого поля.
  3. Корректно обрабатывать полностью пустые поля (например, между двумя пайпами ||).
  4. Получать результат без необходимости дополнительного вызова функции СокрЛП() для каждого элемента.

Первоначальные попытки могут привести к проблемам. Например, регулярное выражение "(?<=(^|\|)\s{0,10})((\w|\{)[\w\s\.\-\d]*(\w|\}))?(?=\s{0,10}\|)", как было отмечено в сообщении 1, либо пропускает пустые поля, либо создаёт лишние нулевые элементы. Другой вариант, "(?<=\|)\s*((\w|\{)[\w\s\.\-\d]*(\w|\}))?\s*(?=\|)", почти работает, но захваченный контент всё равно может потребовать СокрЛП(), что не соответствует нашей цели.

Давайте выясним, как построить регулярное выражение, которое справится со всеми этими условиями.

Ключевые концепции для надёжных регулярных выражений в 1С

Для создания универсальных регулярных выражений, способных корректно обрабатывать разнообразные строковые данные в 1С, необходимо освоить несколько ключевых приёмов:

  1. Использование \s* для необязательных пробелов

    Спецификатор \s* означает "ноль или более пробельных символов" (пробелы, табы, переводы строк). Это критически важно для игнорирования пробелов, которые могут окружать содержимое поля или разделители. В вашем случае, если поле выглядит как " Финансовые расходы ", \s* поможет пропустить пробелы, сосредоточившись на полезном содержимом.

    Пример: Вместо того чтобы пытаться захватить все пробелы фиксированным количеством (например, \s{0,10}), используйте \s* для максимальной гибкости.

  2. Нежадный квантификатор *?

    Квантификатор * сам по себе является "жадным" — он пытается захватить максимально возможное количество символов. Если у вас есть строка "|Поле1 | Поле2|" и вы используете .* для захвата содержимого, то он может "проглотить" всё до самого последнего разделителя. Добавление знака вопроса ? делает квантификатор "нежадным" (*?) — он захватывает минимально возможное количество символов, необходимое для совпадения.

    Пример: (.*?) будет захватывать любые символы (кроме перевода строки по умолчанию) до ближайшего следующего элемента шаблона, что идеально подходит для извлечения содержимого поля между разделителями.

  3. Опциональные захватывающие группы (...)?

    Если поле может быть полностью пустым (т.е. ||), использование опциональной захватывающей группы (...)? позволяет регулярному выражению найти совпадение, даже если содержимое группы отсутствует. В результатах поиска соответствующий элемент массива будет либо пустым, либо содержать Undefined, в зависимости от конкретной реализации движка регулярных выражений.

    В нашем случае, при использовании (.*?) для содержимого поля, если между разделителями нет символов (только пробелы или ничего), .*? успешно захватит пустую строку, что и требуется.

  4. Утверждения нулевой ширины (Lookarounds): (?<=...) и (?=...)

    Эти конструкции позволяют проверять наличие определённого шаблона до ((?<=...), lookbehind) или после ((?=...), lookahead) текущей позиции, не включая сам шаблон в окончательное совпадение. Это очень полезно для определения границ поля, как в вашем случае с разделителем |.

    Пример:

    • (?<=\|): "позиция должна быть сразу после символа |".
    • (?=\|): "позиция должна быть сразу перед символом |".

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

  5. Поддержка Юникода и особенности \w в 1С

    Ранее в 1С могли возникать сложности с использованием \w для кириллицы. Однако, начиная с версии платформы 8.3.23, 1С поддерживает регулярные выражения, основанные на диалекте ICU, который обладает расширенными возможностями работы со строками Unicode. Это означает, что \w в большинстве случаев будет корректно работать с символами кириллицы, а также с цифрами и знаком подчёркивания.

    Если же вы столкнётесь с проблемами, всегда можно использовать явные символьные классы, такие как [а-яА-ЯЁё], или же более общий нежадный шаблон .*?, который захватывает любые символы (кроме перевода строки) и не зависит от языковой принадлежности.

  6. Отсутствие именованных групп

    Важно отметить, что движок регулярных выражений в 1С не поддерживает именованные группы (например, (?<name>...)). Это означает, что при работе с захваченными группами вам придётся обращаться к ним по числовым индексам (Результат.ПолучитьГруппы()[Индекс]), а не по именам.

Создаём универсальное регулярное выражение для полей без СокрЛП

Наша цель — создать регулярное выражение, которое при использовании с СтрНайтиВсеПоРегулярномуВыражению вернёт коллекцию совпадений, где каждое совпадение будет уже очищенным (без ведущих/завершающих пробелов) значением поля. Использование утверждений нулевой ширины позволяет нам получить именно это.

Рассмотрим предложенную в обсуждении строку:


Строка = "|Финансовые расходы | Расходы от прочих операций| |Ложь| Истина |{d3dac491-f264-4a67-8040-45ada6ef7e0e} |";

Для извлечения каждого поля как отдельного значения, при этом обрезая пробелы и корректно обрабатывая пустые поля, мы можем использовать следующее регулярное выражение:

РегВырДляПоляБезСокрЛП = "(?<=\|)\s*(.*?)\s*(?=\|)";

Давайте разберём его по частям:

Когда СтрНайтиВсеПоРегулярномуВыражению применяется с этим регулярным выражением к нашей строке, оно найдёт каждое поле по отдельности. Каждое совпадение, возвращаемое методом, будет содержать очищенное от ведущих и завершающих пробелов значение поля, без необходимости дополнительного вызова СокрЛП().

Пример использования в 1С: Заполнение Таблицы Значений

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


// Пример входных данных из обсуждения
СтрокаДанныхЗаполнения = " Наименование | Родитель |ЭтоГруппа|Доходы|Расходы| ПКО | Идентификатор |" + Символы.ПС +
                        "|Финансовые расходы |Расходы от прочих операций |Ложь |Ложь |Истина |Справочник_ГруппыФинансовогоУчетаДоходовРасходов|{d3dac491-f264-4a67-8040-45ada6ef7e0e}|" + Символы.ПС +
                        "|Расходы от прочих операций | |Истина | | |Справочник_ГруппыФинансовогоУчетаДоходовРасходов|{d9420a8e-94a8-480e-a708-03ca61bca502}|";

// Итоговое регулярное выражение для извлечения содержимого полей,
// включая пустые поля и автоматически обрезая пробелы.
// (?:\|\s*) - не захватывает начальный разделитель с пробелами
// (.*?)     - захватывает само содержимое поля (нежадно, любые символы)
// (?=\|)    - не захватывает конечный разделитель, но проверяет его наличие
РегВырДляПоляБезСокрЛП = "(?<=\|)\s*(.*?)\s*(?=\|)";

// Разделяем общую строку на отдельные строки таблицы
МассивСтрок = СтрРазделить(СтрокаДанныхЗаполнения, Символы.ПС);

// Инициализируем ТаблицуЗначений и массив для хранения имен колонок
ТаблицаДанных = Новый ТаблицаЗначений;
ИменаКолонок = Новый Массив;

// Обрабатываем каждую строку данных
Для Каждого ТекущаяСтрока Из МассивСтрок Цикл
    // Пропускаем пустые строки или строки, содержащие только пробелы
    Если ПустаяСтрока(СокрЛП(ТекущаяСтрока)) Тогда
        Продолжить;
    КонецЕсли;

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

    Если РезультатПоиска.Количество() = 0 Тогда
        // Если в строке не найдено ни одного поля, пропускаем ее
        Продолжить;
    КонецЕсли;

    Если ИменаКолонок.Количество() = 0 Тогда
        // Это первая обработанная строка, считаем ее заголовком
        // Создаем колонки в ТаблицеЗначений
        Для Каждого ЭлементСовпадения Из РезультатПоиска Цикл
            ИмяКолонки = ЭлементСовпадения.Значение; // Получаем очищенное имя колонки
            // Проверяем, что имя колонки не пустое и не содержит только пробелы.
            // Хотя наше regex уже обрезает, дополнительная проверка не помешает
            Если Не ПустаяСтрока(ИмяКолонки) Тогда
                Попытка
                    ТаблицаДанных.Колонки.Добавить(ИмяКолонки);
                    ИменаКолонок.Добавить(ИмяКолонки);
                Исключение
                    // Обработка случая, если колонка с таким именем уже существует (дубликат в заголовке)
                    Сообщить("Ошибка добавления колонки '" + ИмяКолонки + "': " + ОписаниеОшибки());
                КонецПопытки;
            КонецЕсли;
        КонецЦикла;
    Иначе
        // Это строка данных, заполняем новую строку ТаблицыЗначений
        НоваяСтрокаТЗ = ТаблицаДанных.Добавить();
        Для Счетчик = 0 По РезультатПоиска.Количество() - 1 Цикл
            Если Счетчик < ИменаКолонок.Количество() Тогда
                ИмяКолонки = ИменаКолонок[Счетчик];
                ЗначениеПоля = РезультатПоиска[Счетчик].Значение; // Получаем очищенное значение поля
                Попытка
                    НоваяСтрокаТЗ[ИмяКолонки] = ЗначениеПоля;
                Исключение
                    // Обработка случая, если колонка не найдена (например, из-за ошибки в заголовке или данных)
                    Сообщить("Ошибка присвоения значения '" + ЗначениеПоля + "' колонке '" + ИмяКолонки + "' по индексу " + Счетчик + ": " + ОписаниеОшибки());
                КонецПопытки;
            Иначе
                // Если количество полей в строке данных превышает количество колонок заголовка
                Сообщить("Предупреждение: Количество полей в строке данных превышает количество колонок в заголовке. Лишнее поле: '" + РезультатПоиска[Счетчик].Значение + "'");
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
КонецЦикла;

// Выведем результат для проверки
Сообщить("--- Результат парсинга в ТаблицуЗначений ---");
Если ТаблицаДанных.Количество() > 0 Тогда
    Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
        СтрокаДляСообщения = "";
        Для Каждого КолонкаТЗ Из ТаблицаДанных.Колонки Цикл
            СтрокаДляСообщения = СтрокаДляСообщения + КолонкаТЗ.Имя + ": """ + СтрокаТЗ[КолонкаТЗ.Имя] + """; ";
        КонецЦикла;
        Сообщить(СтрокаДляСообщения);
    КонецЦикла;
Иначе
    Сообщить("Таблица данных пуста или не удалось распарсить.");
КонецЕсли;

В этом примере мы делаем следующее:

  1. Разбиваем исходную многострочную строку на отдельные строки с помощью СтрРазделить.
  2. Используем универсальное регулярное выражение "(?<=\|)\s*(.*?)\s*(?=\|)" для каждой строки, чтобы найти все поля.
  3. Первую непустую строку интерпретируем как заголовок таблицы и используем извлечённые значения для создания колонок в ТаблицаЗначений.
  4. Последующие строки интерпретируем как строки данных, создаём новую строку в ТаблицаЗначений и заполняем её значениями, извлечёнными регулярным выражением.

Обратите внимание, что регулярное выражение само по себе не обрабатывает начальный и конечный разделители всей строки (например, | в самом начале и в самом конце строки). Оно ищет совпадения *между* разделителями. В нашем случае это приводит к желаемому результату, так как метод СтрНайтиВсеПоРегулярномуВыражению обнаружит все подстроки, соответствующие паттерну, даже если они расположены друг за другом.

Дополнительные соображения и оптимизация

Мы добились нашей основной цели: извлекать поля из строк с разделителями, автоматически удаляя пробелы и корректно обрабатывая пустые поля, без необходимости использования СокрЛП().

Однако, давайте рассмотрим несколько дополнительных моментов, которые могут быть полезны:

  1. Гибкость регулярного выражения для содержимого поля

    Если содержимое поля имеет очень специфичный формат (например, только числа, только GUIDы), вы можете заменить .*? на более строгий шаблон. Например, для GUID это может быть \{[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}\}. Однако, для общей задачи парсинга произвольных текстовых полей .*? является наиболее универсальным и подходящим решением.

  2. Производительность

    Как справедливо было отмечено в обсуждении, регулярные выражения могут работать очень быстро. При правильном составлении они часто превосходят по производительности ручной разбор строк с помощью циклов и функций Найти() или Сред() для сложных паттернов. Для больших объёмов данных или часто повторяющихся операций парсинга, использование регулярок — это эффективный выбор.

  3. Обработка ошибок и валидация

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

Заключение

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

Мы рассмотрели, как построить регулярное выражение, которое извлекает очищенные значения полей из пайп-разделённой строки, демонстрируя его применение для заполнения ТаблицаЗначений. Этот подход позволяет вам глубже понять возможности регулярных выражений и эффективно применять их в вашей работе с 1С.

← На главную