2016/10/18 21:15:13

PHP正規表現 複数の要素を一回の実行で抽出する

php-logo
目次(クリックするとジャンプします)
  • 1:どうやって抽出する?
  • 2:カッコで囲んで抽出する
  • 3:配列の並びを変える
  • 4:複数抽出してみたら?
  • 5:カッコを入れ子にしてみたら?
  • 6:マッチしなかった場合、配列はどうなる?

どうやって抽出する?

preg_match_allを使う場合の話です。

例えば以下のような文字列があったとして、

googleのページはhttps://www.google.co.jp/で、wikipediaはhttps://ja.wikipedia.org/です。またyoutubeはhttps://www.youtube.comです。

URL全体とトップレベルドメインの両方を得たいとします。 つまり以下のような要素を抽出したいわけです。

  • https://www.google.co.jp/
  • https://ja.wikipedia.org/
  • https://www.youtube.com
  • .jp
  • .org
  • com

例文の場合、URLの全体はこんな感じの正規表現で抽出できます。

/https://[a-z.]*.[a-z]{2,3}/?/

でもトップレベルドメインの抽出はどうすれば良いのでしょうか?

2回正規表現で抽出しないといけないのかな 、あれ変だな、あれオカシイな、と思っていたのですが、ちゃんと一回で抽出できるんですね。これは便利です。

カッコで囲んで抽出する

具体的には抽出したい箇所を( )で囲みます。これで正規表現でマッチした結果とは別に( )で囲んだ箇所が抽出されます。

正規表現で言うとトップレベルドメインの箇所は.[a-z]{2,3}に応りますので、そこを( )で囲みます。

/https://[a-z.]*(.[a-z]{2,3})/?/

試してみましょう。以下のようなスクリプトを書いてみました。

実行してみます。

$php test.php

array(2) {
  [0]=>
  array(3) {
    [0]=>
    string(25) "https://www.google.co.jp/"
    [1]=>
    string(25) "https://ja.wikipedia.org/"
    [2]=>
    string(23) "https://www.youtube.com"
  }
  [1]=>
  array(3) {
    [0]=>
    string(3) ".jp"
    [1]=>
    string(4) ".org"
    [2]=>
    string(4) ".com"
  }
}

ちゃんとトップレベルドメインも抽出されていますね。

配列の並びを変える

preg_match_allはデフォルトでこんな感じの配列が返ってきます。

[0]正規表現全体で抽出した要素の配列
     [0]1個目にマッチした要素
     [1]2個目にマッチした要素
     [2]3個目にマッチした要素
[1]()で抽出した要素の配列
     [0]1個目にマッチした要素
     [1]2個目にマッチした要素
     [2]3個目にマッチした要素

場合によってはこの配列の並びが面倒なこともあるかもしれません。並びを変えることもできますよ。 preg_match_all の第4引数にPREG_SET_ORDERを適用します。

並びが変わりました。

array(3) {
  [0]=>
  array(2) {
    [0]=>
    string(25) "https://www.google.co.jp/"
    [1]=>
    string(3) ".jp"
  }
  [1]=>
  array(2) {
    [0]=>
    string(25) "https://ja.wikipedia.org/"
    [1]=>
    string(4) ".org"
  }
  [2]=>
  array(2) {
    [0]=>
    string(23) "https://www.youtube.com"
    [1]=>
    string(4) ".com"
  }
}

こんな感じの並びになるわけです。要素を同時に連続的に処理するならこっちの方が良いかもしれません。

[0]1個目の配列
     [0]1個目の正規表現全体で抽出した要素
     [1]1個目の()で抽出した要素
[1]2個目の配列
     [0]2個目の正規表現全体で抽出した要素
     [1]2個目の()で抽出した要素
[2]3個目の配列
     [0]3個目の正規表現全体で抽出した要素
     [1]3個目の()で抽出した要素

複数抽出してみたら?

URL全体とプロトコル部分(https://)とトップレベルドメインを抽出してみます。 ( )で囲まれた場所が2つになりました。

/(https://)([a-z.]*(.[a-z]{2,3})/?)/

配列の順番はより左側の( )で囲んでいる箇所が先になります。

array(3) {
  [0]=>
  array(3) {
    [0]=>
    string(25) "https://www.google.co.jp/"
    [1]=>
    string(8) "https://"
    [2]=>
    string(3) ".jp"
  }
  [1]=>
  array(3) {
    [0]=>
    string(25) "https://ja.wikipedia.org/"
    [1]=>
    string(8) "https://"
    [2]=>
    string(4) ".org"
  }
  [2]=>
  array(3) {
    [0]=>
    string(23) "https://www.youtube.com"
    [1]=>
    string(8) "https://"
    [2]=>
    string(4) ".com"
  }
}

カッコを入れ子にしてみたら?

URL全体とドメイン部分(https://以外の場所)とトップレベルドメインを抽出してみます。 以下の様に( )が入れ子になりました。

/https://([a-z.]*(.[a-z]{2,3})/?)/

配列の順番は外側の( )で抽出した箇所が先になります。

array(3) {
  [0]=>
  array(3) {
    [0]=>
    string(25) "https://www.google.co.jp/"
    [1]=>
    string(17) "www.google.co.jp/"
    [2]=>
    string(3) ".jp"
  }
  [1]=>
  array(3) {
    [0]=>
    string(25) "https://ja.wikipedia.org/"
    [1]=>
    string(17) "ja.wikipedia.org/"
    [2]=>
    string(4) ".org"
  }
  [2]=>
  array(3) {
    [0]=>
    string(23) "https://www.youtube.com"
    [1]=>
    string(15) "www.youtube.com"
    [2]=>
    string(4) ".com"
  }
}

マッチしなかった場合、配列はどうなる?

( )で囲まれた箇所がマッチしない場合どうなるでしょうか?

ちょっと例文を変えてテストしてみます(強引な感じの例文)。この例文からドメイン部分とトップレベルドメインを抽出するとします。

例文では故意にドメイン部分が抽出できないURL(http://hoge.com)を入れています。

抽出箇所を複数にすることで配列の並びに影響があるか確認してみます。

http://hoge.abc.com
http://hoge.dfg.com
http://hoge.hij.com
http://hoge.com

正規表現はこんな感じです。

/http://hoge.?[a-z]*(.com)/

抽出できなかった箇所は””(空文字)になりますので、配列がずれることはないようです。foreachなどで処理する際も安心デス。

  array(3) {
    [0]=>
    string(19) "http://hoge.abc.com"
    [1]=>
    string(3) "abc"
    [2]=>
    string(4) ".com"
  }
  [1]=>
  array(3) {
    [0]=>
    string(19) "http://hoge.dfg.com"
    [1]=>
    string(3) "dfg"
    [2]=>
    string(4) ".com"
  }
  [2]=>
  array(3) {
    [0]=>
    string(19) "http://hoge.hij.com"
    [1]=>
    string(3) "hij"
    [2]=>
    string(4) ".com"
  }
  [3]=>
  array(3) {
    [0]=>
    string(15) "http://hoge.com"
    [1]=>
    string(0) ""
    [2]=>
    string(4) ".com"
  }
}

後記

このことを勉強するまで、欲しい要素を抽出するために何度も正規表現処理をかけていました。すごく苦労したよ…。こんな便利なものもっと早く言ってよ…。

今回の記事の内容に関してはPHPマニュアルの以下のページに説明があります。
サブパターン
http://php.net/manual/ja/regexp.reference.subpatterns.php