SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

XQueryの制御構造の活用

XQueryの使い方の勘所

フィルタリング

 フィルタリングとは初期コレクションを絞り込んで重要なレコードだけを処理できるようにすることを意味します。たとえば、特定の部署(たとえば、"Materials")に所属する従業員レコードだけを取り出すものとします。もちろん、この操作は取得プロセスの中で実行できます。

let $employees :=  collection("/db/employees[division = 'Materials']")

 一方、これを2つの別のステップに分けると、設計とパフォーマンスの両面でメリットがあります。

let $employees :=  collection("/db/employees")
let $filtered-employees := for $employee in $employees[division = 'Materials']" return $employee

 XQuery WHEREコマンドも使用でき、これで評価の述部が分離されます。

let $employees :=  collection("/db/employees")
let $filtered-employees := for $employee in $employees
    where $employee[division = 'Materials'] 
    return $employee

 どちらが有利でしょうか? 述部([]内の式)が比較的小さく、自己完結的なときは、シーケンスでXPathを使用した方が通常は高速です。しかし、式が複雑なとき、複数の変数が含まれているとき、あるいは、この操作にSORT BYが付随するときは、WHERE句を使用した方が通常は効率的で、可読性も高くなります。理屈上は中心となるFLOWRの各演算子があれば何とかなるはずですが、それらの式を組み合わせるだけでは手に負えないこともかなりあります。そのような場合に、IF ... THEN ... ELSE構造を使用します。

if ($condExpr) then $resultExprTrue else $resultExprFalse

 この文のthenelseにはどちらにも暗黙のreturnが関連付けられており、IF文を用いてかなり複雑なスクリプトを作成できます。たとえば、テーブルに特定セクションの従業員をリストするとき、セクションに従業員がいなければ、ステータスメッセージを表示するものとします。このようなケースでIF...THEN...ELSE文は非常に役立ちます(リスト4を参照)。

リスト4 IF文を用いた複雑なスクリプトの作成
let $employees := doc('employees.xml')/employees/employee
let $divisions := ('Materials','AcctsPayable','Operations')
let $results := <html>
    <head>
         <title>Division Roster</title>
     </head>
     <body>
{
  for $division in $divisions return
    if (not(empty($employees[division = $division]))) then
        <div>
        <h2>{$division}</h2>
        <table>
            <tr>
                <th>Last Name</th>
                <th>First Name</th>
                <th>Title</th>
            </tr>
            {for $employee in $employees[division = $division] order by $employee/lastname ascending return
                <tr>

                    <td>{string($employee/lastname)}</td>
                    <td>{string($employee/firstname)}</td>

                    <td>{string($employee/title)}</td>
                </tr>
            }
       </table>
       </div>
   else <h2>There are no employees in the {string($division)} division.</h2>
    }
    </body>
</html>
return $results    

 条件文があって、その条件が真か偽のときだけ出力を得たい場合は、空のシーケンス(()と表記)を出力に使用します。

if ($cond) then $output else ()

 条件が偽と評価されれば、空のシーケンスが返され、これは空白の出力となります。

 IF...THEN...ELSE文を結果ブロック内に入れ子にすることもできますが、複数の文を入れ子にすると、式がかなり複雑になることがあります。変数$hの値に基づいて異なるヘッダースタイルを作成することを考えます(この変数は1~6の値を取るものとします)。埋め込みのIF文を使えば、次のようなスイッチを作成できます。

let $title := "This is a test."
let $result := if ($h = 1) then <h1>{$title}</h1>
else if ($h = 2) then <h2>{$title}</h2>
else if ($h = 3) then <h3>{$title}</h3>
else if ($h = 4) then <h4>{$title}</h4>
else if ($h = 5) then <h5>{$title}</h5>
else <h6>{$title}</h6>
return $result

 あるいは、element文を利用して要素を直接作成することもできます。

let $title := "This is a test."
let $result := element {concat("h",$h)}{$title}

 element文は、最初の式を新しく作成される要素の名前として扱い、次の括弧内の式を要素のコンテンツとして扱います。このようにXQueryでは、同じ作業をいくつもの異なる方法で実行できます。

 XQueryは複雑になりがちなので、case値に基づいて特定の出力を得られる次のようなswitch文を使いたいところです。

switch($expr){
    case $expr1:  $result1
    case $expr2: $result2
    default: $fallthruResult
     }

 しかし、XQueryには単純なスイッチはありません。XQueryに用意されているのは型スイッチであり、特定の変数のデータ型に基づいてアクションを実行できます。型スイッチのアイデアは、当初、XMLドキュメント内の要素を識別し、その要素に基づき何らかの処理を行う手段として考え出されました。

typeswitch($context)
    case $a as type1 return $expr1
    case $a as type2 return $expr2
    case $a as type3 return $expr3
    default $a return $fallthruResult

 この方法はやや直観的でないかもしれませんが、例を見ればわかるでしょう。従業員レコードの各ノードに特別なフォーマットを適用するものとします。これを行うために次のようなtypeswitch制御構造を使います。

<div>
{for $employee in doc("employees.xml")/employees/employee return 
  for $node in $employee/*
  return typeswitch($node)
    case $a as element (firstname) return <span>{string($a)} </span>
    case $a as element (lastname) return <span>{string($a)}</span>
    case $a as element (title) return <div>{string($a)}</div>
    case $a as element (division) return <div><b>{string($a)}</b></div>
    default $a return <div>{string($a)}</div>
}
</div>

 typeswitchの問題は、言語のエッジケースを解決するのには有効でも、文字列トークンに基づいて異なるパスを選択するような一般的な条件選択には向いていないことです。幸い、多少手を加えることで、typeswitchを従来のswitchに近づけることができます。これはXQueryの式を用いて一時要素にトークンを返すという手法です。これにより、従業員の所属する部門に基づいて、異なる出力を生成できます(リスト5を参照)。

リスト5 XQuery式の使用
<div>{
let $employees := doc("employees.xml")/employees/employee
let $output := for $employee in $employees return 
    let $division := element {string($employee/division)}{}
    return typeswitch($division)
 case $a as element (Materials) return
    <div>
          <h1 class="materials_title">Materials Section</h1>
          <div class="name">{concat($employee/firstname,' ',$employee/lastname)}</div>
          <div class="title">{string($employee/title)}</div>
          <div class="manager">{
              let $manager := $employees[@id = string($employee/supervisor)]
              return concat($manager/firstname,' ',$manager/lastname)
              }</div>
               </div>
 case $a as element (AcctsPayable) return
    <div>
          <h1 class="acctspayable_title">Accounts Payable</h1>
          <div class="name">{concat($employee/firstname,' ',$employee/lastname)}</div>
          <div class="title">{string($employee/title)}</div>
          <div class="manager">{
              let $manager := $employees[@id = string($employee/supervisor)]
              return concat($manager/firstname,' ',$manager/lastname)
              }</div>
               </div>
default return
    <div>
        <h1>Warning!</h1>
        <p>{concat($employee/firstname,' ',$employee/lastname)} is not in a known division</p>
               </div>
return $output
}</div>

 このルーチンで重要なのは次の1行です。

    let $division := element {string($employee/division)}{}

 式element {string($employee/division)}{}は一見すると暗号のようですが、1つ1つ分解すればすぐ理解できます。最初の{}内の式はMaterialsAcctsPayableというような部門の名前となります。次の空の{}内の式は要素が空であることを示します。これで<Materials /><AcctsPayable />という形式の要素が作成されます。この要素から、さまざまなケースに展開していくことができます。次に例を示します。

case $a as element (Materials) return
  <div>
    <h1 class="materials_title">Materials Section</h1>
    <div class="name">{concat($employee/firstname,' ',$employee/lastname)}</div>
    <div class="title">{string($employee/title)}</div>
    <div class="manager">{
      let $manager := $employees[@id = string($employee/supervisor)]
      return concat($manager/firstname,' ',$manager/lastname)
      }</div>
  </div>

 この出力は資材(material)の種類に応じて適切な形式に変換されます。一致するものがないデフォルトのケースでは、特定の従業員レコードに問題があることを示す警告メッセージが発せられます。このケースでは$a変数に部門要素のポインタが設定されますが、上の例ではこれは使われておらず、caseルーチンの単なるプレースホルダーとなっています。

 XQuery 1.1作業草案では、他の制御構造についても言及されています。たとえば、GROUP BY演算子は、グループ選択子で結果を集約できるようにします。また、WINDOW句は、特定のシーケンスのサブシーケンスに対して集合操作を容易に行えるようにします。さらにXQuery Scripting Extensions(XqueryScript)では、より本格的なスクリプティングロールの中でXQueryを簡単に使えるようにするための制御構造が提供されます。しかし、これらの草案はどちらもまだ開発途上にあり、現在のところ、これらの機能をサポートする商用またはオープンソースのXQuery実装は存在しません。

XQueryと制御構造

 制御構造は必ずしも魅力的なものではありません。実際、それらは鉄筋を支える型枠のようなものですが、建築における型枠がそうであるように、XQueryアプリケーションという構造物の必須の要素でもあります。これらの構造の操作方法を理解するかどうかで、実用的で柔軟性の高いアプリケーションを作成できるか、毎回書き直すしかない1回限りのコードばかりを作成するかが決まってきます。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Kurt Cagle(Kurt Cagle)

ライター、情報アーキテクト、XML News NetworkとMetaphorical Webのウェブマスター。カナダ、ブリティッシュコロンビア州のビクトリア在住。XMLToday.orgの編集長、O'Reilly Mediaの寄稿編集者。現在、XBRLに関する著書を執筆中。彼のTwitterはtw...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/3011 2008/09/18 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー