重複するタグ表示API (20:02)
「タグの日本語揺らぎをロジックで吸収する」にtrackbackをもらったんだけど、いまいちそっち方面に脳みそが働かないんで、具体的な行動に出られないでいた。
で、いつまで経っても脳みそが働かないっぽいんで、ひとまず誰か代わりに脳みそを働かせてくれる人がいないかなー、ということで、単純なAPIを用意してみた。
http://1470.net/api/similar_genres.php?genre=[MMのジャンル]
を叩くと、EUC-JPでそのタグと重複して使われているジャンルを、
[ジャンル文字列]\t[重複数]
というTSV形式(EUC-JP)で出力する。たとえば「Ajaxと重複するタグ」とか。思いつく応用例としては、「Ajaxで上記を読み込んで、類似タグへのナビゲートを行ったり」とかかなー。何かいい使い道が思いつく人、使ってみてください。
dotprojectを試してみようと思ったけど (15:09)
最近あちらこちらで名前を見かけたdotprojectを試しに入れてみたんだけど、認証周りがおかしいなー。アカウント情報をいじると、ログインできなくなる。追いかけていったところ、GACLとかいう認証ライブラリ(?)が権限チェックではねているらしいけど、外部ライブラリとの連携まで追いかける気になれないしなー。というか、GACLって初めて知ったな。これって使えるのかな?
というかそもそも、インストールの段階からいろいろうまく行かないところが多かった。DB初期化でインストーラはsuccessといってるのに、いくつかのテーブル(projectsとか)がうまく作られなくって、何回か全削除からやり直したりとか。まだ安定してないのかなー。これちゃんと使っている人いるの?
tDiaryのアンカーリンク時にドキュメントタイトルを表示する その5 (04:14)
決定版とか言っておきながら、まだいじっている。セクションアンカーの文字列長を、ちゃんと@confから持ってくるようにした。これで曖昧な部分がなくなったんじゃないでしょうか。
23,29d22
<
< if (highlightElem.tagName == 'H3') {
< var diary_title = "#{@conf.html_title.gsub(/"/, '\\"')} (#{@date.strftime('%Y-%m-%d')})";
< var sanchor_length = #{@conf.section_anchor.gsub(/<[^>]+?>/, '').length};
< var section_title = highlightElem.innerHTML.replace(/<[^>]+?>/g, '').substr(sanchor_length);
< document.title = section_title + ' - ' + diary_title;
< }
Re:「Alpha Geek Tracker」 (12:34)
他サービスのブックマークも取り込めるようにしてくれたらいいのになー
その手の機能はMMのお気に入りリストを作るときに考えたし、作るのも簡単だと思うんだけど、権利関連の問題がでてきそうなんで、サービスとして公開するのはちょっと難しいだろうなーと思ってやめた。
新着とか人気URLとかあたりから持ってくるならば、それほど問題にならないと思うけど、ある特定の人のデータを勝手に他のサービスで取り込んで、加工して(他の人のデータと混ぜて)使うのは、一般的な公開サービスの形式でやるのはちょっとリスキー。
URLのみを流用するならまだ大丈夫かもしれないけど、流用データにコメント部分が含まれると、データの著作物性が高まって、流用することにクレームをつける人が出てくると思う。Planet〜系とかでクレームが付いたりしている事例を見ると、その手の機能を誰でも登録できる一般サービス化したら運営管理が面倒くさそう。
というわけで、MMでその手の機能を載せないのは、機能的には便利そうだけど運営管理コストがかかりそうなんでそのリスクを取れないから、だったりする。Alpha Geek Trackerみたいに個人的に(誰も登録できるわけではない形で)やる分には大丈夫だと思うけど。
HTML_QuickFormを使う場合 (22:12)
<label><input type="checkbox" name="foo[]" value="abc">abc</label> <label><input type="checkbox" name="foo[]" value="def">def</label> <label><input type="checkbox" name="foo[]" value="ghi">ghi</label>
みたいな使い方は想定されていないのかな?
tDiaryのアンカーリンク時にドキュメントタイトルを表示する その4 (18:23)
決定版は、こんな感じかなー。
23,28d22
<
< if (highlightElem.tagName == 'H3') {
< var diary_title = "#{@conf.html_title.gsub(/"/, '\\"')} (#{@date.strftime('%Y-%m-%d')})";
< var section_title = highlightElem.innerHTML.replace(/<[^>]+?>/g, '').replace(/^[^ ]* /, '');
< document.title = section_title + ' - ' + diary_title;
< }
結局「"」のエスケープについては、
print '"'.gsub(/"/, '\\"')
で「\"」にエスケープできたけど、なんで
print '"'.tr('"', '\\"')
の結果が「"」になって、
print '"'.tr('"', '\\\\"')
の結果が「\」になるのかよくわからんかった。trとgsubでは何が違うんだ?
ああ!
trは文字列置換じゃなくて、文字単位での置換なのか!
MM/Memoでも一応対応コードをつけてみた (15:10)
MM/Memoの場合はTypeKey認証を使っている関係上、直接アプリケーションからPOSTするようなツールは使えない(ものすごく頑張れば使えなくもないけど)から、こういうチェックを導入したせいで、その手のツールが使えなくなるという人はいないよね? ブラウザ上でbookmarkletとか使ってGETでパラメータ渡ししている場合は問題ないし。
ちなみにチェック処理の内容は、フォームを生成するときに、
$check_key = md5([ユーザーID] , [秘密の文字列]);
で生成したチェックコードを、
<input type="hidden" name="check_key" value="<?=h($check_key) ?>">
なんて感じで埋め込んでおき、POSTされたときに、
if ($_POST['check_key'] != md5([ユーザーID] , [秘密の文字列])) {
die('認証失敗');
}
しているだけね。認証キーはユーザーIDから生成しなくても、まったくランダムに作ってセッションに保存しておいてもいい。
それにしても、これからの公開Webアプリはみんなこういう対策を標準で取らないとだめなんだろうなー。これから作るフォーム周りのライブラリには、こういう機能を標準で持たせておくべきか。
あ、できればリファラーチェックで弾くような実装はやめてね。俺は基本的にリファラーオフが標準だし。
tDiaryのアンカーリンク時にドキュメントタイトルを表示する その3 (14:43)
段落アンカーの頭は必ずしも1文字ではないようなんで、最初の半角スペースまでを除去するように変更。あと、日記タイトルはh1から取得するのはやめて、tDiary側でJavaScriptに埋め込むようにしてみた。
23,27d22
<
< if (highlightElem.tagName == 'H3') {
< var title = "#{@conf.html_title}(#{@date.strftime('%Y-%m-%d')})";
< document.title = (highlightElem.innerHTML + ' - ' + title).replace(/<[^>]+?>/g, '').replace(/^[^ ]+ (.*)/, '$1');
< }
これだともしも@conf.html_titleに「"」が含まれているような場合にまずかったりするんだけど、なんか上記の文脈で「"」を適切にエスケープする方法が良くわからなかったんでパス。@conf.html_title.tr('"', '\\"')だとダメなんだよなー。
Rubyのヒアドキュメントと式展開が入れ子になっていて、さらにその結果をJavaScriptの文字列に埋め込む場合、適切なエスケープはどう書くのが正解でしょう?
ネタがかぶっていた
「最初の半角スペースまでを除去する」というネタがかぶっていた。
replace(/[^ ]* /, '')
の方がスマートな表現か。
ちなみにdocument.titletitleタグは、title_tag.rbを使っている場合は、その日のすべての段落タイトルが含まれてしまうんで、日記タイトル取得用には使えないんですよねー。
テーマを追加 (19:31)
MM/Memoの個人ページで使えるテーマとして、Treetop、マーガレット、Digital Gadgets、カーテン、Orange Flower、お花見、Sleepy Kitten、ナトリウムランプを追加。っつーか、Sleepy Kittenを使ってみたかっただけ。
BMediaNode: PHPプログラマの力量を計る その2 (18:49)
OOPってのは、要はMVCのことなのか。ってことで、MVCで書いてみた。modelをNULLで返すパターンはあんまり美しくないなー。空のmodelでも返した方がきれいか。でもまああまり深く考えることはやめておこう。
index3.php
<?php
main();
function main()
{
header('content-type: text/html; charset=UTF-8');
$ctrl =& new MyController();
$ctrl->run();
$model =& $ctrl->getModel();
$view =& $ctrl->getView();
$view->display($model);
}
class MyController
{
var $_model;
var $_view;
function run()
{
switch ($_SERVER['REQUEST_METHOD']) {
case 'POST':
$this->_model =& new MyModel();
switch ($this->_getMode()) {
case 'input':
$this->_view =& new MyViewInput();
break;
case 'check':
$this->_view =& new MyViewCheck();
break;
case 'result':
$this->_view =& new MyViewResult();
break;
}
break;
default:
$this->_view =& new MyViewInput();
break;
}
}
function _getMode()
{
if (isset($_POST['btnSave']) && $this->_model->isValid()) {
return 'result';
} elseif (isset($_POST['btnNext']) && $this->_model->isValid()) {
return 'check';
} else {
return 'input';
}
}
function &getModel()
{
return $this->_model;
}
function &getView()
{
return $this->_view;
}
}
class MyModel
{
var $_params = array();
var $_errors = array();
function MyModel()
{
$this->_init();
$this->_validate();
}
function _init()
{
$this->_param = array();
$zip = isset($_POST['zip']) ? $_POST['zip'] : '';
$zip = trim(mb_convert_kana($zip, 'KVas'));
$zip = str_replace('-', '', $zip);
$this->_params['zip'] = $zip;
}
function _validate()
{
$this->_errors = array();
if ($this->_params['zip'] == '') {
$this->_errors[] = '入力されていません';
} elseif (!preg_match('|^[0-9]{7}$|', $this->_params['zip'])) {
$this->_errors[] = '入力内容が正しくありません';
}
}
function getParams()
{
return $this->_params;
}
function isValid()
{
return count($this->_errors) == 0;
}
function getErrors()
{
return $this->_errors;
}
}
class MyViewInput
{
function display($model)
{
if (isset($model)) {
extract($model->getParams());
$errors = $model->getErrors();
} else {
$zip = NULL;
}
include 'templates/input.tmpl';
}
}
class MyViewCheck
{
function display($model)
{
extract($model->getParams());
include 'templates/check.tmpl';
}
}
class MyViewResult
{
function display($model)
{
extract($model->getParams());
include 'templates/result.tmpl';
}
}
function h($str)
{
return htmlspecialchars($str);
}
?>
テンプレートは、昨日のやつとはちょっと変わっているんだけど面倒くさいから省略。エラーが配列になって、actionのURLはテンプレート側で解決($_SERVER['PHP_SELF'])するようにしただけ。
より規模が大きいフォームとかに拡張することを考えたプロトタイプとして書くならこういう書き方もありかなー。ただ、これはもともとの仕様を満たすには適材適所を大幅にはみ出ている気がする。
まあ、頭の体操にはいいよね。多分人によっては、MVCの切り分け範囲が違ってくるんだろうな。
ああ
$ctrl->_getMode()が$ctrl->runの中のswitchの条件に依存している。危険危険。