本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。プロフィールで選択した情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

XSLT 1.0 から 2.0 へのアップグレード計画 第 7 回: XSLT 2.0 機能の抜粋、そしてそれぞれが対処する XSLT 1.0 の欠点

スタイルシートをアップグレードするさらなる理由

David Marston, Engineer, IBM Research
David Marstonは1998年の後半からXMLを扱う仕事をしてきました。25年以上に及ぶコンピューティング・ビジネスを通して、ソフトウェア開発のすべての側面にかかわってきました。彼はDartmouth College卒業で、ACMのメンバーです。IBM ResearchのNext-Generation Webチームに属しています。

概要: XSLT 2.0 は多くの新規機能を導入していますが、その一部は特に、XSLT 1.0 での欠点に対処することを目的としています。今回の記事は、XSLT2.0 の機能のなかでもとりわけ価値のある機能を抜粋して紹介した連載第 1 回「XSLT の改善内容」の延長として、データ編成、XPath 式構文の拡大、テンプレートとのパラメーターの受け渡し、そしてストリングの処理という領域での XSLT 2.0の機能拡張を紹介します。記載した例には、共通のアプリケーションを 1.0 での構文と、それに置き換わる一層単純で用途の広い 2.0 の構文で比較しています。シーケンスや照合など、XSLT2.0 で新しく登場したコンセプトについては、既存の XML 変換アプリケーションに適用可能な利用方法を紹介します。

日付:  2007年 7月 03日
レベル:  中級 この記事の原文:  英語
アクティビティー: 4189 ビュー
お気軽にご意見・ご感想をお寄せください: 


この連載について

W3C がリリースした最新の仕様、XSLT 2.0 は、XML 文書を変換するための言語です。この仕様には多くの新規機能が含まれ、その一部は特に、XSLT 1.0 での欠点に対処することを目的としています。この連載では、XSLT 1.0 の今までの問題を修正し、XSLT 2.0 の新しい技法を学んで何に注意すべきかを知りたいという XSLT 1.0 ユーザーの視点から、XSLT 2.0 の概要および詳細を説明します。XSLT 2.0 へのアップグレードを計画している方のために、共通アプリケーションと実用的提案にちなんだ例を記載します。また、XSLT 2.0 をスムーズに使い始めることができるよう、マイグレーション手法も紹介する予定です。


一層強化された XPath 式

Sidebars

今回の記事では以下の点を重視して、XSLT 2.0 の新規機能をさらに詳しく説明します。

  • XPath 式を入力データに適用する方法
  • データのソート方法
  • XSLT 命令の代わりに式をもっと活用する方法
  • スタイルシートで変数参照の数を減らす方法

ここではいくつかの新しい XSLT 命令についても説明しますが、XSLT 命令の拡張については連載第 1 回で詳しく説明しています (連載のこれまでの記事へのリンクは、「参考文献」を参照してください)。


XPath ノード・テストの機能拡張

XPath でノード・テストという言葉が指すのは、どのような形であれ、一連のノードをその種類 (要素、属性など) または名前で絞り込むことです。ノード・テストは、パス式のそれぞれの軸ステップで、軸と (任意のフィルタリングのために存在する可能性のある) 述語の間に入ります。

デフォルトの要素/型名前空間

XPath 1.0 の機能は、混乱を招くことがあります。例えば doc/chapter のようなパス式では、デフォルト名前空間のバインディングが XPath 式を含む要素のスコープ内にあったとしても、docchapter の名前テストは名前空間には含まれない要素ノードだけに一致します。これと対照的なのは (pub:doc のような) 接頭辞を持つ名前テストで、この場合はスコープに含まれる pub の名前空間宣言が関連します。

XSLT 2.0 と XPath 2.0 は、接頭辞のない名前テストのデフォルトの解釈を変更することはありません。ただし、XPath 2.0 ではデフォルトの要素/型名前空間の概念を導入しており、2.0 のスタイルシートでは [xsl:]xpath-default-namespace という標準属性を使ってデフォルトの要素/型名前空間を設定することができます (標準属性のスコーピング、および xsl: 接頭辞を使用する場合については、連載第 4 回を参照してください)。接頭辞のない名前テストが同じローカル名を持つ要素ノードと一致するのは、ノードの名前空間がスコープ内に現在含まれるデフォルトの要素/型名前空間と同じである場合、またはデフォルトの要素/型名前空間がなく、ノードが名前空間内にない場合です。したがって以下のフラグメントでは docchapter は、"http://example.org/pub" 名前空間内にある要素ノードと一致します。

<xsl:template match="doc" xpath-default-namespace="http://example.org/pub">
  <xsl:for-each select="chapter">
    <!-- and so on -->

ここで、接頭辞のない名前がスタイルシートのスコープに現在含まれるデフォルト名前空間を使用すると誤解していた場合、XSLT 2.0 では xpath-default-namespace 属性を設定すれば簡単に誤りを修正することができます。つまり、スタイルシートの名前テストすべてにそれぞれ接頭辞を追加する手間が省けるということです。XML 文書と XSLT スタイルシートのデフォルト名前空間を使用するようにスタイルを設定している場合、常にデフォルト名前空間宣言には、対応する xpath-default-namespace 属性を伴わせておけば、この新しい機能を生かすことができます。

種類テストの機能拡張

XPath 2.0 では、XPath 1.0 での場合よりもノード・テストの構文の一貫性が向上しており、ノード選択の際のスキーマ認識サポートを組み込むように拡張されています (連載第 3 回では、一部の XSLT 2.0 プロセッサーでサポートされる機能としてスキーマ認識の概要を説明しています。また、第 1 回には、スキーマ認識処理によって振る舞いが異なる XSLT 構成要素のリストも記載されています)。XPath 1.0の場合、comment()processing-instruction()、または text() といった種類テストを使用すれば、パス式でそれぞれコメント、処理命令、テキスト・ノードを選択することができます。また、node() 種類テストで任意のノードを選択することもできます。しかし一方で、任意の要素や任意の属性ノードを選択するには、ワイルドカード構文 (* または @*) を使わなければなりませんでした。

XPath 2.0 では、element()attribute()schema-element()schema-attribute()、および document-node() といった新しい種類テストを導入し、一致する可能性のあるあらゆるノードの種類に完全に対応するようになりました。element()attribute() の種類テストには、オプションで名前パラメーターと型パラメーターを指定することができます。パラメーターが指定されていない場合、element() は任意の要素を選択し、attribute() は任意の属性ノードを選択します。名前を指定すると、種類テストはその名前を持つノードだけを選択し、型を指定すると、指定された型と同じか、あるいはその型から派生した型を持つノードだけを選択します。例えば、element(ship-addr, loc:USPostalAddress) で選択されるのは、有効な loc:USPostalAddress 型 (この型そのもの、またはこれから派生した型) を持つ ship-addr という名前のノードです。

schema-element() および schema-attribute() 種類テストにはそれぞれ必須パラメーターが 1 つあります。前者の場合は、スタイルシートに使用可能なスコープ内スキーマ定義での要素の名前、後者の場合はこのスキーマ定義での属性宣言です。この 2 つの種類テストは要素または属性宣言の型またはその型から派生した型、そして宣言と同じ名前 (schema-element() の場合は、置換グループ内にある名前) を持つノードを選択します。

ルート・ノード (文書ノードの XPath 1.0 名) にナビゲートするために作成しなければならない曖昧なテストは、XPath 1.0 が持つ構文上の不規則性をさらに浮き彫りにします。例えば XPath 1.0 では、コンテキスト・ノードの親が文書ノードである場合にのみその親を選択するには、parent::node()[not(self::*)] のようなコードを作成しなければなりません。このコードでは、述語によって、要素ではないノードがフィルタリングされて除去されます。他のノードの親となれるのは文書ノードと要素だけなので、フィルターは文書ノードのみを残すという仕組みです。

パラメーターが 1 つもない document-node() テストでは文書ノードが選択され、element 種類テストまたは schema-element() 種類テストのいずれかをオプション・パラメーターとすることができます。このような形の document-node() 種類テストは、指定された element() テストまたはschema-element() テストの結果と一致する単一の要素の子を持つ文書ノードと一致することになります。


文字コードと照合の導入

非常によくある例として、スタイルシートには、スタイルシートが処理するストリング・データを人間が検討しやすい順番 (最も可能性として高いのは、いわゆるアルファベット順) で記載しなければならない場合があります。ストリング・データを比較して順番に配置するプロセスは、照合として知られています。構文解析済みの XML では、ストリング・データは Unicode® 文字で構成されます。すべての文字には、それぞれの文字に関連付けられた一意の整数値、つまりコード・ポイントがあります。Unicode コード・ポイントについての詳細は、「参考文献」を参照してください (「A brief introduction to code pages and Unicode」)。興味のある読者には、ストリング比較で明らかにできる問題の例を掘り下げて紹介している記事もあります (「Efficient text searching in Java™: Finding the right string in any language」)。

照合の基礎としては、ストリングに含まれる個々の文字のコード・ポイントを使用することができます。この方法は、スタイルシートで英語のデータだけを照合する場合にはかなり有効に機能しますが、グローバルな時代には複数の異なる言語および国/地域別情報に応じてデータを照合することができるスタイルシートが必要となる場合が考えられます。スタイルシートがたった 1 つの言語のルールだけに従ってストリング・データを処理すればいいだけの場合でも、大文字と小文字の違いを考慮するかどうか、あるいは発音区別符を有意にするかどうかなど、ストリングの比較方法をさまざまな状況に応じてきめ細かく制御しなければならないことがあります。

XSLT 1.0 では、この後の「ソートでの照合」で説明するように、xsl:sort 命令での場合は別として、ストリングは常に個々の文字が持つコード・ポイントを基準に比較されていました。XSLT 2.0 では照合 URI を導入したことにより、スタイルシートのストリング比較方法を細かく制御することができます。照合 URI とは実装が定義する URI のことで、特定のストリング比較操作に適用する言語および国/地域別情報を指定するために使用することができます。現在、xsl:sort および xsl:keycollation 属性内で starts-with などのストリング比較関数の引数として、あるいは [xsl:]default-collation 属性でのデフォルトとして、照合 URI を指定できるようになっています。後者の属性はスタイルシートのどこにでも出現する標準属性の 1 つです。標準属性のスコープ、そして値の変更による影響についての詳細は、連載第 4 回を参照してください。比較については、後で詳しく説明します。

XPath 2.0 が定義している唯一の照合 URI は、Unicode コード・ポイント照合を対象としたもので、ストリングの比較は、この照合 URI に従って、ストリングを構成する文字ごとの Unicode コード・ポイントを基準に行われます。XSLT 2.0 では、Unicode コード・ポイントをデフォルト照合 URI として使用します。XPath 2.0 の他のホスト言語では、別のデフォルト照合 URI を使用する場合があります。

追加の照合 URI を標準化するために推奨されているフレームワークについては、RFC 4790 に詳細が記載されています (「参考文献」にリンクを記載)。標準照合 URI のレパートリーが増え、広範にサポートされるようになるまでは、該当するベンダーの資料でプロセッサーがサポートする追加の照合 URI の詳細を調べてください。スタイルシートに必要な照合 URI を保持するには、グローバル変数を使用します。これによって簡単に、ベンダー固有の照合 URI のセットを他のセットや標準照合 URI のセットへとグローバルに変更できるようになります。


データ編成の機能拡張

XSLT で可能なことの中でも注目すべきことは、データを再構成できることです。第 1 回では、今まで度々求められていた新規機能として、グループ化について説明しました。今回の記事では、XSLT 1.0 ですでに有効になっていた再構成機能の拡張である、ソート関数と集約関数を取り上げます。XSLT 2.0 では、この 2 つの関数をシーケンスに適用できるようになっています。シーケンスはかつての概念を新たに捕え直したもので、ノード・セットの概念をアトミック (プリミティブな非ノード) 値にまで広げています。

値のシーケンス

入力ツリーを処理して中間結果を計算し (ノードごとに 1 つの値)、それからさらに中間結果を処理するスタイルシートを作成しなければならないことがあります。その一例として、販売取引の合計値とともに、個々の取引の合計のパーセンテージも算出したいとします。XSLT 1.0 を使用する場合は、個々の取引の値を計算してその値を実行中の集計に追加し、最終的には合計がテキスト・ノードして含まれる結果ツリー・フラグメント (RTF: result-tree fragment) を返す再帰テンプレートを作成するという方法があります。こうすると、スタイルシートを各取引に繰り返し適用し、取引それぞれの値を再計算して合計のパーセンテージとして表現できます。しかしこの方法を使う場合は、合計したいさまざまな入力データごとに別々の再帰テンプレートを作成しなければなりません。また、取引の値の計算がとりわけ複雑な場合は、スタイルシートの実行速度が落ちる可能性もあります。これは、スタイルシートが各取引の合計値を 2 度計算しなければならないためです。それよりも適切なのは、各取引の値を計算するのは 1 度だけにして、その中間結果の値を使って合計値を計算し、それから再び中間結果の値を使って合計値のパーセンテージを計算することです。

XSLT 1.0 で任意の数の中間結果を保存するために使用できるデータ型は、ストリングと RTF だけです。RTF を使う場合、フラグメントに保存された個々の値にアクセスするには node-set 拡張関数を使わなければなりません。もう一方のストリングは使い勝手が悪く、値ごとにストリング内の特定の文字数を確保したり、あるいは区切り文字を使ってそれぞれの値を区切る必要が生じます。例えば、フィールドごとに使用する文字数が 10 であるとすると、作成するコードはリスト 1 のようになります。


リスト 1. 一連の値をパーセンテージとしてフォーマットする今までの厄介な方法
                
<!-- Place all transaction values in a single string value -->
<xsl:variable name="transaction_values">
  <xsl:for-each select="transaction">
    <xsl:variable name="value" select="@unit_price * @quantity"/>
    <xsl:value-of select="substring(concat($value,'          '),1,10)"/>
  </xsl:for-each>
</xsl:variable>

<!-- Calculate the total value of the transactions -->
<xsl:variable name="total">
  <xsl:call-template name="calculate_total">
    <xsl:with-param name="valueList" select="$transaction_values" />
  </xsl:call-template>
</xsl:variable>

<!-- Format each value as a percentage -->
<table>
 <tr><th>Transaction ID</th><th>Value</th><th>Percentage of total</th></tr>
 <xsl:for-each select="transaction">
   <xsl:variable name="value" select="substring($transaction_values,10*position()-9,10)"/>
   <!-- Format the output here with ($value div $total)*100 -->
 </xsl:for-each>
</table>

XPath 2.0 では、シーケンスの概念を導入しています。シーケンスとは、ゼロ個以上の値 (ノード、整数、ストリングなど) の順序付きリストです。事実、すべての XPath 2.0 の式と XSLT 2.0 の命令はシーケンスに対して評価されるため、単一のノードや単一のアトミック値は、実際には 1 つの項目が含まれるシーケンスとなります。XPath 2.0 にはノード・セットはなく、ノードのシーケンスがあるだけです。xsl:for-eachxsl:copy などの一部の XSLT 命令、そして今まではノード・セットまたは単一のノードでしか使われていなかった count()sum()などの一部の XPath 関数は、現在、任意のシーケンスで使うことができるようになっています。シーケンス内の var 変数に複数の値が含まれる可能性がある場合、XPath 1.0 では $var[1] を使ってノード・セット変数の最初のノードを選択しますが、XPath 2.0 ではそれとまったく同じように$var[1] を使って最初の項目を選択することができます。

リスト 1 で取り上げたサンプル・シナリオを再度検討してみると、XSLT 2.0 で、まず初めにすべての取引値を単一の値のシーケンスにまとめれば、元のリストで合計を計算していた再帰テンプレートは必要なくなります。このサンプル・シナリオの XSLT 2.0 バージョンは、リスト 2 のとおりです。


リスト 2. 一連の値をパーセンテージとしてフォーマットする XSLT 2.0 の方法
                
<!-- Place all transaction values in a single sequence -->
<xsl:variable name="transaction_values" as="item()*">
  <xsl:for-each select="transaction">
    <xsl:sequence select="@unit_price * @quantity"/>
  </xsl:for-each>
</xsl:variable>

<!-- Calculate the total value of the transactions -->
<xsl:variable name="total" select="sum($transaction_values)"/>

<!-- Format each value as a percentage -->
<table>
  <tr><th>Transaction ID</th><th>Value</th><th>Percentage of total</th></tr>
  <xsl:for-each select="$transaction_values">
    <!-- Format the output here with (. div $total)*100 -->
  </xsl:for-each>
</table>

上記の例には、注目すべき点がいくつかあります。

  • xsl:for-each 命令と sum 関数がシーケンスに使えるようになりました。
  • ドット (.) がコンテキスト項目式として認識されるようになったため、処理中のシーケンスでの現行項目によって、ドットはノードあるいはアトミック値になります。
  • xsl:sequence 命令の実行結果は、その select 式の評価結果 (上記の場合は、単一の数値) のシーケンスとなります。一方、xsl:for-each の結果は、各繰り返し処理による個々のシーケンスを連結した結果のシーケンスとなります (別の例については、連載第 5 回の最初の例を参照してください)。
  • 最初の xsl:variable 要素に含まれる as 属性は、XSLT プロセッサーに対し、変数の値を RTF に変換するのでなく、任意の項目のシーケンスのままにするように命令します (ツリー構造の変数が必要な場合は、連載第 1 回で RTF の後継である暗黙的文書ノードについての詳細を説明しているので参照してください)。

XSLT 2.0 バージョンのスタイルシートは、この記事の後で説明する新規 for 演算子を使って、さらに短くすることができます。

シーケンスの操作には、F&O (Functions and Operators) 仕様 (「参考文献」にリンクを記載) で説明しているいくつかの新規関数を使用できます。これらの新規関数には、位置を基準にメンバーを挿入または削除する関数、特定の値が発生する位置を検索する関数、ストリングのサブストリングと同様のサブシーケンスを取得する関数などがあります。また、使用する頻度が高くなりそうな関数としては、distinct-values() もあります。一連の異なるキーを取得すればいいだけの場合には、グループ化の設定をするよりも、この関数を使ったほうが処理が高速となり簡単で読みやすく、エラーを発生させることなく個別のデータ型をそのまま維持することができます (distinct-values() の使用例は、連載第 5 回の「choosing between functions」の例を見てください)。さらに、それぞれブール値を返す empty 関数と exists 関数を使うと、シーケンスにメンバーがあるかどうかを簡単に調べられます。

後方互換性モードが有効になっていると、アトミック値が必要な場合にシーケンスの最初のメンバーが取得されることがあります。詳細は、連載第 6 回を参照してください。

ソートの機能拡張

今まではノード・セットをソートしてから、ソート順にノード間の相互関係を考慮に入れた計算を実行したいと思っても、XSLT 1.0 ではソートされたノードをそのまま使えるのは xsl:for-each または xsl:apply-templates だけだったため、途方に暮れた経験があるはずです。例えば、売上高上位 10 人の従業員の合計売上高を割り出すには、リスト 3 のようなコードを作成しなければなりませんでした。このコードはまず、売上高を基準に従業員をソートして第 10 位に位置する従業員の売上高の値を検出します。次にその売上高の値を超える売上高を持つ従業員と、その売上高とまったく同じ売上高を持つ従業員を選択し、10 位の従業員が複数いる場合にはその従業員の集合を削除します。


リスト 3. 上位 10 人の売上高を合計する今までの困難な方法
                
<xsl:variable name="number_ten_text">
  <xsl:for-each select="sales_force/employee">
    <xsl:sort data-type="number" select="number(@total_sales)" order="descending"/>
    <xsl:if test="position()=10">
      <xsl:value-of select="@total_sales"/>
    </xsl:if>
  </xsl:for-each>
</xsl:variable>
<xsl:variable name="number_ten" select="number($number_ten_text)"/>
<xsl:variable name="above_tenth"
              select="sales_force/employee/@total_sales[. &gt; $number_ten]"/>
<xsl:variable name="tied_for_tenth"
              select="sales_force/employee/@total_sales[. = $number_ten]"/>

<xsl:text>Total sales for top ten = </xsl:text>
<xsl:value-of select="sum($above_tenth |
                          $tied_for_tenth[position()+count($above_tenth) &lt;= 10])"/>

元のノード・セットをソートして、ソートされたノードを変数に格納し、ソート済みセットから最初の 10 のメンバーを選択するという方法を考えるかもしれませんが、XSLT 1.0 ではこの方法を使えません。XSLT 1.0 ではすべてのノード・セットが文書の順またはその逆順で処理されるからです。また、ノードをソートされた順に結果ツリー・フラグメントにコピーすると、スタイルシートがノード・セット拡張関数に依存しなければならなくなってしまいます。XSLT 2.0では、ノードのシーケンスを処理する順序が文書の順に制限されることはありません。ノードはコンテキスト・ノードからの軸に従って文書の順で返されることに変わりありませんが、これらのノードが返された後は、スタイルシートで必要に応じてノードを再配列できます。XSLT 2.0 の xsl:perform-sort 命令は、上記のタスクを大幅に単純化します (以下に記載する最後の要素に含まれる le 演算子は、値比較演算子の一例です)。


リスト 4. 上位 10 人の売上高を合計する 2.0 の手法
                
<xsl:variable name="sorted_employees" as="item()*">
  <xsl:perform-sort select="sales_force/employee">
    <xsl:sort select="number(@total_sales)" order="descending"/>
  </xsl:perform-sort>
</xsl:variable>
<xsl:text>Total sales for top ten = </xsl:text>
<xsl:value-of select="sum($sorted_employees[position() le 10]/@total_sales)"/>

経験したことがあるかと思いますが、XSLT 1.0 では xsl:sort 要素に data-type 属性がないと、わざわざソート・キーを数値に変換したとしても、デフォルトでソート・キーの値がストリングとして処理されてしまうという使いにくさがあります (リスト 3xsl:sortnumber()@data-type の両方があるのは、そのためです)。例えばデフォルトでは、11 が 22 の前と 3 の前にソートされることになります。XSLT 2.0では、data-type 属性がない場合、ソートの際にソート・キーの実際の型が考慮されるため、スタイルシートが整数型のソート・キーを選択すると、ソート・プロセスではソート・キーを整数として比較します。これによって一般的なエラーの原因は排除されますが、スタイルシートが XSLT 1.0 プロセッサーと XSLT 2.0 プロセッサーの両方と連動する必要がある場合には、この点を念頭に置いておいてください。

ソートでの照合

XSLT 1.0 では常に、ストリングに含まれる各 Unicode 文字のコード・ポイントを基準にストリングを比較していましたが、1 つだけ例外があります。その例外とは、xsl:sort 命令です。この命令では、lang 属性がプロセッサーに、指定された言語の国/地域別情報に従ってストリングをソートするように指示します。デフォルトではプロセッサーがシステム環境に基づいて言語を選択するようになっていますが、このデフォルトは度々混乱の種となっていました。例えば、あるプロセッサーでは「resume」と「résumé」というストリングをソートの際に同等のものとして処理する一方、他のプロセッサーでは別のものとして処理するなど、使用するデフォルトの振る舞いはプロセッサーによってさまざまだからです。lang 属性があったとしても、状況によって適切なルールは異なります。例えば、発音区別符号は一部の用途では重要な意味がないとしても、lang 属性にはそれを示す手段がありません。

XSLT 2.0 では xsl:sort 命令に、ソートする際のストリングの比較方法を制御するための新しい属性、collation を導入しています。照合 URI についての詳細は、上記の「文字コードと照合の導入」を参照してください。XSLT 2.0 でのデフォルト照合 URI は Unicode コード・ポイント照合です。そのため、collation 属性と lang 属性がどちらも指定されていない xsl:sort 命令のデフォルトの振る舞いは、すべての XSLT 2.0 プロセッサーで一貫することになります。

範囲式

XSLT 1.0 で再帰テンプレートを使用する際に、繰り返し処理を一定の回数実行できる方法があればいいと思った経験があるかもしれません。テンプレートが繰り返さなければならない処理回数に入力文書との関係がない場合、XSLT 1.0 で例えば 1 回から 10 回、単純に繰り返し処理するのは要領が悪い話です。「ヒント: ノード・セットによるカウント」(「参考文献」を参照) を読むとわかるように、そのような繰り返し処理を実行する再帰を使わないようにするため、あらゆる努力を惜しまない人々もいます。このような一定回数の繰り返しを行うタスクを単純化するのが、XPath 2.0 の範囲式です。例えば、(1 to 10) という式は 1 から 10 の整数値が含まれるシーケンスを作成します。そのため、<xsl:for-each select="1 to 10"> という式を作成すると、1 から 10 の値を順に取るコンテキスト項目式によって実行回数が 10 回のループを作成できます。

数値集約関数

この記事に記載する一部の例は、要素や属性にはアトミック数値が含まれるのが一般的であることを指摘しています。例えば、値をソートまたは比較するだけでなく、一連の数値を単一の値に集約したい場合があります。その場合、XPath 1.0 では sum 関数と count関数を使用できました。いずれも単一のノード・セットを引数として使用する関数で、この 2 つを同時に使用すると平均を計算することができます。しかし、この他に一連の数値で実行できる一般的な操作には、再帰テンプレートまたは拡張関数によって最小値と最大値を検出するという 2 つの操作しかありません。

現在、集約関数のセットは sumcountavg()min()、そして max() まで拡大されています。いずれの関数も一連のノードだけでなく、シーケンスの値が操作のタイプに互換する限り、あらゆるシーケンスを使用することが可能です。count() 関数は任意のシーケンスを使用できます。sum() および avg() 関数には加算できる値が必要で、この値は任意の数値型 xs:yearMonthDuration または xs:dayTimeDuration にすることができます。min() 関数と max()関数に必要なのは、lt (より小さい。これは新しい種類の比較演算子です) の定義対象となる値で、ふさわしい型はいくつかあります。値がストリングの場合は、照合を使用するように指定することができます。


新規および拡張されたストリング関数

XSLT および XPath 2.0 は、1.0 のスタイルシートで悩まされていた作成者が熱望していた多くのストリング操作機能を提供しています。新規関数のなかには、upper-case 関数と lower-case 関数のように実に便利で、読みやすさを改善するものがあります。例えば 1.0 では translate() を使用して文字を指定した大/小文字にマッピングすることができますが、この方法はエラーを引き起こしやすい方法で、特化した関数ほどは効率的ではありませんでした。また、F&O 仕様 (「参考文献」にリンクを記載) では starts-with 関数と contains 関数を補完するends-with を追加しています。

ストリングのシーケンスからストリングを集約することもできます。concat() は多くの引数を取り、その引数のそれぞれがストリングでしたが、新しい string-join 関数はシーケンスを引数として使います。これと逆のアクションを実行するのが新規 tokenize 関数で、区切り文字で境界にマークを付けてストリングを分解します。この関数が使用する正規表現の機能は、その他の新規関数と XSLT 命令で使用することができます。ストリングをシーケンスに分けると、 insert-before() などの新しいシーケンス操作関数を使用して変更することも可能になります。

連載第 6 回では、式における型の不一致について説明し、計算された値が確実に正しい型になるようにするためにキャスト関数を使用することを推奨しました。F&O には fn:string() (1.0 の string() の後継) だけでなく xs:string() もあります。この 2 つの関数が異なるのは、引数シーケンスの長さが 1 以外の場合だけです。引数が空のシーケンスの場合、fn:string(seq) は長さゼロのストリングを返しますが、もう一方の関数は空のシーケンスを返します。いずれの関数も引数が複数のメンバーを持っていると通常となりますが、後方互換性モードが有効であれば、fn:string(seq) ではエラーになりません (後方互換性モードについての詳細は、連載第 3 回から第 6 回を参照してください)。ただし、fn:booleanxs:boolean については同じことが言えません。この 2 つは、ストリングの処理方法が異なるためです。xs:boolean() はすべての数値をキャストできますが、ストリングについては truefalse"1"、そして "0" しかキャストできません。xs:untypedAtomic の型付き入力文書からの値はこの 4 つの値に制限され、その他の値は型エラーとなります。1.0 の boolean 関数は fn:boolean() となりました。これはゼロ長以外のストリングをすべて true に変換するため、1.0 の振る舞いと互換します。一方、xs:boolean("false") はブール値として false を返す点に注意してください。また、xxs:boolean("0")xs:boolean(0) と同じく false を返します (このパラグラフ以外では、標準関数の名前に F&O 仕様に記載されている fn: 接頭辞は付けません。レガシー・スタイルシートを 2.0 プロセッサーで動作させるには、標準関数名の接頭辞は不要ですが、キャスト関数には接頭辞が必要です。boolean() を使用するレガシー・スタイルシートは、2.0 では互換性のある fn:boolean() を呼び出します)。

スタイルシートは国/地域別情報に従ってストリングを比較できることから、containsstarts-withsubstring-before、および substring-after といったストリング操作関数にはすべて、新規関数 compareends-withdeep-equalsdistinct-values と同様にオプションの照合 URI 引数があります。照合 URI を適切に選択することで、starts-with('encyclopædia volume 3', 'encyclopedia', $collation-uri) の結果を true と false のどちらにするかを制御できます。照合 URI を省略すると、デフォルトの照合が適用されます。デフォルト照合も、一般的な比較および値比較の演算子によるストリングの比較方法を左右します。そのためデフォルトを変更するときには、compare 関数と Unicode コード・ポイントの照合を併用したり、あるいは新規 codepoint-equal 関数を使って、再びコード・ポイントごとの等価比較を行わなければならない場合があることに注意してください。

XPath 2.0 の string-to-codepoints および codepoints-to-string という 2 つの新規関数は、ストリングを構成する文字を操作する手段となります。string-to-codepoints 関数は入力ストリングの引数を整数のシーケンスに変換します。変換されたそれぞれの整数が、すなわち入力ストリング内の対応する文字が持つコード・ポイント値です。codepoints-to-string はこのプロセスを逆に行います。変換の結果が XML にはならない場合 (例えば、Java コードや別のプログラミング言語のコードになる場合)、このような操作はエスケープ文字に特殊な規則を適用するのに役立つはずです。ASCII グラフィックの範囲外にある文字をコード・ポイント 63 の疑問符に置き換えるには、以下のコードを作成します。

codepoints-to-string(for $cp in string-to-codepoints($string) return 
        if ($cp lt 32 or $cp gt 126) then 63 else $cp)

上記の例には、新しい値比較演算子 (詳細は次のセクションを参照)、そして IfExpr 内でネストされた IfExpr もあります。

normalize-unicode などのその他の新規関数、および URI のエンコードとエスケープの実行方法に対する明示的制御を行う関数については、連載第 1 回を参照してください。

値比較と一般比較

codepoints-to-string の例では新しい XPath 値比較演算子のうちの 2 つ、lt (より小さい) とgt (より大きい) も使用しています。この他の新しい値比較演算子には、eq (等しい)、le (より小さいか等しい)、ne (等しくない)、および ge (より大きいか等しい) があります。これ以前の演算子 (<><=>==!=) は一般比較演算子として知られています。値比較はアトミック値同士の関係に関するもので、集合全体をチェックすることはしません。値の集合をチェックする場合には一般比較が用いられますが、この記事の他の場所で説明する、より明示的な some 演算子についても留意してください。

一般比較演算子のオペランドに想定されるノードは最大でも 1 つだけなのに、スタイルシートのバグが原因で、オペランドのうちのいずれかが複数のノードを持つという状況に遭遇したことがあるかと思います。新しい値比較演算子は、比較のオペランドのいずれかに複数の値があるとエラーを報告するため、これらの値比較演算子を使うとエラーを素早く検出することができます。

また、他に考えられる問題として、XSLT 1.0 の<, >, <=および >= といった不等演算子で比較している値が常に数値に変換されてしまうため、ストリングの比較を実行できないという状況を経験したことがあるかもしれません。これは厄介なデバッグを行わないと突き止められない問題です。あらゆるストリングは、NaNに変換するだけの場合でも、正常に数値に変換されてしまうからです。新しい値比較演算子はストリングに対して辞書のような比較を行い、自動的にストリング値を数値に変換することがないため、エラーの共通の原因を無くすとともに便利な新しい機能を提供してくれます。値比較演算子は、時系列型などの順序を持つデータ型に対する不等比較にも使えます。

テキスト・ファイルの読み取り

ご存知のとおり、XSLT への変換入力は XML 文書であることが必須であるため、XSLT では document 関数によって追加 XML 文書を読み取ることができます。XSLT 2.0 は、文書のファイルやその他の形式をストリングとして読み取ることができる unparsed-text という新規関数を追加しています。この関数によって読み取ったストリングは、文書全体を含む構造化されていない 1 つのストリングですが、新規のストリング関数をすべて使用できるので、必要に応じて追加処理を行うことができます。そのうち、この機能の独創的な使用方法が出現してくるはずです。非常に可能性の高い使用方法のひとつは、煩わしいほど多数のパラメーターをスタイルシートに渡すための代替策です。以下のコードを使用すると、ストリングを複数のテキスト行に分割することができます。

<xsl:variable name="xList" as="xs:string*" 
        select="tokenize(unparsed-text('listFile'),'&#10;')"/>

このようにすると、それぞれがファイルの各行を表すストリングのシーケンスを設定できます。lsfind などのよく使われる UNIX® または Linux™ のシェル・コマンドのいずれかでファイルを生成することも可能です。ファイルのそれぞれの行が XML ファイルの名前である場合は、これらの名前を一度にすべて document 関数に渡し、XML として読み取らせることもできます (documentunparsed-text はどちらも、"fn:" F&O セットではなく XSLT 関数セットの一部なので、XQuery および XForms では使用できないことに注意してください)。ファイルの文字エンコード方式は、自分で指定することも、XSLT 仕様に従って記述されたヒント・リストを使用してプロセッサーに推測させることもできます。


新規 XPath 演算子

XPath 2.0 仕様では、IfExpr、ForExpr、QuantExpr という 3 つの新しい構文 (この仕様で作成されたもの) について言及しています。それぞれの構文は多数のキーワードと副次式を指定された順序で結合します。前に説明した範囲式も新しいタイプの式ですが、より単純になっています。

if 演算子

入力データから値を計算したい場合はよくありますが、この場合、関数のようなものが望みだとしても、独自の数式を記述するのに最適な方法は if-then-else 表現を使うことです。XSLT 1.0 では通常、プログラムによる分岐を使わなければならず、おそらく xsl:choose を使用することになります。この場合、中間の値を計算し、この値を中心とした内部式と外部式を作成しなければなりません。ときには、リスト 5 でOliver Becker が示している 1.0 の手法のように、意外な方法で関数を使用することもできます。このコードでは、サブジェクト行の前に「Re:」があるかどうかによって、すべてのメッセージを 1 つのスレッドにまとめています。


リスト 5. 接頭辞がある場合に接頭辞を破棄する今までの困難な方法
                
<xsl:sort select="concat(
        substring(subj,1,number(not(starts-with(subj,'Re: ')))*string-length(subj)),
        substring(substring-after(subj,'Re: '),1,
          number(starts-with(subj,'Re: '))*string-length(substring-after(subj,'Re: '))))"
        data-type="text" />

リスト 5 が依存しているのは、一部の極端な場合での substring() に対する特定の振る舞い、そしてブール値から数値への変換です。その結果、数式は理解しにくいものになりますが、少なくとも 1.0 の xsl:sort が条件とするように数式は 1 つとなります (記載した例は、1.0 の場合でもさらに単純化することが可能です)。


2.0 では、xsl:sort で完全なシーケンス・コンストラクターを使えるため、xsl:choose を使うこともできますが、xsl:choose を使う代わりに if演算子を単一の式で使用することができます。こうすると、リスト 6 のように遥かに読みやすくなります。


リスト 6. 接頭辞が存在する場合に接頭辞を破棄する IfExpr
                
<xsl:sort select="if (not(starts-with(subj,'Re: ')))
        then subj
        else substring-after(subj,'Re: ')"
        data-type="text" />

プログラマチックな xsl:if とは異なり、xsl:ifは式のなかに含まれているため、値を常に存在させるには else 節が必要です。

for 演算子

XPath 1.0 には、ノード・セットに含まれるノードの数値を合計するのに便利な手段として sum() 関数が用意されていました。ただし、ノードのそれぞれで式を計算し (例えば、ノードが持つ 2 つの属性の積)、それらの値を合計するには、さらに別の再帰的な指定テンプレートを使用するはめになりました。あるいは、結果ツリー・フラグメントを作成し、そこに元のノード・セット内のノードごとに 1 つの要素を含める (この要素のストリング値は、要素が属しているノードに数式を適用した値) という方法も考えられますが、この方法では node-set 拡張関数に依存して値を合計しなければならないことを意味します。

XPath 2.0 では、シーケンスfor 演算子によってタスクが大幅に単純化されます。この新しい演算子を使用すれば、1 つ以上のシーケンスに含まれる値のそれぞれに変数をバインドし、入力シーケンスのそれぞれの値の組み合わせについて新規の値を計算することができます。例えば、異なる為替レートを適用して、さまざまな通貨で行われる多数の取引の合計値を米ドル単位で計算するには、XSLT 2.0 スタイルシートで以下の式を使用することができます。

 sum(for $trans in transaction return $trans/@exchange-rate * $trans/@value)

量化式

XPath 1.0 では、ノード・セットが関わる比較は存在に関する量化と言われます。これは、ノード・セット内のあるノードについての比較が true であれば (つまり、比較の結果を true にするノードが存在すれば)、セット全体が関わる比較も true であることを表す飾った表現です。このような比較に求める意味はその通りかもしれませんが、エラーの原因となることもよくあります。

例えば、個人データが含まれる XML 文書で、ある従業員の姓が「Smith」であるかどうかを判断したいとしましょう。そのために employee/@surname='Smith' というコードを作成しました。その後、条件を変更して、この姓を持つ従業員が一人もいないことを確かめることにするとします。その場合、employee/@surname!='Smith' というコードを作成して条件を逆にしようと思うかもしれませんが、このコードは実際には「Smith 以外の姓を持つ従業員はいるかどうか」と尋ねていることになります。該当する姓を持つ従業員がいないことを確かめるには、実際にはnot(employee/@surname='Smith') というコードを作成しなければなりません。

XPath 2.0 の量化表現では、このような比較を実行する際の内容を明示的に記述します。some 式は、式の評価結果が何らかの値または値のセットに対して true である場合に true となり、every 式はすべての値または値のセットに対して true である場合に true となります。構文は for 式の構文と同様ですが、結果は常に単一のブール値です。ForExpr の場合と同じく、QuantExpr は 1 つ以上の変数をバインドすることができます。

「Smith」という姓の従業員がいるかどうかをテストには、以下のコードを作成します。

some $i in employee satisfies $i/@surname eq 'Smith'

この姓を持つ従業員が一人もいないことをテストするには、以下のコードを作成します。

every $i in employee satisfies $i/@surname ne 'Smith'

上記のそれぞれにある eqne は、値比較演算子です。


パラメーターの受け渡し

XSLT 2.0 では、前のバージョンからの 3 つを含め、4 つの命令がテンプレートを呼び出し、この 4 つすべてでパラメーターの受け渡しが可能です。新しく加わった命令は、xsl:next-match です。この命令は xsl:apply-imports と似ていますが、下位のインポート優先順位だけでなく同じ優先順位のマッチング・テンプレートもトリガーできるという点が異なります。文書ノードと要素に関する組み込みルールもテンプレートを有効にすることがあり、その場合にはパラメーターが伝播されます。組み込みルールはパラメーターを使用しませんが、今では次のテンプレートにパラメーターを渡すようになっています。

XSLT 1.0 ではパラメーターの受け渡しを繰り返すことがよくありますが、この場合、パラメーターは下位にネストされたテンプレートにしか渡されません。 2.0 ではパラメーターをトンネル・パラメーターとして指定すると、パラメーターが使用される場所のみでパラメーターを宣言できます。トンネル・パラメーターは実際にはグローバル・プールにより近いものですが、トンネル・パラメーターの場合はテンプレート呼び出しスタックの中で値を変更することができます。パラメーターをトンネルに組み込むには、xsl:with-param 命令に tunnel="yes" を追加するだけです。トンネルからはパラメーターを 1 回以上読み取ることができますが、パラメーターが有効になるのは、そのパラメーターを最初にトンネルに入力した命令の処理中に呼び出されたテンプレート内のみです。つまり、トンネル・パラメーターは、テンプレート呼び出しスタックの複数のレベルから累積することができます。

単純なルールとして、パラメーターを受け取るテンプレートは、ローカルで使用するパラメーターを個別に宣言し、そしてもちろんすべてのパラメーター名は固有でなければなりません。テンプレート内の変数 ($foo など) への各参照については、参照を解決する方法が必要となります。XSLT 2.0 では、考えられる方法がいくつかあります。以下のリストで新規とマークしているものは XSLT 2.0 で新しく登場した方法で、残りは XSLT 1.0 から使用できた方法です。

  • 新規: ForExpr または QuantExpr 内に含まれ、該当する式にローカルな変数セット
  • xsl:variable により、テンプレート内に以前から存在するローカル変数セット
  • 従来のxsl:param命令でテンプレートが受け取ったパラメーター
  • 新規: xsl:param tunnel="yes" 命令によってテンプレート内でアクティブにされたトンネル・パラメーター
  • スタイルシートで一度定義するだけでどこででも利用可能なグローバル変数またはパラメーター

テンプレートで使用されないことからトンネル・パラメーターを宣言しないのであれば、その名前と該当テンプレート内の変数参照とがぶつかる心配をする必要はありません。リストの 3 番目と 4 番目の項目は、同じ名前を持つトンネル・パラメーター (有効にされた場合) と従来のパラメーターとの違いを示しています。

トンネル・パラメーターは何度も読み取ることができます。そのため、テンプレートが、トンネル・パラメーターを使用する他のテンプレートを呼び出す場合には、xsl:with-param tunnel="yes" を使用しなくても同じパラメーターを続けて渡すことができます。ただし、下位のテンプレートに対して値を変更する必要がある場合は値を変更することができます。

シーケンス・コンストラクターが含まれるグローバル・レベルの xsl:variable はテンプレートのようなものですが、xsl:variablexsl:param を子として取りません。つまり、変数がトンネル・パラメーターや従来のパラメーターを受け取ることはないということです。スタイルシート・パラメーターはトンネル・パラメーターとしては指定できませんが、グローバルに使用することはできます。トンネル・パラメーターは xsl:function 内では使用でず、トンネル・パラメーターは、テンプレート間での値の受け渡しのためだけに使用されます。

テンプレートは、それが受け取るパラメーターの特定の側面を制御できなければなりません。データ型の認識が強化されている 2.0 では、受け取るパラメーターを特定の型にキャストしないと、パラメーターがテンプレートで役に立たない場合があります。例えば、テンプレートは、受け取ったパラメーターと既知の型を持つ値との値比較を実行する場合があります。データ型を指定するには、新しい as 属性を xsl:with-param やその他の命令で使用できます (簡単な例については、連載第 1 回の 2 番目のリストを参照してください)。興味深いことに、asxsl:with-param でも使用できます。このような使用方法はシーケンス・コンストラクターが含まれている場合には役立ちますが、暗黙的文書ノードは望ましいものではありません (@as の使用方法についての詳細は、この記事の「テキスト・ファイルの読み取り」の例とリスト 2第 1 回の「暗黙的文書ノード」の例、および第 5 回の「Stylesheet Function」の例を参照してください)。

xsl:param 要素ではパラメーターをオプションまたは必須として指定できますが、この要素が xsl:functionの子である場合は例外です。この場合、パラメーターは常に必須となります。required="yes" 属性が存在している場合は、select 属性またはシーケンス・コンストラクターを使ってデフォルト値を設定することはできません。



表 1. 必須パラメーターとオプション・パラメーター
xsl:param 要素の場所@required の設定 (デフォルトは no)@tunnel の設定 (デフォルトは no)パラメーター値が指定された場合 (またはテンプレート呼び出し時にトンネルで検出された場合) のアクションパラメーター値が使用できない場合のアクション注記
xsl:stylesheet no (使用不可)標準デフォルトを計算これは、スタイルシート・パラメーターに対する 1.0 のシナリオです。
yes 標準エラー 
一致または名前によって呼び出された xsl:template no no 標準デフォルトを計算起動時に呼び出された指定テンプレートに対してパラメーターを設定することは不可能です。
yes トンネルから取得デフォルトを計算。ただし、ローカルでのみ使用 
yes no 標準エラー 
yes トンネルから取得エラー 
一致によって呼び出された xsl:template (その名前のパラメーターは宣言されない)NANA無視未決定 
名前によって呼び出された xsl:template (その名前のパラメーターは宣言されない)NANA後方互換性モードが有効になっていない限りエラー。有効になっている場合は無視未決定互換性モードについての詳細は、連載第 6 回を参照。
xsl:function (常に必須)(不要)(使用不可)標準エラーパラメーター名は xsl:function 内でのみ使用され、受け取った値は引数リストのそれぞれの位置に応じて割り当てられます。
組み込みテンプレート・ルールNANA自動的に呼び出されたテンプレートに転送未決定慣例による、明らかに xsl:param の子および変数参照はありません。

表 1 で"NA"(適用不可) とある箇所は、required 属性または tunnel 属性を含める xsl:param 命令がない場合にはこの 2 つの属性を設定できないことを意味します。xsl:function についての詳細は連載第 1 回を、起動時オプションについての詳細は第 3 回を参照してください。


XSLT 2.0 のその他の機能

この記事で取り上げなかった XSLT 2.0 のその他の機能に関心をお持ちの方は、XSLT 2.0 仕様の Appendix J.2 を読むと大体の概念がわかるはずです (「参考文献」にリンクを記載)。また、developerWorks で XSLT および XPath に関するその他の記事も調べてください。


参考文献

学ぶために

製品や技術を入手するために

議論するために

著者について

David Marstonは1998年の後半からXMLを扱う仕事をしてきました。25年以上に及ぶコンピューティング・ビジネスを通して、ソフトウェア開発のすべての側面にかかわってきました。彼はDartmouth College卒業で、ACMのメンバーです。IBM ResearchのNext-Generation Webチームに属しています。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=245660
ArticleTitle=XSLT 1.0 から 2.0 へのアップグレード計画 第 7 回: XSLT 2.0 機能の抜粋、そしてそれぞれが対処する XSLT 1.0 の欠点
publish-date=07032007
author1-email=David_Marston@us.ibm.com
author1-email-cc=