![]() ![]() ![]() ![]() .NETでの正規表現の使用法この記事のURLhttp://japan.internet.com/developer/20050822/28.html
著者:Darren Neimke
海外internet.com発の記事
はじめに正規表現については既に多くの記事が書かれていますが、本稿の目的は.NETに新しく装備された正規表現の概要を解説し、これらの用途および使用法に関する簡単なガイドラインを示すことです。本稿の読者としては、正規表現についての基礎知識がある方を想定しています。 正規表現の初心者の方は、Regular Expressions Article Indexをご覧ください。入門者レベルの解説記事としては、An Introduction to Regular Expressions with VBScriptをお勧めします。 私は以前、VBScriptやJScriptのコード内で正規表現を使用できるくらいの基礎知識は持っていても、いざサンプルやドキュメントで正規表現が使われていると、その理解に苦労することがたびたびありました。たとえば前後読み(lookaround)や名前付きキャプチャ(named capture)などという新機能には、かなり悩まされたのを憶えています。これに加えて、正規表現に関するドキュメントそのものが数少なく、そのほとんどにはサンプルコードが付いていませんでした。そのため、以前は.NETプロジェクトで正規表現を使用することを最初から避けていました。 本稿では、このような新機能をいくつか紹介するとともに、読者の皆さんがかつての私と同じ苦労をせずに済むよう、わかりにくい部分を解説したいと思います。 マッチング: Groupsと名前付きキャプチャ 正規表現を実際に使った経験があれば、カッコで指定されたキャプチャを Dim userName As String = "Neimke, Darren" Dim re As New RegEx( "(w+),s(w+)" ) userName = re.Replace( userName, "$2 $1" ) Response.Write( userName ) ここで使用したパターンは、コンマとスペースで区切られた2つの英単語にマッチし、ユーザーの名前と名字をキャプチャした上で、名字と名前の順番を入れ換えたフォーマットで出力します。私の名前であれば、ブラウザには「Darren Neimke」と表示されるはずです。 この 現行の Dim userName As String = "Neimke, Darren" userName = Regex.Replace( userName, "(w+),s(w+)", "$2 $1" ) こうした静的メンバのもたらすメリットは、次のようなケースでも実感できます。このコードでは、何らかの処理を施す前に、処理対象の文字列中に数値が含まれているかどうかを If Regex.IsMatch( userInputString, "d+(.?d+)" ) Then ’ perform some conversion and math operations here End If .NET以前の正規表現では、 Dim userName As String = "Neimke, Darren" Response.Write( Regex.Match( username, "(w+),s(w+)" ).Groups(2).ToString() ) この結果、キャプチャした 名前付きキャプチャ さらに Dim userName As String = "Neimke, Darren" Dim pattern As String = "(?<surname>(w+)),s(?<firstname>(w+))" Response.Write( Regex.Match( userName, pattern ).Groups("firstname").ToString() ) この結果、「Darren」が表示されます。 キャプチャの回避マッチングは有用な機能である反面、パフォーマンスの悪化を伴います。VBScriptやJScriptでは、正規表現パターン中にカッコを使用すると必ずキャプチャが行われます。しかし、カッコを使用する必要はあるが、該当データをキャプチャする必要はない、というケースも考えられます。たとえば「Let’s go this way」か「Let’s go that way」のいずれかにマッチさせたいのであれば、次のような正規表現で検索することになるでしょう。 Let’s go th(is|at) way カッコと縦棒の組み合わせは、オプション指定であることを意味し、この場合で言えば「th」の後に「is」か「at」のいずれかが続く場合にマッチします。ここで問題なのは、キャプチャしたテキスト(つまり「is」または「at」)が後方参照用に記憶される分だけ、パフォーマンスが悪化するという点です。 幸い、.NETの正規表現では Let’s go th(?:is|at) way このパターンは、次のどちらのテキストにもマッチします。
ただし、マッチしたテキスト全体を参照する 前後読み前後読み(先読み/戻り読み)の機能は、JScriptでは部分的に実装されていますが、VBScriptには実装されていません。前後読みでは、先読み(lookahead)と戻り読み(lookbehind)という2種類の方向を指定でき、それぞれの方向に対して肯定表明(positive assertion)と否定表明(negative assertion)を指定できます。各々の構文は次のとおりです。
先読みと戻り読みの使い方を把握するには、マッチテキストとマッチ位置の意味を理解しなければなりません。最初に覚えておいてほしいのは、前後読みとは無駄を省くための機能であるという点です。この意味を説明するために、まずは次の単純なサンプルを見てみましょう。 pattern = "test" text = "testing" 上記のパターンとテキストでマッチングを実行すると、最終的に正規表現パーサのコンテキストは文字列「testing」の2番目の「t」と「i」の間に移動します。これは、正規表現パーサが次のように文字列の先頭から順次マッチングを進めていくからです(^はパーサの処理位置を示します)。
いったんパーサが通過した後に、位置をさかのぼってマッチングの開始位置に戻るような方法は存在しません。こうした制限がどのような問題を引き起こすかは、「tested」という文字列の中の「test」だけを検索したいが「tester」などの文字列は検索対象外にしたい、というケースを考えてみればわかるでしょう。しかし、ここで先読みを用いれば、 これが機能する理由は、前後読みの場合の正規表現パーサは、マッチングの開始位置を文字列の先頭に限定しないためです。この機能が特に役立つのは、先読みと戻り読みを組み合わせて、ドキュメント中の位置検索をするような場合です。具体的な例として、文字列「protested」の中の「test」を検索したいが文字列「detested」は検索の対象外としたい、というケースを考えてみましょう。これを実行するには、戻り読みの否定に「de」を指定し、先読みの肯定に「tested」を指定して、 このタイプの検索は、「テキスト中の指定位置からマッチを始めさせる機能」と言い換えることもできるでしょう。上記のパターンであれば、正規検索パーサの位置は、対象となる文字列「protested」の中を次のように移動していきます。
前後読みの使用例としては、たとえば「パスワードには8から20個のキャラクタを使用できるが、最低2個の文字および2個の数値を含んでいる必要がある。使用できるのは文字と数字だけである」というように、特殊なパスワード指定を検証する場合も挙げられます。 このようなパスワード制限に合致している文字列であれば、次のパターンでマッチするはずです。 信頼性と保守性私が個人的に気に入っている新機能の1つに、正規表現中にコメントを埋め込むというものがあります。たとえば、次のような正規表現パターンに遭遇したとします。 Dim re As New Regex( "(?<=(#|@))(?=w+)w+", RegexOptions.Multiline ) 運が良ければ、この正規表現パターンの使用目的についてのコメントが書かれている場合もあるかもしれませんが、このパターンを修正するような場合、途中で何が何を意味しているのか訳がわからなくなって、最初から書き直した方が早い、という事態に陥ってしまうのではないでしょうか。しかし.NETでは、 この機能を利用すると、疑似コード的にコメントを埋め込んで、コードの読みやすさを向上させることができます。 Dim re As New Regex ( _ "(?<= (?# Start a positive lookBEHIND assertion ) " & _ "(#|@) (?# Find a # or a @ symbol ) " & _ ") (?# End the lookBEHIND assertion ) " & _ "(?= (?# Start a positive lookAHEAD assertion ) " & _ " w+ (?# Find at least one word character ) " & _ ") (?# End the lookAHEAD assertion ) " & _ "w+ (?# Match multiple word characters leading up to a word boundary)", _ RegexOptions.Multiline Or RegexOptions.IgnoreCase Or RegexOptions.IgnoreWhitespace _ ) デリゲート 最後に.NET Frameworkの追加機能の中で最も有用なものとして、 Dim myString As String = RegEx.Replace( "a true taste of the temperature", "t.*?e", "a" ) この置換処理を行うと、 これに該当する事例としては、英文中のすべての単語の先頭文字を大文字に変更するという処理が考えられます。その場合は、おそらく mc = re.Matches( bodyOfText ) Dim m As Match For Each m In mc sb.AppendFormat("{0}{1}", m.Groups(1).Value.ToUpper(), m.Groups(2).Value) Next 変換対象となる文字列がアルファベットだけを含んでいるならばこの方式でも問題はないはずですが、「~~~ This %%% is ### a chunk of text」というように、アルファベット以外の記号類も使われている場合はどうでしょうか。置換を実行してみると、記号類がすべて消え去り、「ThisIsAChunkOfText」という文字列ができてしまうはずです。こうした難点を回避する方法は多数存在しますが、通常はより複雑なパターンを指定して、アルファベットの置き換え処理を多重化することで対策するといったところでしょう。 よりエレガントな方式は、 この方式の具体例として、先のサンプルコードを書き換えて、デリゲートを用いて単語の先頭部を大文字化するように変更してみましょう。 Sub Page_Load(sender as Object, e as EventArgs) Dim myDelegate As New MatchEvaluator( AddressOf MatchHandler ) Dim sb As New System.Text.Stringbuilder() Dim bodyOfText As String = _ "~~~ This %%% is ### a chunk of text." Dim pattern As String = "(w)(w+)?" Dim re As New Regex( _ pattern, RegexOptions.Multiline Or _ RegexOptions.IgnoreCase _ ) Dim newString As String = re.Replace(bodyOfText, myDelegate) Response.Write( bodyOfText & "<hr>" & newString ) End Sub Private Function MatchHandler( ByVal m As Match ) As String Return m.Groups(1).Value.ToUpper() & m.Groups(2).Value End Function このように、コードの構造が非常に明確なものになり、置換ロジックは独立したハンドラメソッドで処理するようになっています。このように複雑な置換処理でも、可読性や保守性を損なわずに実装することができ、何よりもありがたいことに、文字列を再構成する際にデータの欠落が生じる危険性も減少するので、データの完全性を保証する上でも有益です。 まとめ文字列を処理する場合、初心者プログラマは、鈍重で込み入った処理のソリューションを組んでしまいがちですが、言語に精通しているプログラマであれば、たいがいのテキスト処理を、正規表現を用いた操作でこなしてしまうものです。 .NETには、効率的かつ洗練された手法で正規表現を行うための機能が用意されています。たしかに正規表現の学習には時間がかかりますが、ひとたびマスターしてしまえば、正確かつ効率的なソリューションを構築できるようになります。 サンプルASP.NET Webページにアクセスすると、本稿で解説した各種の新機能を用いたデモを使用することができます。このサンプルページでは、リモートのWebサーバーからHTMLデータを抽出して、先頭部がhttp://で始まっていないURLを使っているハイパーリンク部を探し、プレフィックスを挿入します。 著者紹介Darren Neimke(Darren Neimke)
japan.internet.comのウエブサイトの内容は全て、国際法、日本国内法の定める著作権法並びに商標法の規定によって保護されており、その知的財産権、著作権、商標の所有者はインターネットコム株式会社、インターネットコム株式会社の関連会社または第三者にあたる権利者となっています。
本サイトの全てのコンテンツ、テキスト、グラフィック、写真、表、グラフ、音声、動画などに関して、その一部または全部を、japan.internet.comの許諾なしに、変更、複製、再出版、アップロード、掲示、転送、配布、さらには、社内LAN、メーリングリストなどにおいて共有することはできません。 ただし、コンテンツの著作権又は所有権情報を変更あるいは削除せず、利用者自身の個人的かつ非商業的な利用目的に限ってのみ、本サイトのコンテンツをプリント、ダウンロードすることは認められています。 |