目次


XML: GWT と PHP とのブリッジ

GWT で PHP によるサービスを使用し、データの交換を容易にするためのブリッジとして XML を活用する

Comments

GWT を利用すると、Java 言語でプログラミングされたサーバー・サイドのサーブレットに容易にアクセスすることができ、データはユーザーにはわからないようにバックグラウンドで、クライアントとサーバーとの間で交換されます。ただし、GWT が通信する相手はサーブレットに限定されるわけではなく、あらゆる種類の Web サービスとの間で自由にデータを交換することができます。(単純なサービスでは) 多くの場合、そうしたデータ転送はプレーン・テキストで行われますが、データが構造を持つようになると、あるいはデータが複雑になると (例えば RSS を考えてみてください)、そうしたデータを XML で表現した方が適切です。

この記事では簡単な GWT アプリケーションといくつかの PHP Web サービスを取り上げ、XML 文書の生成方法と利用方法をいくつか説明します。この記事は、完全なチュートリアルやハンドブックを意図したものではなく、皆さんが自分の力ですぐに XML を GWT と PHP とのブリッジとして扱えるようになるためのアドバイスを伝えるためのものです。

テスト・アプリケーション

PHP と GWT とのブリッジとしての XML の使い方を示すために、ここでは、国 (countries)、地域 (regions)、都市 (cities) のデータに基づく簡単なアプリケーションを使用することにします。データベースを作成するリスト 1 のコードを見ると、以下のことがわかります。

  • 国 (countries) は一意のコード (例えば、ウルグアイ (Uruguay) の場合は “UY” など) と名前を持っています。
  • 国 (countries) は地域 (regions) に分かれており、地域 (regions) は (その国内で一意の) コードで識別され、また名前を持っています。
  • 地域 (regions) には都市 (cities) があり、都市 (cities) には名前 (cityName: ASCII のみで表現される名前) と、その国で使われている名前 (cityAccentedName: 英語以外の文字が含まれる場合があります)、人口 (population: 未知の場合は 0)、緯度 (latitude)、そして経度 (longitude) などがあります。都市の名前は、同じ国の別々の地域に現れる可能性があります。
リスト 1. データベースを作成するコード
CREATE DATABASE world
    DEFAULT CHARACTER SET latin1
    COLLATE latin1_general_ci;

USE world;

CREATE TABLE countries (
    countryCode char(2) NOT NULL,
    countryName varchar(50) NOT NULL,
    PRIMARY KEY (countryCode)
    KEY countryName (countryName)
    );

CREATE TABLE regions (
    countryCode char(2) NOT NULL,
    regionCode char(2) NOT NULL,
    regionName varchar(50) NOT NULL,
    PRIMARY KEY (countryCode,regionCode),
    KEY regionName (regionName)
    );

CREATE TABLE cities (
    countryCode char(2) NOT NULL,
    cityName varchar(50) NOT NULL,
    cityAccentedName varchar(50) NOT NULL,
    regionCode char(2) NOT NULL,
    population bigint(20) NOT NULL,
    latitude float(10,7) NOT NULL,
    longitude float(10,7) NOT NULL,
    KEY `INDEX` (countryCode,regionCode,cityName),
    KEY cityName (cityName),
    KEY cityAccentedName (cityAccentedName)
    );

ここでは 1 つのフォームと 2、3 の PHP Web サービスのみを持つ簡単な GWT プロジェクトを作成しています。(完全なソース・コードは「ダウンロード」セクションを参照してください)。このアプリケーションを起動すると、図 1 に示す単純なウィンドウが表示されます。

図 1. 空のフォーム
空のフォーム
空のフォーム

この GWT フォームでは、都市名の一部を入力して PHP サービスを呼び出し、入力した内容に一致するすべての都市を取得することができます。取得された都市はグリッドの中に表示され、それらの都市の population、latitude、longitude フィールドを編集することができます。編集したデータを別の PHP Web サービスに送信すると、その Web サービスによってデータベースが更新されます。データの転送はすべて XML で行われます。2009年は Charles Darwin の生誕 200 年と彼の著書『The Origin of Species (種の起源)』の発刊 150 周年の両方に当たるので、名前が DARWIN の都市を検索してみましょう。すると図 2 のような結果が得られます。

図 2. 名前に DARWIN が含まれる都市を検索する
名前に DARWIN が含まれる都市を検索する
名前に DARWIN が含まれる都市を検索する

追加の構成

参考までに、私は以下を使用しました。

  • GWT バージョン 1.5.3
  • PHP バージョン 5.2.8
  • MySQL® データベース・サーバー・バージョン 5.0.67
  • OpenSUSE® バージョン 11.1 の下での Apache バージョン 2.2.10

私はすべてのソフトウェアをそのまま何も特別な設定をせずにインストールしましたが、GWT と PHP との接続をテストするためには GWT に追加の構成ステップが必要です。このステップが必要な理由については「SOP の問題」を参照してください。内部 GWT ブラウザーでの SOP (Same-Origin Policy: 同一生成元ポリシー) チェックを無効にするためには、GWT ディレクトリー内の ./mozilla-1.7.12/greprefs/all.js ファイルを編集し、このファイルの最後にリスト 2 に示す行を追加します。

リスト 2. 内部 GWT ブラウザーの構成の変更
pref("capability.policy.default.XMLHttpRequest.abort", "allAccess");
pref("capability.policy.default.XMLHttpRequest.getAllResponseHeaders","allAccess");
pref("capability.policy.default.XMLHttpRequest.getResponseHeader","allAccess");
pref("capability.policy.default.XMLHttpRequest.open", "allAccess");
pref("capability.policy.default.XMLHttpRequest.send", "allAccess");
pref("capability.policy.default.XMLHttpRequest.setRequestHeader","allAccess");
pref("capability.policy.default.XMLHttpRequest.onreadystatechange","allAccess");
pref("capability.policy.default.XMLHttpRequest.readyState", "allAccess");
pref("capability.policy.default.XMLHttpRequest.responseText","allAccess");
pref("capability.policy.default.XMLHttpRequest.responseXML","allAccess");
pref("capability.policy.default.XMLHttpRequest.status", "allAccess");
pref("capability.policy.default.XMLHttpRequest.statusText", "allAccess");

GWT を更新した場合には、この変更を再度行う必要があります。この変更を行わないと、作成したコードはコンパイル・モードでは適切に実行されますがホスト・モードでは失敗することになり、この変更を行うと、作成したコードはコンパイル・モードでは失敗しますがホスト・モードでは実行できることになるので、注意してください。

PHP を使って XML を送信する

このアプリケーションは単純なフォームを定義するにすぎません。このフォームを構成する要素にはいくつかのラベルと、1 つのテキスト・ボックス、2 つのコマンド・ボタン、そして結果を表示するグリッドがあります。「Get cities (都市を取得)」をクリックすると、アプリケーションは PHP サービスを呼び出し、テキスト・ボックスに入力された内容と一致するすべての都市を含んだ XML 文書を取得します。リスト 3 は PHP サービスから GWT アプリケーションに送信される (多少省略した) XML の例を示しています。この生成された XML コードは XML によるいくつかの機能を説明するためのものであり、そのため実際のアプリケーションに使われる XML コードよりもはるかに長くなっています。通常、適切に設計された XML サービスでは、ここに示した例よりも、使うタグは少なく、属性は多く、インデントはなく、また文書はこれほど長くありません。

リスト 3. 「tokyo」を検索するために生成された XML 文書
<?xml version="1.0" encoding="UTF-8"?>
 <cities>
  <city name="tokyo">
   <country code="JP" name="Japan"/>
   <region code="40" name="Tokyo"/>
   <coords>
    <lat>35.6850014</lat>
    <lon>139.7513885</lon>
   </coords>
   <pop>31480498</pop>
  </city>
  <city name="tokyo">
   <country code="PG" name="Papua New Guinea"/>
    <region code="01" name="Central"/>
   <coords>
    <lat>-8.3999996</lat>
    <lon>147.1499939</lon>
   </coords>
  </city>
  <city name="tokyojitori">
   <country code="KR" name="Korea, Republic of"/>
   <region code="16" name="Cholla-namdo"/>
   <coords>
    <lat>34.2380562</lat>
    <lon>125.9394455</lon>
   </coords>
  </city>
</cities>

この PHP サービス自体には getcities1.php と getcities2.php という 2 つのバージョンがあり、それぞれのバージョンが XML を生成するための異なる方法を示しています。

XML を生成するための最も単純な方法は何と言っても、適切なテキストをシーケンシャルに出力する方法、あるいはストリングを作成してからそれを echo を使って出力する方法です。生成するにあたっては、内容が適切に認識されるようにコンテンツ・タイプを text/xml に設定し、XML のバージョンとデータのエンコーディングを規定するための適切な記述を含んだ行も必ず出力に含めます。また、より小 (<)、より大 (>) アンパサンド (&) などの文字を含まないようにストリングをエスケープする必要があります。そのための最も簡単な方法は PHP 関数 htmlspecialchars() を使う方法です。このコーディングは容易であり、その一部をリスト 4 に示します。インデントと改行は実際には必要なく、コードを読みやすくするために入れてあることに注意してください。

リスト 4. PHP サービスから XML を生成するための最も単純な方法
...
header("Content-type: text/xml");
...
echo '<?xml version="1.0" encoding="UTF-8"?>'."\n";
echo '<cities>'."\n";
...
while ($row= mysql_fetch_assoc($result)) {
  echo ' <city name="'.htmlspecialchars($row['cityName']).'">'."\n";
  echo '  <country code="'.$row['countryCode'].'" ';
  echo 'name="'.htmlentities($row['countryName']).'"/>'."\n";
  echo '  <region code="'.$row['regionCode'].'" ';
  echo 'name="'.htmlentities($row['regionName']).'"/>'."\n";

  echo '  <coords>'."\n";
  echo '    <lat>'.$row['latitude'].'</lat>'."\n";
  echo '    <lon>'.$row['longitude'].'</lon>'."\n";
  echo '  </coords>'."\n";

  if ($row['population']>0) {
    echo '  <pop>'.$row['population'].'</pop>'."\n";
  }

  echo ' </city>'."\n";
}
echo '</cities>'."\n";

XML コードを生成するための第 2 の (そして第 1 の方法ほど長くない) 方法は、XMLWriter を使う方法です。(この XMLWriter の仲間のクラスである XMLReader を使うと XML を処理することができます。) この場合は文字をエスケープする必要はありません (自動的に処理されます)。この方法は先ほどの echo() メソッドによる方法よりも冗長に見えるかもしれませんが、この方法の方がはるかにコードを理解しやすくなります。php://output プロトコルを使っていることに特に注意してください。こうすることで echo コマンドの場合と同じようにテキストが出力されます (リスト 5 を参照)。

リスト 5. XMLWriter を使った単純な方法 (要素を 1 つずつ作成しながら XML 文書を作成する)
...
$writer= new XMLWriter();
$writer->openURI('php://output')
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement("cities");

while ($row= mysql_fetch_assoc($result)) {
  $writer->startElement("city");
  $writer->writeAttribute("name", $row['cityName']);

  $writer->startElement("country");
  $writer->writeAttribute("code", $row['countryCode']);
  $writer->writeAttribute("name", $row['countryName']);
  $writer->endElement();

  $writer->startElement("region");
  $writer->writeAttribute("code", $row['regionCode']);
  $writer->writeAttribute("name", $row['regionName']);
  $writer->endElement();

  $writer->startElement("coords");
  $writer->writeElement("lat", $row['latitude']);
  $writer->writeElement("lon", $row['longitude']);
  $writer->endElement();

  if ($row['population']>0) {
    $writer->writeElement("pop", $row['population']);
  }

  $writer->endElement();	// city
}
$writer->endElement(); // cities
...

他の方法で XML を生成してみたい場合には、PHP にはたくさんの選択肢があります。例えば SimpleXML を使うことができます。後ほど SimpleXML を使って XML を読み取りますが、SimpleXML で XML 文書を作成することもできます。また、PEAR フレームワークも調べてみる価値があります。PEAR フレームワークには XML を容易に生成できるクラスがいくつか含まれています (詳細については「参考文献」を参照)。

GWT を使って XML を処理する

GWT には XML の読み取りと作成両方のためのツールとして (com.google.gwt.xml.client パッケージの中に) XMLParser のみが提供されています。parse() メソッドを使って Document を作成し、次にその文書のルート要素を getDocumentElement() を使って取得します。すると XML のウォークスルーを開始することができます。

重要なポイントは、removeWhitespace() メソッドを使って文書から空白を削除する必要があるという点です。ブラウザーのパーサーがタブや改行に対応して空のテキスト・ノードを作成することがあり、そうした空ノードを削除しないとプロセスの途中に予想外の余分な要素が出現し、ロジックがおかしくなる可能性があります (リスト 6 を参照)。もう 1 つのポイントとして、CDATA セクションが予想される場合にはブラウザーが supportsCDATASection() メソッドを受け付けるかどうかを確認する必要があります。このメソッドが受け付けられない場合には CDATA セクションによってテキスト・ノードが生成されます。この詳細については GWT のドキュメントを参照してください (「参考文献」を参照)。

リスト 6. GWT では XMLParser が XML の読み取りと作成を行う
protected void loadCities(final String xmlCities) {
  ...
  final Document xmlDoc= XMLParser.parse(xmlCities);
  final Element root= xmlDoc.getDocumentElement();
  XMLParser.removeWhitespace(xmlDoc);

  final NodeList cities= root.getElementsByTagName("city");
  for (int i= 0; i < cities.getLength(); i++) {
    final Element city= (Element)cities.item(i);
    // show city.getAttributeNode("name").getValue()

    final Element country= (Element)city.getElementsByTagName("country").item(0);
    // show country.getAttributeNode("code").getValue()
    // show country.getAttributeNode("name").getValue()
    ...
    final Element population= (Element)city.getElementsByTagName("pop").item(0);
    if (population != null) {
      // show population.getFirstChild().getNodeValue()
    }

    final Element coords= (Element)city.getElementsByTagName("coords").item(0);
    final Element lat= (Element)coords.getElementsByTagName("lat").item(0);
    // show lat.getFirstChild().getNodeValue()
    ...
  }
...

取得したルート要素の配列に対して、getElementsByTagName() メソッドを使用することで、繰り返される要素 (この例では city) を順に取得することができます。もう 1 つの方法は、getFirstChild() メソッドを使用して要素を取得し、取得した要素と同じレベルの残りの要素を、getNextSibling() を使用してウォークスルーします。属性を取得するためには最初に getAttributeNode() メソッドを使用してから getValue() メソッドを使用します。他にもさまざまなメソッドがあり、CDATA セクション、コメント、その他あらゆる XML コンポーネントを処理することができます。

GWT を使って XML を送信する

GWT アプリケーションを利用するとユーザーが population、latitude、longitude フィールドを編集することができ、その編集された cities データをサーバーに送り返すことでデータベースを更新することができます。ここでは 2 つのアルゴリズムを示します。1 つは要素ごとに XML ストリングを作成する単純なアルゴリズムであり、もう 1 つは特定のメソッドを使用して必要な構造を作成する XMLParser ベースのアルゴリズムです。

単純な方のアルゴリズムを getCities1() メソッドとして示します。このメソッドでは、String オブジェクトまたは StringBuffer オブジェクトのいずれかを使って XML を生成します。私はコードが理解しやすくなることから String オブジェクトを使いましたが、パフォーマンスの点からは StringBuffer オブジェクトを使った方が適切でしょう。文字をエスケープする問題を PHP バージョンで説明しましたが、GWT を使用する場合にも同じ問題があります。そこでその問題を Html.htmlspecialchars() メソッドを使って修正します。(リスト 7 を参照。このリストは理解しやすくするために、実際のメソッドを少し変更してあります。)

リスト 7. ストリングを使用して XML の要素を 1 つずつ作成する単純な方法
protected String getCities1() {
  String result= "";

  result+= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  result+= "<cities>\n";

  for (all rows in the grid) {
    // get cityName, countryCode, regionCode, pop, lat, and lon, from the grid

    result+= " <city name=\"" + Html.htmlspecialchars(cityName) + "\">\n";
    result+= "  <country code=\"" + countryCode + "\"/>\n";
    result+= "  <region code=\"" + regionCode + "\"/>\n";
    if (!pop.equals("0") && !pop.isEmpty()) {
      result+= "  <pop>" + pop + "</pop>\n";
    }

    result+= "  <coords>\n";
    result+= "   <lat>" + lat + "</lat>\n";
    result+= "   <lon>" + lon + "</lon>\n";
    result+= "  </coords>\n";
    result+= "</city>\n";
  }
  result+= "</cities>\n";
  return result;
}

XML を生成するための、もう 1 つのアルゴリズムが XMLParser クラスの createDocument() メソッドです。PHP での SimpleXML 関数と非常によく似たスタイルで、まず空の文書を作成し、その文書に要素を追加します。この方法で、あらゆる種類のノードを作成することも、属性の値を設定することもできます。最後に、標準的な Java の toString() メソッドによってストリング・オブジェクトの表現を生成します。そのためにはバージョンに関する最初の行とエンコーディングの行のみを追加すればよく、これにより、必要なストリングが得られます。(リスト 8 を参照。このリストは理解しやすくするために、実際のメソッドに対して変更と省略を行っています。)

リスト 8. XMLParser メソッドを使って XML を作成する方法 (PHP での SimpleXML と似た方法)
protected String getCities2() {
  Document xml= XMLParser.createDocument();
  Element cities= xml.createElement("cities");
  xml.appendChild(cities);

  for (all rows in the grid) {
    // get cityName, countryCode, regionCode, pop, lat, and lon, from the grid

    Element city= xml.createElement("city");
    city.setAttribute("name", cityName);

    Element country= xml.createElement("country");
    country.setAttribute("code", countryCode);
    city.appendChild(country);
    ...
    if (!pop.equals("0") && !pop.isEmpty()) {
      Element popEl= xml.createElement("pop");
      Text popText= xml.createTextNode(pop);
      popEl.appendChild(popText);
      city.appendChild(popEl);
    }

    Element coords= xml.createElement("coords");
    Element lat= xml.createElement("lat");
    Text latText= xml.createTextNode(lat);
    lat.appendChild(latText);
    coords.appendChild(lat);
    ...
    city.appendChild(coords);
    cities.appendChild(city);
  }
  return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + xml.toString();
}

PHP で XML を読み取る

PHP での XML の処理は昔からの問題であり、非常にたくさんのソリューションがあります。しかし私の意見では、明確さと簡潔さの点で SimpleXML に匹敵するソリューションはありません。基本的に SimpleXML では XML 文書を simplexml_load_string() メソッドで処理し、その結果を標準的な PHP 演算子を使ってトラバースすることで PHP オブジェクトを作成します。children() メソッドを使うことで、XML 要素の属性は配列の要素になり (例えばリスト 9 での国コードの読み取り方法を見てください)、XML 要素には配列としてアクセスできるようになります。あるいは XML 要素にはオブジェクトの属性として直接アクセスすることもできます。

リスト 9. PHP で XML を処理する方法として最も容易な SimpleXML
$xml_str= $_POST["xmldata"];
$xml_obj= simplexml_load_string($xml_str);
...
foreach($xml_obj->children() as $city) {
  $name= addslashes($city['name']);
  $country= $city->country['code'];
  $region= $city->region['code'];
  $pop= $city->pop;
  $lat= $city->coords->lat;
  $lon= $city->coords->lon;

  mysql_query("REPLACE INTO cities ".
    "(cityName, countryCode, regionCode, population, latitude, longitude) VALUES (".
    "'{$name}', '{$country}', '{$region}', '{$pop}', '{$lat}', '{$lon}')");
}

PHP で XML を処理する方法には他にも数多くあります。「参考文献」セクションのリンクを参照してください。

まとめ

GWT と PHP とのブリッジとして XML を使用する方法は数多くあり、この記事ではその一部を説明したにすぎませんが、ここで紹介した方法は出発点として十分なはずです。覚えておかなければならない重要なポイントは、GWT では GWT 独自の RPC メソッドしか使えないわけではなく、GWT は XML と共存することができ、XML 文書の生成や利用は容易だということです。


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


関連トピック

  • W3C の XML のサイトで XML について学んでください。XML 標準に関する勧告の最新版 (第 5 版) を含めてドキュメントが豊富に用意されており、XML について学ぶことができます。
  • PHP で XML を処理するための方法を学んでください。特に、この記事で使用した、SimpleXMLXMLReaderXMLWriter による方法を学んでください。
  • XMLParser クラスに関する GWT のドキュメントを読んでください。
  • XML に代わるものとして JSON に関心がある人は JSON.org を訪れてください。Java 言語や PHP など、最近の大部分のプログラミング言語に関するリソースが豊富に用意されています。
  • SOP (Same-Origin Policy: 同一生成元ポリシー) に関して学び、SOP 問題に対する元々のソリューションについて調べ、またこの問題に関する他の考慮事項についての資料を読んでください。
  • XML および関連技術において IBM 認定技術者になる方法については、IBM XML certification を参照してください。
  • developerWorks の XML ゾーンは XML の技術ライブラリーとして、広範な話題を網羅した技術記事やヒント、チュートリアル、技術標準、IBM Redbooks などを用意しています。
  • developerWorks podcasts ではソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
  • Google Web Toolkit をダウンロードし、この記事に含まれているコード・サンプルを試してみてください。
  • 再利用可能 PHP コンポーネントのための PEAR フレームワークを試してみてください。
  • MaxMind が提供する無料の都市テーブルをダウンロードし、また国や地域のデータへのリンクも見つけてください。
  • IBM 製品の試用版をダウンロードするか、あるいは IBM SOA Sandbox をオンラインで試し、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品をお試しください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Java technology, Open source, Web development
ArticleID=388339
ArticleTitle=XML: GWT と PHP とのブリッジ
publish-date=04072009