XHTML 1.1からHTML 4.01への変換

deadspace #2009-05-20 にてXSLTの問題提議がされていたので、少し考えてみました。かいつまんで言うと、XHTML 1.1からHTML 4.01、HTML4.01からXHTML 1.1へのXSL変換では、単にコピーしただけではうまくいかない箇所がでてくるというものです。

取り敢えずこの記事ではXHTML 1.1文書からHTML 4.01文書への変換について考えてみました。

input.xml :

<?xml version="1.0" encoding="UTF-8"?>
<!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">
<head>
  <meta name="robots" content="noarchive"/>
  <title>sample document</title>
  <link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
  <h1>sample document</h1>
  <p>foo bar<br/>baz</p>
</body>
</html>

tr2html.xsl :

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:h="http://www.w3.org/1999/xhtml"
>
<xsl:output
 method="html"
 doctype-public="-//W3C//DTD HTML 4.01//EN"
 doctype-system="http://www.w3.org/TR/html4/strict.dtd"
/>

<!--
  XHTML名前空間に属する要素は、
  同じ要素名かつ名前空間に属さない要素に変換
-->

<xsl:template match="h:*">
  <xsl:element name="{local-name()}">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

<!--
  xml:lang属性はlang属性に変換
-->

<xsl:template match="@xml:lang">
  <xsl:attribute name="lang">
    <xsl:value-of select="."/>
  </xsl:attribute>
</xsl:template>

<!--
  その他の属性・ノードはそのままコピー
  cf. http://www.w3.org/TR/xslt#copying
-->

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

で、結果は以下の通り。

$ xsltproc --version
Using libxml 20703, libxslt 10124 and libexslt 813
xsltproc was compiled against libxml 20632, libxslt 10124 and libexslt 813
libxslt 10124 was compiled against libxml 20632
libexslt 813 was compiled against libxml 20632
$ xsltproc --novalid tr2html.xsl input.xml
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="robots" content="noarchive">
  <title>sample document</title>
  <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
  <h1>sample document</h1>
  <p>foo bar<br>baz</p>
</body>
</html>

※ --novalid オプションが無いと、不要な属性(html要素のversion属性、head要素のprofile属性など)が出力文書に現われるので注意。

XSLT 1.0でHTML出力メソッド (<xsl:output method="html"/>) をうまく働かすには、出力する要素が名前空間に属さないことが必要であるようです。以下仕様書和訳の 16.2 HTML 出力メソッド より。

html 出力メソッドは、エレメントの展開された名前が null のネームスペース URI を持つ場合を除き、xml 出力メソッドと異なる方法でエレメントを出力すべきではない。展開された名前が null 以外のネームスペース URI を持つエレメントは、XML として出力すべきである。 展開された名前が null のネームスペース URI を持つが、ローカルパートが HTML エレメントの名前であると認識されないエレメントは、span などの空でないインラインエレメントと同じ方法で出力すべきである。

html 出力メソッドは空エレメントのエンドタグを出力すべきではない。 HTML 4.0 の空エレメントは、areabasebasefontbrcolframehrimginputisindexlinkmeta、および param である。 たとえば、スタイルシートに <br/> または <br></br> と記述されたエレメントは、<br> と出力される。