だるろぐ

明日できることは、今日しない。

寄り道: string クラスの拡張

WebMatrix でユーザー認証機能(3) ―― なにはともあれユーザー登録しないと始まらん - だるろぐ の脱線。

個人的には

<fieldset>
    <legend>Register Your Account</legend>
    @this.RenderTextWithValidation(
        "name", "Name", new { Value = name} )
    @this.RenderPasswordWithValidation(
        "password", "Password")
    @this.RenderPasswordWithValidation(
        "confirmPassword", "Confirm Password")
    <input type="submit" value="Register" />
</fieldset>

って書くのがイケてない。「"password", "Password"」だの、「"confirmPassword", "Confirm Password"」だの、おんなじこと二回も書きたくないじゃん。あと、 HTML 要素の class や id は勝手にハイフン区切りにしてほしい。 Ruby on Rails にはソレ系のユーティリティが用意されているので楽なんだけどな(ActiveSupport)。

まぁ、なければ作れって話だ。

まず、あると便利な拡張メソッドを作っていく。先頭を大文字、後を小文字にする Capitalize() は欲しいよね。

public static string Capitalize(this string target)
{
    return target.Length > 1
        ? char.ToUpper(target[0]) +
          target.Substring(1, target.Length - 1).ToLower()
        : target.ToUpper();
}

char.ToUpper() を 'a'.ToUpper() にしたいが今日のところは面倒なので許してやろう。

さぁ、どんどん作っていくよ! つぎは痴漢系、もとい置換系だな。 C# (.NET Framework)ってちょっと文字列操作系のメソッドが充実してもいいと思う。

public static string Gsub(
    this string target, Regex r, string replacement)
{
    return r.Replace(target, replacement);
}

public static string Gsub(
    this string target, Regex r, MatchEvaluator m)
{
    return r.Replace(target, m);
}

public static string Gsub(
    this string target, string pattern, string replacement)
{
    return target.Gsub(new Regex(pattern), replacement);
}

public static string Gsub(
    this string target, string pattern, MatchEvaluator m)
{
    return target.Gsub(new Regex(pattern), m);
}

これの何が便利かというと、

public static string Underscore(this string target)
{
    // 空白やハイフンをアンダースコアに置き換える
    return target.Gsub(@"[  -]", "_");
}

みたいに使える。 string.Replace() みたいに手軽で、それでいて強力! 場合によっては、第一引数に正規表現を渡してもいいし、第二引数で Match を引数にとったラムダを使ってもいい。ホントのことを言えば C# にも正規表現リテラルがあると嬉しいのだけれどね……

private static string DivideIntoWords(
    this string target, string separator = " ")
{
    return target.Gsub(
        @"([\w-[A-Z]])([A-Z])",
        string.Format("$1{0}$2", separator)
    );
}

あと、こんなのも作ってみた。これは「HelloWorld」を「Hello World」に分割する。第二引数を渡せば、「Hello_World」や「Hello-World」も作れる。こいつらを使って、 PascalCase (UpperCamelCase)に変換する拡張メソッドを作ってみよう。

static readonly char[] SPACE_AND_DELIMITTER = "  -_".ToArray();

public static string ToPascalCase(this string target)
{
    return target                    // sampleText_is-Cool
        .DivideIntoWords()           // sample Text_is-Cool
        .Split(SPACE_AND_DELIMITTER) // { sample, Text, is, Cool }
        .Select(_ => _.Capitalize()) // { Sample, Text, Is, Cool }
        .Combine();                  // SampleTextIsCool
}

public static string ToUpperCamelCase(this string target)
{
    return target.ToPascalCase();
}

private static string Combine(
    this IEnumerable<string> target, string separator = "")
{
    return string.Join(separator, target.ToArray());
}

Combine() はメソッドチェーンを切らずに string.Join() したいので作った IEnumerable<string> の拡張メソッドで、@xin9le さんが命名してくれた。

同様にして CamelCase(LowerCamelCase)も作れる。

public static string Camelize(this string target)
{
    return target
        .DivideIntoWords()
        .Split(SPACE_AND_DELIMITTER_CHARS)
        .Select((_, i) => (i == 0) // <-- カウンターを使う
            ? _.ToLower()
            : _.Capitalize())
        .Combine();
}

1回目(i == 0)のときだけ ToLower()、あとはさっき作った Capitalize() すればいいね。

今まで知らなかったのだけれど、 Enumerable.Select(Func<TSource, Int32, TResult>) (http://msdn.microsoft.com/ja-jp/library/bb534869)を使うと「今何回目の処理をしているか(インデックス)」が取得できるのね。 Ruby の map {|i| ... } みたいなものかな。

public static string Titlize(this string target)
{
    return target
        .Gsub(@"[_-]", " ")
        .DivideIntoWords()
        .Split(SPACE_AND_DELIMITTER)
        .Select(_ => _.Capitalize())
        .Combine(" ");
}

public static string Humanize(this string target)
{
    return target
        .Gsub(@"[_-]", " ")
        .DivideIntoWords()
        .Split(SPACE_AND_DELIMITTER)
        .Select((_, i) => (i == 0) ? _.Capitalize() : _.ToLower())
        .Combine(" ");
}

あとは僕の望みの、Titlize() や Humanize() も作ってみた。メソッド名は ActiveSupport から拝借。

f:id:daruyanagi:20120828080159p:plain

"man from the boondocks".Titlize()   # => "Man From The Boondocks"
"man from the boondocks".Humanize()  # => "Man from the boondocks"

"x-men: the last stand".Titlize()    # => "X Men: The Last Stand"
"x-men: the last stand".Humanize()   # => "X men: the last stand"

"TheManWithoutAPast".Titlize()       # => "The Man Without A Past"
"TheManWithoutAPast".Humanize()      # => "The man without a past"

"raiders_of_the_lost_ark".Titlize()  # => "Raiders Of The Lost Ark"
"raiders_of_the_lost_ark".Humanize() # => "Raiders of the lost ark"

CSS text-transform の capitalize に相当する処理が Capitalize() ではなく Titlize() なのが罠といえば罠。