くわぽんのプログラミングmemo
  Welcome to my web site. Thank you for your access.
HOME Convert Regular Expression Request Headers HTTP cookie Counter  
Regular Expression(正規表現)

正規表現の対象
  正規表現には、文字列がある規則に基づいているか検証する機能(Validation)とフォーマットの中から文字列を取り出したり、操作したりする機能の2種類があります。

ここでは、主に、検証する機能での使い方を、Visual Studio 2008のVisual Web Developer 2008 Expressで提供されている、
「RegularExpressionValidator」で使える正規表現を中心に話を進めていきます。

具体的に、他のプログラム言語で利用する場合とでどのあたりに違いがあるかというと、RubyやPHPなどの場合、"/"で囲まれた範囲を正規表現と認識するため、正規表現中の"/"をエスケープ("\"を前につける)必要があります。Java、JavaScriptなどはそのようなことはないようです。また、そのほかには、UNIX固有の表現などもあります。
ここでは、それらの固有の違いには触れず、あくまでMicrosoft .NETの「RegularExpressionValidator」に特化した表現を紹介します。
特別な意味を持つ文字
 
() 半角
括弧 
グループ分けに使われます。計算の括弧と意味的には近いものです。
例:(http(s)?://|HTTP(S)?://)
  "http://"を大文字のみ、または小文字のみで比較します。そのとき、"https://"も比較できるように、
 "s"だけをグループとして取り出して"(s)"として、"?"で0回または1回の繰り返し
 (つまり、有っても無くてもよい)を検証(取り出)します。
 その後、の文字列に影響しないように全体を括弧でくるんでグループ化します。
[] 半角
大括弧
括弧内に表現された文字のどれか一つに一致する一文字に該当します。
例:[hH][tT][tT][pP][sS]?://
 "http://"が大文字小文字を混ぜた状態でもマッチ(該当)します。
. 半角
ピリオド
改行以外の任意の一文字に該当します。
該当する文字の種類は解釈する環境に依存しますが、日本語環境では、漢字なども一文字としてマッチします。
但し、"[]"の中に書かれた場合は、"."半角ピリオドの一文字にマッチするようになります。
例:...
 "3文字"など(全角、半角に関係なく)任意の3文字に該当します。
[^ ] 角括弧+^ 括弧の中に書かれた文字を除く一文字に該当します。
例:http://[^/]
 "http:///"は該当しませんが、"http://w"には該当します。/以外なら何でもマッチします。
^   行の最初にマッチします。
例:^(http(s)?://|HTTP(S)?://)
 "http://"、"HTTP://"、"https://"、"HTTPS://"で始まる文字列にマッチしますが、
 "このページはhttp://〜〜 "など、文中の"http://"にはマッチしません。
$   行の最後にマッチします。
例:^http://.*/$
 "http://つれづれ日記.jp/"にはマッチしますが、"http://つれづれ日記.jp"にはマッチしません。
[-] 大括弧
内の- 
大括弧内の"-"は特別な意味を持ちます。
これは、前後に文字の指定を伴って、前の文字コードと後ろの文字コードの間の文字を意味します。
たとえば、"[0-9]"と書いた場合、"[0123456789]"と同じ意味になりますが、
"[1-10]"と書いても"[01]"と同じ意味になります。0〜10までの数字を表したい場合は、
"([0-9]|10)"などと書きます。 
\ 円マーク
または
バックスラッシュ
エスケープ文字といいます。
いくつかの使い方があります。
(1) 特別な意味を持つ文字を普通の文字として扱う様に指定する。
 例:"What's happened\?" => "What's happened ?"とマッチする。
   "What's happened?" => "What's happene"、"What's happened"とマッチする。

(2) 文字コードを指定する。
 例:"[\x20-\x7e]" => "[ -~]"と同じ、"\xhh"は16進ASCIIコードと一致する。
    ("h"は16進コード)
 同様に
  "\uhhhh"はユニコード(Unicode)
  "\ooo"は8進3桁コード(oは8進コード)

(3) コントロール文字(ASCII制御文字)を指定する。
 例:"\cC" => "ctrl-C"と一致する。

(4) 特定の文字や文字グループを指定する。
 "\a" => アラーム(\u0007)と一致します。
 "\b" => 大括弧で囲まれていない場合は、ワード境界と一致します。
       大括弧で囲まれている場合は、バックスペース文字(\u0008)と一致します。
 "\t" => タブ(\u0009)と一致します。
 "\r" => キャリッジ リターンの \u000D と一致します。
 "\w" => 垂直タブの \u000B と一致します。
 "\f" => フォームフィードの \u000C と一致します。
 "\n" => 改行の \u000A と一致します。
 "\e" => エスケープの \u001B と一致します。
 "\w" => 半角英数と一致します。"[a-zA-Z_0-9]"と同じです。
 "\W" => \w以外の文字と一致します。"[^a-zA-Z_0-9]"と同じです。
 "\s" => 空白文字(スペース、タブ、改行など)と一致します。"[ \f\n\r\t\v]"と同じです。
 "\S" => \s以外の文字と一致します。"[^ \f\n\r\t\v]"と同じです。
 "\d" => 10進数の数字と一致します。"[0-9]"と同じです。
 "\D" => \dの否定です。"[^0-9]" と同じです。
 ※実際には、Unicodeを意識するともう少し複雑ですが、
  これ以上は、環境や設定に依存するので割愛します。

繰り返しの回数を表す記号(数指定子)
? 直前の文字やグループの0回か1回の繰り返し
*   直前の文字やグループの0回以上の繰り返し(但し、発見可能な最長のパターンにマッチします)
@   直前の文字やグループの0回以上の繰り返し(但し、発見可能な最短のパターンにマッチします)
+   直前の文字やグループの1回以上の繰り返し(但し、発見可能な最長のパターンにマッチします)
#   直前の文字やグループの1回以上の繰り返し(但し、発見可能な最短のパターンにマッチします)
{} 半角
中括弧
直前の文字やグループの指定した回数の繰り返し
例:yaho{2}
"yahoo"(o2回の繰り返し)にマッチします。
例:yaho{2,4}
"yahoo"、"yahooo"、"yahoooo"(oの2回から4回の繰り返し)にマッチします。
例:yaho{,3}
"yah"、"yaho"、"yahoo"、"yahooo"(oの0回から3回の繰り返しにマッチします。)
^   直前の文字やグループの指定した一桁回繰り返し
例:yaho^2
"yahoo"にマッチします。

先読み
(?= ) 肯定の
先読み
先読みして一致(マッチ)を確認します。この条件にマッチすると、後の正規表現で値を取り出したり、置き換えたりできます。
マッチした場合、この後ろに書かれた正規表現は、先読みを無視して(なかったものとして)評価されます。
(?! ) 否定の
先読み
先読みして一致(マッチ)を確認します。この条件にマッチすると、後の正規表現は評価されず、検証には必ず失敗します。
マッチしなかった場合、この後ろに書かれた正規表現は、先読みを無視して(なかったものとして)評価されます。


"*"と"@"の違いは、例えば、以下のようになります。
"http://www.kuwamori.com/tracer/convert2/hogehoge/default.aspx"を検証させる場合、
"^http://www.kuwamori.com(/[^/])*/?"とした場合は
"http://www.kuwamori.com/tracer/convert2/hogehoge/"とマッチしますが、
"^http://www.kuwamori.com(/[^/])@/?"とした場合は
"http://www.kuwamori.com/"とマッチします。

同様に、"+"と"#"の違いも
"^http://www.kuwamori.com(/[^/])+/?"とした場合は
"http://www.kuwamori.com/tracer/convert2/hogehoge/"とマッチしますが、
"^http://www.kuwamori.com(/[^/])@/?"とした場合は
"http://www.kuwamori.com/tracer/"とマッチします。
具体例1:日付検証
  ここでは、日付を検証する例を書きます。
まず、見本です。
この例では日付は、0000年1月1日〜9999年12月31日までを検証しています。
それぞれの数字の頭にくる0は省略可能です。

少しづつ区切って見ていきましょう。
^((\d{1,4}/"は1文字から4文字の数字で始まり、後ろに"/"が来る文字列を示しています。つまり、西暦です。

その次の、"((0?[13578]|10|12)/(0?[1-9]|[1-2][0-9]|3[0-1]))|((0?[469]|11)/(0?[1-9]|[1-2][0-9]|30))"は前後に分かれます。

前半の、"(0?[13578]|10|12)/(0?[1-9]|[1-2][0-9]|3[0-1])"は"(0?[13578]|10|12)/"で西暦に続く部分を"1,01,3,03,5,05,7,07,8,08,10,12"のいずれかである場合と制約しています。これは、1月3月5月7月8月10月12月を示しています。そう、これは、一か月が31日間ある月です。
その後ろを見てみると、"(0?[1-9]|[1-2][0-9]|3[0-1])"の"0?[1-9]"は1日〜9日まで、"[1-2][0-9]"は10日から29日まで、"3[0-1]"は30日と31日をマッチする値として制約しています。

後半の"(0?[469]|11)/(0?[1-9]|[1-2][0-9]|30)"は2月を除く小の月(1月の日数が30日の月)を表しています。
"(0?[469]|11)"は"4,04,6,06,9,09,11"のいづれかと一致する月に制約しています。
"(0?[1-9]|[1-2][0-9]|30)"は先ほど同様、1日〜9日、10日〜29日、30日に分けてマッチする値を制約しています。
2月は?というと、すべての年で28日まではマッチして問題ないので、
"(0?2/(0?[1-9]|[1-2][0-8]|19))"と書きました。
変則的な書き方ですが、1日〜9日、(10日〜18日と20日〜28日)、19日をマッチするように制約しています。


ここまでは、年の値に影響されずに常に同じ日数をマッチする条件にすればよかったわけですが、この後は少々面倒になります。
そう、うるう年です。

うるう年は、西暦が4で割れる月はうるう年となりますが、その中で西暦が100で割れる月はうるう年となりません。またまたその中で西暦が400で割れる月はうるう年になります。

うるうを指定し、2月29日をマッチするように書けばよいわけです。

まず、400年に一度をマッチさせます。
西暦0000年は本来的にはおかしいかもしれませんが、今回は除外せず、400で割れる年に含めます。(数学的に割り切ります(^^;)
"00|[02468]?[048]00|[13579][26]00"の部分がこれに当たります。
西暦の4ケタ中、上の2ケタだけを取り出し、4の倍数にマッチさせます。具体的には、四桁目が偶数なら三ケタ目は"0,4,8"のいずれか、四桁目が奇数なら、三ケタ目は"2,6"のいずれかになります。
その部分が、"[02468]?[048]00|[13579][26]00"です。この例では、3ケタ以上の表記にしか対応できないため、"00"と"0"は別途記述する必要があります。

次に、4年に一度にマッチさせますが、このとき、100年に一度にマッチさせない工夫が必要です。
今回の例では、"\d{0,2}[2468]0|\d{0,2}[02468][48]|\d{0,2}[13579][26]"の"\d{0,2}[2468]0"と"\d{0,2}[02468][48]"に分けているところがミソです。
4年に一度は下2桁だけ見ればいいので、二桁目が偶数なら一ケタ目は"0,4,8"のいずれか、二桁目が奇数なら、一ケタ目は"2,6"のいずれかになります。ただし、00は避けたいので、一桁目が0になるケースを分けて、二桁目から0をなくし、二桁目が0を含む偶数の場合、一桁目に0を入れないようにしています。二桁目が奇数になる場合は、0を考えなくてよいので分ける必要はありません。
ただ、このままだと、西暦を一桁で入力された場合を正しく認識しないので、先ほどの"0"一桁、二桁と合わせて、"[048]|00|"を先頭に追加しました。
これで、うるう年の29日ある年を制約し終わったので、2月29日をマッチするように書きます。
"/0?2/29"

大体、こんな感じです。


では、西暦0年を許可したくない場合はどうなるでしょう。
もっとも簡単な方法は、「否定の先読み」を行う方法です。
"^(?!0{1,4}/)"と先頭に書けば、西暦0年は以降の正規表現を評価することなく、マッチしないものとして扱われます。
0001年1月1日〜9999年12月31日までの正規表現

考え方によって、さまざまな書き方があると思います。
参考にしてください。

参考
・2000年1月1日〜2099年12月31日を検証する例


・1900年1月1日〜9999年12月31日を検証する例(SQL ServerのDatetime型用)

{正確には1753年1月1日〜9999年12月31日なのでもう少し面倒}
具体例2:Base64エンコード文字列の検証
  Base64は64進数でエンコードされた文字列です。
64進数を
構成する文字は、
0〜25 => A〜Z
26〜51 => a〜z
52〜61 => 0〜9
62 => +(URLやファイル名の場合は"-")
63 => /(URLやファイル名の場合は"_")
となっています。

8bitを64進数(6bit)で表すため、最小公倍数である24bitを単位としてエンコードします。。
3Byteを4文字にエンコードすることを意味しています。

つまり、エンコード結果は必ず4の倍数文字になります。
"A〜Z ,a〜z,0〜9"を表すために"\w"を使う手もありますが、環境依存を排除するために、
"[A-Za-z0-9\+/]"とします。
"\+"となっているのは、いわゆるエスケープです。これは、念のため書いているだけで、大括弧の中では不要です。


また、エンコード後の文字数が4文字に満たなかった場合は、"="を補うことになっています。
つまり、

となります。
MIMEの場合には、76文字ごとに改行コードが挿入されます。
これを表現すると、

となるのでしょうか?
具体例3:すべての文字コードに該当する正規表現
  すべての文字にマッチさせるなら正規表現を使う必要なんてないじゃないか(`ヘ´) プンプン。
とはいわずに、参考にしてください。

答えは簡単で、改行以外の任意の文字に該当する"."と改行"\n"を組み合わせればいい・・・のですが、
"[.\n]"と書いてしまうと、"."ピリオドという文字と改行のどちらかにマッチするという事になってしまうのです。

そこで、"(.|\n)"と書くのが正解です。

文字列にするなら、

と書く事になります。
("\r?\n?"としている理由は、クライアントの環境が、UNIX、Apple、Windowsでいろいろなケースがあるからです。)
Copyright(C) 2010 Kuwapon All Rights Reserved.