C# で XML 名前空間指定時のノード取得方法
前回の記事では XML の要素を SelectNodes や SelectSingleNode メソッドで選択して読み込む方法を示しました。
今回も要素 (ノード) の読み込みについてですが、少し発展させて、次のような XML を考えましょう。
この XML ドキュメント内には employee という要素があります。 ところが、よくみると 2 種類の employee 要素があります。
最初に出てくる employee 要素は次のような構造で・・・
最後に出てくるのは次のような構造です。
"x:" という部分を除けばどちらも同じ名前の要素なのにデータ構造が違います。 今回はこのように名前が同じでデータ構造違いの要素を含む XML を扱いましょう。
基本的には一度決めたデータ構造は、常に同じ構造をとるべきです。通常はデータ構造を XSD に記述しておきます。
例えば次の XSD のように定義できます。
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="employees">
<xs:complexType>
<xs:sequence>
<xs:element name="employee" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="first"/>
<xs:element type="xs:string" name="last"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" name="title"/>
</xs:sequence>
<xs:attribute type="xs:integer" name="age" use="optional"/>
<xs:attribute type="xs:string" name="gender" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
ここで要素の名前は勝手に決めています。要素の名前を世界中で一意な名前・データ構造にする必要はありません。
例えば、私はここで employee という名前のデータ構造を 勝手に定義したわけですが、きっと他の誰かも同じ名前の employee というデータ構造を定義しているでしょうし、 それらは全く同じものではないでしょう。別にそれで構いません。
ただし、どこで定義されたものか区別する必要はあります。それを行うのが後述の名前空間です。
名前の衝突と名前空間
同じ名前の要素なのに異なるデータ構造である可能性があります。 名前がかぶってしまう問題を 名前の衝突というような言い方をします。
名前の衝突の問題を解決するためには、どこで定義された何というデータであるか、 何らかの方法で書いておければよいですね。
名前の衝突の問題を解決するために使うのが名前空間です。名前空間は英語で namespace (ネームスペース) です。それで省略して出てくるときは "ns" というような文字が出てきます。
名前空間の指定
名前空間は次のように要素の xmlns 属性で指定します。ここでは二つの名前空間が指定してあります。
ひとつめは xmlns="http://abc.com/" です。これはデフォルトの名前空間になります。 特別に何か言わない限り、この名前空間に属するということです。
もうひとつの名前空間指定には xmlns に続いて、コロン (:) x と書いてあります。
xmlns:x="http://xyz.com" のところです。この x はプリフィックス (接頭辞) です。こちらの名前空間に属するデータ要素には x という文字をつけて区別しますよ、ということです。
もとのデータに戻ってみてみましょう。
次の部分の employee は「デフォルトの名前空間にある employee」 ということになります。
一方、次の employee は x: がついているので、 デフォルトの名前空間ではなく、「xmlns:x="http://xyz.com" に属する employee」ということです。
データ構造が違うので、プログラムからの読み取り方もそれに合わせないといけないですし、 そもそも同じ名前の要素でも意味が違うかもしれません。
XmlDocument からノード選択する
それでは C# で書いたコードで、名前空間の指定のある XML を読み込んでみましょう。
使うデータはこちらの employees.xml です。
<?xml version="1.0" encoding="UTF-8"?>
<employees
xmlns="http://abc.com/"
xmlns:x="http://xyz.com">
<employee age="25" gender="F">
<name>
<first>Hanako</first>
<last>Yamada</last>
</name>
<title>Manager</title>
</employee>
<employee age="45" gender="M">
<name>
<first>Ichiro</first>
<last>Suzuki</last>
</name>
<title>CEO</title>
</employee>
<x:employee>
<x:firstname>Kevin</x:firstname>
<x:lastname>Smith</x:lastname>
<x:title>Sales Representative</x:title>
<x:age>30</x:age>
</x:employee>
</employees>
.NET Framework の XmlDocument を使いましょう。短いコードなので一度に全て載せます。
using System;
using System.Xml;
namespace TestApp {
class Program {
static void Main(string[] args) {
var xmlDoc = new XmlDocument();
xmlDoc.Load(@"employees.xml");
var nt = xmlDoc.NameTable;
var nsmgr = new XmlNamespaceManager(nt);
nsmgr.AddNamespace("a", "http://abc.com/");
nsmgr.AddNamespace("x", "http://xyz.com/");
var nodes = xmlDoc.SelectNodes("//a:employee", nsmgr);
foreach(XmlNode n in nodes) {
var fname = n.SelectSingleNode("a:name/a:first", nsmgr).InnerText;
var lname = n.SelectSingleNode("a:name/a:last", nsmgr).InnerText;
Console.WriteLine($"{fname} {lname}");
}
}
}
}
XmlNamespaceManager を使って、名前空間と読み込むときの接頭辞を登録しています。
これによって、XPath を指定するときに、どの名前空間のどの要素を取得するべきか明確にすることができます。
上のコードでは名前空間 "http://abc.com/" に対する接頭辞を "a" としているので、XPath で要素を指定するときに "a:" を付けるとこの名前空間の要素を選択することができます。
ちなみに、要素名は <接頭辞>:<ローカル名> として構成されています。 名前空間に関わらずローカル名が "employee" である要素を選択する場合の XPath は次のように指定できます。
var nodes = xmlDoc.SelectNodes("//*[local-name()='employee']", nsmgr);
XPath の [ ] の中身はフィルターです。
以上、名前空間が明示的に指定された XML 文書からデータを読み込む方法を説明しました。