XPath を使用して Google Calendar イベントを PHP Web サイトに表示する

XPath と SimpleXML で XML 構文解析 API の可読性と冗長性を両立させる

Google Calendar をはじめとするオンライン・カレンダー・アプリケーションが実現する一元化された単純なシステムでは、オンライン・コミュニティーがイベント・カレンダーを管理し、コミュニティーのメンバーが近く予定されているイベントの情報を入手することができます。しかし、イベント・カレンダーをコミュニティーのポータルやフォーラム、あるいはブログに表示したいという組織も数多くあります。このような組織はたいていの場合、イベント・カレンダーの情報をオンライン・カレンダー・アプリケーションから組織の Web サイトにコピーしてきますが、そうなるとオンラインでのイベント一元管理の有効性は損なわれます。この問題に対する効果的なソリューションとなるのが、Google Calendar が提供する統合 API (Application Program Interface) です。この記事を読んで、XPath を使用して Google Calendar データを抽出し、PHP Web サイトに表示する方法を学んでください。

PJ Cabrera (pjcabrera@pobox.com), Software Engineer, Freelance Writer

P.J. CabreraP.J. Cabrera は、Ruby on Rails の e-commerce およびコンテンツ管理システムの開発を専門とする開発者です。Ruby on Rails およびオープン・ソースのスクリプト言語とフレームワーク、アジャイル開発の慣例、メッシュ・ネットワーク、Compute Cloud、XML 解析処理技術、マイクロフォーマットによるセマンティック Web コンテンツの強化、そして情報検索、質問応答、テキスト分類、抽出の改善を目的としたベイズ式フィルタリングおよび処理の革新的用法の研究など、多岐にわたって興味を持っています。pjtrix.com/blawg/ にアクセスして彼のウェブログを読んでください。



2007年 11月 27日

フリーランスの Web 開発者として働いていた何年か前、ある特定の自動車モデルのオーナーとファンで構成されるコミュニティー (通称、カー・クラブ。私が住んでいる辺りではとても人気があります) のために PHP で駆動する Web ポータルを開発したことがあります。私に連絡してくる数年前、カー・クラブのリーダーたちは基本的な Web スキルを習得して自分たちで Web サイトを作りましたが、サイトが大きくなるにつれ、それぞれにイベント・カレンダーの情報を掲載するページが (古くなったものも含め) とても増えてしまいました。このサイトの主な目的は一般の人々やコミュニティーのメンバーにコミュニティーの活動を知らせることだったので、これらのページはイベント・カレンダー情報だらけになってしまったのです。

そのうち、コミュニティーの個々のイベント・カレンダーのスナップショットは同期が乱れてきました。つまり、一部のページには、とうの昔に終わったイベントを通知するサイドバーが残っているといった状態です。適切な管理と効果的な Web 設計をもってしても、イベント・カレンダー・データのコピーをページごとに管理するとなると時間がかかり、間違いも起こりがちです。カー・クラブは、Web サイトが自分たちの役に立つどころか、自分たちが Web サイトのために忙しく働くはめになっていることに気付きました。そこで、彼らはもっといい方法を紹介してくれるだろうという期待で私に連絡してきたわけです。

彼らが望んでいたのは、クラブ・メンバーが自分たちのお気に入りの自動車について話し合うためのフォーラムと、最新イベントについての記事と予告を掲載する中心的なニュース・ページで構成されたサイトです。ニュース・ページでは、親睦会、名所や地方へのドライブ・ツアー、それに会費と予算編成案を話し合う総会などといったイベントをメンバーに通知します。さらに重要な点は、イベント・カレンダーのコピーをたくさん管理しなくても済むようにすることです。イベント・カレンダーを一箇所で入力し、その情報にはサイトのどこからでもアクセスできるようにしたいというのがカー・クラブの要望でした。

Google Calendar と Google データ API

1 つのソリューションとなるのは、Google Calendar のようなオンライン・カレンダー・アプリケーションです。イベントの表示と管理を一元化すると、Google Calendar ユーザーが一箇所に集められたイベント・データを共有および管理できるため、イベントの計画過程で発生する可能性のある多くのエラー原因の 1 つが排除されます。コミュニティーのメンバーは、オンライン・カレンダーにアクセスすれば、近く予定されているイベントや活動の最新情報を入手することができます。そのため、さまざまな Web ページに掲載された古いイベント情報に惑わされることはありません。オンライン・カレンダー・アプリケーションは理想的なソリューションのように思われました。

Web サイトの開発には Drupal コンテンツ管理システム (「参考文献」を参照) を使用する一方、イベント・カレンダーの管理には Google Calendar を使用するようクラブのリーダーたちに勧めました。最初はこのアイデアが受け入れられ、非常に能率的だという報告を受けました。私が彼らのために作成したイベント・サイドバーを更新するには Drupal 管理フロント・エンドが使いやすかったためです。しかし、クラブが盛況になるにつれて発表するイベント数が増えてくると、イベント・サイドバーの編集はもはや楽しい作業ではなくなり、次第に負担のかかる雑務となってきました。

この窮地を救ったのは、Google データ API、そしてこの API による Google Calendar イベント・データへのアクセスです。Google データ API が提供する Atom 出版プロトコル (APP: Atom Publishing Protocol) の実装は、さまざまなタイプの文書と情報を読み取って更新するための Web サービス API となります。さらに、Microsoft® .NET、Java™ プログラミング言語、Python、そして PHP に対応したサード・パーティーとの統合用 API もあり、これらの API が Google データ API の機能の多くを一連のオブジェクト指向ラッパー・クラスにカプセル化します。

ある程度調査した結果、イベント・サイドバーが Google Calendar アカウントから最新イベント・カレンダー・データを抽出して常に最新の状態を維持するようにカー・クラブの Drupal サイトを拡張することができました。


Google Calendar フィード

Google データ API には、Google が提供する Web サービスの多くを対象とした文書と情報が含まれる Atom フィードが多数用意されています。Google Calendar もその例に漏れず、Google Calendar データの大半をカプセル化する多数のフィードを提供しています。これらのフィードには、HTTP 認証フィードと公開フィードがあります。HTTP クライアントが認証フィードを取得して利用するには、HTTP GET リクエストと併せて認証情報を提供しなければなりません。また、認証フィードでは HTTP POST リクエストによって Google Calendar アカウントを更新することもできます。認証フィードを使用する HTTP クライアントには、イベントの追加と削除、カレンダーのサブスクライブとアンサブスクライブ、Google アカウントのカレンダーの作成および削除が可能になります。

Google Calendar API は、ユーザーがアクセス可能な個々のカレンダーすべてに Google Calendar GUI からアクセスするフィードを提供します。これらのカレンダーには、ユーザーが所有するカレンダー、ユーザーがサブスクライブしている別のユーザーのカレンダー、そしてユーザーが読み取り専用の状態でインポートしたカレンダーが含まれます。それぞれのカレンダーごとに固有の専用認証イベント・フィードおよび公開イベント・フィードがあり、そのフィードにはカレンダー内のイベントがリストされます。この記事で重点とするのは、公開イベント・フィードです。

Google Calendar フィードを公開フィードにする方法

Google Calendar カレンダーの公開イベント・フィード (アカウントごとに多数ある場合があります) を取得するには、Google Calendar アプリケーションにサインインして処理対象のカレンダーを選択し、カレンダー名の隣にある小さな下矢印をクリックします。この矢印をクリックするとフィードの名前の横にメニューが表示されるので、Calendar Settings を選択してください (図 1 に丸で囲んで示しています)。

図 1. 選択されたカレンダーの Google Calendar ドロップダウン・メニュー
選択されたカレンダーの Google Calendar ドロップダウン・メニュー

以上の操作によって Google Calendar アプリケーションは、イベントの時間帯、カレンダーの名前など、さまざまなカレンダー設定を選択できるページを表示します。公開フィードの場合に重要なオプションは、カレンダー自体を公開カレンダーにするか、それとも共有カレンダーにするかの設定です。共有カレンダーにして公開イベントのフィードから取得できるようにするには、Change sharing settings オプションをクリックします (図 2 を参照)。

図 2. カレンダーの可視性を変更して共有または公開カレンダーにする
カレンダーの可視性を変更して共有または公開カレンダーにする

ページに Share this calendar タブが表示されたら、このタブで Share all information on this calendar with everyone を選択します。すると、カレンダーを公開するかどうかを確認する一連の質問が表示されるので Yes と答えます。タブの下部にある Save をクリックして設定を保存することも忘れないでください。カレンダーへのイベントの追加を続けるには、Back to Calendar をクリックします。

Google Calendar フィードの内容について

Google Calendar フィードの例は、「ダウンロード」セクションに記載したサンプル・コードに含まれる full.xml ファイルを参照してください。また、この記事のサンプル・コードで使用しているダミー・フィードへのリンクも記載しておきました (「参考文献」を参照)。

イベント・フィードには、イベントのタイトルや説明、そしてイベントが行われる日時と場所など、イベントを記述する各種の要素が含まれます。また、Google Calendar はイベントに招待する人々のリストを E メールのリストによって管理し、イベントが更新されるたびに、リストされた人々にイベントの詳細を送信します。E メール・アドレスが Google Calendar ユーザーを表す場合、ユーザーはアプリケーションを介してその招待に返答することができます。その場合、イベントは出席状況も保有することもなりますが、出席に関する詳細の統合はこの記事の範囲外なので、ここではイベントのタイトルと開催日時および場所などの基本的なイベント情報に焦点を絞ります。リスト 1 は、サンプル・フィードから抜粋したイベント・エントリーです。

リスト 1. サンプル Google Calendar イベント・フィードのエントリー: ID とタイムスタンプ
    <entry>
	    <id>
            http://www.google.com/calendar/feeds/foss.sanjuan%40gmail.com/public/full/
            s19o15ve3nn209gv5qf6c43ao4
        </id>
        <published>2007-08-12T15:45:40.000Z</published>
        <updated>2007-08-12T15:53:37.000Z</updated>
        ...
        ...

id 要素は、Google Calendar システム内部でこのイベントを識別する固有の URI (Uniform Resource Identifier) を指定します。この要素は固有の番号を保有するだけでなく、イベントの取得元となったフィードも識別します。published 要素と updated 要素が使用するのは RFC 3339 タイムスタンプ・フォーマットです。updated 要素はイベントが最後に編集された日時、あるいは新規イベントの場合には作成された日時を示します。

idpublishedupdated 要素の後には、より人間に読みやすい情報が含まれる要素が続きます (リスト 2 を参照)。この情報は、サイドバーまたはイベント・ページに表示できます。

リスト 2. サンプル Google Calendar イベント・フィードのエントリー: タイトル、作成者、状況
        ...
        ...

        <title type="text">Linux Install Fest</title>
        ...
        ...

        <author>
            <name>Open Source San Juan</name>
            <email>foss.sanjuan@gmail.com</email>
        </author>
        ...
        ...

        <gd:eventStatus value="http://schemas.google.com/g/2005#event.confirmed"/>
        ...
        ...

title 要素はイベントを識別する単純なストリングで、固有である必要はありません。author 要素は name および email 要素からなります。イベントの作成者とは、そのイベントをカレンダーに入力した Google Calendar ユーザーのことです。認証フィードと書き込みアクセス許可がそれ相応に設定されていれば、カレンダーの所有者以外の Google Calendar ユーザーが別のユーザーのカレンダーにイベントを作成することができます。status 要素に含まれる可能性のある値は、表 1 のとおりです。

表 1. gd:eventStatus 要素の値
内容
http://schemas.google.com/g/2005#event.cancelledキャンセルされたイベント
http://schemas.google.com/g/2005#event.confirmed確定されたイベント
http://schemas.google.com/g/2005#event.tentative暫定的に予定されているイベント

次に続くのは、イベントの開催日時と場所を記述する要素です (リスト 3 を参照)

リスト 3. サンプル Google Calendar イベント・フィードのエントリー: 日時と場所
        ...
        ...

        <gd:when startTime="2007-08-03T16:00:00.000-04:00" 
            endTime="2007-08-03T19:00:00.000-04:00"/>
        <gd:where 
            valueString="Guaynabo Public High School Auditorium, Guaynabo, PR"/>
    </entry>

when 要素には 2 つの属性が含まれます。1 つはイベントの開始時間、もう 1 つは終了時間で、いずれも RFC 3339 タイムスタンプ・フォーマットとなります。where 要素の valueString 属性は、Google Calendar アプリケーションからも、API からも検索することができます。Google Calendar と Google データ API はいずれも個々の要素を基準とした検索をサポートしない代わりに、string 要素 (titleauthordescription など) と where 要素の valueString 属性で全文検索を行います。ただしこの後説明するように、Google データ API の場合には開始日の範囲を設定してクエリー結果に含めるイベントを制限することができます。

Google Calendar フィードのコンテンツを制限する方法

まさに対象とするデータのセットを取得できるように、Google データ API では HTTP GET リクエストでクエリー・パラメーターの概念をサポートします。Google データ API クライアントはこれらのパラメーターを使用して、返されるエントリーの最大数 (max-results パラメーター)、フィードに含まれるエントリーのソート基準にする要素 (orderby パラメーター)、返されるエントリーの範囲に含める開始日時と終了日時 (start-min および start-max パラメーター) を指定することができます。この最後の 2 つのパラメーターは、イベントの結果セットに含めるイベント開始日時の日付範囲のことです。つまり、start-min は範囲の開始日を指し、start-max は範囲の終了日を指します。両方とも、RFC 3339 タイムスタンプ・フォーマットで表されます。

もう 1 つ、singleevents というパラメーターもあります。このパラメーターをクエリー・ストリングに含めると、再帰イベントが解析しやすくなります。singleevents パラメーターの値が true の場合、再帰イベントは 1 つの個別のイベントであるかのようにフィードに指定されます。値が true でない場合は、iCal フォーマットの再帰ルールを含む <gd:recurrence> 要素が再帰イベントに組み込まれます。iCal フォーマットとその解析方法は、この記事では説明しません。

すべてのクエリー・パラメーターが追加されたイベント・フィードの URL は、リスト 4 のようになります。この URL は非常に長いので、読みやすくするために複数の行に分けて編集してあります。

リスト 4. クエリー・パラメーターを指定したサンプル Google Calendar フィードの URL
    http://www.google.com/calendar/feeds/foss.sanjuan%40gmail.com/public/full?
    max-results=25&
    singleevents=true&
    orderby=starttime&
    start-min=2007-05-22T09%3A58%3A47-04%3A00&
    start-max=2007-11-06T09%3A58%3A47-04%3A00

PHP での Google Calendar フィードの解析

Google Calendar イベント・フィードの要素、そしてフィードをクエリーして対象のエントリーを取得する方法を説明したところで、ここからはフィードを解析してページに表示する手段について検討していきます。イベント・エントリーのリストを取得し、各イベントのタイトル、日付、時間、そして場所を抽出するには、PHP が提供する複数の XML API を使用することができます。そのうち、まずは DOM (Document Object Model) API について説明します。

DOM によるフィードの解析

XML DOM API は、XML アプリケーションのプログラミングでもっともよく使われている 標準 XML 構文解析 API です。DOM API の使用方法はこの記事の範囲外ですが、Google Calendar イベント・フィードを解析するプログラムを例に DOM API の利点と欠点について説明します。

XML DOM API の利点の 1 つは、その速さです。この API は XML 文書全体をメモリーにロードするため、XML 文書からの要素の取得はすべてメモリー内で行われます。この方法は大規模な XML ファイルには非効率的ですが、2 MB や 3 MB ほどの XML ファイルであれば許容範囲内です。

XML DOM API の利点としては、読みやすさも挙げられます。DOM による XML の構文解析は、XML 文書を開くコマンド、そしてその文書から階層的に特定の要素を取得するコマンドで構成されます。この解析は英語に似ていて、PHP に「タグ名が 'entry' の要素を取得」と指示するようなものです。リスト 5 はその一例です。

リスト 5. DOM API による Google Calendar イベント・フィードの解析
<?php 
    $confirmed = 'http://schemas.google.com/g/2005#event.confirmed';

    $three_months_in_seconds = 60 * 60 * 24 * 28 * 3;
    $three_months_ago = date("Y-m-d\Th:i:sP", time() - $three_months_in_seconds);
    $three_months_from_today = date("Y-m-d\Th:i:sP", time() + $three_months_in_seconds);

    $feed = "http://www.google.com/calendar/feeds/foss.sanjuan%40gmail.com/" . 
        "public/full?orderby=starttime&singleevents=true&" . 
        "start-min=" . $three_months_ago . "&" .
        "start-max=" . $three_months_from_today;

    $doc = new DOMDocument(); 
    $doc->load( $feed );

    $entries = $doc->getElementsByTagName( "entry" ); 

    foreach ( $entries as $entry ) { 

        $status = $entry->getElementsByTagName( "eventStatus" ); 
        $eventStatus = $status->item(0)->getAttributeNode("value")->value;

        if ($eventStatus == $confirmed) {
            $titles = $entry->getElementsByTagName( "title" ); 
            $title = $titles->item(0)->nodeValue;

            $times = $entry->getElementsByTagName( "when" ); 
            $startTime = $times->item(0)->getAttributeNode("startTime")->value;
            $when = date( "l jS \o\f F Y - h:i A", strtotime( $startTime ) );

            $places = $entry->getElementsByTagName( "where" ); 
            $where = $places->item(0)->getAttributeNode("valueString")->value;

            print $title . "\n"; 
            print $when . " AST\n"; 
            print $where . "\n"; 
            print "\n"; 
        }
    }
?>

上記のサンプル・コードでは、必要なパラメーターを指定してフィードの URL を設定し、フィードを開き、それから DOMNodeList に含まれるすべてのイベント・エントリーを取得しています。取得したエントリーは foreach イテレーターで処理することができます。そこで、それぞれのイベント・エントリーについて、gd:eventStatus 要素の value 属性を確定済みイベントの既知の値と比較しています。element タグには gd: 接頭辞を指定する必要がないことに注目してください。PHP DOM API は名前空間を理解するため、gd: 接頭辞を指定してしまうと、対象とする要素をパーサーが検出できなくなります。

イベント・エントリーが確定済みの場合は、titlegd:whengd:where 要素を取得します。gd:when 要素と gd:where 要素では、startTimevalueString などの特定の属性を要求する必要があります。イベントの日時は人間が読めるように表示したいので、gd:when 要素の startTime 属性を解析して日時を表す長整数型に変換し、それから共通表示フォーマットを使用して date 関数に渡します。

DOM API の 1 つの欠点は、多少冗長だということです。各ステートメントはアルゴリズムのテキスト記述に対応するため、その意図は明確になりますが、代わりにコードの簡潔さが犠牲になります。DOMNode および DOMDocument クラスの getElementsByTagName メソッドと getAttributeNode メソッドによって、コードがかなり増えてしまうからです。

SAX によるフィードの解析

PHP に使用できるもう 1 つの XML 構文解析 API は、SAX (Simple API for XML) API です。DOM API が読みやすいけれども冗長であるのに対し、SAX は簡潔ではあるものの、経験を積んだ開発者でさえも理解しにくい場合があります。SAX と DOM との違いは、SAX は DOM のように XML 文書全体をメモリーにロードしないという点です。効率的な SAX 処理コードは非常に処理が速く、メモリー使用量を最小限に抑えてアプリケーションの特定要件に合わせて調整することができます。一方、DOM の場合には、アプリケーションで処理する最大の XML 文書のサイズが最小メモリー要件となります。

リスト 6 は、Google Calendar イベント・フィードを SAX で処理するサンプル・コードからの抜粋です。完全なサンプル・コードについては、「ダウンロード」セクションに記載しているサンプル・スクリプト・パッケージに含まれる sax_sample.php スクリプトを参照してください。

リスト 6. SAX API による Google Calendar イベント・フィードの解析
    function startElement( $parser, $tagName, $attr )
    {
        global $g_entries, $g_tagName, $g_confirmed, $g_is_confirmed, 
            $g_in_entry, $g_in_originalevent;

        if ( $tagName == 'ENTRY' ) {
            if ($g_is_confirmed || count( $g_entries ) == 0) {
                $g_entries []= array();
            }
            $g_is_confirmed = false;
            $g_in_entry = true;
        }
        else if ($tagName == 'GD:EVENTSTATUS')
        {
            if ($attr['VALUE'] == $g_confirmed) {
                $g_is_confirmed = true;
            }
        }
        else if ($tagName == 'GD:WHEN' && $g_is_confirmed && 
            $g_in_originalevent == false)
        {
            $startTime = date( "l jS \o\f F Y - h:i A", strtotime($attr['STARTTIME']) );
            $g_entries[ count( $g_entries ) - 1 ]['when'] = $startTime;
        }
        else if ($tagName == 'GD:WHERE' && $g_is_confirmed)
        {
            $g_entries[ count( $g_entries ) - 1 ]['where'] = $attr['VALUESTRING'];
        }
        else if ( $tagName == 'GD:ORIGINALEVENT' ) {
            $g_in_originalevent = true;
        }
        $g_tagName = $tagName;
    }

    function endElement( $parser, $tagName ) 
    {
        global $g_tagName, $g_in_entry, $g_in_originalevent;

        if ( $tagName == 'ENTRY' ) {
            $g_in_entry = false;
        }
        else if ( $tagName == 'GD:ORIGINALEVENT' ) {
            $g_in_originalevent = false;
        }
        $g_tagName = null;
    }

    function textData( $parser, $text )
    {
        global $g_entries, $g_tagName, $g_in_entry;
        if ($g_tagName == 'TITLE' && $g_in_entry) {
            $g_entries[ count( $g_entries ) - 1 ]['title'] = $text;
        }
    }

上記のコードには、SAX サンプル・スクリプトの SAX パーサー・エンジンに登録された startElementendElement、および textData が示されています。コードのこの部分が示しているのは、複数の対象要素を処理する方法です。コードがかなり複雑になっている理由は、SAX では XML 文書の上から下に向かって XML 要素を読み取るためです。つまり、対象要素のいくつかを処理するまでは現行のイベント・エントリーが確定済みであるかどうかをチェックすることができません。例えば、イベント内では gd:eventStatus 要素が title 要素の後に来ています。コードを複雑にするもう 1 つの原因は、フィードの title 要素だけでなく、エントリーごとにも title 要素があるという点です。コードにエントリー内の title 要素を読み取っていることを認識させるには、フラグを設定しなければなりません。また、再帰イベントの gd:originalEvent 要素に含まれる gd:when 要素は、対象としている gd:when 要素とは別のものです。そのため、状態確認ロジックを使わずに単純に要素名を突き合せるだけでは、誤った出力になってしまいます。

この例から、SAX API の 2 つの欠点がわかります。1 つは DOM API よりも、はるかに冗長であること、そして DOM サンプル・コードと比べると SAX パーサーはその何倍も複雑であることです。

Zend Google API ラッパーによるフィードの解析

Zend Technologies は Google との連携の下、Zend Framework を拡張して PHP を対象とした一連のオブジェクト指向のクラスを提供しています。これらのクラスは、Google データ API による Atom フィード詳細の多くを隠す一方、あらゆる Google データ情報をサポートします。また、各種の Google データ API 文書をメソッドと単純なパラメーターを持つ抽象オブジェクトとして表したり、Atom フィードと HTTP プロトコルのアクション (GETPUTPOST など) の詳細を隠したりするなど、さまざまな利点があります。オブジェクト指向のクラスは Google データ API でのユーザー認証の詳細もカプセル化するため、サード・パーティー・クライアントからのイベントの作成と構成をはじめ、Google データ API のすべての機能をサポートするアプリケーションを簡単に作成することができます。

このように、XML の構文解析や HTTP プロトコルの詳細に馴染みのない開発者にとって開発は簡単になるものの、Zend Framework の Google データ API クラスには欠点もあります。それは、パラメーターを使用しないオブジェクト・コンストラクターが多いため、リスト 7 に示すようにコードが冗長になるという点です。したがってオブジェクトを作成した後に、プロパティーの設定を通じてオプションを設定しなければなりません。

リスト 7. Zend PHP クラスによる Google Calendar イベント・フィードの要求と処理
<?php
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_Calendar');

$three_months_in_seconds = 60 * 60 * 24 * 28 * 3;
$three_months_ago = date("Y-m-d\Th:i:sP", time() - $three_months_in_seconds);
$three_months_from_today = date("Y-m-d\Th:i:sP", time() + $three_months_in_seconds);

// Create an instance of the Calendar service without authentication,
// for read-only access to a public feed
$service = new Zend_Gdata_Calendar();

$query = $service->newEventQuery();
$query->setUser('foss.sanjuan%40gmail.com');
$query->setVisibility('public');
$query->setProjection('full');
$query->setOrderby('starttime');
$query->setStartMin($three_months_ago);
$query->setStartMax($three_months_from_today);

// Retrieve the event list from the calendar server
try {
    $eventFeed = $service->getCalendarEventFeed($query);
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getResponse();
}

// Iterate through the list of events, outputting them as an HTML list
print "<ul>\n";
foreach ($eventFeed as $event) {
    print "<li>" . $event->title . "</li>\n";

	$startTime = $event->when->startTime;
    print "<li>" . date("l jS \o\f F Y - h:i A", strtotime( $startTime ) );

	// Google Calendar API's support of timezones is buggy
    print " AST</li>\n";

    print "<li>" . $event->where->valueString . "</li>\n";
}
print "</ul>\n";

Zend Google データ API クラスの利点は、eventFeed オブジェクトを取得した後、eventFeed オブジェクトのプロパティーとしてフィードの要素と属性を階層的にトラバースできることです。私の意見としては、この方法では各種の要素が XML 文書内での表示と同じように編成されるため、処理コードを理解しやすくなると思います。さらに属性についても、DOMNode の getAttributeNode("attributeName")->value メソッドとは異なり、その親要素から階層的にトラバースすることができます。


XPath と SimpleXML による構文解析

私がとりわけ使いやすい XML の構文解析 API だと思うのは、XPath を使用した XML 文書のクエリーをサポートする SimpleXML API です。SimpleXML API は XML 文書を、ロード済み XML 文書のオブジェクト・プロパティーの階層セットとしてカプセル化します。これは、DOM が階層的にトラバース可能な DOMNode オブジェクト・インスタンスを返す方法と同様です。つまり、Zend クラスとは異なり、要素の属性を取得するには DOM の DOMNode オブジェクトと同じく attributes() メソッドを呼び出します。この呼び出しによって、属性をオブジェクト・プロパティーとして保持するオブジェクトが返されます。リスト 8 に、SimpleXML で Google データ API カレンダー・フィードを解析およびトラバースするために必要なすべてのロジックを記載します。

リスト 8. XPath と SimpleXML による Google Calendar イベント・フィードの解析
    $s = simplexml_load_file($feed); 

    foreach ($s->entry as $item) {
        $gd = $item->children('http://schemas.google.com/g/2005');

        if ($gd->eventStatus->attributes()->value == $confirmed) {
?>
            <font size=+1><b>
                <?php print $item->title; ?>
            </b></font><br>

<?php 
            $startTime = '';
            if ( $gd->when ) {
                $startTime = $gd->when->attributes()->startTime;
            } elseif ( $gd->recurrence ) {
                $startTime = $gd->recurrence->when->attributes()->startTime; 
            } 

            print date("l jS \o\f F Y - h:i A", strtotime( $startTime ) );
            // Google Calendar API's support of timezones is buggy
            print " AST<br>";
?>
            <?php print $gd->where->attributes()->valueString; ?>
            <br><br>

<?php
        }
    } ?>

PHP の SimpleXML API には、DOM や SAX よりも最大 4 倍時間がかかるという欠点があります。その原因は、SimpleXML は PHP の動的性質を活用して実行中に要素と属性ノードを作成するからです。これとは対照的に、Zend Google データ API クラスはクラス・コード内に要素および属性ノードを定義するため、Zend クラスによる階層的ノードのトラバーサルのほうが短時間で行われます。

SimpleXML のもう 1 つの欠点は、その XML 名前空間のサポートです。名前空間から要素を取得するには、要素内で children() メソッドを呼び出さなければなりません。このメソッド呼び出しは、該当する名前空間に含まれる要素を持つオブジェクトをプロパティーとして返します。これに比べ、すべての名前空間のすべての要素を他の要素の階層的にトラバース可能な個々のノードとして表す DOM と Zend クラスのサポートのほうが優れています。


パフォーマンス上の考慮事項とキャッシング

PHP での XML 構文解析についての説明で、SimpleXML は DOM や SAX での構文解析に比べ、何倍も時間がかかる可能性があると言いましたが、それでも私が SimpleXML を選択する理由は、SAX よりも使いやすいこと、そして DOM よりも読みやすい XML 構文解析コードになるためです。SimpleXML は要素ノードと属性ノードを他のノードのプロパティーとして実行中に生成するため、ノードのトラバーサル・コードは Zend Google データ API クラスと同じくらい読みやすくなります。

ただし、この記事に記載したサンプル・コードには別の問題があります。それは、ユーザーがページにアクセスするたびに、このコードが Google Calendar イベント・フィードを要求することです。このサンプルを PHP サイトのサイドバーに配置すると、ユーザーがサイトのいずれかのページにアクセスするたびにフィードがあらためてダウンロードされ、解析されることになります。カレンダーが 1 時間ごと、あるいは毎日変更される可能性はまずないため、これは無駄な動作です。サイトに人気があって、ページのアクセス件数が毎日 1000 件を超える場合 (ユーザー数が 20 から 30 しかない人気のフォーラムでも簡単に達成できるアクセス数です)、ページがアクセスされるごとにフィードが要求され、処理されることを考えてみてください。Google データ API には、このような使用方法が許可されないことを明確に規定する使用条件があります。この使用条件に反した場合、Google はそのサーバーの IP アドレスをブロックするはずです。さらに Google がアカウントを取り消してしまうおそれもあります。したがって、このサンプル・コードにはより賢明な手段が必要なことは明らかです。

サンプル・コードを Google API の使用条件の範囲内で賢明に機能させるために実装できる方法は複数あります。いずれの方法にも共通して必要なのは、スクリプトによって表示される情報をキャッシュに入れ、その情報が変更された場合にのみフィード全体の処理を要求することです。キャッシング手法についての詳しい説明はこの記事の範囲外ですが、簡単にだけ説明することで、スクリプトの改善方法についての概念を簡単に説明しておきます。

1 つの手法は、HTTP リクエスト・ヘッダーを使って Google データ API フィードが変更されているかどうかを調べることです。Google データ・サービスでは、返された情報に含まれる <atom:updated> 要素の値に応じて Last-Modified 応答ヘッダーを設定します。スクリプトは初めて呼び出されるときにフィードの処理によって生成された出力を HTML ファイルに保存し、<atom:updated> タイムスタンプの値をデータベース・レコードに格納することができます。そのため、次回ユーザーがイベント・カレンダーの表示を要求するときに、クライアントがタイムスタンプを取得し、そのタイムスタンプを If-Modified-Since リクエスト・ヘッダーに格納してリクエストを送信すれば、変更されていないフィードが再び取得されることはありません。

If-Modified-Since ヘッダーに示された時刻以降にコンテンツが変更されていない場合、Google データ・サービスは 304 (Not Modified) HTTP 応答を返します。この応答はイベント・データが変更されていないことを意味するため、スクリプトは前に生成された HTML ファイルを組み込みます。フィードが変更されている場合には、Google データ・サービスは 200 (OK) HTTP 応答を返します。この応答が返されると、スクリプトは HTML ファイルのコンテンツを生成し直してデータベース・レコードを <atom:updated> 要素の新しいタイムスタンプに設定し、新たに生成された HTML ファイルを組み込みます。

タイムスタンプを 1 つだけデータベースに保存するという方法はかなり行き過ぎですが、PHP は Microsoft ASP (Active Server Pages)、Microsoft ASP.NET、Java サーブレット、そして JSP (JavaServer Pages) のようにはアプリケーション・レベルの変数をサポートしません (少なくとも、設定なしですぐに使用することはできません)。同様のソリューションには、共有メモリーを使用するという手段もありますが、セキュリティーのリスクになるおそれがあることから、すべての PHP システムが共有メモリーをサポートするとは限りません。別のキャッシング手段としては、memcached も考えられます。この手段は、PHP アプリケーションを Web サーバー・クラスター内で実行している場合には有力候補です。このクラスターに対応したメモリー・キャッシング・サービスは元々 LiveJournal の作成者たちによって作成されたものですが、今では FaceBook の開発者によって大幅に改善されています。


まとめ

Google Calendar はWeb アプリケーションの中心的フロント・エンドとなり、組織とそのリーダーたちはこのフロント・エンドから、所属するメンバーや一般の人々向けのイベント・カレンダーを管理し、公開することができます。Google データ API が提供する Atom フィードと Atom 出版プロトコルは、Google Calendar をはじめとするほとんどすべての Google アプリケーションを使用したイベントやその他の情報の取得、問い合わせ、更新、作成に対応します。

XPath を使用して Google データ API イベント・フィードに問い合わせを行い、エントリーの要素のなかから関連する詳細に対応するエントリーを解析することで、Web サイトが常に最新のイベント予定を自動表示するようにできます。XPath は PHP ツールキットのなかで最速の XML API ではありませんが、きちんと文書化された XML 文書が手元にある場合にはもっとも使いやすい XML API です。XPath が他と比べて速度が劣ることから生じるパフォーマンスの影響は、キャッシングを用いることで軽減してください。


ダウンロード

内容ファイル名サイズ
Sample PHP codeos-php-xpath.google-calendar-api.zip4KB

参考文献

学ぶために

  • Drupal の Web サイトで Drupal コンテンツ管理システムの詳細を学んでください。
  • Google Calendar APIs and Tools 概要に、Google Calendar API に考えられる使用法がわかりやすく説明されています。また、この API のすべてのパーツに関する詳しい資料へのリンクも記載されています。
  • PHP.net は、PHP 開発者のための情報源です。
  • Recommended PHP reading list」を読んでください。
  • developerWorks ですべての PHP 関連記事を調べてください。
  • IBM developerWorks の PHP project resources にアクセスして、PHP のスキルを磨いてください。
  • ソフトウェア開発者を対象とした興味深いインタービューや討論については、developerWorks ポッドキャストをチェックしてください。
  • PHP でデータベースを使用するには Zend Core for IBM を調べてみてください。シームレスにすぐに使えて、インストールも簡単なこの PHP 開発および実動環境は、IBM DB2 V9 をサポートします。
  • developerWorks の Technical events and webcasts で最新情報を入手してください。
  • 世界中で近日中に予定されている IBM オープン・ソース開発者を対象とした会議、見本市、ウェブ放送やその他のイベントをチェックしてください。
  • オープン・ソース技術を使用して開発し、IBM の製品と併用するときに役立つ広範囲のハウツー情報、ツール、およびプロジェクト・アップデートについては、developerWorks Open source ゾーンを参照してください。
  • 無料の developerWorks On demand demos で、IBM およびオープンン・ソースの技術と製品機能を調べて試してみてください。

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

議論するために

コメント

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=Open source, XML
ArticleID=277975
ArticleTitle=XPath を使用して Google Calendar イベントを PHP Web サイトに表示する
publish-date=11272007