Michael Champion
February 2007
日本語版最終更新日 2007 年 10 月 4 日
適用対象 :
Visual Studio 2008
.Net Framework 3.5
概要 : LINQ to XML は XML に対する統合言語クエリを考慮して開発され、標準クエリ演算子を活用し、XML に特化したクエリ拡張機能が導入されています。説明を簡潔にするため、このドキュメントでは、ほとんどのサンプルを C# で紹介しています。
目次
はじめに
サンプル XML
LINQ to XML を使用した XML のプログラミング
LINQ to XML のデザイン方式
LINQ to XML のクラス階層
XML 名
既存の XML の読み込み
ゼロからの XML の作成
XML のスキャン
XML の操作
属性の操作
XML ノードのその他の型の操作
ノードへのユーザー定義情報の注釈の追加
XML の出力
XML の検証
LINQ to XML による XML のクエリ
XML のクエリ
XML でのクエリ式の使用
LINQ to XML での XPath と XSLT の使用
XML とその他のデータ モデルの併用
データベースから XML への読み込み
XML の読み込みとデータベースの更新
LINQ to XML の階層化テクノロジ
Visual Basic 9.0 における LINQ to XML
スキーマ対応 XML プログラミング
参考資料
はじめに
Word ファイル、ネットワーク、構成ファイル、データベースなどのさまざまな領域におけるデータ書式化の基盤として、XML では実に多くの採択を行ってきました。現在では、XML はあらゆる場面で使用されています。一方、開発分野においては、XML には、いまだに扱いづらい部分があります。平均的なソフトウェア開発者に XML での作業を依頼すると、多くの場合、大きなため息が聞こえるでしょう。XML に対応した API を採用する場合、DOM のような古くて冗長な API か、習得するにはやる気と勉強と時間が必要な XQuery や XSLT などの XML 固有の API のどちらかを選択することになるようです。LINQ プロジェクトの一要素である LINQ to XML は、この問題に対処することを目的としています。LINQ to XML は、最新の .NET Framework の言語革新を利用するようにデザインされた、現代的なインメモリ XML プログラミング API です。LINQ to XML により、DOM と XQuery/XPath を合わせた機能性が、さまざまな LINQ 対応のデータ アクセス テクノロジで一貫したプログラミング体験として提供されます。
LINQ to XML について考え理解する場合、2 つの主要観点があります。1 つの観点からは、LINQ to XML は、LINQ プロジェクト ファミリの一要素で、XML 統合言語クエリと、オブジェクト、リレーショナル データベース (LINQ to SQL、LINQ to DataSet、LINQ to Entities)、および他のデータ アクセス テクノロジ (LINQ 対応になり次第) の一貫したクエリ表現を提供していると考えることができます。もう 1 つの観点からは、LINQ to XML は、デザインが見直された現代的なドキュメント オブジェクト モデル (DOM) XML プログラミング API に XPath と XSLT のいくつかの重要な機能を加えたものと同等の、完全装備のインメモリ XML プログラミング API と考えることができます。
LINQ to XML は、当初から XML に対する統合言語クエリを考慮して開発されました。LINQ to XMLで は標準クエリ演算子を利用して、XML に特化したクエリ拡張機能が導入されています。XML の観点からすると、LINQ to XML では LINQ パターンを実装する .NET Framework 言語 (C# や Visual Basic など) に統合された XQuery と XPath のクエリと変換の能力が提供されます。このため、LINQ 対応の API 全体で一貫性のあるクエリ表現が提供されるので、XML クエリと XML 変換を他のデータ ソースからのクエリと組み合わせることができます。LINQ to XML のクエリ機能の詳細については、「LINQ to XML による XML のクエリ」で説明します。
LINQ to XML の統合言語クエリと同様に、LINQ to XML の機能は新しい現代的なインメモリ XML プログラミング API を表します。LINQ to XML は、高速化と軽量化を実現しただけでなく、より明確かつ現代的な API になるようにデザインされました。LINQ to XML ではジェネリック型や Null 許容型などの現代的な言語機能が使用され、XML に対するプログラミングを簡略化するさまざまな革新技術が行われたことにより、DOM プログラミング モデルとは異なる方向へ進み始めました。統合言語クエリ機能を差し引いても、LINQ to XML は、XML プログラミングを大きく前進させたと言えます。次の「LINQ to XML を使用した XML のプログラミング」では、LINQ to XML におけるインメモリ XML プログラミング API について詳細に説明します。
LINQ to XML は、言語にとらわれない LINQ プロジェクトの一要素です。説明を簡潔にするため、このドキュメントではほとんどのサンプルを C# で紹介していますが、Visual Basic .NET コンパイラの LINQ 対応バージョンでも LINQ to XML を使用できます。「Visual Basic 9.0 における LINQ to XML」では、LINQ to XML を使用した Visual Basic 固有のプログラミングについて詳しく説明します。
サンプル XML
次に示す、XML 形式の簡単な連絡先一覧を作成しましょう。このサンプルは、このドキュメントを通じて使用します。
<contacts>
<contact>
<name>Patrick Hines</name>
<phone type="home">206-555-0144</phone>
<phone type="work">425-555-0145</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
<netWorth>10</netWorth>
</contact>
<contact>
<name>Gretchen Rivas</name>
<phone type="mobile">206-555-0163</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
<netWorth>11</netWorth>
</contact>
<contact>
<name>Scott MacDonald</name>
<phone type="home">925-555-0134</phone>
<phone type="mobile">425-555-0177</phone>
<address>
<street1>345 Stewart St</street1>
<city>Chatsworth</city>
<state>CA</state>
<postal>91746</postal>
</address>
<netWorth>500000</netWorth>
</contact>
</contacts>
LINQ to XML を使用した XML のプログラミング
ここでは、統合言語クエリにとらわれない LINQ to XML を使用してプログラムを作成する方法について詳しく説明します。LINQ to XML には完全装備のインメモリ XML プログラミング API が用意されているので、XML を読み取るときや操作するときに必要なすべての処理を行えます。次に、いくつかの例を示します。
- さまざまな方法 (ファイルや XmlReader など) で XML をメモリに読み込む。
- ゼロから XML ツリーを作成する。
- 新しい XML 要素をインメモリの XML ツリーに挿入する。
- インメモリの XML ツリーから XML 要素を削除する。
- さまざまな種類の出力 (ファイルや XmlWriter など) に XML を保存する。
このテクノロジを使用すると、XML プログラミングで生じるほとんどの作業を完了できます。
LINQ to XML のデザイン方式
LINQ to XML は、軽量な XML プログラミング API としてデザインされています。プログラミング モデルの使用方法がわかりやすく簡単であることを重視する概念の点においても、メモリとパフォーマンスの点においても、この考え方が適用されています。公開されている LINQ to XML のデータ モデルは、できる限り W3C XML Information Set に準拠するようにデザインされています。
重要な概念
ここでは、LINQ to XML を、他の XML プログラミング API (特に、現在広く使用されている XML プログラミング APIである W3C DOM) と区別するいくつかの重要な概念について説明します。
機能本位の構造
オブジェクト指向のプログラミングでオブジェクト グラフを作成する場合 (W3C DOM では XML ツリーの作成する場合)、XML ツリーは "下から上" の方式で作成されます。たとえば、XmlDocument (マイクロソフトの DOM 実装) を使用する場合、XML ツリーを作成する典型的な方法は次のようになります。
XmlDocument doc = new XmlDocument();
XmlElement name = doc.CreateElement("name");
name.InnerText = "Patrick Hines";
XmlElement phone1 = doc.CreateElement("phone");
phone1.SetAttribute("type", "home");
phone1.InnerText = "206-555-0144";
XmlElement phone2 = doc.CreateElement("phone");
phone2.SetAttribute("type", "work");
phone2.InnerText = "425-555-0145";
XmlElement street1 = doc.CreateElement("street1");
street1.InnerText = "123 Main St";
XmlElement city = doc.CreateElement("city");
city.InnerText = "Mercer Island";
XmlElement state = doc.CreateElement("state");
state.InnerText = "WA";
XmlElement postal = doc.CreateElement("postal");
postal.InnerText = "68042";
XmlElement address = doc.CreateElement("address");
address.AppendChild(street1);
address.AppendChild(city);
address.AppendChild(state);
address.AppendChild(postal);
XmlElement contact = doc.CreateElement("contact");
contact.AppendChild(name);
contact.AppendChild(phone1);
contact.AppendChild(phone2);
contact.AppendChild(address);
XmlElement contacts = doc.CreateElement("contacts");
contacts.AppendChild(contact);
doc.AppendChild(contacts);
このコーディングでは、XML ツリーの構造の手がかりは少ししか得られません。LINQ to XML では、XML ツリーを作成する方法として、このアプローチがサポートされますが、"機能本位の構造" と呼ばれる別の方法もサポートされています。LINQ to XML の機能本位の構造を使用して同じ XML ツリーを作成する方法を次に示します。
XElement contacts =
new XElement("contacts",CopyCode
new XElement("contact",
new XElement("name", "Patrick Hines"),
new XElement("phone", "206-555-0144",
new XAttribute("type", "home")),
new XElement("phone", "425-555-0145",
new XAttribute("type", "work")),
new XElement("address",
new XElement("street1", "123 Main St"),
new XElement("city", "Mercer Island"),
new XElement("state", "WA"),
new XElement("postal", "68042")
)
)
);
この XML ツリーを作成するコードでは、インデントを使用して (また、少し横目で見ると) 、基になる XML の構造を示しています。
機能本位の構造については、「ゼロからの XML の作成」で詳しく説明します。
ドキュメントにとらわれない
XML プログラミングでは、通常、XML 要素と属性に主眼を置きます。XML ツリーは、リーフ以外のレベルでは XML 要素で構成されており、XML を処理する主な目的は XML ツリーを構成する XML 要素のスキャンや操作なので、これは理にかなっています。LINQ to XML では、自然な形で XML を直接操作できます。たとえば、次のことを行えます。
- XML 要素を直接作成する (XML ドキュメントは一切使用しません)。
- XML 要素を、ファイル内の XML から読み込む。
- XML 要素をライタに保存 (記述) する。
これを、XML ドキュメントが XML ツリーの論理コンテナとして使用される W3C DOM と比較してみましょう。DOM では、XML ノードは、要素と属性を含め、XML ドキュメントのコンテキストで作成する必要があります。上記のコード例の name 要素を作成する部分の抜粋を次に示します。
XmlDocument doc = new XmlDocument();
XmlElement name = doc.CreateElement("name");
DOM では XML ドキュメントが必要不可欠な概念です。XML ノードは、XML ドキュメントのコンテキストで作成されます。ある要素を複数のドキュメントで使用する場合は、複数のドキュメントのノードをインポートする必要があります。これが、LINQ to XML で回避している不要な複雑さです。
LINQ to XML では、次のようにして直接 XML 要素を作成します。
XElement name = new XElement("name");
XML ツリーを保持するために XML ドキュメントを作成する必要がありません。LINQ to XML のオブジェクト モデルでは、XML ドキュメントの最上部にコメントや処理命令を追加する必要がある場合など、必要に応じて XML ドキュメントを使用できます。次の例では contacts というコンテンツと、XML 宣言、コメント、および処理命令を含んだ XML ドキュメントの作成方法を示しています。
XDocument contactsDoc =
new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("LINQ to XML Contacts XML Example"),
new XProcessingInstruction("MyApp", "123-44-4444"),
new XElement("contacts",
new XElement("contact",
new XElement("name", "Patrick Hines"),
new XElement("phone", "206-555-0144"),
new XElement("address",
new XElement("street1", "123 Main St"),
new XElement("city", "Mercer Island"),
new XElement("state", "WA"),
new XElement("postal", "68042")
)
)
)
);
このステートメントを使用した場合、contactsDoc のコンテンツは次のようになります。
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--LINQ to XML Contacts XML Example-->
<?MyApp 123-44-4444?>
<contacts>
<contact>
<name>Patrick Hines</name>
<phone>206-555-0144</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
</contact>
</contacts>
XML 名
LINQ to XML では、XML 名をできる限りわかりやすくするように特別な工夫がされています。XML の資料で上級者向けのトピックとして見なされることが多い XML 名の複雑さは、開発者がプログラミングで定期的に使用している名前空間によるのではなく、XML プレフィックスによることはほぼ間違いないでしょう。XML プレフィックスは、XML を入力するときに必要となるキー操作を減らしたり、XML を読みやすくしたりする場合には便利ですが、プレフィックスは完全な XML 名前空間を使用する代わりのショートカットにすぎません。LINQ to XML の入力時に、すべてのプレフィックスが、それぞれ対応する XML 名前空間へと解決され、プログラミング API にはプレフィックスは公開されません。LINQ to XML では、XName が、XNamespace オブジェクトとローカル名で構成される完全な XML 名を表します。通常、XNamespace は、開発者にとって名前空間 URI 文字列よりも使用しやすいオブジェクトです。
たとえば、http://mycompany.com という名前空間を使用する contacts という名前の XElement を作成するには、次のコードを使用できます。
XNamespace ns = "http://mycompany.com";
XElement contacts = new XElement(ns + "contacts");
反対に W3C DOM では、XML 名は API に対してさまざまな方法で公開されます。たとえば、XmlElement を作成するには、3 種類の方法で XML 名を指定できます。この 3 つのすべての方法で、プレフィックスを指定することができます。そのため、プレフィックス、名前空間、および名前空間の宣言 (プレフィックスを XML 名前空間に関連付ける xmlns 属性) を組み合わせると不明確になり、理解しづらい API となります。
LINQ to XML における XML 名前空間プレフィックスの扱いは、シリアル化のオプションにすぎません。XML の読み取り時に、すべてのプレフィックスが解決され、名前が付けられたすべての XML アイテムの名前は名前空間とローカル名を含んだ完全な展開された名前となります。出力時には XML 名前空間の宣言 (xmlns 属性) が受け取られ、適切なプレフィックスが表示されます。XML の出力でプレフィックスを適用する必要がある場合は、XML ツリーの適切な位置に xmlns 属性を追加できます。詳細については、「XML 名」を参照してください。
値としてのテキスト
通常、XML ツリーのリーフ要素には、文字列、整数、小数などの値が含まれます。これは属性の場合も同じです。LINQ to XML では、値を保持している要素および属性を、その値の型にキャストするだけで適切な形で扱えます。たとえば、name が string 型の値を保持する XElement であるとすると、次のことが行えます。
string nameString = (string) name;
これは、通常、子要素を直接参照するというコンテキストで次のように使用されます。
string name = (string) contact.Element("name");
明示的なキャスト演算子は、bool、bool?、int、int?、uint、uint?、long、long?、ulong、ulong?、float、float?、double、double?、decimal、decimal?、DateTime、DateTime?、TimeSpan、TimeSpan?、GUID、および GUID? です。
一方、W3C DOM では、テキストは常に XML ノードとして扱われます。そのため、多くの DOM の実装では、基になるリーフ ノードのテキストを読み取ったり処理したりするには、リーフ ノードのテキスト ノードの子を読み取る必要があります。たとえば、name 要素の値を読み取るだけでも、次のようなコードを記述する必要があります。
XmlNodeList children = name.ChildNodes;
string nameValue = "";
foreach (XmlText text in children) {
nameValue = nameValue + text.Value;
}
Console.WriteLine(nameValue);
マイクロソフトが提供する XmlDocument API などの一部の W3C DOM の実装では、InnerText メソッドを使用することによってこの点が簡略化されました。LINQ to XML には XText クラスがありますが、このクラスは混合コンテンツと CData セクションを処理できるようにするためだけのものです。このような XML の機能を使用しないアプリケーション開発者は、多くの場合テキスト ノードについて心配する必要はありません。通常、.NET Framework ベースの基本型は、直接読み取ったり、XML に直接追加したりすることができます。一般に、混合コンテンツや CData セクションを処理しない限り、XText ノードの存在を無視することが最良の方法です。
LINQ to XML のクラス階層
LINQ to XML で定義されている主要なクラスを図 1 に示します。
図 1. LINQ to XML のクラス階層
LINQ to XML のクラス階層では、次のことに注意してください。
- XElement はクラス階層の中で下位クラスに位置づけられていますが、LINQ to XML に必須のクラスです。通常、XML ツリーは、XElement のツリーで構成されます。XAttribute は、XElement に関連付けられた名前/値のペアです。XDocument は、DTD や最上位の XML 処理命令 (XProcessingInstruction) を保持する場合など、必要な場合にのみ作成されます。その他のすべての XNode は、XElement の下のリーフ ノード、またはルート レベルに存在する場合は、XDocument のリーフ ノードにしかなりません。
- XAttribute と XNode は同等のクラスであり、共通の基本クラスである XObject から派生しています。XML 属性は XML ツリーのノードではなく XML 要素に関連付けられた名前/値のペアでしかないので、XAttribute は XNode ではありません。この点を W3C DOM と比較してください。
- このバージョンの LINQ to XML では XText と XCData は公開されていますが、上記で説明したように、テキスト ノードを公開する必要がある場合を除き、これらは半隠蔽された実装と見なすことが最良の方法です。ユーザーは、要素または属性が保持しているテキスト値を文字列またはその他の単純な値として取得できます。
- 子を持つことができる XNode は XContainer のみです。つまり、XDocument または XElement のいずれかです。XDocument には、XElement (ルート要素)、XDeclaration、XDocumentType、または XProcessingInstruction を含めることができます。XElement には、さらに XElement、XComment、XProcessingInstruction、およびテキストを含めることができます (さまざまな形式で渡すことができますが、XML ツリーではテキストとして表現されます)。
XML 名
XML プログラミング API では複雑な話題として取り上げられることの多い XML 名は、LINQ to XML では簡単に表現されます。XML 名は XML 名前空間 URI をカプセル化する XNamespace オブジェクトとローカル名によって表現されます。XML 名前空間は、クラス名を一意に修飾できるという、.NET Framework ベースのプログラムにおける名前空間と同じ役割を果たします。これは、他のユーザーまたは組み込みの名前との競合の発生を回避するのに役立ちます。XML 名前空間を特定すれば、特定した名前空間内で一意であればよいローカル名を使用できます。たとえば、contacts という名前の XML 要素を作成するには、多くの場合、http://yourCompany.com/ContactList などの URI を使用して XNamespace 内に作成することになります。
XML 名のもう 1 つの側面は、XML 名前空間プレフィックスです。XML 名の複雑さのほとんどは、XML プレフィックスに起因します。XML の構文では、プレフィックスを使用することによって XML 名前空間のショートカットを作成でき、XML ドキュメントを簡潔にし理解しやすくしています。XML プレフィックスが意味を持つかどうかは、コンテキストに依存します。XML プレフィックスの myPrefix は、XML ツリーのある特定部分の特定の XML 名前空間に関連付けることができますが、XML ツリーの別の部分ではまったく異なる XML 名前空間に関連付けることができます。
LINQ to XML では、XML プログラミング API から XML プレフィックスを削除し、XNamespace オブジェクトにカプセル化することによって XML 名を簡略化しています。XML の読み込み時に、各 XML プレフィックスは対応する各 XML 名前空間へと解決されます。そのため、開発者が XML 名を操作する場合、完全に修飾された XML 名、つまり XML 名前空間とローカル名を操作することになります。
LINQ to XML では、XML 名を表すクラスは、XNamespace オブジェクトとローカル名で構成される XName です。たとえば、名前空間 http://mycompany.com を持つ contacts という XElement を作成するには、次のコードを使用できます。
XNamespace ns = "http://mycompany.com";
XElement contacts = new XElement(ns + "contacts");
XML 名は LINQ to XML API のどこにでも現れるので、XML 名が必要な場合には、必ず XName パラメータが見つかります。ただし、XName を直接操作することはほとんどありません。XName では、string からの暗黙的に変換が行われます。
文字列で表した XName は、"展開された名前" と呼ばれます。この名前は次のような形式です。
XML 名前空間が http://yourCompany.com で、ローカル名が contacts の展開された名前は次のようになります。
{http://myCompany.com}contacts
XName が必要な場合は、XNamespace オブジェクトを作成するのではなく、この拡張名の構文を使用することができます。たとえば、次に示すように、XElement のコンストラクタは最初の引数として XName を受け取ります。
XElement contacts = new XElement("{http://myCompany.com}contacts", … );
XML 名を使用するたびに XML 名前空間を入力する必要はありません。言語自体の機能を使用して、XML 名をより簡単に使用できます。たとえば、次の共通パターンを使用できます。
XNamespace myNs = "http://mycompany.com";
XElement contacts =
new XElement(myNs + "contacts",
new XElement(myNs + "contact",
new XElement(myNs + "name", "Patrick Hines"),
new XElement(myNs + "phone", "206-555-0144",
new XAttribute("type", "home")),
new XElement(myNs + "phone", "425-555-0145",
new XAttribute("type", "work")),
new XElement(myNs + "address",
new XElement(myNs + "street1", "123 Main St"),
new XElement(myNs + "city", "Mercer Island"),
new XElement(myNs + "state", "WA"),
new XElement(myNs + "postal", "68042")
)
)
);
結果として生成される XML は次のようになります。
<contacts xmlns="http://mycompany.com">
<contact>
<name>Patrick Hines</name>
<phone type="home">206-555-0144</phone>
<phone type="work">425-555-0145</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
</contact>
</contacts>
XML プレフィックスと出力
先ほど、XML の読み込み時に、プレフィックスは対応する XML 名前空間へと解決されると説明しましたが、出力時にはどうなるのでしょうか。XML の出力時にプレフィックスを適用したり、適用する必要がある場合はどうなるのでしょうか。出力時のプレフィックスの適用は、XML 名前空間にプレフィックスを関連付ける xmlns 属性 (XML 名前空間の宣言) を作成することによって行えます。次に例を示します。
XNamespace ns = "URI";
XElement e =
new XElement(ns + "e",
new XAttribute(XNamespace.Xmlns + "p", ns)
);
このコードからは、次のものが生成されます。
ただし、具体的な出力を考えている場合は、目的の位置に必要なプレフィックスを持つ XML 名前空間の宣言になるように XML を操作することができます。
既存の XML の読み込み
既存の XML を LINQ to XML の XML ツリーに読み込んで、その XML を読み取ったり操作したりすることができます。LINQ to XML では、ファイル、XmlReader、TextReader、string など、多数の入力ソースを使用できます。string を入力するには、Parse メソッドを使用します。Parse メソッドの例を次に示します。
XElement contacts = XElement.Parse(
@"<contacts>
<contact>
<name>Patrick Hines</name>
<phone type=""home"">206-555-0144</phone>
<phone type=""work"">425-555-0145</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
<netWorth>10</netWorth>
</contact>
<contact>
<name>Gretchen Rivas</name>
<phone type=""mobile"">206-555-0163</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
<netWorth>11</netWorth>
</contact>
<contact>
<name>Scott MacDonald</name>
<phone type="home">925-555-0134</phone>
<phone type="mobile">425-555-0177</phone>
<address>
<street1>345 Stewart St</street1>
<city>Chatsworth</city>
<state>CA</state>
<postal>91746</postal>
</address>
<netWorth>500000</netWorth>
</contact>
</contacts>");
他のソースから入力するには Load メソッドを使用します。たとえば、ファイルから XML を読み込むには次のようにします。
XElement contactsFromFile = XElement.Load(@"c:\myContactList.xml");
ゼロからの XML の作成
LINQ to XML には、XML 要素を作成するための強力な手法が用意されています。これは、機能本位の構造と呼ばれます。機能本位の構造を使用すると、1 つのステートメントで XML ツリー全体または XML ツリーの一部を作成することができます。たとえば、contacts という名前の XElement を作成するには、次のコードを使用できます。
XElement contacts =
new XElement("contacts",
new XElement("contact",
new XElement("name", "Patrick Hines"),
new XElement("phone", "206-555-0144"),
new XElement("address",
new XElement("street1", "123 Main St"),
new XElement("city", "Mercer Island"),
new XElement("state", "WA"),
new XElement("postal", "68042")
)
)
);
インデントを付けると、XElement コンストラクタは基になる XML の構造のようになります。機能本位の構造は、params オブジェクトを受け取る XElement コンストラクタによって使用できるようになります。
public XElement(XName name, params object[] contents)
contents は、XElement の正規の子であればどのような型もサポートする、きわめて柔軟なパラメータです。パラメータは、次のいずれかになります。
- string - テキスト コンテンツとして追加されます。これは文字列を要素の値として追加するのに推奨されるパターンです。LINQ to XML では、内部的な XText ノードが作成されます。
- XText - 文字列値または CData 値のいずれかを保持でき、子のコンテンツとして追加されます。多くの場合、CData 値に使用すると便利です。string は通常の文字列値よりも間単に使用できます。
- XElement - 子要素として追加されます。
- XAttribute - 属性として追加されます。
- XProcessingInstruction または XComment - 子コンテンツとして追加されます。
- IEnumerable - 列挙型です。これらのルールは再帰的に適用されます。
- その他 - ToString() が呼び出され、結果がテキスト コンテンツとして追加されます。
- null - 無視されます。
機能本位の構造を示す上記の例では、文字列 ("Patrick Hines") が name という名前の XElement コンストラクタに渡されます。これは、変数 (new XElement("name", custName) など) や、文字列以外の他の型 (new XElement("quantity", 55) など) にも、次のような関数呼び出しの結果にもできます。
{
...
XElement qty = new XElement("quantity", GetQuantity());
...
}
public int GetQuantity() { return 55; }
また、IEnumerable<XElement> にすることもできます。たとえば、一般的なシナリオとしては、コンストラクタ内でクエリを使用して内部的な XML を作成することなどが挙げられます。次のコードでは、Person オブジェクトの配列の連絡先を、新しい XML 要素の contacts に読み込んでいます。
class Person
{
public string Name;
public string[] PhoneNumbers;
}
var persons = new[] {
new Person {
Name = "Patrick Hines",
PhoneNumbers = new[] { "206-555-0144", "425-555-0145" }
},
new Person {
Name = "Gretchen Rivas",
PhoneNumbers = new[] { "206-555-0163" }
}
};
XElement contacts =
new XElement("contacts",
from p in persons
select new XElement("contact",
new XElement("name", p.Name),
from ph in p.PhoneNumbers
select new XElement("phone", ph)
)
);
Console.WriteLine(contacts);
出力は次のようになります。
<contacts>
<contact>
<name>Patrick Hines</name>
<phone>206-555-0144</phone>
<phone>425-555-0145</phone>
</contact>
<contact>
<name>Gretchen Rivas</name>
<phone>206-555-0163</phone>
</contact>
</contacts>
XML の内側の部分に注目してください。繰り返されている contact 要素と、各 contact 要素内で繰り返されている phone 要素は、IEnumerable を返すクエリによって生成されました。
プログラムの目的が XML 出力の作成である場合は、機能本位の構造により、結果を念頭に置いて作成を開始できます。機能本位の構造を使用すると、目的の出力ドキュメントの形式を決定し、XML アイテムのサブツリーをインラインで作成したり、そのような処理をする関数を呼び出したりすることができます。
機能本位の構造は、変換を行う際に役立ちます。変換については「XML 変換」で詳しく説明します。変換は、XML において重要な使用方法であり、機能本位の構造はこの作業に適しています。
XML のスキャン
XML をインメモリで使用できるようにする場合、通常、次の手順は、操作対象の XML 要素に移動することです。統合言語クエリには、これを行うための専用の強力なオプションが用意されています (「LINQ to XML による XML のクエリ」で説明しています)。ここでは、従来の方法に近い XML ツリーのスキャン方法を説明します。
XML 要素の子の取得
LINQ to XML には、XElement の子を取得するためのメソッドが用意されています。XElement (または XDocument) のすべての子を取得するには、Nodes() メソッドを使用します。テキストは LINQ to XML の他の型と共に使用できるので、このメソッドは IEnumerable<object> を返します。たとえば、次の XML を contact という名前の XElement に読み込むことができます。
<contact>
Met in 2005.
<name>Patrick Hines</name>
<phone>206-555-0144</phone>
<phone>425-555-0145</phone>
<!--Avoid whenever possible-->
</contact>
次のコードのように Nodes() を使用すると、すべての子を取得して、結果を出力できます。
foreach (c in contact.Nodes()) {
Console.WriteLine(c);
}
結果は、次のようにコンソールに表示されます。
Met in 2005.
<name>Patrick Hines</name>
<phone>206-555-0144</phone>
<phone>425-555-0145</phone>
<!--Avoid whenever possible-->
最初の子は "Met in 2005." という文字列、2 番目の子は name という名前の XElement、3 番目の子は最初の phone という名前の XElement、4 番目の子は 2 つ目の phone という名前の XElement、5 番目の子は "Avoid whenever possible" という値を持つ XComment です。XElement などの XNode で ToString() を使用すると、ノードの型に基づいて書式化された XML 文字列が返されます。これは、非常に便利なので、このドキュメントでは何度も使用しています。
より限定的にする場合は、特定の型の XElement のコンテンツ ノードを要求できます。たとえば、contact という名前の XElement のみについて子の XElement を取得することができます。この場合、次のようにして、パラメータ化された型を指定できます。
foreach (c in contact.Nodes().OfType<XElement>()) {
Console.WriteLine(c)
}
コンソールには、次のように、contact 要素の子のみが表示されます。
<name>Patrick Hines</name>
<phone>206-555-0144</phone>
<phone>425-555-0145</phone>
多くの XML のシナリオでは XML 要素は一般的かつ重要な要素なので、XML ツリー内の特定の XElement の直下にある XElement に移動するためのメソッドがあります。Elements() メソッドは、IEnumerable<XElement> を返す、Nodes().OfType<XElement>() のショートカットです。たとえば、contact 要素のすべての子を取得するには、次のようにします。
foreach (x in contact.Elements()) {
Console.WriteLine(x);
}
この場合も、子の XElement のみが出力されます。
<name>Patrick Hines</name>
<phone>206-555-0144</phone>
<phone>425-555-0145</phone>
特定の名前を持つすべての XElement を取得する場合は、XName をパラメータとして受け取る Elements(XName) オーバーロードを使用できます。たとえば、phone という名前の XElement のみを取得するには、次のようにできます。
foreach (x in contact.Elements("phone")) {
Console.WriteLine(x);
}
コンソールには、phone という名前のすべての XElement が出力されます。
<phone>206-555-0144</phone>
<phone>425-555-0145</phone>
特定の名前を持つ子要素が 1 つしかないことがわかっている場合は、単一の XElement を返す Element(XName) メソッド (単数形) を使用できます。この名前を持つ要素が複数ある場合は、最初の要素を取得することになります。たとえば、name という名前の XElement を取得するには、次のコードを使用できます。
XElement name = contact.Element("name");
または、次のようにして name の値を取得できます。
string name = (string) contact.Element("name");
Nodes()、Elements()、Elements(XName)、および Element(XName) は、XML を簡単にスキャンするための基本的なメソッドです。XPath に詳しい場合は、これらのメソッドを、それぞれ child::node()、child::*、child::name、および child::name[1] と似たものと考えることができます。「LINQ to XML を使用した XML のクエリ」で説明しているように、Descendants() や Ancestors() などの XML クエリ拡張機能は同様のスキャン処理に使用でき、多くの場合、基本的な操作メソッドと組み合わせて使用されます。
XML 要素の親とドキュメントの入手
XML ツリーを上方向にスキャンするには、XElement の Parent プロパティを使用できます。たとえば、phone という名前の XElement がある場合は、次のコードを使用して、関連付けられた contact を取得します。
XElement contact = phone.Parent;
ルート要素の Parent プロパティは null になります。他の XML API では関連付けられたドキュメントとなる場合もありますが、ここでは異なります。LINQ to XML では、XML ドキュメントは XML ツリーの一部とは見なされません。XElement (または任意の XNode) に関連付けられたドキュメントが必要な場合は、Document プロパティから取得できます。ドキュメントのルート要素として XElement を関連付ける必要がある場合は、その要素を XDocument コンストラクタに渡すか、そのルートを子要素としてドキュメントに追加することができます。たとえば、contactsDoc という名前の XDocument のルート要素として contacts という名前の XElement を作成するには、次のようにできます。
XDocument contactsDoc = new XDocument(contacts);
または
XDocument contactsDoc = new XDocument();
contactsDoc.Add(contacts);
XML の操作
LINQ to XML には、XML を操作するための完全なメソッド セットが用意されており、XML コンテンツの挿入、削除、コピー、および更新を行えます。
XML の挿入
既存の XML ツリーに簡単にコンテンツを追加できます。Add() メソッドを使用して phone という名前の別の XElement を追加するには、次のようにします。
XElement mobilePhone = new XElement("phone", "206-555-0168");
contact.Add(mobilePhone);
このコードでは、mobilePhone という名前の XElement はコンテンツの "最後の子" として追加されます。子の先頭に追加する場合は、AddFirst() メソッドを使用します。子を特定の場所に追加する場合は、AddBeforeSelf() または AddAfterSelf() を使用して目的の場所の前または後の子に移動します。たとえば、mobilePhone を 2 番目の phone 要素にするには、次のようにできます。
XElement mobilePhone = new XElement("phone", "206-555-0168");
XElement firstPhone = contact.Element("phone");
firstPhone.AddAfterSelf(mobilePhone);
Add メソッドは、XElement コンストラクタや XDocument (実際には XContainer) コンストラクタと同様に動作するので、機能本位の構造で簡単に完全な XML サブツリーを追加できます。たとえば、次のようにして address という名前の XElemnt を contact に追加できます。
XElement mobilePhone = new XElement("phone", "206-555-0168");
Console.WriteLine(mobilePhone.Parent); // null が出力されます。
Add メソッドを使用してこの子要素を親に追加すると、LINQ to XML によって子要素に親がないかどうかが確認され、親がない場合、LINQ to XML によって子要素の Parent プロパティに Add メソッドを呼び出した XElement が設定され、この子要素に親が設定されます。
contact.Add(mobilePhone);
Console.WriteLine(mobilePhone.Parent); // 連絡先が出力されます。
これは、ほとんどの XML ツリーの作成に共通するシナリオなので、きわめて重要かつ効果的なテクニックです。
mobilePhone を別の連絡先に追加するには、次のようにします。
contact2.Add(mobilePhone);
この場合も、LINQ to XML によって子要素に親があるかどうかが確認されます。今回は、子要素には既に親が設定されています。子に既に別の親が設定されている場合は、LINQ to XML によって目的の親の下に子要素の複製が作成されます。上記の例は、次に示すコードと同じことをしています。
contact2.Add(new XElement(mobilePhone));
XML の削除
XML を削除するには、削除するコンテンツに移動し、Remove() を呼び出します。たとえば、ある contact の最初の電話番号を削除するには、次のようにします。
contact.Element("phone").Remove();
Remove() は IEnumerable にも使用できるので、ある contact のすべての電話番号を 1 度の呼び出しで削除することができます。
contact.Elements("phone").Remove();
また、RemoveNodes() メソッドを使用して、ある XElement からすべてのコンテンツを削除することもできます。たとえば、次のステートメントを使用して、最初の contact の最初の address のコンテンツを削除できます。
contacts.Element("contact").Element("address").RemoveNodes();
要素を削除する方法には、SetElement を使用して要素を null に設定する方法もありますが、この方法については次の「XML の更新」で詳しく説明します。
XML の更新
XML を更新するには、置き換えるコンテンツを持つ XElement に移動した後、ReplaceNodes() メソッドを使用することができます。たとえば、ある contact の phone という名前の最初の XElement の電話番号を変更するには、次のようにできます。
contact.Element("phone").ReplaceNodes("425-555-0155");
ReplaceContent() を使用すると、XML サブツリーを更新することもできます。たとえば、ある address を更新するには、次のようにできます。
contact.Element("address").ReplaceContent(
new XElement("street", "123 Brown Lane"),
new XElement("city", "Redmond"),
new XElement("state", "WA"),
new XElement("country", "USA"),
new XElement("postalCode", "68072")
);
ReplaceContent() は汎用メソッドです。SetElement() は単純なコンテンツで動作するようにデザインされています。ReplaceContent() の呼び出しはその要素自体で行いますが、SetElement() では、その要素の親を操作します。たとえば、次のステートメントを使用すると、上記で説明したコードと同じように最初の電話番号を更新することができます。
contact.SetElement("phone", "425-555-0155");
結果は同じになります。電話番号がない場合は、phone という名前の XElement が contact の下に追加されます。たとえば、birthday を contact に追加する場合を考えます。birthday が既に存在する場合は更新し、birthday が存在しない場合は birthday を挿入するようにするには、次のようにします。
contact.SetElement("birthday", "12/12");
また、SetElement() で null 値を指定すると、その XElement は削除されます。次のステートメントを使用すると、birthday 要素を完全に削除できます。
contact.SetElement("birthday", null);
属性には、SetAttribute() という同様のメソッドを使用できます。このメソッドについては、「属性の操作」で説明します。
遅延クエリを実行する際の注意事項
XML を操作するときには、ほとんどのクエリ演算子が "遅延実行" (別称、緩やかな実行) に基づいて動作する、つまり、クエリはクエリの開始時にすべて解決されるのではなく、要求に応じて解決されるということに注意してください。たとえば、連絡先一覧のすべての phone 要素を削除する次のクエリを考えましょう。
// このようなことはしないでください。 NullReferenceException
foreach (var phone in contacts.Descendants("phone")) {
phone.Remove();
}
上記のクエリでは、反復処理が中断されるため、ツリーから削除される子孫は最初の phone のみです。この問題は、ToList() または ToArray() を使用してシーケンス全体が解決されるようにすることで解決できます。たとえば、次の方法は成功します。
foreach (var phone in contacts.Descendants("phone").ToList()) {
phone.Remove();
}
この方法では電話番号のリストが完全にキャッシュされるので、電話番号に対して反復処理や削除処理を行っても問題はありません。
クエリ拡張機能の Remove() は、遅延実行を使用せず、この ToList() を使用して削除対象の項目をキャッシュする、数少ない拡張メソッドの 1 つです。上記の例は、次のように記述することができます。
contacts.Descendants("phone").Remove();
データ操作と遅延クエリの実行を組み合わせた場合に発生する問題は、削除処理で最も顕著に現れますが、他の処理でもこの問題は発生します。いくつかのアドバイスを次に示します。
- このような、緩やかな評価とデータ処理間の複雑な反復処理は、LINQ to XML の "バグ" ではないことをご理解ください。これは、コンピュータ サイエンス分野の根本的な問題です (多くの場合、ハロウィーン問題と呼ばれます)。
- 一般に、最小限の機能に抑えるという LINQ to XML の考え方により、大規模な分析と最適化の機能が除外され、このような問題が回避されます。独自のアプリケーションに関しては、ハロウィーン問題を恐れずに、XML ドキュメントの特定の領域を操作する前に、その静的なコピーを作成することと、そのデータ操作によって、予想の難しい方法でクエリ結果の定義が変更される可能性があるという現実に慎重に対処することの間で、どのようにバランスを取るのかを決定する必要があります。
- データ操作ロジックをデザインするときは、インプレース更新ではなく、"機能本位の" 変換方法を使用することを検討してください。LINQ to XML の機能本位のコンストラクタを使用すると、入力ドキュメントの変形として定義された構造と値を持つ新しいドキュメントを動的に作成することは、非常に簡単です。イベント指向の API や XSLT を使用しなくても、LINQ to XML のみを使用して効果的な XML 変換パイプラインを作成できます。
属性の操作
XElement クラスと XAttribute クラスでは、実質的な操作は同じです。ただし、LINQ to XML のクラス階層では XElement と XAttribute は完全に区別されており、共通の基本クラスから発生しているわけではありません。これは、XML 属性が XML ツリーのノードではなく、XML 要素に関連付けられた順序のない名前と値のペアであるからです。LINQ to XML ではこのように区別されますが、実際の XAttribute の操作は XElement の操作と非常に似ています。XML 属性の性質を考えると、どこが異なるのかがわかります。
XML 属性の追加
XAttribute の追加は、XElement の追加と非常に似ています。サンプル XML の各電話番号には、自宅、仕事、携帯電話のいずれかを示す type 属性が含まれています。
<contacts>
<contact>
<name>Patrick Hines</name>
<phone type="home">206-555-0144</phone>
<phone type="work">425-555-0145</phone>
</contact>
...
XAttribute は、機能本位の作成を使用して、単純な型を持つ XElement を作成する場合と同じように作成します。機能本位の作成を使用して contact を作成するには、次のようにします。
XElement contact =
new XElement("contact",
new XElement("name", "Patrick Hines"),
new XElement("phone",
new XAttribute("type", "home"),
"206-555-0144"
),
new XElement("phone",
new XAttribute("type", "work"),
"425-555-0145"
)
);
SetElement を使用して単純型の要素の更新、追加、または削除を行うのと同じように、XElement で SetAttribute(XName, object) メソッドを使用して同じことを行えます。その属性が存在する場合、その属性は更新されます。その属性が存在しない場合、その属性は追加されます。object の値が null の場合、その属性は削除されます。
XML 属性の取得
XAttribute にアクセスする基本的な方法は、XElement で Attribute(XName) メソッドを使用する方法です。たとえば、type 属性を使用して連絡先の自宅電話番号を取得するには、次のようにします。
foreach (p in contact.Elements("phone")) {
if ((string)p.Attribute("type") == "home")
Console.Write("Home phone is: " + (string)p);
}
Attribute(XName) の動作は Element(XName) メソッドと非常に似ています。また、同一の明示的なキャスト演算子も存在します。この演算子を使用すると、XAttribute をさまざまな単純型にキャストできます (XElements と XAttributes から明示的にキャストできる型の一覧については、「値としてのテキスト」を参照してください)。
XML 属性の削除
属性を削除するには、Remove メソッドを使用するか、object の値として null を渡した SetAttribute(XName, object) を使用することができます。たとえば、Remove メソッドを使用して最初の phone 要素から type 属性を削除するには、次のようにします。
contact.Elements("phone").First().Attribute("type").Remove();
SetAttribute を使用する場合は、次のようにします。
contact.Elements("phone").First().SetAttribute("type", null);
XML ノードのその他の型の操作
LINQ to XML には、XML で使用される、さまざまな型の XML ノードが用意されています。これを説明するため、次のような、すべての XML ノード型を使用するドキュメントを作成できます。
XDocument xdoc =
new XDocument(
new XDeclaration("1.0", "UTF-8", "yes"),
new XProcessingInstruction("myApp", "My App Data"),
new XComment("My comment"),
new XElement("rootElement",
new XAttribute("myAttribute", "att"),
1234,
new XCData("Text with a <left> bracket"),
"mystring"
)
);
xdoc を出力すると、次の XML が生成されます。
<?xml version="1.0" standalone="yes"?>
<!--DOCTYPE-->
<?myApp My App Data?>
<!--My comment-->
<rootElement myAttribute="att">
1234<![CDATA[Text with a <left> bracket]]>mystring
</rootElement>
LINQ to XML では、XML 要素と XML 属性の操作が可能な限り簡単になりますが、他の XML ノード型も必要に応じて使用できます。
ノードへのユーザー定義情報の注釈の追加
LINQ to XML では、アプリケーション固有の一部の情報を XML ツリーの特定のノードに関連付けることができます。たとえば、要素の解析が開始されたソース ファイル内の行番号範囲、スキーマ検証後の要素の型、XML 情報がコピーされたデータ構造とそれを処理するためのメソッド (たとえば、データを持つ CLR の実際の請求オブジェクトや型を定義されたアプリケーション) などがあります。
LINQ to XML では、クラスのインスタンスに、それぞれ型の異なる 1 つまたは複数のオブジェクトで注釈を付けることができるメソッドを XContainer クラスで定義することで、このニーズに対応しています。XContainer オブジェクトの一連の注釈は、概念上は、キーが型、オブジェクト自体が値である辞書に似ています。
注釈を XElement オブジェクトまたは XDocument オブジェクトに追加するには、次のようにします。
XElement contact = new XElement(...);
LineNumberInfo linenum = new LineNumberInfo(...);
contact.AddAnnotation(linenum);
LineNumberInfo は、行番号情報を格納するためのアプリケーション定義のクラスです。注釈は、次のステートメントを使用して取得できます。
LineNumberInfo annotation = contact.Annotation<LineNumberInfo>();
指定された型の注釈が要素に存在しない場合、Annotation() メソッドは null を返します。次のステートメントを使用すると、注釈が削除されます。
contact.RemoveAnnotations<LineNumberInfo>();
注意点が 2 つあります。注釈の参照は型の ID に基づいています。そのため、インターフェイスや継承などは認識されません。たとえば、Person 型から派生した (または Person インターフェイスを実装する) Customer 型のオブジェクトを使用して注釈を追加する場合、GetAnnotation<Person>() の呼び出しでは、このオブジェクトは認識されません。そのため、XElement オブジェクトに注釈を付ける場合は、一意であることが確実な型のプライベート クラスのインスタンスを使用する必要があります。
XML の出力
XML を読み込むか、ゼロから作成し、さまざまな方法で操作した後は、おそらく XML を出力することが必要になります。XML は、XElement または XDocument でオーバーロードした Save() メソッドのいずれかを使用してさまざまな方法で出力できます。保存先として、ファイル、TextWriter、または XmlWriter を使用できます。たとえば、contacts という名前の XElement をファイルに保存するには、次のようにします。
contacts.Save(@"c:\contacts.xml");
XML の検証
System.Xml.Schema 名前空間の拡張メソッドによって、XElement ツリーを XML スキーマに基づいて検証できます。これは、この名前空間のクラスを "ブリッジ" のみを使用して LINQ to XML に公開するという点が、.NET 2.0 の機能とまったく同じです。
この機能を使用するには、次のステートメントを使用します。
XmlSchemaObject オブジェクトと XmlSchemaSet オブジェクトにデータを設定するには、.NET 2.0 のクラスとメソッドを使用します。スキーマに基づいて Validate()XElement オブジェクト、XAttribute オブジェクト、または XDocument オブジェクトで使用でき、オプションで注釈としてスキーマ検証後の情報セットを LINQ to XML ツリーに設定するメソッドがあります。
LINQ to XML による XML のクエリ
LINQ to XML とその他のインメモリ XML プログラミング API とを区別する大きな要因は、統合言語クエリです。統合言語クエリは、1 つのクエリで複数のデータ モデルを組み合わせる機能だけでなく、異なるデータ モデル間で一貫したクエリ表現を提供します。このセクションでは、XML に対して統合言語クエリを使用する方法について説明します。これ以降のセクションでは、データ モデル間での統合言語クエリのいくつかの使用例を紹介します。
標準クエリ演算子は、IEnumerable<T> の完全なクエリ言語を形成します。標準クエリ演算子は、IEnumerable<T> を実装するあらゆるオブジェクトの拡張メソッドとして表示され、他のメソッドと同様に呼び出すことができます。クエリ メソッドを直接呼び出すこの方法は、"明確なドット表記" と呼ばれることがあります。標準クエリ演算子に加え、次の 5 つの一般的なクエリ演算子のクエリ式があります。
- Where
- Select
- SelectMany
- OrderBy
- GroupBy
クエリ式は、基となる明確なドット表記に加えて、使いやすい層を提供します。それは、GetEnumerator() の呼び出しと while ループから成る foreach が使いやすいメカニズムであることと同様です。XML を操作する場合に、どちらの方法も役に立つと思うでしょう。明確なドット表記を適応させることで、XML の統合言語クエリの背後にある基本概念を得ることができ、クエリ式が処理をどのように簡略化するかを理解することができます。
XML のクエリ
統合言語クエリについての詳細な情報については、このドキュメントの「参考資料」の資料を確認することをお勧めします。このセクションでは、統合言語クエリを使用することを視野に入れて説明をし、XML のクエリ パターンに重点を置き、クエリ パターンの例を紹介します。
統合言語クエリと LINQ to XML の統合は、次の 3 つの点において明白です。
- 標準クエリ演算子の活用
- XML クエリの拡張機能の使用
- XML 変換の使用
1 つ目はデータ アクセス テクノロジに対応した他の統合言語クエリと同様で、一貫したクエリ表現の提供に貢献しています。2 つ目と 3 つ目は、XML 固有のクエリと変換機能を提供します。
標準クエリ演算子と XML
LINQ to XML では、一貫した方法で標準クエリ演算子を十分に活用し、IEnumerable インターフェイスを実装するコレクションを公開します。標準クエリ演算子の使用方法の詳細については、「.NET 標準クエリ演算子」 を参照してください。このセクションでは、標準クエリ演算子を使用する場合に発生することがある 2 つのシナリオを扱います。
1 つの Select で複数のピア ノードを作成する
XML に変換するときに、標準クエリ演算子 Select で単一の XElement を作成する場合は、予想通りに処理されます。しかし 1 つの Select で複数のピア要素を作成する必要がある場合はどうでしょうか。たとえば、連絡先一覧をフラット化する必要があり、個々の <contact> 要素ではなくルートの <contacts> 要素の直下に連絡先の情報を一覧する必要があるとすると、次のようになります。
<contacts>
<!-- contact -->
<name>Patrick Hines</name>
<phone type="home">206-555-0144</phone>
<phone type="work">425-555-0145</phone>
<address>
<address>
<state>WA</state>
</address>
</address>
<!-- contact -->
<name>Gretchen Rivas</name>
<address>
<address>
<state>WA</state>
</address>
</address>
<!-- contact -->
<name>Scott MacDonald</name>
<phone type="home">925-555-0134</phone>
<phone type="mobile">425-555-0177</phone>
<address>
<address>
<state>CA</state>
</address>
</address>
</contacts>
これを行うのに、次のクエリを使用できます。
new XElement("contacts",
from c in contacts.Elements("contact")
select new object[] {
new XComment("contact"),
new XElement("name", (string)c.Element("name")),
c.Elements("phone"),
new XElement("address", c.Element("address"))
}
);
contracts 要素の直下に配置される一連の子を作成するのに、配列初期化子を使用しました。
変換で Null を処理する
機能本位の構造を使用して XML の変換を記述していると、ある要素が省略可能で、その要素が存在しない場合は変換後の XML の一部を作成しないようにしたい状況があるでしょう。たとえば、次のクエリでは、名前と電話番号を取得し、ラップしている要素 <phoneNumbers> の下に電話番号を配置します。
new XElement("contacts",
from c in contacts.Elements("contact")
select new XElement("contact",
c.Element("name"),
new XElement("phoneNumbers", c.Elements("phone"))
)
);
連絡先に電話番号がない場合、ラップしている要素 phoneNumbers が存在しますが、子要素の phone はありません。次の例ではこのような状況を解決する方法を紹介しています。
new XElement("contacts",
from c in contacts.Elements("contact")
select new XElement("contact",
c.Element("name"),
c.Elements("phone").Any() ?
new XElement("phoneNumbers", c.Elements("phone")) :
null
)
);
機能本位の構造では Null を使用しても問題ありません。したがって、三項演算子のインライン (c.Elements("phone").Any() ? ... : null) を使用すると、連絡先に電話番号がない場合に phoneNumber を抑制することができます。クエリから関数を呼び出すことによって、三項演算子を使用しなくても同じ結果を導き出すことができます。
new XElement("contacts",
from c in contacts.Elements("contact")
select new XElement("contact",
c.Element("name"),
GetPhoneNumbers(c)
)
);
...
static XElement GetPhoneNumbers(XElement c) {
if (c.Elements("phone").Any())
return new XElement("phoneNumbers", c.Elements("phone"));
else
return null;
}
XML クエリ拡張機能
XML 固有のクエリ拡張機能は、XML ツリーのデータ構造で作業する場合に想定されるクエリ操作を提供します。このような XML 固有のクエリ拡張機能は XPath 軸に似ています。たとえば、Elements メソッドは XPath * (star) 演算子と同等です。次のセクションでは、それぞれの XML 固有のクエリ拡張機能について順に説明します。
要素とコンテンツ
Elements のクエリ演算子は、一連の XElements (IEnumerable<XElement>) の XElement ごとに子要素を返します。たとえば、連絡先一覧のすべての連絡先の子要素を取得するには、次のようにできます。
foreach (XElement x in contacts.Elements("contact").Elements())
{
Console.WriteLine(x);
}
この例の 2 つの Elements() メソッドでは同じ処理を行っていますが、これらのメソッドは異なります。1 つ目の Elements は XElement メソッドの Elements() を呼び出して、単一の contacts という名前の XElement で子要素を含んでいる IEnumerable<XObject> を返します。2 つ目の Elements() メソッドは IEnumerable<XObject> の拡張メソッドとして定義されています。2 つ目の Elements() メソッドは、一覧に含まれるすべての XElement の子要素のシーケンスを返します。上記の結果は次のようになります。
<name>Patrick Hines</name>
<phone type="home">206-555-0144</phone>
<phone type="work">425-555-0145</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
<netWorth>10</netWorth>
<name>Gretchen Rivas</name>
<phone type="mobile">206-232-4444</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
<netWorth>11</netWorth>
<name>Scott MacDonald</name>
<phone type="home">925-555-0134</phone>
<phone type="mobile">425-555-0177</phone>
<address>
<street1>345 Stewart St</street1>
<city>Chatsworth</city>
<state>CA</state>
<postal>92345</postal>
</address>
<netWorth>500000</netWorth>
特定の名前のすべての子を取得する場合は、Elements(XName) のオーバーロードを使用することができます。たとえば、次のようにします。
foreach (XElement x in contacts.Elements("contact").Elements("phone"))
{
Console.WriteLine(x);
}
上記のクエリの結果は次のようになります。
<phone>206-555-0144</phone>
<phone>425-555-0145</phone>
<phone>925-555-0134</phone>
<phone>425-555-0177</phone>
Descendants と Ancestors
Descendants と Ancestors のクエリ演算子を使用すると、XML ツリーを上方向と下方向でクエリすることができます。パラメータのない Descendants は、XElement のすべての子コンテンツを返します。この際、各子のコンテンツにはリーフ ノード (XML サブツリー) のコンテンツまでが含まれます。必要に応じて、XName (Descendants(XName)) を指定して、特定の名前のすべての子孫を取得できます。または、型 (Descendants<T>) を指定して、指定された LINQ to XML の型 (たとえば、XComment など) のすべての子孫を取得することができます。
連絡先一覧のすべての電話番号を取得するには、次のようにすることができます。
contacts.Descendants("phone");
Descendants と Ancestors では現在のノードは対象になりません。ルート要素で Descendants() を使用すると、ルート要素以外の XML ツリー全体を取得します。現在のノードを含める必要がある場合は、XName または型を指定できる DescendantsAndSelf を使用します。
Ancestors と AncestorsAndSelf は、Descendants と DescendantsAndSelf と同じように機能します。つまり Ancestors と AncestorsAndSelf は単に XML ツリーを下方向ではなく、上方向に処理します。たとえば、contacts の XML ツリーの 1 つ目の電話番号を取得して、その祖先を表示することができます。
XElement phone = contacts.Descendants("phone").First();
foreach (XElement a in phone.Ancestors()) {
Console.WriteLine(a.Name);
};
結果は次のようになります。
AncestorsAndSelf で同じ処理をすると、出力には phone も表示されます。
XElement phone = contacts.Descendants("phone").First();
foreach (XElement a in phone.AncestorsAndSelf()) {
Console.WriteLine(a.Name);
};
結果は次のようになります。
Descendants と Ancestors という XML クエリ拡張機能により、XML ツリーコードをスキャンするのに必要なコードを大幅に減少させることができます。XML ツリー内をすばやく移動するのに、Descendants と Ancestors を頻繁に使用することになるでしょう。
Attributes
XML クエリの拡張機能である Attributes は、IEnumerable<XElement> で呼び出され、一連の属性 (IEnumerable<XAttribute>) を返します。必要に応じて、XName を指定することができ、その名前の属性だけを返すことができます。たとえば、次のコードを使用して、連絡先一覧に含まれる電話番号の一意の型の一覧を取得することができます。
contacts.Descendants("phone").
Attributes("type").Select(t => t.Value).Distinct();
結果は次のようになります。
ElementsBeforeSelf、ElementsAfterSelf、NodesBeforeSelf、および NodeAfterSelf
特定の要素に位置している場合は、その特定の要素より前にあるすべての子要素や子コンテンツを取得したり、その特定の要素の後にあるすべての子要素や子コンテンツを取得したりする必要のある場合があります。クエリ拡張機能である ElementsBeforeSelf では、その要素の前にある兄弟要素を含んでいる IEnumerable<IElement> が返されます。ElementsAfterSelf では、その要素の後にある兄弟要素が返されます。NodesBeforeSelf クエリ拡張機能では、その要素の前にある任意の型 (たとえば、string、XComment、XElement など) の兄弟が返されます。そのため、NodesBeforeSelf では IEnumerable<XNode> が返されます。同様に、NodesAfterSelf では任意の型のその要素の後にある兄弟が返されます。
技術的な注意事項 : XML クエリの拡張機能
LINQ to XML 固有の拡張メソッドは XElementSequence クラスに含まれています。標準クエリ演算子が一般的に IEnumerable<T> の拡張メソッドとして定義されているのと同じように、XML クエリの演算子は一般的に IEnumerable<XElement> の拡張メソッドとして定義されています。XElementSequence はこのような拡張メソッドを保持するためのコンテナ クラスにすぎません。おそらく、このような静的メソッドの呼び出しに XElementSequence を使用することはありませんが、使用することは可能です。たとえば、連絡先一覧のすべての電話番号を取得する次のクエリについて考えてみましょう。
IEnumerable<XElement> phones =
contacts.Elements("contact").Elements("phone");
これは静的な拡張メソッド ElementsSequence の Elements(この IEnumerable<XElement> のソース, XName 名) を使用して次のように書き換えることができます。
IEnumerable<XElement> phones =
XElementSequence.Elements(contacts.Elements("contact"), "phone");
クエリ拡張機能の技術面の詳細については、「C# 3.0 の概要」 ドキュメント (「参考資料」参照) で調べることができます。
XML 変換
XML の変換は、重要な XML 使用シナリオです。XML の変換はとても重要なので、2 つの主な XML 技術である XQuery と XSLT でも重要な機能となっています。XSLT は LINQ to XML で利用できますが、あらゆる種類の入力データを変換する "正真正銘の" LINQ 方法は、機能本位の構造を使用することです。XML ドキュメントへの変換のほとんどは、対象の XML を機能本位に構成していると言えます。つまり、結果を想定して作業を開始することができ、必要に応じて、クエリや関数を組み合わせて使用して、目的の XML を形成し、XML ブロックを書き込むことができます。
たとえば、連絡先一覧を顧客リストの形式に変換する必要がある場合があります。結果を想定して処理を開始すると、顧客リストを次のようにする必要があります。
<Customers>
<Customer>
<Name>Patrick Hines</Name>
<PhoneNumbers>
<Phone type="home">206-555-0144</Phone>
<Phone type="work">425-555-0145</Phone>
</PhoneNumbers>
</Customer>
</Customers>
機能本位の構造を使用してこの XML を作成すると、次のようになります。
new XElement("Customers",
new XElement("Customer",
new XElement("Name", "Patrick Hines"),
new XElement("PhoneNumbers",
new XElement("Phone",
new XAttribute("type", "home"),
"206-232-2222"),
new XElement("Phone",
new XAttribute("type", "work"),
"425-555-0145")
)
)
);
連絡先一覧を上記の新しい形式に変換するには、次のようにします。
new XElement("Customers",
from c in contacts.Elements("contact")
select new XElement("Customer",
new XElement("Name", (string) c.Element("name")),
new XElement("PhoneNumbers",
from ph in c.Elements("phone")
select new XElement("phone", (string) ph,
ph.Attribute("type")
)
)
)
);
変換では対象のドキュメントの構造に準拠しています。まず、対象 XML の外部、ルート要素を作成します。
new XElement("Customers", ...
元の XML のすべての contact と対応する Customer XElement を作成する必要があります。この操作を行うには、contact ごとに必要なものを選択する必要があるので、contacts の下にあるすべての contact 要素を取得します。
... from c in contacts.Elements("contact")...
Select は、contact ごとに実行されるその他の機能本位の構造ブロックを開始します。
select new XElement("Customer",
次に対象の XML の <Customer> 部分を構成します。まず、Customer XElement を作成します。
select new XElement("Customer",
new XElement("Name", (string) c.Element("name")),
連絡先一覧の電話番号は直接連絡先の直下に表示されているので、<PhoneNumbers> の子はより複雑です。
<contact><phone>...</phone><phone>...</phone></contact>
この処理を完了するには、contact の電話番号をクエリし、<PhoneNumbers> 要素の子として配置します。
...
new XElement("PhoneNumbers",
from ph in c.Elements("phone")
select new XElement("phone", (string) ph,
ph.Attribute("type")
)
)
このコードでは、phone ごとの連絡先の電話番号、c.Elements("phone") をクエリします。また、元の phone と同じ type 属性と同じ値を持つ Phone という名前の新しい XElement も作成します。
多くの場合、変換処理の一部を担う機能を使用して、変換処理を簡略化する必要があります。たとえば、上記の変換処理を、変換を分割するより多くの関数を使用したものに書き直すことができます。自分のデザインの感覚に基づいて、大きく複雑な機能を分割するかしないかを決めるのと同様に、変換を分割するかどいうかを決めるのは完全に自由です。複雑な機能を分割する方法の 1 つに、次のようなものがあります。
new XElement("Customers", GetCustomers(contacts));
static IEnumerable<XElement> GetCustomers(XElement contacts) {
return from c in contacts.Elements("contact")
select FormatCustomer(c);
}
static XElement FormatCustomer(XElement c) {
return new XElement("Customer",
new XElement("Name", (string) c.Element("name"),
GetPhoneNumbers(c)));
}
static XElement GetPhoneNumbers(XElement c) {
return !c.Elements("phone").Any() ? null :
new XElement("PhoneNumbers",
from ph in c.Elements("phone")
select new XElement("Phone",
ph.Attribute("type"),
(string) ph)
);
}
この例では、.NET Framework の統合言語クエリの比較的単純な変換能力を紹介しました。機能本位の構造と関数呼び出しを含められる機能を使用すると、単一のクエリまたは変換で複雑なドキュメントを作成することができます。XML と同様に、さまざまなデータ ソースのデータを簡単に含めることができます。
XML でのクエリ式の使用
LINQ to XML がクエリ式と連携する方法は他と変わらないので、ここでは、参照ドキュメントに記載されている情報は割愛します。次に、LINQ to XML でクエリ式を使用している、いくつかの簡単な例を示します。
このクエリは、ワシントンのすべての連絡先を取得し、名前順に並べます。それから、そのすべての連絡先を string として返します (このクエリの結果は IEnumerable<string> です)。
from c in contacts.Elements("contact")
where (string) c.Element("address").Element("state") == "WA"
orderby (string) c.Element("name")
select (string) c.Element("name");
このクエリは、市外局番が 206 のワシントンの連絡先を取得し、名前順に並べ替えます。このクエリの結果は IEnumerable<XElement> です。
from c in contacts.Elements("contact"),
ph in c.Elements("phone")
where (string) c.Element("address").Element("state") == "WA" &&
ph.Value.StartsWith("206")
orderby (string) c.Element("name")
select c;
次のクエリは、平均以上の純資産を持っている連絡先を取得する例です。
from c in contacts.Elements("contact"),
average = contacts.Elements("contact").
Average(x => (int) x.Element("netWorth"))
where (int) c.Element("netWorth") > average
select c;
LINQ to XML での XPath と XSLT の使用
LINQ to XML は、XPath と XSLT を含む System.Xml 名前空間で、既存の機能と連携することができる一連の "ブリッジ クラス" をサポートしています。
注 System.Xml は、.NET Framework 3.5 で導入されたこのような仕様の 1.0 バージョンのみをサポートしています。
XPath をサポートしている拡張メソッドは、System.Xml.XPath 名前空間を参照することによって使用できるようになります。
このコードは、XPathNavigator オブジェクトを作成する CreateNavigator オーバーロード、XPath 式を評価する XPathEvaluate オーバーロード、および System.Xml DOM API の SelectSingleNode メソッドや XPatheXelectNodes メソッドによく似た機能をする XPathSelectElement[s] のオーバーロードをスコープ内に入れます。名前空間で修飾された XPath 式を使用するには、DOM と同様に NamespaceResolver オブジェクトを渡す必要があります。
たとえば、phone という名前のすべての要素を表示するには、次のコードを使用します。
foreach (var phone in contacts.XPathSelectElements("//phone"))
Console.WriteLine(phone);
同様に、XSLT も System.Xml.Xsl 名前空間を参照することで使用できるようになります。
これにより、XDocumentCreateNavigator() を使用して XPathNavigator を作成し、Transform() メソッドに渡すことができます。
XML とその他のデータ モデルの併用
統合言語クエリは、標準クエリ演算子とラムダ式の使用によって、異なるデータ モデル間で一貫したクエリ表現を提供します。また、単一のクエリ内で、統合言語クエリ対応のデータ モデルや API を組み合わせる機能も提供します。このセクションでは、Northwind サンプル データベースを使用して、リレーショナル データと XML を結合する一般的なシナリオの簡単な例を 2 つ紹介します。
データベースから XML への読み込み
次に、LINQ to SQL を使用して Northwind データベースから、ロンドンの顧客を取得するためにデータを読み取り、取得したデータを XML に変換する簡単な例を示します。
XElement londonCustomers =
new XElement("Customers",
from c in db.Customers
where c.City == "London"
select new XElement("Customer",
new XAttribute("CustomerID", c.CustomerID),
new XElement("Name", c.ContactName),
new XElement("Phone", c.Phone)
)
);
Console.WriteLine(londonCustomers);
結果の XML 出力は次のようになります。
<Customers>
<Customer CustomerID="AROUT">
<Name>Mark Harrington</Name>
<Phone>(171) 555-0188</Phone>
</Customer>
<Customer CustomerID="BSBEV">
<Name>Michelle Alexander</Name>
<Phone>(171) 555-0112</Phone>
</Customer>
<Customer CustomerID="CONSH">
<Name>Nicole Holliday</Name>
<Phone>(171) 555-0182</Phone>
</Customer>
<Customer CustomerID="EASTC">
<Name>Kim Ralls</Name>
<Phone>(171) 555-0197</Phone>
</Customer>
<Customer CustomerID="NORTS">
<Name>Scott Culp</Name>
<Phone>(171) 555-0173</Phone>
</Customer>
<Customer CustomerID="SEVES">
<Name>Deepak Kumar</Name>
<Phone>(171) 555-0117</Phone>
</Customer>
</Customers>
XML の読み込みとデータベースの更新
また、XML を読み込み、その情報をデータベースに格納することができます。この例では、XML 形式で顧客データの更新内容を取得するとします。説明を簡単にするために、更新レコードには電話番号の変更だけが含まれています。
次に XML のサンプルを示します。
<customerUpdates>
<customerUpdate>
<custid>ALFKI</custid>
<phone>206-555-0103</phone>
</customerUpdate>
<customerUpdate>
<custid>EASTC</custid>
<phone>425-555-0143</phone>
</customerUpdate>
</customerUpdates>
この更新を完了するために、各 customerUpdate 要素をクエリし、データベースを呼び出して対応する Customer レコードを取得します。それから、Customer 列のデータを新しい電話番号で更新します。
foreach (var cu in customerUpdates.Elements("customerUpdate")) {
Customer cust = db.Customers.
First(c => c.CustomerID == (string)cu.Element("custid"));
cust.Phone = (string)cu.Element("phone");
}
db.SubmitChanges();
ここで紹介した例は、データ モデル間で統合言語クエリを使用してできることのほんの一例です。LINQ to SQL の使用例の詳細については、LINQ to SQL の概要ドキュメントを参照してください (「参考資料」参照)。
LINQ to XML の階層化テクノロジ
LINQ to XML という XML プログラミング API は、さまざまな階層化テクノロジの基盤になります。このセクションでは、このようなテクノロジのうち 2 つのテクノロジについて説明します。
Visual Basic 9.0 における LINQ to XML
Visual Basic 9.0 では、LINQ to XML の十分なサポートを提供します。Visual Basic 9.0 では、メソッドを使用して XML の作成や移動を行う代わりに、作成には "XML リテラル" を、移動には "XML 軸プロパティ" を使用します。これは重要な特徴で、Visual Basic の設計目的に近づいています。XML リテラルを使用すると、Visual Basic 開発者は使い慣れた XML 構文を使用して XDocument や XElement などの LINQ to XML オブジェクトを直接作成できます。これらのオブジェクトに含まれる値は、式の評価や変数の置換を使用して作成できます。XML 軸プロパティを使用すると、開発者は、メソッド呼び出しで間接的に XML ノードにアクセスするのではなく、XML 軸と要素名または属性名を含む特別な構文を使って、XML ノードに直接アクセスできます。この 2 つの機能は、Visual Basic での XMLプログラミングおよび LINQ to XML プログラミングに対する、明確で、使いやすく、強力かつ十分なサポートを提供します。
XML リテラル
このドキュメントの最初の例 (「機能本位の構造」参照) をもう一度使用しましょう。ただし、今回は Visual Basic で記述されています。この構文は、既存の C# 構文に非常によく似ています。
Dim contacts As XElement = _
New XElement("contacts", _
New XElement("contact", _
New XElement("name", "Patrick Hines"), _
New XElement("phone", "206-555-0144", _
New XAttribute("type", "home")), _
New XElement("phone", "425-555-0145", _
New XAttribute("type", "work")), _
New XElement("address", _
New XElement("street1", "123 Main St"), _
New XElement("city", "Mercer Island"), _
New XElement("state", "WA"), _
New XElement("postal", "98040"))))
上記の Visual Basic ステートメントでは、従来の API アプローチを使用して、変数 contacts の値を初期化し、XElement 型の新しいオブジェクトを作成しています。Visual Basic では、新しいオブジェクトを作成するのに、LINQ to XML API の呼び出しよりもさらに高度な手段を利用できます。この方法では、実際の XML 構文を使ってインラインで XML を記述できます。
Dim contacts As XElement = _
<contacts>
<contact>
<name>Patrick Hines</name>
<phone type="home">206-555-0144</phone>
<phone type="work">425-555-0145</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>98040</postal>
</address>
</contact>
</contacts>
結果である XElement の XML 構造が明確なので、Visual Basic のコードを簡単に読んだり管理したりすることができます。Visual Basic コンパイラでは、ステートメントの右側の XML リテラルが適切な LINQ to XML API の呼び出しに変換され、最初の例とまったく同じコードが生成されます。これにより、Visual Basic と LINQ to XML を利用する他の言語との間に、完全な相互運用性が保障されます。
XML リテラルに行継続文字は必要ありません。この性質により、開発者は任意の XML ソース ドキュメントから、または XML ソース ドキュメントに対して、XML のコピーと貼り付けを行うことができます。
もう 1 つの例では同じ contact オブジェクトを作成しますが、実際の XML 構文ではなく変数を使用します。Visual Basic では、XML リテラルに式を埋め込んで、実行時に XML 値を作成できます。たとえば、連絡先が MyName という名前の変数に格納されているとします。この場合、次のように記述できます。
Dim myName = "Patrick Hines"
Dim contact As XElement = <contact>
<name><%=myName %></name>
</contact>
ASP.NET に慣れた開発者であれば、すぐに <%= と %> の構文に気が付くでしょう。この構文は Visual Basic の式をかっこで囲むために使用され、式の値は要素コンテンツになります。MyName などの変数値の置換は、1 つの例にすぎません。かっこで囲まれた式は、データベース参照、配列のアクセス、文字列など有効な要素コンテンツになる型を返すライブラリ関数の呼び出し、XElement のリストなどに簡単に変更できます。
XML 構文の山かっこ内でも、同じ埋め込み構文を使用します。次の例では、type 属性の値を式で設定しています。
Dim phoneType = IIf(i = 1, "home", "work")
Dim contact = <contact>
<phone type=<%= phoneType %>>206-555-0144</phone>
</contact>
同様に、要素名を式から計算することもできます。
Dim MyName = "Patrick Hines"
Dim elementName = "contact"
Dim contact As XElement = <<%=elementName %>>
<name><%= MyName %></name>
</>
要素の終了記号として、</> を使用できます。特に要素名が計算されている場合、この機能は非常に便利です。
XML 軸プロパティ
Visual Basic 9.0 では、XML の作成に XML リテラルを使用するだけでなく、XElement 型や XDocument 型に使用できる XML 軸プロパティにより、簡単に XML 構造のアクセスや移動を実行できます。つまり、明示的にメソッドを呼び出して要素や属性の移動や検出を行う代わりに、XML 軸プロパティを LINQ to XML オブジェクト プロパティとして使用できます。次に例を示します。
- 子軸の contact.<phone> を使用して、contact 要素からすべての phone 要素を取得します。
- 属性軸の phone.@type を使用して、phone 要素の type 属性から文字列の値を取得します。
- 子孫軸の contact...<city> (ソース コードでは、このとおり 3 つのピリオドを記述します) を使用して、階層の深さに関係なく、contact 要素のすべての city 子要素を取得します。
- Value 拡張プロパティを使用して、XML 軸プロパティから返される IEnumerable の最初のオブジェクトの文字列値を取得します。
- IEnumerable(Of T) で拡張インデクサを使用して、結果のシーケンスの最初の要素を選択します。
上記の新しい技術をすべて合わせて、コードを簡略化しました。たとえば、電話の種類と連絡先の都市を表示するコードは次のようになります。
For Each phone In contact.<phone>
Console.WriteLine(phone.@type)
Next
Console.WriteLine(contacts...<city>.Value)
コンパイラでは、対象式が XElement 型、XDocument 型、またはこれらの型のコレクションの場合に、XML に対する XML 軸プロパティの使用が認識されます。
コンパイラでは XML 軸プロパティが次のように変換されます。
- 子軸の式 contact.<phone> は、未処理の LINQ to XML 呼び出しである contact.Elements("phone") に変換されます。contact.Elements("phone") は contact 要素の phone という名前のすべての子要素のコレクションを返します。
- 属性軸の式 phone.@type は phone.Attributes("type").Value に変換されます。phone.Attributes("type").Value は、type という名前の属性の文字列値を返し、type 属性が存在しない場合は "Nothing" が返されます。
- 子孫軸 contact...<city> の式は、手順の組み合わせに変換されます。まず、contact.Descendants("city") メソッドが呼び出され、city という名前で contact 以下のあらゆる階層にある、全要素のコレクションが返されます。次に、最初の要素が取得され、その要素に Value プロパティがある場合は、Value プロパティが呼び出されます。
LINQ to XML 呼び出しへ変換した後の、上記の例と同等のコードは、次のようになります。
For Each Dim phone In contact.Element("phone")
Console.WriteLine(CStr(phone.Attribute("type")))
Next
If Any(contact.Descendants("city")) Then
Console.WriteLine(ElementAt(contact.Descendants("city"),0)).Value)
End If
統合言語クエリと XML の新機能
統合言語クエリと Visual Basic 9.0 の新しい XML 機能を組み合わせて使うことで、多くの一般的な XML プログラミング作業を実行するための、単純かつ強力な方法が提供されます。「1 つの Select で複数のピア ノードを作成する」で使用したフラットな連絡先一覧を作成し contact 要素を削除するクエリを検討してみましょう。
<contacts>
<!-- contact -->
<name>Patrick Hines</name>
<phone type="home">206-555-0144</phone>
<phone type="work">425-555-0145</phone>
<address>
<address>
<state>WA</state>
</address>
</address>
</contacts>
C# で記述した場合のコードを次に示します。
XElement contacts =
new XElement("contacts",
from c in contacts.Elements("contact")
select new XElement ("newContact"
new XComment("contact"),
new XElement("name", (string)c.Element("name")),
c.Elements("phone"),
new XElement("address", c.Element("address"))
)
);
Visual Basic 9.0 では、次のように記述できます。
Dim contacts as XElement = _
<contacts>
<%= From c In contacts _
Select _
<newContact>
<!-- contact -->
<name><%= c.<name>.Value %> </name>
<%= c.<phone> %>
<address><%= c.<address> %> </address>
</newContact>
%>
</contacts>
スキーマ対応 XML プログラミング
LINQ to XML では、XElement というジェネリック ツリー型を使用します。このため、XML ツリーは基本的に型指定されていない方法で処理されます。共通言語ランタイム型の生成に使用できるメタデータがあり、共通言語ランタイム型に XML の構成方法や適切で単純な型の情報が含まれている場合、この状況を大幅に改善できます。まさにこの作業に活用できるのが、XML スキーマです。
次に、特定の郵便番号の注文を合計する LINQ to XML コード サンプルを示します。
public static double GetTotalByZip(XElement root, int zip) {
return (from o in root.Elements("order"),
from i in order.Elements("item"),
where (int)o.Element("address").Element("postal") == zip
select (double)i.Element("price")
* (int)i.Element("quantity")).Sum();
}
LINQ to XML API のジェネリックな性質により、さまざまな引用符 (..."price" ...) やキャスト ( ...(double)i.Element("price") ...) が必要になっています。つまり、LINQ to XML API には XML の形式、属性の型、および要素の型に関する情報がなく、price 要素が item 要素の下にあり、その型が倍精度浮動小数点型であることは認識されません。したがって、開発者はこのような情報を把握し、(引用符やキャストを使用して) アサートする必要があります。Orders にスキーマ派生オブジェクト モデルを使用すると、次のようにコードを記述できます。
public static double GetTotalByZip(Orders root, int zip) {
return (from o in root.Order,
from i in o.Item
where order.Address.Postal == zip
select i.Price * i.Quantity).Sum();
}
引用符やキャストの代わりに、Orders や Item などの型、Price や Quantity などのプロパティを利用しています。スキーマ派生オブジェクト モデルでは、静的な型指定の利点に加え、仮想メソッドによるクラスの拡張、型情報を利用したデバッグ、XML らしさの隠蔽など、他のさまざまな機能を利用できます。したがって、プログラマは XML プログラミングをオブジェクト指向 (OO) プログラミングの一種と見なすことができます。
スキーマ派生オブジェクト モデルは、LINQ to XML を無視しません。代わりに、スキーマ派生クラスでは、バックグラウンドで LINQ to XML を使用して XML データを 一般的な XML ツリーに格納します。このデザインは、XML の忠実度が、型指定されたプログラミング モデルによって保持されることを示しています。また、このデザインは、型を厳格に選択する必要がないことを示しています。したがって、アプリケーションの中でジェネリック API が適している部分ではジェネリック API を使用し、他の部分ではスキーマ派生オブジェクト モデルを使用することができます。スキーマ派生クラスは LINQ to XML ツリーの "型指定されたビュー" なので、アプリケーションのどちらの部分でも、シリアル化や同期に関する問題をまったく引き起こさずに XML ツリーを共有できます。
参考資料
その他のドキュメント、サンプル、チュートリアルなども入手できます。