Извлечение числа из текста средствами встроенного языка 1С
Содержание
Достать число из текстовой строки в 1С можно четырьмя путями: преобразовать строку целиком функцией Число(), вырезать нужный фрагмент по позиции через Сред() и СтрНайти(), перебрать строку посимвольно и собрать цифры вручную или применить регулярное выражение. Выбор зависит от того, насколько предсказуем формат: для чистого числа хватит одной функции, для «1 250,50 руб.» или артикула внутри фразы нужен разбор.
Ниже — рабочий код под каждый случай, разбор того, что ломает выделение числа (минус, дробь, разделитель разрядов), и способ честно измерить скорость, а не полагаться на ощущения.
Какой способ подобрать под вашу задачу
Прежде чем писать код, определите формат входной строки. От него зависит метод.
| Способ | Когда подходит | Ограничения |
|---|---|---|
Число() | Строка целиком — это число ("1250", "-12.5") | Падает с ошибкой на любом мусоре, нужен перехват |
Сред() + СтрНайти() | Число стоит на известной позиции или после метки (№, от) | Жёстко завязано на формат строки |
| Посимвольный перебор | Число «вкраплено» в произвольный текст | Код длиннее, сам следишь за знаком и дробью |
| Регулярное выражение | Формат заранее неизвестен, чисел может быть несколько | Нужна платформа новее или COM, есть накладные расходы |
Не усложняйте без нужды. Если на вход всегда приходит аккуратная строка из своего же кода, хватит Число() в блоке Попытка. Регулярные выражения берегите для разбора внешних данных: писем, выгрузок, наименований.
Число() в блоке Попытка — одна строка кода, перехватывает ошибку на некорректных данных.СтрНайти() + Сред(): находим якорь и вырезаем фрагмент нужной длины.РегулярноеВыражение (платформа не ниже 8.3.23) либо COM-объект на Windows.Прямое преобразование: функция Число() и перехват ошибок
Отдельной функции «выдели число» во встроенном языке 1С нет. Для приведения значения к числу служит Число(). Она берёт строку и возвращает числовой тип:
Результат = Число("1250"); // 1250
Результат = Число("-12.5"); // -12.5
Число() ожидает точку как десятичный разделитель и не принимает пробелы-разделители разрядов. Строки "12,5" и "1 250" приведут к ошибке. Поэтому перед вызовом строку нормализуют, а сам вызов оборачивают в Попытка, иначе любой нечисловой символ обрушит выполнение:
Функция СтрокаВЧисло(Исходная) Экспорт
Подготовленная = СокрЛП(Исходная);
Подготовленная = СтрЗаменить(Подготовленная, ",", ".");
Попытка
Возврат Число(Подготовленная);
Исключение
Возврат 0;
КонецПопытки;
КонецФункции
СокрЛП() убирает пробелы слева и справа, СтрЗаменить() меняет запятую на точку. Если после этого в строке остались буквы или лишние символы, Число() выбросит исключение и функция вернёт 0. При желании вместо нуля можно возвращать Неопределено — так вызывающий код отличит «ноль» от «не число».
Этот способ хорош, когда вы сами контролируете источник: поле ввода, колонку выгрузки, результат другого расчёта. Для свободного текста он не годится — там число надо сначала вычленить.
Вырезаем число по позиции: Лев(), Прав(), Сред() и СтрНайти()
Когда число стоит на предсказуемом месте, его проще вырезать, чем перебирать. Базовый набор строковых функций 1С:
- 📍
Лев(Строка, Длина)— N символов слева. - 📍
Прав(Строка, Длина)— N символов справа. - 📍
Сред(Строка, Начало, Длина)— фрагмент с заданной позиции. - 📍
СтрНайти(Строка, Искомое)— позиция подстроки или0. - 📍
СтрДлина(Строка)— длина строки.
Типичная задача — достать номер после метки. Пусть на входе "Заказ № 1234 от 05.10". Находим №, отступаем от него и вырезаем хвост до пробела:
Текст = "Заказ № 1234 от 05.10";
ПозицияМетки = СтрНайти(Текст, "№");
Если ПозицияМетки > 0 Тогда
Хвост = СокрЛП(Сред(Текст, ПозицияМетки + 1)); // "1234 от 05.10"
ПозицияПробела = СтрНайти(Хвост, " ");
Если ПозицияПробела > 0 Тогда
НомерСтрокой = Лев(Хвост, ПозицияПробела - 1); // "1234"
Иначе
НомерСтрокой = Хвост;
КонецЕсли;
Номер = Число(НомерСтрокой); // 1234
КонецЕсли;
Когда число стоит в самом конце строки, удобнее Прав(). Например, из "Счёт-фактура-1250" достаём хвост, пока это цифры:
Текст = "Счёт-фактура-1250";
Поз = СтрДлина(Текст);
Длина = 0;
Пока Поз >= 1 Цикл
Симв = Сред(Текст, Поз, 1);
Если СтрНайти("0123456789", Симв) > 0 Тогда
Длина = Длина + 1;
Поз = Поз - 1;
Иначе
Прервать;
КонецЕсли;
КонецЦикла;
НомерСтрокой = Прав(Текст, Длина); // "1250"
Цикл Пока идёт от конца строки к началу (в Для шаг назад не задаётся) и считает, сколько цифр подряд стоит справа, а Прав() вырезает ровно этот хвост.
Такой разбор мгновенный и прозрачный, но хрупкий: поменяется формат строки — и Сред() вырежет не то. Применяйте его, когда формат гарантирован (ваши же печатные формы, коды документов, строки обмена с фиксированной структурой). Для плавающего формата переходите к перебору.
СтрНайти() возвращает позицию первого вхождения. Если меток несколько ("№1 и №2"), вырежется только первая. Для всех вхождений используйте расширенную форму СтрНайти() с указанием начальной позиции или регулярное выражение.
Посимвольный перебор: универсальный разбор без зависимостей
Самый надёжный способ достать число из произвольного текста — пройти строку по одному символу и собрать только цифры. Он работает на любой платформе и в любой конфигурации, не требует COM и не зависит от версии.
Простейший вариант — оставить одни цифры:
Функция ТолькоЦифры(Исходная) Экспорт
Цифры = "0123456789";
Результат = "";
Для Поз = 1 По СтрДлина(Исходная) Цикл
Симв = Сред(Исходная, Поз, 1);
Если СтрНайти(Цифры, Симв) > 0 Тогда
Результат = Результат + Симв;
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Проверку «символ — цифра?» делаем через СтрНайти() по строке-эталону "0123456789": так не зависим от кодировки и регистра. Для "арт. A-1500/B" функция вернёт "1500".
Минус подхода виден сразу: для "1 250,50" склеятся разряды и пропадёт дробь — выйдет "125050". Поэтому для денег и дробей нужен разбор поумнее, с учётом знака и десятичного разделителя:
Функция ПервоеЧисло(Текст) Экспорт
Цифры = "0123456789";
Собрано = "";
Знак = "";
ТочкаЕсть = Ложь;
Для Поз = 1 По СтрДлина(Текст) Цикл
Симв = Сред(Текст, Поз, 1);
Если СтрНайти(Цифры, Симв) > 0 Тогда
Собрано = Собрано + Симв;
ИначеЕсли (Симв = "." ИЛИ Симв = ",") И ЗначениеЗаполнено(Собрано) И НЕ ТочкаЕсть Тогда
Собрано = Собрано + ".";
ТочкаЕсть = Истина;
ИначеЕсли Симв = "-" И НЕ ЗначениеЗаполнено(Собрано) Тогда
Знак = "-";
ИначеЕсли ЗначениеЗаполнено(Собрано) Тогда
Прервать; // число кончилось, выходим
КонецЕсли;
КонецЦикла;
Если НЕ ЗначениеЗаполнено(Собрано) Тогда
Возврат 0;
КонецЕсли;
Возврат Число(Знак + Собрано);
КонецФункции
Функция возвращает первое число в строке. Логика такая: цифры копим в Собрано; точку или запятую принимаем один раз и только после первой цифры; минус — только в самом начале; как встретили разделитель уже после накопленного числа, выходим по Прервать. Запятую сразу превращаем в точку, чтобы итоговую строку принял Число().
Остаётся одна тонкость — разделитель разрядов. В "1 250,50" пробел прервёт сбор на "1". Если суммы могут приходить с пробелами, уберите их заранее, учтя и неразрывный пробел:
Текст = СтрЗаменить(Текст, " ", ""); // обычный пробел
Текст = СтрЗаменить(Текст, Символ(160), ""); // неразрывный пробел
После очистки ПервоеЧисло("Итого: 1 250,50 руб.") вернёт 1250.5.
Регулярные выражения: когда формат заранее неизвестен
Если в строке несколько чисел или форматы разные, перебор обрастает условиями. Тут выручают регулярные выражения — язык шаблонов для поиска по образцу.
В платформе не ниже 8.3.23 доступен встроенный объект РегулярноеВыражение. Шаблон -?\d+(\.\d+)? описывает «необязательный минус, цифры, необязательную дробную часть»:
РегВыр = Новый РегулярноеВыражение("-?\d+(\.\d+)?");
Совпадения = РегВыр.НайтиСовпадения("цена 1250.50, скидка -300");
Для Каждого Совпадение Из Совпадения Цикл
Сообщить(Совпадение.Значение); // 1250.50, затем -300
КонецЦикла;
Метод НайтиСовпадения() возвращает коллекцию найденного, у каждого элемента есть свойство Значение. Точные имена методов и свойств сверьте в синтакс-помощнике своей версии платформы — объект развивается от релиза к релизу.
На платформе старше 8.3.23 нативного объекта нет. На Windows задачу решает COM-объект VBScript.RegExp:
РегВыр = Новый COMОбъект("VBScript.RegExp");
РегВыр.Pattern = "-?\d+([\.,]\d+)?";
РегВыр.Global = Истина;
Совпадения = РегВыр.Execute("цена 1250,50 и 300 шт");
Для Каждого Совпадение Из Совпадения Цикл
Сообщить(Совпадение.Value);
КонецЦикла;
COM-объект доступен только под Windows и требует, чтобы компонент был зарегистрирован в системе. На сервере 1С под Linux он не заработает — там выбирайте нативный объект или перебор.
Коротко про сам шаблон: \d — любая цифра, + — одна или больше, ? — ноль или один раз, -? — необязательный минус в начале, [\.,] — точка или запятая. Этого набора хватает, чтобы выловить и целые, и дробные значения.
Знак, дробь и разделитель разрядов: что ломает разбор
Большинство ошибок при выделении числа из строки — это крайние случаи, которые забыли учесть. Разберём их отдельно.
- ➖ Отрицательные числа. Минус живёт только в начале числа. В перемешанном тексте легко принять дефис из артикула (
"A-1500") за знак. В примере выше минус засчитывается, лишь если цифр ещё не было, — дефис внутри кода не портит результат. - 🔢 Дробная часть. Решите заранее, какой разделитель ждёте. Нормализуйте запятую в точку перед
Число()и следите, чтобы точка встретилась один раз:"12.5.3"— это уже не число. - ␣ Разделитель разрядов. Пробел, неразрывный пробел или апостроф между разрядами надо вычистить до разбора, иначе число разорвётся. Удаляйте их через
СтрЗаменить(). - 📏 Длинные номера. Числовой тип 1С хранит до 38 знаков, но у реквизита в базе своя разрядность. Если тянете длинный штрихкод или ИНН, держите его строкой, а не числом, — иначе потеряете ведущие нули и упрётесь в точность.
Отдельно про ведущие нули: Число("007") даст 7, и "007" восстановить уже нельзя. Коды, артикулы и номера, где нули значимы, не переводите в число вообще — работайте со строкой.
Прежде чем писать разбор, соберите 5–10 реальных примеров входных строк и прогоните функцию на них. Крайние случаи всплывают именно на живых данных, а не в голове.
Как измерить скорость и что на неё влияет
Спорить, «что быстрее — перебор или регулярка», без замера бессмысленно: результат зависит от длины строк и их количества. Мерить просто — функцией ТекущаяУниверсальнаяДатаВМиллисекундах() до и после цикла:
Образец = "Заказ № 1250 на сумму 48 500,00 руб.";
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Счётчик = 1 По 100000 Цикл
Результат = ПервоеЧисло(Образец);
КонецЦикла;
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Старт;
Сообщить(СтрШаблон("Перебор: %1 мс на 100 000 вызовов", Длительность));
Обратите внимание на СтрШаблон() вместо склейки строки с числом через +: так число подставляется корректно и в любой локали.
Что реально влияет на скорость:
- 🏃 На коротких строках посимвольный перебор обычно расходует меньше всего времени — он не компилирует шаблон и не поднимает COM.
- 🐢 Регулярное выражение платит за компиляцию шаблона. Если разбираете строки в цикле, создавайте объект
РегулярноеВыражениеодин раз до цикла, а не внутри него. - 🧱 COM-объект самый тяжёлый на старте: его создание и вызовы через COM дороже нативного кода. Для разовой операции это незаметно, для сотен тысяч строк — ощутимо.
Практический вывод: для единичных строк берите то, что читается проще. Для пакетной обработки больших выгрузок сначала замерьте оба варианта на своих данных и только потом выбирайте.
Частые вопросы
Чем `Число()` отличается от попытки сложить строку с числом?
Число() — явное приведение типа и единственный корректный способ. Оператор + со строкой выполнит конкатенацию, а не преобразование, и вы получите склеенный текст вместо числа.Как достать сразу все числа из строки, а не первое?
Global у COM, НайтиСовпадения() у нативного объекта), либо перебором: не выходить по Прервать, а складывать найденные числа в массив.Почему `Число("1 250,50")` выдаёт ошибку?
Число() не принимает пробелы-разделители разрядов и ждёт точку как десятичный знак. Уберите пробелы через СтрЗаменить() и замените запятую на точку — тогда преобразование пройдёт.Как сохранить ведущие нули у номера или штрихкода?
Число("007") вернёт 7, нули потеряются безвозвратно. Храните и обрабатывайте такие коды строкой.Работает ли COM-объект `VBScript.RegExp` на сервере под Linux?
РегулярноеВыражение (платформа не ниже 8.3.23) или посимвольный перебор.