C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

.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.XElementElement / Elements を使おうとしたら…

1. bbbbb:ZElement しようとしたら System.Exception 例外が "The ':' character, hexadecimal value 0x3A, cannot be included in a name." とか Message に入って飛んだ場合

例外の Message に書いてあるままなんだけど、要素名 bbbbb:ZXElement へアクセスしようと以下のようなコードを書くと発生する:

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. BElements しようとしたら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 ) を扱うよう実装を書いてあげないと期待動作しないのでした。

めんどくさいなぁ…と思いながら、また数カ月後とかに使う機会があったときに「なんでだっけ…🤔」とかなりそうな予感がしたのでメモ。

Reference