[C#] XElementを使ってXMLを読み込む

C#
  • 2023/06/11

C#でXMLにアクセスする方法の一つに、XElementを使う方法があります。
XMLはルート要素、子要素、属性などで構成されていますが、それぞれXElementでどのようにアクセスできるのかをまとめました。

使用するXML

Microsoftのサイトにあるサンプルが今回の記事に適していたので、これを使って説明していくことにします。

XMLファイルの読み込み

xmlファイルを読み込むにはLoadメソッドを使います。

string fileName = "PurchaseOrder.xml";
XElement xml = XElement.Load(fileName);

この時、変数xmlはPurchaseOrder.xmlのルート要素を指しています。
ルート要素とはXMLの一番外側の要素のことで、今回の場合はPurchaseOrder要素のことです。

<?xml version="1.0"?>
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">  <!--ルート要素-->
  <Address Type="Shipping">
    <Name>Ellen Adams</Name>
    <Street>123 Maple Street</Street>
    ...
</PurchaseOrder>

この変数xmlから、子要素や属性にアクセスしていきます。

要素名の取得

変数xmlから"PurchaseOrder"という要素名を取得するには、Nameプロパティにアクセスします。

Console.WriteLine(xml.Name); // PurchaseOrder

子要素の取得

Elements()メソッドにアクセスすることで、一つ下の階層の要素の一覧を取得することができます。

foreach(var element in xml.Elements())
{
    Console.WriteLine(element.Name);
}

結果

Address
Address
DeliveryNotes
Items

子要素の名前を指定すると、その名前の子要素だけを取得することができます。

foreach(var element in xml.Elements("Address"))
{
    Console.WriteLine(element);
}

結果

<Address Type="Shipping">
  <Name>Ellen Adams</Name>
  <Street>123 Maple Street</Street>
  <City>Mill Valley</City>
  <State>CA</State>
  <Zip>10999</Zip>
  <Country>USA</Country>
</Address>
<Address Type="Billing">
  <Name>Tai Yee</Name>
  <Street>8 Oak Avenue</Street>
  <City>Old Town</City>
  <State>PA</State>
  <Zip>95819</Zip>
  <Country>USA</Country>
</Address>

Element()メソッドを使うと、一つだけ子要素を取得します。
また、Element()に対してElement()やElements()を呼び出すこともできます。

foreach (var element in xml.Element("Items").Elements("Item"))
{
    Console.WriteLine(element);
}

結果

<Item PartNumber="872-AA">
  <ProductName>Lawnmower</ProductName>
  <Quantity>1</Quantity>
  <USPrice>148.95</USPrice>
  <Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
  <ProductName>Baby Monitor</ProductName>
  <Quantity>2</Quantity>
  <USPrice>39.98</USPrice>
  <ShipDate>1999-05-21</ShipDate>
</Item>

孫要素以下の取得

孫要素にアクセスするには、Elemet()をつなげてアクセスすることも可能ですが、Descendants()を使って直接アクセスすることもできます。

var descendants = xml.Descendants("ProductName");
foreach (var descendant in descendants)
{
    Console.WriteLine(descendant);
}

結果

<ProductName>Lawnmower</ProductName>
<ProductName>Baby Monitor</ProductName>

値の取得

次に要素の値を取得します。
一つ目のItem要素の下にある、ProductNameの値"Lawnmower"を取得してみます。

  <Items>
    <Item PartNumber="872-AA">
      <ProductName>Lawnmower</ProductName>

まず、Elements()メソッドを使って取得した複数の要素のうち、一つ目の要素を取り出すにはFirst()を指定します。
このようにして得られた一つ目のItem要素に対して子要素のProductNameを取り出します。
この子要素の、Valueプロパティにアクセスすることで、要素の値を取得することができます。

var item = xml.Element("Items").Elements("Item").First();
var value = item.Element("ProductName").Value;
Console.WriteLine(value); // Lawnmower

属性の取得

属性は要素名の隣に並んでいる値のことです。
属性名="属性値"のような表記をします。

<!-- {属性名: PurchaseOrderNumbe, 属性値: 99503}, {属性名: OrderDate, 属性値: 1999-10-20} -->
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
  <!-- {属性名: Type, 属性値: Shipping} -->
  <Address Type="Shipping">
    <Name>Ellen Adams</Name>
    <Street>123 Maple Street</Street>

各要素のAttributesメソッドを呼び出し、NameプロパティとValueプロパティにアクセスして、それぞれ属性名と属性値を取得します。
Elements/Elementと同様に、Attributeメソッドで一つだけ取り出すこともできますし、属性名を指定して、一致する属性だけを取り出すこともできます。

var rootAttributes = xml.Attributes();
foreach(var attribute in rootAttributes)
{
    Console.WriteLine($"{attribute.Name}: {attribute.Value}");
}
Console.WriteLine("");

var firstAddress = xml.Elements("Address").First();
var addressAttribute = firstAddress.Attribute("Type");
Console.WriteLine($"{addressAttribute.Name}: {addressAttribute.Value}");

結果

PurchaseOrderNumber: 99503
OrderDate: 1999-10-20

Type: Shipping

Namespaceが付いている場合

XMLではxmlnsという属性名が付けられている場合があります。
これはxmlnsは名前空間(Namespace)と呼ばれる属性です。
Namespaceの取得方法や、親要素がxmlns属性を持っている場合の子要素へのアクセス方法は異なります。
xmlns属性付きのサンプルを使って動作を確認してみます。

<Tests xmlns="http://www.adatum.com">
  <Test TestId="0001" TestType="CMD">
    <Name>Convert number to string</Name>
    <CommandLine>Examp1.EXE</CommandLine>
...

xmlns属性がついていない場合、Nameプロパティにアクセスすると要素名を取得することができましたが、ついている場合はNamespace+要素名が取得されます。

XElement xml = XElement.Load(fileName);
Console.WriteLine(xml.Name);

結果

{http://www.adatum.com}Tests

この場合、要素名とNamespaceはそれぞれNameプロパティのLocalName, NameSpaceから個別に取得することができます。

Console.WriteLine(xml.Name.Namespace);
Console.WriteLine(xml.Name.LocalName);

結果

http://www.adatum.com
Tests

Namespaceを持つ要素の子予想や孫要素は、すべてNameプロパティにNamespaceが付与されています。
したがって、Elementsメソッドで子要素にアクセスする場合も、Namespaceを指定してあげる必要があります。
また、子要素も親要素と同様にName, LocalName, Namespaceが取得できます。

var namespaceName = xml.Name.Namespace;
var testName = namespaceName + "Test";
var testElement = xml.Element(testName);
Console.WriteLine(testElement.Name);
Console.WriteLine(testElement.Name.Namespace);
Console.WriteLine(testElement.Name.LocalName);

結果

{http://www.adatum.com}Test
http://www.adatum.com
Test

まとめ

Namespace付きの場合に子要素を取得できず混乱したのがきっかけでXElementについてまとめようと思いました。
子要素にもNamespaceが付与されていることがわかり、よかったです。
これで一通りxmlには簡単にアクセスできるようになるかと思います。

Profile

Hotaru

メーカーで組み込み系のソフトウェアやファームウェアの開発をしています。

仕事では主にC言語、Python、C#を使っています。 ...もっと見る