WebSurfer's Home

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

W3C 検証にパスしない

by WebSurfer 2011年6月13日 23:23

BlogEngine.NET はバージョン 2.0 になって W3C Markup Validation Service にパスしなくなってしまいました。

原因は、以下のように、外部スクリプトファイルの定義にクエリ文字列 &minify= が追加されたことによります。& そのままでなく、HTML エンコードして & にしないとパスしません。

<script 
  type="text/javascript" 
  src="/BlogEngine2/js.axd?path=Scripts%2fblog.js&minify=">
</script>
<script 
  type="text/javascript" 
  src="/BlogEngine2/js.axd?path=%2fBlogEngine2%2fadmin%2fwidget.js&minify=">
</script>

この問題を解決するには、Core.Utils の ResolveScriptUrl メソッドを以下のように修正します。

public static string ResolveScriptUrl(string url, bool minify)
{
  var minifyQuery = (minify ? "&minify=" : String.Empty);

  //return string.Format("{0}js.axd?path={1}{2}", 
  //  Utils.RelativeWebRoot,
  //  HttpUtility.UrlEncode(url),
  //  minifyQuery);

  return string.Format("{0}js.axd?path={1}{2}",
    Utils.RelativeWebRoot,
    HttpUtility.UrlEncode(url),
    HttpUtility.HtmlEncode(minifyQuery));
}

さらに、ページングする際に &page=n(n はページ番号)というクエリ文字列がページャーのリンクの URL に追加される場合があります。その場合はやはり W3C Markup Validation は通りません。この問題はバージョン 1.6.1 にもありましたが、今まで気がつきませんでした。(汗)

App_Code/Control/PostPager.cs の PageUrl メソッド

これはバージョン 2.0 のページャー用に追加されたものです(バージョン 1.6.1 にはありません)。

private static string PageUrl()
{
  var path = 
    HttpContext.Current.Request.RawUrl.
    Replace("Default.aspx", string.Empty);
  if (path.Contains("?"))
  {
    if (path.Contains("page="))
    {
      var index = path.IndexOf("page=");
      path = path.Substring(0, index);
    }
    else
    {                    
      // ここで単純に path += "&amp;"; としてもダメ。
      path += "&";
    }
  }
  else
  {
    path += "?";
  }

  // これを追加。
  path = path.Replace("&", "&amp;");

  return path + "page={0}";
}

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

これはバージョン 2.0 と 1.6.1 両方にあります。バージョン 2.0 では、これを使うページャーは style="display: none" となっており表示はされませんが、html コードには現れてくるので修正が必要です。

private void InitPaging()
{            
  var path = 
    this.Request.RawUrl.Replace("Default.aspx", string.Empty);

  path = path.Contains("?") ? 
    (path.Contains("page=") ? 
      path.Substring(0, path.IndexOf("page=")) 
      : string.Format("{0}&", path))
    : string.Format("{0}?", path);

  var page = this.GetPageIndex();
  var url = string.Format("{0}page={{0}}", path);

  // 下記の追加のみ
  url = url.Replace("&", "&amp;");

  this.hlNext.HRef = string.Format(url, page);
  this.hlPrev.HRef = string.Format(url, page + 2);

  if (page == 0)
  {
    this.hlNext.Visible = false;
  }
  else
  {
     ((BlogBasePage)this.Page).AddGenericLink(
       "next", 
       "Next page", 
       this.hlNext.HRef);
  }

  if (this.hlPrev.Visible)
  {
    ((BlogBasePage)this.Page).AddGenericLink(
      "prev", 
      "Previous page", 
      string.Format(url, page + 2));
  }
}

Tags:

BlogEngine.NET 2.0

日本語の著者名の問題

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

About this blog

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

Calendar

<<  2018年6月  >>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

View posts in large calendar