そしてXPathに戻る

Linq to Xmlのメソッド群はXPathにそれぞれ対応する。XPathを覚えることなく、メソッドを繋げていくことで対象ノードが絞りこめる。これによる利点は、IntelliSenseが効くので高速にタイプセーフに思考の流れのまま記述出来るということ。例えばrootというXElementから「h1要素のうちクラス名がhogeの親をリストアップする」ならば

root.Descendants("h1")
	.Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "hoge")
	.Select(e => e.Parent);

……あれ? オカシイデスネ。簡潔とは程遠い勢いです。せめてWhereでのAttributeの参照をもう少し綺麗に書ければ……。nullが敵すぎて困る。そんな時のため、というわけではないのでしょうがSystem.Xml.XPath名前空間をインポートすることで、XElementにXPathSelectElementsという拡張メソッドが追加されます。これで、

root.XPathSelectElements("//h1[@class='hoge']/..");

と、簡潔明快に記述できる。ふむ。まあ、そりゃXPathのほうが簡潔になるのは「当たり前」ですもの。この程度ならまだいいけど、複雑怪奇になったときは(正規表現と同じように)解読出来ねえよ、と。個人的には全然LINQのほうが好きですよ!nullに気を遣う必要がなくなれば。ていうか、XPath->IEnumerableは別に普通にLinqですけど。やー、何かもうDescendants使う機会が無くなってしまう気がするなあ。

で、linq.jsではXPathの実装までやるつもりはありません。あー、解析してLinq形式に変換する程度ならやるかも。まあ、はっきしいって、愚直にイテレータ回してるので速度でいったら明らかに遅いんですけどね。翌々冷静に考えるとネタというか色物としかいえない仕上がりな気がしてきましたよ。みんなJavaScript-XPath使えばいいさ……。

そういえば、XAttributeのValueは代入可能で、当然代入すると即座に反映されるわけなんだけど、JavaScriptだとメソッドが擬態したプロパティって無理ですよね?Value()とSetValue()に分けるしかないのかなあ。XElementがその形なので違和感無いといえば無いんですけど微妙(そもそもXElementのほうもValueプロパティ一個でいいのに、何で分けたんだろう)。誰か良い案あればください。

今日のlinq.js

誰得状態になってもちゃんと書き進めてますので安心。あ、linq.jsダウンロード50到達ありがとうございます。次はLinq to Xml搭載で100ダウンロード到達を目指します。で、ええ、と、子ノードを全部削除するよくあるメソッドを作ろうと思ったのです。XElementにも当然あります、「RemoveNodes」です。

this.Elements().ForEach(function(e) { e.Remove() });

thisはXElementで、Removeは自身を親ノードから削除するというもの。Elements()は子ノードを列挙するものなので、そのままForEachで削除していけば出来あがり。ではないというよくある話を普通にやりました。たはー。列挙しているものの長さ(childNodes.length)がRemoveで変わっていってしまうんですね。

E.From(this.Elements().ToArray()).ForEach(function(e) { e.Remove() });

ToArray()でコピーを作って列挙。これなら問題なく動く。これがスナップショットって奴でしょうか? コピーのロスがあるので逆順forとかwhile(firstChild)使えよって話ではありますけど、今更速度どうこう言ってもしょうがないので普通に無視。と言いつつ、書き直しましたけど。ただ、prototype.jsでも内部で同じようなことやってるようなので大丈夫(何が?)

$A(element.childNodes).each(function(node) { element.removeChild(node) });

やっぱ、Arrayを直接拡張してeachが使えるとスッキリしていいですねえ。うーん、E.From(E.From(要素).ToArray())のショートカットメソッドが欲しいかもしれない。でも、名前が思いつかない。Cloneというのも変だし……。CopyDoとかどうでしょう、ダサいですかダサいですね。ていうか意味不明ですね。

Elements: function(name)
{
    var elems = Linq.Enumerable.From(this.Source.childNodes)
        .Where(function(elem) { return elem.nodeType === Linq.Xml.NodeType.Element });
    if (name != null)
    {
        elems = elems.Where(function(elem) { return Linq.Xml.Utils.StringCaseCompare(elem.nodeName, name); });
    }
    return elems.Select(function(elem) { return new Linq.Xml.XElement(elem); })
},

いやー、LINQは条件を好き勝手あとで継ぎ足していけるのがいいですよねー。で、再掲したのは前回のが間違っていたから。前回のElementsの例でpタグのものを取った時、TextNodeも取得しちゃってたのは完全に間違いです。TextNodeも含めたい時はNodes()メソッドを使い、Elements()ではNodeTypeがElementのものしか取れない。というように直しました。如何せんC#のLinq to Xmlの知識自体が欠けているので、Nodesメソッドを実装しようとしてから初めてこの事に気がついたという有様。Nodes()の戻り値はXNodeなんですね、XElementとXTextが混在するから……。

そういえば、要素名を大文字小文字を区別すべきか、しないほうがいいか悩み中。C#のLinq to Xmlでは当然区別しているんですが、区別するとWhere使うときにタグの大文字小文字区別が面倒くさいとかあるので、区別なしにしちゃおうかなー、とは思ってます。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive