最近LINQを勉強したので、忘れないうちに、LINQ to Objectsに続いてLINQ to XMLの備忘録を掲載します。LINQ to Objectsほど複数の記事にしない予定なのです。
確認環境
- Windows Vista Enterprise
- 開発環境: Visual Studio 2008 Professional
- .NET 3.5
0. 使用するデータの定義
最初はXMLに対してクエリを行いたいので、ファイルから読み取るXMLの中身を定義します。本記事では以下のXMLファイルの内容にしたいしてLINQクエリを実行します。
ファイル名はproducts.xmlとします。
<?xml version="1.0" encoding="utf-8"?> <products> <product id="product1" category="book"> <name>Programing Something</name> <price>2000</price> <publishDate>2008/10/10</publishDate> <authors> <author>Tarou Book</author> <author>Hanako Book</author> </authors> </product> <product id="product2" category="book"> <name>Administrating Something</name> <price>5000</price> <publishDate>2008/10/12</publishDate> <authors> <author>Kotarou Book1</author> <author>Kohanako Book2</author> </authors> </product> <product id="product3" category="novel"> <name>Suspection novel</name> <price>500</price> <publishDate>2007/12/12</publishDate> <authors> <author>Jirou Tarou</author> </authors> </product> <product id="product4" category="novel"> <name>Fantasy novel</name> <price>540</price> <publishDate>2008/9/14</publishDate> <authors> <author>Tarou Book</author> </authors> </product> <product id="product5" category="cram"> <name>Study English</name> <price>2400</price> <publishDate>2008/9/14</publishDate> <authors> <author>English Tarou</author> <author>English Jirou</author> </authors> </product> </products>
1. LINQクエリメモ
1.1 XMLファイルのロード
XElement,XDocumentのLoadメソッドを使用するとファイルやネットワークからXMLファイルをロードし、XElement,XDocumentを初期化することができます。
XElement elem = XElement.Load(@"D:\temp\products.xml"); Console.WriteLine(elem); XDocument doc = XDocument.Load(@"D:\temp\products.xml"); Console.WriteLine(doc.ToString());
1.2 選択処理
LINQ to Objectsと同じように、XMLに対してクエリを発行します。以下サンプルプログラムです。
XElement products = XElement.Load(@"D:\temp\products.xml"); var query = from p in products.Elements("product") where (string)p.Attribute("category") == "book" select new { ID = (string)p.Attribute("id"), Name = (string)p.Element("name"), Price = (decimal)p.Element("price") }; foreach (var item in query) { Console.WriteLine(item); }
実行結果は次のようになります。
{ ID = product1, Name = Programing Something, Price = 2000 }
{ ID = product2, Name = Administrating Something, Price = 5000 }
LINQ to Objectと同時に使用することができます。以下サンプルプログラムです。
XElement products = XElement.Load(@"D:\temp\products.xml"); var categories = new string[] { "book", "novel" }; // on節でp.Element("category")とするとコンパイルエラーとなるので、 // (string) p.Element("category")と明示的にキャストする必要があります var query = from p in products.Elements("product") let xCategory = (string) p.Attribute("category") join c in categories on xCategory equals c orderby xCategory select new { Name = (string)p.Element("name"), Price = (decimal)p.Element("price"), Category = xCategory }; foreach (var item in query) { Console.WriteLine(item); }
実行結果は次のようになります。
{ Name = Programing Something, Price = 2000, Category = book }
{ Name = Administrating Something, Price = 5000, Category = book }
{ Name = Suspection novel, Price = 500, Category = novel }
{ Name = Fantasy novel, Price = 540, Category = novel }
1.3 ドメインオブジェクトの作成
XMLファイルからLINQ to XML を使用してクラスのインスタンスを作成する方法を記述します。前提として読み込むファイルの内容は"使用するデータの定義"で掲載したデータとします。
XMLに対応するクラスは次のようになっているとします。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Linq2Xml { public class Product { public string ID { get; set; } public decimal Price { get; set; } public DateTime PublishData { get; set; } public string Category { get; set; } public string Name { get; set; } public Author[] Authors { get; set; } public override string ToString() { StringBuilder builder = new StringBuilder(); builder.Append(string.Format("ID:{0}, Name:{1}, Price:{2:c}", this.ID, this.Name, this.Price)); if (Authors != null) { foreach (Author a in Authors) { builder.Append("\n" + a.ToString()); } } return builder.ToString(); } } public class Author { public Author() { } public Author(string name) { Name = name; } public String Name { get; set; } public override string ToString() { return "Author:" + Name; } } }
読み込むファイルが小さなファイルの場合は、XElement.Loadメソッドを使用します。
public static void CreateObjectFromXml() { XElement products = XElement.Load(@"D:\temp\products.xml"); var query = from p in products.Elements("product") select new Product() { ID = (string)p.Attribute("id"), Category = (string)p.Attribute("category"), Name = (string)p.Element("name"), Price = (decimal)p.Element("price"), Authors = ( from a in p.Descendants("author") select new Author(a.Value)).ToArray() }; foreach (var item in query) { Console.WriteLine(item); } }
実行結果は次のようになります。
ID:product1, Name:Programing Something, Price:\2,000
Author:Tarou Book
Author:Hanako Book
ID:product2, Name:Administrating Something, Price:\5,000
Author:Kotarou Book1
Author:Kohanako Book2
ID:product3, Name:Suspection novel, Price:\500
Author:Jirou Tarou
ID:product4, Name:Fantasy novel, Price:\540
Author:Tarou Book
ID:product5, Name:Study English, Price:\2,400
Author:English Tarou
Author:English Jirou
上記の場合、メモリ上にXMLファイルの内容が読み込まれてからクエリの評価が行われますが、XMLファイルのサイズが大きかったり、リモートサイトにある場合は、メモリ上にすべて読み込んでからクエリの評価を行うとパフォーマンスが落ちる可能性があります。そこで、LINQの遅延クエリ評価を利用して、次のようにXmlReaderを使用して、ブロックごとに読み込むようにします。
public static void CreateObjectFromLargeXml() { var products = BlockXmlReader(@"D:\temp\products.xml", "product"); var query = from p in products select new Product() { ID = (string)p.Attribute("id"), Category = (string)p.Attribute("category"), Name = (string)p.Element("name"), Price = (decimal)p.Element("price"), Authors = (from a in p.Descendants("author") select new Author(a.Value)).ToArray() }; foreach (var item in query) { Console.WriteLine(item); } } /// <summary> /// 指定されたURIからelemNameを持つ要素のリストを取得する /// </summary> public static IEnumerable<XElement> BlockXmlReader(string uri, string elemName) { XmlReaderSettings settings = new XmlReaderSettings(); XmlReader reader = XmlReader.Create(uri, settings); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == elemName) { yield return XElement.ReadFrom(reader) as XElement; } } }
実行結果はメモリにXMLファイルをすべて読み込む場合の実行結果例と同じです。
1.4 Xmlファイルの変換
Xmlファイルを別スキーマのXmlファイルに変換します。もちろんXSLTを使用することもできますが、LINQ to XMLを使ったサンプルを掲載します。XNamespaceも使ってみます。
public static void TransformingXml() { XElement products = XElement.Load(@"D:\temp\products.xml"); XNamespace ns = "http://www.pine4.net/Products"; XElement transformedProducts = new XElement(ns + "products", new XAttribute(XNamespace.Xmlns + "p", ns), from p in products.Elements("product") select new XElement(ns + "product", new XAttribute("id", (string)p.Attribute("id")), new XElement(ns + "name", (string)p.Element("name")), new XElement(ns + "price", (decimal)p.Element("price")), from a in p.Descendants("author") select new XElement("author", a.Value)) ); transformedProducts.Save(@"D:\temp\transform.xml"); }
実行結果のファイルは次のようになります。
<?xml version="1.0" encoding="utf-8"?> <p:products xmlns:p="http://www.pine4.net/Products"> <p:product id="product1"> <p:name>Programing Something</p:name> <p:price>2000</p:price> <authro>Tarou Book</authro> <authro>Hanako Book</authro> </p:product> <p:product id="product2"> <p:name>Administrating Something</p:name> <p:price>5000</p:price> <authro>Kotarou Book1</authro> <authro>Kohanako Book2</authro> </p:product> <p:product id="product3"> <p:name>Suspection novel</p:name> <p:price>500</p:price> <authro>Jirou Tarou</authro> </p:product> <p:product id="product4"> <p:name>Fantasy novel</p:name> <p:price>540</p:price> <authro>Tarou Book</authro> </p:product> <p:product id="product5"> <p:name>Study English</p:name> <p:price>2400</p:price> <authro>English Tarou</authro> <authro>English Jirou</authro> </p:product> </p:products>
説明は以上です。誤り、指摘等がありましたら、ご連絡ください。
さんのコメント: さんのコメント: