PROJECT KySS(プロジェクト・キッス)
四国のSOHO。薬師寺 国安(フリーWebプログラマ)と,薬師寺 聖(デザイナ,個人事業所自営,http://www.SeinDesign.net/)によるコラボレーション・ユニット。XMLに関する記事や著書多数。両名とも,Microsoft MVP for Windows Server System - XML (Oct 2003-Sep 2008) http://www.PROJECTKySS.NET

 前回は,LINQ to XMLの概要について説明した。今回から,実際にLINQクエリーのサンプル・コードを取り上げて解説していく。

 XMLデータ処理で避けて通れないのは,処理対象ノードの選択である。W3C仕様でいえば,XPathのロケーションパスの指定方法にあたるものだ。ロケーションパスが,DOM,XSLT,XQueryの中で用いられていることを考えれば,その必要性は自明である。

 本連載では,Visual Studio(VS)2005以降のユーザーに対し,XMLそのものについての基本知識はあるものとして解説を進めていく。したがって,VSの基本操作手順や,ノードや要素や属性といった最低限の用語は把握しておく必要がある。ただし,XML関連仕様(DOM,XPath,XSLT,XQueryなど)についてなじみのない読者は,その仕様についての解説部分はとばして読んでも差し支えない。XMLおよびXML関連仕様と実務での利用方法については,他の拙著を参照してほしい。『XML完全マスター』(発行 メディア・テック出版),『XML MASTERテキスト XMLマスター(ベーシック)』(共著,発行 ソフトバンクパブリッシング)など。

 今回の開発に使った筆者の環境は,Visual Studio Team System 2008である。本稿で取り上げるのはASP.NETプログラムで,開発言語はVisual Basic。ASP.NETでLINQを試すなら,無料で公開されているVisual Web Developer 2008 Express Editionが,マイクロソフトのWebサイトからダウンロードできる。

 なお,本記事で紹介するサンプル・プログラムのソース・ファイル(zipアーカイブ)は,下記からダウンロードできる。
 02LINQtoXML.zip

XML文書の読み込み

 まず,処理対象とするXML文書が必要なので,既存のXMLファイルを読み込んでおこう。作成したASP.NETプロジェクトの,ソリューションエクスプローラから,App_Dataフォルダ内に,リスト1のXMLファイル(udons_sampleData.xml)を追加する。文字コードはUTF-8だ。本稿で使用するXMLファイルはすべて,App_Dataフォルダ内に読み込んでおく必要がある。

<?xml version="1.0"?>
<商品情報>
    <商品>
        <品番>udon_01</品番>
        <品名>天ぷらうどん</品名>
        <セット内容>うどん、かけつゆ、えび天ぷら、あげ巻き</セット内容>
        <価格>3500</価格>
    </商品>
    <商品>
        <品番>udon_02</品番>
        <品名>かきあげうどん</品名>
        <セット内容>うどん、かけつゆ、季節のかきあげ、かまぼこ</セット内容>
        <価格>2800</価格>
    </商品>
    <商品>
        <品番>udon_03</品番>
        <品名>きつねうどん</品名>
        <セット内容>うどん、かけつゆ、味付け油あげ、かまぼこ</セット内容>
        <価格>1500</価格>
    </商品>
    <商品>~</商品>繰り返し
    <商品>
        <品番>udon_10</品番>
        <品名>カレーうどん</品名>
        <セット内容>うどん、かけつゆ、粉末カレー、きじ肉、野菜</セット内容>
        <価格>3000</価格>
    </商品>
</商品情報>
リスト1●フラットな定型の階層のサンプルXML文書(udons_sampleData.xml)

1.XDocumentとXElement

 最初に,XDocumentやXElementクラスのLoadメソッドを使い,XMLファイル名を指定して読み込む。それから,読み込んだ文書のルートノードを起点として,任意の要素や属性といった選択対象までの道のりを,パスで指定していく。

 その際,LINQ to XML軸の起点となるルートノードの扱い方が,XPathとは異なるので注意する必要がある(XSLTに詳しい人は,XSLTのdocument関数での参照先指定をイメージしてほしい)。System.Xml.Linq名前空間には,XDocumentとXElementという二つのクラスがあり,この二つのどちらを使うか,つまり「どのようにしてXML文書を読み込むか」によって,起点が異なるのである。

 XDocumentクラスは,その名の通り,XMLドキュメントを表す。XDocument.Loadで読み込んだ場合は,読み込んだXML文書のルート要素ノードは「ルートノードの子扱いになる」。ルートノードの子孫といえば,読み込んだXML文書のルート要素以下を指す。

 一方,XElementクラスは,XML要素を表す。XElement.Loadで読み込んだ場合は,読み込んだXML文書のルート要素ノードが「ルートノード扱いになる」。ルートノードの子孫とは,読み込んだXML文書のルート要素の子孫を指す。つまり,ルート要素自身は含まれない。

 起点が異なると,クエリーの記述方法も異なる。XMLデータの処理ではルート要素の子孫を処理するケースが多いので,XElementクラスを使うのが基本である。本稿でも基本的にXElement.Loadで読み込んでいる。

 XML文書(ファイル)を読み込む構文は,次の通りだ。

Dim returnValue As XDocument= XDocument.Load(XMLファイルのURI)
Dim returnValue As XElement= XElement.Load(XMLファイルのURI)

 先のリスト1を,それぞれの方法で読み込むと,結果は図1のようになる。上図がXDocument.Loadで読み込んで子要素のコレクションを取得した場合,下図がXElement.Loadで読み込んで子要素のコレクションを取得した場合だ。

図1●XDocumentクラスとXElementクラスを使ってリスト1を読み込んだ結果
図1●XDocumentクラスとXElementクラスを使ってリスト1を読み込んだ結果
[画像のクリックで拡大表示]

 このように,XDocumentクラスを使った場合,ルートノードの子要素のコレクションとは,ルート要素である<商品情報>以下になる。

 一方,XElementクラスを使った場合は,ルート要素<商品情報>がルート扱いになるので,子要素のコレクションとは<商品>要素以下となる。

 LINQクエリーのコードは,リスト2のように記述する。

リスト2●XDocumentクラスやXElementクラスを使ってリスト1を読み込むコード
リスト2●XDocumentクラスやXElementクラスを使ってリスト1を読み込むコード
[画像のクリックで拡大表示]

 (1)ImportsメソッドでSystem.LinqとSystem.Xml.Linq名前空間を読み込む。System.Linq名前空間は,LINQを使用するクエリーをサポートするクラスとインターフェイスを提供する。System.Xml.Linq名前空間は,LINQ to XMLでXMLを処理するクラスが含まれている。

 (2)Server.MapPathでサーバー上の物理パスを指定する。

 (3)次にApp_Dataフォルダ内に読み込んでおいた,udons_sampleData.xmlを読み込む。ここでは,XDocumentクラスのLoadメソッドを使用している。

 (4)IEnumerable (Of T)ジェネリック・インタフェースを使い,指定した型のコレクションに対する単純な反復処理を実行する。構文は,次の通りだ。

IEnumerable(Of 列挙するオブジェクトの型)

 xmldoc.Elementsクエリーで,読み込んだXML文書の要素を選択している。Elementsメソッドは,ドキュメントの子要素のコレクションをドキュメント順に返す。ここでは,XDocumentクラスで読み込んだXMLの要素に対してクエリーを発行しているので,ルート要素(<商品情報>)が含まれて選択される。なお,Elements(要素名)と記述した場合は,指定した要素のコレクションをドキュメント順に返す。この場合はXElement.LoadでXMLを読み込む。

 (5)クエリーを作成しただけでは,クエリーは実行されない。クエリー変数queryDoc内にクエリーの定義が格納されるだけだ。そこで,For Each~Inステートメントを使用してクエリーを実行し,TextBox1コントロール内にXML文書を表示している。このような処理を「遅延実行」という。構文は,次の通りだ。

For Each 反復変数 In オブジェクトコレクションまたは配列の名前
     ステートメント
Next

 「遅延実行」以外に,クエリーを定義するのと同時に実行される「即時実行」がある。これは,単一の値を返す標準クエリー演算子の使用によって強制的に行われる。詳しくは第4回で取り上げる。

 XSLTユーザーなら,For Each~Inステートメントは,<xsl:for each select=”処理対象ノード”></xsl:for-each>をイメージすればわかりやすいだろう。

 (6)TextBox2内に表示されるXML文書は,XElement.Loadメソッドで読み込んでいるため,ルート要素(<商品情報>)の子である<商品>要素以下となる。