目次


型とスキーマを使用して XSLT 2.0 スタイルシートを改善する

スキーマ型、パラメーター型、戻り値を XSLT 2.0 によって指定し、デバッグや保守を容易にする

Comments

XML で記述されたコンテンツは複雑なものになる可能性があり、予測することができません。XSLT 1.0 を使用して XML を処理する場合、適切な式を定義したり、コンテンツが取り得るあらゆる構造を処理したりする上で、何度も試行錯誤を繰り返す必要があります。例えば、XPath 1.0 式で名前の綴りを間違えた場合でも、有用なエラー・メッセージが返されるわけではなく、何も返されないため、デバッグするのは簡単ではありません。

XSLT 2.0 はバージョン 1.0 よりも大幅に改善されており、強い型付けとスキーマ認識機能が便利な機能として追加されています。XSLT スタイルシートで値の型を宣言すると、XML で記述されたコンテンツに含まれるデータ型に関する想定が不適切な場合や、特定の要素や属性の出現回数を不適切に想定している場合に、プロセッサーがそれを指摘してくれます。この機能により、従来よりもはるかに有用なエラー・メッセージが表示されます。さらに XML Schema をインポートすると、XPath 式が正しくない場合に、何も返されないのではなく、入力文書の構造に関してユーザーに通知するのに十分な情報がプロセッサーに提供されるようになります。XML Schema は XML コンテンツに含まれるデータ型に関する情報も提供してくれるため、データ型に合わない処理をすることがなくなります。

XSLT で型を使用する

ほとんどのプログラミング言語には、変数やパラメーターの型を指定する手段が用意されています。XSLT 2.0 では、as 属性を使用して、以下のようにさまざまな場所で式の型を宣言することができます。

  • xsl:variable 要素または xsl:param 要素: その変数またはパラメーターの型を示します
  • xsl:template 要素または xsl:function 要素: そのテンプレートまたは関数の戻り型を示します
  • xsl:sequence 要素: そのシーケンスの型を示します
  • xsl:with-param 要素: テンプレートに渡される値の型を示します

as 属性の値は「シーケンス型」と呼ばれます。

XML Schema の組み込み型を使用する

as 属性で指定可能な一般的なシーケンス型は、string や integer など、特定のデータ型の名前です。XSLT の場合、XML Schema 仕様で規定されているデータ型の 1 つを使用します。そのためには、型の名前の前に例えば xs: を追加し、スタイルシートの先頭で xs: 名前空間を宣言します。表 1 は最も一般的に使用される XML Schema のデータ型を一覧にしたものです。

表 1. 一般的に使用される XML Schema のデータ型
データ型の名前説明
string任意のテキスト文字列abc, this is a string
integer任意の大きさの整数1, 2
decimal小数1.2, 5.0
double倍精度浮動小数点数1.2, 5.0
dateYYYY-MM-DD 形式の日付2009-12-25
timeHH:MM:SS 形式の時刻12:05:04
booleantrue または false の値true, false
anyAtomicType任意の単純型の値a string, 123, false, 2009-12-25

例えば、ある変数の値が日付であると宣言したい場合には、以下のように表現することができます。

<variable name="effDate" select="bill/effDate" as="xs:date"/>

文字列や日付などの単純な値はアトミック値と呼ばれます。上記例の effDate が要素の場合、effDate 要素の内容は xs:date 型のアトミック値に変換されることに注意してください (ただし入力文書に何もスキーマが関連付けられていないという前提です)。

XML ノードを表現するシーケンス型を使用する

より汎用的なシーケンス型 (表 2) を使用して XML ツリーのノードの種類を表現することもできます。シーケンス型は XML Schema の型ではないため、シーケンス型の前には xs: を付けません。

表 2. XML ノードを表現するシーケンス型
シーケンス型説明
element()任意の要素
element(book)book という名前の任意の要素
attribute()任意の属性
attribute(isbn)isbn という名前の任意の属性
text()任意のテキスト・ノード
node()任意の種類のノード (要素、属性、テキスト・ノードなど)
item()任意の種類のノード、または任意の種類のアトミック値 (文字列、整数など)

例えば、ある変数にバインドされている値が要素であることを表現したい場合には、以下のように表現することができます。

<variable name="effDate" select="//effDate" as="element()"/>

先ほどの例とは異なり、この要素はアトミック値には変換されず、変数は要素自体を含むことになります。

オカレンス・インディケーターを使用する

表 3 のオカレンス・インディケーターを使用すると、ある特定の項目が何回出現するかを表現することもできます。これらのオカレンス・インディケーターは表 1 または表 2 の式の後で、シーケンス型の末尾で使用されます。

表 3. オカレンス・インディケーターを使用する
オカレンス・インディケーター説明
*0 回以上
?0 回または 1 回
+1 回以上
(オカレンス・インディケーターなし)1 回のみ

例えば、ある変数にバインドされている値が、0 回以上出現する要素であることを表現したい場合には、以下のように表現することができます。

<variable name="effDate" select="//effDate" as="element()*"/>

出現回数が 0 回の要素 (または任意の種類の項目) は「空シーケンス」とも呼ばれます。

型を使用してスタイルシートをより確実なものにする

as 属性を使用すると、どのようにスタイルシートが改善されるのでしょう。いくつか例を見てみましょう。この記事のコード・サンプルはすべて .zip ファイルとしてダウンロードすることができます (「ダウンロード」を参照)。

シナリオ 1: 型を使用してカーディナリティーを設定する

リスト 1 は 2 冊の本を含む入力文書の例です。

リスト 1. 本の入力文書の例 (books.xml)
<books xmlns="http://www.datypic.com/books/ns">
  <book>
    <title>The East-West House: Noguchi's Childhood in Japan</title>
    <author>
      <person>
        <first>Christy</first>
        <last>Hale</last>
      </person>
    </author>
    <price>9.95</price>
    <discount>1.95</discount>
  </book>
  <book>
    <title>Buckminster Fuller and Isamu Noguchi: Best of Friends</title>
    <author>
      <person>
        <first>Shoji</first>
        <last>Sadao</last>
      </person>
    </author>
    <price>65.00</price>
    <discount>5.00</discount>
  </book>
</books>

リスト 2 は本の入力文書を HTML に変換するスタイルシートです。

リスト 2. シナリオ 1 の XSLT の最初のバージョン (xslt_before.xsl)
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:dtyf="http://www.datypic.com/functions"
  xpath-default-namespace="http://www.datypic.com/books/ns">

  <xsl:output method="html"/>

  <xsl:template match="books">
    <html>
      <body>
        <table border="1">
          <xsl:apply-templates/>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="book">
    <tr>
      <td><xsl:value-of select="title"/></td>
      <td><xsl:value-of select="dtyf:format-person-name(author/person)"/></td>
      <td><xsl:value-of select="price"/></td>
    </tr>
  </xsl:template>

  <xsl:function name="dtyf:format-person-name">
    <xsl:param name="person"/>
    <xsl:value-of select="$person/last"/>
    <xsl:text>, </xsl:text>
    <xsl:value-of select="$person/first"/>
  </xsl:function>
</xsl:stylesheet>

上記の入力文書を使用して、想定どおりに XSLT が実行され、図 1 の結果が出力されます。

図 1. シナリオ 1 の XSLT の最初のバージョンを books.xml に対して実行した結果
シナリオ 1 の XSLT の最初のバージョンを books.xml に対して実行した結果
シナリオ 1 の XSLT の最初のバージョンを books.xml に対して実行した結果

ここで仮に、もっと変化に富んだ morebooks.xml (リスト 3) のようなテスト・データに対して XSLT を実行するとしましょう

リスト 3. 変化に富んだ入力文書 (morebooks.xml)
<books xmlns="http://www.datypic.com/books/ns">
  <book>
    <title>Isamu Noguchi: Sculptural Design</title>
    <author>
      <institution>Vitra Design Museum</institution>
    </author>
    <price>125.00</price>
  </book>
  <book>
    <title>The East-West House: Noguchi's Childhood in Japan</title>
    <author>
      <person>
        <first>Christy</first>
        <last>Hale</last>
      </person>
    </author>
    <price>9.95</price>
    <discount>1.95</discount>
  </book>
  <book>
    <title>Buckminster Fuller and Isamu Noguchi: Best of Friends</title>
    <author>
      <person>
        <first>Shoji</first>
        <last>Sadao</last>
      </person>
    </author>
    <price>65.00</price>
    <discount>5.00</discount>
  </book>
  <book>
    <title>Isamu Noguchi and Modern Japanese Ceramics: A Close Embrace
               of the Earth</title>
    <author>
      <person>
        <first>Louise</first>
        <middle>Allison</middle>
        <last>Cort</last>
      </person>
    </author>
    <author>
      <person>
        <first>Bert</first>
        <last>Winther-Tamaki</last>
      </person>
    </author>
    <author>
      <person>
        <first>Bruce</first>
        <middle>J.</middle>
        <last>Altshuler</last>
      </person>
    </author>
    <author>
      <person>
        <first>Ryu</first>
        <last>Niimi</last>
      </person>
    </author>
    <price>29.95</price>
    <discount>5.95</discount>
  </book>
</books>

図 2 を見ると、不完全な結果であることがわかります。何らかの施設や団体が本の著者になっている場合には、著者が表示されていません。また、本の著者が複数の場合には、コンマの前にすべての著者の苗字 (ラスト・ネーム) が表示され、コンマの後にすべての著者の名前 (ファースト・ネーム) が表示されています。スタイルシートからは、エラー・メッセージは出力されず、代わりに不適切な変換結果が何の警告もなく出力されています。

図 2. シナリオ 1 の XSLT の最初のバージョンを morebooks.xml に対して実行した結果
シナリオ 1 の XSLT の最初のバージョンを morebooks.xml に対して実行した結果
シナリオ 1 の XSLT の最初のバージョンを morebooks.xml に対して実行した結果

より確実なスタイルシートを作成するために、dtyf:format-person-name 関数をリスト 4 のように修正します。ここではパラメーターと関数の両方に as 属性を (戻り型として) 追加し、この関数を作成したときに何を想定していたのかを明確にしています。

リスト 4. シナリオ 1 の XSLT の改訂後の dtyf:format-person-name 関数
<xsl:function name="dtyf:format-person-name" as="xs:string*">
  <xsl:param name="person" as="element()"/>
  <xsl:value-of select="$person/last"/>
  <xsl:text>, </xsl:text>
  <xsl:value-of select="$person/first"/>
</xsl:function>

シーケンス型 element() は 1 つの要素のみが許可されることを示します。この変更した XSLT を実行すると、以下のように適切なエラー・メッセージが出力されます。

  • 「An empty sequence is not allowed as the first argument of dtyf:format-person-name() (dtyf:format-person-name() の最初の引数として、空シーケンスは許可されません)」 (施設や団体が本の著者になっている場合)
  • 「A sequence of more than one item is not allowed as the first argument of dtyf:format-person-name() (dtyf:format-person-name() の最初の引数として、複数の項目を含むシーケンスは許可されません)」 (複数の著者がいる場合)

これらのエラー・メッセージから、施設や団体が本の著者になっている場合と複数の著者がいる場合の両方を処理するためには、リスト 5 のように XSLT を変更すればよいことがわかります。

リスト 5. シナリオ 1 の XSLT の最終バージョン
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:dtyf="http://www.datypic.com/functions"
  xpath-default-namespace="http://www.datypic.com/books/ns">

  <xsl:output method="html"/>

  <xsl:template match="books">
    <html>
      <body>
        <table border="1">
          <xsl:apply-templates/>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="book">
    <tr>
      <td><xsl:value-of select="title"/></td>
      <td>
<xsl:for-each select="author">
          <xsl:choose>
            <xsl:when test="person">
              <xsl:value-of select="dtyf:format-person-name(person)"/>
            </xsl:when>
            <xsl:when test="institution">
              <xsl:value-of select="institution"/>
            </xsl:when>
          </xsl:choose>
          <xsl:if test="position() != last()">
            <br/>
          </xsl:if>
        </xsl:for-each>
      </td>
      <td><xsl:value-of select="price"/></td>
    </tr>
  </xsl:template>

  <xsl:function name="dtyf:format-person-name" as="xs:string*">
    <xsl:param name="person" as="element()"/>
    <xsl:value-of select="$person/last"/>
    <xsl:text>, </xsl:text>
    <xsl:value-of select="$person/first"/>
  </xsl:function>
</xsl:stylesheet>

上記の XSLT を実行した結果 (図 3) は、はるかに改善されており、今度は施設や団体が本の著者になっている場合も、複数の著者がいる場合も適切に表示されています。

図 3. シナリオ 1 の XSLT の最終バージョンを morebooks.xml に対して実行した結果
シナリオ 1 の XSLT の最終バージョンを morebooks.xml に対して実行した結果
シナリオ 1 の XSLT の最終バージョンを morebooks.xml に対して実行した結果

この例では、関数の戻り型はたいしたことをしていませんが、いずれにしても、何を返すようにしているのかを明確にすることが適切なプラクティスです。例えば、ある関数を実行した結果が演算で使用される場合に、その演算では渡される結果が特定の型であることを想定しているケースもあります。

シナリオ 2: 型を使用して演算子を適切に解釈させる

本の例を続けましょう。価格を計算する際に discount 要素を考慮する XSLT をリスト 6 に示します。

リスト 6. シナリオ 2 の XSLT の最初のバージョン
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:dtyf="http://www.datypic.com/functions"
 xpath-default-namespace="http://www.datypic.com/books/ns">

 <xsl:output method="html"/>

 <xsl:template match="books">
  <html>
   <body>
    <table border="1">
     <xsl:apply-templates/>
    </table>
   </body>
  </html>
 </xsl:template>

 <xsl:template match="book">
  <tr>
   <td><xsl:value-of select="title"/></td>
   <td><xsl:value-of select="author"/></td>
   <td><xsl:value-of select="dtyf:calculate-price(price, discount)"/></td>
  </tr>
 </xsl:template>

 <xsl:function name="dtyf:calculate-price">
  <xsl:param name="price"/>
  <xsl:param name="discount"/>
  <xsl:sequence select="$price - $discount"/>
 </xsl:function>

</xsl:stylesheet>

この XSLT も、books.xml に対して実行すると (丸め誤差を除けば) 適切な結果が得られますが、morebooks.xml に対して実行すると、値引きのない本を適切に処理することができません (図 4)。これは $discount の値が空シーケンスであるためです。空シーケンスに対してどのような数学演算を実行した場合も、空シーケンスが返されます。

図 4. シナリオ 2 の XSLT の最初のバージョンを morebooks.xml に対して実行した結果
シナリオ 2 の XSLT の最初のバージョンを morebooks.xml に対して実行した結果
シナリオ 2 の XSLT の最初のバージョンを morebooks.xml に対して実行した結果

この問題を修正するために、私は単純に $discount を 0 と比較することも検討しましたが、値引き額が価格未満であることを確認することで、この関数がより一層確実な動作をするようになると判断しました。表にはマイナスの価格を表示したくないので、リスト 7 のように dtyf:calculate-price 関数に $discount < $price という比較を含めるようにします。

リスト 7. シナリオ 2 の XSLT の dtyf:calculate-price 関数改訂後のバージョン
 <xsl:function name="dtyf:calculate-price">
  <xsl:param name="price"/>
  <xsl:param name="discount"/>
  <xsl:choose>
<xsl:when test="$discount > 0 and $discount &lt; $price">
    <xsl:sequence select="$price - $discount"/>
   </xsl:when>
   <xsl:otherwise>
    <xsl:sequence select="$price"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:function>

図 5 に、この dtyf:calculate-price 関数改訂後の XSLT を実行した結果を示します。値引きのない最初の本の価格も適切に表示されており、一見良さそうに見えます。しかしよく見てみると、(私は運良く気づきましたが) 最後の本の値引き額が価格から引かれていないことに気付きます。

図 5. シナリオ 2 の XSLT の改訂後のバージョンを morebooks.xml に対して実行した結果
シナリオ 2 の XSLT の改訂後のバージョンを morebooks.xml に対して実行した結果
シナリオ 2 の XSLT の改訂後のバージョンを morebooks.xml に対して実行した結果

この問題が起きる理由は、この XML が price と discount を文字列として比較しているためです。オペランドの型が指定されていない場合、比較演算子はデフォルトで文字列同士としての比較を行います。文字列 “5.95” は文字列 “29.95” よりも大きいため、比較によって true ではなく false が返されています。リスト 8 のようにスキーマに型を追加すると、この問題を修正することができます。

リスト 8. シナリオ 2 の XSLT の最終バージョン
<xsl:function name="dtyf:calculate-price" as="xs:decimal">
  <xsl:param name="price" as="xs:decimal"/>
  <xsl:param name="discount" as="xs:decimal?"/>
  <xsl:choose>
    <xsl:when test="$discount > 0 and $discount &lt; $price">
       <xsl:sequence select="$price - $discount"/>
    </xsl:when>
    <xsl:otherwise>
       <xsl:sequence select="$price"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>

ここでは両方のパラメーターを小数として指定しており、またオカレンス・インディケーターとして、「?」を使用して値引きに対して空シーケンスを許可しています。これは値引きがない場合もあり得るからです。改訂後の XSLT では、この dtyf:calculate-price 関数が呼び出されると、price 要素と discount 要素の値は小数に変換されます (この変換は入力データの型が指定されていない場合 (つまり入力にスキーマがない場合) に自動的に行われます)。これで、この XML は価格と値引き額を数字として比較するようになり、適切な結果が得られるようになります (図 6)。

図 6. シナリオ 2 の XSLT の最終バージョンを morebooks.xml に対して実行した結果
シナリオ 2 の XSLT の最終バージョンを morebooks.xml に対して実行した結果
シナリオ 2 の XSLT の最終バージョンを morebooks.xml に対して実行した結果

図 6 では丸め誤差もなくなっています。減算演算子は、浮動小数点数を表す xs:double 型の値として数値を処理するのがデフォルトであるため、改訂前の XSLT では xs:double 型の値として数値を処理していましたが、それよりも精度の高い xs:decimal 型の値として処理するように改訂したことで、丸め誤差はなくなりました。

スキーマを使用してスタイルシートを改善する

これまでの例では、XML Schema の組み込み型を使用していましたが、入力文書にスキーマは必要ありませんでした。今度は、XML スキーマを使用することによって XSLT がさらに確実なものになることを説明します。スキーマ認識機能は XSLT プロセッサーのオプション機能であり、このセクションで説明するスタイルシートはスキーマを認識する XSLT プロセッサーで実行する必要があります。この記事の例は Saxon-EE を使用してテストしてあります (Saxon-EE については「参考文献」のリンクを参照)。

スキーマをインポートして使用する

ここで仮に、books.xml 文書のスキーマがあり、そのスキーマの名前がbooks.xsd だとします (リスト 9)。books.xsd は、さまざまな要素のすべてのデータ型とカーディナリティーをすべて定義します。この記事では XML Schema についての完全な説明はしませんが、XML Schema の基本を紹介した資料として、「XML Schema Part 0: Primer」(「参考文献」を参照) を読むことをお勧めします。

リスト 9. books スキーマ
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://www.datypic.com/books/ns"
  xmlns="http://www.datypic.com/books/ns" elementFormDefault="qualified">
  <xs:element name="books" type="BooksType"/>

  <xs:complexType name="BooksType">
    <xs:sequence>
      <xs:element ref="book" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <xs:element name="book" type="BookType"/>
  <xs:complexType name="BookType">
    <xs:sequence>
      <xs:element ref="title"/>
      <xs:element ref="author" maxOccurs="unbounded"/>
      <xs:element ref="price"/>
      <xs:element ref="discount"  minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:element name="title" type="xs:string"/>
  <xs:element name="author" type="AuthorType"/>
  <xs:element name="price" type="xs:decimal"/>
  <xs:element name="discount" type="xs:decimal"/>
  <xs:complexType name="AuthorType">
    <xs:choice>
      <xs:element ref="person"/>
      <xs:element ref="institution"/>
    </xs:choice>
  </xs:complexType>

  <xs:element name="person" type="PersonType"/>
  <xs:element name="institution" type="xs:string"/>
  <xs:complexType name="PersonType">
    <xs:sequence>
      <xs:element ref="first"/>
      <xs:element ref="middle" minOccurs="0" maxOccurs="unbounded"/>
      <xs:element ref="last"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="first" type="xs:string"/>
  <xs:element name="middle" type="xs:string"/>
  <xs:element name="last" type="xs:string"/>

</xs:schema>

スタイルシートとともにスキーマを使用すると、さらに別のシーケンス型を使用することができます。その一覧を表 4 に示します。

表 4. スキーマを認識するシーケンス型
シーケンス型の例意味
element(*,BookType)型が BookType の任意の要素
element(book,BookType)名前が book で型が BookType の任意の要素
schema-element(book)スキーマ内の book 要素のグローバル宣言またはその置換グループのメンバーに対して妥当性検証された任意の要素
attribute(*,ISBNType)型が ISBNType の任意の属性
attribute(isbn,ISBNType)名前が isbn で型が ISBNType の任意の属性
schema-attribute(isbn)スキーマ内の isbn 属性のグローバル宣言またはその置換グループのメンバーに対して妥当性検証された任意の属性

シナリオ 3. 無効なパスを検出する

スキーマを使用することで利用できる非常に便利な機能の 1 つは、XPath 式の中で無効な名前やパスを使用した場合に、そのことを通知してくれる機能です。リスト 10 は、いくつかの些細な誤りを含む XSLT の例です。

リスト 10. シナリオ 3 の XSLT の最初のバージョン
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xpath-default-namespace="http://www.datypic.com/books/ns">

  <xsl:output method="html"/>
  <xsl:template match="books">
    <html>
      <body>
        <table border="1">
          <xsl:apply-templates/>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="book">
    <tr>
      <td><xsl:value-of select="title"/></td>
      <td><xsl:value-of select="author/last"/></td>
      <td><xsl:value-of select="author/first"/></td>
      <td><xsl:value-of select="prce"/></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

この XSLT を実行した結果の出力 (図 7) には著者名と価格がありません。スキーマがない場合には、何が問題なのかを試行錯誤によって判断しなければなりません。

図 7. シナリオ 3 の XSLT の最初のバージョンを books.xml に対して実行した結果
シナリオ 3 の XSLT の最初のバージョンを books.xml に対して実行した結果
シナリオ 3 の XSLT の最初のバージョンを books.xml に対して実行した結果

スキーマを認識するようにスタイルシートを変更すると、この問題を解決することができます。リスト 11 に示すように、まずスキーマをインポートする必要があります。その際にスキーマのファイル名 (絶対パスまたは相対パス) と、このスキーマのターゲット名前空間を指定する必要があります。また、必要なエラー・チェックを行うために、match 属性に使用するシーケンス型を、schema-element() を使用するように変更する必要があります

リスト 11. シナリオ 3 の XSLT の改訂後のバージョン
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xpath-default-namespace="http://www.datypic.com/books/ns">

  <xsl:output method="html"/>
  <xsl:import-schema namespace="http://www.datypic.com/books/ns"
                     schema-location="books.xsd"/>
  <xsl:template match="schema-element(books)">
    <html>
      <body>
        <table border="1">
          <xsl:apply-templates/>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="schema-element(book)">
    <tr>
      <td><xsl:value-of select="title"/></td>
      <td><xsl:value-of select="author/last"/></td>
      <td><xsl:value-of select="author/first"/></td>
      <td><xsl:value-of select="prce"/></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

すると、今度は以下の 3 つのエラー・メッセージが出力されます。

  • 「The complex type AuthorType does not allow a child element named last (複合型 AuthorType に last という名前の子要素は許可されません)」
  • 「The complex type AuthorType does not allow a child element named first (複合型 AuthorType に first という名前の子要素は許可されません)」
  • 「The complex type BookType does not allow a child element named prce (複合型 BookTypeprce という名前の子要素は許可されません)」

最初の 2 つのエラー・メッセージはパスが誤っていることを指摘しています。私は author の子要素の person 要素を忘れていました。3 番目のメッセージから、price の綴りが誤っていることがわかります。適切なパスを使用するように修正すると (リスト 12)、完璧な結果を得ることができます。

リスト 12. シナリオ 3 の XSLT の最終バージョン
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xpath-default-namespace="http://www.datypic.com/books/ns">

 <xsl:output method="html"/>
 <xsl:import-schema namespace="http://www.datypic.com/books/ns"
                    schema-location="books.xsd"/>
 <xsl:template match="books">
  <html>
   <body>
    <table border="1">
     <xsl:apply-templates/>
    </table>
   </body>
  </html>
 </xsl:template>

 <xsl:template match="schema-element(book)">
  <tr>
   <td><xsl:value-of select="title"/></td>
   <td><xsl:value-of select="author/person/last"/></td>
   <td><xsl:value-of select="author/person/first"/></td>
   <td><xsl:value-of select="price"/></td>
  </tr>
 </xsl:template>

</xsl:stylesheet>

シナリオ 4: 出力の妥当性検証を行う

場合によると、生成している出力の妥当性を確認したいことがあります。構造化データを変換する場合は特にそうですが、XHTML を生成している場合にも出力の妥当性を確認したい場合があります。XHTML を出力するために、単純にリスト 12 のコードを使用して、出力方法を xhtml に変更することもできます。この場合、ブラウザーに表示される結果は適切に見えるはずです。ただし、この出力を入力として受け取る対象が、妥当な入力を要求する他のスタイルシートやプロセスである場合や、標準に厳密に従うことを要求する人 (私のような人) がいる場合には、おそらくこの出力は妥当な XHTML にする必要があります。

出力を妥当性検証するためには、入力文書に対するスキーマの場合と同じように、まずスキーマをインポートする必要があります。また、ルート要素に xsl:validation="strict" 属性を追加することで、出力の妥当性検証を行う必要があることをプロセッサーに対して伝える必要があります。このようにすることで、html のすべての内容の妥当性検証が行われます。

もう 1 つの変更として、XHTML の名前空間をデフォルト名前空間にします。出力が妥当であるためには、適切な名前空間に出力しなければならないからです。これらの変更をリスト 13 に示します。

リスト 13. シナリオ 3 から改訂したシナリオ 4 の XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns="http://www.w3.org/1999/xhtml"
 xpath-default-namespace="http://www.datypic.com/books/ns">

 <xsl:output method="xhtml"/>
 <xsl:import-schema namespace="http://www.datypic.com/books/ns"
                    schema-location="books.xsd"/>
 <xsl:import-schema namespace="http://www.w3.org/1999/xhtml"
                    schema-location="xhtml1-strict.xsd"/>

 <xsl:template match="books">
  <html xsl:validation="strict">
   <body>
    <table border="1">
     <xsl:apply-templates/>
    </table>
   </body>
  </html>
 </xsl:template>

 <xsl:template match="schema-element(book)">
  <tr>
   <td><xsl:value-of select="title"/></td>
   <td><xsl:value-of select="author/person/last"/></td>
   <td><xsl:value-of select="author/person/first"/></td>
   <td><xsl:value-of select="price"/></td>
  </tr>
 </xsl:template>

</xsl:stylesheet>

このスタイルシートを実行すると、以下のエラー・メッセージが出力されます。

  • 「In content of element <html>: The content model does not allow element <body> to appear here. Expected: {http://www.w3.org/1999/xhtml}head (<html> 要素の内容に問題があります。このコンテンツ・モデルでは、<body> 要素を現在の場所に指定することはできません。この場所には {http://www.w3.org/1999/xhtml}head を指定する必要があります。)」

このエラー・メッセージは head 要素がないことを指摘しています。何回かスタイルシートを実行してエラーを修正するという作業を繰り返した後、厳密に妥当な XHTML を生成する最終的な XLST はリスト 14 のようになりました。

リスト 14. シナリオ 4 の XSLT の最終バージョン
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns="http://www.w3.org/1999/xhtml"
 xpath-default-namespace="http://www.datypic.com/books/ns">

 <xsl:output method="xhtml"/>
 <xsl:import-schema namespace="http://www.datypic.com/books/ns"
                    schema-location="books.xsd"/>
 <xsl:import-schema namespace="http://www.w3.org/1999/xhtml"
                    schema-location="xhtml1-strict.xsd"/>

 <xsl:template match="books">
  <html xsl:validation="strict">
<head><title>Books</title></head>
   <body>
    <table border="1">
     <xsl:apply-templates/>
    </table>
   </body>
  </html>
 </xsl:template>

 <xsl:template match="schema-element(book)">
  <tr>
   <td><xsl:value-of select="title"/></td>
   <td><xsl:value-of select="author/person/last"/></td>
   <td><xsl:value-of select="author/person/first"/></td>
   <td><xsl:value-of select="price"/></td>
  </tr>
 </xsl:template>

</xsl:stylesheet>

まとめ

この記事の例を見るとわかるように、XSLT スタイルシートによる変換は、暗黙的に失敗する場合や、不適切な結果を生成する場合があります。この記事で紹介した単純な例から、皆さんはそれらの問題を理解して容易に修正することができると思います。しかし入力文書や XSLT スタイルシートは、これらの例よりもはるかに複雑であることが普通です。明示的に型を指定して、スキーマをインポートしない限り、XSLT のデバッグは大変な作業になる可能性があります。

また、テスト・データ文書には必ずしも実際の入力データで遭遇するバリエーションがすべて含まれているわけではありません。スキーマ型を明示的に指定すると、テスト時に遭遇しなかったデータで発生しうる問題も排除することができます。また、パラメーターや戻り値の型を指定すると、関数やテンプレートが適切に文書化され、コードの保守が容易になり、他の人にとってもそのコードが理解しやすいものになります。

まだ信じられない方は、皆さんが作成した XSLT スタイルシートのいくつかに型とスキーマを追加してみてください。その結果を見て、皆さんは驚くかもしれません!


ダウンロード可能なリソース


関連トピック

  • XML Schema Part 0: Primer」: XML Schema の基本を学んでください。
  • developerWorks の XML ゾーン: DTD、スキーマ、XSLT など、XML の領域でのスキルを磨くためのリソースが豊富に用意されています。XML の技術文書一覧には、広範な話題を網羅した技術記事やヒント、チュートリアル、技術標準、IBM Redbooks が豊富に用意されています。
  • IBM の XML 認定技術者: XML および関連技術において IBM 認定技術者になる方法を調べてください。
  • developerWorks on Twitter: 今すぐ Twitter に参加して developerWorks のツイートをフォローしてください。
  • developerWorks podcasts: ソフトウェア開発者のための興味深いインタビューや議論を聞いてください。
  • Saxon: この記事の例をテストするために使用した、スキーマを認識する XSLT 2.0 プロセッサーをダウンロードしてください。
  • IBM 製品の評価版: IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2、Lotus、Rational、Tivoli、WebSphere などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Web development
ArticleID=820359
ArticleTitle=型とスキーマを使用して XSLT 2.0 スタイルシートを改善する
publish-date=06142012