При ведении каталога товаров часто возникает задача нормализации данных. Например, техническое задание требует извлечь размер изделия из наименования (вида «Картонная коробка р.-17,5» или «Болт д. 10») и поместить его в реквизит Артикул. Разберем по шагам, как правильно реализовать подобный алгоритм на встроенном языке 1С, избежать типичных ошибок при работе со строками и оптимизировать производительность при массовой обработке справочника.
Проанализируем распространенные недочеты, которые часто допускают разработчики при решении этой задачи:
Прав(). Попытка вычислить длину остатка строки через разность общей длины и позиции вхождения часто приводит к смещению индексов или падению по ошибке выхода за границы строки, если префикс не найден. Намного проще и надежнее использовать функцию Сред().Выборка.Ссылка.Наименование заставляет платформу выполнять отдельный запрос к базе данных на каждой итерации цикла. Все необходимые поля следует выбирать сразу в запросе.Найти() дважды для одной и той же проверки и последующего вырезания подстроки неоправданно расходует процессорное время. Результат поиска следует сохранять в переменную.Записать() перезапишет элемент без изменений, потратив ресурсы СУБД впустую.Рассмотрим базовый алгоритм, в котором мы ищем заданный префикс (например, «р.-»), вычисляем позицию первого символа значения и обрезаем строку. Для надежности также используем функцию НРег(), чтобы не зависеть от регистра символов при проверке.
Посмотрим на пример оптимальной реализации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В ИЕРАРХИИ(&Папка)
| И НЕ Номенклатура.ЭтоГруппа
| И НЕ Номенклатура.ПометкаУдаления";
Запрос.УстановитьПараметр("Папка", ПапкаНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ИмяНоменклатуры = Выборка.Наименование;
ИмяВНижнемРегистре = НРег(ИмяНоменклатуры);
Префикс = "р.-";
Позиция = Найти(ИмяВНижнемРегистре, Префикс);
Если Позиция > 0 Тогда
// Смещаем позицию на длину префикса, чтобы получить сам размер
НовыйАртикул = СокрЛП(Сред(ИмяНоменклатуры, Позиция + СтрДлина(Префикс)));
// Проверяем, изменилось ли значение, чтобы не записывать лишний раз
Если ЗначениеЗаполнено(НовыйАртикул) И Выборка.Артикул <> НовыйАртикул Тогда
ОбъектНоменклатура = Выборка.Ссылка.ПолучитьОбъект();
ОбъектНоменклатура.ОбменДанными.Загрузка = Истина;
ОбъектНоменклатура.Артикул = НовыйАртикул;
ОбъектНоменклатура.Записать();
КонецЕсли;
КонецЕсли;
КонецЦикла;
На практике пользователи вносят размеры с разными обозначениями: «д.-», «диам.», «д. », «р-». Вместо громоздких конструкций Если ... ИначеЕсли разберем подход с обходом массива возможных шаблонов префиксов.
Посмотрим, как выглядит универсальный код:
// Формируем список возможных префиксов в нижнем регистре
СписокПрефиксов = Новый Массив;
СписокПрефиксов.Добавить("диам. ");
СписокПрефиксов.Добавить("диам.");
СписокПрефиксов.Добавить("д.-");
СписокПрефиксов.Добавить("д-");
СписокПрефиксов.Добавить("д. ");
СписокПрефиксов.Добавить("р.-");
СписокПрефиксов.Добавить("р-");
Пока Выборка.Следующий() Цикл
ИмяНоменклатуры = Выборка.Наименование;
ИмяВНижнемРегистре = НРег(ИмяНоменклатуры);
НовыйАртикул = "";
Для Каждого Префикс Из СписокПрефиксов Цикл
Позиция = Найти(ИмяВНижнемРегистре, Префикс);
Если Позиция > 0 Тогда
НовыйАртикул = СокрЛП(Сред(ИмяНоменклатуры, Позиция + СтрДлина(Префикс)));
Прервать; // Префикс найден, выходим из внутреннего цикла
КонецЕсли;
КонецЦикла;
Если ЗначениеЗаполнено(НовыйАртикул) И Выборка.Артикул <> НовыйАртикул Тогда
ОбъектНоменклатура = Выборка.Ссылка.ПолучитьОбъект();
ОбъектНоменклатура.ОбменДанными.Загрузка = Истина;
ОбъектНоменклатура.Артикул = НовыйАртикул;
ОбъектНоменклатура.Записать();
КонецЕсли;
КонецЦикла;
Функция Сред() считывает весь остаток строки до самого конца. Если после размера в наименовании указаны другие параметры (например, «Коробка р.-17,5 белая 100 шт»), в артикул попадет весь оставшийся текст. Чтобы выделить исключительно числовое значение размера (включая дробную часть с точкой или запятой), проанализируем использование регулярных выражений.
В современных версиях платформы (начиная с 8.3.23) можно применить функцию СтрНайтиПоРегулярномуВыражению(). Для более ранних платформ на операционных системах Windows задачу можно решить через COM-объект VBScript.RegExp:
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина;
RegExp.Global = Ложь;
// Ищем префиксы р, д, диам и захватываем само число (целое или дробное)
RegExp.Pattern = "(?:р|д|диам)\.?\s*[-–—]?\s*(\d+(?:[,\.]\d+)?)";
Пока Выборка.Следующий() Цикл
Матчи = RegExp.Execute(Выборка.Наименование);
Если Матчи.Count > 0 Тогда
// Извлекаем значение первой захваченной группы
Размер = Матчи.Item(0).SubMatches.Item(0);
Если Выборка.Артикул <> Размер Тогда
ОбъектНоменклатура = Выборка.Ссылка.ПолучитьОбъект();
ОбъектНоменклатура.ОбменДанными.Загрузка = Истина;
ОбъектНоменклатура.Артикул = Размер;
ОбъектНоменклатура.Записать();
КонецЕсли;
КонецЕсли;
КонецЦикла;
ОбменДанными.Загрузка = Истина отключает тяжелые подписки на события и встроенные проверки в модуле объекта при записи, ускоряя пакетную операцию в разы.Артикул в типовых конфигурациях составляет от 25 до 50 символов. Если алгоритм по ошибке захватит длинную строку, платформа обрежет её.Выборка.Артикул <> НовыйАртикул). Это исключает бесполезную запись и перезапись блокировок.Символы.НПП на стандартный пробел.