2、3 年前、多くの開発者は、将来を XML や XSLT、XHTML (Extensible HTML)、その他各種のタグ・ベースの「X」言語に賭けていました。今や、新たな流行は AJAX (Asynchronous JavaScript and XML) であり、投資家の目は、JavaScript コードを使うデータ駆動のリッチ・インターネット・アプリケーションに向かいつつあります。しかし開発者は、XML とこうした新技術とのギャップを埋めたのでしょうか。
確かに、Web クライアントで XML パーサーを使えばデータを読むことはできますが、この手法には 2 つの問題があります。第 1 に、セキュリティー上の理由から、XML データは、そのページと同じドメインからしか読むことはできません。これは大きな制約要素ではありませんが、デプロイメントの際に頭痛の種となり、DHTML ウィジェット作成の障害になります。第 2 に、XML の読み取りや構文解析は遅いのです。
別の選択肢として、XML の構文解析作業をサーバーに行わせる方法があります。つまりサーバーを設定することによって、データを JavaScript コード、あるいはもっと流行の言い方をすれば、JSON (JavaScript Object Notation) としてエンコードしてブラウザーに送るのです。この記事では、XSLT V2 言語と Saxon XSLT V2 プロセッサーを使って、下記の 3 つの方法で XML データから JSON を生成します。
- 単純なエンコーディングによる方法
- 関数コールでデータをロードする方法
- オブジェクトをエンコードする方法
データを JSON (単に JavaScript のサブセットにすぎません) としてエンコードする方法を学ぶためには、まず何らかのデータが必要です。リスト 1 は、本のリストを持った XML データ・セットの例を示しています。
リスト 1. 基本的なグラフィックス・ライブラリー
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="1">
<title>Code Generation in Action</title>
<author><first>Jack</first><last>Herrington</last></author>
<publisher>Manning</publisher>
</book>
<book id="2">
<title>PHP Hacks</title>
<author><first>Jack</first><last>Herrington</last></author>
<publisher>O'Reilly</publisher>
</book>
<book id="3">
<title>Podcasting Hacks</title>
<author><first>Jack</first><last>Herrington</last></author>
<publisher>O'Reilly</publisher>
</book>
</books>
|
このデータ・セットは単純であり、3 冊の本を含んでいます。それぞれの本は固有の ID とタイトル、著者の姓名、そして出版社を持っています。(私はこのデータ・セットに、恥ずかしげもなく私の本だけを選んで宣伝していますが、私のことを非難できますか。これらの本は年末や誕生日のお祝いに最適なのです。)
リスト 2 は、これが JSON ではどうなるかを示しています。
リスト 2. JSON でのサンプル・データセット
[ { id: 1,
title: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning' },
... ]
|
大括弧 ([]) は配列を示しています。中括弧 ({}) はハッシュ表を示しています。ハッシュ表は、名前と値のペアーのセットです。この場合はハッシュ表の配列を作成しています。これは、このデータのような構造化データを保存する方法として一般的なものです。
ストリングが、単一引用符、あるいは二重引用符を使ってエンコードされていることに注意してください。つまり単一引用符で囲まれるストリングの中で O'Reilly をエンコードしたい場合には、'O\'Reilly' のように、バックスラッシュを使って単一引用符をエスケープする必要があります。これによって、私の書く XSLT スタイルシートが、少しばかり面白くなります。
この例には何も日付を入れませんでしたが、日付は次の 2 つの方法のいずれかでエンコードすることができます。最初の方法は、ストリングとしてエンコードし、後で構文解析する方法です。2 番目の方法は、下記のようにオブジェクトとしてエンコードする方法です。
publishdate: new Date( 2006, 6, 16, 17, 45, 0 ) |
このコードは、publishdate の値を 7/16/2006 at 5:45:00 p.m. に設定します。
ここでは JSON エンコードの手法として、いくつかを紹介します。最初の手法が最も簡単です。このスタイルシートをリスト 3 に示します。
リスト 3. simple.xsl スタイルシート
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:js="http://muttmansion.com">
<xsl:output method="text" />
<xsl:function name="js:escape">
<xsl:param name="text" />
<xsl:value-of select='replace( $text, "'", "\\'" )' />
</xsl:function>
<xsl:template match="/">
var g_books = [
<xsl:for-each select="books/book">
<xsl:if test="position() > 1">,</xsl:if> {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)" />',
first: '<xsl:value-of select="js:escape(author/first)" />',
last: '<xsl:value-of select="js:escape(author/last)" />',
publisher: '<xsl:value-of select="js:escape( publisher )" />'
}</xsl:for-each>
];
</xsl:template>
</xsl:stylesheet>
|
この、simple.xsl スタイルシートを理解するには、まずリスト 4 に示す出力を見た方が簡単です。
リスト 4. simple.xsl の出力
var g_books = [
{
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
}, {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}, {
id: 3,
name: 'Podcasting Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}
];
|
ここでは、g_books という変数を、3 つのハッシュ表を含む配列に設定しています (各ハッシュ表は、その本に関する情報を含んでいます)。前に示したリスト3 を見るとわかるように、最初のテンプレートが「/」というパスに一致するテンプレートであり、これは入力データ・セットに適用される最初のテンプレートです。このテンプレートは、for-each ループを使って各ブロックをウォークスルーします。次に、<value-of> タグを使って、データからのテキストを出力 JavaScript コードに出力します。
これらのストリングの場合、私は js:escape() というカスタム関数を使っています。これは、このテンプレートの直前に定義されています。この関数は、正規表現を使って、単一引用符をバックスラッシュ付きの単一引用符に変更します。
最後の重要要素は、<xsl:output> タグです。このタグはプロセッサーに対して、XML ではなくテキストを出力したい、と伝えます。私はこのプロセスが動作するかどうかを見るために、simple.js というファイルの中に保存した XSL スタイルシートの出力を参照する、単純な .html ファイルを作りました。この HTML をリスト 5 に示します。
リスト 5. simple.html ファイル
<html>
<head>
<title>Simple JS loader</title>
<script src="simple.js"></script>
</head>
<body>
<script>
document.write( "Found "+g_books.length+" books" );
</script>
</body>
</html>
|
この .html ファイルは、エンコードされた JavaScript コードを、最初の <script> タグを使って単純にロードします。次に、2 番目の <script> タグは、配列の長さをブラウザー・ページに書き出します (図 1)。
図 1. simple.html の出力
素晴らしい!データ・ファイルは 3 冊の本を含み、それに対応する JavaScript ファイルは 3 冊の本を含んでいます。うまくいったのです!
最初の例は非常に単純であり、多くの状況で使えるものですが、いくつかの問題も抱えています。最初の問題は、データがいつロードされたのか示すものがないことです。これは、このページのように、データが静的にロードされる場合には問題ありません。しかし、そのページが <script> タグをすぐに作成し、リクエストに応じて即座にデータをロードする場合には、いつ、その <script> タグが完了したのかを知ることは重要です。そのためには、単にデータを設定するのではなく、エンコードされたデータが JavaScript 関数を呼び出すようにするのが一番です。
この考え方は重要なので、前に戻り、なぜ動的に生成された <script> タグによってデータをロードできる必要があるのかを少し説明しましょう。ページがロードされた後でサーバーからデータを取得することは、Web 2.0 の核心です。このための 1 つの方法として、AJAX 機構を使い、サーバーへのコールによって XML をロードする方法があります。しかしセキュリティー上の理由から、この AJAX 機構は、1 つのドメインからしかデータを取得できないように制限されています。これは大部分の状況では問題ありませんが、場合によると、他の人のページ上で (例えば Google Maps で)、その JavaScript を実行したいこともあるものです。
こうした場合にサーバーからデータを取得するための唯一の方法は、<script> タグを動的にロードすることです。そして、いつ <script> タグがロードされたのかを知る手段としては、単純にデータをロードするのではなく、<script> タグが返すスクリプトに関数をコールさせるのが一番です。リスト 6 は、関数コールでエンコードされたデータを示しています。
リスト 6. Function1.js
AddBooks( [
{
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
}, {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}, {
id: 3,
name: 'Podcasting Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}
] );
|
リスト 7 は、これに対応する .html ファイルを示しています。
リスト 7. Function1.html
<html>
<head>
<title>Function 1 JS loader</title>
<script>
var g_books = [];
function AddBooks( books ) { g_books = books; }
</script>
<script src="function1.js"></script>
<script src="drawbooks.js"></script>
</head>
<body>
<script>drawbooks( g_books );</script>
</body>
</html>
|
drawbooks 関数については、すぐ後で説明します。ここで重要なことは、このページがどのように AddBooks 関数を定義しているかを調べることです。AddBooks 関数は定義されると、function1.js ファイルの中のスクリプトによってコールされます。この AddBooks 関数は、データをどう処理すべきかを判断します。そして AddBooks 関数はコールされると、<script> タグが適切にロードされ、ローディングが完了したことをページに対して伝えます。
function1.js ファイルを作成するためにスタイルシートに加えた変更は、ごくわずかです (リスト 8)。
リスト 8. function1.xsl スタイルシート
<xsl:template match="/">
AddBooks( [
<xsl:for-each select="books/book">
<xsl:if test="position() > 1">,</xsl:if> {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)" />',
first: '<xsl:value-of select="js:escape(author/first)" />',
last: '<xsl:value-of select="js:escape(author/last)" />',
publisher: '<xsl:value-of select="js:escape( publisher )" />'
}</xsl:for-each>
] );
</xsl:template>
|
つまり、単純に変数を設定する代わりに、関数を呼び出すのです。変更は、これだけです。
今度はページに戻り、データが適切にエンコードされ、適切に表示されるように、drawbooks 関数を使って本の表を作ります。この関数は drawbooks.js の中で定義されています (リスト 9)。
リスト 9. Drawbooks.js
function drawbooks( books )
{
var elTable = document.createElement( 'table' );
for( var b in books )
{
var elTR = elTable.insertRow( -1 );
var elTD1 = elTR.insertCell( -1 );
elTD1.appendChild( document.createTextNode( books[b].id ) );
var elTD2 = elTR.insertCell( -1 );
elTD2.appendChild( document.createTextNode( books[b].name ) );
var elTD3 = elTR.insertCell( -1 );
elTD3.appendChild( document.createTextNode( books[b].first ) );
var elTD4 = elTR.insertCell( -1 );
elTD4.appendChild( document.createTextNode( books[b].last ) );
var elTD5 = elTR.insertCell( -1 );
elTD5.appendChild( document.createTextNode( books[b].publisher ) );
}
document.body.appendChild( elTable );
}
|
この単純な関数は、表ノードを作成し、次に本のリストに対して繰り返しを行い、それぞれの本に対して、各データ要素に対して 1 つのセルを持つ行を作成します。このページに対するコードの結果を、図 2 に示します。
図 2. function1.html の結果
これで、このページの出力を見ると、オリジナルの .xml ファイルにあったすべてのものが正しくJavaScript コードに変換され、データが AddData 関数に送られ、ページに適切に追加されていることがわかります。
私は関数コールの手法は好きなのですが、本の全データが 1 つのブロックとして入ってしまうことはあまり感心できません。別の選択肢として、レコード毎にコールするようにします (リスト 10)。
リスト 10. Function2.js
AddBook( {
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
} );
AddBook( {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
} );
...
|
.html ページには、ごくわずかな変更しか必要ありません (リスト 11)。
リスト 11. Function2.html
...
<script>
var g_books = [];
function AddBook( book ) { g_books.push( book ); }
</script>
...
|
XSLT は変更され、for-each ループのボディーの中で関数が呼び出されています。リスト 12 は更新されたスタイルシートを示しています。
リスト 12. function2.xsl
...
<xsl:template match="/">
<xsl:for-each select="books/book">
AddBook( {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)" />',
first: '<xsl:value-of select="js:escape(author/first)" />',
last: '<xsl:value-of select="js:escape(author/last)" />',
publisher: '<xsl:value-of select="js:escape( publisher )" />'
} );</xsl:for-each>
</xsl:template>
...
|
この例の場合で考えると、適当に変更したように見えるかもしれません。しかしオリジナルの XML データ・セットに様々なデータ型が含まれている場合には、それぞれの型を別の関数コールで実現した方が、そのページ上の XSL コードも JavaScript コードも単純になり、維持管理も容易になるのです。
小さなページであれば、JavaScript 関数を使うことには問題はありません。しかし大規模なプロジェクトでは、JavaScript 言語のオブジェクト指向機能を使うことを考えるべきでしょう。そう、JavaScript 言語はオブジェクトを扱うことができ、しかもうまく扱えるのです。
リスト 13 は、データを持つオブジェクトの作成の様子を示しています。
リスト 13. Object.xsl
g_books.push( new Book( {
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
} ) );
g_books.push( new Book( {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
} ) );
|
この場合は、gg_books という配列に単純に Book オブジェクトを追加しています。JavaScript でのオブジェクト作成は、Java™や C#、あるいは C++ などのプログラミング言語の場合と似ています。新しい演算子の後に、クラス名が続きます。次に、引数は括弧の中に入ります。この場合は、いくつかの値を持つ1 つのハッシュ表を入れています。これらの値は、別々のパラメーターとして分割することも簡単にできます。
このオブジェクトを作成するためのコードをリスト 14 に示します。
リスト 14. Object1.xsl
<xsl:template match="/">
<xsl:for-each select="books/book">
g_books.push( new Book( {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)" />',
first: '<xsl:value-of select="js:escape(author/first)" />',
last: '<xsl:value-of select="js:escape(author/last)" />',
publisher: '<xsl:value-of select="js:escape( publisher )" />'
} ) );</xsl:for-each>
</xsl:template>
|
もっと面白いコードは、Book クラスが定義されているページにあります。リスト 15 は、このページを示しています。
リスト 15. object1.html
...
<script>
var g_books = [];
function Book( data )
{
for( var d in data ) { this[d] = data[d]; }
}
</script>
...
|
Book クラスに対するコンストラクターは、ハッシュ表の中のデータに対して繰り返しを行います。各キーによって、その名前とデータを持つインスタンス変数がオブジェクトに対して作成されます。すべてのオブジェクトは、オリジナルのハッシュ表と同じ鍵と値を持っているため、drawbooks 関数には何も変更が必要ありません。そして JavaScript 言語は、ハッシュ表の中の名前付き値へのアクセスと、オブジェクト上の名前付き値へのアクセスとを区別しないのです。
もちろん、Book クラスは、本来は set や get のようなアクセサーを持つべきです。リスト 16 は、私がどのように JavaScript データをエンコードしたいかを示しています。
リスト16. Object2.js
ar b1 = new Book();
b1.setId ( 1 );
b1.setTitle ( 'Code Generation in Action' );
b1.setFirst ( 'Jack' );
b1.setLast ( 'Herrington' );
b1.setPublisher ( 'Manning' );
g_books.push( b1 );
var b2 = new Book();
b2.setId ( 2 );
b2.setTitle ( 'PHP Hacks' );
...
|
そうです。この方がそれらしく見えます。私はオブジェクトを作成し、その値を設定し、そしてこのオブジェクトを配列に追加し、等々です。そのためにはまず、スタイルシートに対して、もっと大きな変更を加えます (リスト 17)。
リスト 17. Object2.xsl
...
<xsl:function name="js:createbook">
<xsl:param name="book" />
<xsl:variable name="b" select="concat( 'b', $book/@id )" />
var <xsl:value-of select="$b" /> = new Book();
<xsl:value-of select="concat( $b, '.setId' )" />
( <xsl:value-of select="$book/@id" /> );
<xsl:value-of select="concat( $b, '.setTitle' )" />
( '<xsl:value-of select="js:escape( $book/title )" />' );
<xsl:value-of select="concat( $b, '.setFirst' )" />
( '<xsl:value-of select="js:escape( $book/author/first )" />' );
<xsl:value-of select="concat( $b, '.setLast' )" />
( '<xsl:value-of select="js:escape( $book/author/last )" />' );
<xsl:value-of select="concat( $b, '.setPublisher' )" />
( '<xsl:value-of select="js:escape( $book/publisher )" />' );
</xsl:function>
<xsl:template match="/">
<xsl:for-each select="books/book">
<xsl:value-of select="js:createbook(.)" />
g_books.push( b<xsl:value-of select="@id" /> );
</xsl:for-each>
</xsl:template>
...
|
ここでは、createbook という新しい関数を定義しました。この関数は book オブジェクトを作成し、それぞれの本のテンプレートで呼び出されます。createbook 関数は、ストリングが適切にエンコードされるように、やはり escape 関数をコールします。
一方 HTML 側では、Book クラスにさらにメソッドを追加し、エンコードされた JavaScript コードがそれらをコールできるようにする必要があります。こうした新しいメソッドをリスト18 に示します。
リスト 18. Object2.html
...
<script>
var g_books = [];
function Book() { }
Book.prototype.setId = function( val ) { this.id = val; }
Book.prototype.setTitle = function( val ) { this.name = val; }
Book.prototype.setFirst = function( val ) { this.first = val; }
Book.prototype.setLast = function( val ) { this.last = val; }
Book.prototype.setPublisher = function( val ) { this.publisher = val; }
</script>
...
|
このプロトタイプ機構は、JavaScript 言語に特有のものです。この言語の各オブジェクトは、それ自体が個別のエンティティーであり、独立に設定可能な独自のデータと関数を持っています。あるクラスの各オブジェクトは、同じプロトタイプを持っています。そのため、すべてのクラスで共有するメソッドを作成するためには、オブジェクトに対してだけではなく、プロトタイプに対しても関数を設定します。
XML で保存されたデータを JavaScript コードとしてエンコードする手法はいくつかあり、どの方法を使っても構いません。どのようにデータをエンコードするかは、Web 2.0 アプリケーションの設計や、ページ上に置いた後にデータをどう扱うかに依存します。重要なことは、生成される動的な JavaScript 言語を最大限に活用する、ということです。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Sample code used in this article | x-xml2json-samplecode.zip | 7KB | HTTP |
学ぶために
-
Json.org には、JavaScript Object Notation に関する有用な情報やライブラリー、リンクなどが用意されています。
-
「
Mastering Ajax, Part 1: Introduction to Ajax」(Brett McLaughlin、developerWorks、2005年12月) を読み、Web サイト構築のための生産的な手法、Ajaxの使い方を学んでください。
-
Ajax に関するdeveloperWorks のチュートリアルや記事のリストを利用して、Ajax や関連の技術を学んでください。
- Ecma International の定義による JavaScript standard を調べてみてください。
-
XSLT 2.0 standard を調べ、XML 文書を他の XML 文書に変換するための、この標準言語の構文や意味体系について学んでください。この標準は W3C (World Wide Web Consortium) によって維持管理されています。
- この記事の著者が使用しているプロセッサー、Saxon XSLT 2.0 プロセッサーについて学んでください。
-
XML 1.1 および関連技術において IBM 認証開発者になる方法についてはこちらを参照してください。
- developerWorks の XML ゾーンには、広範な話題を網羅した技術記事やヒント、チュートリアル、技術標準、IBMレッドブックなどが豊富に用意されています。
-
developerWorks technical events and webcasts で最新情報を入手してください。
製品や技術を入手するために
- 皆さんの次期開発プロジェクトを IBM trial software を使って構築してください。developerWorks から直接ダウンロードすることができます。
議論するために
-
ディスカッション・フォーラムに参加してください。
-
developerWorks blogs から developerWorks コミュニティーに加わってください。
-
XML zone discussion forums では、XML を中心とした話題の議論が行われています。