2007年1月アーカイブ

ふと興味がわいたので、試してみました。

幸い、PNG画像データ生成はPHPの再コンパイルなど無しに出来たので、オプションの意味を理解するだけで使う事が出来ました。
サンプル

マニュアルのサンプルコードを参考に、『表示出来ればよし』まで。ファイルを作りたくなかったので、dataスキームで表示させています。ブラウザによって表示出来ませんが、あしからず。

以下、メモ。

  • フォントファイルまでのパスを、font_pathとfont_fileで指定
  • font_pathとfont_fileは単純に連結される(パスの正規化などはしてくれない)
  • なぜか、Imageドライバのinitメソッドに渡す引数が第1・第2ともにwidth(可変引数で渡した場合)
  • TTFが手元にあって、TTFからPNGを作れる(PHP)環境ならすぐ試す事が出来る

最後に、サンプルのコードを。

<?php
 
/**
 * @see <http://pear.php.net/manual/en/package.text.text-captcha.php#package.text.text-captcha.example>
 *
 *   - 以下のコードは上記ページに掲載されているコードを改変したコードです。
 */
 
require_once 'Text/CAPTCHA.php';
 
/**
 * オプション設定
 *
 *   - Text_CAPTCHA::factoryメソッドによって作られるドライバのinitメソッドに渡す
 *   - Imageドライバのinitメソッドは可変引数に対応している
 *     - ただし、第1、第2引数がともにwidthであるので、連想配列で渡す事にする
 *   - フォントファイルは読み込み可能な場所においてある必要がある
 *     - font_pathとfont_fileを単純に連結するため、パスの指定には注意
 */
$options = array(
                 'width'     => 300,
                 'height'    => 130,
                 'phrase'    => null,
                 'imageOptions' => array(
                                         'font_size' => 36,
                                         'font_path' => './fonts/',
                                         'font_file' => 'sazanami-gothic.ttf'
                                         ),
                 );
 
/**
 * ドライバを作成して初期化
 *
 *   - Imageドライバを選択
 *   - 先に設定したオプションを利用して初期化
 *   - PEAR::isErrorでエラー処理
 */
$c = Text_CAPTCHA::factory('Image');
$retval = $c->init($options);
if (PEAR::isError($retval)) {
    echo '<h1>Error generating CAPTCHA!</h1>';
    echo '<div>' . $retval->getMessage() . '</div>';
    exit;
}
 
 
/**
 * CAPTCHA画像をPNG形式で生成
 *
 *   - PEAR::isErrorでエラー処理
 */
$png = $c->getCAPTCHAAsPNG();
if (PEAR::isError($png)) {
    echo '<h1>Error generating CAPTCHA!</h1>';
    echo '<div>' . $png->getMessage() . '</div>';
    exit;
}
 
/**
 * 文字をテキストとして取得可能
 */
echo "<h1>Answer: " . $c->getPhrase() . "</h1>";
/**
 * 生成した画像データをdataスキームで表示
 */
echo "<img src=\"data:image/png;base64," . base64_encode($png) . "\" />";
 
?>
 
 

ちなみに、今回のサンプル内で利用しているフォントは、SourceForge.jp: Project Info - efontで配布されているさざなみフォントです。

Emacsのphp-modeでは、php-search-documentation(C-c C-f)でPHPのマニュアルを検索可能。

バッファ上の関数名にカーソルを合わせて"C-c C-f"でマニュアルを表示出来るのは便利。問題は、作業環境でネットワークがつながらなかったり、サーバ側に問題があった時にWWW相手ではどうしようもないということ。

そこで、オフラインハックのすすめにのって『マニュアルくらいは手元に置く』事を心がけてみます。基本的にCHMファイルを手元に置いていけば済む話ですが、折角なので『Emacsからの検索』である程度対応したいと思います。

php-search-documentationでは、php-search-urlに指定したURLに問い合わせを実行するので、これを自前のURLに置き換えます。

(add-hook 'php-mode-user-hook
  '(lambda ()
             ; (略)
             (setq php-search-url "http://man.local/php/")
             ; (略)
     ))

これで、例えば"str_replace"の上にカーソルを置いて"C-c C-f"とすれば、"http://man.local/php/str_replace"にリクエストが飛びます。ここで、"/php/"へのリクエストを全て検索スクリプトにリライトするなどして検索結果を得ます。

以下のようなリライトルールを指定しました。

 
        RewriteEngine On
        # PHP function search
        RewriteRule ^php/([\d\w]*)$ php/search.php?q=$1 [L]

以下は"/php/search.php"とした検索スクリプトです。

<?php
 
/**
 * Search someone from local PHP documentations
 *
 *   - 1) from PHP functions
 *   - 2) from PEAR packages
 *   - 3) grep PHP manuals
 *   - 4) grep PEAR manuals
 *   - 5) Document Not Found
 *
 *   Directories:
 *     - /path/to/DocumentRoot
 *       - /php
 *         - /search.php <self>
 *         - /html
 *           - /php <PHP Manual; html>
 *           - /pear <PEAR Manual; html>
 *
 *   Rewrite use(Apache):
 *     - ex)
 *        RewriteEngine On
 *        RewriteRule ^php/([\d\w]*)$ php/search.php?q=$1 [L]
 *
 * @author koshigoe <KoshigoeBushou@gmail.com>
 * @see    PHP: Download documentation <http://jp.php.net/download-docs.php>
 * @see    PEAR :: Documentation <http://pear.php.net/manual/>
 *
 */
 
define("SEARCH_PATH", realpath(dirname(__FILE__) . "/html/"));
 
if (isset($_GET["q"])) {
    $q = "";
}
$q = preg_replace("|[^\d\w]|", "", $_GET["q"]);
 
$search = new PHP_Manual_Search(SEARCH_PATH);
 
if (($content = $search->searchPhpFunction($q)) != null) {
    echo $content;
} else if (($content = $search->searchPearPackage($q)) != null) {
    echo $content;
} else if (preg_match("/[A-Z]/", $q) == 0 and
           ($content = $search->grepPhpManual($q)) != null) {
    echo $content;
} else if (preg_match("/[A-Z]/", $q) > 0 and
           ($content = $search->grepPearManual($q)) != null) {
    echo $content;
} else {
    header("HTTP/1.x 404 Not Found");
    echo '<h1>404 Error Document Not Found: ' . $q . '</h1>';
}
exit;
 
 
class PHP_Manual_Search
{
 
    public function __construct($baseDir)
    {
        if (!file_exists($baseDir) or !is_dir($baseDir)) {
            throw new Exception('base directory not faound.');
        }
        $this->baseDir = $baseDir;
    }
 
    public function searchPhpFunction($search)
    {
        $fileName = "function." . str_replace("_", "-", $search) . ".html";
        $path     = $this->baseDir . "/php/" . $fileName;
        if (is_readable($path)) {
            return file_get_contents($path);
        } else {
            return null;
        }
    }
 
    public function searchPearPackage($search)
    {
        if (strtolower($search) == "pear" and
            is_readable($this->baseDir . "/pear/package.pear.html")) {
            return file_get_contents($this->baseDir . "/pear/package.pear.html");
        }
        $subject = '.*/package\.[^\.]*\.' . str_replace("_", "-", strtolower($search)) . '\.html';
        $where   = $this->baseDir . "/pear/";
        $results = array();
        exec("find $where -regex \"$subject\"", $results);
        if (sizeof($results) > 0) {
            sort($results);
            if (is_readable($results[0])) {
                return file_get_contents($results[0]);
            } else {
                return null;
            }
        } else {
            return null;
        }
    }
 
    public function grepManual($search, $dir, $title = "Search results")
    {
        $results = array();
        $content = "<html>
<head>
  <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />
  <title>$title</title>
</head>
<body>";
        exec("grep -A3 -B3 -r $q " . $dir, $results);
        if (sizeof($results) > 0) {
            $content .= "<h1>Search results</h1>\n";
            $content .= "<dl>\n";
            $data = array();
            foreach ($results as $result) {
                if (preg_match("|^([\/\.\d\w\-]+)\:\s*(.+)$|", $result, $match)) {
                    $link = basename($match[1]);
                    if (!isset($data[$link])) {
                        $data[$link] = "";
                    }
                    $data[$link] .= str_replace(array("<", ">"), "", strip_tags($match[2]));
                }
            }
            foreach ($data as $link => $desc) {
                $content .= '<dt><a href=html/"' . $link . '">' . $link . '</a></dt>' . "\n";
                $content .= '<dd>' . $desc . '</dd>' . "\n";
            }
            $content .= "</dl>\n</body>\n</html>";
        } else {
            return null;
        }
    }
 
    public function grepPhpManual($search)
    {
        $dir = realpath($this->baseDir . "/php/");
        return $this->grepManual($search, $dir, "PHP manual");
    }
 
    public function grepPearManual($search)
    {
        $dir = realpath($this->baseDir . "/pear/");
        return $this->grepManual($search, $dir, "PEAR manual");
    }
 
}
 
?>

『Emacsからの検索』にこだわっての結果が上記スクリプトですが、二兎を追うものなんとやらになりそうな気もします。あまりこだわらず、オンラインやCHMなどを利用しつつ快適なスタイルを身につけていきたいと思います。


IDEを試してみるべきでしょうか?Emacsにこだわりすぎているのかも。

urlencode関数を使うと全部エンコード対象になりますが、一部だけエンコードしたい場合はどう対応すべきでしょうか。

ひとまず、以下3段階を実験中です。

  • %は対象外
  • %を対象に含める
  • 予約文字なども対象に含める(全部)

urlencode関数は文字コードが混在していてもそれぞれのパーセントエンコード文字になるようなので、内部でのエンコード処理はurlencode関数を利用しています。

原則、pchar以外をエンコード対象として扱っていますが、間違いでしょうか?

Shift_JISやらUTF-8やらのマルチバイト文字列をパーセントエンコーディングせずに渡すと、戻って来た時に壊れているのはなぜでしょうか?

以下の環境で確認しました。

  • PHP5.0.4, PHP5.1.6 (CentOS4)
  • PHP5.2.0 (MacOSX; 10.4.8)

内部エンコーディングがEUC-JPだからか、EUC-JPが特別だからか、EUC-JPのマルチバイト文字列を渡した場合はぼちぼち上手く行く模様。

この関数は、指定された URL が有効かどうかを調べるためのもの ではなく、単に URL を上で示した 要素に分解するだけのものです。不完全な URL であっても受け入れられますし、 そのような場合でも parse_url() は可能な限り 正しく解析しようとします。
PHP: parse_url - Manual

分解目的なら、[RFC3986]Appendix B. Parsing a URI Reference with a Regular Expressionにある正規表現を利用したら良いのかな。

ちなみに、PEAR::Net_URLの中でparse_urlが使われているので、Net_URL::getURLを使ってURLを組み立てようとした場合など、注意が必要かもしれません。

php.iniや環境変数などに依存する問題なのかもしれませんが、詳しく調査していません。

ヒゲトリマー購入

いい加減めんどくさくなったので、店においてあった『National ER 206P-K』というヒゲトリマーを購入。

ひげバリカンだと思ってたけど、ヒゲトリマーなんて名前だったのね。

何がどう違くて、何を基準に選んでいいかさっぱり。ひとまずそれっぽくメモ。並びに意味はありません。

Piece Framework

Maple

Tonic

Symfony

Ethna

CakePHP

Zend Framework

個人的にPieceが気になりつつある。ダイコンとやらも気になるのでMapleも気になる。Symfonyも興味ある。CakePHPは一番人気らしいし気になる。RESTfulなTonicもPHP4.3を使おうか悩むくらい気になる。つまるところ、全部気になる。

iPhone

欲しいけど、通話のたびにディスプレイがひどく汚れそうで心配。
Apple - iPhone

皮脂多めの人用にカバー付きを希望。

しかしまあ、Appleを凝縮した印象です。凄い。アジア展開は2008年かららしいけど、1年かぁ。日本は周波数帯が違うっぽいし、別の展開として2007秋くらいに登場とか無いかな。


iTVはリッチな人向けな印象。
Apple - Apple TV
我が家のテレビは14型テレビデオ(モノラル)。ワイドスクリーンって何ですか?弁当箱型は好きだけどね。

弁当箱型と言えば、AirPort Extremeも弁当箱型なんだね。
Apple - AirPort Extreme

『RSS検索』ってなんだ?

『RSSの一覧を得る』の?『アイテムの一覧を得る』の?よく分からないのでネタとして書いてみる。
※内容は無いので無視を推奨します(なんのためにもなりません)。

わりとよく引っかかってる気がする。単純に(日時計算周りは)バグ率が高いってことかな?

date()関数のformatパラメータ"W"について。

ISO-8601 月曜日に始まる年単位の週番号 (PHP 4.1.0 で追加)
PHP: date - Manual

恥ずかしながら、ISO-8601を読まずに、得られる結果だけを見て(一桁は1文字)ました。なので、<=PHP5.0.4での挙動を疑わず、PHP5.1.6での挙動にびっくり。

Fixed bug #34302 (date('W') do not return leading zeros for week 1 to 9). (Derick)
PHP: PHP 5 ChangeLog
PHP: PHP 5 ChangeLog
The Date function have an ISO compilant Week parameter 'W'

This parameter should return leading zeros for week 1 to 9 (alwaysreturning 2 digits).
PHP Bugs: #34302: Date('W') do not return leading zeros for week 1 to 9 (as specified in ISO)

>=PHP5.0.5(>=PHP5.1.0)からは、常にゼロ詰めの2桁で返る模様。

strtotime()関数に年月文字列を指定した場合の日付の扱いに付いて。

echo date("Y-m-d", strtotime("2006-01")) . "\n";

日付が省略された場合、1日とするか現在の日付とするかの違いがある(PHP5.0.4では1日でPHP5.1.6では現在)。これはどれが当てはまる情報かよく分からなかったのでパス。ISOなんたらなのかな?

まあ、じっくり検証しろってことですね。

プロフィール

このアーカイブについて

このページには、2007年1月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2006年12月です。

次のアーカイブは2007年2月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。