全ページをXHTML1.1からHTML5(polyglot)に書き換える際にやったことのメモ。 HTML5化、当サイトでの事例。

前提

まず、当サイトのページの生成方法については次のとおり。 これは移行の前後で変わらず。

  1. PukiWiki記法をベースにした記法で本文ソースを記述 (PukiWiki)
  2. 上記ソースからXMLフラグメントを生成 (pakinaを使用)
  3. ヘッダ・フッタ等が付いたテンプレートにはめ込んでXML文書を生成
  4. パンくずリスト等の装飾・加工を施す
  5. XHTML文書の完成

上記の工程でXHTMLを取り扱うために.NET FrameworkのSystem.Xml.XmlDocumentおよびSystem.Xml.XmlReaderを使っているため、移行の際に主に問題となったのはこれに関わる箇所。

移行作業

以下、移行に際してやったこと、起きた問題と対処など。

文書のHTML5化

文書をXHTML1.1からHTML5(polyglot)に変更。

DOCTYPE等の変更

DOCTYPEを含むヘッダ部分を以下のように変更。

  1. DOCTYPEをXHTML1.1からHTML5に変更
  2. XML宣言を削除
  3. <meta http-equiv="Content-Type" ~/>を<meta charset="utf-8" />に変更
  4. ルート要素の名前空間はそのまま残す (文書を加工する際のXPath式で名前空間を指定しているため)

この変更前後のヘッダ部分は次のとおり。

変更前
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
      :
変更後
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <meta charset="utf-8" />
      :

この変更で参照するDTDが無くなったことにより、以下の問題が発生。

問題1.文字実体参照が解決できない
XHTML1.1のDTDでは宣言されていた文字参照が解決できなくなったため、&copy;や&nbsp;などの文字参照があるとXmlDocument.Loadで例外となり、処理できない。
対処
それほど多くの箇所で使用していたわけではなかったので、文字参照を使わないように変更。 実体に置き換える(&copy;→©)か、数値文字参照に変更する(&nbsp;→&#xa0;)など、使用していた箇所に合わせて適宜変更。
問題2.XmlDocument.GetElementByIdが機能しない
XmlDocument.GetElementByIdは、DTDでID型と定義されている属性の値から特定の要素を探す。 XHTML1.1のDTDがある場合、id属性はID型とみなされる。 一方、HTML5の(DTDがなくid属性がID型と定義されない)文書では、id属性は一般属性として扱われるために該当する要素を見つけられず、常にnullが返されるようになってしまう。
var doc = new XmlDocument();

doc.Load("html5.xhtml");

var nodeHeader = doc.GetElementById("header");

nodeHeader.AppendChild(...); // <- ここでNullReferenceException
対処
次のようなメソッドを用意して、XmlDocument.SelectSingleNodeを使ってXPathで目的の要素を探すように変更。
public class XhtmlDocument : XmlDocument {
  public XmlElement GetXhtmlElementById(string id)
  {
    var attrId = SelectSingleNode(string.Concat("//@id[.='", id, "']")) as XmlAttribute;

    if (attrId == null)
      return null;
    else
      return attrId.OwnerElement;
  }
}
問題3.XmlValidatingReaderを使った検証が出来ない
簡易バリデータとして使っていたXmlValidatingReaderが、XMLとしての検証のみにしか使えなくなった。
対処
公式のDTDは用意されないので、The W3C Markup Validation Serviceを使うことにした。

Content-Typeの変更

送出するContent-Typeヘッダの内容を次のように変更。

  1. メディアタイプをtext/htmlに変更
  2. charsetを明示するように変更
変更前
Content-Type: application/xhtml+xml
変更後
Content-Type: text/html; charset=utf8

CDATAセクションの削除

HTML要素にCDATAセクションを含めないように変更。

  1. style, script要素は特に変更なし(コメントアウトしたCDATAセクションのまま)
  2. ほかの要素内ではCDATAセクションを含まないよう、XmlDocument.CreateCDataSectionXmlDocument.CreateTextNodeに置き換え

構造化要素の導入

HTML5の新しい構造化要素を導入。

HTML5の新要素に対応していないブラウザの考慮

IE6~8についてはhtml5shivで対応し、これで対応できないブラウザは切り捨て。

<head>
  <!--[if lt IE 9]>
  <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  <![endif]-->
</head>

合わせて、CSSに以下の内容を追加。

article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
  display: block;
}

header, footer, articleなどの要素の導入

  1. もともとidでheader, footerと定めてdiv要素でマークアップしていたものを、header, footer要素に置き換え
  2. 合わせて文書全体の構造も見直し、構造の変更に合わせてXPath式やCSS等も修正

この変更前後の文書構造は次のとおり。

導入前の構造
<body>
  <div id="header">
    (ヘッダ)
    <div>
      <ul>(パンくずリスト)</ul>
    </div>
    <h1>(タイトル)</h1>
  </div>
  <div id="contents">
    <div id="menu">
      (サイドバー)
      <div id="toc">
        <ul>(目次)</ul>
      </div>
    </div>
    <div id="body">
      (本文)
      <div id="note">
        (ページ情報等)
      </div>
    </div>
  </div>
  <div id="footer">
    (フッタ)
  </div>
</body>
導入後の構造
<body>
  <header id="header">
    (ヘッダ)
    <nav>
      <ul>(パンくずリスト)</ul>
    </nav>
  </header>
  <article id="contents">
    <header>
      <h1>(タイトル)</h1>
    </header>
    <aside id="menu">
      (サイドバー)
      <nav id="toc">
        <ul>(目次)</ul>
      </nav>
    </aside>
    <div id="body">
      (本文)
    </div>
    <footer id="note">
      (ページ情報等)
    </footer>
  </article>
  <footer id="footer">
    (フッタ)
  </footer>
</body>

この変更の結果、<header id="header">のようにid属性が冗長となるものの、XPath式やCSSなどノードをid属性で参照している箇所が多く、削除しようとすると変更しなければならない箇所が多いのでそのまま残した。

h1要素については、本文の見出しとして扱われるようarticle要素内に移動した。

section要素の導入

  1. もともと導入を考慮してdiv要素でマークアップしていたものを、section要素に置き換え
  2. 各sectionの見出し要素をhn要素からh1要素に統一
  3. hnと同じスタイルを各section内のh1にも適用するようCSSを修正
導入前の構造
<div id="body">
  <p>前文</p>
  <div class="section-nest1" id="section.1">
    <h2>1</h2>
    <p></p>
    <div class="section-nest2" id="section.1.1">
      <h3>1</h3>
      <p></p>
    </div>
    <div class="section-nest2" id="section.1.2">
      <h3>2</h3>
      <p></p>
      <div class="section-nest3" id="section.1.2.1">
        <h4>1</h4>
        <p></p>
      </div>
    </div>
  </div>
  <div class="section-nest1" id="section.2">
    <h2>2</h2>
    <p></p>
  </div>
</div>
導入後の構造
<div id="body">
  <p>前文</p>
  <section class="section-nest1" id="section.1">
    <h1>1</h1>
    <p></p>
    <section class="section-nest2" id="section.1.1">
      <h1>1</h1>
      <p></p>
    </section>
    <section class="section-nest2" id="section.1.2">
      <h1>2</h1>
      <p></p>
      <section class="section-nest3" id="section.1.2.1">
        <h1>1</h1>
        <p></p>
      </section>
    </section>
  </section>
  <section class="section-nest1" id="section.2">
    <h1>2</h1>
    <p></p>
  </section>
</div>

その他細かいHTML5化作業

(現在進行中)

address, small要素

address要素、small要素の意味が変わったのに合わせて、フッタ部を修正。

変更前
<address>
  <span class="copyright">Copyright © 2005-2011 smdn. <a href="mailto:info@smdn.jp" title="e-mail address">contact</a></span>
</address>
変更後
<small>Copyright © 2005-2011 smdn.</small>
<address>
  <a href="mailto:info@smdn.jp" title="e-mail address">contact</a>
</address>

small要素は使っていなかったので、ここ以外での変更は無し。

その他の要素の導入

  1. time要素
    1. 最終更新日などツールで出力している箇所から導入、日付を手打ちした部分は見つけたら置き換える
  2. figure要素
    1. (imgやtable等)
  3. nav要素
    1. (前後ページのリンク)