教えてくれる人が側にいない環境とか、職場の空気が良くないとか、とかく周囲のせいにしがちな私たちだけど、こと自分の成長のためを思ったら「それは俺自身のせい」と思うようにしてみたら?
何が自分を成長させてくれるかは考え方次第。
これは「職場」を「社会」に置き変えてもそう。
自分が「そこ」を構成している一員である以上、自分に出来ることはある筈だ。
教えてくれる人が側にいない環境とか、職場の空気が良くないとか、とかく周囲のせいにしがちな私たちだけど、こと自分の成長のためを思ったら「それは俺自身のせい」と思うようにしてみたら?
何が自分を成長させてくれるかは考え方次第。
これは「職場」を「社会」に置き変えてもそう。
自分が「そこ」を構成している一員である以上、自分に出来ることはある筈だ。
Webアクセシビリティの議論とかで「2007/05/30」はちゃんと読み上げられないから「2007年5月30日」って書こう、みたいな話が必ず出てくる。
これは、読み上げる側が改善されれば良い話であるわけで(本来は支援技術側の仕事)、究極の答えは「じゃぁ、読み上げられる音声ブラウザを作れ」というものになる。
僕にはその力が無いから、「アクセシブルなHTMLはこうやって書くんだよ」といったドキュメントを作成して公開したりブログに書いたりする。
同種の話では、「何でMTですか? 再構築とか、重くないですか」とかも同じ。
そこで思考を止めてしまわずに、「読み上げやすく変換するフィルターを作ろう」とか「どう設計したら再構築のストレスなくなるだろう」とか考えて実際にやってみる。
出来ることから実際に行動していくことが大切。そこから見えてくるものが色々ある。
そこをクリアすると、新しい世界が見えてくるのだ。
ホームページ・リーダーは3.04からPDFの読み上げに対応している。それ以前のバージョンでは未対応だった。PC-Talker XP Version1.14, 95 Reader Version 6.0, JAWS for Windows Professional Version 6.2 ではPDFを読み上げることができるようだ。
とはいえ、まだまだ問題は多そうだ。
PDFに関しては,
- 95 Reader以外は,表示行ごとに区切って読むので,読み上げが不自然だった.
- JAWS以外は,PDFのテーブルを表として読み上げることができなかった.
- グラフの読み上げに関しては,PC-Talkerはグラフの表題しか読み上げなかった,
- HPR 3.04は見出しを読まなかった.
- 画像の代替テキストは,95 ReaderとJAWSは読み上げるが,PC-TalkerはPDF上では読み上げることができなかった.HPR 3.04は読み上げる画像と読み上げない画像があった.
調査の結果,PDFの利用にはかなりの制約があることがわかった.全ユーザエージェントが共通してできたのは,本文と表を上から順に読み上げることだけである(以下略)
これまでNaked(Beta) (音声ブラウザができるだけ読み上げやすいページに変換しようと試みるゲートウェイ) では、Content-Type が text で始まらないものは無条件にリダイレクトしていたのだが、Xpdf をインストールするついでがあったので変換を試みることにした。
見出しが読めるわけでもなく、表が読めるわけでもないが (JAWSでは表がうまく読み上げられるようだ)、文章が途中で分割されてしまう (95 Reader以外は,表示行ごとに区切って読むので,読み上げが不自然だった.
) 部分に関しては、改行(文章や単語の中で改行されたと思われる部分)を削除することで対応させた(つもり)。
この部分はXpdfの解釈もGoogleやYahoo!の検索結果から参照出来るHTML(もどき)も共通していて、PDFをテキスト化した時に「各行毎に改行されてしまう」傾向がある。スクリーンリーダーや音声ブラウザの多くが各行毎に読み上げてしまうことから、おそらくPDFの仕様なんだろう (このあたりは見出しの抽出方法とあわせて今後調べてみようと思う)。
これは想像なのだが、これが仕様であるならばおそらく縦書きのPDFの読み上げ状況が悪いのではないかと思う(誰か分かる人教えてください)。
現状の変換についてはちょっと(まだまだ実用的じゃないと思うけど)以下の通り
表は...ちょっと僕の力量では難しいかもしれないが。
以下、変換結果。元にしたのは (特に意味はありません)「京都府ウェブアクセシビリティガイドラインのPDF版」。
ベタ読みになってしまうのが何とも辛いところだけど、テキスト変換するだけでもこうして携帯版が出来たりルビを振れたりするし、やはりHTMLやプレーンテキストで情報が伝わるように作成することが大切なのだと思う。
以前のエントリー (Naked(beta)のこれから。) で書いたもののうち、検索からのアクセスについて。
URLからのアクセスの他に「検索」から利用スタートできるように(Yahoo! のAPIを使おうかと目論んでいる)
Yahoo!検索WebサービスのAPIについてちょっと調べて2〜3時間くらいあ〜だこ〜だして原型ができた。シンプルで良いです、これ。Yahoo! Japan Good Job! ですよ (反応遅い?) 。
MeCab (和布蕪)を使ってルビ振りを実装してみた。
弾さんのコードを参考にして。
弾さんのやつはXHTMLの空要素が /="/" とかになっちゃうのと、英語の単語間の空白が飛んでしまうのでまぁごにょごにょと。
HTMLパーサでの実装は参考になるなぁ。こうするんだ。
どっちかと言えばインストールの方が大変だったけどね。
小学生向けCSSとか、色々楽しそうだ。
読み上げの簡易的な確認にも使えそう。
※しかし... 「和布蕪」を「和布蕪」を使ってルビを振ると「わかめかぶら」...
こんなのを作ったもんだから (なのか?)AdSenseの広告が「豊 胸」とか「ペ○ス増○」とか「か つ ら」とかが増えてきたような気が...
※Naked=裸
あんまり感じよくないなぁ。フィルタとか使ってみるかな。
Googleもまだまだ...だな! なんて言ってみるのはいいけど、こんなエントリーを上げるとますます「そっち系」に最適化される罠。
HTML書きたちを、プログラマーたちが「下に見ていた」ということは否定できない。そのHTML書きたちの、「私たちにも少しはプログラムさせてよ」という声に他の言語屋たちが耳を充分傾けてこなかったことこそ、猛省すべき課題だろう。
HTML書きはプログラマを「腫れ物に触るように」見ていたのかもしれない。ただ、HTML書きは今やその領域でプログラマが出来ないことを成し遂げてしまった。
デザインについても然り。
結果、プログラマたちはデザインテンプレートサイトや素材ジェネレーターに群がっているし。
結局、大切なのは「何を生み出せるか」なのだ。パートナーは誰だって良い。ただ、相手にも選ぶ権利はある。
この記事を、えぇ、1〜3まで読んだ時にね、「くだらねぇ記事読んで時間を無駄にしたなぁ」って思ったわけで。
まぁ、流せば良いわけですが、この記事が「はてブ」で大量に「ブクマ」されてるわけですよ。
「多くのオープンソースソフトウェアがPHPで書かれているからPHPを覚えよう」ってのがこの記事の主題なわけで(小学校の国語のテストならこれで正解ではないか?)。
とかいうパラグラフは、はっきり言ってどーでもいいブロックじゃないか。この記事においては。
※別に、「多くのオープンソースソフトウェアがPerlで書かれているからPerlを覚えよう」でも全然成立するわけだし。PHPがどうとかPerlがどうとかではなく、「今だからこその「PHPのすすめ」というタイトルの記事でこの内容、且つ大量のブクマってあたりがちょっとひとこといいたくなる原因なわけです。読み終わった後で「時間を返せ」をいう気持ちになったのではないかと推測するのです。
それで(僕ですら切れかけたのだから) こうなるわけですよ。きっと。
だから、言語としてのPHPに対するツッコミではなく、記事、あるいは記事に大量にブクマされているという現実に対する何だ、その、まぁhogehogeなわけだ。と推測してみる。
※つまりね、こんな記事に反応してブクマなんかしないでくれ! ということが言いたい。
さて、2週間でつくったβ版(レベルはまだまだα版) Webサービス、Naked(仮称) の仕様と今後について(考えていることを)簡単に。
音声ブラウザ (に限らず様々なデバイス) に対してできるだけ最適化されたコンテンツに自動変換してページのデータを返すゲートウェイ。
名前 (Naked) の意味は「裸」。CSS Naked Dayとか? 意識? してますよ。もちろん。
現状の音声ブラウザ, 携帯ブラウザ等の仕様や制限によってユーザーに「伝わらない」「伝わりにくい」情報を出来る限り伝わりやすく変換する技術を開発することで、ユーザーが利益が恩恵を受けるだけでなく、制作者の負担を軽くし、様々なUAに対して最適化されたウェブコンテンツを制作しやすくする。
また、旧来の作成手法(テーブルレイアウト、透明GIFでのレイアウト調整)からWeb標準に則ったサイトへの移行をしやすくし、以下のような用途への技術の転用を予定。
※まぁ、これくらいぶち上げておくのも良いか、と。
来週あたりちゃんとデザインしてドメインも取って引っ越ししたい。
今は何かと話題? のオンライン「ロゴジェネレーター」で作成したロゴをペタっと貼っているだけなので。
FizzBuzz問題が面白かったのでやってみた。あくまでも「アマグラマ」の余興のレベルですよ、ええ。
全然短くならないや。まぁ、プログラマ面接受ける予定ないからいいけど...って違う違う、今度面接する時に参考にしよう。
※でもまぁ言うだけだと卑怯? なので貼っておく。
短けりゃいいってもんでもないしまぁいいか。
perl -e 'for(1..100){print$_ if$_%3&&$_%5;print"Fizz"unless$_%3;print"Buzz"unless$_%5;print"\n"}'
エレガントにサクっと書ける方と面接でお会いできる日を楽しみにしていますから!
この5月、ブログに1日1エントリー、毎週何かのアウトプット(WebサービスとかMTプラグインとか)を自分のノルマ化してあれこれ書いたり作ったりしている。
1日1エントリーはさすがにしんどいか。
それでも「本当に好きなこと」「本当にやりたいこと」「儲からない? けどやりたいこと」を今月あたりはあれこれやっている気がする(本当は儲からないわけではなく、それなりの打算もあるわけだが)。
間もなく会社をはじめて4期目の期末。ようやくここまでたどり着いたという感じだ。
さて、それ(自分のやりたいことがやれる会社)が出来る状況に自分を置くためのいくつかの約束事について書く。結局『会社』をつくるってのは「やりたいことを納得いくようにやる」ための手段なのだ(僕にとっては)。
何はともあれ、殆どの人はサラリーマンであったりフリーランスであっても日々の生活の糧に追われる身である。色んな意味で「バブリー」な人たち (アルファな人たちもね) のことはひとまず考えず、まずは目の前の仕事で成果を出そう。「堅実な成果」は裏切らないから。
これは周囲から「文句を言わせない」ための何よりの特効薬でもある。
あなたの上司や会社は常に数字や結果、つまり成果を求めることだろう。
であれば、あなたのやりたいことで成果を出せるのがベストである。成果さえ生み出せれば誰にも文句は言われない。
だから、あなたのやりたいことは社会にどれだけニーズがあって、それはこんな可能性を秘めているんだということを考えてあなたの阻害要因(例えば上司とか) を取り除くこをを考えよう。
自分がやっていることの意義、上記項目と重なるが「私がやりたいこと、やっていること」は「あなたの利益(例えば「儲け」)につながることなのですよ。ということを繰り返し繰り返し話し続けよう。
安易な転職はお勧めしないが、どう考えても接点を持てない場合がある。
今からかれこれ7〜8年くらい前のことだったか、広告制作会社に在籍、「Webアクセシビリティ」が自分のテーマになってしまったことがある。色んなアプローチをとったが根本のところでは結局は理解してもらえなかった。だって、Web系の会社でもないんだし。
※今は少し状況が違う。JIS X 8341-3のこともあるが、何より企業のCSRやコンプライアンスへの意識が高まっている影響で、(あるいはウェブの業界も進んで来たこともあって)、結果的には状況は変わって来たわけだ。
ただ、その時は難しかったな。今となっては理解できるのだが、「接点」が無いところでいくら頑張っても限界はあるのだ。
そういった場合は転職も視野にいれよう。「会社はわかってくれない」のではなくて「その会社には接点がない」のだ。
Googleとか20%は自分の好きな分野の研究がどうのって、与えられることを前提に考えるから時間を作れないのだ。
仕事ができる人は20%の時間なんぞ結果を出しながらひねり出せる。だから、「出来る人を採用して20%好きなことをやらせる」も「出来る人は勝手に時間をひねり出して20%程度は好きなことをやっている」は結果的には同義だ。
あなたの会社が古めかしくて、好きなことやっていたらまわりとの軋轢がうまれるとか、周りが残業続きなのに自分はさっさと片付けて周りから浮いてしまっている...のならば、周囲と同じく「しんどい」演出をしつつ、8割の時間でノルマをこなしてしまえ。で、残りの2割の時間は「難しい顔」をしつつ、同僚を励まし励まされつつ、好きなことをやっていたまえ。
それもまた「大人」のやり方なんだ。
あなたのやりたいことが社会的に意義があって「儲け」との接点も自信を持って説明出来て、それでも尚且つあなたが置かれている環境における周囲の人に理解されないことを気に病んでいるのであれば、(つまり、本当に会社はわかってくれない! のれあれば)『そんな場所はさっさと飛び出してしまえ』。
それでも、再び戻って自問してみるのだ。原点に立ち戻って、その上でそれでも...辞める覚悟があるのなら、
結局独立してフリーランスになっても会社を作っても、最初の問題「儲かるところで結果を出す」に戻ってくる。
自分が立てた仮説通りに物事が進まないのであれば、結局は会社や上司が「わかてくれなかった」ことが「正しかった」ことになる。『あいつの口車に乗らずに良かった』てなもんだ。
結果を出せなければ、会社を辞めて職を失うわ、会社を作ったが借金ばかりが増えていくわ...という状態に陥るわけである。
だから徹底的に計画して、数字のシミュレーションも終わって、「さぁ、これで行けるぞ! 明日から俺も一国一城の主だ! 」と思ったら、その段階でもう一度『その数字を持って周りを洗脳しに動いてみる』のだ。一抹の不安もないのであればさっさと辞めてしまえば良い。迷いがあるなら尚のこと、その数字、計画を『会社向け、上司向けにリライトしてから』持って行けばよい。
さて、あなたは上司にそれを持っていった。そこで感じたこと、得られた反応を大切にしよう。
それで何かが変わるかもしれない。
それでも単なる『分からず屋!』という気持ちになって、『これでもこの計画の意味がわからんか!』という気持ちになったのであれば...
その時こそがキミが起つべき時である。『業を起こす』と書いて『起業』と言う。
そこが「社会」にとってはとても小さな、そして「自分」にとってはとても大きな一歩なのである。
メディアタイプによる分岐とINS, DEL要素の処理を実装してみた。
<ul>
<li class="silent">読み上げないテキスト</li>
<li class="speech">読み上げるテキスト</li>
<li><ins datetime="2007-05-19T13:45+09:00">挿入されたテキスト(日付指定あり)</ins></li>
<li><del datetime="2007-05-18T13:45+09:00">削除されたテキスト(日付指定あり)</del></li>
<li><ins>挿入されたテキスト(日付指定なし)</ins></li>
<li><del>削除されたテキスト(日付指定なし)</del></li>
</ul>
参考情報:
CSSのメディアタイプを指定することによって、対象のデバイスによってレンダリング結果 (視覚的なレンダリングとは限らない, 例えばサウンドとか)を分岐させることができる。多くのブラウザはメディアタイプ print に対応してきているため、既に以下のような使い方は日常的に行われるようになった。
音声ブラウザのようなメディアの場合、メディアタイプ aural (※) を指定することができる。
出来るとは言っても、現状対応しているUAは (僕の知る限り) 殆ど無いのだが。
※CSS2.1からは aural の代わりに speech だそうである。aural メディアタイプに対応した読み上げ環境が殆ど無い現状で仕様も何も...てな思いがあったりなかったり...
メディア別のCSSは例えば以下のようにして適用させることができる。
今回は上記の1番目の書き方に対応させた。このページにおいて以下の LINK要素を追加。
<link rel="stylesheet" href="styles-aural.css" media="aural" type="text/css" />
#beta { /*これはMTの初期のまんま、右側のサイド・バー部分*/
display:none;
/*隠すのがアクセシブルというわけではなくて, あくまでもテスト用*/
}
.speech { /*音声ブラウザに読ませたいところ*/
display:inline;
}
.silent { /*音声ブラウザに読ませくないところ*/
display:none;
}
普通にブラウザ(Safari)で見る限り何も変化がない。そりゃそうだ。メディアタイプで言うと「screen」にあたるのだから。
ところが、HPR (ホームページ・リーダー) だったら aural が適用されるかというと、そんなことはない。HPR は IE がレンダリングした結果を読み上げているので、今僕が Safari で見ているものと変わりないCSSが適用されているのだ。
処理としては、CSSを解釈してページ出力をするのではなく、相手(誰?) が screen メディアとして振る舞うのだったら騙してやれ、ということで「media="aural" , media="speech" 」を「media="all"」に変換することにした。
例えばウェブアクセシビリティにおけるライティング(コピーライティング)の役割は非常に重要なのだが、「ウェブページ」が「ウェブデザイン」というプロセスを経て作成される以上色んな意味で制約が出てくる (アクセシブルにしようとすると表現が冗長になったり)。
UAの特性に適した分岐が可能であることによって「読み上げてわかりやすい表現」の分岐も可能になると思う。
ただ、ソフトウェアが対応していない技術を使うということは自己満足のような面もあるから中々普及しないのだろう。
逆に普及しないからソフトウェアが対応しないのかどうかは僕にはよく分からないが。
INS要素は挿入された部分を、DEL要素は削除された部分を表すのに使う。
多くの視覚系UAは、INS要素は下線、DEL要素は打ち消し線で表現される。
これらの要素は視覚系のUAでも使い方によってしばしば問題になる。下線や打ち消し線を前提とした情報提供を行った場合 (下線は新製品、打ち消し線は売り切れ在庫無し、など) 、下線や打ち消し線が付かない環境では意味が通じなくなるからだ。
例えば僕の携帯電話(SH901iS)のブラウザでは打ち消し線も下線も表示されない。 検索エンジンが表示する「要約部分」にも、多くの場合下線や打ち消し線は付かないだろう。
音声ブラウザでも同じことが言える。削除されたもの、挿入されたもの、ということが音声で表現されなければ 意味が通らないことになる。
そこで、INS要素、DEL要素については以下のように前後にテキストを追加するようにした。
(追記) 挿入されたテキスト (追記ここまで) (削除) 挿入されたテキスト (削除ここまで)
「挿入」よりは「追記」の方が読み上げとしては自然に理解できやすいかと思うがどうだろうか。
また、INS, DEL要素には datetime属性が指定できることになっていて、「2007-05-18T13:45+09:00」のようなフォーマットで挿入、削除されたタイムスタンプを追加できる。
よって、datetime属性が指定されている場合は、
(2007年05月19日 追記) 挿入されたテキスト(日付指定あり) (追記ここまで)
というように日付を (ちゃんと年月日形式に変換した上で) 付加するようにした。
逆に考えると実際のマークアップの際に、ちゃんと削除、挿入されたことをテキストで表現しておけばこのような処理は不要になるのだ。
さらに続き。
今回は、記号、日付、数式、そしてイメージマップについて考えてみる。
(とある)音声ブラウザはデフォルトの設定で記号を読み上げない。
いちいち読み上げたら鬱陶しいというところもあるのだが、以下のような場合は問題になる。
<a href="http://example.com">◎</a>
▲=休館日 ◎=営業日 ※=イベント開催日
少々鬱陶しくなるかもしれないが、こういった記号はやはり読み上げられるようにテキストにしてあげた方が良いような気がする。
とはいえ、記号が何に使われているのかの判断は (これまた文脈解析でもしなければ) 不可能だ。※(あすたりすく)がイベント開催日を表しているのか注釈なのか、どう判断すりゃいいんだ?
判断できそうなものもある。例えば数式や日付など。
$str =~ s/((?:¥G|>)[^<]*?)(^0|[1-9])((?:[0-9]|(?:[¥+|+|¥-|-|/|*|=])|¥s){10,}[0-9])(.*?)/$1.&math_to_str($2.$3).$4/eg;
0以外の数字で始まる(<-悩ましいところだけど電話番号を拾ってしまうので...いや逆に電話番号を識別すればいいのか?)数字と演算記号とスペースで構成される10文字(<-適当)以上の文字列で、且つ数字で終了しているものを置換対象とする。
あと、一応 &math_to_str の中で =(イコール) を含むかどうかチェック。住所の中に 1-2-3 とか出てくるので。
数式の場合は「ー」は「ひく」あるいは「まいなす」に変換してやれば良い(その他の場合は変換しない、とすれば良いか。
また、数式の中の「=」は「いこーる」または「わ」(<-「は」ではなくて) とする。
日付フォーマットも年月日形式に変換する。
「ごぶんのにせんなな じゅうはち」とか読み上げられた日にゃぁ、何が何だか。
$str =~ s/((?:¥G|>)[^<]*?)(?<![0-9]|[a-z]|[A-Z]|"|'|¥/)([0-9]{4})¥/([0-9]{1,2})¥/([0-9]{1,2})(?![0-9]|[a-z]|[A-Z]|¥/)/$1.&checkdate($2,$3,$4,$6)/eg;
タグの中を置換すると /2007/5/18/post.html みたいな(MTとかで良くあるパターン)ものをひっかけてしまうので、タグの外であることと "/ あたりで始まる場合はURL等の可能性があるので除外する。
また、マッチした後に実在する日付かどうか一応チェック。2007/02/30 は日付じゃない! と判断することで精度を高める(ようにできるだけ努力)。
※このあたり実は強くないので添削求む!
記号を「よみ」に変換することで逆に今度は視覚的に理解しづらくなる。 「音声ブラウザでできるだけ読み上げやすいHTML」がコンセプトではあるけれども、文字を大きくしたり配色を変えてみたり色んな用途に使えるエンジンにしたい、という思いもあって (色んなスキンを適用できるってのも単純に楽しいし) 、この切り分けはCSSで行えるようにクラス名を振ることにした。
<span class="naked_sign">×</span><span class="naked_aural">かける</span>
現状はここまで。
▲ページの先頭へ -> さんかくぺーじの先頭へ
とかになってしまうが、回避する策はあるだろうか... 要素内容が「記号とスペース」のみの場合だけ変換する? う〜んいまひとつかもしれない。
イメージマップについても正しく記述していればちゃんと読み上げてくれる。 但し、AREA要素を使う場合はALT属性が必要。
ということで変換しなくても良いのだが画像を削除あるいは外部リンクに変換する関係でそのままではまずかろう。
単にAREA要素をリンクに変換すれば使えるのだが、一連のひとかたまりのリンクということでUL〜/ULの形式に変換する。
以下は Yahoo! Japan の例
<img src="http://i.yimg.jp/images/main13.gif" width=675 height="60" usemap="#Map" border=0 alt="Yahoo! JAPAN">
<map name="Map">
<area shape="rect" coords="194,1,430,57" href="/r/lg">
<area shape="rect" coords="626,1,670,16" href="/r/ht" alt="ヘルプ" title="ヘルプ">
<area shape="rect" coords="627,22,672,40" href="/r/et" alt="登録情報">
...
</map>
変換後のHTML
[画像:<a href="/naked.cgi/default/http://i.yimg.jp/images/main13.gif">Yahoo! JAPAN</a>]
<ul class="image_map">
<li><a href="/naked.cgi/default/http://yahoo.co.jp/r/lg">/r/lg</a></li>
<li><a href="/naked.cgi/default/http://yahoo.co.jp/r/ht">ヘルプ</a></li>
<li><a href="/naked.cgi/default/http://yahoo.co.jp/r/et">登録情報</a></li>
...
</ul>
問題は1つめのリンク。ALT属性がない。ALT属性がないものはぶった切ってしまえ! と思ったけれどイメージマップしか置いていないページ (ALT属性もないし) をこのあいだ目にしたので、仕方ない。リンク先のパスをALTの代用とすることにした。
現状はこのようにしているが一点問題があって、MAP要素はコード上のどこに置かれるかわからないのだ。
<map name="menu">
<area... />
<area... />
</map>
<p>以下のメニューから選択してください。<p>
<p><img src...usemap="#menu" /><p>
そのまま変換すると「以下のメニュー」じゃなくなるのだ。位置関係がおかしくなってしまう。
アンカーで飛ばすか、あるいはスマートなのはこんな感じか。
<dl>
<dt>[イメージマップ:<a href="/naked.cgi/default/http://i.yimg.jp/images/main13.gif">Yahoo! JAPAN</a>]</dt>
<dd>
<ul>
<li><a href="/naked.cgi/default/http://yahoo.co.jp/r/lg">/r/lg</a></li>
<li><a href="/naked.cgi/default/http://yahoo.co.jp/r/ht">ヘルプ</a></li>
<li><a href="/naked.cgi/default/http://yahoo.co.jp/r/et">登録情報</a></li>
...
</ul>
</dd>
</dl>
「イメージマップ」って言葉がわかりやすいかどうかが気にはなるが。
以下、テスト用
引き続きウェブアクセシビリティ関連の話題。
このエントリーの続きだが、今回は単に音声ブラウザとの相性だけでなくレイアウトのためのテーブル関連要素を判別してカットする考え方について書く。
※実はこのエントリーを書く段階で、テーブルの良いサンプルを見つけようと思い「週間天気予報」で検索して「気象庁 週間天気予報」を見つけたのだが...うまく変換できなかったのでプログラムのロジックを変更させられた。ちょっとこれは無いんではないか、気象庁御中。
<table>
</p><br>
<b>全国主要地点の週間天気予報一覧</b>
<p>5月17日17時<br>
<table class="forecastlist" id="infotablefont">
...
テーブル自体は正しくマークアップしてあれば (本来の) 2次元で表現できる情報をわかりやすく表現できるし、ホームページリーダー等でもテーブルの読み上げモードが用意されている。
※今ちょっと手元に環境ないので記憶曖昧だが、「表の先頭」とかちゃんと読み上げてくれる(はず)。
HTMLとテーブルについてはこちらを参照されたし。気象庁と戦ったおかげ? で解説まで書く気力もないし。
| 氏名 | 年齢 | 血液型 |
|---|---|---|
| 野田純生 | 40歳! | A型 |
例えばこんな表を、
氏名→野田純生, 年齢→40歳(不惑? 厄年?), 血液型→A型
ってな具合に読み上げることも可能なのだ。
ところが「テーブルレイアウト」である。表に次ぐ表のオンパレード。どう読み上げれば良いのだろう。
ということでレイアウトのために用いられている「であろう」テーブルをぶった切ることにする。
※「テーブルをぶった切る」っていうと「ちゃぶ台をひっくり返す」みたいですな...
方針を書くところまでは早いのだけれど、実装となるとなかなかにコストのかかる処理になってしまった。
TD要素の中にはTABLE要素が置けるから、
<table>...<tr><td>...<table>...</table>...
というように「ネスト」できるのだ。だから単純に
$src =~ s/<table.*?</table>/ほにゃらら/igs;
みたいなことが出来ない。パーサ使う? ってのもどうかと思うし(何しろ、きちんとパース出来る保証はどこにもないのだから)...とにかく一応 split で切ってループで処理させてみた。段々重くなるような気がするぞ。
この方針に沿って変換したのがこれ(すっぴんバージョン)。
で「裸」にしたら服を着せてみたくなるのが人情, ということでCSSを充ててみたのがこれ。
ね、それなりにCSSベースのレイアウトになってるでしょう?
追伸:
頑張れ! 気象庁
引き続き、次回以降は「日付」「記号」「数式」などについて。
まともなメールのひとつも送れない学生...
色んな意見があるなぁ...と。
どなたかが「プロトコルの違い」と仰っていたように思うが、プロトコルとは約束事とか儀礼のことである
「プロトコルの違い」にはとどまらなくて『異なるプロトコルで違うポートに接続しにいくようなもの』ですかね。
POPサーバーだったら「HELLO」とかFTPだったら「USER」とかっていうのを何も考えずに「GET」とか、『いつもGETで通ってますから』ってなもんで。
だから、『今どきFTPサーバーないの!?』みたいな指摘は的外れだしね。
実際、僕が携帯から社内メーリングリストに送るメールには署名も入れないし「野田です」も入れないけど、顧客や取り引き先へメールを送る時にそんなことはあり得ない。
どうでも良いけど、「はてな」"※デフォルトではないのかな? ってか特定のサービスに限ったことじゃないけど。"とかで、
「このページをアンテナに追加」とか「RSSフィード」ってのが見出し (<h1>) の中にあるってのも UserAgent (例えば音声ブラウザ) に対する「プロトコル(謎)」を考えていない、ということなんだろうなぁ。
先週から「音声ブラウザ用にできるだけ読み上げやすいHTMLに変換するゲートウェイ」なるものを少しずつ書いている。
毎日少しずつ修正しているのだけれど、どうやって読み上げやすいHTMLへ変換するかについての考え方を(これも少しずつ)書いてみよう。
画像には等価なaltテキストが指定されている筈、という前提に立たないで考えないといけないから、どんな条件の場合にはどんな画像が利用されるかについて考えて処理を行う必要がある。
画像には等価なaltテキストが指定されているのであれば画像はすべてaltテキストを展開すれば良いわけだが、続けて読み上げて意味の通る文章になるとは限らないのだ。
また、敢えて「画像」であることが分かったほうが良い場合もある。
日本について。<img alt="富士山" src="fuji.jpg" />
というようなHTMLの場合、
日本について_[一拍置いて]_ 富士山
「富士山」が装飾のための画像だったら、何だかおかしくなってしまうだろう。
日本について_[一拍置いて]_[声を変えて]_富士山_についての写真があります
とすれば「そこに画像があるんだな!?」っていうのが分かる。音で読み上げているにしても「そこに写真があるんだな!?」ってなことが把握したいケースがある筈だ (例としてはあまり良くないかもしれないが)。
画像が「視覚的な」装飾のためでのものあれば以下のように書くべきだろうが、それが装飾のための画像かどうかの判断は (文脈でも理解しない限りは) 不可能である。
日本について。<img alt="" src="fuji.jpg" />
「画像=コンテンツの一部」である場合、強制的にテキスト変換だと困るし (画像であることが理解できるようにして欲しいだろうし)、alt属性を空にすべきではないものもある。
自分が「視覚的」に地図を見ることができなくても、印刷して (それを誰かに見せて) 人に説明を求めることだってできるし、現地で誰かに地図を見せて道を尋ねることもできる。
「何番出口を出てどういってこう行って」等のaltテキストを指定するとベストなのだろうが、現実的にそんなページは殆ど無い。
画像を画像へのリンクにしたら(イメージへの直接リンクは良くない、ということに注意は必要)読み上げ音声も(ホームページリーダー等の場合)変化するし、画像をプリントすることもできるだろう。ただし、その場合のリンクテキストについては
地図_についての写真があります
だと不自然だろうから、単純に
画像_地図
とかの方が良いのかもしれない。また、alt属性に「画像:地図」とか書いてあるページがあって、その場合は重なってしまう(画像_画像_地図)ので「画像」で始まるalt属性の場合は単純にalt属性をただ展開すれば良いだろう。
いわゆる「パンくずリスト」について、
<img alt="現在位置" src="you_are_here.gif" /> ホーム <img alt="の中の" src="arraw.gif" />...
のように音声読み上げ環境に配慮したHTMLがある。これを画像をリンクにして
画像_現在位置
ではちょっと困る。
画像_現在位置_ホーム_画像_の中の
せっかくの配慮が逆効果になってしまう。もちろん「現在位置_についての写真があります」ではもっと困る。
この場合は画像は画像でも、そこに画像がある/ないを意識させないのが親切だろう。
同じく、リストマーカーのような画像(例えば行頭の矢印)等の画像について、
<img alt="→" src="arraw.gif" />
<img alt="矢印" src="arraw.gif" />
とかいう指定をしているもの。これは alt="" にすべきケースが殆どなんだろうが、
画像_矢印
なんて読み上げたら鬱陶しいことこの上ないだろう。
そこで、小さな画像は概ね「写真」や「画像」そのものが意味を持つものではないという仮説を立てる。width属性とheight属性を見て一定サイズ以下だったら「画像_altテキスト」とせずに単にaltテキストを展開する。
「見出し」文字を画像化しているケースも多いだろう。これはプロのウェブデザイナーでもやっている。CSSで画像置換 (Image Replacement) というテクニックも一時流行ったが、画像オフ/CSSオンだと何も見えなくなるということで最近は下火のようだ。
<h1><img src="product.png" alt="製品案内" /></h1>
これが、
[見出し強調された(且つリンクになっている)音声で] 画像_製品案内
これも"ちと"辛い。この場合は「画像であることイコール見栄えを良くしたい」わけだから、音声読み上げ環境のユーザーには関係のないことである。この場合は
[見出し強調された音声で] 製品案内
であるべきだろう。
見出しと似たケースでは「画像ボタン」があげられる。ボタンが「クリック」しやすいように画像にする。ユーザビリティ面でも「ボタンは一目でボタンとわかるように」とか「ボタンは"押せる"ように」とかの鉄則? があるし。この場合も、
<a href="product.html"><img src="product.png" alt="製品案内へ" /></a>
の読み上げは、単純に以下のようになるのが良いだろう。
[リンク用音声で] 製品案内へ
まずは画像について書いたが、まだまだ考慮すべき問題はある。
例えば「日付」「記号」「通貨」「日本語の単語の空白文字での分割」等。これらについてもおいおい書いていこうと思う。
でも、何と言っても難しいのが「レイアウトのためのテーブルと構造を示すテーブルが混在しているHTML」である。これについても考え方はある程度固まっているけれども。
以前のエントリーでご紹介した「音声読み上げ向けページ変換ゲートウェイ」ですが、変換速度の向上といくつかの変換アプローチの見直しを行った上でいくつかのデザインスキンを適用出来るようにしました。
簡単なデモページを用意しました。
以下の通り、記号は使っているわ、文字揃えのために地域名は分割しているわ、日付は「/」で区切っているわ、広告は入っているわ...というパターン。
※このエントリー上では数値参照化しています。
※5月16日少し修正, 記号、(何となく)数式対応にしました。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>デモページ</title>
</head>
<body>
<pre>
⑴ 2007/05/16㈬ 北海道 製 品 デ モ ¥10,000
⑵ 2007/05/17㈭ 東 北 製 品 デ モ ¥10,000
⑶ 2007/05/19㈯ 関 東 セ ミ ナ ー ¥10,000
⑷ 2007/05/20㈰ 近 畿 セ ミ ナ ー ¥15,000
⑸ 2007/05/21㈪ 香 港 懇 親 会 $200
主催:ABC㍿
</pre>
<div style="float:right;padding-left:1.5em"><script type="text/javascript">
<!--
google_ad_client = "pub-1511440356861540";google_ad_width = 120;google_ad_height = 240;google_ad_format = "120x240_as";google_ad_type = "text_image";google_ad_channel = "";google_color_border = "FFFFFF";google_color_bg = "FFFFFF";google_color_link = "335C85";google_color_text = "555555";google_color_url = "555555";
//-->
</script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script></div>
</body>
</html>
えーっと、検索の質とかそういう問題ではなくて。
Another HTML-lint gatewayでのチェック結果。
- Google 日本
80個のエラーがありました。このHTMLは -215点です。
- Yahoo! Japan
869個のエラーがありました。このHTMLは -443点です。
点数だけで比較は出来ないですね(っていうか、どっちもどっち)。タグが 29種類 1462組使われています(Yahoo!)、とタグが 28種類 62組(Google)だからね。
こうやって書く理由ですが単なるHTMLへの純粋な?ツッコミではなくて、ちょっと昨日今日と苦労したのだ。
ってのを作ってみたのだ。「できるだけ音声ブラウザで読みやすいHTMLを返すゲートウェイ」。
最初はMovableTypeのプラグインを書いていたのだ。テキストバージョンテンプレートってのを作って完全なるJIS X8341-3対応! ってのを考えてね。
だいたい出来たところで、(基本的にHTMLを正規表現でああしてこうして...)「これやったら別にMT限定じゃなくてゲートウェイとか作ってみたら実用性があるんじゃないだろうか...」ということで改造して置いてみた。自分のブログとか会社のサイトとか閲覧してさ、だいたいこんなもんでいけるんかなと思ったあたり...
実際に使うとなったらやっぱり検索エンジンとか必要だろうなぁ...ということでGoogle!(ん?、何かおかしい), Yahoo(レイアウトが変...)
ひどかったのはYahoo!のほうだったので、つぎはぎ的に修正して修正して...うん、なんとか使えるに耐える...って、遅い!
CSSベースになっていない、、ってのは百歩譲って。両者に共通なのは、
MTプラグイン作って自分で動かしている時は気にならんかったわけだ。ところがYahoo!やGoogleだとスタイルシートちょんぎってプレーンにしたつもりがちょん切れてない。
タグの大文字小文字もばらばらだし、Googleは改行がなくて見にくいし。
容量減らすために「引用符」「改行」を省略してる!? トラフィック気になるならロボットの振る舞いなんとかしなよ!
まぁ処理が遅いのは自分の書き方が悪いのだけど、もういい加減なんとかならんかね。 正規表現やのうて、最初からパーサーで処理した方が賢かった...というか、パースできんのかね。
肩で息するくらいに吠えたところで両者を改めて見た感想。
Yahoo! JapanとGoogle! 日本はユーザーの層もコンセプトも違うからそのままトップページを比較しても意味がないだろう。検索結果に絞って見る。
my $ua = LWP::UserAgent->new(agent => "Naked/0.1");
という感じ。最初はuse LWP::Simple;を使って「get ...」とかやってたんだが各所で話題の通りGoogleが何も返してくれなかったので適当なua名でアクセス。Googleは広告付きで返してくる。Yahoo!は広告が表示されないようだ。
以下、とりあえず消さないまでも、なんていうか見つけたので。
my $ua = LWP::UserAgent->new(agent => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-jp) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3");
とすると、つまりSafariと偽ってアクセスすると検索結果をh2で返してくる。
my $ua = LWP::UserAgent->new(agent => "Naked/0.1");
とすると見出しにならない...よくわからんがYahoo!もGoogleもUAによって結果が違うのね。
一番気になったのは、Googleの検索結果で「スポンサー広告」と検索結果の識別が出来ないことだ。もちろん視覚的にはできるわけだがYahoo!のように見出しが入っているわけではないから、どこまでがスポンサー広告でどこからが検索結果なのかが理解不能。せめて見出しかアンカーがあれば良いわけだが(もっともYahoo!についても「見えない」ので何とも言えないが(まさかCSSでtext-indent:-1000pxとかやってるわけじゃないだろうな!)実際にどうかはCSS見るか実際に音声ブラウザで読み上げるしかないけど)。
トップページの入り口の好き嫌いはともかく、検索結果の表示を見るとYahoo!に軍配をあげよう。
一瞬、このエントリー見て速攻修正したのかと思った。まさか、ね。いやまじおどろいたよ。
# でもトップページの情報多すぎ(多いのはまだ良いがバキバキのテーブルレイアウトは良い加減何とかしませんか?)。宜しければ誰がご紹介いたしますが...
※ページは予告無く削除されることがあります。
制限事項:
Postメソッドには対応していません。→5月14日対応済み
SSLには非対応です。
追記:
これで見ているとさ、
2007年05月12日→2007年05月12日に変換、とかわけわかんねぇことになるね。
MTのモブログ機能拡張の開発をやりはじめて約一ヶ月。
今日はスタティックバージョンがほぼ完成した。ページ分割も含めて静的HTMLで携帯サイトを構築するもの。
追記:とりあえず暫定公開
(画像のサムネイルをキャッシュしたいところ...というかこれも生成の際に自動作成してやろうか...再構築重そうだけど「サムネイルが存在しなければ生成」ならまぁなんとかなるかも) /追記ここまで
こういうの↓見てると、やっぱりダイナミックよりスタティックかな、と。
ただ気になるところもあって、普通にやると「再構築」時間が倍かかる。何に時間食ってるかというとページ分割部分である。そこでいくつかの工夫を。
プラグインの設定画面で「強制的に再構築」オプションを付けた。
エントリーのmodified_onをチェックして、10分以内に更新されていない且つ静的ファイルが存在している場合は再構築しないように。
インデックス・アーカイブ関係はBlogのエントリーの最終更新日を見て再構築の要不要を判断するようにした。コードはこんな感じ。
unless ($need_rebuild) {
use MT::Entry;
my $entry = MT::Entry->load({
blog_id => $blog_id,
status => 2,
},{
sort => 'modified_on',
direction => 'descend',
limit => 1,
});
if (-f $checkfile) {
my $modified_on = $entry->modified_on;
$modified_on = &get_unixtime($modified_on);
if ($modified_on > &offset_time(time, $blog)-600) {
$need_rebuild = 1;
}
} else {
$need_rebuild = 1;
}
}
問題はカテゴリーアーカイブなんやけど、上手い手が見つからないなぁ。
my $entry = MT::Entry->load({blog_id => $blog_id,}, {
'join' => [ 'MT::Placement', 'entry_id',
{ blog_id => $blog_id,, category_id => $cat_id,},
{ 'sort' => 'modified_on', # <<これが駄目, MT::Placementにないから...
direction => 'descend',
unique => 1,
limit => 1 } ],
});
ループで回すのは効率悪いし...
まぁ、また明日。
最初にMTに触れてから1年強、自分で触り出してから半年くらい。僕には「アーミーナイフ」のようなこのツールがしっくり来た。受託系のWeb制作を生業として10人弱の会社を率いている身としても、このツールを極めることで新しい何かが見えて来そうな気がしている。
ということで、色々やっているんだけどまずはこのブログのしくみ? をご紹介してみる。
カテゴリの表示順を制御したいとか、さらにはエントリーさえも自由に並べ替えたいとか、ブログではなくCMSとして使うのであれば当然出てくるニーズであり、「日付(created_on)を変更してください」「概要(excerpt)」欄に数字を入れてください」と説明していたウチのスタッフに、
という会話から産まれた一品(?)
カテゴリー名(label)の頭と、エントリーのキーワード(keywords)の頭にHTMLのコメントタグを加えるという「ありがちな」ことをやっているわけだがポイントはGUIで並び替え順を制御できること。且つCMS上では何ら意識することはない(コメント部をCMS上でカットしている)。

本当はDrag&Dropに対応させたいところだけど、これで充分という声? もあるし。
正直、これにはかなりの時間を割いた。速くなるわけでもなく、負荷が減るわけでもない。ただ、体感速度があがり、サイト制作シーン等では効率があがることは間違いない。 とにかく時間のかかる「再構築」処理をバックグラウンド化して、CMS上はレスポンスを素早く返す。「体感速度」を向上させるもの。
エントリーのカテゴリーを変更した時に「ゴミ」ファイルが出来る問題や「LaunchBackgroundTasks 1」を指定している時のバグ? (出力先ディレクトリがおかしくなる問題)にも対応している。つまり、できるだけ「ゴミ」を作らないという効果もある。
BackgroundRebuilderを導入していれば不要。
エントリー一覧画面で複数のエントリーをチェックボックスで選択して「非公開にする」を選択した時に、「DeleteFilesAtRebuild」が効かなくて(ゴミ?)ファイルが残ってしまう対策として、ステータスが下書きなのにファイルが残っている場合にリストアップして削除することができる。
エントリーを閲覧している時に「修正したい」と思ったらBookmarkletをクリック。これでエントリーの編集画面にジャンプできる。
主にセキュリティの問題から mt.cgi へのパスを晒したくないケースで使えると思う。
MTから生成されるHTMLファイル内のリンクやimgタグのsrcを相対パスに変換するプラグイン。ローカル環境で閲覧とか、制作はCMS(MT)で、納品はファイルで、といった用途に使える。拡張子やディレクトリ単位で除外出来るようにしているので、IncludeファイルやRSS等を対象外に出来る。
※Includeファイルは、インクルードされた後にちゃんと相対パスに変換されます。

エントリーやカテゴリーの偶数・奇数で出力結果を変更する条件タグ・プラグイン。 いわゆる「縞々」の出力(このBlogのサイドバーのカテゴリーのような)に使える。
Blogのアクセシビリティ向上をCMS側で行う、をコンセプトに作成したフィルタープラグイン。日付フォーマットや単位の表現など、主に「音声読み上げしやすく」する処理を行うもの。画像のalt属性をアップロード画面で入力できる機能も。
(ふざけた仮称ですが...)モブログ機能拡張。携帯電話からメールで投稿、携帯電話からブログの管理、携帯電話からブログの閲覧ができる統合パッケージ。必要なPerlモジュールが結構ある等導入の敷居は低くはないと思うが、インストーラを用意したのでモジュールの問題さえクリアできれば導入は簡単かと。
PerlのUnicode::Normalizeテキストフィルター。Jaccessibilityプラグインに同様の機能あり。
特定のカテゴリーアーカイブを「再構築しない」プラグイン。レア・ケースではあるが、特定のカテゴリーだけ出力結果を変えたい、というニーズがサイト制作にMTを使っている時に稀にあるのだが、その「ニッチ」なニーズに応えるもの。
エントリ投稿時の「確認」を改善するプラグイン。公開状態とほぼ同じ状態でテンプレートやCSSが適用された状態で「プレビュー」できる。設定が容易なのが特徴。プラグインを放り込むだけで有効になる。
エントリーに含まれる文字列とか正規表現でテンプレートの処理を分岐させるプラグイン。同種のものもあるけど、扱い方が簡単。このブログでは携帯からの投稿を分岐処理させるのに使っている。
これは自分でも「すごい」と思う(自画自賛、というか本当にすごいのはHyperEstraierやけど)。HyperEstraierでブログ検索を提供するのだが、エントリーを更新すればリアルタイムに検索インデックスに反映される。また、ブログ毎の検索も、全ブログの横断検索も可能。実際の検索はPHPで書いたオリジナルの検索プログラムで行うのだが、テンプレートデザインの反映や、ブログ・カテゴリのマッチを優先する等柔軟な対応が可能。しかも共通ナビゲーションとかを無視して「エントリー」のコンテンツを優先して検索し、且つエントリーのタイトル等に重みづけがなされるために検索の精度、結果に対する品質もかなり高い。まだ色々と今後も拡張する予定。
MTから出力されたファイルとかアップロードされたファイルを管理するもの。 MacOSのFinderライクに管理できる。今のところビューワとファイルの削除部分を実装済み。
ダイナミックパブリッシングの代わりに「再構築」→「ファイルを生成」せずにDBに保存するもの。実際のBlog運用にメリットは殆どないが(DBの状況では再構築が軽い、というメリットはあるが)、サイト制作の過程で余計なファイルを生成しないで納品時に一気にファイルを書き出す(当然ゴミもない)という用途を想定している。
<MTEntries sort_by="keywords" sort_order="ascend">
<$MTEntryTitle$>
</MTEntries>
ってのは効くのか? マニュアルには excerpt には対応しているとの記述があるが...
ということで MT::Template::ContextHandlers.pm の _hdlr_entries を眺めていたのだが、excerpt は良くて keywords が駄目ってところがどう見てもないような...じゃぁ、Entry.pm で excerpt だけインデックス指定されているのかというと...そうでもない。
じゃぁ、やっちまえってことで実験。効いているようだ。
何故 excerpt じゃだめかというと、ちゃんと理由があるのだ。
並べ替えのキーになるフィールドにコメントタグを入れて番号を振っていこうとしていたんだけど問題が2つあって1つめは「<$MTEntryTrackbackData$>」の中に 「dc:description=excerpt」という感じで概要が入るのだが、これに並べ替えのためのコメントが入ってしまう点。もう一つはエントリーからトラックバックを送る際に、こちら側のエントリーの概要がコメントタグになってしまう点。
title でも同じことがひっかかるし、日付は日付でちゃんと持っていたいし...ということで上述のように keywords でやってみたらちゃんと並べ替えられるじゃないか...というヲチ。
キーワードで並べ替えられるのはいいとして、今度はこの並べ替えのためのコメントが出力されなくなるようにする方法について。
まず、思いつくのはグローバルフィルターを書いてちょん切ってやるやり方。
以下のようなプラグインを書いて remove_num="1" を追加する。
# <$MTEntryKeywords remove_num="1"$>
# ex : <!--item-sort-0001--> => ""
MT::Template::Context->add_global_filter(remove_num => ¥&_Remove_Num);
sub _Remove_Num {
my $text = shift;
my $beg = quotemeta('<!--item-sort-');
my $end = quotemeta('-->');
$text =~ s/^$beg[0-9]{4}?$end//;
return $text;
}
ところが、「テンプレート書き換えるのが面倒」みたいな話が出て来て(どこでだよ?)、以下のようにすると優先されるようだ。でも思いっきり非推薦、自己責任というか、こういうノウハウって開発元の方には嫌がられるだろうから、あくまでもメモです。良い子は真似しないこと。
MT::Template::Context->add_tag(EntryKeywords => ¥&_Alt_EntryKeywords);
今まではよく分かっていなくて Transformer プラグイン でテンプレートが呼び出される時に無理矢理違う動作をさせていたんだけど「オリジナルの__mode が追加できる」とリファレンスにもあるのでやってみた。
sub init_app {
my $plugin = shift;
my ($app) = @_;
return unless $app->isa('MT::App::CMS');
$app->add_methods(itemsort => ¥&_Itemsort);
}
として、mt.cgi?__mode=itemsort&blog_id=1 とかすると、あら動いたわ。
ついでに、既に指定されている __mode をプラグインで指定してやるとどうか。
$app->add_methods(ping => ¥&_Alt_Ping);
上書き(プラグインで書いたものが優先)されるようだ。これも嫌がられる感じがするので良い子は真似しないこと(<=もうええわ!)。
mt-config.cgiに LaunchBackgroundTasks 1と指定すると、エントリーを保存する際の再構築処理がバックグラウンドで行われるようになる。(正直知らなかった。何せユーザー歴がまだ半年なので...)。
MovableType Background Rebuilder Plugin(1.0RC2)でも同じようにエントリーの保存時に再構築処理をバックグラウンドで行っている。思いっきり車輪の再発明...かと思って試していたら、この間自分がつまづいたところと同じところでご本家さんもつまづいてるようだ(同じ事象が発生する)。ということで以下の問題があるようなのでご注意を。
問題はエントリーのカテゴリー変更時に起こる。
エントリーアーカイブのアーカイブマッピングにカテゴリーのbasenameが含まれている時(例:%-c/%f)に、エントリーのプライマリーカテゴリーを変更すると保存場所も変わるのが正しい振る舞いなのだが、LaunchBackgroundTasks 1を指定しているとカテゴリー変更前の保存場所に保存されてしまう。
※もう一度保存し直すと正しい場所に保存される。
また、このブログのようにエントリーカテゴリーをエントリーのページに表示させている場合(エントリーの右下あたり)はカテゴリー名も変更前の古いままになってしまう。
※何故か「複数のカテゴリーを指定」している場合には発生しない。
実はBackground Rebuilder Plugin を書いている時にも同じ現象が発生した。
CMSPreSave_entry, CMSPostSave.entry のタイミングで $entry->permalinkや$entry->categoryを取得しようとしても古いパスやカテゴリーが帰ってきてしまう。$entry->title等は正常に取得できる。
MT::Placementを保存した後でもうまく反映されなかった。$entry->permalinkや$entry->categoryはmt_entryテーブルの項目ではなくメソッドなのでCMSPreSave_entryやCMSPostSave.entryコールバックが呼ばれるタイミングでは既に元のentryのcategory情報や保存先を指定してしまった後だからなんだろうか(未確認)。
結局 Background Rebuilder Plugin では「カテゴリー情報が変わっていたら旧ファイル削除(CMSPreSave_entry)→カテゴリー情報保存→(必要なら)トラックバック送信(CMSPostSave.entry)→保存結果表示テンプレートが呼び出された(MT::App::CMS::AppTemplateSource.edit_entry)」タイミングで再構築を実行、というように修正して解決した。
LaunchBackgroundTasks 1 を指定している方はエントリーのカテゴリー情報変更→保存の際にはご注意を。
Background Rebuilder Plugin では、この問題と旧ファイルが残ってしまう問題を解決しています。
「CMSと静的ファイル管理に関する考察の(更に)続き。」のさらに続き。
具体的なファイルマネージャーの実装を考えてみる。前回のMT::Contentを少し修正。ファイルの属するディレクトリやファイルに対応するオブジェクトのIDを保存出来るようにした。
その上で、まずは簡単なファイルブラウザをつくってみた。
package MT::Content;
use strict;
use MT::Object;
@MT::Content::ISA = qw( MT::Object );
__PACKAGE__->install_properties({
columns => [
'id', 'file', 'directory', 'content',
'archive_type', 'object_id',
'modified', 'size', 'blog_id',
'digest', 'status',
],
indexes => {
id => 1,
file => 1,
directory => 1,
archive_type => 1,
object_id => 1,
modified => 1,
blog_id => 1,
status => 1,
},
datasource => 'content',
});
| フィールド名 | タイプ | 説明 |
|---|---|---|
| id | integer (primary key,auto_incriment) | 一意なID |
| file | text | ファイルのパス |
| directory | text | ファイルの置かれているディレクトリ |
| content | text | 書き出される(べき)HTML |
| archive_type | text | アーカイブの種類 |
| object_id | integer | アーカイブの種類がIndividualの場合entry_id, Categoryの場合category_id |
| modified | integer | 書き出し時刻(Unixtime) |
| size | integer | ファイルサイズ |
| blog_id | integer | ブログのID |
| digest | text | textの要約(digest) |
| status | integer | ステータス |
MTから出力されるファイルはこれで管理できるようになる。 続いてはアップロードファイルの管理を考えてみよう。
基本はMT::Contentと同じだが、ファイルのメタ情報(画像の場合はalt、その他のキーワード)を一緒に保存して、キーワードで検索できたりページに貼付ける時にaltが自動で入ったりできるようにしたり、あるいはよく使う画像にフラグを付けて呼び出しやすくしたりすることを想定しておく。
データベースへの登録のタイミングはCMSUploadFile, あるいはCMSUploadImageコールバックが呼び出された時。altやkeywoedsはアップロード系の管理画面をカスタマイズして登録できるようにする。
管理用テーブルはMT::Contentとは若干構成が違うので別のものを作ることにする。
package MT::UploadItem;
use strict;
use MT::Object;
@MT::UploadItem::ISA = qw( MT::Object );
__PACKAGE__->install_properties({
columns => [
'id', 'file', 'directory', 'alt',
'html', 'keywords', 'type', 'image_type',
'modified', 'size', 'blog_id',
'width', 'height', 'bookmark',
],
indexes => {
id => 1,
file => 1,
directory => 1,
type => 1,
modified => 1,
size => 1,
blog_id => 1,
bookmark => 1,
},
datasource => 'uploaditem',
});
| フィールド名 | タイプ | 説明 |
|---|---|---|
| id | integer (primary key,auto_incriment) | 一意なID |
| file | text | ファイルのパス |
| directory | text | ファイルの置かれているディレクトリ |
| alt | text | 画像の場合はalt属性, その他のファイルの場合はリンクテキストにあたる文字列 |
| keywords | text | 検索, 分類用のキーワード |
| type | text | image, file またはthumbnailのいずれか |
| image | text | GIF, JPG またはPNGのいずれか |
| size | integer | ファイルサイズ |
| blog_id | integer | ブログのID |
| width | integer | (画像の場合)幅(pixel) |
| height | integer | (画像の場合)高さ(pixel) |
| bookmark | integer | (何らかの)フラグ, 例えばよく使う画像等を識別するのに使う |
続いてCMSUploadFile, あるいはCMSUploadImageコールバックに対応するコードを書く...のは次回以降に。
ここら辺に呼応して。
Googleのクローラーがどうかは調べていないけれど。
負荷云々の前に、まずきちんとHTTP_HEADERで攻防をしてから。
| 200 | OK | リクエストは正常に処理された! |
| 301 | Moved Permanently | ページは移動したよ! |
| 302 | Found | 今のところページは移動してるよ! |
| 304 | Not Modified | リクエストされたページは更新されてないからキャッシュを使いな! |
| 401 | Unauthorized | 認証されてない! |
| 402 | Payment Required | 金払え! |
| 403 | Forbidden | 許可されないもんね! |
| 404 | File Not Found | ごめん, 見つかんないよ! |
| 500 | Internal Server Error | ごめん,俺が下手なせいで(Joke!)エラーなっちまったよ! |
| 503 | Service Unavailable | ごめん,今はだめだ! |
主立ったところはこんな感じ。
例えば(多分あまり意識していないであろう)Basic認証におけるHTTP_HEADERのやりとりは、
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="パスワード入れてね!"
(CRLF+CRLF)
というレスポンスをサーバが返し、クライアントは(ID,Passwdを求めるウィンドウを出してから)再度リクエストを送り返す。
GET /basic_auth.html HTTP/1.1
Authorization: Basic (base64エンコードされたID,Passwd情報)...
つまり、これもHTTP_Headerのやりとりなのだ。もちろんCookieだってそうだ。
このしくみを理解していればBasic認証を自前で実装することもできる。ID,PasswordをDBと照合してアクセスを許可することも可能。但し、Apache+Perl CGIの環境ではこの部分のHEADER情報が(おそらく)セキュリティの面(だったと思う)で環境変数から受け取ることができない(少なくとも昔はできなかった)ので無理。PHPであれば
$_SERVER['PHP_AUTH_USER']
$_SERVER['PHP_AUTH_PW']
とかで何でもできる。善し悪しは置いておいて。
また、バーチャルホストだって普通に使っているかもしれないが、
Host: junnama.alfasado.net
ってなヘッダー情報を送ってくるからApacheが適切に解釈できるわけで、ブラウザがHostヘッダを送ってこなければApacheはデフォルトサイトのデータを返すのだ(大昔はブラウザはこんな情報送ってこなかったからバーチャルホストってのもなかった)。
本題? に戻る。負荷軽減のポイントは
の2点か。
まともな? ロボットならまずは※HEADリクエストを送ってHTTP_HEADERをチェックするだろう。
※追記:
良く考えたら後述のConditionalGETが適切に機能するならばHEADリクエストである必要はないなぁ。
HEAD /index.html HTTP/1.1
If-Modified-Since: Mar, 3 Thu 2007 01:00:00 GMT
if_none_match: 23ab7f6be333b3df07b4df3de61a92b9
ページが更新されていないならば、HTTP_HEAD情報のみを返せば良い。
まともな? クローラーならこれで次のリクエストは送ってこない(と思う)。
HTTP/1.1 200 OK
Last-Modified: Mar, 3 Thu 2007 01:00:00 GMT
Content-type: text/html
Content-length: 950
Etag: 23ab7f6be333b3df07b4df3de61a92b9
あるいは(後述するが)
HTTP/1.1 304 Not Modified
Last-Modified: Mar, 3 Thu 2007 01:00:00 GMT
Content-type: text/html
Content-length: 950
Etag: 23ab7f6be333b3df07b4df3de61a92b9
さて、HEADリクエストで求める答えが得られなかったクライアントはConditional GET(条件付きGET)でリクエストを送ってくる(と考える)。
GET /index.html HTTP/1.1
If-Modified-Since: Mar, 3 Thu 2007 01:00:00 GMT
if_none_match: 23ab7f6be333b3df07b4df3de61a92b9
サーバー側の処理は
If-Modified-Since ヘッダで送られて来たタイムスタンプよりも送り返すべきデータの更新日時が新しい場合、あるいは以前送り返したEtagと値が変わっていた場合のみ
HTTP/1.1 200 OK
...HTTP_HEAD情報+CRLF+CRLF+コンテンツの内容
を送り返せば良い。
もしも更新されていなかったなら、ステータス304+返すべきHTTP_HEADER情報のみを送り返せば良い。そうすれば(それがブラウザであるならば)ブラウザのキャッシュが表示される。
HTTP/1.1 304 Not Modified
Last-Modified: Mar, 3 Thu 2007 01:00:00 GMT
Content-type: text/html
Content-length: 950
Etag: 23ab7f6be333b3df07b4df3de61a92b9
まともなクローラーであればこういう振る舞いをすべきだと思う(繰り返すがGoogleのクローラーがどうかは調べていないけれど)。
少なくとも503を返す前にやるべきことをやってから考えるべきではないだろうか?
複数ユーザーからの投稿を可能にした。
後は複数ブログに対応したメニューページと管理画面、エントリーのタグ付けに対応させたらバージョン1完成としましょう。
#最近携帯のバッテリーの持ちが悪いや。
コマンドラインからSQLiteを扱う際のメモ。
sqlite3 ./sql.db
create table new_table(
id INTEGER PRIMARY KEY,
uri text,
content text,
modified integer,
);
drop table new_table;
vacuum new_table;
.table
.schema new_table
select * from new_table;
ファイル単位でバックアップが取れること、パブリックドメインであることが何よりのメリット。
一方で、カラムの型定義が厳密でない(らしい)ので、若干オーバーヘッドが気になるが、これまで使用した感覚ではパフォーマンスは中々良いと思う。