独自のXML名前空間を持つXMLをC#で解析する機会があったため、いろいろな方法でのデータ取得を試してみました。
■環境
- Windows7 SP1
- .NET Framework 4
■対象のXML
XMLを自作するのは手間なので、このエントリでは Amazon Product Advertising API のレスポンスを例として使います。
参考: API Reference - Product Advertising API
なお、Amazon Product Advertising API へのリクエストには署名が必要ですが、Amazonが公式に公開しているサンプルを使います。
Product Advertising API Signed Requests Sample Code - C# REST/QUERY : Sample Code & Libraries : Amazon Web Services
ただ、サンプルにて使用しているAPIはバージョンが古くそのままでは動作しないため、以下のエントリを参考に修正したものを使います。
Amazon Product Advertising API はじめの一歩 | Express for Web
リクエストの主な内容は、以下の通りです。
項目 | 値 |
---|---|
オペレーション | ItemLookup |
レスポンスグループ | Offers, Small |
取得する項目 | Title, TotalNew, LowestNewPrice, TotalUsed, LowestUsedPrice, TotalOffers |
■試してみる方法
以下の4つの方法を試してみました。なお、LINQはメソッド構文を使います*1。
- LINQ to XML + XPathSelectElements
- LINQ to XML + Descendants
- XmlDocument + SelectNodes
- XPathNavigator + Evaluate
それぞれの方法で、レスポンスを受け取った後の流れを以下の通りに実装します。
public class Result { public string Title { get; set; } public int TotalNew { get; set; } public int LowestNewPrice { get; set; } public int TotalUsed { get; set; } public int LowestUsedPrice { get; set; } public int TotalOffers { get; set; } }
■1. LINQ to XML + XPathSelectElements
取得したレスポンスのストリームは、XElement型にLoadします。
var root = XElement.Load(stream);
参考:c# - What's the difference between xelement.load and xdocument.load? - Stack Overflow
名前空間は XmlNamespaceManager オブジェクトに追加します。
string nameSpace = "http://webservices.amazon.com/AWSECommerceService/2011-08-01"; var nsmgr = new XmlNamespaceManager(new NameTable()); nsmgr.AddNamespace("ns", nameSpace);
参考:c# - How do I get an IXmlNamespaceResolver - Stack Overflow
あとはLINQとXPathSelectElementでXPathを使って、項目を取得します。Selectのところで詰め込み先の「Program.Result」クラスを指定しています。
var result = root.XPathSelectElements("//ns:Item", nsmgr) .Select(item => new Program.Result { Title = item.XPathSelectElement("./ns:ItemAttributes/ns:Title", nsmgr).Value, TotalNew = int.Parse(item.XPathSelectElement("./ns:OfferSummary/ns:TotalNew", nsmgr).Value), LowestNewPrice = int.Parse(item.XPathSelectElement("./ns:OfferSummary/ns:LowestNewPrice/ns:Amount", nsmgr).Value), TotalUsed = int.Parse(item.XPathSelectElement("./ns:OfferSummary/ns:TotalUsed", nsmgr).Value), LowestUsedPrice = int.Parse(item.XPathSelectElement("./ns:OfferSummary/ns:LowestUsedPrice/ns:Amount", nsmgr).Value), TotalOffers = int.Parse(item.XPathSelectElement("./ns:Offers/ns:TotalOffers", nsmgr).Value), }).First();
参考:Extensions.XPathSelectElement メソッド (XNode, String, IXmlNamespaceResolver) (System.Xml.XPath)
なお、XPathSelectElementなどのSystem.Xml.XPath.Extensionsクラスにある拡張メソッドを使うと、パフォーマンスが若干低下するとのことです。(2013/12/11 追記)
参考:Extensions クラス (System.Xml.XPath)
■2. LINQ to XML + Descendants
ストリームの取得は 1.同様、XElement型にLoadします。
名前空間は、他と異なりXNameSpace型にセットします。
XNamespace nameSpace = "http://webservices.amazon.com/AWSECommerceService/2011-08-01";
あとはDescendantsで目的のノードへ移動しElement()で取得するため、XPathに詳しくなくても大丈夫そうです。なお、数値型へは直接パースできました。
var result = root.Descendants(nameSpace + "Item") .Select(item => new Program.Result { Title = item.Element(nameSpace + "ItemAttributes").Element(nameSpace + "Title").Value, TotalNew = (int)item.Element(nameSpace + "OfferSummary").Element(nameSpace + "TotalNew"), LowestNewPrice = (int)item.Element(nameSpace + "OfferSummary").Element(nameSpace + "LowestNewPrice").Element(nameSpace + "Amount"), TotalUsed = (int)item.Element(nameSpace + "OfferSummary").Element(nameSpace + "TotalUsed"), LowestUsedPrice = (int)item.Element(nameSpace + "OfferSummary").Element(nameSpace + "LowestUsedPrice").Element(nameSpace + "Amount"), TotalOffers = (int)item.Element(nameSpace + "Offers").Element(nameSpace + "TotalOffers") }).First();
なお、今回のケースでは省略しましたが、実際には null の入ってくる場合も考慮したほうが良いかもしれません。
参考:
■3. XmlDocument + SelectNodes
LINQを使わないため、ストリームはXmlDocument型にLoadします。
var root = new XmlDocument();
root.Load(stream);
名前空間は XmlNamespaceManager オブジェクトへの追加でした。
あとはSelectNodesでXPathを使って設定しますが、「Item(0)」や「InnerText」が出てきて、少し分かりづらい感じです。詰め込みはオブジェクト初期化子でやりました。
var result = new Program.Result { Title = root.SelectNodes("//ns:Title", nsmgr).Item(0).InnerText, TotalNew = int.Parse(root.SelectNodes("//ns:TotalNew", nsmgr).Item(0).InnerText), LowestNewPrice = int.Parse(root.SelectNodes("//ns:LowestNewPrice/ns:Amount", nsmgr).Item(0).InnerText), TotalUsed = int.Parse(root.SelectNodes("//ns:TotalUsed", nsmgr).Item(0).InnerText), LowestUsedPrice = int.Parse(root.SelectNodes("//ns:LowestUsedPrice/ns:Amount", nsmgr).Item(0).InnerText), TotalOffers = int.Parse(root.SelectNodes("//ns:TotalOffers", nsmgr).Item(0).InnerText) };
■4. XPathNavigator + Evaluate
ストリームはXPathNavigator型に入れます。
var root = new XPathDocument(stream).CreateNavigator();
名前空間は XmlNamespaceManager オブジェクトへの追加でした。
あとは結果を詰め込むだけですが、XmlNamespaceManager.Evaluate() の結果は foreachで回さないと取得できないため、強引なワンライナーになっています。キャストが必要だったり var を使えないなど、いろいろな制限もありました。
var result = new Program.Result(); foreach (XPathNavigator r in root.Evaluate("//ns:Title", nsmgr) as XPathNodeIterator) { result.Title = r.Value; } foreach (XPathNavigator r in root.Evaluate("//ns:TotalNew", nsmgr) as XPathNodeIterator) { result.TotalNew = int.Parse(r.Value); } foreach (XPathNavigator r in root.Evaluate("//ns:LowestNewPrice/ns:Amount", nsmgr) as XPathNodeIterator) { result.LowestNewPrice = int.Parse(r.Value); } foreach (XPathNavigator r in root.Evaluate("//ns:TotalUsed", nsmgr) as XPathNodeIterator) { result.TotalUsed = int.Parse(r.Value); } foreach (XPathNavigator r in root.Evaluate("//ns:LowestUsedPrice/ns:Amount", nsmgr) as XPathNodeIterator) { result.LowestUsedPrice = int.Parse(r.Value); } foreach (XPathNavigator r in root.Evaluate("//ns:TotalOffers", nsmgr) as XPathNodeIterator) { result.TotalOffers = int.Parse(r.Value); }
■結果
当然ながら、どれも同じ結果になります。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*1:Web上ではクエリ構文の方をよく見かけた気がします