.net の System.Xml.Linq の XDocument/XElement で Element/Elements メソッドの挙動が意図せず null になったり空になったり、あるいはそもそも名前空間を与えたつもりの実装で例外が飛んだりした時に思い出すため、のメモ
<?xml version="1.0" encoding="utf-8"?> <A xmlns:bbbbb="http://example.com/bbbbb" xmlns="http://example.com/aaaaa" > <bbbbb:Z>z-value</bbbbb:Z> <B>b-value-0</B> <B>b-value-1</B> <B>b-value-2</B> </A>
とかなんとか適当に XML があって、 System.Xml.Linq.XDocument
/ System.Xml.Linq.XElement
の Element
/ Elements
を使おうとしたら…
1. bbbbb:Z
を Element
しようとしたら System.Exception
例外が "The ':' character, hexadecimal value 0x3A, cannot be included in a name."
とか Message
に入って飛んだ場合
例外の Message
に書いてあるままなんだけど、要素名 bbbbb:Z
の XElement
へアクセスしようと以下のようなコードを書くと発生する:
void f( XDocument x ) { // 例外はいてしぬ var z = x.Element( "bbbbb:Z" ); }
名前空間を付けた要素名を与えたい場合は:
void f( XDocument x ) { // string => XNamespace は暗黙変換される XNamespace ns_bbbbb = "http://example.com/bbbbb"; // ( XNamespace + string ) => XName は operator が定義されている XName name_bbbbb_z = ns_bbbbb + "Z"; // Element の1引数版は XName を引数にしている(先の例では string => XName が暗黙変換されていた) var z = x.Element( name_bbbbb_z ); }
こうすると死なない。というか、こうしないとならない仕組み。実際問題でいうとめんどくさいけどまあそれはこの際オイトイテ💁
2. B
を Elements
しようとしたら1個も取れないし Element
したら null
だった場合
void f( XDocument x ) { // bs は要素が空の列挙になる var bs = x.Elements( "B" ); // b0 は null になる var b0 = x.Element( "B" ); // ところがこうして要素名を指定せずに列挙すると B も取り出せてしまう"怪奇現象"かのような事が起こる(実際は仕様であって怪奇現象ではない) foreach ( var e in x.Elements() ) System.Console.Error.WriteLine( e.Value ); }
冒頭の今回扱っている例示の XML では xmlns
によりデフォルトの名前空間を "http://example.com/aaaaa"
と定義している。これを System.Xml.Linq
でも明示的に実装しないと↑のように空になったり null
になったりする。
void f( XDocument x ) { // デフォルトの名前空間を明示的に与えるためのデフォルトなので見えない名前空間をきっちり定義してあげる XNamespace ns_default = "http://example.com/aaaaa"; // 意図通り要素を今回の例なら3つ取れる var bs = x.Elements( ns_default + "B" ); // 意図通り先頭の要素を取れる var b0 = x.Element( ns_default + "B" ); }
「デフォルトとは…」みたいなお気持ちになるけど System.Xml.Linq
はそういう仕様なのでこれもめんどくさいけどわざわざ明示的にデフォルトの名前空間の要素名 ( XName
) を扱うよう実装を書いてあげないと期待動作しないのでした。
めんどくさいなぁ…と思いながら、また数カ月後とかに使う機会があったときに「なんでだっけ…🤔」とかなりそうな予感がしたのでメモ。