WebSurfer's Home

トップ > Blog 2   |   ログイン
APMLフィルター

日本語の著者名の問題

by WebSurfer 2011年6月13日 23:14

各記事の左上に著者名が表示されます。この記事では By WebSurfer がそれです。この著者名をクリックすると、その著者が投稿した記事が選択され表示されるようになっています。(BlogEngine.NET は複数の投稿者を許しています)

ところが、著者名に日本語(たぶん、その他の Multi-Byte 文字も)を使うと、著者名のリンクをクリックしても何も選択されない(従って記事は何も表示されない)という問題があります。

例えば、著者名が "日本太郎" の場合、リンクの URL には以下のように設定されます。

http://surferonwww.info/BlogEngine2/author/日本太郎.aspx

このリンクをクリックすると、ブラウザはこれを URL エンコードしてからサーバーに送信し、これを受けたサーバーは、URL をデコードしてから UrlRewrite モジュールで以下のように書き換えます。

http://surferonwww.info/BlogEngine2/default.aspx?name=日本太郎

default.aspx.cs で、以下のコードで著者名を取得しようとしていますが、URL エンコードしてない生 UTF-8 なので正しく取得できません。

string author = Server.UrlDecode(Request.QueryString["name"]);

結果、著者名が日本語の場合は、著者名が正しく取得できないので著者に関連する記事を選択できず、何も表示されないということになります。

この問題を解決するには、default.aspx.cs に渡すクエリ文字列を URL エンコードしてやります。さらに、default.aspx.cs でクエリ文字列から著者名を取得する際デコードしないようにします。

具体的には以下のとおり修正しました。これで問題なく動いています。

Core/Web/HttpModule/UrlRewrite.cs の ContextBeginRequest メソッド

else if (url.Contains("/AUTHOR/"))
{
  // 日本語の author 名が認識されないので修正。具体的には、
  // author → context.Server.UrlEncode(author) としたのみ。
                
  var author = ExtractTitle(context, url);
  context.RewritePath(
    string.Format("{0}default{1}?name={2}{3}", 
      Utils.RelativeWebRoot, 
      BlogSettings.Instance.FileExtension, 
      context.Server.UrlEncode(author), 
      GetQueryString(context)),
    false);
}

default.aspx.cs の DisplayAuthors メソッド

private void DisplayAuthors()
{
  if (!string.IsNullOrEmpty(Request.QueryString["name"]))
  {
    // UrlDecode は不要なので以下の行を変更。
    //string author = Server.UrlDecode(Request.QueryString["name"]);

    string author = Request.QueryString["name"];

    PostList1.ContentBy = ServingContentBy.Author;
    PostList1.Posts = 
      Post.GetPostsByAuthor(author).ConvertAll(
        new Converter<Post, IPublishable>(delegate(Post p) 
          { return p as IPublishable; }));
    Title = "All posts by " + Server.HtmlEncode(author);
  }
}

なお、この問題は 1.6.1 では発生しません。

1.6.1 の場合、RemoveIllegalCharacters メソッドで UrlEncode が使われ、さらにそれから '%' 文字が除去された著者名がリンクの URL のクエリ文字列に設定されます。従って、DisplayAuthors メソッドで取得できる author はクエリ文字列に設定されたものと全く同じです。

それが Post.GetPostsByAuthor メソッドの引数として渡され、Post.Author で取得した著者名に RemoveIllegalCharacters メソッドを適用した文字列と比較され、一致する記事が選択されます。

ただし、Title に表示される著者名は、RemoveIllegalCharacters メソッドで変換された文字列になります(正しい日本語にはなりません)。

Tags:

BlogEngine.NET 2.0

BlogEngine.NET 2.0 の Tag cloud の問題

by WebSurfer 2011年6月12日 21:19

Tag cloud の中のタグをクリックすると、通常はそのタグを持つ記事が選択されて表示されます。しかしながらバージョン 2.0 では、日本語のタグをクリックすると何も選択されない(従って表示もされない)という問題があります。

この問題は IE を使ったときに発生します。Firefox 4.0.1, Chrome 12.0.742.91, Opera 11.11 などでは問題ありませんでした。

原因は、IE が要求を Web サーバーに送るときクエリ文字列を URL エンコードしないためと、バージョン 2.0 の Core.Utils.RemoveIllegalCharacters メソッドで UrlEncode が HtmlEncode に変更されていたためです(SEO 対策だそうです)。

バージョン 2.0 の場合、Tag cloud の中に「日本語」というタグがあったとすると、そのリンクは以下のようになります(以下で、"日本語" は生の UTF-8 になります)。

http://surferonwww.info/BlogEngine2/?tag=/日本語

IE は、要求を送るときに、? の前までは URL エンコードしてから送信しますが、クエリ文字列はそのまま UTF-8 で送信します(バグ?)。それゆえ、サーバー側で Request.QueryString["tag"] では "/日本語" は正しく取得できません。

この問題を避けるため、tag も category と同様に、クエリ文字列ではなくファイル名に設定するように変更しました。以下のような感じです。

http://surferonwww.info/BlogEngine2/tag/日本語.aspx

こうすれば IE の場合でも "日本語" を URL エンコードしてから要求を送りますので、問題はなくなります。そのために変更しなければならないのは、概略以下の部分です。

  • URL 文字列を組み立ててタグのリンクに設定する部分
  • URL を書き換える UrlRewrite HTTP モジュール
  • 書き換えられた URL からタグ名を取得し関連する記事の一覧を表示する部分

具体的には以下のようにコードを変更しました。

widget/Tag cloud/widget.ascx.cs の LoadWidget メソッド

public override void LoadWidget()
{
  foreach (var key in this.WeightedList.Keys)
  {
    using (var li = new HtmlGenericControl("li"))
    {
      //li.InnerHtml = string.Format(
      //    LinkFormat,
      //    string.Format("{0}?tag=/{1}",
      //        Utils.RelativeWebRoot,
      //        Utils.RemoveIllegalCharacters(key)),
      //    this.WeightedList[key],
      //    "Tag: " + key,
      //    key);

      li.InnerHtml = string.Format(
        LinkFormat,
        string.Format("{0}tag/{1}", 
          Utils.RelativeWebRoot, 
          Utils.RemoveIllegalCharacters(key) + 
            BlogSettings.Instance.FileExtension),
        this.WeightedList[key],
        "Tag: " + Server.HtmlEncode(key),
        Server.HtmlEncode(key));

      this.ulTags.Controls.Add(li);
    }
  }
}

Core\Web\Controls\PostViewBase.cs の TagLinks メソッド

protected virtual string TagLinks(string separator)
{
  StateList<string> tags = this.Post.Tags;
  if (tags.Count == 0)
  {
    return null;
  }

  string[] tagStrings = new string[tags.Count];
  const string Link = "<a href=\"{0}/{1}\" rel=\"tag\">{2}</a>";

  //var path = Utils.RelativeWebRoot + "?tag=";
  //for (var i = 0; i < tags.Count; i++)
  //{
  //  var tag = tags[i];
  //  tagStrings[i] = string.Format(
  //    CultureInfo.InvariantCulture, 
  //    Link, 
  //    path, 
  //    HttpUtility.UrlEncode(tag), 
  //    HttpUtility.HtmlEncode(tag));
  //}

  string path = Utils.RelativeWebRoot + "tag";
  for (int i = 0; i < tags.Count; i++)
  {
    string tag = tags[i];
    tagStrings[i] = string.Format(
      CultureInfo.InvariantCulture, 
      Link, 
      path,
      Utils.RemoveIllegalCharacters(tag) + 
        BlogSettings.Instance.FileExtension, 
      HttpUtility.HtmlEncode(tag));
  }

  return string.Join(separator, tagStrings);
}

Core/Web/HttpModules/UrlRewrite.cs の RewriteTag メソッド

private static void RewriteTag(HttpContext context, string url)
{
  string tag = ExtractTitle(context, url);

  if (url.Contains("/FEED/"))
  {
    //context.RewritePath(
    //  string.Format("syndication.axd?tag={0}{1}", 
    //    tag, 
    //    GetQueryString(context)), 
    //  false);

    context.RewritePath(
      string.Format("syndication.axd?tag={0}{1}",
        context.Server.UrlEncode(tag), 
        GetQueryString(context)),
      false);
  }
  else
  {
    //context.RewritePath(
    //  string.Format("{0}?tag=/{1}{2}", 
    //    Utils.RelativeWebRoot, 
    //    tag, 
    //    GetQueryString(context)), 
    //  false);

    context.RewritePath(
      string.Format("{0}default.aspx?tag={1}{2}", 
        Utils.RelativeWebRoot,
        context.Server.UrlEncode(tag), 
        GetQueryString(context)), 
      false);
  }
}

default.aspx.cs の Page_Load メソッド

protected void Page_Load(object sender, EventArgs e)
{
  if (Page.IsCallback)
  {
    return;
  }

  if (Request.RawUrl.ToLowerInvariant().Contains("/category/"))
  {
    DisplayCategories();
  }
  else if (Request.RawUrl.
    ToLowerInvariant().Contains("/author/"))
  {
    DisplayAuthors();
  }
  // else if (Request.RawUrl.ToLowerInvariant().Contains("?tag="))
  else if (Request.RawUrl.ToLowerInvariant().Contains("/tag/"))
  {
    DisplayTags();
  }
  ....

default.aspx.cs の DisplayTags メソッド

private void DisplayTags()
{
  //if (!string.IsNullOrEmpty(Request.QueryString["tag"]))
  //{
  //  PostList1.ContentBy = ServingContentBy.Tag;
  //  PostList1.Posts = 
  //    Post.GetPostsByTag(Request.QueryString["tag"].
  //      Substring(1)).ConvertAll(
  //        new Converter<Post, IPublishable>(delegate(Post p) 
  //          { return p as IPublishable; }));
  //  base.Title = " All posts tagged '" + 
  //    Request.QueryString["tag"].Substring(1) + "'";
  //}

  if (!string.IsNullOrEmpty(Request.QueryString["tag"]))
  {
    PostList1.ContentBy = ServingContentBy.Tag;
    List<Post> posts =
      Post.GetPostsByTag(Request.QueryString["tag"]);
    PostList1.Posts =
      posts.ConvertAll(new Converter<Post, IPublishable>(
        p => p as IPublishable));
    if (posts.Count > 0)
    {
      foreach (string t in posts[0].Tags)
      {
        if (Utils.RemoveIllegalCharacters(t).Equals(
          Request.QueryString["tag"],
          StringComparison.OrdinalIgnoreCase))
        {
          base.Title = "
             All posts tagged '" + Server.HtmlEncode(t) + "'";
          break;
        }
      }
    }
    else
    {
      base.Title = 
        " All posts tagged '" + 
        Request.QueryString["tag"] + "'";
    }
  }
}

Core.Post.cs の GetPostsByTag メソッド

public static List<Post> GetPostsByTag(string tag)
{
  // RemoveIllegalCharacters not required
  //tag = Utils.RemoveIllegalCharacters(tag);

  var list =
    Posts.FindAll(
      p =>
        p.Tags.Any(t => Utils.RemoveIllegalCharacters(t).
          Equals(tag, StringComparison.OrdinalIgnoreCase)));

  return list;
}

Core\Web\Controls\PostViewBase.cs の DescriptionCharacters プロパティ

public int DescriptionCharacters 
{ 
  get 
  {
    int chars = 0;
    string url = 
      HttpContext.Current.Request.RawUrl.ToUpperInvariant();

    //if (url.Contains("/CATEGORY/") || url.Contains("?TAG=/"))
    if (url.Contains("/CATEGORY/") || url.Contains("/TAG/"))
    {
      if (BlogSettings.Instance.
        ShowDescriptionInPostListForPostsByTagOrCategory)
      {
        return BlogSettings.Instance.
          DescriptionCharactersForPostsByTagOrCategory;
      }
    }
    else
    {
      if (BlogSettings.Instance.ShowDescriptionInPostList)
      {
        return BlogSettings.Instance.DescriptionCharacters;
      }
    }
    return chars;
  }
}

User controls/PostList.ascx.cs の ShowExcerpt メソッド

private bool ShowExcerpt()
{
  string url = this.Request.RawUrl.ToUpperInvariant();

  //bool tagOrCategory = url.Contains("/CATEGORY/") || url.Contains("?TAG=/");

  bool tagOrCategory = 
    url.Contains("/CATEGORY/") || url.Contains("/TAG/");

  return BlogSettings.Instance.ShowDescriptionInPostList ||
    (BlogSettings.Instance.
        ShowDescriptionInPostListForPostsByTagOrCategory 
      && 
      tagOrCategory);
}

Tags:

BlogEngine.NET 2.0

BlogEngine.NET 2.0 の導入

by WebSurfer 2011年6月11日 16:00

今年の 1 月にリリースされた BlogEngine.NET バージョン 2.0 を導入しました。

BlogEngine.NET 2.0 の導入

と言っても、既存のバージョン 1.6.1 のブログ をアップグレードしたわけではなく、そこはそのまま残しておいて、BlogEngine2 という名前の別アプリケーションとして構築しました。

まだ試験段階ですので、試験のための記事しかありませんし、スタイルもデフォルトのままです。

今回は、データストアとして、今まで使っていなかった MySQL を使うことにしました(現在のホスティングサービスでは SQL Server と MySQL がそれぞれ 300MB ずつ使えます)。ちなみに、既存のブログ は SQL Server を使用しています。

Forms 認証にはサイト内で共通の SQL Server を利用した SqlMembershipProvider, SqlRoleProvider を使って、サイト内でアプリケーション間を移動するとき再認証を受けなくても済むようにしました。

とりあえず見つけた問題はすべて解決できましたので、どのような問題があったのか、どう解決したのかを備忘録として書いておきます。

データベース構築の問題

BlogEngine.NET/setup/MySQL フォルダの中にデータベースを構築するためのスクリプトファイルが用意されており、これを走らせて必要なテーブルを生成します。このスクリプトは各テーブルを DEFAULT CHARSET=latin1 で作るように指定しています。(latin1 が MySQL のデフォルトらしい)。

一方、MySQL 全体とデータベースのキャラクターセットはすべて UTF-8 で設定しており、その状態で日本語を使うと、Incorrect string value というエラーが出て投稿できませんでした。

結局 latin1 で作ったデータベースはドロップして、スクリプトの DEFAULT CHARSET=latin1 を utf8 に書き換えてテーブルを作り直して解決しました。

バージョン 1.6.1 の諸問題

以下の致命的な問題(詳細はリンク先の記事を参照ください)はすべて解消されていました。(1) については Encoding.Default が Encoding.UTF8 に変更されていました。(2), (3) は大幅にコードが書き換えられており、1.6.1 で問題だったコードは見つからず、実際に試して不具合は出ませんでした。

(1) 日本語の文字化け

(2) SyntaxHighlighter のコードの間違い

(3) reCaptcha のコードの間違い

以下は見栄えなどの軽微な問題です。1.6.1 の場合と同様な修正が必要でした。問題の詳細および修正方法はリンク先の記事を参照ください。

(4) 各記事の左上の日付の表示が 31. 5月 2010 12:15 のようになる

(5) Search の結果、タイトルの表示が「の検索結果 'XXXX'」となる

(6) 「関連するブログ」でテキストの折り返しがされない

(7) ウィジェットのカレンダーが月曜日から始まっている

(8) タイトルが 5月 31. 2011 のように表示される

以下の問題は 1.6.1 の場合(詳細はリンク先の記事を参照ください)より悪化していました。詳細は CodePlex の Discussions のページにアップしました。まだ検証中ですので、サーバーには修正済みファイルはアップしていません。検証が終わって修正版をサーバーにアップしたら、詳しい話を書きます。

(9) Tag cloud 中の日本語の Tag の問題

最後に、以下の問題は、SyntaxHighlighter のバージョンが変わったためか、解消されていました。

(10) SyntaxHighlighter とフラグメント識別子

------------ 2011/6/14 追記 ------------

上の (9) で書いた日本語の Tag の問題の解決策は BlogEngine.NET 2.0 の Tag cloud の問題 に詳細を書きました。興味がありましたらそちらを参照ください。

Tags:

BlogEngine.NET 2.0

About this blog

ここブログ2は趣味など日常のトピックス、ブログ1 は ASP.NET Web アプリ開発関係のトピックスになっています。

Calendar

<<  2019年1月  >>
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar