ubichupas.net

XML文書をLINQで処理してみるサンプル

LINQ(Language INtegrated Query)は.NET Framework 3.5から登場した新しい構文で、XMLに限らずSQLや配列の要素を検索・集計する定番の処理をより端的に記述できるようにしたものです。

入力するXMLファイル

お題として次のXML文書(GundamList.xml)を使用しました。

GundamList.xml
<?xml version="1.0" encoding="Shift_JIS" ?>

<!DOCTYPE gundamList SYSTEM "GundamList.dtd">

<gundamList xmlns="urn:gundam:anime">
 <gundam media="TV">
     <name>機動戦士ガンダム</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0079</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="TV">
     <name>機動戦士Zガンダム</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0087</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="TV">
     <name>機動戦士ガンダムZZ</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0088</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="MOVIE">
     <name>機動戦士ガンダム 逆襲のシャア</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0093</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="OVA">
     <name>機動戦士ガンダム0080 ポケットの中の戦争</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0080</calendar>
     <director>高山文彦</director>
 </gundam>
 <gundam media="MOVIE">
     <name>機動戦士ガンダムF91</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0123</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="OVA">
     <name>機動戦士ガンダム0083 STARDUST MEMORY</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0083</calendar>
     <director>加瀬充子</director>
<director>今西隆志</director> </gundam> <gundam media="TV"> <name>機動戦士Vガンダム</name> <calendar jpn="宇宙世紀" eng="U.C.">0153</calendar> <director>富野由悠季</director> </gundam> <gundam media="TV"> <name>機動武闘伝Gガンダム</name> <calendar jpn="未来世紀" eng="F.C.">60</calendar> <director>今川泰宏</director> </gundam> <gundam media="TV"> <name>新機動戦記ガンダムW</name> <calendar jpn="アフターコロニー" eng="A.C.">195</calendar> <director>池田成</director> <director>高松信司</director> </gundam> <gundam media="TV"> <name>新機動戦記ガンダムX</name> <calendar jpn="アフターウォー" eng="A.W.">15</calendar> <director>高松信司</director> </gundam> <gundam media="OVA"> <name>機動戦士ガンダム 第08MS小隊</name> <calendar jpn="宇宙世紀" eng="U.C.">0079</calendar> <director>神田武幸</director> <director>飯田馬之介</director> </gundam> <gundam media="OVA"> <name>新機動戦記ガンダムW Endless Waltz</name> <calendar jpn="アフターコロニー" eng="A.C.">196</calendar> <director>青木康直</director> </gundam> <gundam media="OVA"> <name>機動戦士ガンダム 第08MS小隊 特別編 ラスト・リゾート</name> <calendar jpn="宇宙世紀" eng="U.C.">0079</calendar> <director>森邦宏</director> <director>仕舞屋鉄</director> </gundam> <gundam media="TV"> <name>∀ガンダム</name> <calendar jpn="正暦" eng="C.C.">2345</calendar> <director>富野由悠季</director> </gundam> <gundam media="TV"> <name>機動戦士ガンダムSEED</name> <calendar jpn="コズミック・イラ" eng="C.E.">71</calendar> <director>福田己津央</director> </gundam> <gundam media="TV"> <name>機動戦士ガンダムSEED DESTINY</name> <calendar jpn="コズミック・イラ" eng="C.E.">73</calendar> <director>福田己津央</director> </gundam> <gundam media="OVA"> <name>機動戦士ガンダムSEED C.E.73 STARGAZER</name> <calendar jpn="コズミック・イラ" eng="C.E.">73</calendar> <director>西澤晋</director> </gundam> <gundam media="TV"> <name>機動戦士ガンダム00 ファーストシーズン</name> <calendar jpn="西暦" eng="A.D.">2307</calendar> <director>水島精二</director> </gundam> <gundam media="TV"> <name>機動戦士ガンダム00 セカンドシーズン</name> <calendar jpn="西暦" eng="A.D.">2312</calendar> <director>水島精二</director> </gundam> </gundamList>

LINQ to XMLプログラム1の出力

最初に作成するLINQサンプルは「GundamList.xmlの中から高松信司監督のガンダム作品を抽出する」プログラムで、次のような出力結果を目指します。

cmd.exe
A.C.195  新機動戦記ガンダムW
A.W.15   新機動戦記ガンダムX
以上、2作品

LINQ to XMLプログラム1の実装

LINQではXML要素の表現にSystem.Xml.XmlElementでなくSystem.Xml.Linq.XElementを使用します。 一通りの型に対するExplicit変換演算子が用意されていて、XML要素の値をキャストで取得できるのが特徴です。

fromキーワードからセミコロンまでの5行がLINQのクエリで、その結果はIEnumerable<XElement>のオブジェクトです。 結果の型はselectするオブジェクトによって変わってしまうので、常にvarキーワードを使うのが好ましいでしょう。

GundamLinq1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace GundamLinq
{ class GundamLinq1
{ static void Main(string[] args) { // LINQ to XML用のオブジェクトを生成 XElement root = XElement.Load(@"..\..\GundamList.xml"); XNamespace ns = "urn:gundam:anime"; // 高松信司のガンダムを抽出するクエリ IEnumerable<XElement> gundams = from g in root.Elements(ns + "gundam") where ( from d in g.Elements(ns + "director") where "高松信司" == (string)d select d ).Any() select g; // 結果を出力 foreach (XElement g in gundams) { string name = (string)g.Element(ns + "name"); string year = (string)g.Element(ns + "calendar"); string eng = (string)g.Element(ns + "calendar").Attribute("eng"); Console.WriteLine("{0}{1,-4: } {2}", eng, year, name); } Console.WriteLine("以上、{0}作品", gundams.Count()); } } }

GundamLinq1.csでは複数あるdirector要素に対応するためLINQのクエリを二重にしてみました。 どう書くのが効率的なのかイマイチわかりません。

LINQ to XMLプログラム2の出力

次に作成するLINQサンプルは「GundamList.xmlの中から宇宙世紀(U.C.)のガンダムシリーズを抜き出して、作中の年表順にソートする」プログラムで、次のような出力結果を目指します。

cmd.exe
U.C.0079 機動戦士ガンダム
U.C.0079 機動戦士ガンダム 第08MS小隊
U.C.0079 機動戦士ガンダム 第08MS小隊 特別編 ラスト・リゾート
U.C.0080 機動戦士ガンダム0080 ポケットの中の戦争
U.C.0083 機動戦士ガンダム0083 STARDUST MEMORY
U.C.0087 機動戦士Zガンダム
U.C.0088 機動戦士ガンダムZZ
U.C.0093 機動戦士ガンダム 逆襲のシャア
U.C.0123 機動戦士ガンダムF91
U.C.0153 機動戦士Vガンダム
以上、10作品

LINQ to XMLプログラム2の実装

最初のサンプルでは出力処理のところに子要素を取得するコードを残していたため、垢抜けない感じになっていました。 そこで今度は匿名クラスを使って必要な値だけを抜き出してみました。 SQL的な言い方をすると射影でしょうか。

GundamLinq2.cs
using System;
using System.Linq;
using System.Xml.Linq;

namespace GundamLinq
{
    class GundamLinq2
    {
        static void Main(string[] args)
        {
            // LINQ to XML用のオブジェクトを生成
            XElement root = XElement.Load(@"..\..\GundamList.xml");
            XNamespace ns = "urn:gundam:anime";

            // 宇宙世紀のガンダムを抽出してソートするクエリ
            var query = from g in root.Elements(ns + "gundam")
                        let cal = g.Element(ns + "calendar")
                        where "U.C." == (string)cal.Attribute("eng")
                        orderby (string)cal
                        select new
                        {
                            name = (string)g.Element(ns + "name"),
                            year = (string)cal
                        };

            // 結果を出力
            foreach (var q in query)
            {
                Console.WriteLine("U.C.{0} {1}", q.year, q.name);
            }

            Console.WriteLine("以上、{0}作品", query.Count());
        }
    }
}

GundamLinq2.csではクエリ中にcalendar要素を3回も参照するため、letキーワードを使って一時変数に格納しています。 簡単に書けるからとXContainer.Element()メソッドを3回呼んでしまったら流石に非効率でしょう。

LINQを使う利点はorderbyキーワードでソート処理ができる点だと思います。 さらにgroup ~ by ~ into ~というキーワードでグループ化もできるようです。

0 件のコメント: