« 2005年09月 | メイン | 2005年11月 »

2005年10月31日

Class::DBIとTime::Piece

MT::Neko::kak 500 Internal Server ErrorのnekokakさんがClass::DBI::Plugin::TimePieceという記事をアップされてます。

Class::DBIとTime::Pieceの連携(infalte/defalte)はClass::DBI::mysqlの実装がいい感じです。
autoinflateで一発。

__PACKAGE__->autoinflate(column_type => 'Inflation::Class');
#カラム型=>クラス
__PACKAGE__->autoinflate(timestamp => 'Time::Piece');
__PACKAGE__->autoinflate(datetime => 'Time::Piece');
__PACKAGE__->autoinflate(date => 'Time::Piece');
__PACKAGE__->autoinflate(dates => 'Time::Piece');#上の3行を1つでまかなう

inflateクラスにTime::Pieceを指定した場合、内部的にTime::Piece::MySQLを呼び出してフォーマット変換を自動的にやってくれるのもいい具合です。

MySQL限定と思えば、Class::DBI::mysqlを参考にしつつ

package Class::DBI::Plugin::TimePiece::MySQL
use Time::Piece::MySQL;
sub import {
    my $class = shift;
    my $pkg   = caller(0);
    unless($pkg->isa('Class::DBI')){
        croak(__PACKAGE__." is for Class::DBI application.");
    }
    no strict 'refs';
    *{"$pkg\::has_a_timepiece"} = sub {
        my $self  = shift;
        my $colum = shift;
        my $type   = shift;
        $self->has_a(
            $colum  => 'Time::Piece',
            inflate => "from_mysql_$type",
            deflate => "mysql_$type",
       )
    };
}
1;

これで良いと思う。

使い方はTokulogさんのDateTimeを使う方法と同じ。

__PACKAGE__->has_a_timepiece(last_modified=>'datetime');

でどうでしょう。

個人的には、pieceの綴りにいつも自信がないので、has_a_tpとかいうショートカットが欲しいところです。あと、MySQL以外、例えばPostgreSQLなどへ対応をしようと思うと、Time::Piece::Pgがないし、かなり大変かと思う。


あ、そもそもMySQLならClass::DBI::mysqlを使うか。

きよへろさんのPerlコードをリファクタリングしてみる

ハテナオヤさんのところでやってる「きよへろのPerlコードをリファクタしようのコーナー No.2」を自分でも書いてみる。

課題は、

「スクリプトを実行することで、現在の日付を返すスクリプト」


#!/usr/bin/perl
use strict;
use warnings;
my @lt = localtime;
printf("%04d/%02d/%02d %02d:%02d:%02d",$lt[5]+1900,$lt[4]+1,@lt[3,2,1,0]);

何が違うかと言うと、$year、$month、、と変数を使わないあたり。キーボードのストロークが少なくて済みます。
日付の処理はなんども使うので、5=year、4=month、、と覚えてしまう。

でも、「覚える」で終わっては、あまり良くないので

#!/usr/bin/perl
use strict;
use warnings;

my $LT_YEAR=5;
my $LT_MONTH=4;
my $LT_DAY=3;
my $LT_HOUR=2;
my $LT_MIN=1;
my $LT_SEC=0;

my @lt = localtime;
printf("%04d/%02d/%02d %02d:%02d:%02d",
    $lt[$LT_YEAR]+1900,$lt[$LT_MONTH]+1,
    @lt[$LT_DAY,$LT_HOUR,$LT_MIN,$LT_SEC]);

と書いてみたり。
あとは、constantやReadOnlyにしたり外部のモジュールにしたり。

やはりTime::Pieceは便利です。

FormValidator::SimpleでDBに値が存在しないか確認するプラグイン

Class::DBIをつかって値がDBに登録されていないかを確認するValidator。
使い方は、

create table user(
    id varchar(32) not null primary key,
    email varchar(120) not null unique
);

というテーブルを想定して、idとemailがすでに登録されていないかを確認するには、

FormValidator::Simple->check($q,[
    id=>[
        'NOT_BLANK',
        ['REGEX',qr/^[a-z][a-z0-9]{3,15}$/],
        ['CDBIUNIQUE','MyApp::M::CDBI::User']
    ],
    email=>[
        'NOT_BLANK',
        'EMAIL',
        ['CDBIUNIQUE','MyApp::M::CDBI::User','email']
    ]
]);


Class::DBIクラスの後ろにカラム名をいれると、primary keyではなくそのカラム名で検索して該当するレコードの有無を調べられます。便利。

ソースは以下。

package FormValidator::Simple::Plugin:: CDBIUnique;
use strict;
use FormValidator::Simple::Exception;
use FormValidator::Simple::Constants;
our $VERSION = '0.01';
sub CDBIUNIQUE{
    my ($self, $params, $args) = @_;
    if ( scalar(@$args) == 0){
        FormValidator::Simple::Exception->throw(
            qq/validation "CDBIUNIQUE" needs a Class::DBI class./
        );
    }
     my @args=@$args;
     my $cdbi = shift @args;
     my @ret;
     eval{
         if(@args == 0){
         	 @ret = $cdbi->retrieve($params->[0]);
         }else{
	         @ret = $cdbi->search(@args,$params->[0]);
	     }
     };
    FormValidator::Simple::Exception->throw(
        qq/Error executing $cdbi : $@/
    ) if($@);
    return (@ret > 0) ? FALSE : TRUE;
}

1;


もうすこしコードを見直しが必要かなと思うけど便利なのでAge。

2005年10月27日

FormValidator::Simpleプラグインの作成

1つ前のエントリーのFormValidator::Simpleは、validationに使うプラグインが簡単につくれるのも良いところ。すでにCPANにも

のといったプラグインがあがってます。

プラグインを使うには、FormValidator::Simple::Plugin::Japaneseならば

use FormValidator::Simple qw/Japanese [他モジュール]/;

または

FormValidator::Simple->load_plugin('FormValidator::Simple::Plugin:: Japanese'[,他モジュール]);

で利用できます。

プラグインは以下のような形で作成できる。

package FormValidator::Simple::Plugin::Example;

use strict;
use FormValidator::Simple::Exception;
use FormValidator::Simple::Constants;

sub EXAMPLE {
  my ($self, $params, $args) = @_;
  return ($params->[0]) ? TRUE : FALSE;
}

1;

$params, $argsの中身は下で説明します。

利用の方法は、

use FormValidator::Simple qw/Example/;
FormValidator::Simple->check($q=>[
  param1=>['NOT_BLANK',['EXAMPLE','arg1','arg2','arg3']],   #Aパターン
  {param=>[param1,param2]}=>['NOT_BLANK',['EXAMPLE','arg1','arg2','arg3']]   #Bパターン
]);

このとき、FormValidator::Simple::Plugin::Example中のEXAMPLEの引数の中身は

$params=[param1];#Aパターン
$params=[param1,param2];#Bパターン
$args=[arg1,arg2,arg3];

になってます。

せっかくなんで、プラグインを書いてみた。

FormValidator::Simple::Plugin::Subroutine
validatorに無名サブルーチンを使えます。

package FormValidator::Simple::Plugin::Subroutine;

use strict;
use FormValidator::Simple::Exception;
use FormValidator::Simple::Constants;

our $VERSION = '0.01';

sub SUBROUTINE {
    my ($self, $params, $args) = @_;
    if ( scalar(@$args) == 0 || ref($args->[0]) ne "CODE") {
        FormValidator::Simple::Exception->throw(
            qq/validation "SUBROUTINE" needs a subroutine reference/
        );
    }
    my @args=@$args;
    my $data = $params->[0];
    my $subroutine = shift(@args);
    my $ret;
    eval{
        $ret = $subroutine->($data,@args);
    };
    FormValidator::Simple::Exception->throw(
        qq/Error executing  subroutine : $@/
    ) if($@);
    return ($ret) ? TRUE : FALSE;
}

1;

最後の手段的プラグイン。
使い方は、

use FormValidator::Simple qw/Subroutine/;
my $results = FormValidator::Simple->check(
  param=>[['SUBROUTINE',sub{
    my($data,@args)=@_;
    #確認コード
    return ($data) ? 1 : 0;
  },'arg1','arg2']]
);

DBに接続してIDやメールアドレスがすでに使われていないか確認したり用途はあると思われです。

2005年10月26日

フォームの入力を確認するData::FormValidatorとFormValidator::Simple

フォームから送信されたデータのチェックをするモジュールはCPANに二つあります。Data::FormValidatorと加藤さんのFormValidator::Simpleがそれ。いままでガリガリと書いていた部分をこれを用いて楽ができないかと試し中。

メールアドレスとパスワードを入れるような以下の登録画面があって、

<form>
メールアドレス:<input name="mail" type="text" id="mail" size="20" /><br /> 
パスワード:<input name="password" type="password" id="password" size="20" /><br />
もう一度パスワード:<input name="password_confirmation" type="password" id="password_confirmation" size="20" /><br />
パスワードは半角英数字5文字〜32文字
</form>

この入力をチェックする場合、

Data::FormValidatorでは、

use CGI;
use Data::FormValidator;
use Data::FormValidator::Constraints qw(:matchers);#Podでは「:all」だけどそれだと動かない
my $q=CGI->new;
my $dfv_profile={
	#mail passwordが必須
	required=>[qw/mail password/],
	#前後のスペースを除く
	filters=>['trim'],
	#パスワードの確認
	dependency_groups=>{
		password_group=>[qw/password password_confirmation/],
	},
	#入力確認
	constraint_methods=>{
		password=>qr/^[A-Za-z0-9]{5,32}$/,
		#パスワードの確認
		password_confirmation =>[{
			constraint=>sub{
				my $dfv=shift;
				return ($_[0] eq $_[1]) ? 1 : 0;
			},
			params=>[qw/password password_confirmation/]
		}],
		mail=>match_email()
	},
	#メッセージ
	msgs=>{
		prefix=>'dfv_err_',
		format=>'<span class="dfv_err">%s</span>',
		invalid=>"入力が正しくありません",
		missing=>"入力がありません"
	}
};
#チェック
my $results = Data::FormValidator->check($q,$dfv_profile);

長いけど、こうなる。

FormValidator::Simpleを使うと、もうすこし短く

use CGI;
use FormValidator::Simple;
my $fvs_profile=[
	mail=>['NOT_BLANK','EMAIL'],
	password=>['NOT_BLANK','ASCII',['LENGTH',5,32]],
	{password_confirmation=>[qw/password password_confirmation/]}=>['DUPLICATION']
];
#チェック
my $results = FormValidator::Simple->check($q,$fvs_profile);

と、書ける。上(Data::FormValidator)と比べるとコンパクトでわかりやすい。
FormValidator::SimpleはSledge::Plugin::Validatorを参考に作られている。作者が日本人という事もあって、日本語対応もプラグインでFormValidator::Simple::Plugin::Japaneseが提供されていてなかなか良さそう。プラグインの作成も簡単にできる。

ただエラーをチェックしただけでは意味がなく、それをユーザに分かりやすく伝えることが必要。この方法もData::FormValidatorとFormValidator::Simpleとで違う。

Data::FormValidatorの場合、テンプレート(Template-Toolkit)は以下のようになる。

<form>
メールアドレス:<input name="mail" type="text" id="mail" size="20" />[% results.msgs.dfv_err_mail %]<br /> 
パスワード:<input name="password" type="password" id="password" size="20" />[% results.msgs.dfv_err_password %]<br />
もう一度パスワード:<input name="password_confirmation" type="password" id="password_confirmation" size="20" />[% results.msgs.dfv_err_password_confirmation %]<br />
パスワードは半角英数字5文字〜32文字
</form>

エラーの場合、[% results.dfv_err_mail %]の所に、msgsオプションで指定した通り「<span class="dfv_err">入力がありません</span>」または「<span class="dfv_err">入力が正しくありません</span>」と入ります。メッセージの内容は、msgsオプション内で柔軟に変更可能。

FormValidator::Simpleの方はどうかと言うと、メッセージの生成機能は(まだ)なくてテンプレート中に

[% IF results.has_invalid || results.has_missing %]
<ul>
[% IF result.missing('mail') %]
<li>メールアドレスの入力がありません</li>
[% END %]
[% IF result. invalid('mail') %]
<li>メールアドレスが正しくありません</li>
[% END %]
[% IF result.missing('password') %]
<li>パスワードの入力がありません</li>
[% END %]
[% IF result. invalid('password','ASCII') %]
<li>パスワードは半角英数字のみ使用可能です</li>
[% END %]
[% IF result. invalid('password','LENGTH') %]
<li>パスワードは5文字から32文字にしてください</li>
[% END %]
...
[% END %]
<form>
...

とテンプレートに書いて行く事が必要になる。細かいメッセージ出せて良いとも思うけど、多少面倒。
「result. invalid('password')」という使い方をするあたりがTemplate-Toolkit向けです。

とりあえず一長一短。
いろいろ試してみる価値ありそうです。

参考:
Lost-Season: CatalystでSledge風Validation
hide-k.net#blog: Catalyst::Plugin::FormValidator::Simple

CatalystだAjaxだPerl6だ。Shibuya Perl Mongers テクニカルトーク #6

Shibuya Perl Mongers テクニカルトーク #6 が 11月2日に開催。

内容は

1 "Six Apart and Perl" - 宮川達彦
2 "Learning Catalyst" - 加藤@おーさか
3 "Catalyst Tips and Traps(仮)" - Lyo Kato
4 "prototype.js と Perl で Ajax" - 伊藤直也
5 "Pugs でお手軽 Perl6 入門" - 竹迫良範
6 "Perl で ICFP(Perlは「最強の言語」か?)" - 澤勇太


Catalystとか、Ajaxとか、Perl6とか、脊髄反射で申込。

RSSの表示確認はどのようにしたらいいのでしょう

最近では請け負いサイト制作、システム構築などでも「RSS(各バージョン・Atom含む)」を配信とかいうのがあったりすると思います。そこで問題になってきそうなのが、「RSSの表示確認」だと思います。普通のページであれば、IE、Firefox、Opera、Safariなどの各対象ブラウザのバージョンで確認するのが通常ですよね。

RSSも配信する以上、なんらかの確認は必要だと思いますが、RSSリーダーのソフトの種類も非常に多くデファクトスタンダードもありません。validateはしてもそれぞれのソフトの癖もあるでしょうから、HTMLと同じく配信する側で対策を考えておかなければならないところでしょう。

ぱどタウンでRSS配信を行う前に確認したのは、

  • FEED Validatorでvalidationを確認
  • Safari RSS
  • Bloglines
  • Firefox
  • Opera
  • goo RSS リーダー

これくらい。

MyRSS.jpが調べているRSSリーダーランキング(2005/09)では、

1. Bloglines
2. RssBar
3. YahooFeedSeeker
4. Hatena RSS
5. Mozilla/5.0 Firefox
6. Headline-Reader
7. gooRSSreader2
8. cococ
9. FEEDBRINGER
10. Mozilla/5.0 Sage

こういう感じ。ここまでで75%
僕の確認はすこし甘いみたいだ。

どちらにしろ、HTMLと同じく(あるいはそれ以上)マイナーなところまでいくと限りないので最低限のリストがあればいいですね。

キーボードで悪口を打たない

読んでないけど、

アルファブロガー 11人の人気ブロガーが語る成功するウェブログの秘訣とインターネットのこれから
FPN(フューチャー プランニング ネットワーク) 徳力 基彦 渡辺 聡 佐藤 匡彦 上原 仁
翔泳社 (2005/10/21)


Passion For The Futureの橋本大也さんのところの見出しは、橋本さんの紹介によると、

「キーボードで悪口を打たない」のは鉄則です


肝に銘じておこう。

FinderPop 2.0!

MacOS9時代に非常にお世話になった、FinderPopがMac OS X対応で、2.0になって復活。2.0でっせ、2.0。

ただし現在はベータバージョンで、1.99。設定画面には相変わらずビールのドネーションも書いてあるしなつかしなつかし。ittecのTiger対応バージョンがでないからどうしようかと思っていたけどこれで問題解決。

右クリック拡張ソフトで欲しい機能は2つ。

  • ランチャーとして使えるショートカットがおけること。できればメニューバー右クリックでも動くこと
  • フォルダを選択時にフォルダの中身を辿れる事。深いディレクトリも1クリックでたどり着きたい

FinderPopもIttecこの2点も満たしていて、問題も足りない事もないので会社のPantherではIttecを使い続けるかな。

Studio 8も届いた。白い箱ではなかったけどディスク2枚が入っているだけで5万円の重みがない。文鎮でもいいから入れておいてほしいなぁ。

2005年10月25日

エラーはエラーと返すべき

まとめると、実運用ではCGI::CarpのfatalsToBrowserを使わないという話。社内向け

フツーのCGIで、

#!/usr/bin/perl
use strict;
use CGI::Carp qw(fatalsToBrowser);#fatalsToBrowserを利用
use CGI;
my $q = CGI->new;

die "Bad error here";

print $q->header;
print "test";

というものを書いて、実行するともちろんdieをする。そして画面には以下のHTMLが出力される。

<h1>Software error:</h1>
<pre>Bad error here at /var/www/html/test/carp_fatal.cgi line 9.</pre>
<p>
For help, please send mail to the webmaster (<a href="mailto:root@localhost">root@localhost</a>), giving this error message 
and the time and date of the error.

</p>

ここまでは問題ない。
問題はリターンコード。

HTTP/1.0 200 OK
Connection: close
Date: Tue, 25 Oct 2005 07:37:29 GMT
Server: Apache
Content-Type: text/html      

と「200」、「OK」、「問題なし」と返ってくる。問題あるのに。。

エラー時のレスポンスのカスタマイズが普通のCGIではできないので、こういう形になっているのだろうけど、これでは困る事がある。
例えば、

my $ua = LWP::UserAgent->new();
my $req = HTTP::Request->new('GET',"http://nomadscafe.jp/test/carp_fatal.cgi"); 
my $res = $ua->request($req);
if($res->is_success){
    #ほげほげ
}

と言ったときに、目で見ればエラーなんだけどリターンコードが200だから、is_succes内の処理になってしまう。このまま行くのなら、

<h1>Software error:</h1>

これを正規表現で見つけるとかそういう面倒なことをしないとならない。

「use CGI::Carp qw(fatalsToBrowser);」がなければ普通にInternal Server Errorになります。セキュリティの問題もあるし、実運用に入ったらfatalsToBrowserは消しましょう。エラーはエラーログをみましょう。ユーザにエラー画面を見せたくないのなら、ApacheのErrorDocument設定を利用しましょう。

ErrorDocument  500  /error/500.html


PHPはエラー時も「200 OK」が返っているように見えるのですが、エラーはエラーとする方法はないのでしょうか、PHPに詳しい方教えてください。

2005年10月20日

Studio 8 注文

Studio 8注文しました。
やはりDreamweaverとFireworksは使い慣れているし必要。ここに載せる写真の縮小とかも大抵Fireworksだし、サイト作成時のCSS編集にはDreamweaverを用いています。

久しぶりのバージョンアップな様な気がすると思っていたら、バージョンアップ元のソフトがFlash 3.0だった。50,400円飛んでいった。

ぱどタウンでブログパーツはじめました。FlashのRSSリーダにもなってます

ぱどタウンでブログパーツはじめました。
↓こんなのです。ぱどタウンの部屋の一部を切り取って表示して、伝言板の内容も引っ張ってきてます。


張りつけはscriptタグでdocument.writeという典型的な方法です。
伝言板の内容の取得が実はRSSなので、ティッカーとRSSとどっちが鶏で卵なのか思い出せませんが伝言板のRSSも配信と言ってみることになりました。言う事が大事。

ActionScriptでのRSSの処理は、xfactorstudioこちらのXPathクラスを使っています。RSSのパースはここを参考にしたと思う(5ヶ月ほど前)

import com.xfactorstudio.xml.xpath.*;
var rss_titles:Array;
var rss_links:Array;
var rss_descriptions:Array;
var rss = new XML();
rss.ignoreWhite = true;
rss.onLoad = function(success) {
	if (success && rss.status == 0){
		rss_titles = XPath.selectNodes(rss,"//item/title");
		rss_links = XPath.selectNodes(rss,"//item/link");
		rss_descriptions = XPath.selectNodes(rss,"//item/description");
	}
}
rssxml.load(URL);

な感じ。RSSリーダーとしての汎用性はないけど簡単です。XPath様様。

2005年10月19日

Catalystでのセッション管理はどれがいいのだろう

Catalystをちょっとやり始めている。

シスコのHUBの方はデータセンター内で他者さんのラックで使われているのをよく見ます。
うちは使ってないけど。管理機能は別としてすぐに代替が可能なお手頃なHUBの方がうちの運用にあっているんじゃないかと思うのだがごもごもごも。。

んで、勉強にCatalystでアプリケーションを作るのにあたって、セッション管理をどうしたらいいのか調べ中。
プラグインが4つほどあるんですよね。

Catalyst::Plugin::Session::FastMmap
Catalystの制作者のプラグイン。データの保存には、Cache::FastMmapを使う。セッションキーのリダイレクト時の書き換え機能やHTML上のリンクへの埋め込み機能もある。FastMmapはけっこう速いらしい。たぶん一番ベーシック

Catalyst::Plugin::Session::CGISession
CGI::Sessionを利用する。CGI::Sessionで使えるdriverが使えるので、mysql、postgresql、sqlite、file、db_fileとよりどり。インターフェイスとリンク埋め込みなどはFastMmapに準じた設計

MyApp->config->{session}->{cgis_dsn} = 'driver:mysql;';
MyApp->config->{session}->{cgis_options} ={
        Handle=>MyApp::M::CDBI->db_Main
};

とかで行けると書く事が少なくていい。ただPodやソースにいろいろ書いてあるのがなんかアレゲ。

Catalyst::Plugin::Session::Flex
Apache::Session::Flexを用いた実装。FastMmapに準じた設計。今CGI::SessionとApache::Sessionを選択する場面だったらCGI::Sessionを選ぶかなぁ。

Catalyst::Plugin::Session::Manager
Lost-SeasonのKatoさんのモジュール。関連エントリー。Sledgeライクなセッション管理で、CookieとURL埋め込み、StickyQueryが選択可能。StorageもFile、FastMmap、Class::DBIが使える。他との連携を考えるとClass::DBIは楽ができそうだと思ったり。

MyApp->config->{session} = {
        storage => 'CDBI',
        session_class => 'MyApp::M::CDBI::Session',
        expires       => 3600,
}


結局どれにするか決まらない。FastMmapで行っておくかな。

Sessionと認証を組み合わせる必要がある場合はqootas.orgのせきむらさんのエントリー「Catalystで Authentication」が参考になる。

URI::FetchのNoNetworkオプション

URI::FetchのNoNetworkオプションを使うと、Last-Modifiedに関わらず、設定した時間キャッシュを利用するかどうかを調整できる。
CGIなどの動的ページのResponseをキャッシュしてもらいたいときに使える、というか使った。

1時間は必ずキャッシュを「あれば」用いるとき

use URI::Fetch;
use Cache::FileCache;
my $cache = Cache::FileCache->new({
	'namespace'=>'test',
	'default_expires_in'=>3600
});
my $response = URI::Fetch->fetch(
	"http://example.com/example",
	Cache=>$cache,
	NoNetwork=>3600
) or die URI::Fetch->errstr;


NoNetworkオプションは、
0/false の時、デフォルト動作。必ずサーバにリクエストを投げて新しいページを確認する。
1の時、サーバにリクエストを一切行わない。キャッシュにない場合はundefを返す。
1より大きな数字の時、キャッシュがあれば設定した秒数の間はキャッシュを返す。キャッシュにない場合はサーバに取りに行く。

「NoNetwork=>1」とするとオフラインモードが実現できるわけですな。

Trackbackのテスト

Trackbackのテスト。
一つ前のエントリー

2005年10月18日

Xeonを積んだマシンが次々死亡

Xeonを積んだマシンが次々死亡してます。
2003年5月ぐらいに購入・セットアップのIntelのXeonを積んだマシンが3台ほど続けて落ちました。
ハードウェアに原因があると思うのですが、原因分からず。

症状としては、
・1時間〜2時間ぐらいで落ちてしまう。
・キーボード、モニターの反応はまったくなし
・CD-ROMドライブのアクセスランプがつきっぱなし。
・電源スイッチでおちない
再起動すると落ちるまでは何事もなく動く。

この症状はいまのところXeonを積んだマシンにのみ出現している。CeleronやPentium4のマシンには問題がでていない。サーバを別にセットアップして移し替える方法しか現在のところありません。

Pentium M搭載サーバへ思わぬ形で切り替えが進んでいます。
サーバが残り少ないので注文しないといけないな。

2005年10月17日

QYPTHONEの新アルバム

QYPTHONEの新アルバムがamazonから届いた。

SN310079.JPG

一瞬間違えてスター○ォーズのCDでも買ったかと思ってしまった(w

キップソーン エピソード1~キップソーン アーリーコンプリート~
QYPTHONE
ハピネスレコード (2005/10/12)
売り上げランキング: 28,284

ロッテが優勝するなんて。

ファンでもないけど、なんか感動的だ。
こんな日がくるなんて。

Safari + XSLTで文字化け + IEのDOCTYPEスイッチ

何かと問題の多いSafari。XSLTで整形済みのOutputが文字化けする問題にぶちあたる。

IEのDOCTYPEスイッチをCSSをstrictモードにするためには、loose.dtdの場合、

<xsl:output method="html" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"
 doctype-system="http://www.w3.org/TR/html4/loose.dtd" />

と書いておけばいい事は分かった。

しかしこの状態ではSafari(1.3.1)で完全に文字化け。

DOCTYPEスイッチをあきらめて、

method="html" 

を取り除くと文字化けは、XSLファイルに書いた日本語文字に限られる
なのでXSLファイルの日本語文字をUTF-8のEntitiesに下のようにencodeしたら

日本語 → &#x65E5;&#x672C;&#x8A9E; 

文字化けはしない。ただメンテナンス性が最悪だよなぁ。
Safariをもうちょっとなんとかしてくれ。Apple。



ちなみに、はてなのダイアリーのRSSも文字化け。
http://d.hatena.ne.jp/kazeburo/rss

おまけ。Entitiesのencodeのワンライナー。

perl -e 'use encoding "utf8";use HTML::Entities;print encode_entities($ARGV[0].""),"\n";' 日本語

Catalystのはてな技術勉強会

はてな技術発表会日記に10月15日の技術勉強会、テーマは「Catalyst」が公開されてます。
デモとして非常に参考になります。Railsもそうだけどこの手のヘルパースクリプトは、動画にするとインパクトあるなぁ。

2005年10月16日

CGIからのmod_gzipのコントロール

CGIのヘッダー出力で、mod_gzipによる圧縮をするかどうかのコントロールをするTips
1つ前のエントリーの中のmod_gzipの設定で

mod_gzip_item_exclude rspheader "xnozip: yes"

という行があるけど、(xnozipではなくx-nozipにしておくべきだろうなぁ)
これは、レスポンスのヘッダーに「xnozip:yes」という行があった場合圧縮しないという設定。

CGIの方で、header出力時に

print $q->header(-xnozip=>'yes');

としておくと圧縮がされません。


と書いたけど遥か以前のエントリーに既に書いてあったことだった。

mod_gzipのエラー

mod_gzipを導入しているApacheが次のエラーをはくことがある。

[error] mod_gzip: EMPTY FILE [/tmp/_10949_189_90.wrk] in sendfile2
[error] mod_gzip: Make sure all named directories exist and have the correct permissions.      

これが出現したときにApacheが固まったり速度の低下を起こすことがある。

mod_perlの設定はこんな感じ

mod_gzip_on Yes
mod_gzip_keep_workfiles No
mod_gzip_minimum_file_size  256
mod_gzip_maximum_file_size  0
mod_gzip_maximum_inmem_size 60000
mod_gzip_dechunk Yes
mod_gzip_temp_dir "/tmp"
mod_gzip_item_include handler ^perl-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include file \.cgi$
mod_gzip_item_exclude rspheader "xnozip: yes"
mod_gzip_item_exclude file \.css$
mod_gzip_item_exclude file \.js$
mod_gzip_min_http 1000   


Apacheは1.3.27、mod_perlは1.26、mod_gzipは1.3.26.1aな環境です。
調べてはいるんだけど、これだと言う解決方法がみつからない。

2005年10月14日

CGI.pmとUTF8 flag

CGI.pmとuse utf8またはuse encodingを一緒に使うと、

#!/usr/bin/perl
use strict;
use warnings;
use utf8;
binmode STDOUT, ":utf8";
my $q = CGI->new;
print $q->header(-charset=>'utf-8');
print "「" , $q->param('test'),"」が入力\n";

これはうまく動かない。testの部分が文字化けする。use utf8、binmodeの代わりに

use encoding "utf8";

としても同じ。

なぜかと言えば、$q->param('test')の戻り値が、utf8 flagがoff、あるいは変な状態でonになっているのが原因だと思われ。use utf8ではflagはたたないし、use encodingではonだけどおかしい。これは、CGI.pmの内部コードで

  $todecode =~ s/%(?:([0-9a-fA-F]{2})|u([0-9a-fA-F]{4}))/
	defined($1)? chr hex($1) : utf8_chr(hex($2))/ge;

chr(hex())を使っている部分

%E6%97%A5%E6%9C%AC%E8%AA%9E(日本語)

を処理するのに、

chr(hex(E6)) . chr(hex(97)) . chr(hex(A5)) ...

となっているために、「use encoding」宣言されていれば、1つ1つはflagがついていきながら処理される(?)のだけど、本当のところは、

chr(hex(E697A5)) ...

こうなっていないと正しいUTF-8な文字にならないだよな。
これは仕方ない。なかなか修正しようがなさそう。

なのでいい方法としては、

use utf8;
binmode STDOUT, ":utf8";
use Encode;
for my $p ($q->param) {
	my @v = map {Encode::decode('utf8',$_)} $q->param($p);
	$q->param($p,@v);
}

として変換する。(これは「use encoding "utf8"」としていても正しく動きました)
あるいは、1つ1つ対応していくのがいいんでしょうか。

今月「日曜日」が何回あるのか教えてください

antipopさんがCalendar::Simple使っているいますが、horiuchiさんのDate::Simple::Monthでも。

perl -MDate::Simple::Month -e 'print scalar grep {$_->day_of_week==0} 
	Date::Simple::Month->new->dates'

冗長だけど何やっているのか明らかに分かっていいかも。

元々のはてなの質問の回答にあるShell Script の回答がすげぇ。

# cal | cut -b 1-2 | grep -c [0-9]


Hackだなぁ。

2005年10月13日

ウクライナ代表に敗れたサッカー日本代表。その敗因は?

Yahoo!で投票やってる。「ウクライナ代表に敗れたサッカー日本代表。その敗因は?」の途中経過は

21% 決定力不足
2% 大雨で濡れたピッチ
10% 中田浩の退場
7% 宮本らオールスター組の欠場
5% 小野の離脱
8% ジーコ監督のさい配
36% 主審の判定
15% ウクライナが強かった

となっていて、「主審の判定」が多いのだが、とりえあずお前ら落ち着けと。
ちょっと考えれば、「ジーコ監督のさい配」以外にありえないだろ。試合展開を思い出してみてくれ。
相手チームからは明らかな穴なアレックスのサイドバックを続けるのが信じられません。

ActionScriptの暗黙的getter setter

Flash MX 2004のActionScript 2.0の暗黙的なgetter、setterがわかりやすい。
Perlであれば

package Sample;

sub new{
	my $class = shift;
	return bless {},$class;
}

sub value{
	my $self=shift;
	if(@_){
		$self->{p_value}=shift;
		$self->someother();
	}
	return $self->{p_value};
}

こう書くところだとおもうのですが、ActionScript 2.0だと

class Sample{
	var private p_value:String;
	function Sample(){}

	function get value():String{
		return this.p_value;
	}
	
	function set value(newval:String){
		this.p_value=newval;
		this.someother();
	}
}

こんな感じ「function get/set 名前()」で書ける。分かりやすくて良い。JavaScript 2.0でもこういう書き方がサポートされる予定らしい

どなたかActionScriptのヘルプより良い本を教えてください。

2005年10月11日

[経験則]季節の変わり目はサーバトラブル多発

幾度となく書いているが、季節の変わり目はサーバトラブルが多い。。気がする。

本日は、午前3時にサーバトラブル。しかもNFSでプログラム配る中心的なサーバ。
サーバセンター行って、再起動かけたらきちんと起動してきた。なので家に帰って再度寝ようとするとまた同じサーバが落ちた。泣きながらサーバセンター再び行って再起動。1時間30分ぐらいすると落ちるのを3回繰り返した。
CD-ROMドライブのアクセスランプがつきっぱなしになっているなどソフトウェア的に落ちているのではなくてハードウェア的に落ちているような感じ。なのでハードの入れ替えに方針きりかえ。
OS入れて設定して設置して関連するサーバを再起動して全面復旧は13時前。

眠いです。

2005年10月09日

Webalizerがindex.rdfを正しくカウントしない件

アクセスログの解析をするwebalizerには「/example/index.*」を「/example/」としてカウントする仕様(機能)があります。普通のサイトなら便利かもしれないのですが、MovableTypeの生成するのRSSのデフォルトファイル名の「index.rdf」「index.xml」などは正しくカウントしてくれないのです。「index.html」と同じURLとして扱われてしまいます。
この機能は設定ファイルでOn/Offできません。index.*以外を追加する設定は、

IndexAlias home

と書くことでできますが、設定でindexを除くことはできません。
取り除くにはwebalizerソースをいじる必要があります。こちらの記事そのままなのですが、webalizer-2.01-10-src.tar.bz2をダウンロード、展開後、webalizer.cの264行目

   add_nlist("index.",&index_alias);

これをコメントアウト

   /* add_nlist("index.",&index_alias); */

します。あとは普通にmakeです。
インストール後に「IndexAlias」を適当に追加します。

IndexAlias index.html
IndexAlias index.cgi

などです。

このpatchと検索語の日本語対応のpatchをあわせたrpmのsrc packageを置いておきます。

webalizer-2.01_10jacustom-25.src.rpm

サーバの移動しました(2005/10/09)

このブログのサーバの移動をしました。
会社に置いていたのを自宅サーバにしました。

サーバの移動後の環境でもサンプルで作成したCGIなどはなるべく動くようにしましたが、全文検索のRastやEstraierのサンプルはうごきません。
ChaSenやMeCabをUTF8にしているのでソフトのmakeや確認作業とかにもう少し時間がかかりそうです。
動作できましたら、またTIPSとあわせて紹介していきたいと思います。

2005年10月07日

エンターキーの押下でフォームの送信を行わないようにする

エンターキーの押下でフォームの送信を行わないようにするJavaScript。
オライリーの「JavaScript & DHTML クックブック」に載ってたりするわけだが、

function BlockEnter(evt){
	evt = (evt) ? evt : event; 
	var charCode=(evt.charCode) ? evt.charCode : 
		((evt.which) ? evt.which : evt.keyCode);
	if ( Number(charCode) == 13 || Number(charCode) == 3) {
		return false;
	} else {
		return true;
	}
}

こういうコードを書いて、

<input type="text" name="search" onkeydown="return BlockEnter(event);">

というように紹介されている訳だが、いまいちSafariでうまく動かない。エンターキーを押した時点で送信されてしまう。
キーボードに関するイベントには、keyup、keydown、keypressの3つがあるので1つ1つ試したところ、onkeypressでIE、Firefox、Safari全てうまく動いた。

onkeydownをすべてのinput要素に追加して行くのは面倒なので以下のスクリプトを書き、

function attachBlockEnter(formid) {
	var elements = document.forms[formid].elements;
	for (var j=0; j < elements.length; j++) {
		var e = elements[j];	
		if (e.type == "text"){
			e.onkeypress=BlockEnter;
		}
	}
}

formを指定するだけで内部のテキスト入力要素全てに適用されるようにした。
使用例:

<form name="form1">
〜〜〜
<input type="text" name="search">
〜〜
</form>
<script>
attachBlockEnter('form1');
</script>


単純な検索フォームでは適用できないが、アンケート等多くの項目がある場合はいれておくとUIの改善になるのではないでしょうか。

ちなみに、これをprototype.jsで書き直すと、

var attachBlockEnter = Class.create();
attachBlockEnter.prototype = {
	initialize: function(fid) {
		this.fele=$(fid);
		Event.observe(this.fele,'keypress',this.BlockEnter.bindAsEventListener(this), false);
	},

	BlockEnter: function(event) {
    	if(event.keyCode == Event.KEY_RETURN){
    		Event.stop(event);
    		return;
    	}
	}
}

になると思うんだけど、Safariの場合、prototype.jsのEvent.observe functionでkeypressがkeydownに書き換えられてしまいうまく動かない。SafariのJavaScript実装のだめだめさを理解してきた今日この頃。Mac IEは言わずものがな。

2005年10月06日

UTF8対応のMeCabインストール

ChaSenに続いて、UTF8対応のMeCabインストール。
そんなに悩むことはなかったりする。
ついでにSennaに対応させるのpatchもあてた。

ダウンロード

# wget http://chasen.org/~taku/software/mecab/src/mecab-0.81.tar.gz  
# wget http://dev.razil.jp/archive/mecab-0.81.mte.patch      
# wget http://chasen.naist.jp/stable/ipadic/ipadic-2.5.1.tar.gz    

展開して、ipadic-2.5.1.tar.gzをmecabのディレクトリにコピー

# tar zxf mecab-0.81.tar.gz            
# cp ipadic-2.5.1.tar.gz mecab-0.81/dic/             
# cd mecab-0.81/dic/          
# tar zxf ipadic-2.5.1.tar.gz 

patchをあてて、make

# cd ..       
# patch -p1 < ../mecab-0.81.mte.patch     
# ./configure --enable-mutex --prefix=/usr --with-charset=utf8     
# make
# make install


Perl bindingのインストール。

# wget http://www.chasen.org/~taku/software/mecab/bindings/mecab-perl-0.81.tar.gz 
# cd mecab-perl-0.81
# perl Makefile.PL 
# make
# make test
# make install


おそらくmake testの結果は文字化けしてる。けどOK

UTF8対応のChaSenインストール

UTF-8対応のChaSenのインストールメモ
環境はCentOS 4.1です。
PcWebのYet Another 仕事のツールの「 第45回 日本語形態素解析ツール「ChaSen」」を参考にしました。

Dartsのインストール

# wget http://chasen.org/~taku/software/darts/src/darts-0.2.tar.gz   
# tar zxf darts-0.2.tar.gz 
# cd darts-0.2
# ./configure --prefix=/usr
# make
# make install


ChaSenのインストール

# wget http://chasen.aist-nara.ac.jp/stable/chasen/chasen-2.3.3.tar.gz
# tar zxf chasen-2.3.3.tar.gz 
# cd chasen-2.3.3

上のリンクのページを参考にGCC 3.4でmakeができるようにlib/dartsdic.cppを編集

# vi lib/dartsdic.cpp 
# ./configure --prefix=/usr 
# make
# make install 


ipadicのインストール。

# wget http://chasen.naist.jp/stable/ipadic/ipadic-2.7.0.tar.gz
# tar zxf ipadic-2.7.0.tar.gz 
# cd ipadic-2.7.0 
# ./configure --prefix=/usr  

ipadicソースディレクトリ以下の*.dicと*.chaファイルをすべてUTF8にコンバートします。
iconvで変換しました。以下のソースをchasenのソースディレクトリにconvert.shとでもしてコピー。

#!/bin/sh
for file in *.dic *.cha
do
if [ -f $file ]; then
	echo $file
    iconv -f euc-jp -t utf-8 $file > tmpfile
    mv tmpfile $file
fi
done
exit

実行&辞書インストール

# sh ./convert.sh
# `chasen-config --mkchadic`/makemat -i w
# `chasen-config --mkchadic`/makeda -i w chadic *.dic
# make install


以上

2005年10月02日

なんでも虹色 HTML::Rainbow

HTML::Rainbowなるモジュール発見

use HTML::Rainbow;
my $r = HTML::Rainbow->new(use_span=>1);
print $r->rainbow("somewhere over the rainbow, bluebirds fly");


とすると、想像通り

somewhere over the rainbow, bluebirds fly

を吐き出してくれます。
長い文章の方がきれい。

もうちょっとパラメーターをいじって、

my $str = "somewhere over the rainbow, bluebirds fly";
my @period=grep {$_ % int(2+($_/11)) == 0 } (length($str)/4..length($str));
my $r = HTML::Rainbow->new(
	use_span=>1,
	period_list=>?@period
);
print $r->rainbow($str);

やると、

somewhere over the rainbow, bluebirds fly


もうちょっと派手になるかな。

Podに書いてある内容がアレゲ

Win friends, and influence enemies, on your favourite HTML bulletin board.

なんかぱど厨文化。

MovableType 3.2のアーカイブファイル名

ただいま、当サーバの移設準備中。
会社の回線にぶら下がっていたのをプレスコ2.8GHzの自宅サーバで、CentOS 4.1(Perl 5.8.5が標準)で、出たばっかりのMT3.2で設定中なのですが、MovableTypeのアーカイブ・マッピングで迷ったのでメモ。

MovableType 3.2だとエントリーごとのアーカイブファイルが、

~/YYYY/MM/entry_basename.html

に保存されるのがデフォルトです。
entry_basenameは、エントリーのタイトルから自動生成される文字列で、この記事であればマルチバイトを取り除いた「movabletype32.html」になるでしょうか。これだと英語圏ではいいでしょうがファイル名が微妙になってしまうのでもとに戻す方法がないかと探したところ、SixApartの英語のページにマニュアルありました。
http://www.sixapart.com/movabletype/docs/3.2/e_archive_file_path_specifiers/
ファイル名を結構いろいろできますが、ここでは6桁の0埋めEntry IDを使う旧方式にしたいので、「%e」を使います。

MTの設定ページ -> 公開 -> アーカイブ・マッピングのエントリーの所のプルダウンを「カスタマイズする」にして、

archives/%e.html

と入力。再構築して完了。
月別アーカイブは

archives/%y_%m.html

となる。カテゴリー別は使ってないからわからない。

Lightweightな技術力

Yahoo! HACKS 会議 に行ってきた。
んで、Yahoo! HACKS 会議。驚いたのはYahoo! Japanの技術でしょうか。

「Yahoo! Japan」に技術力があると思っていましたか?

と聞かれて、正直「ない」と思ってました。前の方でKNNの神田さんが手をあげそうになっていたけど実は私も。
まぁ、しかしよくよく考えてみれば、コンテンツホルダー受け取ったデータをコンバートするだけではあの巨大サイトは動かないし、技術力は「ない」はずがない。BSDでApacheでCやC++でハンドラーというのは聞いたことがある話ですし。
それでもなぜ、Yahooに技術があるイメージがなかったのか、それは今回みたようなLightweight (language)な技術をみたことがなかったからだと思ってみた。チープ革命な「はてな」とは逆。はてなは速さが命なところがあって開発からリリースが恐ろしく速いというのはよく知られた話で過激なまでにオープン。Yahoo!でこれができるかと言えば非常に難しい。企業規模だけの問題ではなくマジに日本のインターネットに影響を与えてしまうパワーと圧倒的なアクセス数、世界に通じているブランドを守ることと非常に重い責任がかかって(こちらの記事)、サービスとしては後発(MAPもだな)でもあっというまに一流になるかたちで表にでる。でもなかなか技術は表にでない。求められているもでもない。

今回のDemoはそのアルファな開発段階がみれて非常に驚き。Lightweightな感じでAjaxとかつかっての見せ方やUIにもこだわりがあるようなところも新鮮。もっとこういう機会あるいはアルファ、ベータ段階での公開を行う場所を作っていったらYahoo!の技術力のアピールにもなるし技術の向上につながっていくのではないでしょうか。

ありがたく頂戴したお土産の紫の袋をみるとYahoo!のセンスも疑いたくなるが、アメリカのブランドとイメージを合わさないといけないのだろうからそれについては突っ込まないでおこう。

2005年10月01日

ぱどタウンでprototype.js

少し前に、ぱどタウンの部屋ページにJSONで部屋の情報をいくつか埋め込んでみたに書いた通り部屋の情報をJavaScriptで参照できるようにしたのですが、追加でタウンのリスト(33タウン)も入れた。
prototype.jsも読み込んでいるので、ぱど厨なHackもエレガントに行えます。たぶん。

ためしにprototype.jsの勉強もかねて、自分でぱどタウンの中でよく見かけるショートカット集をつくってみた。

<div id="townselecter"></div>
<script>
var TownPageArray=[
	{'page':'townoffice/','name':'住民センター'},
	{'page':'helicopter/','name':'へリポート'},
	{'page':'shop/','name':'ショップ街'},
	{'page':'hiroba/','name':'コンテンツ広場'},
	{'page':'map.cgi','name':'MAP'}
];
var TownSelecter = Class.create();
TownSelecter.prototype={
	initialize: function(divid){
		this.divele=$(divid);
		this.t_select = document.createElement('select');
		for(var i=0;i<PadoTownArray.length;i++){
			var isselected=(RoomJObj.town == PadoTownArray[i].town) ? true : false;
			this.t_select.options[this.t_select.options.length]
				= new Option(PadoTownArray[i].town_name,PadoTownArray[i].town,false,isselected);
		}
		this.divele.appendChild(this.t_select);
		this.p_select = document.createElement('select');
		for(var i=0;i<TownPageArray.length;i++){
			this.p_select.options[this.p_select.options.length]
				= new Option(TownPageArray[i].name,TownPageArray[i].page,false,false);
		}
		this.divele.appendChild(this.p_select);
		this.g_button = document.createElement('input');
		this.g_button.type="button";
		this.g_button.value="GO!";
		this.divele.appendChild(this.g_button);
		Event.observe(this.g_button,'click',this.t_onclick.bindAsEventListener(this), false);
	},
	t_onclick:function () {
		var selected_town=$F(this.t_select);
		var selected_page=$F(this.p_select);
		top.document.location.href="http://"+ selected_town+".padotown.net/"+selected_page;
	}
};
new TownSelecter('townselecter');
</script>


PadoTownArrayはタウンのリスト、RoomJObjは部屋の情報
ずいぶんわかりやすくなっていいですね、prototype.js。
いままでのJavaScriptを全部書き直してみたくなる罠にはまりそうです。