Sep 29, 2004

MTTruncateURL Plugin

Canonical URLを相対URLに変換するプラグインを公開します。

MTTruncateURL_Plugin - ogawa - Google Code

MTEntryPermalinkやMTArchiveLinkを始めとして多くのMTタグがcanonical URL(例: http://www.domain.tld/blog/index.html)を生成します。したがって、生成されるHTMLファイルのサイズは肥大化したり、内部リンクと外部リンクの区別が難しくなったりしがちで不便なこともあります。MTTruncateURL Pluginを使うと、canonical URLを相対URLに簡単に置き換えることができます。


なぜこのプラグインを作ったのかというと、ここのところMozilla FirefoxをマイマシンのDefault BrowserにしているのでCSS3の機能も利用してみたいと思ったからです。なんか話が遠いなあと思った人、大正解!!

CSS3で以下のように書くと、A要素のhref属性の先頭が「http:」にマッチする場合に、そのA要素のcontentの直前に/blog/images/web.gifの画像を追加します(何のことかさっぱり分からない人はFirefoxを使ってこのページを見た方が分かりやすいでしょう)。

a[href^="http:"]:before {
    content: url("/blog/images/web.gif");
    vertical-align: middle;
}

そうするとですよ、外部リンクにのみこれを有効にしたいと思ったら内部リンクは相対URLじゃないと区別できないですよ、外部リンクに全部class属性を振るとかはめんどくさいですよ、と思ったですよ。思うが早いかプラグイン作ったですよ。

が、作って3秒後にはたと気が付きました。Stylesheetに↓を追加すればいいだけですよと…。もうアフォですかと…。

a[href^="http://as-is.net/"]:before {
    content: '';
    vertical-align: baseline;
}

正確にはこのBlogで使っているCSSでは以下のように書いてあります。class属性がcontentである要素の子要素で、class属性を持たない要素に含まれるA要素に関して処理を行っています。訳が分からないかもしれませんが、私のところでは本文、コメントに含まれるA要素にマッチします。

.content *:not([class]) a[href^="http:"]:before {
    content: url("/blog/images/web.gif");
    vertical-align: middle;
}
.content *:not([class]) a[href^="http://as-is.net/"]:before {
    content: '';
    vertical-align: baseline;
}

Sep 28, 2004

今日のクレーム: ANA HOTEL MEMBERS

そう言えば最近クレームつけてないなー、寂しいなーと思っていたら結構香ばし目なクレームネタを見つけました。

全日空ホテルズ

ANAホテルメンバーズは、ANAマイレージクラブの会員でないと登録できない会員組織で、「全日空ホテルを使うとホテルポイントが貯まって××」というよくあるサービスです。

一ヶ月ほど前に登録したでしょうか、ちょっと覚えていないのですが、昨日ようやくANAホテルメンバーズからDMというか会員キットのようなものが普通郵便で届きました。その封筒の宛名書きには、住所氏名のほかに見慣れた10桁の数字が印刷されていました。

んー? これってANAマイレージクラブの会員番号だよねえ?

ということはこの封書を郵便受けから盗んだとすると、ANAマイレージクラブカードを手元に持っているのも同然なわけです。例えば電話でその会員が予約した航空券をキャンセルできてしまいます。予約番号を聞かれるかもしれませんが「分からない」とでも応えておけば大した本人確認もしませんよね。あるいはマイレージを勝手に特典航空券に交換できてしまうかもしれません。

やっちゃったね。少なくとも宛名書きに関する改善と謝罪、それがすぐにできないなら退会処理を要求する予定です。

Sep 25, 2004

GGF12 @ Brussels (cont.)


夜景に再チャレンジ。


いわゆる「命中率を上げるオマジナイ」。

Boarding Timeの3時間半前ほどにCheck-in & Security Checkを済ませ、レストランでビールを飲みつつマタリマタリとしながら、コスタ・リカからやってきた画家志望の兄ちゃんとの心温まる邂逅もあり、ほとんど終わりかけたように思えていた今回の出張、やはり最後の最後にイベントが用意されていました。

Boarding Time 5分前にゲートに行ってみると搭乗予定のLH4587便にCANCELの文字。えー。そんなの知らないよー。しかたがないのでLufthanzaのデスクに行ってご相談。予定としては、LH4587でFrankfurtに行き、その1時間後には成田行きLH9790に乗ることになっていました。が、この後のFrankfurt便ではLH9790に間に合うかどうかは微妙、というよりそれ以前にすでに満席。Paris経由、Stockholm経由などなどを検討してもらったものの、いずれも翌日(金曜)中の到着は不可能。

結局一番早く帰る方法はBrusselsにもう一泊して翌朝のSN3157(SN Belgium)でMilanに行き、MilanからAZ786(Alitalia)で成田に行くという...。Malpensaキター! 約4年前に12時間以上待たされた、あまりにも長い待ち時間に黒い魂のカケラ(Workpad)の紛失または盗難にあったという因縁の、あのMalpensa。面白いのでこのプランを承諾。もちろん、Airport Sheratonの宿泊代と食事代(Dinner 31€まで、Breakfast 25€まで)はLufthanzaが負担してくれるわけです。

2004.09.27追記: その後自分の意思とは関係なく、結構楽な道を選んでいたことが判明しました。システムダウンでかなりの便が遅延・キャンセルされたらしく、Frankfurt便を別便に振り替えてもらった人もFrankfurtにはたどり着けたものの、接続便がなくそこで宿泊させられたようです。翌朝Roma経由Tokyo便に乗ったそうです。

さてゲームの始まりです。SheratonのメインダイニングでDinnerを31ユーロ以内で済ますのはどう考えても至難の業(別に足が出ても構わないのですけど)ですから。綿密なプランニングの上、Lamb Fillet(26.80€)+Coffee(4€)で20¢残すという計画を立てました。Lambを注文後、飲み物を聞かれたので水を頼みました。しかしその水が出てきたのを見て失敗に気が付きました...、50clのEvianボトルでした(水道水でいいんだよぅっ)。ゲームオーバー。コーヒーなし決定。

マネージャの人が気を使ってくれてオードブルをサービスしてくれました。本当は70セント払う必要があったのですが彼が代わりに払ってくれ、私は彼に日本円の100円玉をプレゼントしました ;-)

Malpensaはものすごい勢いで禁煙化が進んでいました。もうChicago並みなんじゃないですか。飛行機も特に遅れることもなく、心配事はと言えば家人が留守電の「帰りが遅くなるよ」コールをちゃんと聞いたかどうかと、やっぱりキャンセル分のマイレージは加算されない(SNとAZはSkyTeamなのでマイレージはどうでもよい)のだろうなということくらいのものです(というこのエントリは京成電鉄の中からPostしています)。

結局キャンセルされた便の分のマイルは付き、Frankfurt-Narita分は付かなかった模様。まあリーズナブルな処理ですね。

Sep 22, 2004

GGF12 @ Brussels

出張でBrusselsに来ています、と言っても明日には帰国します。会場のVrije University Brussels (VUB)を探していたら(?)世界遺産に指定されているGrand-Placeに出くわしてしまったので、写真をペタペタ。


中世~17世紀のギルドハウスがそのまま残っているらしいです。


通称王の家。王が住んだことはない。


小便小僧のオリジナルもさりげなく置いてあります。


夜景は失敗したので今晩再チャレンジ。と思ったが降雨により無期限延期。

Sep 21, 2004

Webメールの実現方法について考えてみた。

Gmailを使っていて思うところがあったのでちょっと長めですが、書いてみます。

既存のWebメールシステムは、SMTPサーバ、POP3/IMAP4サーバにadd-onする形で実現されているために、ファイルシステムをスプールとして利用します。基本的にこれらのWebメールシステムでは、ユーザの要求に応じてメッセージファイルを読み込んでメッセージをパーズし、インデックスを生成することになるため、どうしても動作が遅くなってしまいます。中には独自にインデックス情報をキャッシングするものもありますが、キャッシングのタイミングは依然としてユーザがインデックス生成を指示した時点に制限され、操作中にメールが届いた場合にはそれなりのコストを払って再度インデックス生成する必要があります。また、メッセージボディの表示は単に<pre></pre>で囲んだだけのものになることもしばしばで、もっと凝ったマークアップをしようとすれば当然の結果として動作速度が遅くなることは避けられません。

つまり、Webメールの実装上の問題点とは、インデックス生成、あるいはビュー生成のトリガーがユーザの表示要求時まで遅らされているということに他ならりません。こうした既存のWebメールの実現方法はスプールの大容量化とともに限界を迎えており、単なるadd-onサービスではなく、真っ当なWebアプリケーションサービスの実現という意味では拙過ぎると言えるでしょう。

上ではWebメールとメールスプールの関係に限って述べてきましたが、他のアプリケーションでも同様のことが当てはまります。例えば、SpamフィルターやVirusスキャナーは本質的にメッセージヘッダのパーズ処理を必要としますが、これはWebメールでも必要です。しかし、現状のようにファイルシステムをスプールとして使うことはアプリケーションごとにパーズ結果を捨ててしまうことを意味します。また、仮にある種のWebメールの実装のようにアプリケーションが独自にインデックス情報をキャッシュしたとしても、よほど工夫しない限り他のアプリケーションからはそれを利用できません。

これは結局、スプールがファイルシステム上に実現されており、かつ一義的にはSMTPサーバやPOP3/IMAP4サーバに特化されたものになっているため、あらかじめアプリケーションに適した処理の追加・変更を施せないことに起因していると考えるべきでしょう。

こうした制約をなくすには、MySQL/PostgreSQLのようなRDBMSをスプールとして利用するメールシステムを使うことが一つの解決法になります。もちろん構造データを複数アプリケーションから安全かつ高速に扱えさえすればよいわけでRDBMSには限られませんが、MySQL/PostgreSQLが現状最も安価なソリューションとして存在するという事実は認めないわけにはいきません。

さてRDBMSをスプールとして利用したとすると例えば、メッセージをスプールに蓄積する際にDateやSubjectやFrom、Toアドレスなどでインデックス化しておけば、Webメールでのインデックス生成は、処理のほとんどをSQLサーバにオフロードしてしまうことができるため、格段に容易で柔軟であるだけなく、高速化することも可能になるはずです。また、マークアップされたメッセージボディのビューもあらかじめメール受信時に生成しておけば実行時のオーバーヘッドにはなりません。Spamフィルタの類はインデックスに対する問い合わせ条件にマッチしたらそのレコードをdropするとかupdateするような単純なSQLサーバ上の操作に置き換え可能です。Gmailのようなメッセージボディの検索サービスはあらかじめ特徴語を抽出などの処理を行っておき、あと定期的にインデックスを再生成するとかすれば、受信時の処理とインデックス生成のオーバーヘッドの均衡化が図れそうです。

また、ユーザに一度に提示してみせるべきメールボディは高々数件ですし、POP3/IMAP4サーバなどが要求するスループットはサーバ・クライアント間の転送速度に束縛されますから、RDBMSがファイルシステムに比べてスループットに関して劣るとしても問題にはなりません。

こう考えてきてみると、メールシステムとRDBMSの相性は実に良いように思えます。実際、DBMailのように実装例はあります。が、比較的monolithic作りになっていて私が意図したようなフレームワークとしては設計されていないようです。

Sep 13, 2004

MT3でなぜエントリの追加に時間がかかるようになるのか

Ogawa::Buzz: Movable Type 3.0の Individual Entry Archiveの命名方式の問題点の落ち穂拾いで、私の好きそうなネタです。

気が付いている人がいるかどうかは分かりませんが、MT3では使っているうちに、徐々にRebuildに要する時間が増大するのはもちろんですが、1エントリの追加にかかる時間も増大します。一般的に言って、増大する度合いがエントリ数に比例する程度なら問題ありませんが、エントリ数の2乗、3乗に比例するようだと速度低下が目に付くようになります。

これは以前も述べたmake_unique_basenameという手続きが原因で起きます。

MT::Util::make_unique_basenameは新しいエントリを作るたびに必ず1回呼び出され、そのエントリのbasenameを決定します。このmake_unique_basenameは以下のような手続きからなっています。

sub make_unique_basename {
    my ($entry) = @_;
    my $blog = $entry->blog;
    my $title = $entry->title;
    unless ($title) {
        if (my $text = $entry->text) {
            $title = MT::Util::first_n_words($text, 5);
        }
        $title = 'Post' unless $title;
    }
    my $base = substr(MT::Util::dirify($title), 0, 15);
    $base =~ s/^_+//;
    $base =~ s/_+$//;
    $base = 'post' if $base eq '';
    my $i = 1;
    my $base_copy = $base;
    
    while (MT::Entry->count({ blog_id => $blog->id,
                              basename => $base })) {
        $base = $base_copy . '_' . $i++;
    }
    $base;
}

この手続きをおおまかに説明すると、まずタイトルもしくは本文をdirifyした文字列の最長15文字を取り出します。次にこの文字列をbasenameに持つエントリがないかどうかを、「SELECT COUNT(*) FROM mt_entry WHERE basename=...」の戻り値が、0(=マッチするエントリなし)か、0でないか(=マッチするエントリあり)かによって判定します。

マッチするエントリがあれば、文字列に「_1」を付加して再度SELECT COUNT(*)を実行します。これを「_2」、「_3」、…と繰り返し、マッチするエントリがない状態になったらそれをベースネームとして返します。

例えば、このBlogではタイトルがすべて日本語からなるエントリが400個あります。これらのエントリのbasenameはpost, post_1, post_2, ..., post_399と付けられているわけです。次にすべて日本語からなるタイトルを持つエントリを作ったとすると、401回SELECT COUNT(*)を実行して初めてpost_400というbasenameが付けられるわけです。

少し形式的な書き方をすると、エントリ数をNとすると、一回のインデックススキャン(SELECT COUNT(*))の処理時間は最良でO(1)、平均でO(log N)、一回のmake_unique_basenameはこのシーケンススキャンをO(N)回(※)行います。したがって、エントリを1個追加するのに要する時間はO(N log N)となります。Acceptableかどうかはギリギリというところでしょう。念のためこれはMySQL, PostgreSQLの場合であり、BerkeleyDBではO(N2)になる可能性があります。

※ 私のBlogで約900個のエントリ中、約400個が日本語のみからなるエントリでした。これは平均的な日本人がBlogにおいて日本語のみのエントリを作成する度合いが平均的にNに比例することを示す十分な(十分以上の?)根拠となります。一方で英語国民に関してはこのようなことは起こらず、O(1)となることが予想されます。

どうしてもO(N)にしたければ以下のようなコード(正確な動作はオリジナルと異なります)にすればよいわけですが、普段たいていO(1)で済んでいた英語国民には不幸になります。というかこっちはこっちで重い操作をやっていて、試しに書いてみたというだけのものです。お使いの環境によっては高速化されるとは限りません。

sub make_unique_basename {
    my ($entry) = @_;
    my $blog = $entry->blog;
    my $title = $entry->title;
    unless ($title) {
        if (my $text = $entry->text) {
            if (MT::ConfigMgr->instance->DefaultLanguage eq 'ja') {
                $title = MT::I18N::first_n_text($text, 10);
            } else {
                $title = MT::Util::first_n_words($text, 5);
            }
        }
        $title = 'Post' unless $title;
    }
    my $base = substr(MT::Util::dirify($title), 0, 15);
    $base =~ s/^_+//;
    $base =~ s/_+$//;
    $base = 'post' if $base eq '';
    my @bnames = grep {/^$base(\_[0-9]+)?$/} map {$_->basename}
                        MT::Entry->load({ blog_id => $blog->id });
    return $base unless @bnames;
    my $max = -1;
    foreach my $bname (@bnames) {
        my $bidx = ($bname =~ /$base\_(.*)/) ? $1 : 0;
        $max = $bidx if $max < $bidx;
    }
    return $base . '_' . ++$max;
}

よくある質問とその答え

Q1. 「個別のアーカイブへのリンクに以前の形式(id)を利用」したり、アーカイブファイルの形式を指定すれば、この問題を回避できますか?

A1. できません。エントリを追加した際にmake_unique_basenameが必ず呼ばれてしまいます。回避するとすれば、make_unique_basenameの定義を書き換えるか、プラグインなどで上書きする必要があります。

Q2. そんなに遅くなった気がしないのですが?

A2. ここで述べている問題は「下書き」状態でエントリを保存するのにかかる時間、あるいはエクスポートデータのインポートにかかる時間、に関するものだと考えてもらった方が直感に合っています。make_unique_basenameはそのエントリが「下書き」にしろ「公開」にしろ最初に保存されたときに一回だけ呼び出されるのですから。

もっともエントリの追加と同時に公開+再構築を行う場合などは、make_unique_basenameの処理時間は一般に無視できるかもしれません。なぜなら再構築はテンプレートの解釈やファイルI/Oを伴うもっとずっと重い操作ですから。しかし、再構築の処理オーダーがO(N)であるのなら、いずれmake_unique_basenameの処理オーバーヘッドが顕在化することも予想できます。

Sep 9, 2004

Sony Music Access: Timeless Anthology

…あ、今気が付いた。ぉぃぉぃ…Sony Music Access、終わるんじゃん。

先週金曜日のTimeless Anthology Part 1って総集編・第一回、みたいなものだったのか。てことはだ、1983年末から「Sony Music TV」という200分番組で始まり、「Sony Music Disc」、「Sony Music Access」と20年以上の長きに渡ってリレーされてきた、SONYを冠に頂くミュージックビデオ番組が…終わってしまうと?

ついに?! 昭和が 80年代が 終わってしまうー!!!

駄目だ…ちょっとしばらく立ち直れそうにありません。もう少し若い世代だとこの喪失感は理解できないのでしょうね。1984年を核にした前後合わせて3~4年間くらいの期間というのはポピュラー音楽にとって豊潤の時期だったのです(ちょっと振り返ってみるためのスレ: Ogawa::Buzz: 後ろ向きの音源リスト)。そういう時期に中高生なんかやらかしていた私なんかにしてみると、この番組は趣味嗜好の形成にまで影響してしまっているのです。思えばあの頃気が付いてしまったのかもしれません、認めたくないものだな。自分自身の、若さゆえの過ちと言うものをー。事実に。「音楽にしろ絵画にしろ文学にしろ建築にしろ、『様式』とは滅多に発明されるものではない。ポップとは『様式』を模倣することであり、『ポップである』ことを演じることである。」という事実に。

ここで唐突に豆知識
Sonyの「CDウォークマン」と「ディスクマン」は別系統の製品です。わずか二代ほどでCDウォークマンの系列が途絶えた後、ディスクマンがCDウォークマンの名前を名乗っているのです。ディスクマンはD-50(1984年発売)に代表されるように基本的に現在のポータブルCDプレイヤーと同様の形状をしていました。一方、ウォークマン並みのUltra Portableを目指した「元祖」CDウォークマンは8cm Discに専用化された特殊な製品でした。正確には初代の「元祖」CDウォークマンはディスクをはみ出す形で12.5cm Discも再生できるという画期的な製品でした。WALKMAN 25th Anniversary -Sony Styleでは、なぜか元祖CDウォークマンの存在は抹消されています。むべなるかな。

それはともかくCATVでこういうチャンネルを買わないといけない時代なんですか…。

MUSIC AIR
MUSIC ON! TV

Sep 8, 2004

巨神兵がドーン!

我が家はあまりテレビを観ません。世間様がドラマや世界遺産や世界の車窓からを録画したり、NHKアーカイブスを再アーカイブしたりしてビデオを大活躍させている一方で、我が家のビデオデッキはテレビに接続されもせず、一年半放置されているくらいです。そんな我が家で今割に定期的に観る努力をしているのは、WORLD DOWN TOWNSony Music Access(テレビ神奈川)と、そしてsaku saku(テレビ神奈川)です。

そのsaku sakuに出演中の木村カエラ©(神奈川県民の心の友。ある意味、松浦あやの対極にいる人。)のBlogができていました。

Kaela★Blog

アイドルとしては押しがあまりに弱い、その割には案外口は悪く、しかし口調は丁寧という、徹底した微妙キャラですね。マイナーの星として今後も頑張ってもらいたいです。

巨神兵がドーン!」でググると結構ヒットするのでビックリ。まあ、TVK42チャンネルを観ない(観られない)人には訳分からないでしょうけど…。

Sep 7, 2004

Javascriptでの表示・非表示切り替え

Webは自分の書いたコードを広めるのにも適していますが、初期の掲示板CGIの例などを挙げるまでもなく、誰か一人が書いたに違いない必ずしも望ましくないコードが延々使い回されるという状況を助長する面もあります。

さてしばしばエントリの追記(extend)部分の表示・非表示を切り替えるのにJavascriptを使う例が散見されます。どうやらオリジナルはscriptygoddessやら「続きを読む」を隠したり出したり : cync.jp :: squint sightsあたりらしいのですが、これらのスクリプトではexpand/collapseする要素にいちいちid属性を振る必要があり、また「にもかかわらず」すべてのextend要素をまとめてexpand/collapseなどといったごく簡単な応用もできません。もちろんidを冗長に与えてロジックを単純にするという発想は正しいですが、id属性は一個の要素に一個しか与えられていない貴重な「資源」なのでwastefulな使い方は避けるべきです。Javascriptにはなぜだかこういう「仕事」が特に多い気がするのですが、解説本の影響でもあるのでしょうか。

このエントリで述べるのは、このような用途のスクリプトを私ならどのように実現するかということです。先例のスクリプトではexpand/collapseする要素にid属性を振り、getElementByIdで対象要素の操作をするわけですが、どうせDOMを使うのなら本質的にidを振る必要性はありません。

このエントリでは、類題として以下のようにexcerpt部分のBLOCKQUOTEの表示・非表示を切り替えるボタンを設ける場合を考えます。

テンプレートは以下のように記述してあるものとします。

List 1:
<MTEntries sort_order="ascend">
<$MTEntryTrackbackData$>

<MTDateHeader>
<a id="<$MTEntryDate format="d%d"$>"></a>
</MTDateHeader>

<h2 id="a<$MTEntryID pad="1"$>">
<a href="<$MTEntryPermalink valid_html="1"$>"><$MTEntryTitle$></a>
</h2>

<div class="meta">
Category:
<a href="<$MTEntryLink archive_type="Category"$>"><$MTEntryCategory$></a>
| <$MTEntryAuthor$> @ <$MTEntryDate format="%x %X"$>
<img onclick="toggleExcerpt(this)" alt="Expand excerpt"
 src="<$MTCGIPath$>images/expand.gif" />
</div>

<blockquote style="display:none"><$MTEntryExcerpt$></blockquote>
</MTEntries>

切り替えボタンを表示するIMG要素のonclickイベントとしてtoggleExcerptというユーザ定義メソッドを使います。このメソッドの引数はカレントノード(this)だけです。また、表示・非表示を切り替えるBLOCKQUOTE要素にid属性などは振りません。つまり、赤字で示したIMG要素だけを追加すれば切り替えボタン機能としてするようにします。

ではtoggleExcerptはどのように定義するかというと以下を参照してください。

List 2:
function toggleExcerpt(o) {
  var bqNode = o.parentNode.nextSibling;
  while (bqNode.nodeName.toUpperCase() != "BLOCKQUOTE") {
    bqNode = bqNode.nextSibling;
  }
  if (bqNode.style.display == "none") {
    bqNode.style.display = "block";
    o.alt = "Collapse excerpt";
    o.src = "<$MTCGIPath$>images/collapse.gif";
  } else {
    bqNode.style.display = "none";
    o.alt = "Expand excerpt"
    o.src = "<$MTCGIPath$>images/expand.gif";
  }
}

引数で渡されるカレントノード(IMG)のparentNode(DIV class="meta")の次の要素を順にスキャンして、nodeNameがBLOCKQUOTEである最初の要素を探し出し、style属性を書き換えるだけです(エラー処理は省略しています)。これを多少変更すればすべてのBLOCKQUOTE要素の属性をまとめて操作できることも分かるでしょう。

さてここで少しadvancedなことを考えてみます。<$MTEntryTrackbackData$>は以下のようにrdfに展開されるのですからExcerptを含んでいます。したがって、<blockquote style="display:none"><$MTEntryExcerpt$></blockquote>のようにしてExcerptをわざわざ二度も展開するのは無駄じゃないか、と。つまり、expandしたときにこのコメント内の文字列をBLOCKQUOTEに挿入することはできないかということです。

List 3:
<!--
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
         xmlns:dc="http://purl.org/dc/elements/1.1/">
<rdf:Description
    rdf:about="http://www.example.com/blog/archives/2004_08.html#000896"
    trackback:ping="http://www.example.com/mt/trackback.cgi/166"
    dc:title="mt-resave-entries.cgi: basenameがNULLのエントリを再保存するCGI"
    dc:identifier="http://www.example.com/blog/archives/2004_08.html#000896"
    dc:subject="Weblog"
    dc:description="Excerpt Text"
    dc:creator="ogawa"
    dc:date="2004-08-06T08:21:02+09:00" />
</rdf:RDF>
-->

これももちろんできます。List 1のBLOCKQUOTEは<blockquote style="display:none"></blockquote>のように空にしてあるとします。

toggleExcerptの定義はList 2とほぼ同じですが、expandするときにinnerHTMLが空だったらextractExcerptの返す文字列をinnerHTMLにセットする手続きを追加しておきます。

List 4:
function toggleExcerpt(o) {
  var bqNode = o.parentNode.nextSibling;
  while (bqNode.nodeName.toUpperCase() != "BLOCKQUOTE") {
    bqNode = bqNode.nextSibling;
  }
  if (bqNode.style.display == "none") {
    if (!bqNode.innerHTML) {
      bqNode.innerHTML = extractExcerpt(o);
    }
    bqNode.style.display = "block";
    o.alt = "Collapse excerpt";
    o.src = "<$MTCGIPath$>images/collapse.gif";
  } else {
    bqNode.style.display = "none";
    o.alt = "Expand excerpt"
    o.src = "<$MTCGIPath$>images/expand.gif";
  }
}

一方、extractExcerptは、引数で渡されるカレントノード(IMG)のparentNode(DIV class="meta")の前の要素を順にスキャンしてnodeNameが#commentのものを探し出し、コメント文字列から「dc:description="」と「"」で囲まれる文字列を取り出して返します。

var excerptStr = "dc:description=\"";
function extractExcerpt(o) {
  var cmntNode = o.parentNode.previousSibling;
  while (cmntNode.nodeName != "#comment") {
    cmntNode = c.previousSibling;
  }
  var s = cmntNode.nodeValue;
  var startpos = s.indexOf(excerptStr) + excerptStr.length;
  var endpos = s.indexOf("\"", startpos);
  return s.substring(startpos, endpos);
}

Sep 1, 2004

Movable Type 3.1 and a temporary Japanized patch

予定通りMovable Type 3.1Plugin packがリリースされました。ベータテストに参加していて間に合わないような感触だったので遅れるのではないかと思っていました。

Six Apart Professional Networkというのも立ち上がっており、登録するとNetwork Member License(確か5ユーザー利用可能な商用ライセンス?)が手に入ります。ちなみに手続きは無闇に面倒ですので気力があるときにトライしましょう。ついでにProfessional Networkのメーリングリストに入ると、Movable Typeでどうやって金儲けするとかいった話(そ、そっちの「プロ」のネットワークだったのか?)が聞けます。

ところで、とりあえず(概ね3.01D日本語版と同等の機能を追加する)日本語化パッチをでっち上げました。手元で「テンプレートごとに管理可能なダイナミックPHPページ生成」の動作環境が用意できていないの十分テストできていないのですが、見切り発車で公開してしまいます。駄目だったら駄目で修正すればいいやという方針です…。

MT-3.11-en_us-jpatch02.zip (36,279bytes)
jpatch01に加え、MTCommentFieldsへの比較的大きな変更を行ったパッチ

MT-3.11-en_us-jpatch01.zip (34,029bytes)
MT3.11英語版に日本語版として動作するように変更を行ったパッチ

MT-3.1-en_us-jpatch01.zip (34,049bytes)
MT3.1英語版に日本語版として動作するように変更を行ったパッチ

Release Notes
2004.09.08: MTCommentFieldsのはくコードを見ていたら腹が立ってきたので修正してパッチファイルを更新しました。
2004.09.04: 3.11がリリースされたので対応しました。3.12がすぐ出そうな悪寒。
2004.09.02: 最初のリリース。概ね3.01D日本語版と同等の機能を実現しています。つまり、MT3英語版の文字化け等の不具合の解消、ユーザインタフェースのより完全な日本語化などがパッチパッケージの主な機能です。

前準備

前準備として、英語版で不足しているいくつかのファイルを日本語版からコピーしてやる必要があります。

まず、Movable Type 3.01D日本語版と3.11英語版を用意し、それぞれアーカイブを展開します。以下ではそれぞれのディレクトリを<MT3J_DIR>、<MT31E_DIR>と呼ぶことにします。

  • <MT3J_DIR>/extlibを<MT31E_DIR>/extlibにコピーします。上書きが面倒な場合はあらかじめ<MT31E_DIR>/extlibを削除しておくとよいでしょう。
  • <MT3J_DIR>/lib/MT/I18N.pmを<MT31E_DIR>/lib/MTにコピーします。
  • <MT3J_DIR>/lib/MT/L10N/ja.pmを<MT31E_DIR>/lib/MT/L10Nにコピーします。

こうやって作った<MT31E_DIR>を用意しておいてください。また、適当な名前でアーカイブにしておくことをお勧めします。

パッチの適用

UNIX汎用パッチ
<MT31E_DIR>ディレクトリに、上記のZipファイルに含まれているMT-3.11-en_us-jpatch02.diffをコピーして、以下の要領でパッチを当ててください。
patch -p1 < MT-3.11-en_us-jpatch02.diff
Windows用パッチ
<MT31E_DIR>ディレクトリに、上記のZipファイルに含まれているMT-3.11-en_us-jpatch02.EXEをコピーし、ダブルクリックして実行してください。

あとは通常通りWebサーバーにファイルをアップロードして設定するだけです。

MTCommentFieldsに関する補足

jpatch02は、あるべきMTCommentFieldsの参照実装を与えることを意図した修正を含んでいますので、オリジナルのMTCommentFieldsとは非互換性があります。非互換性を避けたい場合にはjpatch01をご利用ください。

具体的にはオリジナルには以下のような問題があります。

  • Templateですでに含まれているため不要であるにも関わらず、getCookie用のJavascriptを生成する。
  • しかも、getCookieは生成するが、rememberMe、forgetMeは生成しない。
  • Author, E-mail, URLなどのフィールドを必ずクッキーから読み込むJavascriptが生成される。例えば、URL欄をblankにしたいと思ってもクッキーから自動的にフィルされてしまう。
  • コードが汚い。

そこでjpatch02では一部整理しました。

  • 不要なJavascriptは生成しない。生成するならdefault-templates.plの変更と併せて。
  • preview="1" (Comment Preview, Comment Error)のときは、クッキーからフィルするのはYes/Noラジオボタンのみとする。preview="0" (Comment Listing)のときは、すべてのフィールドをクッキーからフィルする。
  • コードを少々クリーンアップ。

配布の条件など

まず、このパッチパッケージはSixApart Japan社製「Movable Type 3.01D日本語版」の派生的作品です。私はこのパッチパッケージの大部分に関して知的所有権を有しませんし、それ以外の部分に関してもSixApart Japanにdonateします。したがってパッチパッケージの配布の条件もSixApart Japanの意思に従うものであり、以下に記載する配布条件は変更されるべきときには変更されます。

このパッチパッケージは自己責任で使用してください。このエントリの引用・リンクなどはもちろん自由に行っていただいて構いませんが、このエントリに含まれるパッチパッケージを再配布すること、またこのエントリに含まれることを明記せずにパッチパッケージに直接リンクすることはお控えください。その必要がある場合にはコメント欄などでご連絡ください。

リリースにあたってのコメント

このパッチを含めて従来の日本語版の日本語化方法はダメダメだなー、と私は随分以前から思っています。簡単に言うと、現状の日本語版は、PublishCharsetで設定したcharsetでユーザーインタフェースの入出力表現と内部表現が統一されてしまいます。単にPublishCharsetと言辞矛盾があるというだけではなく、考えてみれば実害もあります。実際、特定のcharsetでだけ生じる問題にad hocに対処する必要が生じましたし、冗長なコード変換操作も多いはずです。また、日本語以外のcharsetへの対応は誰か真面目に考えたでしょうか。

より良い、より広範な言語に対応できる実装とは次のようなものです。何らかの内部表現を一つ決めます。日本人にとってはEUC-JPやShift_JISでもいいですが、中文化・ハングル化を含めたI18N化を考えるなら内部表現はUTF-8にしておくのは決して悪くない選択です。ユーザインタフェースや(Rebuildを含む)ビューを生成する際には内部表現からPublishCharsetへの変換を行い、逆に入力は必ずPublishCharsetから内部表現に変換してDBに保持します。TrackbackやXMLRPCの入出力インタフェースは、特例としてPublishCharsetに従うのではなく内部表現(=UTF-8)で行えばよいわけです。

問題はUTF-8と各charset間の相互変換を実現し、かつPerl 5.004でも動作する(Pure)Perl Moduleがないということですね。Encode.pm + Perl 5.8がある今となっては誰も実装したいとは思わないでしょうし…。

Gmail and GmailFS

昨日ようやくGmailにinviteしてもらったところです。フォルダ分けのような無駄なロジックを排した結果、とても小気味良いWebメールシステムに仕上がっています。

inviteしてもらったときにGmailやLivedoorギガメーラーをストレージ代わりに使えないかという話をちょうどしていたのですが、やっぱり実装してしまう人が現れました。

GmailFS - Gmail Filesystem

GmailFS provides a mountable Linux filesystem which uses your Gmail account as its storage medium. GmailFS is a Python application and uses the FUSE userland filesystem infrastructure to help provide the filesystem, and libgmail to communicate with Gmail.

Gmailをネットワークストレージに改造するプログラムが公開される

米Googleの無料Webメールサービス「Gmail」は、1GBという大容量のメール保存スペースを提供することで大きな話題となったが、ある利用者がGmailをネットワークストレージに利用できるようにするプログラムを公開したことで話題を呼んでいる。

Windows版はまだ出ていないのでそんなにお手軽ではありませんが、とりあえず試してみるつもりです。

あーそれと私、Gmailに一日あたり6名様までinviteできるみたいです(無限にreloadされる?)。まだ持っていない方に差し上げますので、欲しい方は(なるべくならTypekeyを使って)コメント欄に心意気を示してください。また、invite基準という訳ではありませんが、一応下の注意事項もお読みください。

注意事項

私が「心意気」といっているのは、(1)欲しいという意思表示、(2)自分は誰某である、またはこういうブログやWebページをやっている、またはこういう人間であるという人格表示、この二つです。フリーメールアドレスなどからリクエストしてくださっている方が何名かいらっしゃって、その方々にInvitationを差し上げるのも吝かではないのですが、この点を踏まえて差し支えなければ再度リクエストしていただきたいと思っております。

About Me

My Photo

つくばで働く研究者

Total Pageviews

Amazon

Copyright 2012 Ogawa::Buzz | Powered by Blogger
Design by Web2feel | Blogger Template by NewBloggerThemes.com