目次


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

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

Comments

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 の出力
図 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 の結果
図 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 言語を最大限に活用する、ということです。


ダウンロード可能なリソース


関連トピック

  • 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レッドブックなどが豊富に用意されています。
  • 皆さんの次期開発プロジェクトを IBM trial software を使って構築してください。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