Unityな日々(Unity Geek)

Unityで可視化アプリを開発するための試行錯誤の覚書

LINQ to XMLを使ったXML操作

.NET(C#)でXMLを扱うにはいくつかの方法があるが、'LINQ to XML'を使うと簡潔に書けそうだ。

今回、Unity上で'LINK to XML'を動かしてみた。Unityバージョンは、2017.2。

XMLサンプルファイル

XMLのサンプルは、次を使う。

<?xml version="1.0" encoding="utf-8"?>
<persons>
  <person>
    <name yomi="はりそんふぉーど">Harrison Ford</name>
    <birth>1942-07-13</birth>
    <works>
      <title>Star Wars</title>
      <title>Blade Runner</title>
    </works>
  </person>
  <person>
    <name yomi="みふねとしろう">三船敏郎</name>
    <birth>1920-04-01</birth>
    <works>
      <title>七人の侍</title>
      <title>天国と地獄</title>
      <title>羅生門</title>
    </works>
  </person>
  <person>
    <name yomi="じゃんぎゃばん">Jean Gabin</name>
    <birth>1904-05-17</birth>
    <works>
      <title>ヘッドライト</title>
      <title>地下室のメロディー</title>
      <title>暗黒街のふたり</title>
    </works>
  </person>
</persons>

LINQ to XML

LINQ to XMLを使うには、

をUsingしておく。

手順としては、

  1. XDocumentクラスのLoad静的メソッドでXMLファイルを読み込む。
  2. XDocumentインスタンスRoot.Elements()メソッドで、トップ階層のタグ要素のコレクション(IEnumerable)を作成する。
  3. さらに下層の要素を取り出すには、Elements(タグ名) or Element(タグ名)メソッドを入れ子にし、タグ要素クラス(XElement)を取得する。
  4. アトリビュートがある場合は、XElementインスタンスAttribute(アトリビュート名)メソッドで、アトリビュートクラスを取得する。
  5. タグ要素、アトリビュート要素ともに、Valueプロパティで、値を文字列として取り出す

となる。

文章で書くとわかりにくいが、次のサンプルコードを見れば理解するのはそう難しくないと思う。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System.Xml.Linq;

namespace XOOMS.IO
{
    public class XMLLinq : MonoBehaviour
    {
        //LINQを使ってXMLファイルを解析する

        [SerializeField] string xmlFileName = "actors.xml";
        [SerializeField] string tagName0    = "name";
        [SerializeField] string attribute01 = "yomi";
        [SerializeField] string tagName1    = "birth";
        [SerializeField] string tagName2    = "works";
        [SerializeField] string tagName20   = "title";
        

        void Start()
        {
            XDocument xdoc = XDocument.Load(Application.dataPath + "/" + xmlFileName);


            IEnumerable<XElement> xelements = xdoc.Root.Elements();   //ルート直下の要素のコレクション
            
            foreach (var xelement in xelements)
            {
                XElement xelem0     = xelement.Element(tagName0);       //タグtagNames[0]の要素
                XAttribute xattr01  = xelem0.Attribute(attribute01);    //タグ0のアトリビュートを取り出す
                XElement xelem1     = xelement.Element(tagName1);       //タグtagNames[1]の要素

                //XElement.Valueで値を取り出す
                Debug.Log(string.Format("{0}:{1} ({2}),  {3}:{4}", tagName0, xelem0.Value, xattr01.Value, tagName1, xelem1.Value ));
            }
            
        }

    }
}

これを実行すると、次のように出力される。

name:Harrison Ford (はりそんふぉーど),  birth:1942-07-13
name:三船敏郎 (みふねとしろう),  birth:1920-04-01
name:Jean Gabin (じゃんぎゃばん),  birth:1904-05-17

なお、対応するタグやアトリビュートがない場合、XElement, XAttributeはnullになる。nullオブジェクトの.Valueプロパティにアクセスするとエラーになるので、実際の利用では各要素を取り出した後にnullチェックを入れたほうがいい。

取得条件を加える

ある条件を満たす要素だけを取り出すことも、LINQ to XMLを使えば簡潔に書ける。

条件の記述は、Whereメソッドを使う。

たとえば、生年が1920年以降の人物だけを抽出するには、xelementsの抽出箇所に、次のように条件を追記する。

IEnumerable<XElement> xelements = xdoc.Root.Elements()
                    .Where(x => ((DateTime)x.Element(tagName1)).Year >= 1920);   //生年が1920年以降の要素のみ抽出する

要素を並べ替える

ある要素の値で並べ替えるには、OrderByメソッドを使う。

たとえば、よみがな順に並べ替えるには、

IEnumerable<XElement> xelements = xdoc.Root.Elements()
                    .OrderBy(x => (string)(x.Element(tagName0).Attribute(attribute01)));   //よみがなで並べ替え

OrderBy()は昇順に並べ替える。降順に並べ借るには、.OrderByDescending()メソッドを使う。

入れ子になった要素を取得する

入れ子になった要素の取得は、Element(親要素の名前).Elements(子要素の名前)と数珠繋ぎにしていけばいい。

上のXMLサンプルファイルで、各俳優の代表作はworksの子のtitleに書かれている。これを取得するには次のようにする。

foreach (var xelement in xelements)
            {
                //入れ子になった要素を取得
                IEnumerable<string> selems2 = xelement.Element(tagName2) //works要素
                                            .Elements(tagName20)        //works要素の下のtitle要素
                                            .Select(x => x.Value) ;       //title要素の値のコレクション
                foreach (string selem2 in selems2)
                {
                    Debug.Log(string.Format("title: {0}", selem2));
                }
            }

上の例では、LINQSelectメソッドを使って、一挙に作品名のstringまで取得しているが、先の例のようにIEnumerable<XElement>の取得をして、その後のforeach内でValueを取り出してもよい。

XMLの階層構造を考えずに子孫要素を取り出す

前の例では、「works要素の子のtitle要素」と階層構造が分かったうえで要素を取り出したが、このような階層構造を考えずに、とにかくある名前の要素をすべて取り出すこともできる。これには、Descendantsメソッドを使う。

            IEnumerable<XElement> xtitles = xdoc.Root.Descendants(tagName20);  //Root下の"title'タグを再帰的にすべて取り出す
            foreach(XElement xtitle in xtitles)
            {
                Debug.Log(string.Format("title: {0}", xtitle.Value));
            }

LINQSelectメソッドを使って、次のようにも書ける。

            IEnumerable<string> titles = xdoc.Root.Descendants(tagName20)
                                        .Select(x => x.Value);
            foreach(string title in titles)
            {
                Debug.Log(string.Format("title: {0}", title));
            }

参考資料

今回の記事は、出井秀行著「実戦で役立つC#プログラミングのイデオム/定石&パターン」の第11章、「XMLファイルの操作」を参考にした。