テンプレートを定義していない要素をそのまま出力

はじめに

xmlの要素に対応するテンプレートがxsltにある場合だけテンプレートを適応し,対応するテンプレートがない場合xmlの要素をそのまま出力してくれると便利である.そういうテンプレートを考えてみた.

環境は以下の通り.

  • Microsoft Windows XP SP2+セキュリティパッチたくさん
  • Cygwin 1.5.24(uname -r で確認)
  • Saxon-B 9.0.0.2J

以下試行錯誤の結果をつらつら書いている.結果を知りたい場合は最終結果へ.

テスト用xml

テスト用に以下のxmlを使用する.

sam.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!-- comment1 -->
<root>
  begin

  <!-- comment2 -->

  <ul class="ul_attr" id="ul_id">
    <li>foo<date>2008.01.29</date></li>
    <li>bar<date>2008.01.30</date></li>
  </ul>

  end
</root>

以下の要素を含んでいる.

属性のない要素
root,li
属性のある要素
ul
xsltで変換する要素
date
テキストノード
begin,end,foo,bar,2008.01.29,2008.01.30
コメント
comment1,comment2

これを以下のように変換(date要素はspanに変換.それ以外はそのままコピー)するxsltを作ろう.

変換結果:

<?xml version="1.0" encoding="UTF-8"?>
<!-- comment1 -->
<root>
  begin

  <!-- comment2 -->

  <ul class="ul_attr" id="ul_id">
    <li>foo<span class="date">2008.01.29</span></li>
    <li>bar<span class="date">2008.01.30</span></li>
  </ul>

  end
</root>

テンプレートを何も定義しないxslt

テンプレートを何も定義しない下記のxsltで上記sam.xmlを変換してみよう.

sam.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

</xsl:stylesheet>

コマンドライン(Cygwin版bash)から以下のように実行する.なお,Saxonをインストールしたディレクトリを$saxon_homeと表記する.

java -jar "${saxon_home}/saxon9.jar" -versionmsg:off -s:sam.xml -xsl:sam.xsl

こんな結果になる.

実行結果:

<?xml version="1.0" encoding="UTF-8"?>
    begin




            foo2008.01.29
            bar2008.01.30


    end

テンプレートを何も定義しない場合,デフォルトで以下の規則が有効になるからである.

要素ノード
xsl:apply-templatesを呼ぶ
テキストノード
そのまま出力
コメントノード
出力しない

したがって,sam.xmlにある

要素ノード(root,li,ul,date)
合致するテンプレートがなく出力しない.
テキストノード(ulのclassとid)
そのまま出力
コメントノード(comment1,comment2)
出力しない

となる.

コピーはできる

単純にコピーするだけだったら,以下のxsltでできる.

sam.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:template match="node()">
    <xsl:copy-of select="."/>
  </xsl:template>

</xsl:stylesheet>

しかし,コピーしているだけなので,xsltで変換する要素(date)のテンプレートを定義しても変換はできない.つまり,以下のxsltでsam.xmlを変換しても要素dateはspanにはならない.任意のノード(node())にマッチするテンプレートの中でxsl:apply-templatesを呼んでいないためである.

sam.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:template match="date">
    <span class="date"><xsl:value-of select="."/></span>  <!-- ここのテンプレートはマッチしない -->
  </xsl:template>

  <xsl:template match="node()">
    <xsl:copy-of select="."/>  <!-- xsl:apply-templatesを呼んでないので -->
  </xsl:template>
</xsl:stylesheet>

実行結果:

<?xml version="1.0" encoding="UTF-8"?>
<!-- comment1 -->
<root>
  begin

  <!-- comment2 -->

  <ul class="ul_attr" id="ul_id">
    <li>foo<date>2008.01.29</date></li>
    <li>bar<date>2008.01.30</date></li>
  </ul>

  end
</root>

apply-templatesを呼ぶ

「カレントノードをそのままコピーして,子ノードにテンプレートを適応するためにapply-templatesを呼ぶ」ようなテンプレートを作れば良いようだ.ということで考えたのが以下のxsltである.

sam.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:template match="date">
    <span class="date"><xsl:value-of select="."/></span>
  </xsl:template>

  <xsl:template match="*">
    <xsl:element name="{name()}"> <!-- マッチしたのと同じ名前のノードを作って -->
      <xsl:apply-templates select="node()|@*"/> <!-- ノード or 属性のテンプレートを呼ぶ -->
    </xsl:element>
  </xsl:template>

  <xsl:template match="@*|comment()"> <!-- 属性とコメントはそのままコピー -->
    <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>

実行結果:

<?xml version="1.0" encoding="UTF-8"?>
<!-- comment1 -->
<root>
  begin

  <!-- comment2 -->

  <ul class="ul_attr" id="ul_id">
    <li>foo<span class="date">2008.01.29</span></li>
    <li>bar<span class="date">2008.01.30</span></li>
  </ul>

  end
</root>

想定した内容になった.

copyで囲む

<xsl:copy/>を使うとノードのコピーができる.これを使ってみよう.

sam.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:template match="date">
    <span class="date"><xsl:value-of select="."/></span>
  </xsl:template>

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

  <!-- xsl:copyでコピーできるので,このテンプレートは不要
  <xsl:template match="@*|comment()">
    <xsl:copy-of select="."/>
  </xsl:template>
  -->
</xsl:stylesheet>

実行結果:

<?xml version="1.0" encoding="UTF-8"?>
<!-- comment1 -->
<root>
  begin

  <!-- comment2 -->

  <ul class="ul_attr" id="ul_id">
    <li>foo<span class="date">2008.01.29</span></li>
    <li>bar<span class="date">2008.01.30</span></li>
  </ul>

  end
</root>

最終結果

ということで,最終結果は以下の通り.

sam.xml(変換元):

<?xml version="1.0" encoding="UTF-8"?>
<!-- comment1 -->
<root>
  begin

  <!-- comment2 -->

  <ul class="ul_attr" id="ul_id">
    <li>foo<date>2008.01.29</date></li>
    <li>bar<date>2008.01.30</date></li>
  </ul>

  end
</root>

sam.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:template match="date">
    <span class="date"><xsl:value-of select="."/></span>
  </xsl:template>

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

実行方法:

$ java -jar "${saxon_home}/saxon9.jar" -versionmsg:off -s:sam.xml -xsl:sam.xsl

実行結果:

<?xml version="1.0" encoding="UTF-8"?>
<!-- comment1 -->
<root>
  begin

  <!-- comment2 -->

  <ul class="ul_attr" id="ul_id">
    <li>foo<span class="date">2008.01.29</span></li>
    <li>bar<span class="date">2008.01.30</span></li>
  </ul>

  end
</root>

変換元のコメントは変換結果に出力しないxsltは以下の通り.

sam.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:template match="date">
    <span class="date"><xsl:value-of select="."/></span>
  </xsl:template>

  <xsl:template match="*|@*|text()"> <!-- コメント要素以外にマッチ -->
    <xsl:copy>
      <xsl:apply-templates select="*|@*|text()"/> <!-- コメント要素以外に適応 -->
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

実行結果:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  begin



  <ul class="ul_attr" id="ul_id">
    <li>foo<span class="date">2008.01.29</span></li>
    <li>bar<span class="date">2008.01.30</span></li>
  </ul>

  end
</root>

参考文献

Steven Holzner著/株式会社クイック訳,XSLT実践ガイド,株式会社アスキー