PHPでマッシュアップするための準備

(1/1)
最近、Yahoo!Google の検索エンジン、Amazon楽天市場の商品データベースといったものを、自分のホームページから利用できるようになってきた。従来のように GET/POST メソッドを使ってパラメータを渡して Yahoo! や Google のページとして表示するのではなく、見た目は自分のサイトデザインのまま、中身のデータだけを Yahoo! や Google から受け取れるようになったのである。
こうした仕組みを WebAPI、これを利用することを マッシュアップ(MushUp) と呼んでいる。

WebAPI は無料で利用できるものが多い。また、WebAPI を公開する企業としても、無償でマッシュアップして顧客を呼び込んでくれるパワーユーザーの存在はありがたい。今後、WebAPI の公開が、ますます盛んになるだろう。
というわけで、このコーナーは「ぱふぅ家のホームページ」100 万回アクセス記念として、PHPWeb API を利用し、マッシュアップしていく方法を説明していくことにする。

WebAPI の仕組み

WebAPIの仕組み
上の図は、WebAPI の仕組みを表したものである。
クライアント(ブラウザ)から見えるのは、1 つのドメイン(サーバサイド)だけである。サーバサイドは、PHP などのプログラムを介して、Yahoo! や Amazon の WebAPI を利用する。したがって、クライアントからどんな WebAPI を利用しているのかはわからない。ここまでのデータ交換は、すべて http通信で処理される。
さらに、WebAPI の裏側にはデータベースなどのシステムがあるが、クライアントから WebAPI が見えないのと同様、サーバサイドからそれらのシステムを見ることはできない。

XML のツボ

WebAPI を利用するためには、まず、XML に関する知識が必要になる。Google や Yahoo! のような WebAPI から戻ってくるデータ形式が XML だからだ。XML についてご存知の方は、この項を読み飛ばしていただいて構わない。XML を扱うのは初めてという方は、とりあえず、次の 3 点を押さえておいてほしい。

1.HTML のようなタグ構造をしているテキスト

XML は、<hoge>...</hoge> というタグ構造をなしているテキストデータである。
ただ、HTML と異なり、タグの名前は自由に設定できる(厳密には定義が必要)。


2.UTF-8 が標準

XML ファイルの冒頭行は
  <?xml version="1.0" encoding="utf-8"?>
と書かれていることが多い。つまり、標準コードは UTF-8 である。
ShiftJIS や EUC-JP で書くこともできるが、PHP が用意している XML 処理関数の中には UTF-8 でないと正常に処理できないものがあるため、ここではすべて UTF-8 として扱うことにする。


3.PHP では XML を扱う方法が複数ある

PHP バージョン 4 と 5 では、同じ DOM(Document Object Model)でも互換性がほとんどない。そこで、

  • PHP4 ⇒ DOM
  • PHP5 ⇒ SimpleXML
を利用することにする。SimpleXML は PHP バージョン 5 で導入された関数群で、DOM や XML パーサ(SAX)に比べて短いコードで XML 処理を行うことができるのが特長だ。

XML ファイルの構造

Yahoo! ウェブ検索Web サービス」は Web API の一種だが、これを利用して得られる XML ファイルを解釈するプログラムを作ってみることにする。
このサービスを利用し、ある日の "中央線E233 系" の検索結果の XML ファイルを用意した。

サンプルXMLファイルをダウンロード

応答データ構造(xml) ResultSet Result Title ページのタイトル Summary ページの要約 Url ページURL ClickUrl ページのリンクURL ModificationDate ページの最終更新日 MimeType MIMEタイプ Cache Url キャッシュ結果のURL Size キャッシュ・サイズ(byte)
この XML ファイルの構造は上図のようになっている。
冒頭の <?xml ...> はヘッダである。
それ以降は <ResultSet> で囲まれるブロックが 1 つある。
<ResultSet> の下には、<Result> で囲まれるブロックが複数ある。
<Result> の下には、<Title>, <Summary>, <Url>, <ClickUrl>, <ModificationDate>, <MimeType> で囲まれるブロックが各々1 つずつある。

次のページで紹介するサンプルプログラムでは、複数登場する <Title> タグの内容を、配列変数に格納することを目指す。

サンプル・プログラム(PHP4専用)

サンプル・プログラムの解説:DOM XML

0061: /**
0062:  * 指定XMLファイルを読み込んでDOMを返す
0063:  * @param string $xml XMLファイル名
0064:  * @return object DOMオブジェクト/NULL 失敗
0065: */
0066: function read_xml4($xml) {
0067:     if (! isphp5over())  return NULL;
0068:     if (($fp = fopen($xml, "r")) == FALSE)  return NULL;
0069: 
0070:     //いったん変数に読み込む
0071:     $str = fgets($fp);
0072:     $str = preg_replace("/UTF-8/", "utf-8", $str);       //コーディング文字の置換
0073: 
0074:     while (! feof($fp)) {
0075:         $str = $str . fgets($fp);
0076:     }
0077:     fclose($fp);
0078: 
0079:     //DOMを返す
0080:     $dom = domxml_open_mem($str);
0081:     if ($dom == NULL) {
0082:         echo "\n>Error while parsing the document - " . $xml . "\n";
0083:         exit(1);
0084:     }
0085: 
0086:     return $dom;
0087: }

ユーザー関数 read_xml は、XML ファイルを読み込んで DOM オブジェクトを返す。
PHP4 には、XML ファイルを読み込んで DOM を返す関数 domxml_open_file があるのだが、日本語ファイルを正常に処理できないことがあったので(バージョンや OS によるものかもしれない)、ユーザー関数として用意することにした。

まず、XML ファイルをテキストファイルとみなし、いったん変数 $str にすべて読み込む。この際、冒頭行のエンコーディング指定が大文字の 'UTF-8' だと、DOM 関数群が正常に作用しないので(これも PHP のバージョンや OS に依存する問題かもしれない)、小文字の 'utf-8' に置換しておく。
XML ファイルをすべて変数 $str に読み込んだら、関数 domxml_open_mem を使って、DOM を得る。XML の構造が間違っていたり、UTF-8 で書かれていないとエラーになる。

サンプル・プログラムの解説:XML処理

0089: /**
0090:  * Yahoo! ウェブ検索WebサービスAPIが出力するXMLから
0091:  * タイトルを配列に格納する
0092:  * @param array $items  情報を格納する配列
0093:  * @param string $xml   XMLファイル名
0094:  * @return int 情報の数/FALSE
0095: */
0096: function getTitles4(&$items$xml) {
0097:     $name = 'Title';
0098: 
0099:     if (($dom = read_xml4($xml)) == NULL)   return FALSE;
0100:     $resultset = $dom->get_elements_by_tagname('ResultSet');
0101:     $results   = $resultset[0]->get_elements_by_tagname('Result');
0102:     //検索結果取りだし
0103:     $cnt = 1;
0104:     foreach ($results as $element) {
0105:         $node = $element->get_elements_by_tagname($name);
0106:         if ($node != NULL) {
0107:             $items[$cnt] = $node[0]->get_content();
0108:         }
0109:         $cnt++;
0110:     }
0111: 
0112:     return ($cnt - 1);
0113: }

ユーザー関数 getTitles4 は、Yahoo! ウェブ検索Web サービス が出力する XML から、Title タグの内容を配列変数に格納して返す。引数は、格納したい配列変数と、XML ファイル名の 2 つ。

まず、ユーザー関数 read_xml を呼び出して DOM を取得する。
次に、全体を一括りにしているタグ <ResultSet> をオブジェクトとして、メソッド getElementsByTagName を適用する。ここでは <ResultSet> は 1 つしかないので、$resultset[0] が全体を括る <ResultSet> オブジェクトを示す。

<ResultSet> には複数の <Result> がぶら下がっているが、$resultset[0] に対してメソッド getElementsByTagName を適用することで、これらのオブジェクトを取得できる。
<Result> は複数あるので、各々のオブジェクトは、$results[0], $results[1], $results[3]‥‥に格納されていく。

<Title><ResultSet> にぶら下がっているので、$results[N] に対してメソッド getElementsByTagName を適用することで、<Title> オブジェクトを取得できる。
<Title> の内容は、オブジェクトに対してメソッド get_content を適用すれば取得できる。

サンプル・プログラムの解説:HTML部分

ファイル入力と表示を 1 つのプログラムで賄っている。

0115: /**
0116:  * HTML BODYを作成する
0117:  * @param string $fname XMLファイル名
0118:  * @return string HTML BODY
0119: */
0120: function makeCommonBody($fname) {
0121:     $myself = MYSELF;
0122:     $refere = REFERENCE;
0123: 
0124:     $p_title = TITLE;
0125:     $version = '<span style="font-size:small;">' . date('Y/m/d版', filemtime(__FILE__)) . '</span>';
0126: 
0127:     //XMLの解釈と表示
0128:     $items = array();
0129:     if (file_exists($fname)) {
0130:         $n = getTitles4($items$fname);
0131:         if ($n == FALSE) {
0132:             $res = 'error - 正しいXMLファイル名を入力してください.';
0133:         } else {
0134:             $i = 1;
0135:             $res = '';
0136:             foreach ($items as $title) {
0137:                 $res .= "{$i} : {$title}<br />\n";
0138:                 $i++;
0139:             }
0140:         }
0141:     } else {
0142:         $res = 'ファイル名を入力してください.';
0143:     }
0144: 
0145: $body =<<< EOT
0146: <body>
0147: <h2>{$p_title} {$version}</h2>
0148: <form name="myform" method="post" action="{$myself}" enctype="multipart/form-data">
0149: XMLファイル:<input name="file" type="file" size="80" />
0150: <input type="submit" name="execute" value="抽出" />
0151: </form>
0152: 
0153: <div style="border-style:solid; border-width:1px; margin:20px 0px 0px 0px; padding:5px; width:500px; font-size:small;">
0154: <h3>使い方</h3>
0155: <ol>
0156: <li>[<span style="font-weight:bold;">XMLファイル</span>]に抽出したいファイルを入力し、[<span style="font-weight:bold;">抽出</span>] ボタンを押してください。</li>
0157: <li>抽出結果が表示されます。</li>
0158: </ol>
0159: ※参考サイト:<a href="{$refere}">{$refere}</a>
0160: </div>
0161: <hr />
0162: {$res}
0163: </body>
0164: 
0165: EOT;
0166:     return $body;
0167: }

サンプル・プログラム(PHP5/7専用)

サンプル・プログラムの解説:SimpleXML

0061: /**
0062:  * Yahoo! ウェブ検索WebサービスAPIが出力するXMLから
0063:  * タイトルを配列に格納する
0064:  * @param array $items  情報を格納する配列
0065:  * @param string $xml   XMLファイル名
0066:  * @return int 情報の数/FALSE
0067: */
0068: function getTitles5(&$items$xml) {
0069:     $name = 'Title';
0070: 
0071:     if (($ResultSet = @simplexml_load_file($xml)) == FALSE)  return FALSE;
0072:     //検索結果取りだし
0073:     $cnt = 1;
0074:     foreach ($ResultSet->Result as $element) {
0075:         if (isset($element->$name)) {
0076:             $items[$cnt] = $element->$name;
0077:         }
0078:         $cnt++;
0079:     }
0080: 
0081:     return ($cnt - 1);
0082: }

次に PHP5 用のプログラムをつくる。基本構造は PHP4用と同じだが、DOM XML の代わりに SimpleXML を利用することにする。

SimpleXML は PHP5 で導入された関数群で、DOM や XML パーサ(SAX)に比べて短いコードで XML 処理を行うことができるのが特長だ。
たとえば、今回の XML ファイルの <Title> の内容を参照したければ、
  $ResultSet->Result->Title
と記述するだけでよい。

ユーザー関数 read_xml は不要である。
関数  simplexml_load_file  を利用し、一気に XML ファイルを読み込む。

次に、ResultSet->Result まで一気に下り、複数ある <Title> の内容を配列変数 $items に逐次格納していく。

これ以外の部分は PHP4用と共通である。

サンプル・プログラム(PHP4/5/7共用)

サンプル・プログラムの解説:バージョン検査

0051: /**
0052:  * PHP5以上かどうか検査する
0053:  * @return bool TRUE:PHP5以上/FALSE:PHP5未満
0054: */
0055: function isphp5over() {
0056:     $version = explode('.', phpversion());
0057: 
0058:     return $version[0] >= 5 ? TRUE : FALSE;
0059: }

最後に、PHP4用と PHP5用プログラムを 1 本化する。
関数  phpversion  を利用し、PHP5 以上かどうかを判定する関数 isphp5over を用意する。

サンプル・プログラムの解説:read_xml

ユーザー関数 read_xml だが、PHP5 の場合には常に NULL を返すようにする。

0061: /**
0062:  * 指定XMLファイルを読み込んでDOMを返す
0063:  * @param string $xml XMLファイル名
0064:  * @return object DOMオブジェクト/NULL 失敗
0065: */
0066: function read_xml($xml) {
0067:     if (isphp5over())   return NULL;
0068:     if (($fp = fopen($xml, "r")) == FALSE)  return NULL;
0069: 
0070:     //いったん変数に読み込む
0071:     $str = fgets($fp);
0072:     $str = preg_replace("/UTF-8/", "utf-8", $str);       //コーディング文字の置換
0073: 
0074:     while (! feof($fp)) {
0075:         $str = $str . fgets($fp);
0076:     }
0077:     fclose($fp);
0078: 
0079:     //DOMを返す
0080:     $dom = domxml_open_mem($str);
0081:     if ($dom == NULL) {
0082:         echo "\n>Error while parsing the document - " . $xml . "\n";
0083:         exit(1);
0084:     }
0085: 
0086:     return $dom;
0087: }

サンプル・プログラムの解説:getTitles

ユーザー関数 getTitles だが、PHP4 と PHP5 の場合で処理を分けている。

0089: /**
0090:  * Yahoo! ウェブ検索WebサービスAPIが出力するXMLから
0091:  * タイトルを配列に格納する
0092:  * @param array $items  情報を格納する配列
0093:  * @param string $xml   XMLファイル名
0094:  * @return int 情報の数/FALSE
0095: */
0096: function getTitles(&$items$xml) {
0097:     $name = 'Title';
0098: 
0099: //PHP4用; DOM XML利用
0100:     if (isphp5over() == FALSE) {
0101:         if (($dom = read_xml($xml)) == NULL)    return FALSE;
0102:         $resultset = $dom->get_elements_by_tagname('ResultSet');
0103:         $results   = $resultset[0]->get_elements_by_tagname('Result');
0104:         //検索結果取りだし
0105:         $cnt = 1;
0106:         foreach ($results as $element) {
0107:             $node = $element->get_elements_by_tagname($name);
0108:             if ($node != NULL) {
0109:                 $items[$cnt] = $node[0]->get_content();
0110:             }
0111:             $cnt++;
0112:         }
0113: 
0114: //PHP5用; SimpleXML利用
0115:     } else {
0116:         if (($ResultSet = @simplexml_load_file($xml)) == FALSE)  return FALSE;
0117:         //検索結果取りだし
0118:         $cnt = 1;
0119:         foreach ($ResultSet->Result as $element) {
0120:             if (isset($element->$name)) {
0121:                 $items[$cnt] = $element->$name;
0122:             }
0123:             $cnt++;
0124:         }
0125:     }
0126: 
0127:     return ($cnt - 1);
0128: }

参考サイト

(この項おわり)
header