[C#]XML操作はLINQよりXPath

以下の記事を読んでいて思い出したこと。
最終回 C# 4の使いどころはどこか | @IT

本記事中で、XElementから特定の条件にマッチした要素をLINQで探す、というコードが掲載されている。具体的には、以下の様な感じだ。

public IEnumerable<string> Records
{
  get
  {
    return doc.Element("root").Element("collections").Elements("own").Select(c => c.Descendants("ID").First().Value);
  }
}

root要素の中のcollection要素の中のown要素集合に対してそれぞれ最初に出てくるID要素を取得する。みたいな。こういうことをやりたい場合は、XPathを使う、もしくは併用すると良い。XElementでも、System.Xml.XPathをインポートすることで、XPathSelectElement(s)という拡張メソッドが使用できるようになる。これは、その名の通り、対象のXElementからXPathで要素を取得するメソッドである。

これを用いると、上記のコードは以下のようになる。

public IEnumerable<string> Records
{
  get
  {
    return doc.XPathSelectElements("//root/collections/own/ID");
  }
}

ただ、元のXMLファイルと、このメソッドでやりたい事がよくわからないので、作者の意図とは違ったことをやっている可能性はある。

ほかにも例を挙げるとすると、たとえばドキュメント中に一つだけあるvehicle要素の中にあるcar要素で、maker属性がmazdaのものを取ってくることを考える。これをLINQで書くと、「maker属性がmazda」というのを書くのが長ったらしくなってしまう。

var elem = XElement.Parse(something);
var maz = elem.Element("vehicle").Elements("car").Where(c => ((string)c.Attribute("maker")) == "mazda"));

これをさらに面倒にさせているのが、Attributeメソッドは、該当する属性がなければnullを返すことだ。本来であればc.Attribute("maker").Value.Equals("mazda")と書きたいところだけど、上記のように書くしか無い。文字列を==で比較するのは、元Java使いとしてはいささか気持ち悪くもある。

こういうときも、XPathを使うと以下のように簡潔に、またわかりやすく記述できる。

var elem = XElement.Parse(something);
var maz = elem.XPathSelectElements("//vehicle/car[@maker='mazda']");

ただし、これは上記とは若干動作が異なるので注意。vehicle要素が複数あったとき、前者は最初のvehicle要素だけを探索するが、後者はすべてのvehicle要素を横断して探索する。これを厳密に合わせるならば、以下のようになる。

var elem = XElement.Parse(something);
var maz = elem.XPathSelectElements("/vehicle[1]/car[@maker='mazda']");

vehicle[1]とすることで、1番目のvehicle要素、と厳密に指定できる。

XPathの書き方としては、以下のページがまとまっていてわかりやすい。

XPath 構文 | MSDN
XPath の例 | MSDN

LINQを覚えると何でもLINQでやりたくなってくる。けれども、場合によっては遠回りになってしまうこともあるよ、という紹介でした。(以前は私もLINQでXMLいじってた)