目次


Java環境からXQueryを使用する

JavaアプリケーションからXQueryによって文書を検索する

Comments

SQLデータベース、XMLデータ、そしてクエリー、一体どうすればよいのでしょうか

プログラミングの世界、そして特にJavaプログラミングが増大する中で、標準化された選択肢も増えつつあります。つまり、Sunから提供または承認され、利用可能になったAPIがますます多くなっているということです。この標準化の結果、その中核技術以外の分野に進出する開発者が急増し、新たなテクノロジーを習得しています。

習得すべきツールおよびAPIを有意義かつ興味深い順に並べると、データ処理に関連するものが上位にランクされます。いかにアプリケーションが優れていようとも、その価値は最終的にデータの処理能力で決まります。しかも、APIが増え続けているのに対し、一般に使用されている有名なデータ・フォーマットは減り続けています。オブジェクト指向データベース管理システムまたはXML駆動型データベースを依然として使用するデータ・マネージャーもいますが、リレーショナル・データベース (RDBMS) が混乱を切り抜け、多くのデータ・マネージャーから引き続き選ばれているようです。その結果、JDBC (データベース接続用) か、ことによるとJDO (Java Data Object) を使用してJava開発者がSQLデータベースとやり取りするような状況になっています。

また、データベースに存在しないデータの大半が、データ・フォーマットとしてXMLで標準化されています。XMLは冗長な一方で強固であり、おそらく言語のなかでは、他の非JavaメディアよりもXMLと連動しているAPIの数が多いと思われます。構文解析、データ・バインディング、変換のいずれを行っているかにかかわらず、ご使用のアプリケーションは、XMLを処理できなければ、制限が多いとみなされるか、いささか時代遅れとのレッテルも貼られかねないでしょう。

しかし、この一見無関係な2つの事実 (SQLデータベースにデータが存在する傾向と、データベース外部のあらゆるデータに対するXMLの普及) が固有の問題をもたらしています。SQLデータベースでは照会は簡単ですが、XML文書では簡単ではありません。利用者はデータを簡単に検索できることを期待します。これはデータベース内のデータについてはうまくいくものの、XML文書のデータについてはさほど容易ではありません。言うまでもなく、検索の容易化のみを目的としてXMLフォーマットのデータを取り込んでデータベースにダンプするのは誤った方法です。そこでXQuery、さらに当然の結果として、XQuery API for Java (XQJ) が登場します。

XQuery:3つの部分からなるテクノロジー

XQueryを最も単純に表現すると、XML文書の検索を定義するために用いられる言語ということになります。特定の状況でSQLがSELECTやFROMに固有の意味を与えるように、XQueryはスラッシュ (/) とアットマーク (@)、その他多くのキーワードおよび主要文字の意味を定義します。

ただし、本質的にXQueryを構成するのは以下の3つのコンポーネントです。

  1. XPath仕様:XML文書で0個、1個、または複数のノードを選択する方法。
  2. 追加の構文規則。これにより、特定のXML文書を選択するほか、XPathから返されたノードに選択基準を追加します。
  3. 特定のプログラミング言語でXQuery式を評価するAPI (XQJ、XQuery for Javaなど)。

XQueryを会得するには、上記の 3つのコンポーネントを確実に理解する必要があります。それは Java プログラマーにとって、とりもなおさず、XPathを習得し、XPath構文に関する他の構造を習得し、これらすべてを JavaベースのAPIにまとめ、XQuery式をXML文書に発行するということです。

その長所は、XPathおよびXQueryの構文が極めて直感的な点にあります。UNIX®シェル、Mac OS Xのターミナル、またはDOSウィンドウのディレクトリー構造をナビゲートした経験がおありなら、有利な状況といえます。小なり記号 (<)、大なり記号 (>)、等号 (=) などの演算子の基本的な使用に加えて経験を積めば、XPathを極める道の半分は踏破したことになります。

XPathの基本

実際のところXQueryは、別のXML仕様であるXPath仕様にほぼ完全に依存しています。XPathは、XML文書の一部を参照するパスの作成方法を定義するだけの機能にとどまりません。たとえば、XPath/play/act/sceneは、要素actの内部に要素sceneがすべてネストされており、ルート要素playの内部に要素actがネストされていると解釈されます。

XPathは相対的

基本的なXPathでは要素名およびスラッシュを使用します。また、デフォルトの場合、XPathはXML文書内の現在位置から開始します。したがって、たとえばDOM (文書オブジェクト・モデル) を使用して、speechという要素にナビゲートし、speakerというパスを発行すると、現在位置の要素speechの内部にある要素speakerと評価されます。このように、XPath は、文書内の現在位置と相対的に評価します。

カレント・ノードから移動する

文書のルートに移動するには、パスの先頭にスラッシュを付加します。そのため/playと指定した場合には、文書内の現在位置にかかわりなく、ルート要素playが検索されます。また、../では、現在の要素の親要素を選択することができます。実際、そのように指定すると、ディレクトリー構造と同様に見えるでしょう。したがって、../../personae/titleというパスを指定した場合には、現在の要素から2つ上のレベルに移動して、要素personaeを検索した後、personaeの内部にネストされている要素titleを検索することになります。

属性を検索する

選択できるものは要素のほかにも数多く存在します。/cds/cd@titleというXPath構文について考えてみましょう。この場合、ルート要素cdsの内部にネストされている要素cdに存在する“title”という属性がすべて返されます。

@構文で返されるのは属性値ではなく、属性自体であることに注意してください。したがって、@isbnと指定した場合には、属性isbnがすべて選択されますが、いずれの属性値も選択されません。また、XPathでは、属性という用語は属性の名前および値を指します (詳細については、ノードの説明部分でただちに取り上げます)。

テキストを選択する

要素および属性を選択できるのと同様に、要素内のテキストも選択できます。XPath構文の末尾が/cds/cd/titleのような要素名である場合には要素を選択しています。選択した要素には、その内部のテキストは含まれません。ただし、要素内のテキストが必要な場合には text() 構文を使用します。したがって、CDセットのテキスト・タイトルをすべて選択するには、/cds/cd/title/text() のようなパスを指定できます。そのため、このパスを指定した場合、要素は選択されず、指示された要素の内部にあるテキストが選択されます。

XPath はノードのセットを選択

XPathでは常にノード・セットが評価されるという点に気付くことが、XPathを活用する鍵です。ノード・セットにはノードが含まれていないことも、単一または複数のノードが含まれていることもありますが、XPathの結果は常に1つのノード・セットです。これは、XPath構文を記述する際に大半の人が考えること、すなわちパスから返されるのは1個の要素、1個の属性、または一部のテキストであるという発想とは相容れないものです。しかし、それはまったく違います。

DOMを使用している方なら、ノードについて考えたことがおありでしょう。DOMでは、XML文書内のすべてのもの、すなわち要素、属性、テキストはノードです。要素は要素ノード、属性 (およびその値) は属性ノードであり、要素内のテキストもテキスト・ノードです。したがって、../../personae/titleのようなパスを指定すると、要素titleが最終的に選択されますが、実際に返されるのはノード・セットです。ノード・セットでは、ノードがない (一致する要素が存在しない) こともあれば、1個または2個以上ということもあります。この場合に該当するノードは、すべて“title”という要素になります。

パスがより複雑になってセットの選択の幅が広がり、属性と要素を同時に含めるか、またはテキストと要素を含めることが可能になったとしても、最終的に、そのパスでは依然としてノード・セットを選択するにとどまります。この点に留意することがXQueryを正しく使用するための鍵となります。XPathを使用してノード・セットを選択した後、XQueryを使用し、通常は選択済みノードのサブセットを検索条件を指定して選択するか、複数のノード・セットを結合した後に検索条件を適用することもあります。単一のセットが複数のタイプ (要素、テキスト、または属性) を持つ可能性に留意することで、それらのパスをより適切に記述することができ、必要なものが評価されるようになります。

XPathで選択度を高める

ノード・セットをノード名を基準に選択する方法 (要素および属性の場合) と、それらのノードの親を選択する方法 (テキストの場合、または指定された親の子ノードをすべて選択する場合) について見てきました。XPathはそのままでも十分に強力ですが、すべての場合に述語と呼ばれるものを使用して、選択度を充実させています。

述語の基本および構文

述語は、既存のノード・セットに適用できる部分式です。述語は、大括弧“[" および "]”で囲みます。述語の適用対象は、述語の左側にあるノード・セット (パスに表示) です。たとえば、/cds/cdというパスを考えた場合には、“cds”というルート要素の内部にある要素がすべて選択されます。ここで、最初のCDが必要と想定した場合、以下のように述語を指定して、そのCDを取得することができます。/cds/cd[1].このように指定すると、パス/cds/cdで選択したノードの1番目が返されます。

どのノード・セットにも述語は適用可能

述語の適用対象は述語の左側にあるノード・セットということを忘れないでください。ただし、完全なXPath構文の末尾にのみ述語が現れるという意味ではありません。XPath自体は、実際にはパスの集合であって、それぞれがノード・セットを返し、その後に各セットの絞り込みまたは処理を行うパスの部分を返すものと考えてください。したがって、/cds/cd/titleの実体は以下の3つのパスです。

  1. /cds。“cds”というルート要素 (1個の要素ノードを含む単一のセット) を返します。
  2. cd (前のノード・セットに対して相対的)。前のノード・セットの内部にネストされている要素cdをすべて返します。
  3. title (これも前のノード・セットに対して相対的)。前のノード・セットの内部にネストされている要素titleをすべて返します。

述語はノード・セットに適用する必要がありますが、どのノード・セットにも適用できます。したがって、完全に正当なパスは /cds[1]/cd[2]/title[1] となります。このように指定すると、/cds で選択したノード・セットの1番目、/cds[1]/cd で選択したノードの2番目、/cds[1]/cd[2]/title で選択したノードの1番目がそれぞれ選択されます。

注意:このパスの一部は実際には意味がありません。たとえば、/を使用してルート要素を選択した後に述語[1]を適用すると、そのセットの1番目 (および唯一) の要素が返されます。述語が要素を返さないのは、ノード・セット自体が空である場合に限られます。このとき、指示されたルート要素の名前は文書の実際のルート要素と一致していません。ただし、図示という目的上、また、技術的観点からも、XPath自体または述語を使用することに問題はありません。

述語の数値索引を超越する

数値でしかサイトを参照できないAPIであれば、相当の制限があるのも当然です。必要とするアイテムがセット内のどこにあるかを絶えず正確に知るには、そのモデルの形式に頼らざるを得ません。しかしXPathでは、そのような制約を超えるメリットを提供します。まず last() 関数を述語で使用して、セット内のアイテムの個数にかかわらず、そのセット内の最後のアイテムを選択することができます。したがって、/cds/cd[last]と指定すると、文書内の最後のcdが選択されます。

また、position() 関数を使用して、特定の位置よりも値が大きい (または小さい) アイテムをすべて選択することもできます。position() 関数は、指定されたノードのセット内における位置を返します。たとえば、最初の5つのCDが必要な場合、パス/cds/cd[position()<6] を使用できます。この指定では、position() によって6未満と評価されたノードがすべて選択されます。

データを基準にノードを選択する

これは XPathの概要であって、XPathの完全な解説ではありませんが、ノードの子要素またはノードの属性に基づくノード選択について最後に説明します。XPath構文で順次処理される各部分の基準は、先行パスで決定されたノードのセットですが、この場合と同様に、セットの述語の基準は述語の適用対象となるセットです。小なり記号 (<)、大なり記号 (>) などの演算子を述語で使用できるほか、セット全体に含まれる各ノードの位置のみならず、選択したノード内のデータを基準に選択度を高めることができます。

そこで、属性“style” (値は“folk”) を持つCDがすべて必要になったと仮定しましょう。使用する式では、CDをすべて選択した後、各CDの属性 style と値“folk”を比較します。XPath では、式の記述は/cds/cd[@style='folk'] となります。既にお読みいただいた内容からすると、これについては特に説明を要しないでしょう。最初に、/cds/cd によってノードのセットが選択されます。次に、部分述語@styleによって各ノードの属性“style”が分離されます。前述したように、名前の前に@が付いている場合、属性を意味します。また、その属性は、既に選択されているノードのセット (この場合は要素 cd すべて) に関連しているとみなされます。次に、これらの属性の値がストリング“folk”と比較されます。属性の一致するものが返され、それ以外はすべて結果セットから除外されます。

選択したセットのネストされた要素についても同様の処理が可能です。リスト1の文書に含まれているような構造化された文書を想定してみてください。

リスト 1. サンプルCDリスト文書の構造
<cds>
 <cd style="some-style">
  <title>CD title</title>
  <track-listing>
    <track>Track title</track>
    <!-- More track elements... -->
  </track-listing>
 </cd>
 <!-- More CDs... -->
</cds>

さて、トラック数が10以上のCDがすべて必要になったとしましょう。ここでなすべき作業は、何度も見てきたパス (/cds/cd) と関係している要素CDをすべて選択することです。次に述語を使用して、選択したセットに関係する特定ノード・セットを数えます。この場合、track要素ノードをすべて選択しますが、track要素ノードはtrackリスト要素内、trackリスト要素は実際に返されるべきノード・セット内にそれぞれネストされています。最後に、これらのノードのカウントを適用します。XPathでは、count() 関数で簡単にカウントすることができます。重要な点を最後に付け加えると、ここでは、そのカウントと数値10を比較しなければなりません。これらをすべてまとめると、以下のパスおよび述語を得られます。/cds/cd[count(track-listing/track) >= 10].

XPathの述語に含まれる矛盾に関する注意

注意深くお読みいただいた場合には、XPathで要素テキストと属性を扱う方法に矛盾があることにお気付きかもしれません。属性および属性値はいずれも属性ノードで、単一の情報単位として扱われることについては、既にお話ししました。しかし述語では、@typeなどの式は type 属性のノード全体を参照するのではなく、type属性の値のみを参照します。これにより、(@type='reggae' の場合のように) その値と他の値の比較が可能となります。

同様に、/cds/cd[title='Revolver'] のように、述語に含まれる要素のテキストを参照することができます。ここで、cd の内部にネストされている type 要素の値が値“Revolver”と比較されます。そして、属性ノードの場合と同じく、要素に関する考え方についての標準ルールに違反しています。通常、要素ノードはテキスト・ノードの親ですが、ここでは、基本的に述語が要素内のテキストを逆参照しています。

これらの軽微なルール逸脱は、その存在を認識している限り問題ではなく、要素と属性に関する2つの考え方を切り替えることができます。いかなる場合に属性と属性値を1つのノードとみなし、いかなる場合に属性値を参照するかを常に明確にするほか、同様に、いかなる場合に要素に他のテキスト・ノードが含まれており、いかなる場合にそのテキストと他の値を比較できるかを知る必要があります。

XQueryをミックスに追加する

XPathは非常に強力ですが、多少の制限があります。第一に、XPathは多くの場合、静的データに適しています。つまり、述語およびXPathを使用することで、要素、属性、およびテキストを比較する特定データ・セットによって特定文書のXPathクエリーを作成します。また、XPathは (if/else 文のような) 制御構造を持っておらず、単純な比較を超える処理を実行することもできません。

とはいうものの、プログラマーでない人の多くにとって、そのような制限は大した問題ではありません。しかし、完全なプログラミング言語としての威力を備えたJava (またはC#、Python) プログラマーなら、XML文書での検索についてXPath単独でもたらされるよりもはるかに優れた案をすぐに思い付くでしょう。ここでXQueryが登場するのは言うまでもありません。

文書の選択が第一歩

XQueryには、使用頻度が低いにもかかわらず、最も重要な機能があります。その一例は、XPath が適用される文書を指示する機能です。したがって、XPathで /cds/cd[title='Revolver'] のようなパスを特定の文書に適用する場合には、doc() 関数を使用して、実際のXQueryで文書を指示することができます。そのため、catalog.XMLを検索する必要があるときは、doc("catalog.XML")/cds/cd[title='Revolver'] をXQuery式として指定します。

この小さな関数の能力は、文書をプログラムで選択するコードを (たとえばユーザー入力に基づき) 記述するか、または文書のセット (たとえばネットワーク上のiTunesカタログすべて) を連続処理して各文書にステートメントを簡単に適用することです。

XQueryおよびFLWOR

もちろんXQueryには、単なる実行時文書選択を超える機能が数多く用意されています。FLWOR と呼ばれる機能を利用すると、XQueryの威力は最大限に発揮されます。FLWORは、“for、let、where、order by、return”の頭字語です。これらはいずれも、より正確な結果を得るためにXQuery式で使用できる節です。

  • for:ノード・セットを取得し、それに対して処理を反復することができます。さまざまな態様で、for によってノード・セット内の現行値に変数が割り当てられるため、その変数を操作することができます。
  • let:変数に値を割り当てるときに使用します。ただし (間もなく明らかになることですが)、let は他のFLWOR節ほどには使用頻度が高くありません。
  • where:XPathで提供されている範囲を超えた選択基準をノード・セットに適用できます。もちろん、多くのクエリーにおいて、whereはXPathを超えるものではありません。where はXPathの述語を別の場所に移動するだけです。
  • order by:データを変更またはフィルターするものではなく、結果セットに順序を適用するだけなので、XPathで使用されている場所以外の基準で値をソートすることができます。
  • return:ノード・セットを操作できますが、ノード・セット以外が (結果として) 返されます。セットを選択して順序設定およびフィルター処理を行い、その結果の子要素のみが返されるようにしたいこともあります。returnは、そのような処理を実現する鍵です。

これらの各節について、もう少し掘り下げてみましょう。

for節を使用する

for節の用法は、Java、C#などのプログラミング言語の場合とほぼ同じです。for節の書式を以下に示します。

for $variable-name in XPath
...

変数名は、xのような通常の識別子でかまいません。通常、変数には用途に応じた (firstName、titleなどの) 名前が最適ですが、この変数は基本的にループ・カウンターなので1文字の名前でも可とします。

XPathの指定内容は任意ですが、/cds/cdは絶好の例です。したがって、XPathはこのように指定されることもあります。

for $cd in doc("catalog.xml")/cds/cd
...

正にこのとおりです。各ノードの値がXPath /cds/cdによって返され、ここで変数$cdがその値を受け取ります。この場合、... が表すのはXQuery式の残りであり、これについては次に説明します。

プログラマーにとって、このステートメントは以下と何ら変わりません。

for (int i = 0; i<cdArray.length; i++) {
  CD cd = cdArray[i];
  // Process CD
}

あるいはリストの場合、次のようになります。

for (Iterator i = cdList.iterator(); i.hasNext(); ) {
  CD cd = (CD)i.next();
  // Process each CD
}

letを使用して変数を割り当てる

前述したXQueryについてはしばらく保留し、let節を使用して変数を割り当てます。XQuery でご覧になったとおり、変数を定義するには識別子名の先頭にドル記号 ($) を付けます。XQueryでは、上記のように変数を使用することが多いでしょう。もっとも、変数は let 節によって明示的に作成されることはなく、for節によって暗黙的に作成されます。

ただし、明示的な変数を使用したいことがあるかもしれません。その場合、以下のような処理が可能です。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
...

ここで、検索対象の文書が変数に割り当てられます。XQueryを割り当てるには := を使用しますが、若い頃にPascalを利用されたことがない限り、この指定方法は少しばかり奇妙に見えるかもしれません。しかし、より現実的な状況でこれを捉えると、XML文書のリストに対して反復処理を実行し、$docNameに対して各文書名を順に割り当てる機能が手に入るとも考えられます。そうすれば、その大きなリスト内の各文書に含まれる個別のcd要素を選択し、それぞれの要素を同じ方法で処理することができます。

return節によってクエリーを完了する

クエリーを完了するために、FLWORから割り当てられた順序からいったん抜け出します。ここまでの構文は以下のようになります。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
...

次は何かを返す段階です。このクエリーはcd要素をすべて選択しますが、それらの要素を返すことはさほど有益ではありません。それが目的のノード・セットであったとしても同じことが言えます。これらの要素を返す代わりに、より識別可能なもの (要素に格納されている各CDのタイトルなど) が必要と仮定してみましょう。いよいよreturnの出番です。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
return $cd/title/text()

ここでcd要素がすべて選択された後、その結果として得られる各ノードが$cdに順次割り当てられます。最後にreturn節が返すのは、その要素自体ではなく、ターゲット・データが格納されている“title”という子要素です。

単純なミスをしないように注意して、以下のようにクエリーを記述します。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
return /cds/cd/title/text()

これには以下のように3つの重大な問題があります。

  • 第1に、これはXQueryの目的を損なうものであり、照会を実行するのではなく、XPath のロケーションを返すだけになっています。
  • 第2に、どの文書からCDを選択するかを示すdoc($docName)とは無関係にデータが返されています。
  • 最後に、最も重要な点ですが、fo節によって返されるノード・セットに対して実行されるフィルター処理または順序付けがすべて無視されてしまいます。

次に、この種の構文を作成していただくため、このことの重要性はすぐ明確になります。ただし現在は、for節で定義した変数がreturn節にも必ず出現するようにしてください。この簡単な経験則は、クエリーを思いどおりに振る舞わせるのに役立ちます。

whereで選択度を高める

whereを使用すると、XQueryでの選択の幅が広がります。XQueryでwhere節を使用すると、SQLの場合と同様に機能します。where節を選択に追加する目的は、結果セットを絞り込むことにあります。極めて単純な例を以下に示します。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
where $cd@type = 'reggae'
return $cd/title/text()

この構文では、レゲエCDのタイトルがすべて返されます。この場合、説明はほとんど不要でしょう。where節は単純明快です。ただし、以下のようにandを使って、複合条件を適用することも可能です。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
where $cd@type = 'reggae'
  and count($cd/track-listing/track) > 10
return $cd/title/text()

この構文では、トラック数が10を超えるレゲエCDがすべて返されます。

where節には重要な用途がもう1つありますが、それは基本的に結合を実行する場合です。リスト2のように設定したXMLファイルがあるとしましょう。

リスト 2. CDリスト文書の拡張構造
<cds>
 <cd style="some-style">
  <title>CD title</title>
  <artist id="289" />
  <track-listing>
    <track>Track title</track>
    <!-- More track elements... -->
  </track-listing>
 </cd>
 <!-- More CDs... -->

 <artists>
  <artist id="289">
   <firstName>Bob</firstName>
   <lastName>Marley</lastName>
  </artist>

  <!-- More artist elements -->
 </artists>
</cds>

これはリスト1のXML構造を拡張したものです。ここで追加されるartist要素は、id属性で識別されたものであり、かつ、各cd要素の内部にネストされた1つ以上のartist要素によって 各CDに関連付けられています。

XQueryでは、CDとCDのアーチストの結合に相当するものを実行できます。以下に例を示します。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
                where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
return $cd/title/text()

目下2つの事柄が進行中です。この段階で、for節で複数の変数を定義できることが初めてわかります。このステートメントではCDの識別に限らず$artistの定義も行っているため、artist要素のセットを処理するのは簡単です。

この場合、where $cd/artist/$id = $artist/$idと記述された行により、where節はCDとCDのアーチストを基本的に結合します。なお、各CDと各アーチストのマッチングにより、SQL結合と同じ効果を得られます。その後、以下のように詳細な選択が定義されます。$artist/lastName = 'Marley'.そのため、“Marley”という姓のアーチストがすべて選択されます。ただし、結合も存在し、return節がCDタイトルを返すことを忘れないでください。このようにして、“Marley”という姓のアーチストのCDの全タイトルが得られます。

ここで、いよいよXQueryが真価を発揮します。SQLと同様の複雑な結合および選択が実行可能で、すべてがXML文書に適用されます (その多くは、高度な検索を考慮して構造化されたものではないでしょう)。

ノード・セットの順序付け

whereがSQLの場合とほぼ同じように機能する場合、order byはSQLの場合とまったく同じように機能します。以下のように、XPathで参照可能なものなら何を使用しても結果を順序付けられます。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
order by $cd/release/@date
return $cd/title/text()

ここでは、返されたCDタイトルが、各CDの子要素のdate属性であるreleaseによって順序付けられます。デフォルトではソートは昇順ですが、必要な場合は明示的にソート順を指定できます。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
order by $cd/release/@date ascending
return $cd/title/text()

降順のソートも可能であることはいうまでもありません。この場合、以下のように、直近でリリースされたCDが最初に返されます。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
order by $cd/release/@date descending
return $cd/title/text()

注意:date属性がdate型と認識されるスキーマまたはXQueryプロセッサーを使用しないと、このステートメントでエラーが発生する可能性があります。日付がテキスト・データとみなされ、ABC順にされるという一層悪い状況もありえます。ただし、最新プロセッサーの大半は日付を処理するため、この問題はめったに生じません。

複数のソート基準を含めることもできます。たとえば現在の式では、“Marley”という姓のアーチスト全員 (Bobとは限りません) のCDがすべて返され、それらのCDはリリース日によるソート時にインターリーブされます。以下のように、アーチスト名によるソート後にリリース日でソートされるように式を改良することもできます。

let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
    $artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
  and $artist/lastName = 'Marley'
order by $artist/firstName, $cd/release/@date
return $cd/title/text()

結果がすべて同じ姓の値になるため、$artist/lastNameでソートした後に$artist/firstNameでソートするのは冗長です。

Javaテクノロジーをミックスに追加する

これまで長い導入部分を読んできたが、Java環境からXQueryを使用する方法の解説部分に到達するだけのためにそれほど手間がかかるのか、と思われるかもしれません。しかしながら、XQJ (JavaのXQuery APIを表すためによく使われる頭字語) を選択するプログラマーの多くは、XPathやXQueryを表面的に理解している程度にすぎません。これまで皆さんが学習してきた内容は基本を上回るレベルなので、前述した式をJavaプログラムから使用する用意が整ったことになります。

XQueryの仕様はベンダー非依存

XQuery for Java APIはSunの支援を受け、Java Community Process, JSR 225の一環として開発されています (リソースのリンクを参照してください)。その仕様自体には、Sun、Nokia、BEA、Oracle、Intelといったベンダーのほか、サーブレット、JDOM、XMLフレームの専門家であるJason Hunterのような少数の重要な個人も関わっています。したがって、単独のデータベース・ベンダーやXML製品メーカーに縛られない環境でアイデアを練り上げる絶好の機会となります。

ただしXQJのインプリメンテーションは例外

Sun標準のXQJインプリメンテーションは、残念ながらまだ存在しません。エキスパート・グループ所属ベンダーの大半の勤務先企業では、XQJのインプリメンテーションを自社製品と共に提供しているため、ユーザーはベンダー固有の問題に対処することになります。XMLパーサーやXSLプロセッサーがしのぎを削る状況が2000年代初期の大きな特徴でしたが、XMLの熟練者にとって、それが再現されていることは言うまでもありません。XQJ はいずれ標準化されると見込まれており、SunがXQJの独自バージョンをリリースするか、JAXPがXMLパーサーやXSLプロセッサーに対して機能するのと同様に機能するXQJインプリメンテーション用ラッパーAPIをリリースするのもほぼ確実です。

XQJのインプリメンテーションを達成する

DataDirectから無料試用版をダウンロードするのが、XQJを始める最も簡単な方法です。煩雑なフォームに記入しなければなりませんが、その後には相当の時間、すなわち本書を終えるのに十分な時間が確保されています。DataDirect XQueryダウンロード・サイト (リソースのリンク) にアクセスしてください。アクセスしたいデータベースの名称を入力する必要もあります(XML Documents Onlyオプションを選択する場合も同様です)。これらのオプションを適宜選択すると、datadirectxquery.jarというJARファイルのダウンロード先の指示が記載されたEメールが届きます。

JARred JARを展開する

インストール・プロセスは若干分かりにくく、ダウンロードしたdatadirectxquery.jarをまず展開しなければなりません。展開するには jar コマンドを使用します。ただし、jarコマンドの実行前にインストール用ディレクトリーを作成し、その中にJARファイルを展開する必要があります。その後で、以下のjarコマンドを実行することができます。

[bdm0509:~/Desktop] mkdir xqj
[bdm0509:~/Desktop] cd xqj
[bdm0509:~/Desktop/xqj] jar xvf ../datadirectxquery.jar 
 inflated: XQueryInstaller.jar
 inflated: ddxqj.jar
 inflated: ExtensionTool.jar
 inflated: Readme.txt
 inflated: 3rdPartySoftware.txt
 inflated: Fixes.txt
 inflated: installer.properties

インストーラーを実行する

新規作成したディレクトリーを開き、XQueryInstaller.jarファイルをダブルクリックします。システムにJavaがインストールされている場合、GUIインストーラーが起動します。

試用版またはライセンス版を選択するプロンプトが表示されたら、試用版を選択します。次に、インストール・ディレクトリーを選択します。ここで入力するディレクトリーへのファイル書き込みが許可されていることを確認してください。つまり、ご使用のシステムで/usr/local/java/xqjを選択した後、まず、/usr/local/javaディレクトリーへの書き込みが可能なことを確認する必要があります。インストールによって、最後のサブディレクトリー (この例では xqj) が作成され、DataDirect XQueryファイルが格納されます。最終段階では、インストールされたファイルを実行し、Finishをクリックします。

完了後に新規ディレクトリーにナビゲートして内容を確認すると、以下のように表示されます。

[bdm0509:/usr/local/java] cd xqj
[bdm0509:/usr/local/java/xqj] ls
3rdPartySoftware.txt    examples                lib
Fixes.txt               help                    planExplain
Readme.txt              javadoc                 src

クラスパスにXQJ JARを追加する

ここで、XQJクラスを含むXQuery JARが組み込まれるように、クラスパスを更新します。このJARはddxq.jarという名前でlibディレクトリーに保存されています。これは当初のダウンロード済み JARではなく、上記で展開したJARによってインストールされたものです (DataDirect では、ZIPまたは.tar.gzファイルを主要なダウンロードに使用することで、処理の煩雑さを軽減できたかもしれません)。クラスパスは手動で設定できますが、シェル・スクリプト、.profileファイルまたはIDEを使用してクラスパスにJARを追加することも可能です。ddxq.jarがクラスパスで有効になっていることを確認してください。

データベースの接続を設定する

DataDirectのダウンロードにより、データベースへの接続も提供され、リレーショナル・データベースに対してXQueryを実行することができます。ダウンロード・フォームの記入時には、接続する (可能性のある) データベースの選択が必須でした。次にDataDirectによってダウンロードがカスタマイズされ、データベースのセットアップが使用可能になっています。それは本書の範囲を大きく超えるものですが、DataDirectライブラリーを使用してデータベースに接続することと、ディスクに保存されたXML文書に対してXQueryを実行することに関心をお持ちの場合は、リソースの関連リンクを確認してください。その他多数のJARファイルは、インストール済み環境のlibディレクトリーにありますが、いずれも単純なファイル照会に関しては不要です。データベース接続のためにDataDirectを後で使用する場合は、これらのJARを検討する必要が生じることもあります。

XQueryをJavaから実行する

XPathとXQueryを確保し、XQJのインプリメンテーションがクラスパスに指定されていれば、Javaコードを記述し、クエリーを実行する準備が整っています。プログラムを作成する際には、以下の2つの基本ステップを実行します。

  1. XQueryデータ・ソースを作成またはアクセスします。
  2. XQueryを実行します。

どちらも非常に簡単なステップであり、XQueryの別のインプリメンテーションに切り替えない限り、どのプログラムでも最初のステップは変わりません。実際、データ・ソースの構成およびユーティリティー・クラスへの接続を処理するために記述するコードをバンドルしたいこともあります (練習問題として残してあります)。

XQueryデータ・ソースで作業する

JDBCの作業経験が豊富な方や、n階層のデータ指向アプリケーションを作成したことがある方にとって、データ・ソースという考え方はおなじみでしょう。この状況下においてデータ・ソースは、接続方法および接続先の詳細を抽象化する接続オブジェクトです。そのためデータ・ソースは、MySQLデータベースへのネットワーク接続を指すこともあれば、静的XML文書へのファイル・ベースの接続を表すこともあります。ただし、データ・ソースがあれば、接続のセマンティクスにかかわらず操作することができます。

ローカル・ディスク上のXML文書を単に照会する場合 (本書の中心課題)、接続をセットアップするのは極めて簡単です。リスト3に示す基本的なJavaプログラムでは、データ・ソースを新規作成し、照会の準備をしています。

リスト 3. 照会に備えてデータ・ソースをセットアップする
package ibm.dw.xqj;

import com.ddtek.xquery3.XQConnection;
import com.ddtek.xquery3.XQException;
import com.ddtek.xquery3.xqj.DDXQDataSource;

public class XQueryTester {

  // Filename for XML document to query
  private String filename;

  // Data Source for querying
  private DDXQDataSource dataSource;

  // Connection for querying
  private XQConnection conn;

  public XQueryTester(String filename) {
    this.filename = filename;
  }

  public void init() throws XQException {
    dataSource = new DDXQDataSource();
    conn = dataSource.getConnection();
  }

  public static void main(String[] args) {
    if (args.length != 1) {
      System.err.println("Usage: java ibm.dw.xqj.XQueryTester [XML filename]");
      System.exit(-1);
    }

    try {
      String xmlFilename = args[0];
      XQueryTester tester = new XQueryTester(xmlFilename);
      tester.init();
    } catch (Exception e) {
      e.printStackTrace(System.err);
      System.err.println(e.getMessage());
    }
  }
}

これは、実際よりもはるかに長大かつ複雑な外観となっています。これは、コンストラクターと init() メソッドによってテスト・プログラムがモジュール式でセットアップされているためですが、その結果として、このコードは大きな変更を要することなく、ユーザー自身の目的に合わせて簡単に再利用できます。

このプログラムは、ファイル名を取得し (コマンド行から取得し、クラスのコンストラクターに渡す)、以下のコードを実行します。

dataSource = new DDXQDataSource();
conn = dataSource.getConnection();

まず、データ・ソースが新規作成されます。このオブジェクトのタイプは com.ddtek.xquery3.xqj.DDXQDataSourceです。空のコンストラクターを使用します。この場合、データベースに接続するわけではないので、さらに構成を進める必要はありません。次に、そのデータ・ソースを使用して、新しい com.ddtek.xquery3.XQConnectionオブジェクトを取得します。このオブジェクトは、XQuery式を新規作成および実行するための鍵です。まだ実行していませんが、プログラムは照会ストリングを取り込んで実行する準備が整いました。

実際のXML文書を照会する

実際に照会するには、XMLファイルも必要です。ダウンロード時には、圧縮形式のサンプルCDカタログ・ライブラリー・ファイル (W3Cがサンプルとして提供している以下のようなXML文書) をダウンロードするためのリンクが表示されます。200行を超えているため、このファイルは出力しませんが、照会するのに適した文書です。

リスト 4 では、その文書の一部を示し、構造がわかるようにしています。

リスト4. cd_catalog.XMLの一部
<?xml version="1.0" encoding="ISO-8859-1"?>
<CATALOG>
        <CD>
                <TITLE>Empire Burlesque</TITLE>
                <ARTIST>Bob Dylan</ARTIST>
                <COUNTRY>USA</COUNTRY>
                <COMPANY>Columbia</COMPANY>
                <PRICE>10.90</PRICE>
                <YEAR>1985</YEAR>
        </CD>
        <CD>
                <TITLE>Hide your heart</TITLE>
                <ARTIST>Bonnie Tyler</ARTIST>
                <COUNTRY>UK</COUNTRY>
                <COMPANY>CBS Records</COMPANY>
                <PRICE>9.90</PRICE>
                <YEAR>1988</YEAR>
        </CD>
  <!-- more CD listings ... -->
</CATALOG>

XQueryを作成する

次に、実際のクエリーを作成する必要があります。最も簡単に作成するには、サンプルの Java Stringを使用します。新しい変数を作成し、XQueryTesterクラスにおいて、ここで示している照会のストリング・バージョンをその変数に設定します (このコードはすべて main() メソッドです)。

try {
      String xmlFilename = args[0];
      XQueryTester tester = new XQueryTester(xmlFilename);
      tester.init();

      final String sep = System.getProperty("line.separator");
      String queryString =
        "      for $cd in doc($docName)/CATALOG/CD " +
        "    where $cd/YEAR > 1980 " +
        "      and $cd/COUNTRY = 'USA' " +
        " order by $cd/YEAR " +
        "   return " +
        "<cd><title>{$cd/TITLE/text()}</title>" +
        " <year>{$cd/YEAR/text()}</year></cd>";
      System.out.println(tester.query(queryString));
    } catch (Exception e) {
      e.printStackTrace(System.err);
      System.err.println(e.getMessage());
    }

この照会では、USレーベルで録音された 1981 年以降のCDがすべて選択され、リリース年別に配列され、返された各CDのタイトルとリリース年を含むXMLが返されます。ここで、いくつか注目すべきことがあります。

  • docName変数は、検索する文書を表します。このコードは、コマンド行を使用して、プログラムに提供される文書を変数に設定する処理を行う必要があります。
  • 各ノードの単一値が結果セットで返されるのではなく、XMLストリングが返されます。この XMLは、別の大きなXML文書にドロップすることが可能ですが、オンラインで表示することも、XSL プロセッサーに渡すこともできます。
  • ソース・ドキュメント内のXML要素名 (CD、TITLE、YEARなど) は結果セットで破棄され、cd、title、yearが新しいXML要素名として使用されます。

いずれもそれ自体は難しいものではなく、XQueryがXMLからデータを選択して返す際に柔軟性をもたらす別の方法です。

特筆すべき点は、XMLのストリングを結果セットとして返すときに返すストリングに含まれる変数を中括弧で囲まなければならないということです。

return <cd><title>{$cd/TITLE/text()}</title>" +
        " <year>{$cd/YEAR/text()}</year></cd>"

これらの中括弧により、XQueryプロセッサーでは、括弧で囲まれたデータはリテラル・テキストではなく、評価されて値に置換される変数と認識されます。

外部変数を宣言する

XQueryは、変数 $docNameを使用してその文書を表します。ただし、その変数を明示的に宣言し、使用する変数が外部の何か (この場合はJavaプログラム) によって定義されることをクエリーに通知しなければなりません。そのために、XQueryではdeclare構文を使用します。declare構文の書式を以下に示します。

declare variable [variable-name] as [variable-type] external;

この場合、[variable-name (変数名)] は $docNameです。[variable-type (変数タイプ) ] は、XMLスキーマの基本データ・タイプでなければなりません。ほとんどの場合、ストリングには xs:stringを、整数にはxs:intを使用します。ほかにも種類がいくつかありますが、これらが最も一般的です。

そのためXQueryTesterクラスの場合、以下のようにクエリーを変更する必要があります。

try {
      String xmlFilename = args[0];
      XQueryTester tester = new XQueryTester(xmlFilename);
      tester.init();

      final String sep = System.getProperty("line.separator");
      String queryString =
        "declare variable $docName as xs:string external;" + sep +
        "      for $cd in doc($docName)/CATALOG/CD " +
        "    where $cd/YEAR > 1980 " +
        "      and $cd/COUNTRY = 'USA' " +
        " order by $cd/YEAR " +
        "   return " +
        "<cd><title>{$cd/TITLE/text()}</title>" +
        " <year>{$cd/YEAR/text()}</year></cd>";
      System.out.println(tester.query(queryString));
    } catch (Exception e) {
      e.printStackTrace(System.err);
      System.err.println(e.getMessage());
    }

後は、このクエリーを実行する関数を作成するだけです。

XQueryを実行する

クエリーを実行するには、以下のステップを実行する必要があります。

  1. XQExpressionオブジェクトをXQConnectionから作成します。
  2. XQExpressionオブジェクトでbindXXX()メソッドを使用して、変数をクエリーにバインドします。
  3. クエリーを実行し、XQSequenceオブジェクトに結果を保存します。

ここで注意を要するのは、変数をバインドするステップだけです。この例では、プログラム名に渡されるファイル名と変数docNameを関連付ける必要があります。ストリング変数をバインドするので、bindStringを使用します。そのメソッドは、以下の3つの引数を取ります。

  1. javax.XML.namespace.QNameインスタンス (このクラスはJAXPパッケージに含まれています) と、XQueryでの変数の名前
  2. その変数にバインドする値
  3. 変数タイプに対応するアトミック・タイプ

これらをすべてまとめると、以下のようなメソッドになります。

public String query(String queryString) throws XQException {
  XQExpression expression = conn.createExpression();
  expression.bindString(new QName("docName"), filename,
    conn.createAtomicType(XQItemType.XQBASETYPE_STRING));
  XQSequence results = expression.executeQuery(queryString);
  return results.getSequenceAsString(new Properties());
}

最初の行 XQExpression expression = conn.createExpression(); では、式オブジェクトが新規作成されます。次に、照会する文書のファイル名が docName 変数 expression.bindString(new QName("docName"), filename, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)) にバインドされます。この時点で、QNameオブジェクトの機構を心配しすぎるのは無意味です。変数の名前 ($ 記号なしで) をXQueryに指定すればかまいません。次の引数はファイル名です。3番目はもう1つのほぼ一定した値、つまり、値が適合しているかを確認する必要があるタイプです。クエリーでは、変数が xs:string として定義されたため、タイプ XQItemType.XQBASETYPE_STRING が必要になります。

その一部は直感的ではありませんが、これらの大半はAPIドキュメントを検索するだけで済みます (リソースのリンクを参照してください)。bindInt()、bindFloat() など、その他の bindXXX() メソッドもすべて同様に機能します。

最後にクエリーを実行し、結果のシーケンスに戻ります。この結果セットを繰り返し適用できますが、さらに簡単な方法として、結果セットをストリングに変換し、長ストリングのデータとしてプログラムに処理させることも可能です。正にquery() メソッドで行われる処理ですが、これはquery() メソッドでは、呼び出し側プログラムでDataDirect XQuery API を認識する必要がないからです。

リスト5では、完成されたXQueryTesterコードを示しています。

リスト 5. 完成したXQueryTesterクラス
package ibm.dw.xqj;

import javax.xml.namespace.QName;
import java.util.Properties;

import com.ddtek.xquery3.XQConnection;
import com.ddtek.xquery3.XQException;
import com.ddtek.xquery3.XQExpression;
import com.ddtek.xquery3.XQItemType;
import com.ddtek.xquery3.XQSequence;
import com.ddtek.xquery3.xqj.DDXQDataSource;

public class XQueryTester {

  // Filename for XML document to query
  private String filename;

  // Data Source for querying
  private DDXQDataSource dataSource;

  // Connection for querying
  private XQConnection conn;

  public XQueryTester(String filename) {
    this.filename = filename;
  }

  public void init() throws XQException {
    dataSource = new DDXQDataSource();
    conn = dataSource.getConnection();
  }

  public String query(String queryString) throws XQException {
    XQExpression expression = conn.createExpression();
    expression.bindString(new QName("docName"), filename,
      conn.createAtomicType(XQItemType.XQBASETYPE_STRING));
    XQSequence results = expression.executeQuery(queryString);
    return results.getSequenceAsString(new Properties());
  }

  public static void main(String[] args) {
    if (args.length != 1) {
      System.err.println("Usage: java ibm.dw.xqj.XQueryTester [XML filename]");
      System.exit(-1);
    }

    try {
      String xmlFilename = args[0];
      XQueryTester tester = new XQueryTester(xmlFilename);
      tester.init();

      final String sep = System.getProperty("line.separator");
      String queryString =
        "declare variable $docName as xs:string external;" + sep +
        "      for $cd in doc($docName)/CATALOG/CD " +
        "    where $cd/YEAR > 1980 " +
        "      and $cd/COUNTRY = 'USA' " +
        " order by $cd/YEAR " +
        "   return " +
        "<cd><title>{$cd/TITLE/text()}</title>" +
        " <year>{$cd/YEAR/text()}</year></cd>";
      System.out.println(tester.query(queryString));
    } catch (Exception e) {
      e.printStackTrace(System.err);
      System.err.println(e.getMessage());
    }
  }
}

XQueryTesterを実行する

このプログラムをコンパイルし、以下のように実行します。

[bdm0509:~/Documents/developerworks/java_xquery] 
   java ibm.dw.xqj.XQueryTester cd_catalog.xml 
<cd><title>Greatest Hits</title><year>1982</year></cd><cd><title>
Empire Burlesque</title><year>1985</year></cd><cd><title>When a man loves a woman
</title><year>1987</year></cd><cd><title>The dock of the bay</title><year>
1987</year></cd><cd><title>Unchain my heart</title><year>1987</year></cd>
<cd><title>Big Willie style</title><year>1997</year></cd><cd><title>
1999 Grammy Nominees</title><year>1999</year></cd>

注意:オンライン記事の書式に合わせて、ここに改行を入れました。実際の結果は改行されておらず、すべてが1行になっています。

実験
このプログラムは、クエリーを処理し、XML文書にバインドするようにセットアップされています。ここで実際に照会ストリングを操作し、XQueryと、Javaプログラムからクエリーを実行する感覚を得られます。まず、CDの全選択を試した後、返された結果の書式を書式設定されたテキストに変更します。すべてのCD、すなわち最も新しいものから最も古いものまで、または価格が10ドル未満のCDをすべて返すことができるかどうかを確認します。このようなプログラムがあれば、クエリーを微調整し、実験することも、プログラムに送るXML文書を変更することさえも簡単です。

結論

XQueryの基本を理解せずにXQJについて語るのは容易ではなく、XPathを深く掘り下げずに XQueryについて語るのは不可能です。これらの異なった部分をすべて結び付けるのは、往々にして1つの部分 (クエリーを実行するJavaプログラム、またはクエリーを開始する文書における位置) のみを処理するということですが、その方法なら非常に簡単です。これは、洗練された強力なプログラムへの結合が可能な個々の単純な部分の集合としてJavaからXQueryを使用することについて考える最適な方法でもあります。

何種類かのテクノロジーを使いこなせるようになる必要があるため、2つのコンポーネントは同じままとし、3番目を実地に試します。したがって、Javaプログラムを同じに保ち、各種のクエリーや各種の入力文書を実際に操作してみます。次に、基本的なクエリーを使って、クエリーで宣言された入力変数をさらに多く使用する実験をしてみてください。XPathをreturnステートメントで試した後、検索の開始点で試してみたくなる場合もあります。各コンポーネントに親しむにつれて、クエリーがさらに複雑になり、Javaコーディングが一層強固になっていることがわかります。


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


関連トピック


コメント

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Information Management, XML, Java technology
ArticleID=377829
ArticleTitle=Java環境からXQueryを使用する
publish-date=12032010