2010年8月8日日曜日

サルベージ開始

最近だとMVC3 Preview1がとても興味深く、楽しそうなプロダクトで、あれやこれやいじり倒してみたいところですが、なんか忙しかったり、テンション上がらなかったりとエントリ書く気も起きないな~、なんてぼやく日曜日。

で、思い出したんですよ。昔々「オレがルールだ」なんていうブログを書いていたのを。その時のデータベースファイル(MDF)を発掘したので、そこからエントリをサルベージするってどうなんよ、と。データベースファイルをローカルのSQL Expressにアタッチしたのはいいけど、そのままテキストだけだと画像なんかも見れないし、なんか良く解らん状態。なので、せっかくなのでMVC3P1 Razorで参照専用のWebアプリを書いてみたわけです。書くと言っても参照専用なのでほとんどすることはないんですけどね。EF4CTP1でも使おうかと思ったけど、インストールしてないし、するのも面倒だから、既にインストール済みのPOCOテンプレートを使ってみました。これまた特にすることもなく、EDMX作ってカスタムツール外して、ファイル名をttに埋め込むだけなんですけどね。

なんかかんや、録画しておいてハゲタカTHE MOVIE見ながらチクチクと。

salvage1 salvage2

書いてて気がついたんですが、RazorってデフォルトHTMLEncodeかけるんですね。HTMLをそのまま出力したいときにはどうしましょう...。

とりあえず、以下のようなヘルパーを用意しておくことで簡単に出力できるとStackoverflow.com先生が教えてくれました。

namespace System.Web.Mvc
{
  public static class HtmlHelperExtensions
  {
    public static IHtmlString Literal(this HtmlHelper htmlHelper, string html)
    {
      return MvcHtmlString.Create(html);
    }

    public static IHtmlString AsHtml(this string html)
    {
      return MvcHtmlString.Create(html);
    }
  }
}

Emitting unencoded strings in a Razor view - Stack Overflow

↑これがあれば↓こうですね!

  <div class="entry_view">
    @Html.Literal(Model.contents)
  </div>
  <div class="entry_view">
    @Model.contents.AsHtml()
  </div>

グダグダ前置き長くてすいません。というわけで第1弾「もっと速く出来ると思う」です。


もっと早く出来ると思う

2009/01/16 18:52:29

ここ最近Philさんのブログでホットな話題になってる名前書式化スピード競争。

Fun With Named Formats, String Parsing, and Edge Cases

↑このエントリで始まって、Philさんが「オレのんが速え~」って事でコードを載せてたけど、ここにきて新たな刺客登場。

Named Formats Redux

正規表現だから遅いんじゃないぜ!といわんばかりにJamesFormatterを作成したJamesさん。
おいおいフィル、同じ会社の社員としてそれじゃいかんぜ、とHenriFomatterを作成したHenri Weichersさん。
いずれもPhilさんの速度を超えた。

ルールは簡単。
通常のstring.Format(”書式化文字列”,value)の書式化では引数のインデックスを埋め込むけど、匿名クラスのパラメータ名を渡して書式化出来るようにすること。んで、テストがちゃんと通ること。

例えば
var values = new { foo = 123, bar = 456.7, boo = "Hello!" };
string text = "{foo} {bar} {boo}.";
なら
text = "123 456.7 Hello!."
になる。

これは面白そう。
流石に、速いコードを書く事に慣れてないから苦戦すると思ったけど...。

str

誰よりも速えーじゃねーか。
※順番で不公平(なわけないけど)だ!なんて言われてもイヤなんで、最初と最後の2箇所で。
そんなに難しいコードじゃなくて、テストを通すことと、書式化文字列がもっと長い場合とかは考慮せず、シンプルに書いてみた。

str2

ほら。テストも全通過。
テストツールはNUnit入れてないからVisual Studioに入ってるやつで出来るようにコピペして作り直してるけど、やってるテスト内容は同じ。

コードは↓これ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;

namespace StringLib
{
  public static class TakeharaNamedFormatter
  {
    public static IEnumerable<KeyValuePair<string, object>> AnonProperties(string prefix, PropertyInfo pi, object inst)
    {
      var piVal = pi.GetValue(inst, null);
      if (!pi.PropertyType.IsValueType && pi.PropertyType.IsNotPublic)
      {
        foreach (var subpi in piVal.GetType().GetProperties())
          foreach (var kv in AnonProperties(pi.Name + ".", subpi, piVal))
            yield return kv;
      }
      else
        yield return new KeyValuePair<string, object>(prefix + pi.Name, piVal);
    }

    public static string TakeharaFormat(this string format, object values)
    {
      if (format == null)
        throw new ArgumentNullException("format");

      List<string> formats = new List<string>();
      Dictionary<string, object> vals = new Dictionary<string, object>();
      foreach (var pi in values.GetType().GetProperties())
        foreach(var kv in AnonProperties("", pi, values))
          vals.Add(kv.Key, kv.Value);

      var len = format.Length;
      string result = format;

      char atchar;
      int sbrace = 0, ebrace = 0;
      string replace_format = "";

      for (int i = 0; i < len; i++)
      {
        atchar = format[i];

        // start
        if (atchar == '{')
        {
          if (replace_format != "")
            sbrace = 0;

          sbrace++;
          ebrace = 0;
          replace_format = "";
        }
        else
        {
          // end?
          if (atchar == '}')
          {
            ebrace++;
            if (sbrace % 2 != 0 && replace_format != "")
            {
              //sbrace = 0;
              formats.Add(replace_format);
            }
          }
          // format string?
          else
            if (sbrace > 0 && ebrace == 0)
              replace_format += atchar;
            else if (((sbrace + ebrace) % 2 != 0) && ebrace != sbrace)
              throw new FormatException();
        }
      }

      if ((sbrace + ebrace) % 2 != 0)
        throw new FormatException();

      // replace!
      var value_format = "";
      foreach (var f in formats)
      {
        var fa = f.Split(':');

        if (!vals.ContainsKey(fa[0]))
          throw new FormatException();

        if(fa.Length == 2)
          value_format = "{0:" + fa[1] + "}";
        else
          value_format = "{0}";

        result = result.Replace(string.Format("{{{0}}}", f), 
                                string.Format(value_format, vals[fa[0]]));
      }

      result = result.Replace("{{", "{")
                     .Replace("}}", "}");

      return result;
    }
  }
}

 

リフレクション使ってるし、ジェネリックも使ってるけど、遅くないよね...。
もっと速いコードが出てきたら、また考える。


いや~、コピペって楽ですね!

ストックが1813件あるということは、しばらく...。そんなわけないですね。ほとんどがどうでもいい話だったりするので、これなら今公開しても、まぁいいかなというのを、ちょいちょい探してサルベージしていこうかと思う次第です。