ltxml.js - LINQ to XML for JavaScript
- 2012-10-24
以前、linq.js ver.3のセッションの時に、ちょびっとだけ触れたLINQ to XML for JavaScriptが公開されました!
作者は私ではなく、Eric White氏です。誰?ってことですが、元Microsoftの人です。氏のMS時代のMSDNブログのLINQ to XMLタグには超DEEPなLINQ to XMLの使いこなしが書いてあって必見。というわけで、非常にLINQ to XMLに詳しい、というか、MSDNのLINQ to XMLの解説ドキュメントを書いたのは氏だそうです。なので、詳しいとかそういう次元じゃなく、普通に中の人ということですね。
概要
そんなわけで、ltxml.jsとは、C#のXML操作ライブラリであるLINQ to XMLのJavaScript移植です。C#のLINQ to XMLがLINQ to Objectsの上に構築されている(ElementsやDescendantsの戻り値がIEnumerableとなり、LINQベースのクエリ操作となる)ように、ltxml.jsはLINQ to ObjectsのJavaScript移植であるlinq.jsの上に構築されています。ltxml.jsのelementsやdescendantsは、linq.jsのwhereやselectなどのクエリ操作によってXMLを展開できます。
C#版と構造はほとんど一緒です。ただし、JavaScriptの慣習に則りメソッド名がlowerCamelCaseであることと、プロパティが.getHoge()であること(ただしECMAScript 5に対応しているならば.hogeといったようにプロパティでアクセスできます)、オペレーターオーバーロードが存在しないことによる挙動の違い程度です。また、C#版よりも機能向上している面もあります。それは、私がlinq.jsにおいてC#のLINQ to Objectsで物足りないと思った機能を追加していたようなもの、でしょうか、多分ね、きっと。
また、パフォーマンス上の考慮により、descendantsなどは、デフォルトは即時実行で配列(をEnumerableでラップしたもの)を返します。.lazy = trueをXElementなどに投下することで、完全な遅延実行になります。もし巨大なXMLを扱うのならば、遅延実行が効果を発揮するでしょう。通常考えられるサイズのXMLならば、デフォルトのとおり即時実行のほうが良好だと思われます。
使い方
ぶっきらぼうにも、ドキュメントがほとんどないですね!まあ、それは追々紹介されていくことでしょう。ともあれ現状は、ファイルをダウンロードするとltxmlTest.htmlというファイルがあって、それがユニットテスト兼サンプルになっているので、とりあえずそれを読みましょう。また、JavaScript特有の違いはあるものの、基本的にはC#のそれと等しいので、MSDNのLINQ to XMLの解説ドキュメントがまんま使えないこともないです。
ともあれ、まずは簡単なXMLをパースしてみましょう。
var xml =
"<Contacts>\
<Contact>\
<Name>Peter Hage</Name>\
<Phone>206-555-0144</Phone>\
</Contact>\
<Contact>\
<Name>John Hoge</Name>\
<Phone>106-333-2222</Phone>\
</Contact>\
</Contacts>";
// parseでただの文字列からLINQ to XMLのXElementに変換
var xElem = Ltxml.XElement.parse(xml);
// 子孫ノードのNameを選択し、値だけ抽出
var names = xElem.descendants("Name")
.select(function (x) { return x.getValue(); })
.toArray();
alert(names); // Peter Hage, John Hoge
descendants.selectといったように、LINQです!完全に!これをLINQと言わずして何をLINQと言うか!
名前空間
ltxml.jsの全てのクラスはLtxmlオブジェクトの下に格納されています。グローバルを汚さない。しかし、いちいちLtxml.XElementなどと呼ぶのは面倒くさい話です。以下のようなショートカットを先頭に用意するのをお薦めします。
var XAttribute = Ltxml.XAttribute;
var XCData = Ltxml.XCData;
var XComment = Ltxml.XComment;
var XContainer = Ltxml.XContainer;
var XDeclaration = Ltxml.XDeclaration;
var XDocument = Ltxml.XDocument;
var XElement = Ltxml.XElement;
var XName = Ltxml.XName;
var XNamespace = Ltxml.XNamespace;
var XNode = Ltxml.XNode;
var XObject = Ltxml.XObject;
var XProcessingInstruction = Ltxml.XProcessingInstruction;
var XText = Ltxml.XText;
var XEntity = Ltxml.XEntity;
var XEnumerable = Ltxml.XEnumerable;
また、C#版ではEnumerableへの拡張メソッドとして用意されていた幾つかのメソッドは、ltxml.jsではEnumerableに追加されているasXEnumerableを呼び、XEnumerableへと変換することで、呼び出すことができます。しかし、もしそれを手間だと思う場合は、linq.jsのEnumerableを拡張することで、よりスムーズに接続することが可能です。ただし、C#版ではジェネリックによって区別されていましたが、JavaScriptではジェネリックが存在しないので、汎用性のないシーケンスの要素がltxml.jsに固有でなければならないメソッドをEnumerableに追加することとなります。また、removeなどは、他の人の拡張と名前が衝突する可能性が高いことなどにも注意。
Enumerable.prototype.elements = Ltxml.XEnumerable.prototype.elements;
Enumerable.prototype.ancestors = Ltxml.XEnumerable.prototype.ancestors;
Enumerable.prototype.ancestorsAndSelf = Ltxml.XEnumerable.prototype.ancestorsAndSelf;
Enumerable.prototype.attributes = Ltxml.XEnumerable.prototype.attributes;
Enumerable.prototype.descendantNodes = Ltxml.XEnumerable.prototype.descendantNodes;
Enumerable.prototype.descendantNodesAndSelf = Ltxml.XEnumerable.prototype.descendantNodesAndSelf;
Enumerable.prototype.descendants = Ltxml.XEnumerable.prototype.descendants;
Enumerable.prototype.descendantsAndSelf = Ltxml.XEnumerable.prototype.descendantsAndSelf;
Enumerable.prototype.elements = Ltxml.XEnumerable.prototype.elements;
Enumerable.prototype.nodes = Ltxml.XEnumerable.prototype.nodes;
Enumerable.prototype.remove = Ltxml.XEnumerable.prototype.remove;
私個人としては、Enumerableへの拡張はそんなに薦められないかな、という感じですが、ヘヴィにXMLを処理する局面では、拡張したほうがサクサク書けて良いのではかとも思います。この辺は好みでどうぞ。
関数型構築
XMLを作るときは、コンストラクタで可変長引数として連鎖させます。これをLINQ to XMLでは関数型構築と呼んでいます。
var xml =
new XElement("root",
new XElement("user", new XAttribute("id", 1),
new XElement("age", 100)));
// <root><user id = '1'><age>100</age></user></root>
alert(xml.toString()); // toStringで文字列化
閉じタグが不要であったり、安全であったり(JavaScriptだってカッコ閉じ忘れとかは警告入るからね)と、生文字列で組み立てるのに比べて、遥かに利点があります。また、要素がlinq.jsのEnumerableである場合も、きちんと展開されます。
// C#と同様にEnumerable<XElement>は展開される
var users = Enumerable.range(1, 10)
.select(function (x) {
return new XElement("user", new XAttribute("id", x),
new XElement("age", x * x));
});
var xml = new XElement("root", users);
// <root>
// <user id = '1'>
// <age>1</age>
// </user>
// <user id = '2'>
// <age>4</age>
// </user>
// (略)
// </root>
alert(xml.toString(true)); // 引数にtrueを渡すとインデントつけて整形
どうでしょう、イメージつきます?
用途
Eric White氏がOpenXML(Officeのxlsxとかがそれ)の専門家ということで、JavaScript上でOfficeファイルを容易に扱うことが可能になるでしょう。つまり、サーバーサイドでのコンバート処理など不要に、JavaScriptだけでビューワーであたり要素抽出であったりが、完結する未来があります。なんて興奮する話でしょう!
とはいえ、それはあまりにも専門的すぎて、実に面白いし役立つでしょうけれど、実際にそれでもって作り上げる側に周るのは極少数の人に違いありません。では、他にXMLを使う局面なんてあるのか?ふむ……。恐らく、ブラウザ上で動くアプリケーションにとって機会はないでしょう、どこもかしこもJSONに集約される!AJAXのXはXMLのX!だった時もありました。いや、今もそうでしょうけれど。
では、どこに?というと、Node.jsはどうだろう?結局、未だにXMLのサービスなどは多いのだ。RSSはJSONにならないでしょう。サーバーサイドで行うならXMLは無視できないのだ。またはクライアントアプリでも、TitaniumやPhoneGapはどうだ?またはWindows 8のアプリケーションはJavaScriptで作ることができる。そこではまだまだXMLは現役に違いない。JavaScriptの活躍範囲がブラウザを超えていけばいくほど、残り続けるXMLに遭遇する機会は増える。
AtomPub(ああ!今はもうあまり名前を聞かない!)の構築に、LINQ to XMLの関数型構築は役に立つことでしょう。とにかく言えることは、XMLは決して死んでいないし、望まれるか望まれないかはともかくとして、生き残り続けるでしょう。そのために、私達には強力な武器が必要なのです、LINQ to XMLという。
もしくは、単純にHTMLビルダーとして使ったっていいかもしれない。HTMLはXMLなのだもの、ええ、大丈夫、そのようにも使えます。文字列連結してHTMLを組み立てるなんてしなくていい。また、もちろん、HTMLのDOM操作にだって、ね(でもDOMの操作ならば、きっとjQuery使いますね)
未来
ところでしかし現状ltxml.jsはベータです。何故か。linq.jsがまだベータだからです。ver.3.0.3-Beta4ってなんだよクソが。すみませんすみません、なるべく早く正式リリース出来るよう鋭意努力します。NEETなので暇、じゃあないんですよねえ、残念ながら。でも急ぎます。さすがに!いやほんと私の作業ペースの遅さには我ながらどうかと思う感じですが、もうさすがに猶予ないですね!
ちなみに7月31日に作ってるよー、ってメール貰って、そこから何度かやり取りしてました。ltxml.jsのコード自体、かなりパワフルにlinq.jsを使いこなしているので(私のアドバイスの賜物です!)そういう意味でも面白いですね。ちなみに、その時には8月中にlinq.jsリリースするって言ってたかなあ、今もう10月末ですねえ、どうなってるんでしょうねえ、ごめんなさいごめんなさい。