XPath の道を外れないために

成功を後押しする、XSLT で XPath を使用するための 5 つのヒント

この記事で説明する 5 つのヒントを適用して XPath を使ってみてください。この記事では、2 値論理に関する意外な事実を明らかにし、XPath の position() 関数の値がコンテキストによってどのように変わるのかを説明します。さらに、XPath を使用して特定の名前を持つ最初の要素を選択する方法、そして最もよく起こりがちで、しかも難しい不具合をデバッグする方法を伝授します。

Doug Domeny, Senior Software Engineer, Freelance

Doug Domeny は、ブラウザー・ベースで多言語対応、ビジネス・ユーザー向けの XML エディターを、XSLT、W3C XML Schema、DHTML、JavaScript、jQuery、正規表現、CSS を使って開発した経験があります。彼はマサチューセッツ州 Wenham の Gordon College でコンピューター・サイエンスおよび数学の学位を取得しており、XLIFF (XML Localization Interchange File Format) や OAXAL (Open Architecture for XML Authoring and Localization) など、OASIS の技術委員会で長年委員を務めてきました。彼はソフトウェア・エンジニアとして業務を行う中で、ソフトウェア・エンジニアリングやアーキテクチャー、UI 設計、テクニカルライティングで高いスキルを身に付けています。



2011年 6月 24日

この記事では XPath を使用するための 5 つのヒントとして、実際のアプリケーションで XPath のややこしい振る舞いや予想外の振る舞いについて深く調べるために何時間も費やした経験から得られたヒントを紹介します。以下にその 5 つのヒントを記載します。

  • 偽が真となることもあります。
  • (x != y)not(x = y) は同じ XPath 式ではありません。それぞれに異なった結果になります。
  • XPath の position() 関数の値はコンテキストに応じて変わります。
  • XPath を使用して特定の名前を持つ最初の要素を選択する。
  • デフォルトの名前空間によって失敗する select 属性の XPath 式をデバッグする。

以上のヒントによって、何時間もイライラさせられることがなくなるはずです。

よく使われる頭文字語

  • API: Application Programming Interface
  • W3C: World Wide Web Consortium
  • XHTML: Extensible HyperText Markup Language
  • XML: Extensible Markup Language
  • XSLT: Extensible Stylesheet Language Transformations

ヒント 1: 偽が真となることもあります

長さが 1 以上の文字列に Boolean 関数を適用すると、その文字列の値が 'false' であったとしても、常に真として評価されます。

XPath 式では、文字列と Boolean 関数を比較すると予想外の結果になることがあります。問題を避けるためには、文字列を組み込み Boolean 関数の true()false() とは比較せずに、文字列値 'true'' false' と比較してください。Boolean 関数 true() および false() を不用意に文字列に変換されないようにして Boolean 関数として維持するのは困難です。文字列が空でなければ、その値が 'false' だとしても真と見なされます。したがって、boolean('false') = true()boolean('true') = true() となってしまいます。空の文字列 ('') のみが偽 (つまり、boolean('') = false()) となります。

文字列に変換された Boolean 関数を元に戻す場合の予想外の結果を考えてみてください。まず、Boolean 関数が文字列に変換されます (例えば、string(false()) = 'false')。これを boolean(string(false())) のように元に戻すと、結果は true() となります。

その一方、string() 関数だけが Boolean 関数を文字列に変換する手段ではありません。結果ツリー・フラグメント (RTF: Result Tree Fragment) が避けられない場合がありますが、これは、文字列です。例として、リスト 1 に複数の資料からなる XML 文書を記載します。それぞれの文書には、文書名、公開された年、そしてページ数の情報があります。

リスト 1. XML ソース文書
<Documents>
  <Document id="18396">
    <Name>Knowing the Good</Name>
    <Year>2010</Year>
    <Pages>35</Pages>
  </Document>
  <Document id="18397">
    <Name>Beyond Standards: Best Practices</Name>
    <Year>2011</Year>
    <Pages>12</Pages>
  </Document>
  <Document id="18398">
    <Name>101 Ways to Do the Same Thing</Name>
    <Year>2011</Year>
    <Pages>50</Pages>
  </Document>
</Documents>

リスト 2 は、ページ数が 20 ページ以上の最近の資料を検索する XSLT テンプレートです。残念ながら、このテンプレートは正しい結果を出しません。

リスト 2. 誤りのある XSLT テンプレート
<xsl:template match="Document">
  <xsl:variable name="recentFullLengthPublications">
    <xsl:choose>
      <xsl:when test="Year=2011 and Pages &gt;= 20">
        <xsl:value-of select="true()"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="false()"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:variable>
  <xsl:if test="$recentFullLengthPublications">
    <xsl:copy-of select="Name"/>
  </xsl:if>
</xsl:template>

このテンプレートを変換するとリスト 3 の XML が返されますが、これは誤った結果です。本来ならば、返される要素は 1 つだけで、それは「101 Ways to Do the Same Thing」でなければなりません。けれども、この結果では 3 つすべての要素が返されています。

リスト 3. 誤った結果
<Name>Knowing the Good</Name>
<Name>Beyond Standards: Best Practices</Name>
<Name>101 Ways to Do the Same Thing</Name>

結果ツリー・フラグメント (XSLT 1.0)

XSLT で表現される変換は、ソース・ツリーを結果ツリーに変換するためのルールを記述します。結果ツリーの構成部分は、テンプレートが特定のソース要素に対してインスタンス化されることによって作成されます。テンプレートがインスタンス化されるときには、それぞれの命令が実行され、その命令によって作成された RTF (Result Tree Fragment: 結果ツリー・フラグメント) で置き換えられます。変数が RTF にバインドされる場合もあります。RTF は、単一のルート・ノードが含まれるノード・セットと同じように扱われますが、RTF で操作が許可されるのは、その操作が文字列で許可される場合のみです (「XSL Transformations (XSLT) Version 1.0」から引用。リンクについては「参考文献」を参照)。

2011年に公開された 20 ページ以上の資料は、「101 Ways to Do the Same Thing」という名前の資料だけです。では、何が間違っていたのでしょうか。変数の内容は RTF (Result Tree Fragment: 結果ツリー・フラグメント) を生成するため、このフラグメントはその値が Boolean 関数であったとしても、文字列として評価されます。

このことから、リスト 2 では <xsl:if test="$recentFullLengthPublications"> の条件式が誤っています。この条件式は常に真になることから、ソース文書のすべての <Name> 要素が返されるというわけです。<xsl:if test="$recentFullLengthPublications=true()"> という条件式も正しくありません。$recentFullLengthPublications 変数は 'true' または 'false' のいずれかの文字列になり、Boolean 型のデータにはならないからです。したがって、<xsl:if test="$recentFullLengthPublications='true'"> という文にすれば上手くいきます。

けれども、true() および false() 関数を使用していることによって、このテンプレートが紛らわしいものになっていることには変わりありません。この例で、これらの関数を使用すると誤解を招きます。リスト 4 に記載するテンプレートは、正しくてしかも読みやすくなっています。このテンプレートは 'true''false' の代わりに 'yes''no' を使用して、Boolean 型との関連付けを避けています。'yes''no' は必ず、単一引用符で囲んでください。

リスト 4. 正しい XSLT テンプレート
<xsl:template match="Document">
  <xsl:variable name="recentFullLengthPublications">
    <xsl:choose>
      <xsl:when test="Year=2011 and Pages &gt;= 20">
        <xsl:value-of select="'yes'"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="'no'"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:variable>
  <xsl:if test="$recentFullLengthPublications='yes'">
    <xsl:copy-of select="Name"/>
  </xsl:if>
</xsl:template>

Boolean 関数 true() および false() には注意しなければなりません。この単純な例では、変数は必要ありません。変数を使用するとしても、select 属性を使って定義できるはずです。select 属性を使用すれば、結果が自動的に文字列に変換されることはないため、Boolean 値は Boolean 値のまま維持されます。select 属性は RTF を返しません。

select 属性の場合、条件が複雑になりがちです、また、変数を RTF として定義しなければなりません。リスト 5リスト 6 のテンプレートは、どちらもこの単純な例に好ましいテンプレートです。リスト 5 の条件式は文字列と比較する必要がありません。select 属性を使用した結果として変数が返すのは、RTF ではなく Boolean 型であるためです。

リスト 5. 改善された単純な XSLT テンプレート
<xsl:template match="Document">
  <xsl:variable name="recentFullLengthPublications" 
        select="Year=2011 and Pages &gt;= 20" />
  <xsl:if test="$recentFullLengthPublications">
    <xsl:copy-of select="Name"/>
  </xsl:if>
</xsl:template>

あるいは、この条件はかなり単純なので、リスト 6 のように変数を使わずに、変数を自己文書化するという選択肢もあります。

リスト 6. もう 1 つの単純な XSLT テンプレート
<xsl:template match="Document">
  <xsl:if test="Year=2011 and Pages &gt;= 20">
    <xsl:copy-of select="Name"/>
  </xsl:if>
</xsl:template>

ヒント 2: (x != y) と not(x = y) の違いを知ってください

(x != y)not(x = y) は同じ XPath 式ではありません。

以下は、簡単な要約です。

  • (x != y) は、x と y を両方とも持つノードを選択します。x と y の値は異なることが条件です。この式は、ノードに x と y の両方がなければならないため、not(x = y) よりも排他的です。
  • not(x = y) は、x または y のどちらかがないノード、あるいは x と y の両方がある一方、2 つの値が異なるノードを選択します。この式のほうが包含的です。

ほとんどのコンピューター言語では x != y と not(x = y) は同等の式ですが、XML では、一方のノードが欠落しているという理由から値を未定義にすることができます。この場合、XML ノードの値を比較するとなると、疑問が持ち上がってきます。まず、「欠落した値をどうやって評価するのか?」という疑問です。例えば単純な同等比較である x = y では、x または y のいずれかが欠けていると、結果は偽となります。もう 1 つは、「どうやって、この式を否定の意味にするのか?」という疑問です。以下に記載する例は、これらの疑問に答えてくれるものです。

リスト 7 に、複数の資料に関する情報で構成された XML 文書を記載します。このデータに対してクエリーを実行すると、特定の年に公開された資料や、ページ数が一定の範囲内にある資料を検索することができます。けれども最後の資料、「Prospective Projects」には、公開された年の情報も、ページ数の情報もありません。この資料は未完成であり、まだ公開されていないためです。

リスト 7. XML ソース文書
<Documents>
  <Document id="18396">
    <Name>Knowing the Good</Name>
    <Year>2010</Year>
    <Pages>35</Pages>
  </Document>
  <Document id="18397">
    <Name>Beyond Standards: Best Practices</Name>
    <Year>2011</Year>
    <Pages>12</Pages>
  </Document>
  <Document id="18398">
    <Name>101 Ways to Do the Same Thing</Name>
    <Year>2011</Year>
    <Pages>50</Pages>
  </Document>
  <Document id="18399">
    <Name>Prospective Projects</Name>
  </Document>
</Documents>

XPath 式 //Document[Year = 2011]/Name は、2011年に公開された資料を選択します。リスト 8 に記載する結果には、2 つの資料が示されています。

リスト 8. 2011年に公開された資料
<Name>Beyond Standards: Best Practices</Name>
<Name>101 Ways to Do the Same Thing</Name>

次に、2011年に公開されたのではない資料を選択する方法を考えてみましょう。XPath 式を否定の意味にするには、not()!= の 2 つの選択肢があります。XPath 式を /Document[not(Year = 2011)]/Name とすると、2011年の資料セットに含まれないすべての資料が選択されます。リスト 9 の結果には、前の結果とは別の 2 つの資料が示されています。

リスト 9. 2011年に公開されたのではない資料
<Name>Knowing the Good</Name>
<Name>Prospective Projects</Name>

2011年以外の年に公開されたすべての資料を選択するには、//Document[Year != 2011]/Name という式を使用します。この式の結果はリスト 10 のとおりです。ここには、1 つの資料しか示されていません。

リスト 10. 2011年以外の年に公開された資料
<Name>Knowing the Good</Name>

このように、not(Year = 2011)Year != 2011 という 2 つの XPath 式の式は、わずかに異なります。一方、20 ページ以上の資料をすべて取得する場合には、XPath 式 //Document[Pages >= 20]/Name を使用します。リスト 11 の結果には、この条件と一致する両方の資料が示されています。

リスト 11. 長編の資料
<Name>Knowing the Good</Name>
<Name>101 Ways to Do the Same Thing</Name>

今度は逆に、20 ページ未満の資料を取得するとします。これに該当する資料を選択するための式は、/Document[Pages < 20]/Name です。この式で一致した資料は、リスト 12 のとおり、1 つしかありません。

リスト 12. 短編の資料
<Name>Beyond Standards: Best Practices</Name>

最後に、「Prospective Projects」のページ数は不明であることに注目してください。このような資料を結果に含めるには、例えば /Document[not(Pages >= 20)]/Name のように、!= ではなく、not() を使用します。この結果はリスト 13 です。ここには、2 つの資料が示されています。

リスト 13. 20 ページ未満またはページ数が不明の資料
<Name>Beyond Standards: Best Practices</Name>
<Name>Prospective Projects</Name>

ページ数が不明の資料のリストを取得するには、XPath 式 //Document[not(Pages)]/Name を使用します。リスト 14 に、この条件と一致する資料が示されています。

リスト 14. ページ数が不明の資料
<Name>Prospective Projects</Name>

属性または要素が欠けているノードを処理するには、not() 関数が必要不可欠です。XPath の使用方法に関するその他のヒントについては、「参考文献」に記載されているリンクを参照してください。


ヒント 3: position() の値は、この関数がどこで使われているかによって変わります

position() 関数の値はそのコンテキスト、つまり、どこで使用されているかによって変わります。この依存性は、特に述部で使用する場合に顕著に現れてきます。

position() 関数は、ノード・セット内でのノードの位置を 1 から始まるインデックスで返します。ノード数 (要素とテキスト・ノードの両方) をカウントする position() 関数は、テキストがホワイト・スペースしかないとしても、ノードとしてカウントします。この後いくつかの例で、異なるコンテキストで position() を使用した場合を説明します。

リスト 15 は、著者情報を含む複数の資料で構成された XML 文書です。ここでは、<Name> 要素に焦点を当てます。

リスト 15. XML ソース文書
<Documents>
  <Document id="18396">
    <Name>Knowing the Good</Name>
    <Authors>
      <Name>Bob Bloggs</Name>
      <Name>Kim Bergess</Name>
    </Authors>
  </Document>
  <Document id="18397">
    <Name>Beyond Standards: Best Practices</Name>
    <Authors>
      <Name>Bill Agnew</Name>
    </Authors>
  </Document>
  <Document id="18398">
    <Name>101 Ways to Do the Same Thing</Name>
    <Authors>
      <Name>Jay Nerks</Name>
      <Name>Paula Towne</Name>
      <Name>Carol Tinney</Name>
    </Authors>
  </Document>
</Documents>

リスト 16 の XSLT テンプレートは、各 <Name> 要素の位置を表示します。その結果は、リスト 17 のようになります。

リスト 16. position() を使用した XSLT テンプレート
<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

最初の列に示されているのが、position() 関数の値です。この XSLT テンプレートによる変換の結果は、リスト 17 のようになります。

リスト 17. リスト 16 の結果
2 Knowing the Good
2 Bob Bloggs
4 Kim Bergess
2 Beyond Standards: Best Practices
2 Bill Agnew
2 101 Ways to Do the Same Thing
2 Jay Nerks
4 Paula Towne
6 Carol Tinney

この結果に表示されている <Name> 要素の位置は、テンプレートのコンテキスト内での位置です。ほとんどの名前要素は、その親要素のなかでの最初の要素ですが、それにも関わらず 2 番目のノードとして示される理由は、タグの間に (改行を含む) 空白のテキスト・ノードがあるためです。さらに、この位置は文書全体での相対位置ではなく、その親要素に対する相対位置となっています。

リスト 18 の 2 つのテンプレートは、<Name> 要素をテキスト・ノードから切り離します。

リスト 18. 2 つの XSLT テンプレート
<xsl:template match="Documents">
  <xsl:apply-templates select="//Name"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

ホワイト・スペースのテキスト・ノードが無視されると、リスト 19 の結果となります。position() 関数の値は、最初の列に示されています。

リスト 19. リスト 18 の結果
1 Knowing the Good
2 Bob Bloggs
3 Kim Bergess
4 Beyond Standards: Best Practices
5 Bill Agnew
6 101 Ways to Do the Same Thing
7 Jay Nerks
8 Paula Towne
9 Carol Tinney

最初のテンプレートがすべての <Name> 要素を選択して処理します。このテンプレートによって 9 つの <Name> 要素がまとめて 1 つのセットとして選択されることから、position() はそのセット内での要素のインデックスとなります。

次の例では、position() 関数を xsl:apply-templates 文の述部で使用します。この文が選択するのは、位置の値が 1 の名前です (リスト 20 を参照)。

リスト 20. 述部で position() を使用したテンプレート
<xsl:template match="Documents">
  <xsl:apply-templates select="//Name[position()=1]"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

結果はリスト 21 のとおりです。ここでは 6 つの項目しか返されていないことに注目してください。最初の列に示されているのは、親要素のなかで最初の要素となっている Name 要素の position() 関数の値です。

リスト 21. リスト 20 の結果
1 Knowing the Good
2 Bob Bloggs
3 Beyond Standards: Best Practices
4 Bill Agnew
5 101 Ways to Do the Same Thing
6 Jay Nerks

著者のなかでこの結果に含まれているのは、最初の著者だけです。position() を述部で使用すると (つまり、角括弧 ([]) のなかで使用すると)、インデックスの基準は <Name> 要素の親となります。そのため、<Authors> 要素に含まれる最初の <Name> 要素しか返されません。要するに、Name[position()=1]Name[1] と表現することができます。

リスト 22 のテンプレートでは、xsl:apply-templates 文ではなく、テンプレートの match 式のなかに position() 関数を配置します。3 番目のテンプレートで、位置 1 にはない Name 要素を破棄します。

リスト 22. position() を使用した match 述部
<xsl:template match="Documents">
  <xsl:apply-templates select="//Name"/>
</xsl:template>

<xsl:template match="Name[position()=1]">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

<xsl:template match="Name" />

リスト 23 に記載する結果は、前と同じ 6 つの項目ですが、位置の値は同じではありません。

リスト 23. リスト 22 の結果
1 Knowing the Good
2 Bob Bloggs
4 Beyond Standards: Best Practices
5 Bill Agnew
6 101 Ways to Do the Same Thing
7 Jay Nerks

リスト 23 に示されている名前はリスト 21 と変わりませんが、位置の値は異なっています。この場合、9 つすべての名前がテンプレートに適用され、そのうちの 6 つだけが一致したということですが、リスト 20 では、そもそも 6 つの名前しかテンプレートに適用されていません。要するに、Name[position()=1]Name[1] と表現することができます。

リスト 24 に、XML 文書の最初の <Name> 要素だけを処理するテンプレートを記載します。

リスト 24. 要素をフィルタリングしてから適用する場合
<xsl:template match="Documents">
  <xsl:apply-templates select="(//Name)[1]"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

リスト 25 には、1 つの結果しか示されていません。

リスト 25. リスト 24 の結果
1 Knowing the Good

select 属性で使われている括弧 (()) は、正しい振る舞いには欠かせません。述部 [1] は、[position()=1] と同じです。

最初のテンプレートではなく、2 番目のテンプレートで位置をチェックすると、判定をする場所がテンプレートの呼び出し側からテンプレート自体に移されます (リスト 26 を参照)。

リスト 26. 要素を適用してからフィルタリングする場合
<xsl:template match="Documents">
  <xsl:apply-templates select="//Name"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:if test="position()=1">
    <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
  </xsl:if>
</xsl:template>

位置のチェックがテンプレートのなかに移されました。位置のチェックは match 式の述部から外されて、xsl:if 文の一部となっています。述部がテンプレートの match 式に含まれている場合 (つまり、<xsl:template match="Name[1]">)リスト 23 と同じ結果になります。リスト 26 の結果をリスト 27 に記載します。

リスト 27. リスト 26 の結果
1 Joe Bloggs

リスト 28 に、リスト 17<Name> 要素の位置に影響を及ぼしたホワイト・スペースのテキスト・ノードを無視するためのテンプレートを記載します。

リスト 28. テキスト・ノードを無視するためのテンプレート
<xsl:template match="*">
  <xsl:apply-templates select="*"/>
</xsl:template>

<xsl:template match="Name">
  <xsl:value-of select="concat(position(),' ',.,'&#10;')"/>
</xsl:template>

テキスト・ノードが無視されると、リスト 29 の結果となります。

リスト 29. リスト 28 の結果
1 Knowing the Good
1 Bob Bloggs
2 Kim Bergess
1 Beyond Standards: Best Practices
1 Bill Agnew
1 101 Ways to Do the Same Thing
1 Jay Nerks
2 Paula Towne
3 Carol Tinney

以上のように、position() 関数が返す結果は、この関数がどこで使われているのか、さらに使われている場所がテンプレートの match 式の中なのか、それとも述部の中、あるいは xsl:apply-templates で選択されたノード・セットの中なのかによって異なります。


ヒント 4: 特定の名前を持つ最初の要素を選択する方法

XML 文書で Name という名前を持つ最初の要素を選択するには、2 つの方法があります。1 つは //Name[not(preceding::Name)] を使用する方法で、これにはテンプレートとの突き合わせが必要になります。もう 1 つの方法として、select 属性に含めるとしたら、(//Name)[1] を使用することもできます。要素のセットから最初の要素を選択するのに最も簡単な方法は、値 1 の述部を使用することです。例えば、名前、公開された年、そしてページ数を情報として含む資料で構成されたリスト 30 の XML 文書があるとします。

リスト 30. XML ソース文書
<Documents>
  <Document id="18396">
    <Name>Knowing the Good</Name>
    <Year>2010</Year>
    <Pages>35</Pages>
  </Document>
  <Document id="18397">
    <Name>Beyond Standards: Best Practices</Name>
    <Year>2011</Year>
    <Pages>12</Pages>
  </Document>
  <Document id="18398">
    <Name>101 Ways to Do the Same Thing</Name>
    <Year>2011</Year>
    <Pages>50</Pages>
  </Document>
</Documents>

XPath 式 //Document[1] (あるいは、/Documents/Document[1]) は、最初の資料 (<Document>) 要素を選択します。結果をリスト 31 に記載します。

リスト 31. 最初の資料 (<Document>) 要素
<Document id="18396">
  <Name>Knowing the Good</Name>
  <Year>2010</Year>
  <Pages>35</Pages>
</Document>

この XPath 式は <Document> 要素のリストを選択してから、最初の <Document> 要素を返します。述部の [1] は、「位置が 1 の要素」を意味します。これと同じことを表現するもう 1 つの方法は、//Document[position()=1] です。

今度は、最初の資料の名前を選択する場合を考えてみてください。<Name> 要素は、<Document> 要素の子ノードです。子を選択するには、そのタグ名を XPath に追加し、//Document[1]/Name とします。これで、最初の <Document> 要素の <Name> 要素が返されます。結果をリスト 32 に記載します。

リスト 32. 最初の資料の名前 (<Name>) 要素
<Name>Knowing the Good</Name>

XPath 式 //Name[1] は、XML 文書内での位置に関係なく、最初の <Name> 要素を選択します。しかしこれには問題があり、リスト 33 に示すような予想外の結果となります。この結果では、1 つの要素だけでなく、3 つの要素が返されています。

リスト 33. 予想外の結果
<Name>Knowing the Good</Name>
<Name>Beyond Standards: Best Practices</Name>
<Name>101 Ways to Do the Same Thing</Name>

XPath 1.0 の軸

XPath の軸は、XML 文書をナビゲートする方向を定義します。文書内の要素は、ノードのツリーという形をとります。ツリー内の特定の要素からのナビゲート方向には、主に、親要素のチェーンを上へと向かう方向、子ノードの下へと向かう方向、文書内の次の兄弟ノードに進む方向、前の兄弟ノードに戻る方向の 4 つがあります。以下に挙げるような軸が、文書内のノードのサブセットを作成します。

  • ancestor: 親、親の親など
  • ancestor-or-self: コンテキスト・ノードとその祖先
  • attribute: 要素の属性
  • child: 直下の子
  • descendant: 子、子の子など
  • descendant-or-self: コンテキスト・ノードとその子孫
  • following: 文書の順において、コンテキスト・ノードの後に現れるすべてのノード
  • following-sibling: コンテキスト・ノードの後に現れるすべての兄弟
  • namespace: コンテキスト要素の名前空間ノード
  • parent: コンテキスト・ノードの親
  • preceding: 文書の順において、コンテキスト・ノードの前に現れるすべてのノード
  • preceding-sibling: コンテキスト・ノードの前に現れるすべての兄弟
  • self: コンテキスト・ノード自体

1 つの要素が返されることが予想されるのに対し、3 つの要素が返されています。それは、各 <Document> 要素の最初の <Name> 要素が選択されるためです。<Document> 要素に複数の <Name> 要素が含まれているとしたら、最初の要素だけが返されることになります。

XML 文書で最初の <Name> 要素を選択する方法は、XPath をどのように使うかによって異なります。XSLTテンプレートの match 属性で使用できるオプションは、select 属性よりも限られています。match 属性の XPath 式では、XPath の preceding 軸を使用して最初の要素を選択します。リスト 34 に、この式を記載します。

リスト 34. preceding 軸を使用したテンプレートの突き合わせ
<xsl:template match="//Name[not(preceding::Name)]">

リスト 35 には予想通りの結果、つまり最初の要素が示されています。

リスト 35. リスト 34 のテンプレートの結果
<Name>Knowing the Good</Name>

この式は、「XML 文書のなかで、前に <Name> 要素が 1 つもない <Name> 要素を選択すること」を意味します。

xsl:apply-templates 文のような select 式の場合には、XPath はより柔軟になります。リスト 34 に記載した式でも期待通りの結果を得られますが、慎重に括弧を適用すれば、作業がより簡潔になり、2 番目、3 番目、あるいは他のどのインデックスを選択する場合にも簡単に適応することができます。リスト 36 を見てください。

リスト 36. preceding 軸を使用したテンプレートの突き合わせ
<xsl:apply-templates select="(//Name)[1]"/>

リスト 37 に示す結果は、リスト 35 と同じです。

リスト 37. リスト 36 のテンプレートの結果
<Name>Knowing the Good</Name>

この式はすべての <Name> 要素を選択してから、最初の要素を返します。

目的の要素を選択する場合には、括弧および軸を慎重に配置することが重要です。


ヒント 5: デフォルト名前空間によって文書の突き合わせに失敗する select 属性の XPath 式に対処する方法

XSLT のデフォルト名前空間は、XPath 式には適用されません。この問題を解決するには、以下の 3 つの方法が考えられます。

  • XML 文書のデフォルト名前空間を削除する
  • 名前空間接頭辞を XPath 式に追加する
  • 前処理によって名前空間を削除する

方法 1: XML 文書のデフォルト名前空間を削除する

XML 文書を変更しても構わない場合には、デフォルト名前空間を削除してください。リスト 38 は、デフォルト名前空間が設定された XHTML 文書 (本質的には、XML 文書) です。

リスト 38. デフォルト名前空間が設定された XHTML 文書
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>This is the title</title>
  </head>
  <body>
    <p>Hello world.</p>
  </body>
</html>

方法 2: 名前空間接頭辞を XPath 式に追加する

リスト 39 に記載する XSLT では、接頭辞に XML 文書のデフォルト名前空間が割り当てられています。接頭辞 xhtml は単なる一例で、任意の接頭辞を使用することができます。

リスト 39. 名前空間接頭辞を使用した XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xhtml="http://www.w3.org/1999/xhtml" 
    xmlns="http://www.w3.org/1999/xhtml" 
    exclude-result-prefixes="xhtml" >
  <xsl:output method="xml" version="1.0"/>
  <xsl:template match="xhtml:title">
    <title>Replaced with a new title</title>
  </xsl:template>

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

リスト 40 に変換後の XHTML 文書を記載します。このとおり、<title> 要素のなかにあったテキストは正しく置き換えられています。XSLT で exclude-result-prefixes 属性を使うことによって、xhtml: 接頭辞は変換後の出力文書から排除されます。

リスト 40. 変換後の XHTML 文書
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Replaced with a new title</title>
  </head>
  <body>
    <p>Hello world.</p>
  </body>
</html>

方法 3: 前処理によって名前空間を削除する

XML 文書を XSLT で前処理して名前空間を削除するという方法も 1 つの選択肢ですが、これには時間がかかる場合があります。リスト 41 に記載する XSLT は、ソース XML 文書のテキスト、ノード、および属性ノードをコピーする一方、名前空間ノードと DOCTYPE 宣言は省略します。

リスト 41. 名前空間ノードを削除するための XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0"/>

  <!-- copy elements without namespace -->
  <xsl:template match="*">
    <xsl:element name="{name()}">
      <xsl:apply-templates select="node()|@*"/>
    </xsl:element>
  </xsl:template>

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

この 2 つのパスに分割する方法では、最初の XSLT の結果を 2 番目の XSLT で変換します。つまり、文書を 2 回変換します。マルチパス変換についての詳細は、「参考文献」を参照してください。

方法 1 を適用できる場合には、これが最も簡単で単純な解決方法となります。XML 文書が一貫してデフォルト名前空間を使用しているとしたら (これが通常の場合です)、 方法 2 を適用することが可能です。XPath 式に名前空間接頭辞を追加するための入力作業が多少必要ですが、この方法 2 は有効な解決方法であり、処理を追加することもありません。方法 3 は XML 文書を 2 回処理するため、時間がかかります。ただし、この方法の場合、ソース XML 文書がデフォルト名前空間を使用しているかどうかは関係なくなります。


まとめ

XPath 式の 2 値論理は、単に true または false のどちらであるかというよりも少し複雑です。等しいか等しくないかを比較する場合、値がなければ偽という結果になります。また、Boolean 値から文字列へ変換されたものを Boolean として解釈されると、真であると評価されます。3 番目のヒントでは、コンテキストによって変わる position() 関数の相対的な性質を説明し、4 番目のヒントでは、XPath の preceding 軸または述部を使用して、リストから特定の名前を持つ最初の要素を選択する方法を紹介しました。そして最後に、XPath の select 式が失敗するという一般的な問題を、XML 文書のデフォルト名前空間が原因となっているものと特定し、考えられるいくつかの解決策を提示しました。

XPath は XML 文書内のノードを選択するのに力を発揮する関数型言語です。けれどもすべてのコンピューター言語の例に洩れず、XPath にも学習曲線があります。この記事で紹介した例を試して、さらに理解を深めてください。

参考文献

学ぶために

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

議論するために

コメント

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=681611
ArticleTitle=XPath の道を外れないために
publish-date=06242011