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

 W3C DOMを使ってXMLツリーを生成するには,子孫要素の内容からルート要素までを生成しては追加していくというボトムアップ方式をとる。一方,LINQでは,XMLツリーそのままの直観的な処理が可能だ。今回は,いろいろなXMLツリー生成処理について見ていこう。

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

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

 03LINQtoXML.zip

生成処理

 本稿で生成するXMLツリーでは,プログラムコードとの区別をわかりやすくするため,日本語タグを使っている。文字コードはutf-8だ。

 まず,新規XMLツリーの生成手順から見ていこう。

1.1からのXMLツリーの生成

 LINQでは,宣言型の関数型構築(第1回リスト4参照)や,XMLリテラル(第1回リスト5参照)によって,ツリーを生成できる。次のようなコード(リスト1)に,要素名や要素の内容(データ)を当てはめるだけでよい。記述する位置や,カッコさえ間違えなければ,すぐに生成処理を実装できるだろう。

Dim xmldoc As XDocument = _
  New XDocument(New XDeclaration("1.0", "utf-8", "yes"), _
  New XElement("ルート要素名", New XElement("子要素名1", _
  New XElement("子要素名1の子要素名", “要素の内容”), _
  New XElement("子要素名1の子要素名", “要素の内容”))))
リスト1●3階層定型のXMLを生成する基本のコード

 リスト1を理解したうえで,2個の要素を持つ親要素が繰り返す,リスト2のような構造のXMLツリーを生成してみよう。

<商品情報>
  <商品>
    <品名>入力された品名データ</品名>
    <価格>入力された価格データ</価格>
  </商品>
  <商品>~</商品>繰り返して追加
</商品情報>
リスト2●<品名>と<価格>要素を持つ,<商品>要素が繰り返す構造

 ここでは,「品名」「単価」用の入力ボックスを2個用意し,データを入力して[OK]ボタンをクリックすると,ツリーが生成されるようにする。XML用のデータ入力と,生成したツリーを表示して確認する領域を設けるため,TextBoxコントロールを3個と,[OK]ボタン用のButtonコントロールを1個使っている。

 また,入力された価格を検証するため,CustomValidatorコントロールを,「うどん単価」のデータを入力するTextBox2コントロールの横にレイアウトしている。CustomValidatorコントロールは,標準の検証コントロールでは対応できない独自の検証ロジックを実装するためのコントロールだ。

 いくつかのデータを入力して生成したツリーは,図1のようになる。なお,本稿の目的はLINQクエリーの紹介にあるので,基本的に,表示結果とaspx.vbのコードのみ掲載して説明していく。

図1●入力されたデータからXMLツリーが生成され,TextBox内に表示されている
図1●入力されたデータからXMLツリーが生成され,TextBox内に表示されている

 今回のXML生成において,生成したXMLをTextBox3コントロール内に表示させるため,aspxファイル内の@PageディレクティブにvalidateRequest="false"を追加している。

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" validateRequest="false" %>

 この記述がなければ,セキュリティの関係で,TextBoxコントロールに大なり,小なり記号(>や<)の付いたデータが入力されると,危険なコードとみなされエラーが発生する。原則的にはセキュリティ上,validateRequest="false"を追加するのは避けるべきだが,今回のサンプルでは,生成したXMLを表示させるだけなので,validateRequest="false"を,あえて追加している。

 この処理のポイントは,新規生成の場合と,XMLツリーが生成済みの場合の条件分岐処理を書き分けることだ。一度XMLツリーが生成された後は,既存のXMLファイルを読み込んで,生成済みのルート要素以下に追加しなければならないからだ。

 ツリー生成処理のコードは,リスト3のようになる。一見複雑そうに見えるのは分岐処理を書いているからであって,生成処理自体は簡単だ。

リスト3●リスト1の構造のXMLを生成,順次データを追加可能とする処理
リスト3●リスト1の構造のXMLを生成,順次データを追加可能とする処理
[画像のクリックで拡大表示]

 (1)ImportsメソッドでSystem.IO名前空間を読み込む。この名前空間はファイルの入出力に関するクラスを提供する。(2)のFile.ExistsでApp_Dataフォルダ内に,任意のファイルが存在しているかどうかをチェックしているため,この名前空間の読み込みが必要になる。

 (2)App_Dataフォルダ内に,udons_makeXml.xmlというファイルが存在すれば,XDocument.Loadメソッドで,このファイルを読み込み,TextBox3コントロール内にXMLを表示する。

 (3)(6)で定義しているCustomValidatorの,単価が数値であるかどうかを検証するプロシジャで,ページの検証が成功した場合(入力されたデータが数値であった場合),単価を入力するTextBox2の内容(単価を入力するTextBox)が空ではなかった場合の処理を記述している。

 (4)生成されたXMLが表示されるTextBox3コントロール内が空であった場合,つまり,はじめてXMLが生成される場合は,XMLの宣言とルート要素(<商品情報>),ルート要素の子要素となる<商品>要素,それに各TextBoxに入力されたデータをもとに,<品名>と<価格>のXMLを新規に生成する(生成手順はリスト1の基本のコードを参照)。

 生成されたXMLをSaveメソッドで保存し,同時にTextBox3コントロールにも生成したXMLツリーを表示する。TextBox3に表示されるXMLツリーにはXML宣言は表示されないが,保存した場合は,きちんとXML宣言が付く。

 (5)TextBox3コントロールに,生成済みのXMLツリーが表示されている場合,つまり,App_Dataフォルダ内にudons_makeXml.xmlというファイルが存在する場合の処理。まず,XElement.LoadでXMLファイル(udons_makeXml.xml)を読み込む。

 New XElementで,<商品>要素と各TextBoxに入力されたデータを内容に,<品名>と<価格>要素を生成する。読み込んだXMLに,Addメソッドで,生成したXMLを追加する。追加されたXMLをSaveメソッドで保存し,同時にTextBox3コントロール内に表示する。

 ここで注意しなければならないのは,既存のXML文書を読み込んでデータを追加する場合は,XElement.Loadで読み込むことだ,XDocument.Loadでルート要素まで読み込んでしまうと,整形式ではない文書になってしまい,エラーが発生するので注意しよう。

 (6)この部分のコードはLINQとは直接関係はないが,CustomValidatorで利用する,価格に入力されたデータが数値かどうかを検証する処理だ。

 検証対象となる入力コントロールの値には,検証メソッドのパラメータとして渡されるServerValidateEventArgsオブジェクトのValueプロパティを介してアクセスする。e.Valueで,単価を入力するTextBox2コントロールに入力された値を取得できる。

 IsNumeric関数で数値かどうかを判別する。数値ならTrue,それ以外はFalseを返す。IsValidプロパティで検証が成功したかどうかを判別する。e.Isvalid=Trueなら検証は成功,e.IsValid=Falseなら検証は失敗となる。ここでは,CustomValidatorのコードとしてprice_checkプロシジャを定義している。

 このプロシジャをCustomValidatorのイベントに関連付けるには,価格を入力するTextBox2コントロールの横にレイアウトしたCustomValidatorコントロールを選択してプロパティを表示し,上方にある「稲妻」アイコンをクリックしてイベントを表示させる。ServerValidateイベントの横のドロップダウンリストをクリックすると,price_checkプロシジャの名前が表示されるので,これを選択する(図2表1)。

図2●CustomValidatorのServerValidateイベントの設定
図2●CustomValidatorのServerValidateイベントの設定

表1●CustomValidatorのプロパティ
プロパティ 説明
ControlToValidate 検証するコントロールのID TextBox2
ErrorMessage 検証が失敗したときに表示するエラー・メッセージ
Display 検証コントロールの表示方法 Dynamic
SetFocusOnError 検証が無効のとき,バリデータがコントロールにフォーカスを移すかどうか(TrueまたはFalse) True