Цифры и числа из текста: разбор средствами встроенного языка 1С
Содержание
Выделить число из строки в 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 тысяч строк из регистра или файла, мелочи складываются в минуты.
Что даёт реальный выигрыш:
- Фильтруйте в запросе. Строки без цифр не нужно тащить в код. Условие
ГДЕ Таблица.Комментарий ПОДОБНО "%[0-9]%"оставит только записи, где есть хотя бы одна цифра. - Не склеивайте длинные строки в цикле. Конструкция
Результат = Результат + Символна каждой итерации создаёт новую строку. Собирайте символы в массив и соединяйте однимСтрСоединить(). - Создавайте COM-объект один раз.
Новый COMОбъект("VBScript.RegExp")выносите за пределы цикла по строкам. - Разбирайте на сервере. Обработку таблицы целиком передайте в серверную процедуру. Сотни вызовов сервера из клиентского цикла медленнее самого парсинга.
- Замеряйте, а не угадывайте. Включите замер производительности в отладчике конфигуратора и посмотрите, какая строка съедает время.
💡 Для одного и того же формата результат разбора можно кешировать в Соответствии: ключ — исходная строка, значение — массив чисел. В выписках и прайсах одинаковые строки повторяются часто.
Частые ошибки при разборе чисел
- 🐞 Пробел, которого не видно. Строка
1 250из Excel не превращается в число, хотя пробел убран. Внутри стоит неразрывный пробел — удаляйтеСимволы.НППотдельно. - 🐞 Потерянные ведущие нули. Артикул
00745послеЧисло()стал745. Если значение — идентификатор, а не количество, храните его строкой. - 🐞 Точка как разделитель тысяч. В немецком формате
1.250,75точка разделяет разряды. Простая замена запятой на точку даст1.250.75, иЧисло()упадёт. Сначала удаляйте разделитель разрядов, потом меняйте десятичный. - 🐞 Регулярка не на той платформе. Код с
СтрНайтиВсеПоРегулярномуВыражению()не скомпилируется на платформе ниже 8.3.23. Проверьте номер версии на всех рабочих местах и на сервере. - 🐞 COM на Linux-сервере. Процедура с
VBScript.RegExpработает у разработчика в файловой базе и падает в рабочей клиент-серверной под Linux. - 🐞 Склейка числа со строкой. Выражение
"Сумма: " + 1250000даст строку с разделителем разрядов. Для логов и ключей используйтеФормат(Число, "ЧГ=0").