2013年3月14日木曜日

URI中の指定キーに対応するクエリ文字列の値を取り出す処理をLINQで。UTもついでに。

以前書いた、こんなコードがありました。

string valueKey1 = null;

foreach (string query in uri.Query.Split('&')) // uriはUriオブジェクト
{
  string[] kv = query.Split('=');

  if (kv.Length < 2)
    continue;
  if (kv[0].StartsWith("?"))
    kv[0] = kv[0].Substring(1);

  if (kv[0] == "key1")
  {
    valueKey1 = kv[1];
    break;
  }
}

要は、URIのクエリ文字列から、指定のパラメータの値を取り出す処理です。これをもう少し今っぽいコードにして、ついでに汎用化(ライブラリ化)して、さらについでにユニットテストまでやってしまおうと目論みました。

VS Express 2012 を起動し、新規プロジェクトをソリューション付きで、クラスライブラリをターゲットに作成します。


ライブラリ化にあたっての方針はこんな感じ。

  • Uriクラスの拡張メソッドにする。
  • パラメータ名を指定して値を取り出すメソッドを実装。
  • このメソッドにはオプショナルで、Uriエスケープの有無を指定できるようにする。ただし、VS2008とかでもソースをそのまま使えるように、メソッドのオーバーロードで対応する。
  • Uriエスケープありの場合、Uriエスケープをほどいた値を返す。

こうしてみました。

public static class UriExtensions
{
  public static string FindQuery(this Uri uri, string key)
  {
    return FindQuery(uri, key, true);
  }

  public static string FindQuery(this Uri uri, string key, bool uriEscape)
  {
    throw new NotImplementedException();
  }
}

テストコードを書きます。「テスト」メニューを開いてみると、「新しいテスト」メニューがないのね…。んじゃ、「新しいプロジェクト」で単体テストプロジェクトを、ソリューションに追加するように作成。


デフォルトで作成されたテストクラスの名前を変更して、参照設定にテスト対象プロジェクトを追加。さらにUsingを指定してテスト準備OK。

作成されたテストコードには、初期状態でClassInitializeもTestInitializeも無いんだ…。テストコードがすっきりしてるのはいいけど、いざテストの初期化をしようとしたときに面倒だなぁ。VS2010までで慣れてるといろいろ違和感がある。まぁ、そのうちなれるかな。

気を取り直して、テストコードを書き始めます。この辺が網羅できればいいでしょ。

  • Uriクエリ文字列のパラメータ数が、0、1、2以上。
  • 指定したキーがヒットする、しない。
  • Uriエスケープされている、されていない。
  • Uriエスケープのオプション指定をする、しない。

書いてみたテストコードがこれです。
[TestClass]
public class UriExtensionsTest
{
  [TestMethod]
  public void FindParamValueTest()
  {
    Uri u1 = new Uri("http://unkkown/test.html");
    Assert.IsNull(u1.FindQuery("unknownkey"));

    Uri u2 = new Uri("http://unknown/test.html?key1=xxx&key2=yyy&key3=zzz");
    Assert.IsNull(u2.FindQuery("key4"));
    Assert.AreEqual("yyy", u2.FindQuery("key2"));

    string key = "%E3%82%AD%E3%83%BC"; // "キー"のURIエンコード(UTF-8)
    string value = "%E5%80%A4"; // "値"のURIエンコード(UTF-8)
    Uri u3 = new Uri("http://unknown/test.html?" + key + "=" + value);
    Assert.AreEqual("値", u3.FindQuery("キー"));
    Assert.AreEqual(value, u3.FindQuery(key, false));
  }
}

テストを実行すると失敗して、「NotImplementedException」がスローされたとのこと。予定通り。ここからUriExtensionsクラスを実装します。試行錯誤の結果、テストをパスして出来上がったコードがこれ。

public static class UriExtensions
{
  public static string FindQuery(this Uri uri, string key)
  {
    return FindQuery(uri, key, true);
  }

  public static string FindQuery(this Uri uri, string key, bool uriEscape)
  {
    string query = uri.Query;
    string keyName = uriEscape ? Uri.EscapeDataString(key) : key;
    Func<string, string> vconv = s => s;

    if (string.IsNullOrEmpty(query))
      return null;
    if (query.StartsWith("?"))
      query = query.Substring(1);
    if (uriEscape)
      vconv = s => Uri.UnescapeDataString(s);

    return query.Split('&').Where(q => q.StartsWith(keyName + "="))
       .Select(q => vconv(q.Substring(keyName.Length + 1)))
        .FirstOrDefault();
  }
}

こんな感じでしょうかね。うん。 あとはコードコメントを書いてリリースビルドすれば作業完了です。そこは省略。

0 件のコメント:

コメントを投稿