Время на прочтение
13 мин
Количество просмотров 55K
Возникла задача автоматического форматирования телефонных номеров в виде страна (город) номер, и первым делом я обратился к существующим решениям.
К сожалению, оказалось, что все найденные решения основываются на обычном подгоне строки под пользовательский формат, имея ограниченную область применения и ошибки при выходе за ее пределы.
Для начала приведу обзор найденных решений. Тем, кому это не интересно, рекомендую прокрутить ниже до заголовка «Форматы телефонных номеров» — там уже представлен мой вариант разбора номера с ссылкой на код.
Всеуничтожающий примитив
(Найденное решение. Мое ниже)
Первое, на что я наткнулся — были сообщения на форумах и банки скриптов, предлагающие решения следующего плана:
<?
function phone_number($sPhone){
$sPhone = ereg_replace(«[^0-9]»,»,$sPhone);
if(strlen($sPhone) != 10) return(False);
$sArea = substr($sPhone, 0,3);
$sPrefix = substr($sPhone,3,3);
$sNumber = substr($sPhone,6,4);
$sPhone = «(«.$sArea.«)».$sPrefix.«-«.$sNumber;
return($sPhone);
}
?>
Один из простых вариантов шустрого форматирования телефонных номеров, но каждое такое решение ориентировано на телефонные номера из конкретной локальной зоны и не является решением задачи.
Форматирование с помощью sscanf
(Найденное решение. Мое ниже)
function formatPhone($phone) {
if (empty($phone)) return «»;
if (strlen($phone) == 7)
sscanf($phone, «%3s%4s», $prefix, $exchange);
else if (strlen($phone) == 10)
sscanf($phone, «%3s%3s%4s», $area, $prefix, $exchange);
else if (strlen($phone) > 10)
if(substr($phone, 0,1)==‘1’) {
sscanf($phone, «%1s%3s%3s%4s», $country, $area, $prefix, $exchange);
}
else{
sscanf($phone, «%3s%3s%4s%s», $area, $prefix, $exchange, $extension);
}
else
return «unknown phone format: $phone»;
$out = «»;
$out .= isset($country)? $country.‘ ‘: »;
$out .= isset($area)? ‘(‘. $area. ‘) ‘: »;
$out .= $prefix. ‘-‘. $exchange;
$out .= isset($extension)? ‘ x’. $extension: »;
return $out;
}
Не смотря на простое решение, эта функция уже умеет форматировать номера длиной 7, 10 и более цифр, но попадись ей номер из российской глубинки, она подавится и выдаст ошибочный результат.
Symfony, lib/helpers/PhoneHelper.php, format_phone
(Найденное решение. Мое ниже)
<?php
function format_phone($phone = », $convert = false, $trim = true)
{
// If we have not entered a phone number just return empty
if (empty($phone)) {
return »;
}// Strip out any extra characters that we do not need only keep letters and numbers
$phone = preg_replace(«/[^0-9A-Za-z]/», «», $phone);// Do we want to convert phone numbers with letters to their number equivalent?
// Samples are: 1-800-TERMINIX, 1-800-FLOWERS, 1-800-Petmeds
if ($convert == true) {
$replace = array(‘2’=>array(‘a’,‘b’,‘c’),
‘3’=>array(‘d’,‘e’,‘f’),
‘4’=>array(‘g’,‘h’,‘i’),
‘5’=>array(‘j’,‘k’,‘l’),
‘6’=>array(‘m’,‘n’,‘o’),
‘7’=>array(‘p’,‘q’,‘r’,‘s’),
‘8’=>array(‘t’,‘u’,‘v’), ‘9’=>array(‘w’,‘x’,‘y’,‘z’));// Replace each letter with a number
// Notice this is case insensitive with the str_ireplace instead of str_replace
foreach($replace as $digit=>$letters) {
$phone = str_ireplace($letters, $digit, $phone);
}
}// If we have a number longer than 11 digits cut the string down to only 11
// This is also only ran if we want to limit only to 11 characters
if ($trim == true && strlen($phone)>11) {
$phone = substr($phone, 0, 11);
} // Perform phone number formatting here
if (strlen($phone) == 7) {
return preg_replace(«/([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/», «$1-$2», $phone);
} elseif (strlen($phone) == 10) {
return preg_replace(«/([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/», «($1) $2-$3», $phone);
} elseif (strlen($phone) == 11) {
return preg_replace(«/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/», «$1($2) $3-$4», $phone);
}// Return original phone if not 7, 10 or 11 digits long
return $phone;
}
?>
Функция позволяет не только форматировать в XXX-XXXX, (XXX) XXX-XXXX и X (XXX) XXX-XXXX, но и конвертировать номера, написанные цифрами. Ограниченность функции в форматировании номеров длиной 7, 10 и 11 символов никак не подходит.
Форматы телефонных номеров
Из вики-статьи видно, что никакого простого и удобного паттерна для быстрого форматирования всех номеров не существует. Коды стран регистрируются, подобно доменным зонам, а коды городов — остаются на совести каждой из стран.
Другими словами, маршрутизация звонков идет по маске, начиная с кода страны: звонок, направленный в конкретную страну далее пробивает себе маршрут в соответствии с кодами области, города, района и т.д. начиная с самой левой цифры, пока последнее звено не перебросит его на конкретный телефонный/факсовый аппарат. Проблема усложняется еще и тем, что коды городов внутри стран точно так же не поддаются единой сквозной стандартизации, т.е. в худшем из вариантов для правильного форматирования номеров придется использовать двумерный массив с кодами стран и их городов.
На самом деле, все оказалось не так страшно. В каждой стране можно разделить все коды городов на две части: на те, что в большинстве своем совпадают по длине, и все остальные. Этого достаточно, чтобы резко сократить область перебора кодов при сравнении. Т.е. можно создать массив из данных по каждой стране вида:
<?
$data = Array(
‘Код страны’=>Array(
‘name’=>‘Имя страны’, // для удобства. Не будет использоваться.
‘cityCodeLength’=> обычная_длина_кода_города_для_этой_страны,
‘exceptions’=>Array(коды_городов_исключения),
)
);
?>
Затем провести предварительную обработку данных, дополнив его полями, сужающими область перебора, exceptions_max и exceptions_min — максимальной и минимальной длиной кода городов-исключений, соответственно. Также необходимо учесть страны, в которых коды городов начинаются на 0 — отразим эту «особенность» полем zeroHack. Как пример:
<?
$data = Array(
‘886’=>Array(
‘name’=>‘Taiwan’,
‘cityCodeLength’=>1,
‘zeroHack’=>false,
‘exceptions’=>Array(89,90,91,92,93,96,60,70,94,95),
‘exceptions_max’=>2,
‘exceptions_min’=>2
),
);
?>
После этого возьмем подходящие участки кода из решений выше и сделаем функцию форматирования:
<?
function phone($phone = », $convert = true, $trim = true)
{
global $phoneCodes; // только для примера! При реализации избавиться от глобальной переменной.
if (empty($phone)) {
return »;
}
// очистка от лишнего мусора с сохранением информации о «плюсе» в начале номера
$phone=trim($phone);
$plus = ($phone[ 0] == ‘+’);
$phone = preg_replace(«/[^0-9A-Za-z]/», «», $phone);
$OriginalPhone = $phone;// конвертируем буквенный номер в цифровой
if ($convert == true && !is_numeric($phone)) {
$replace = array(‘2’=>array(‘a’,‘b’,‘c’),
‘3’=>array(‘d’,‘e’,‘f’),
‘4’=>array(‘g’,‘h’,‘i’),
‘5’=>array(‘j’,‘k’,‘l’),
‘6’=>array(‘m’,‘n’,‘o’),
‘7’=>array(‘p’,‘q’,‘r’,‘s’),
‘8’=>array(‘t’,‘u’,‘v’),
‘9’=>array(‘w’,‘x’,‘y’,‘z’));foreach($replace as $digit=>$letters) {
$phone = str_ireplace($letters, $digit, $phone);
}
}// заменяем 00 в начале номера на +
if (substr($phone, 0, 2)==«00»)
{
$phone = substr($phone, 2, strlen($phone)-2);
$plus=true;
}// если телефон длиннее 7 символов, начинаем поиск страны
if (strlen($phone)>7)
foreach ($phoneCodes as $countryCode=>$data)
{
$codeLen = strlen($countryCode);
if (substr($phone, 0, $codeLen)==$countryCode)
{
// как только страна обнаружена, урезаем телефон до уровня кода города
$phone = substr($phone, $codeLen, strlen($phone)-$codeLen);
$zero=false;
// проверяем на наличие нулей в коде города
if ($data[‘zeroHack’] && $phone[ 0]==‘0’)
{
$zero=true;
$phone = substr($phone, 1, strlen($phone)-1);
}$cityCode=NULL;
// сначала сравниваем с городами-исключениями
if ($data[‘exceptions_max’]!= 0)
for ($cityCodeLen=$data[‘exceptions_max’]; $cityCodeLen>=$data[‘exceptions_min’]; $cityCodeLen—)
if (in_array(intval(substr($phone, 0, $cityCodeLen)), $data[‘exceptions’]))
{
$cityCode = ($zero? «0»: «»).substr($phone, 0, $cityCodeLen);
$phone = substr($phone, $cityCodeLen, strlen($phone)-$cityCodeLen);
break;
}
// в случае неудачи с исключениями вырезаем код города в соответствии с длиной по умолчанию
if (is_null($cityCode))
{
$cityCode = substr($phone, 0, $data[‘cityCodeLength’]);
$phone = substr($phone, $data[‘cityCodeLength’], strlen($phone)-$data[‘cityCodeLength’]);
}
// возвращаем результат
return ($plus? «+»: «»).$countryCode.‘(‘.$cityCode.‘)’.phoneBlocks($phone);
}
}
// возвращаем результат без кода страны и города
return ($plus? «+»: «»).phoneBlocks($phone);
}// функция превращает любое число в строку формата XX-XX-… или XXX-XX-XX-… в зависимости от четности кол-ва цифр
function phoneBlocks($number){
$add=»;
if (strlen($number)%2)
{
$add = $number[ 0];
$add .= (strlen($number)<=5? «-«: «»);
$number = substr($number, 1, strlen($number)-1);
}
return $add.implode(«-«, str_split($number, 2));
}// тесты
echo phone(«+38 (044) 226-22-04»).«<br />»;
echo phone(«0038 (044) 226-22-04»).«<br />»;
echo phone(«+79263874814»).«<br />»;
echo phone(«4816145»).«<br />»;
echo phone(«+44 (0) 870 770 5370»).«<br />»;
echo phone(«0044 (0) 870 770 5370»).«<br />»;
echo phone(«+436764505509»).«<br />»;
echo phone(«(+38-048) 784-15-46 «).«<br />»;
echo phone(«(38-057) 706-34-03 «).«<br />»;
echo phone(«+38 (044) 244 12 01 «).«<br />»;
?>
, где global $phoneCodes; — тот самый массив с информацией по всем странам.
Выведет
+380(44)226-22-04<br/>+380(44)226-22-04<br/>+7(926)387-48-14<br/>481-61-45<br/>+44(0870)770-53-70<br/>+44(0870)770-53-70<br/>+43(6764)50-55-09<br/>380(4878)415-46<br/>380(5770)634-03<br/>+380(44)244-12-01
Функция полностью решает поставленную задачу.
Из недостатков функции следует отметить отсутствие анализа медленных участков с целью оптимизаци, а также обработки телефонных номеров, где есть код города, но нет кода страны (в этом случае достаточно бить на блоки функцией phoneBlocks или воспользоваться одним из решений выше). При использовании ее в какой-либо реализации необходимо заменить глобальную переменную на ссылку в параметре, а также можно доработать или заменить формат вывода, за который отвечает функция phoneBlocks.
Самое интересное
Используя информацию с сайтов:
http://www.mtt.ru/info/def/index.wbp
http://www.hella.ru/code/codeuro.htm
http://www.scross.ru/guide/phone-global/
я собрал массив данных по всем представленным странам, включая города-исключения, флаги zeroHack, а также коды мобильных сетей. Код можно загрузить здесь.
Быстродействие
Вопреки всем самым пессимистичным ожиданиям, код отрабатывает 10.000 номеров менее чем за 2 секунды.
UPD Готовятся поправки:
- поддержка паттернов форматирования, принятых внутри конкретных стран («локально-принятые» нормы отображения номеров);
- добавление флага для указания, относительно какой страны выполнять форматирование номера;
- добавление параметра для указания формата вывода (в случае личных предпочтений и исключений);
- поддержка нелатинских буквенных номеров
- определение сотовых номеров и замена скобок на пробелы
UPD: Архив пропал с сервера, выложил на https://github.com/mrXCray/PhoneCodesСкоро будет обновление по поправкам выше + бонус.
Телефонные номера, которые пишут вещи, предназначены не только для корпораций с причудливыми номерами 1-800; вероятность того, что ваш номер телефона что-то заклинает. Если вам интересно, какие слова могут быть скрыты в вашем номере, проверьте номер телефона в Word. Этот веб-сервис позволяет вам конвертировать телефонный номер в слова и видеть список всех возможных буквенных комбинаций, которые позволяет этот номер.
Объем информации, предоставляемой этим сайтом, огромен, но если вам интересно, есть ли там какое-то определенное слово, вы всегда можете «Ctl» и «f» пройти по нему. Кроме того, вы можете прокрутить, пока слово не появится на вас.
К сожалению, об этой услуге: она имеет дело только с 7-значными телефонными номерами. В наш век мобильных телефонов это кажется мне менее полезным, чем, возможно, казалось бы в эпоху наземных линий связи и дальней связи.
- Найти слова, спрятанные в вашем номере телефона.
- Показывает все возможные комбинации букв в любом 7-значном номере телефона.
- Подавляющее количество информации.
- Аналогичный инструмент: PhoneSpell
Проверьте номер телефона для Word @ www.labrocca.com/phone
При настройке SIP-аккаунтов от некоторых операторов связи или регистрации с АТС, чьих настроек нет у нас в списке Шаблонов серверов может потребоваться вручную настроить правила конвертации номеров для исходящих звонков.
Перед отправкой телефона SIP-оператору, может потребоваться преобразование номера в его формат. Например, в телефоне номера часто записаны через 8, со скобками и пробелами. Такие номера для совместимости лучше привести к стандартному формату:
8 (926) 123-45-67 → 79261234567
Суть необходимого преобразования номера: удалить пробелы, скобки и дефисы. Цифру 8 в начале номера нужно преобразовать в код страны, например, для России это 7.
Такого рода преобразования делаются с помощью одного или нескольких правил регулярных выражений.
- Имеется 2 типа правил: содержащие знак = (равенство) и не содержащие его
- Все правила применяются по очереди
- Рекомендуем использовать символ ` (одинарная кавычка) в начале и в конце правил, чтобы отключить возможное преобразование части правила в смайлики.