PHPライブラリ「toInlineCSSDoCoMo」をEthnaに組み込んでみた

アシアルBlogにて、すばらしいライブラリが公開されました。
DoCoMo向けにCSS指定をインラインに埋め込むPHPライブラリ「toInlineCSSDoCoMo」作りました : アシアルブログ


昨日、GREEさんのオープンソースカンファレンスに参加し、株式会社ディー・エヌ・エーの川崎さんによる、MobaSiFのお話を聞いて来ました。
GREE Engineers' Blog | グリーエンジニアブログ
モバイルで工夫している点など、いろいろ聞けましたが、やはり工夫している方向性は似たような感じでした。


そこでも話が出てきたのですが、そろそろ下位端末を対応範囲から外し、よりリッチなデザイン性のあるモバイルサイトをという方向になっていくと思います。
モバゲーのような携帯サイトを作るための12のTips 携帯サイトを作ろう! -ちょっと詳しいモバイルサイトの作り方-」こちらのようなブログがすごいブクマされているのも、みんなが気にしていることだからではないでしょうか?


しかし、そこで問題になってくるのが、docomoのCSSの仕様です。
docomoのCSSは「i-CSS」というらしいのですが、以下のような制限があります。

  • i-CSSは、iモード対応XHTML用のCSSです。
  • インラインのみに対応しています。
  • 内部参照にも基本的に対応していませんが、以下に限り内部参照に対応しています。
    • リンク擬似クラスa:link
    • 動的擬似クラスa:focus
    • リンク擬似クラスa:visited
  • 外部参照には対応していません。

http://www.nttdocomo.co.jp/service/imode/make/content/xhtml/about/index.html


「外部参照には対応していません。」こいつが曲者です。しかも、styleタグも対応していないので、インラインで全て書かなければなりません。
これまでの、fontタグでちまちま書いていたのと同じと言えばそれまでですが、やはりCSSの良さが半減です。


そこで、使えそうなのが、この「toInlineCSSDoCoMo」です。
PerlにあるHTML::DoCoMoCSSを移植したいなーと考えていたところでしたので、タイムリーで早速使ってみました。


ただの素で使う形式でもいいのですが、Ethnaも 2.5.0 preview1が出たところなので、さっそくそちらも使ってみます。

まずは準備

Ethnaを落としてきます。

$ mkdir sample
$ svn export http://svn.sourceforge.jp/svnroot/ethna/ethna/tags/ETHNA_2_5_0_PREVIEW1/ sample/lib/Ethna
$ echo -e '#!/bin/sh\nexport ETHNA_HOME=$(dirname $0)/lib/Ethna\n$ETHNA_HOME/bin/ethna.sh "$@"' > sample/ethna.sh


プロジェクトを作成します。

$ sh sample/ethna.sh add-project -b ./ sample

エントリポイントを修正

$ vi sample/www/index.php
<?php
require_once dirname(__FILE__) . '/../app/Sample_Controller.php';

Sample_Controller::main('Sample_Controller', 'index');
?>

toInlineCSSDoCoMoの準備

toInlineCSSDoCoMo.tar.gzを落としてきて、解凍します。

$ wget http://blog.asial.co.jp/data/toInlineCSSDoCoMo.tar.gz
$ tar zxvf toInlineCSSDoCoMo.tar.gz


Ethnaのlibディレクトリ内に解凍したファイルを移動します。

$ mv toInlineCSSDoCoMo/lib/toInlineCSSDoCoMo.php sample/lib/
$ mv toInlineCSSDoCoMo/lib/selectorToXPath.php sample/lib/


sampleディレクトリ内に移動

$ cd sample

※以降は、sampleディレクトリ内でコマンドを発行します。


PEARのHTML_CSSが必要なので、落としてきます。
ここでは、ethnaのlibディレクトリ内にインストールするようにします。

$ sh ethna.sh pear-local install HTML_CSS

Smartyインストール

Smartyが必要なので、こちらもインストールします。Smartyは、本家から落としてきてもいいのですが、pear.ethna.jpから落としてきます。

$ sh ethna.sh pear-local channel-discover pear.ethna.jp
$ sh ethna.sh pear-local install ethna/smarty


あとで、差分を見るためにコピーしておきます。これは本来は必要ありません。

$ cp -r ../sample ../sample_base

toInlineCSSDoCoMoを組み込む

今回の組み込み方は、Smartyのoutputフィルタを使ってみます。

ke-tai.orgさんのエントリにもありますが、outputフィルタよりも、obのフィルタの方がよさそうです。
ドコモのCSSをインラインに埋め込んでくれるPHPライブラリ「toInlineCSSDoCoMo」(続き) | ke-tai.org - インフィニットループ」(追記:2008-07-10)

まずは、Smartyプラグインを置くディレクトリを作成します。

$ mkdir app/plugin/Smarty


このディレクトリが使われるように Sample_Controllerを編集します。

$ vi app/Sample_Controller.php

修正箇所 98行目付近

<?php //色づけ
    /**
     *  @var    array       application directory.
     */
    var $directory = array(
()
        'log'           => 'log',
        'plugins'       => array('app/plugin/Smarty'),
        'template'      => 'template',
()
    );


次に、フィルタを作成します。

$ vi app/plugin/Smarty/outputfilter.cssdocomo.php
<?php
/**
 *  outputfilter.cssdocomo.php
 *
 *  @author     Tomoyuki MARUTA <tomoyuki.maruta@gmail.com>
 *  @package    Smarty plugin
 *  @version    0.0.1
 */

include_once 'toInlineCSSDoCoMo.php';

function smarty_outputfilter_cssdocomo($output, &$smarty)
{
    $basedir = dirname($_SERVER['SCRIPT_FILENAME']) . '/';
    try {
        $buf = toInlineCSSDoCoMo::getInstance()->setBaseDir($basedir)->apply($output);
    } catch (Exception $e) {
        // なんかする
    }

    header("Content-Type: application/xhtml+xml");
    header("Content-Length: " . strlen($buf));
    return $buf;
}

BaseDirをどうしようか悩んだのですが、とりあえず、SCRIPT_FILENAMEを基準にしてみます。
Content-Typeをだし、Content-Lengthも出してみます。このフィルタよりも後ろで何らかの変換をする場合には注意が必要です。


このフィルタが使われるようにSample_Controllerを修正します。

$ vi app/Sample_Controller.php

修正箇所 98行目付近

<?php //色づけ
    /**
     *  テンプレートエンジンのデフォルト状態を設定する
     *
     *  @access protected
     *  @param  object  Ethna_Renderer  レンダラオブジェクト
     *  @obsolete
     */
    function _setDefaultTemplateEngine(&$renderer)
    {
        if (preg_match('!^DoCoMo/2.0 .*!', $_SERVER['HTTP_USER_AGENT'])) {
            $smarty =& $renderer->engine;
            $smarty->autoload_filters['output'][] = 'cssdocomo';
        }
    }


これで準備ができました。

さいごに

ちょうど会社のデザイナーさんが、モバイルCSS使ったページを作っていたので、それの外部CSS化をしてみたいと思います。


ちょっと、思ったこと。
外部CSSの読み込み時にファイル名等で、キャリアによって読み込み分けをしたりとかすると、より柔軟性があがりそうだと思いました。


最後に今回の作業のdiffです。

diff -r -U2 -P sample_base/app/Sample_Controller.php sample/app/Sample_Controller.php
--- sample_base/app/Sample_Controller.php       2008-07-09 21:52:04.000000000 +0900
+++ sample/app/Sample_Controller.php    2008-07-09 21:53:06.000000000 +0900
@@ -96,5 +96,5 @@
         'locale'        => 'locale',
         'log'           => 'log',
-        'plugins'       => array(),
+        'plugins'       => array('app/plugin/Smarty'),
         'template'      => 'template',
         'template_c'    => 'tmp',
@@ -273,4 +273,19 @@
         return array('ja_JP', 'UTF-8', 'UTF-8');
     }
+
+    /**
+     *  テンプレートエンジンのデフォルト状態を設定する
+     *
+     *  @access protected
+     *  @param  object  Ethna_Renderer  レンダラオブジェクト
+     *  @obsolete
+     */
+    function _setDefaultTemplateEngine(&$renderer)
+    {
+        if (preg_match('!^DoCoMo/2.0 .*!', $_SERVER['HTTP_USER_AGENT'])) {
+            $smarty =& $renderer->engine;
+            $smarty->autoload_filters['output'][] = 'cssdocomo';
+        }
+    }
 }

diff -r -U2 -P sample_base/app/plugin/Smarty/outputfilter.cssdocomo.php sample/app/plugin/Smarty/outputfilter.cssdocomo.php
--- sample_base/app/plugin/Smarty/outputfilter.cssdocomo.php    1970-01-01 09:00:00.000000000 +0900
+++ sample/app/plugin/Smarty/outputfilter.cssdocomo.php 2008-07-09 21:52:29.000000000 +0900
@@ -0,0 +1,24 @@
+<?php
+/**
+ *  outputfilter.cssdocomo.php
+ *
+ *  @author     Tomoyuki MARUTA <tomoyuki.maruta@gmail.com>
+ *  @package    Smarty plugin
+ *  @version    0.0.1
+ */
+
+include_once 'toInlineCSSDoCoMo.php';
+
+function smarty_outputfilter_cssdocomo($output, &$smarty)
+{
+    $basedir = dirname($_SERVER['SCRIPT_FILENAME']) . '/';
+    try {
+        $buf = toInlineCSSDoCoMo::getInstance()->setBaseDir($basedir)->apply($output);
+    } catch (Exception $e) {
+        // なんかする
+    }
+
+    header("Content-Type: application/xhtml+xml");
+    header("Content-Length: " . strlen($buf));
+    return $buf;
+}

追記(2008-07-10)

ke-tai.orgさんのところで、取り上げていただいたようです。
ドコモのCSSをインラインに埋め込んでくれるPHPライブラリ「toInlineCSSDoCoMo」(続き) | ke-tai.org - インフィニットループ

私のサイトでは、よく使う表示ブロックは、Smartyのテンプレート関数を作成し、独自タグを設定しています。

例えば、最新ニュースを表示したいときには、タグ「{displayNews num="5″}」で表示するようにしています。
(こうすると、あちこちでニュースを表示できて便利なためです)

内部ではsmarty->fetchメソッドでニュースを取得しているのですが、fetchに対してもoutpufilterが効いてしまいます。
fetchされるテンプレートは当然ヘッダ情報などを持っていませんので、outputfilterとtoInlineCSSDoCoMoとの相性は、あまり良くありません。
http://ke-tai.org/blog/2008/07/10/cssdocomo2/

たしかに、うちでもよくやる手法なので、そこまで試していませんでした。
obのフィルタの方がよさそうですね。


ただ、obのフィルタだと、何かあったときに戻ってこれないので、いろいろちゃんと作りこんであげる必要がありそうです。


早く実用できるぐらいにしたいですね〜