reflux flow > XSLTでフラットな文書を構造化
次のようにdiv要素を追加するXSLTスタイルシートです。
<!-- 変換前 -->
<h1>見出し</h1>
<h2>見出し</h2>
<p>本文</p>
<h3>見出し</h3>
<p>本文</p>
<h3>見出し</h3>
<p>本文</p>
<h2>見出し</h2>
<p>本文</p>
<!-- 変換後 -->
<div class="section">
<h1>見出し</h1>
<div class="section">
<h2>見出し</h2>
<p>本文</p>
<div class="section">
<h3>見出し</h3>
<p>本文</p>
</div>
<div class="section">
<h3>見出し</h3>
<p>本文</p>
</div>
</div>
<div class="section">
<h2>見出し</h2>
<p>本文</p>
</div>
</div>
最終更新日: 2004年12月1日
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output
method="xml"
version="1.0"
encoding="UTF-8"
indent="yes"
/>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="attribute::*|child::node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="attribute::*|text()">
<xsl:copy/>
</xsl:template>
<!-- startStructuringテンプレートの呼び出し例 -->
<xsl:template match="*[local-name() = 'body' and namespace-uri() = 'http://www.w3.org/1999/xhtml']">
<xsl:element name="{name()}">
<xsl:call-template name="startStructuring">
<xsl:with-param name="node" select="child::node()[1]"/>
</xsl:call-template>
</xsl:element>
</xsl:template>
<!-- ここから本体 -->
<xsl:template name="startStructuring">
<!-- nodeパラメータに構造化をはじめる最初のノード指定する -->
<xsl:param name="node"/>
<xsl:for-each select="$node">
<xsl:call-template name="traceNode">
<xsl:with-param name="currentLevel" select="number('0')"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="traceNode">
<!-- 同レベルか上位の見出しが見つかるまでノードを出力する -->
<xsl:param name="currentLevel"/>
<xsl:choose>
<xsl:when test="starts-with(local-name(),'h') and boolean(number(substring-after(local-name(),'h')))">
<!-- カレントノードが見出しの場合 -->
<xsl:variable name="headingLevel" select="number(substring-after(local-name(),'h'))"/>
<xsl:if test="$headingLevel > $currentLevel">
<!-- 現在のレベルよりも下位の見出しの場合は出力する -->
<xsl:call-template name="createSection">
<xsl:with-param name="currentLevel" select="$headingLevel"/>
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="self::node()"/>
<!-- 見出しを見つけるまで再帰的にテンプレートを呼び出す -->
<xsl:for-each select="following-sibling::node()[1]">
<xsl:call-template name="traceNode">
<xsl:with-param name="currentLevel" select="$currentLevel"/>
</xsl:call-template>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="createSection">
<xsl:param name="currentLevel"/>
<!-- div要素と見出し要素の出力 -->
<div class="section">
<xsl:apply-templates select="self::node()"/>
<xsl:for-each select="following-sibling::node()[1]">
<xsl:call-template name="traceNode">
<xsl:with-param name="currentLevel" select="$currentLevel"/>
</xsl:call-template>
</xsl:for-each>
</div>
<!-- 同じsection内の同レベルの見出しを探す -->
<xsl:call-template name="searchHeading">
<xsl:with-param name="currentLevel" select="$currentLevel"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="searchHeading">
<xsl:param name="currentLevel"/>
<xsl:for-each select="following-sibling::node()[1]">
<xsl:variable name="headingLevel">
<!-- 見出しのレベルを調べる -->
<xsl:choose>
<xsl:when test="starts-with(local-name(),'h') and boolean(number(substring-after(local-name(),'h')))">
<xsl:value-of select="number(substring-after(local-name(),'h'))"/>
</xsl:when>
<xsl:otherwise>
<!-- 見出しでない場合は、レベルを最大にする -->
<xsl:value-of select="number('1000')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<!-- 現在のレベルと同じか上位の見出しが現れるまで再帰的にテンプレートを呼び出す -->
<xsl:when test="$headingLevel > $currentLevel">
<xsl:call-template name="searchHeading">
<xsl:with-param name="currentLevel" select="$currentLevel"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$headingLevel = $currentLevel">
<!-- レベルが同じ見出しの場合は書き出す -->
<xsl:call-template name="createSection">
<xsl:with-param name="currentLevel" select="$headingLevel"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
必要なテンプレートは、traceNode、createSection、searchHeadingの3つです。また、構造化を開始する記述か少々複雑なので、それを簡略化するためにstartStructuringというテンプレートを用意しました。よくわからない場合はこれを利用して下さい。
構造化した文書を埋め込む場所(例えばbody要素の中)でstartStructuringテンプレートを呼び出します。このとき、構造化を開始するノード(例えばbody要素の最初の子ノード)をnodeパラメータとして渡します。
<!-- 例 -->
<xsl:template match="body">
<body>
<xsl:call-template name="startStructuring">
<xsl:with-param name="node" select="child::node()[1]"/>
</xsl:call-template>
</body>
</xsl:template>
自由に書き換えて再利用して構いませんが、ご使用は自己責任でお願いします。