reflux flow > XSLTでフラットな文書を構造化

XSLTでフラットな文書を構造化

このページの目次
  1. 概要
  2. ソースコード
  3. 簡単な説明
  4. 関連記事
公開日
2008-09-20
最終更新日
2007-06-03T11:37:14+09:00

概要

次のように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 &gt; $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 &gt; $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>

注意事項

自由に書き換えて再利用して構いませんが、ご使用は自己責任でお願いします。

関連記事

外部リンク

フラットな文書を構造化 その2(agenda)
構造化スタイルシートの元祖?
連絡先: r e f l u x f l o w [あっとまーく] g m a i l . c o m