Цифры и числа из текста: разбор средствами встроенного языка 1С

· 9 мин

Содержание

Выделить число из строки в 1С можно тремя способами: перебрать строку посимвольно через Сред() и собрать цифры, разрезать её функцией СтрРазделить() по всем нецифровым символам или найти числа регулярным выражением СтрНайтиВсеПоРегулярномуВыражению() (платформа 8.3.23 и новее). Найденный фрагмент превращают в число функцией Число() внутри Попытка…Исключение.

Какой способ выбрать, решают две вещи: версия платформы и формат чисел в тексте. Целые числа из комментария или артикула легко достать любым методом. Суммы вида 1 250,75 или -12.5 требуют отдельной обработки разделителей и минуса. Ниже — рабочий код для каждого случая.

Посимвольный перебор: как выделить цифры из строки в 1С

Самый надёжный метод. Работает на любой платформе 8.3, на клиенте и на сервере, под Windows и Linux.

Идея простая: идём по строке от первого символа до последнего и проверяем, цифра ли перед нами. Для проверки удобно искать символ в строке-эталоне "0123456789".

Все цифры подряд одной строкой

Нужен, когда из Арт. 12-45/7 надо получить 12457 — например, чтобы очистить телефон или ИНН от пробелов и скобок.

Функция ТолькоЦифры(Знач Текст) Экспорт
    Цифры = Новый Массив;
    Для Позиция = 1 По СтрДлина(Текст) Цикл
        Символ = Сред(Текст, Позиция, 1);
        Если СтрНайти("0123456789", Символ) > 0 Тогда
            Цифры.Добавить(Символ);
        КонецЕсли;
    КонецЦикла;
    Возврат СтрСоединить(Цифры, "");
КонецФункции

Результат — строка, а не число. Это правильно: у телефона +7 (900) 012-34-56 или артикула 00745 ведущие нули значимы, а Число("00745") их съест.

Каждое число отдельно

Если в тексте Счёт 154 от 12.03, сумма 4800 нужно получить три отдельных значения, собираем цифры в буфер и сбрасываем его при встрече нецифрового символа.

Функция ЦелыеЧислаИзСтроки(Знач Текст) Экспорт
    Результат = Новый Массив;
    Буфер = "";
    Текст = Текст + " "; // символ-сторож, чтобы сбросить последнее число
    Для Позиция = 1 По СтрДлина(Текст) Цикл
        Символ = Сред(Текст, Позиция, 1);
        Если СтрНайти("0123456789", Символ) > 0 Тогда
            Буфер = Буфер + Символ;
        ИначеЕсли Буфер <> "" Тогда
            Результат.Добавить(Число(Буфер));
            Буфер = "";
        КонецЕсли;
    КонецЦикла;
    Возврат Результат;
КонецФункции

Для строки из примера функция вернёт массив 154, 12, 3, 4800. Дата 12.03 развалилась на два числа — это ожидаемо, дробную часть и даты разбираем ниже.

💡 Вместо СтрНайти() можно сравнивать коды символов: КодСимвола(Текст, Позиция) от 48 до 57 — это цифры 0–9. На длинных строках так чуть быстрее, потому что не создаётся подстрока на каждой итерации.

Как выделить числа из строки без цикла по символам

Функция СтрРазделить() (платформа 8.3.6 и новее) считает разделителем каждый символ строки-разделителя. Значит, если передать ей все нецифровые символы текста, она вернёт только группы цифр.

Получить набор «всё, кроме цифр» можно, удалив из копии текста десять цифр:

Функция ГруппыЦифр(Знач Текст) Экспорт
    НеЦифры = Текст;
    Для Каждого Цифра Из СтрРазделить("0,1,2,3,4,5,6,7,8,9", ",") Цикл
        НеЦифры = СтрЗаменить(НеЦифры, Цифра, "");
    КонецЦикла;

    Если НеЦифры = "" Тогда // строка целиком из цифр
        Результат = Новый Массив;
        Результат.Добавить(Текст);
        Возврат Результат;
    КонецЕсли;

    Возврат СтрРазделить(Текст, НеЦифры, Ложь);
КонецФункции

Третий параметр Ложь выбрасывает пустые элементы, которые получаются между соседними разделителями. На выходе — массив строк вроде "154", "12", "03", "4800".

Метод хорош тем, что вся тяжёлая работа идёт внутри платформенных функций: 10 вызовов СтрЗаменить() и один СтрРазделить() вместо цикла по каждому символу. На строках в сотни символов разница с перебором заметна.

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

Регулярные выражения в 1С для поиска чисел

С платформы 8.3.23 во встроенном языке есть функции для работы с регулярными выражениями. Для нашей задачи нужны две:

ФункцияЧто возвращает
СтрНайтиПоРегулярномуВыражению(Строка, Выражение)первое совпадение
СтрНайтиВсеПоРегулярномуВыражению(Строка, Выражение)массив всех совпадений

Каждое совпадение — объект со свойствами НачальнаяПозиция, Длина и Значение. Найденный текст лежит в Значение.

Встроенные функции платформы

Функция ЧислаРегуляркой(Знач Текст) Экспорт
    Результат = Новый Массив;
    Совпадения = СтрНайтиВсеПоРегулярномуВыражению(Текст, "-?[0-9]+([.,][0-9]+)?");
    Для Каждого Совпадение Из Совпадения Цикл
        Фрагмент = СтрЗаменить(Совпадение.Значение, ",", ".");
        Результат.Добавить(Число(Фрагмент));
    КонецЦикла;
    Возврат Результат;
КонецФункции

Разбор шаблона -?[0-9]+([.,][0-9]+)?:

  • 🔹 -? — необязательный минус;
  • 🔹 [0-9]+ — одна или больше цифр целой части;
  • 🔹 ([.,][0-9]+)? — необязательная дробная часть через точку или запятую.

Класс [0-9] вместо \d выбран сознательно: он одинаково понимается во всех диалектах регулярных выражений, и при переносе шаблона в другой механизм ничего не сломается.

COM-объект VBScript.RegExp для старых платформ

На платформе ниже 8.3.23 под Windows регулярные выражения берут из системного COM-объекта:

РегВыр = Новый COMОбъект("VBScript.RegExp");
РегВыр.Pattern = "-?\d+([.,]\d+)?";
РегВыр.Global = Истина; // искать все совпадения, а не только первое

Числа = Новый Массив;
Для Каждого Совпадение Из РегВыр.Execute(Текст) Цикл
    Числа.Добавить(Число(СтрЗаменить(Совпадение.Value, ",", ".")));
КонецЦикла;

Ограничения у способа серьёзные:

  • ❌ не работает на сервере 1С под Linux и в веб-клиенте;
  • ❌ Microsoft объявила VBScript устаревшим, в будущих версиях Windows компонент может отсутствовать;
  • ❌ при каждом Новый COMОбъект тратится время на создание объекта.

Для новой разработки на актуальной платформе лучше встроенные функции. COM оставьте для поддержки старого кода.

Минус, дробная часть и разделители разрядов

Реальные строки редко содержат «чистые» числа. Суммы из банковской выписки, Excel или письма поставщика приходят в разных форматах:

В текстеЧто мешаетЧто делать
1 250,75пробел внутри числа, запятаяубрать пробелы, запятую заменить точкой
1 250,75 с неразрывным пробеломСимволы.НПП выглядит как пробелзаменить Символы.НПП на пустую строку
-12.5минусучитывать - перед цифрой
1,250.75запятая — разделитель тысячудалить запятые до замены точки
12.03.2024дата похожа на дробьразбирать как дату, а не как число

Если формат источника известен заранее, нормализуйте строку под него. Если нет — пишите парсер, который берёт не больше одного десятичного разделителя и только если за ним идёт цифра:

Функция ЭтоЦифра(Символ)
    Возврат СтрДлина(Символ) = 1 И СтрНайти("0123456789", Символ) > 0;
КонецФункции

Функция ДробныеЧислаИзСтроки(Знач Текст) Экспорт
    Результат = Новый Массив;
    Текст = СтрЗаменить(Текст, Символы.НПП, "");
    Длина = СтрДлина(Текст);
    Позиция = 1;

    Пока Позиция <= Длина Цикл
        Символ = Сред(Текст, Позиция, 1);
        ЭтоМинус = (Символ = "-" И ЭтоЦифра(Сред(Текст, Позиция + 1, 1)));
        Если Не ЭтоЦифра(Символ) И Не ЭтоМинус Тогда
            Позиция = Позиция + 1;
            Продолжить;
        КонецЕсли;

        Начало = Позиция;
        Позиция = Позиция + 1;
        БылРазделитель = Ложь;
        Пока Позиция <= Длина Цикл
            Символ = Сред(Текст, Позиция, 1);
            Если ЭтоЦифра(Символ) Тогда
                Позиция = Позиция + 1;
            ИначеЕсли (Символ = "," Или Символ = ".") И Не БылРазделитель
                И ЭтоЦифра(Сред(Текст, Позиция + 1, 1)) Тогда
                БылРазделитель = Истина;
                Позиция = Позиция + 1;
            Иначе
                Прервать;
            КонецЕсли;
        КонецЦикла;

        Фрагмент = Сред(Текст, Начало, Позиция - Начало);
        Результат.Добавить(Число(СтрЗаменить(Фрагмент, ",", ".")));
    КонецЦикла;

    Возврат Результат;
КонецФункции

Прогоним на примерах:

  • 🔸 Оплата -1250,75 руб. → -1250.75;
  • 🔸 Скидка 5.5% на 3 позиции → 5.5 и 3;
  • 🔸 Дата 12.03.2024 → 12.03 и 2024.

Последний пример показывает предел подхода. Функция не знает, что перед ней дата. Если в тексте бывают даты, вырежьте их заранее или ищите шаблоном вида [0-9]{2}\.[0-9]{2}\.[0-9]{4} и обрабатывайте отдельно.

Дефис в артикулах (А-12) парсер примет за минус и вернёт -12. Для артикулов и номеров документов минус не учитывайте: уберите условие ЭтоМинус.

Преобразование строки в число без падения кода

Функция Число() при неудачном преобразовании вызывает исключение с текстом Преобразование значения к типу Число не может быть выполнено. В обработке, которая разбирает тысячу строк, одна кривая строка остановит всё.

Через Попытка…Исключение

Функция СтрокаВЧислоБезопасно(Знач Текст, ПоУмолчанию = Неопределено) Экспорт
    Текст = СокрЛП(Текст);
    Текст = СтрЗаменить(Текст, Символы.НПП, "");
    Текст = СтрЗаменить(Текст, " ", "");
    Текст = СтрЗаменить(Текст, ",", ".");
    Если Текст = "" Тогда
        Возврат ПоУмолчанию;
    КонецЕсли;
    Попытка
        Возврат Число(Текст);
    Исключение
        Возврат ПоУмолчанию;
    КонецПопытки;
КонецФункции

Значение по умолчанию Неопределено, а не 0, — намеренно. Так вызывающий код отличает «в строке ноль» от «в строке мусор».

Через ОписаниеТипов

ОписаниеЧисла = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
Сумма = ОписаниеЧисла.ПривестиЗначение("1250.75"); // 1250.75

ПривестиЗначение() не бросает исключение: если строку привести нельзя, вернёт 0. Заодно значение обрезается под квалификатор — 15 знаков, из них 2 после запятой. Минус способа — ноль и ошибка неразличимы.

Функции БСП

В типовых конфигурациях на Библиотеке стандартных подсистем есть готовые функции в общем модуле СтроковыеФункцииКлиентСервер. Например, ТолькоЦифрыВСтроке() проверяет, что строка состоит из одних цифр. Набор функций модуля зависит от версии БСП, поэтому перед использованием откройте модуль в конфигураторе и посмотрите, что в нём есть.

Как ускорить выделение чисел на больших объёмах

Когда парсер разбирает 100–200 тысяч строк из регистра или файла, мелочи складываются в минуты.

Какой способ выделения чисел вам подходит?

Что даёт реальный выигрыш:

  1. Фильтруйте в запросе. Строки без цифр не нужно тащить в код. Условие ГДЕ Таблица.Комментарий ПОДОБНО "%[0-9]%" оставит только записи, где есть хотя бы одна цифра.
  2. Не склеивайте длинные строки в цикле. Конструкция Результат = Результат + Символ на каждой итерации создаёт новую строку. Собирайте символы в массив и соединяйте одним СтрСоединить().
  3. Создавайте COM-объект один раз. Новый COMОбъект("VBScript.RegExp") выносите за пределы цикла по строкам.
  4. Разбирайте на сервере. Обработку таблицы целиком передайте в серверную процедуру. Сотни вызовов сервера из клиентского цикла медленнее самого парсинга.
  5. Замеряйте, а не угадывайте. Включите замер производительности в отладчике конфигуратора и посмотрите, какая строка съедает время.

💡 Для одного и того же формата результат разбора можно кешировать в Соответствии: ключ — исходная строка, значение — массив чисел. В выписках и прайсах одинаковые строки повторяются часто.

Частые ошибки при разборе чисел

  • 🐞 Пробел, которого не видно. Строка 1 250 из Excel не превращается в число, хотя пробел убран. Внутри стоит неразрывный пробел — удаляйте Символы.НПП отдельно.
  • 🐞 Потерянные ведущие нули. Артикул 00745 после Число() стал 745. Если значение — идентификатор, а не количество, храните его строкой.
  • 🐞 Точка как разделитель тысяч. В немецком формате 1.250,75 точка разделяет разряды. Простая замена запятой на точку даст 1.250.75, и Число() упадёт. Сначала удаляйте разделитель разрядов, потом меняйте десятичный.
  • 🐞 Регулярка не на той платформе. Код с СтрНайтиВсеПоРегулярномуВыражению() не скомпилируется на платформе ниже 8.3.23. Проверьте номер версии на всех рабочих местах и на сервере.
  • 🐞 COM на Linux-сервере. Процедура с VBScript.RegExp работает у разработчика в файловой базе и падает в рабочей клиент-серверной под Linux.
  • 🐞 Склейка числа со строкой. Выражение "Сумма: " + 1250000 даст строку с разделителем разрядов. Для логов и ключей используйте Формат(Число, "ЧГ=0").

Частые вопросы

Можно ли выделить число из строки прямо в запросе 1С?
Нет. Язык запросов не умеет приводить строку к числу. Запросом можно только отобрать строки с цифрами через ПОДОБНО “%[0-9]%”, а разбирать их в коде.
Как получить номер документа без префикса?
Номер документа — строка. Отрежьте нецифровую часть слева перебором или функцией ГруппыЦифр и возьмите последний элемент массива. Число() применяйте, только если нужно сравнивать номера арифметически.
Чем Число() отличается от ОписаниеТипов.ПривестиЗначение()?
Число() бросает исключение на некорректной строке. ПривестиЗначение() возвращает 0 и дополнительно приводит значение к разрядности и точности квалификатора.
Как найти только первое число в строке?
На платформе 8.3.23 и новее — функцией СтрНайтиПоРегулярномуВыражению. На старых платформах — посимвольным перебором с выходом из цикла через Прервать после первого найденного числа.
Почему СтрНайти("0123456789", Символ) надёжнее, чем Число(Символ) в Попытке?
Исключение — дорогая операция. Если ловить его на каждом нецифровом символе, разбор длинного текста замедлится в разы. Поиск символа в строке-эталоне исключений не вызывает.