昔、10年ぐらい前だったか C++ でこんなアイデアを思いつきました。
1 2 3 4 | < members > < person name = 'masuda' age = '40' >masuda tomoaki</ person > < person name = 'yamada' age = '20' >yamada taro</ person > </ members > |
こんな風な XML があったときに、
1 | XmlElement *el = doc/members/person@name == "masuda" ; |
のように、XPath 風にデータを取得できたら楽だろう、と思ったわけです。
実際は、members や person は、文字列になるので、
1 | XmlElement *el = doc/ "members" / "person" @ "name" == "masuda" ; |
な感じでコンパイルが通らないかなぁ、と。これをどうやって実現するかというと、【演算子】のオーバーロード機能を使います。
/ 演算子をオーバーライドして、
1 2 3 4 | list<XmlElement*> operator / ( list<XmlElement*> *els, string ) { ... } |
のように演算子の多重定義すればできるかなぁ、と。それで、10 年越しに C# で実現してみました。
残念ながら @ 演算子がないので、% 演算子で代用して、
1 | EXElement el = doc/ "members" / "person" % "name" == "masuda" ; |
とすると、name 属性の値の要素が取れます。
更に言えば、
1 | string val = doc/ "members" / "person" % "name" == "masuda" ; |
のように string 型の変数に代入することで自動的に “masuda tomoaki” が取れるようになります。
このあたりの自動キャストは implicit で暗黙のキャストを使います。普段はキャストを暗黙にすると問題が発生しそうでやめておくのですが、こんな風に LINQ っぽく書く(あるいは関数言語っぽい書き方)をするおきは、自動キャストをうまく考えておくと、処理する行が短くなります。
# ちなみに == も演算子の多重定義をして、EXElements を返す処理にしていますw
そんな訳で、C# で次のコードを動かせるようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // person タグをコレクションで取得 EXElements els = doc / "members" / "person" ; // ひとつしかないと分かっていれば、単数で取得 EXElements el = doc / "members" / "person" ; // どうせだから、直接、文字列に変換してしまう。 string [] vals = doc / "members" / "person" ; string val = doc / "members" / "person" ; // 比較演算子を使って、マッチングもできる EXElements els = doc / "members" / "person" % "name" == "masuda" ; // 複数の属性で検索したい場合は、ラムダ式を使う。 EXElements els = (doc / "members" / "person" ).Find( m => m % "name" == "masuda" && m % "age" == "40" ) // 子孫を検索するときは、* 演算子を使う. EXElements els = doc * "members" / "person" % "name" == "masuda" ; |
こんな感じで、なにやら不思議なものができたので、後でアップします。
ええ、当然のごとく NUnit を使ってテストをしています。と言いますか、この手の複雑なロジックは NUnit を使わないと仕上がりません。