Как разобрать строку с номенклатурой, ценой и количеством в 1С: разбор надежных методов

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

При интеграции 1С с внешними системами (интернет-магазинами, кассовыми аппаратами, SMS-шлюзами или банками) разработчики часто сталкиваются с необходимостью парсинга слабоформализованных текстовых строк. Типичный пример такой строки — позиция чека или заказа, записанная в одну строчку: "Лизобакт таб. д/рассас. №30 275.00 x 1 = 275.00".

В этой статье мы подробно проанализируем, как решить эту задачу в среде 1С. Мы разберем простое решение, предложенное на форуме, выявим его критические уязвимости, которые обязательно приведут к сбоям в реальной эксплуатации, и предложим два альтернативных профессиональных метода: кроссплатформенный поиск справа налево и использование регулярных выражений (RegExp).

Простой метод: Разбиение строки по пробелам (Решение с форума)

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

Рассмотрим пример кода, который решает эту задачу "в лоб" для нескольких строк, разделенных переносом строки:


// Пример простого парсинга через СтрРазделить
МассивСтрокДокумента = СтрРазделить(ИсходныйТекст, Символы.ПС, Ложь);

Для Каждого ТекущаяСтрока Из МассивСтрокДокумента Цикл
    ЧастиСтроки = СтрРазделить(ТекущаяСтрока, " ", Ложь);
    РазмерМассива = ЧастиСтроки.Количество();
    
    // Проверяем, что в строке достаточно элементов для анализа
    Если РазмерМассива > 4 Тогда
        Попытка
            // Склеиваем наименование товара из начальных элементов массива
            Наименование = "";
            Для Индекс = 0 По РазмерМассива - 6 Цикл
                Наименование = СокрЛП(Наименование + " " + ЧастиСтроки[Индекс]);
            КонецЦикла;
            
            // Получаем числовые показатели по обратным индексам
            Цена = Число(ЧастиСтроки[РазмерМассива - 5]);
            Количество = Число(ЧастиСтроки[РазмерМассива - 3]);
            Сумма = Число(ЧастиСтроки[РазмерМассива - 1]);
            
            Сообщить(СтрШаблон("Успешно разобрано: %1 | Цена: %2 | Кол-во: %3 | Сумма: %4", 
                Наименование, Цена, Количество, Сумма));
        Исключение
            Сообщить("Ошибка формата в строке: " + ТекущаяСтрока);
        КонецПопытки;
    КонецЕсли;
КонецЦикла;

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

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

Почему простое разбиение по пробелам опасно: Анализ уязвимостей

Проанализируем ключевые факторы риска, которые делают код на основе СтрРазделить(..., " ") крайне нестабильным:

  1. Пробелы-разделители разрядов в числах. Если цена или сумма товара превысит 1000, система или внешняя программа может отформатировать число с разделителем триад (например, "1 700.00" вместо "1700.00"). В этом случае функция СтрРазделить разрежет одну только цену на два отдельных элемента массива. Вся логика обратных индексов (РазмерМассива - 5) мгновенно сдвинется, и программа запишет некорректные данные или вызовет исключение.
  2. Неоднозначность символа умножения. В исходной строке в качестве знака умножения может выступать латинская строчная "x", латинская заглавная "X", кириллическая "х", кириллическая "Х" или символ звездочки "*". Кроме того, вокруг знака умножения могут отсутствовать пробелы (например, "275.00x1"). В таком случае разделение по пробелу вообще не выделит количество как отдельный элемент.
  3. Региональные стандарты разделителей дробной части. В зависимости от настроек операционной системы (особенно при переносе кода на сервер под управлением Linux), разделителем дробной части числа может ожидаться запятая, а не точка. Попытка вызвать функцию Число() для строки "275.00" в системе с русской локалью без предварительной замены точки на запятую приведет к падению в блок Исключение.

Чтобы избежать этих проблем, рассмотрим значительно более устойчивые и профессиональные варианты реализации.

Альтернативный метод №1: Кроссплатформенный разбор строки справа налево

Если ваше решение должно работать в файловом и клиент-серверном вариантах как на Windows, так и на Linux (где использование COM-объектов невозможно), оптимальным выбором станет разбор строки с конца. Для этого мы будем использовать стандартные функции платформы 1С версии 8.3.6 и выше, поддерживающие поиск с конца строки.

Логика алгоритма базируется на том, что наименование номенклатуры (которое может содержать любые спецсимволы, цифры и пробелы) всегда находится слева. А вот структурированные данные (цена, количество, сумма) всегда идут в строгом порядке справа. Поэтому парсить строку нужно именно **справа налево**.

Разберем по шагам реализацию этого надежного алгоритма:


// Функция производит надежный разбор строки справа налево
Функция РазобратьСтрокуСправаНалево(Знач ВхСтрока) Экспорт
    
    Результат = Новый Структура("Наименование, Цена, Количество, Сумма, Успех", "", 0, 0, 0, Ложь);
    
    // Этап 1: Унификация символов умножения для упрощения поиска
    ВхСтрока = СтрЗаменить(ВхСтрока, "x", "х"); // латиница в кириллицу
    ВхСтрока = СтрЗаменить(ВхСтрока, "X", "х");
    ВхСтрока = СтрЗаменить(ВхСтрока, "Х", "х"); // заглавная кириллица в строчную
    ВхСтрока = СтрЗаменить(ВхСтрока, "*", "х"); // звездочка в единый символ "х"
    
    // Этап 2: Выделение Суммы (ищем первый символ "=" с конца строки)
    ПозицияРавно = СтрНайти(ВхСтрока, "=", НаправлениеПоиска.Конец);
    Если ПозицияРавно = 0 Тогда
        Возврат Результат; // Строка не соответствует формату
    КонецЕсли;
    
    ПодстрокаСуммы = СокрЛП(Сред(ВхСтрока, ПозицияРавно + 1));
    ВхСтрока = СокрЛП(Лев(ВхСтрока, ПозицияРавно - 1));
    
    // Этап 3: Выделение Количества (ищем наш унифицированный символ "х" с конца строки)
    ПозицияУмножения = СтрНайти(ВхСтрока, "х", НаправлениеПоиска.Конец);
    Если ПозицияУмножения = 0 Тогда
        Возврат Результат;
    КонецЕсли;
    
    ПодстрокаКоличества = СокрЛП(Сред(ВхСтрока, ПозицияУмножения + 1));
    ВхСтрока = СокрЛП(Лев(ВхСтрока, ПозицияУмножения - 1));
    
    // Этап 4: Разделение оставшейся части на Наименование и Цену
    // Цена — это последнее "слово" (число) перед знаком умножения, отделенное пробелом
    ПозицияПробелаЦены = СтрНайти(ВхСтрока, " ", НаправлениеПоиска.Конец);
    Если ПозицияПробелаЦены = 0 Тогда
        Возврат Результат;
    КонецЕсли;
    
    ПодстрокаЦена = СокрЛП(Сред(ВхСтрока, ПозицияПробелаЦены + 1));
    Наименование = СокрЛП(Лев(ВхСтрока, ПозицияПробелаЦены - 1));
    
    // Этап 5: Нормализация данных и приведение к числовым типам
    Попытка
        Результат.Сумма = ПреобразоватьВЧисло1С(ПодстрокаСуммы);
        Результат.Количество = ПреобразоватьВЧисло1С(ПодстрокаКоличества);
        Результат.Цена = ПреобразоватьВЧисло1С(ПодстрокаЦена);
        Результат.Наименование = Наименование;
        Результат.Успех = Истина;
    Исключение
        Результат.Успех = Ложь;
        ЗаписьЖурналаРегистрации("ПарсингСтроки", УровеньЖурналаРегистрации.Предупреждение, , , 
            "Не удалось преобразовать числа в строке: " + ВхСтрока);
    КонецПопытки;
    
    Возврат Результат;
КонецФункции

// Вспомогательная функция очистки чисел от мусора и пробелов
Функция ПреобразоватьВЧисло1С(Знач СтрЗначение)
    // Удаляем неразрывные пробелы (код 160), которые часто приходят из HTML или макетов
    СтрЗначение = СтрЗаменить(СтрЗначение, Символ(160), "");
    // Удаляем обычные пробелы (возможные разделители тысяч)
    СтрЗначение = СтрЗаменить(СтрЗначение, " ", "");
    // Приводим точку к запятой для универсальности преобразования в русской локали
    СтрЗначение = СтрЗаменить(СтрЗначение, ".", ",");
    
    Возврат Число(СтрЗначение);
КонецФункции

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

Альтернативный метод №2: Использование регулярных выражений (RegExp)

Для операционных систем семейства Windows идеальным и наиболее лаконичным решением является использование регулярных выражений через COM-объект VBScript.RegExp. Регулярные выражения позволяют проверить соответствие строки шаблону и мгновенно извлечь все группы данных (наименование, цену, количество и сумму) за один проход, полностью игнорируя вариативность пробелов.

Рассмотрим пример использования регулярных выражений для решения нашей задачи:


// Функция парсинга строки с использованием RegExp
Функция РазобратьСтрокуRegExp(Знач ВхСтрока) Экспорт
    
    Результат = Новый Структура("Наименование, Цена, Количество, Сумма, Успех", "", 0, 0, 0, Ложь);
    
    Попытка
        RegExp = Новый COMОбъект("VBScript.RegExp");
    Исключение
        ВызватьИсключение "На сервере не установлен компонент работы с регулярными выражениями VBScript.RegExp!";
    КонецПопытки;
    
    RegExp.IgnoreCase = Истина;
    RegExp.Global = Ложь;
    RegExp.MultiLine = Ложь;
    
    // Шаблон регулярного выражения:
    // 1-я группа (.*?): ленивый захват наименования с начала строки
    // 2-я группа ([\d\s, ]+(?:\.\d+)?): захват цены (цифры, пробелы, запятые, точки)
    // [xхXХ*]: гибкий разделитель количества (латинские/русские х, звездочка)
    // 3-я группа: количество
    // 4-я группа: сумма после знака "="
    RegExp.Pattern = "^(.*?)\s+([\d\s, ]+(?:\.\d+)?)\s*[xхXХ*]\s*([\d\s, ]+(?:\.\d+)?)\s*=\s*([\d\s, ]+(?:\.\d+)?)$";
    
    Если RegExp.Test(ВхСтрока) Тогда
        КоллекцияСовпадений = RegExp.Execute(ВхСтрока);
        Если КоллекцияСовпадений.Количество() > 0 Тогда
            НайденоСовпадение = КоллекцияСовпадений.Получить(0);
            SubMatches = НайденоСовпадение.SubMatches;
            
            Попытка
                Результат.Наименование = СокрЛП(SubMatches.Item(0));
                Результат.Цена = ПреобразоватьВЧисло1С(SubMatches.Item(1));
                ResultQty = SubMatches.Item(2);
                Результат.Количество = ПреобразоватьВЧисло1С(ResultQty);
                Результат.Сумма = ПреобразоватьВЧисло1С(SubMatches.Item(3));
                Результат.Успех = Истина;
            Исключение
                Результат.Успех = Ложь;
            КонецПопытки;
        КонецЕсли;
    КонецЕсли;
    
    Возврат Результат;
КонецФункции

Давайте подробно проанализируем структуру регулярного выражения ^(.*?)\s+([\d\s, ]+(?:\.\d+)?)...:

Сводная таблица сравнения методов

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

Критерий сравнения Разбиение по пробелам Поиск справа налево Регулярные выражения (RegExp)
Сложность реализации Очень простая Средняя Средняя
Устойчивость к пробелам в числах Низкая (код сломается) Высокая (полная обработка) Высокая (полная обработка)
Кроссплатформенность Да Да (1С 8.3.6+) Нет (только под ОС Windows)
Производительность Высокая Очень высокая Средняя
Устойчивость к разным знакам умножения Низкая Высокая Максимальная

Таким образом, для простых кроссплатформенных задач на современных конфигурациях 1С рекомендуется использовать Метод поиска справа налево. Если разработка ведется строго под Windows-инфраструктуру и требуется максимальная лаконичность кода — выбор за Регулярными выражениями.

← На главную