Нередко при импорте контактов или из-за некорректного ручного ввода пользователи заносят в поле номера телефона дополнительную информацию, например: «380000000000 (директор)» или «+7 (495) 111-22-33: Иванов А. Б.». В результате в базе скапливаются тысячи записей, где в одном строковом реквизите смешаны цифры номера и текстовые пояснения. Выясним причину этой проблемы и разберем практические способы программного разделения таких строк на чистый номер и комментарий.
Рассмотрим самый простой сценарий: номер расположен строго в начале строки, а за ним следует пояснение в скобках или через пробел. В этом случае мы можем последовательно перебирать символы строки с помощью функций Сред() и КодСимвола(). Как только алгоритм встречает символ с кодом вне диапазона цифр (коды ASCII от 48 до 57 соответствуют цифрам от 0 до 9), строка разбивается на две части.
Посмотрим на пример реализации:
ИсходнаяСтрока = СокрЛП(СтрокаКонтакта);
Длина = СтрДлина(ИсходнаяСтрока);
СтрокаНомер = "";
СтрокаКомментарий = "";
Для Инд = 1 По Длина Цикл
Символ = Сред(ИсходнаяСтрока, Инд, 1);
КодСимвола = КодСимвола(Символ);
Если КодСимвола >= 48 И КодСимвола <= 57 Тогда
СтрокаНомер = СтрокаНомер + Символ;
Иначе
СтрокаКомментарий = СокрЛП(Сред(ИсходнаяСтрока, Инд, Длина));
Прервать;
КонецЕсли;
КонецЦикла;
Такой подход отлично справляется с простыми случаями, когда номер состоит исключительно из слитных цифр, но не учитывает наличие в номере знаков «+», скобок или дефисов.
Проанализируем ситуацию, когда телефонный номер может содержать пробелы, а текстовый комментарий начинается со слов и заключен в круглые скобки. Мы можем сканировать строку до тех пор, пока не встретим первую букву алфавита, после чего отделим хвост строки как комментарий и удалим лишние обрамляющие знаки препинания.
Разберем по шагам данный алгоритм:
СтрокаНомер = "";
СтрокаКомментарий = "";
стрДлина = СтрДлина(ИсходнаяСтрока);
Для х = 1 По стрДлина Цикл
симв = Сред(ИсходнаяСтрока, х, 1);
// Проверяем, является ли символ цифрой
Если (симв >= "0") И (симв <= "9") Тогда
СтрокаНомер = СтрокаНомер + симв;
// Находим начало буквенного комментария
ИначеЕсли (симв >= "А" И симв <= "я") ИЛИ симв = "Ё" ИЛИ симв = "ё" Тогда
СтрокаКомментарий = Сред(ИсходнаяСтрока, х);
Прервать;
КонецЕсли;
КонецЦикла;
// Очищаем закрывающую скобку в конце комментария, если перед ним была открывающая скобка
Если Прав(СтрокаКомментарий, 1) = ")" И х > 1 И Сред(ИсходнаяСтрока, х - 1, 1) = "(" Тогда
СтрокаКомментарий = Лев(СтрокаКомментарий, СтрДлина(СтрокаКомментарий) - 1);
КонецЕсли;
СтрокаКомментарий = СокрЛП(СтрокаКомментарий);
Важный нюанс: при проверке диапазонов символов встроенного языка кириллические буквы «Ё» и «ё» имеют коды 1025 и 1105, которые не входят в непрерывный диапазон кодов от «А» до «я» (1040–1103). Их необходимо проверять отдельно, чтобы избежать логических ошибок при разборе фамилий или должностей.
Рассмотрим более гибкий подход, когда номер телефона может содержать форматирование («+7 (495) 111-22-33»), а за ним следует пояснение («Иванов А. Б.»). Алгоритм находит первую осмысленную букву, после чего выполняет шаг назад для поиска символа-разделителя (пробел, двоеточие или скобка) и делит исходную строку на две части.
Посмотрим на пример функции:
Функция РазделитьНомерИПояснение(ИсходнаяСтрока)
Длина = СтрДлина(ИсходнаяСтрока);
ПозицияТекста = 0;
// Ищем первый буквенный символ
Для Инд = 1 По Длина Цикл
Символ = ВРег(Сред(ИсходнаяСтрока, Инд, 1));
Если (Символ >= "А" И Символ <= "Я") ИЛИ (Символ >= "A" И Символ <= "Z") ИЛИ Символ = "Ё" Тогда
ПозицияТекста = Инд;
Прервать;
КонецЕсли;
КонецЦикла;
Если ПозицияТекста = 0 Тогда
Возврат Новый Структура("Номер, Комментарий", СокрЛП(ИсходнаяСтрока), "");
КонецЕсли;
// Отступаем назад, отсекая разделители
ПозицияРазделителя = ПозицияТекста;
Пока ПозицияРазделителя > 1 Цикл
СимволПред = Сред(ИсходнаяСтрока, ПозицияРазделителя - 1, 1);
Если Найти(" :-(", СимволПред) > 0 Тогда
ПозицияРазделителя = ПозицияРазделителя - 1;
Иначе
Прервать;
КонецЕсли;
КонецЦикла;
Номер = СокрЛП(Лев(ИсходнаяСтрока, ПозицияРазделителя - 1));
Комментарий = СокрЛП(Сред(ИсходнаяСтрока, ПозицияТекста));
// Удаляем концевую скобку у комментария, если она осталась
Если Прав(Комментарий, 1) = ")" Тогда
Комментарий = СокрЛП(Лев(Комментарий, СтрДлина(Комментарий) - 1));
КонецЕсли;
Возврат Новый Структура("Номер, Комментарий", Номер, Комментарий);
КонецФункции
Если в конфигурацию внедрена Библиотека стандартных подсистем, мы можем воспользоваться готовыми методами модуля СтроковыеФункцииКлиентСервер. Например, функция РазложитьСтрокуВМассивСлов() позволяет быстро выделить все нецифровые фрагменты строки, передав в качестве разделителей строку со всеми цифрами.
Разберем пример извлечения блоков:
Функция РазложитьНаМногострочнуюСВыделениемЦифр(ИсходнаяСтрока)
МассивНеСтрок = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивСлов(ИсходнаяСтрока, "0123456789");
ТекущийОтрезок = ИсходнаяСтрока;
Результат = "";
Пока НЕ ПустаяСтрока(ТекущийОтрезок) Цикл
Если МассивНеСтрок.Количество() = 0 Тогда
Результат = Результат + Символы.ПС + ТекущийОтрезок;
ТекущийОтрезок = "";
Иначе
ИндексНеЦифровогоОтрезка = Найти(ТекущийОтрезок, МассивНеСтрок[0]);
Результат = Результат + Символы.ПС + Лев(ТекущийОтрезок, ИндексНеЦифровогоОтрезка - 1);
Результат = Результат + Символы.ПС + МассивНеСтрок[0];
ТекущийОтрезок = Сред(ТекущийОтрезок, ИндексНеЦифровогоОтрезка + СтрДлина(МассивНеСтрок[0]));
МассивНеСтрок.Удалить(0);
КонецЕсли;
КонецЦикла;
Возврат СокрЛП(Результат);
КонецФункции
Кроме того, в типовых конфигурациях подсистема работы с контактной информацией предоставляет общий модуль УправлениеКонтактнойИнформациейКлиентСервер, функции которого умеют преобразовывать произвольную строку телефонного номера в каноническую структуру с полями номера, кода города и комментария.
Когда требуется обработать массив из 10 000 и более записей, необходимо позаботиться об оптимизации производительности:
Запрос, получая исключительно ссылку и поле контакта, а не динамической выборкой справочника.НачатьТранзакцию() и ЗафиксироватьТранзакцию(), чтобы не перегружать журнал СУБД.ОбменДанными.Загрузка = Истина у модифицируемого объекта. Это отключит лишние подписки на события и пользовательские проверки.