XML から JSON を生成し、Ajax で使う

XSLT V2 を使って XML データを JSON (JavaScript Object Notation) に変換する

最近では、データ駆動の Web アプリケーションに対話性を追加するために、JavaScript コードを使うことが流行です。データを JSON (JavaScript Object Notation) としてエンコードできれば、そうしたコードを JavaScript 言語で簡単に使えるようになります。XSLT V2 を使って XML データから JSON を生成する様々な手法について学びましょう。

Jack Herrington (jherr@pobox.com), Editor-in-Chief, Code Generation Network

Jack D. Herringtonは、20年以上の経験を持つシニア・ソフトウェア・エンジニアです。著者には、「Code Generation in Action」、「Podcasting Hacks」、そして近々刊行予定の「PHP Hacks」の3冊があります。彼は30本以上の技術記事も執筆しています。



2006年 9月 12日

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 の紹介

データを 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 の出力
図 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 の結果
図 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 クラスは、本来は setget のようなアクセサーを持つべきです。リスト 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 articlex-xml2json-samplecode.zip7KB

参考文献

学ぶために

製品や技術を入手するために

  • 皆さんの次期開発プロジェクトを IBM trial software を使って構築してください。developerWorks から直接ダウンロードすることができます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Web development
ArticleID=252757
ArticleTitle=XML から JSON を生成し、Ajax で使う
publish-date=09122006