電話番号の正規表現を割と真面目に考えることにした!自分の中ではもうこれは決定版か?

電話番号にはルールがある
http://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/number_shitei.html


切り取るにもルールを設けよう

    1. まず市外局番・市内局番・加入者番号を取り出したい
    2. 市内局番には括弧が付いているかもしれないのでそれを考慮
    3. 携帯番号にも対応させたい 090-1234-5678 or 090-123-4567
    4. フリーダイヤルにも対応させたい 0120-123-456 or 0120-12-3456
    5. ついでにIP電話とかにも対応させたい
    6. できる範囲でその番号が正しいのかチェックも兼ねたい

さて考えよ!>俺


で、2日間考えた。
先読み否定が良いものか?
先読み肯定がよいものか?


そのへんにあるものは、03(1234-5678 も通るだろう?
俺のは通さない!


で、完成した。
意地でも使おうっと。

  let ary = myreg.exec (str);
  if (ary)
    let [, ...tel] = ary;
    return tel.join ('-');
  else
    throw new Error ('電話番号の間違い') 
<!DOCTYPE html>
<html lang="ja">
<meta charset="UTF-8">
<title>Telephone</title>
<style>
table {
  font-size: x-small;
  width: 100%;
}
tr.red {
  background: #f88;
}

</style>

<body>
<table border="1"></table>


<script>
let ary = [
'x:5x4321',
'x:56-43215123456789',
'x:56-43215',
'x:56-432',
'x:2-43215',
'o:5-4321',
'o: 5-4321',
'o:5-4321    ',
'o: 5-4321 ',
'o:65-4321',
'o:765-4321',
'o:8765-4321',

'o:(5)4321',
'o:(65)4321',
'o:(765)4321',
'o:(8765)4321',

'x:012345-6-7890',
'o:01234-5-6789',
'o:0123-45-6789',
'o:012-345-6789',
'o:03-2345-6789',
'x:0-12345-6789',
'x:09-2345-6789',

'x:0120-1-23456',
'o:0120-12-3456',
'o:0120-123-456',
'x:0120-1234-56',
'o:0120-123-456',
'o: 0120-123-456',
'o:0120-123-456 ',
'o: 0120-123-456 ',

'o:01234(5)6789',
'o:0123(45)6789',
'o:012(345)6789',
'o:03(2345)6789',
'x:11(2345)6789',

'o:090-1234-5678',
'x:090-1234-56789',
'x:090-12344-5678',
'x:0901-1234-5678',
'x:030-1234-5678',
'x:190-1234-5678',
'x:290-1234-5678',
'x:390-1234-5678',
'x:390/1234/5678',
'x:403-1234-567891',
'o:090(8765)4321',
'o: 090(8765)4321',
'o:090(8765)4321 ',
'o: 090(8765)4321 ',
'o:0120(34)5678',
];


//先読みで書式を選別し、利用できる番号で選別し、最後は緩く抜き出す

let
  hyphen = '\\-',
  num = '\\d+',
  local = '\\d{4}',
  delimiter = '\\D',
  OR = '|';
  
let
  type1 = '(?:' + num + hyphen + ')?' + num + hyphen + num, // x-x-x
  type2 = '(?:' + num + ')?\\(' + num + '\\)' + num,        // x(x)x
  format  = type1 + OR + type2;


let fixed1 = '(?:0[1-9][1-9][0-9]{2}' + delimiter + ')?' + delimiter + '?[2-9]' + delimiter + local;   //0xxxx-2-xxxx
let fixed2 = '(?:0[1-9][1-9][0-9]' + delimiter + ')?' + delimiter + '?[2-9][0-9]' + delimiter + local; //0xxx-23-xxxx
let fixed3 = '(?:0[1-9][1-9]' + delimiter + ')?' + delimiter + '?[2-9][0-9]{2}' + delimiter + local;   //0xx-234-xxxx
let fixed4 = '(?:0[36]' + delimiter + ')?' + delimiter + '?[2-9][0-9]{3}' + delimiter + local;         //0x-2345-xxxx

let fixed  = fixed1 + OR + fixed2 + OR + fixed3 + OR + fixed4;                            //固定電話
let free   = '0120' + delimiter + '(?:\\d{3}' + delimiter + '\\d{3}' + OR + '\\d{2}' + delimiter + local + ')'; //フリーダイヤル
let mobile = '0[5789]0' + delimiter + '(?:[0-9]{3}' + delimiter + '\\d{5}' + OR + '\\d{4}' + delimiter + local + ')';    //携帯電話
let phone  = fixed + OR + free + OR + mobile;


let phone_pattern   = '^\\s*(?=' + format + ')' + '(?=(?:' + phone + ')\\s*$)';
let pattern = phone_pattern;
let pickup  = '(?:(' + num + ')' + delimiter + ')?' + delimiter + '?(' + num + ')' + delimiter + '(' + num + ')';//条件を経て最終的に抜き出す

let reg = new RegExp (pattern + pickup);

//__________________________________

let table = document.querySelector ('table');

for (let a of ary) {
  let tr = table.insertRow (-1);
  let ox;
  [,ox, a] = /^(o|x)\:(.*)/.exec (a);
  let r = reg.exec(a);
  let cols = r
    ? [ox, 'o', a].concat (r)
    : [ox, 'x', a].concat ([,,,,]);
     
  if (cols[1]!==ox)
    tr.className="red";
  for (c of cols) {
    let td = tr.insertCell(-1);
    td.textContent = c;
  }
}

console.log(reg);
</script>

結局のところどこで妥協するかなんだろうな。

/^\s*(?=(?:\d+\-)?\d+\-\d+|(?:\d+)?\(\d+\)\d+)(?=(?:(?:0[1-9][1-9][0-9]{2}\D)?\D?[2-9]\D\d{4}|(?:0[1-9][1-9][0-9]\D)?\D?[2-9][0-9]\D\d{4}|(?:0[1-9][1-9]\D)?\D?[2-9][0-9]{2}\D\d{4}|(?:0[36]\D)?\D?[2-9][0-9]{3}\D\d{4}|0120\D(?:\d{3}\D\d{3}|\d{2}\D\d{4})|0[5789]0\D(?:[0-9]{3}\D\d{5}|\d{4}\D\d{4}))\s*$)(?:(\d+)\D)?\D?(\d+)\D(\d+)/


原点に振り返ってみて思うこと。
なんで、市外・市内・加入番号に分けようとしたのだろう?
括弧付きの部分をハイフンに置き換えれば良いだけなのでは?
まぁ深く考えまい


電話番号の正規表現とは

国内は0から始まる10桁
携帯は11桁
番号の構成は、0・市外局番・市内局番・加入番号の順
市外局番の桁数と市内局番の桁数の和は5
市外局番が1桁は、3と6だけ
市外局番の2桁目は[1-9]、3桁目は0もある
市内局番は[2-9]で始まる
加入番号は4桁
市内局番は括弧が前後につける書式もある
携帯電話は、3・3.5桁が本当の書式で、3・4・4が普及している
フリーダイヤルは、4・3・3桁もあれば4・2・4もある(4・2・2・2なんてのも語呂合わせであるらしい)







JavaScript は、奥が深いと思っているが、正規表現もなかなかどうして。



追記
ちょっと修正した。
0120(34)5678
090(8765)4321
の形式も通るようにした。