PR
リスト1●記事投稿のServlet(呼び出し時)<BR>呼び出した時は,テンプレートを使って画面を作成する。このとき,idが指定されていればその記事を呼び出しておく。指定されていなければ新規記事として処理する。新規記事であればタイトルなどは空になっているので,そのまま文字列を置き換えればよい。
リスト1●記事投稿のServlet(呼び出し時)<BR>呼び出した時は,テンプレートを使って画面を作成する。このとき,idが指定されていればその記事を呼び出しておく。指定されていなければ新規記事として処理する。新規記事であればタイトルなどは空になっているので,そのまま文字列を置き換えればよい。
[画像のクリックで拡大表示]
画面1●記事投稿画面
画面1●記事投稿画面
[画像のクリックで拡大表示]
リスト2●記事投稿のServlet(登録時)&lt;BR&gt;「登録」ボタンを押すと,POSTメソッドでこのクラスが呼び出される。テンプレート上は多くのブログツールにあるように「確認」ボタンもあるが,実装していない。新しいEntryクラスのオブジェクトを作って,そこに登録された要素を入れて保存させている。
リスト2●記事投稿のServlet(登録時)<BR>「登録」ボタンを押すと,POSTメソッドでこのクラスが呼び出される。テンプレート上は多くのブログツールにあるように「確認」ボタンもあるが,実装していない。新しいEntryクラスのオブジェクトを作って,そこに登録された要素を入れて保存させている。
[画像のクリックで拡大表示]
リスト3●個別記事を表示するServlet&lt;BR&gt;指定した記事番号に応じてEntryクラスのオブジェクトをロードし,それに基づいてデータを置き換えている。これで個別記事の表示が可能になった。idの指定がない場合の処理も一応入れてある。ただこのエラー処理は不完全で,idに該当する記事がなかった場合の処理が入っていない。
リスト3●個別記事を表示するServlet<BR>指定した記事番号に応じてEntryクラスのオブジェクトをロードし,それに基づいてデータを置き換えている。これで個別記事の表示が可能になった。idの指定がない場合の処理も一応入れてある。ただこのエラー処理は不完全で,idに該当する記事がなかった場合の処理が入っていない。
[画像のクリックで拡大表示]
リスト4●インデックスの操作&lt;BR&gt;日付,タイトル,IDが一組のデータとなる。これを日付順に並べた配列をインデックスとした。
リスト4●インデックスの操作<BR>日付,タイトル,IDが一組のデータとなる。これを日付順に並べた配列をインデックスとした。
[画像のクリックで拡大表示]
リスト5●複数記事表示のServlet&lt;BR&gt;インデックスから最近の記事を必要な個数(ここでは10個でハードコードした)取り出している。HTMLの生成は別のモジュールに分けている。
リスト5●複数記事表示のServlet<BR>インデックスから最近の記事を必要な個数(ここでは10個でハードコードした)取り出している。HTMLの生成は別のモジュールに分けている。
[画像のクリックで拡大表示]
リスト6●複数記事を表示&lt;BR&gt;繰り返し部分とそうでない部分を別々のテンプレートに定義してある。後者から繰り返しの前と後を切り出し,繰り返し部分を間に挿入してHT ML文書を生成している。
リスト6●複数記事を表示<BR>繰り返し部分とそうでない部分を別々のテンプレートに定義してある。後者から繰り返しの前と後を切り出し,繰り返し部分を間に挿入してHT ML文書を生成している。
[画像のクリックで拡大表示]
リスト7●テンプレートからキーワードを抽出&lt;BR&gt;正規表現を利用してキーワードを取り出している。キーワードに対応する処理は,Rubyの動的にプログラムを実行する機能を利用して実現した。
リスト7●テンプレートからキーワードを抽出<BR>正規表現を利用してキーワードを取り出している。キーワードに対応する処理は,Rubyの動的にプログラムを実行する機能を利用して実現した。
[画像のクリックで拡大表示]
図●テンプレート・エンジンの役割&lt;BR&gt;Blogシステムにおいては,さまざまな場面でテンプレート・エンジンを利用できる。逆に言えば,Blogシステムの中核はテンプレート・エンジンだとも言える。
図●テンプレート・エンジンの役割<BR>Blogシステムにおいては,さまざまな場面でテンプレート・エンジンを利用できる。逆に言えば,Blogシステムの中核はテンプレート・エンジンだとも言える。
[画像のクリックで拡大表示]
画面2●複数表示画面&lt;BR&gt;Blogのトップページとしてよくある複数ページの表示である。画面のデザインはライブドアBlogにある「blueframe」を使った。
画面2●複数表示画面<BR>Blogのトップページとしてよくある複数ページの表示である。画面のデザインはライブドアBlogにある「blueframe」を使った。
[画像のクリックで拡大表示]
リスト8●RSS(RDF Site Summary)配信のためのテンプレート&lt;BR&gt;RDF(Resource Description Format)に基づいて,配信する内容を適用できるように作ればよい。
リスト8●RSS(RDF Site Summary)配信のためのテンプレート<BR>RDF(Resource Description Format)に基づいて,配信する内容を適用できるように作ればよい。
[画像のクリックで拡大表示]
リスト9●テンプレート・エンジンを見直し&lt;BR&gt;これまでループが1個と固定で指定していたものを,複数回発生しても対処できるように見直した。
リスト9●テンプレート・エンジンを見直し<BR>これまでループが1個と固定で指定していたものを,複数回発生しても対処できるように見直した。
[画像のクリックで拡大表示]
リスト10●RSSを配信するサーブレット&lt;BR&gt;本来はFileServletを使うべきかもしれないが,一度やってうまくいった方法を再度用いた。
リスト10●RSSを配信するサーブレット<BR>本来はFileServletを使うべきかもしれないが,一度やってうまくいった方法を再度用いた。
[画像のクリックで拡大表示]

 ブログ作成の第2回。前回作ったものは,テンプレートの処理がハード・コーディングしてあるなど,いかにもその場限りで適当に作ったようなものだった。表示形式を増やすにあたり,テンプレートの処理を拡張可能な形に変更。言わば本格的なブログとして作り直した形だ。テンプレート・エンジンと呼ぶほどではないが,ブログの表示だけでなく管理やRSS(RDF Site Summary)配信など多くの場面で使えるものにした。

 まずは前回積み残した,記事を登録・編集するServletを見ておこう。編集画面はGET呼び出しなので,対応するdo_GETメソッドでIDを受け取る。IDの指定があれば該当エントリをロードし,なければ空のエントリを作って,テンプレートに載せて表示する(リスト1[拡大表示],画面1[拡大表示])。クエリー文字列と呼ばれるものなので,メソッド「query」で取り出すようになっている。

 次にPOST呼び出しの処理で,指定通りに渡された値を取り出してEntryクラスのオブジェクトを組み立て,保存する(リスト2[拡大表示])。当初なかなか正しくデータが更新されず悩んだが,実は条件判定で「==」を使うべきところに「=」を使うという,まるでC言語初心者のようなつまらないミスをしていたのだった。正直へこんだが,その割りに同じミスを何度か繰り返してしまって情けない気分になった。

 最後に表示部分もidによって識別するように変更する。idが正しく指定されていれば記事をロードし,テンプレートに混ぜ合わせるようServletを更新する(リスト3[拡大表示])。このあたりは記事の登録・編集と同じ仕組みなのでごく簡単である。

 ここまでで,個別の記事を表示する部分は一応完成し,記事の登録・編集もできるようになった。しかし,いかにも付け焼き刃というか,拡張性に乏しい(プログラマ道場風に言えば,いかにも無明である)。一覧表示をするためにはどうすべきか。ブログ(Weblog)らしくコメントを付けたりするにはどうしたらよいか。本体の記事以外に,さまざまなナビゲーションを付けられるようにするにはどうしたらよいか。新しく何か表示したい要素が出てきたらどう対処するか。ある意味,「引き返すなら今」ではないかと思い始めていた。

第3ステップ
複数記事を取り出す仕組み

 記事の入力・編集が可能になり,個別の記事が正しく表示できるようになったところで,次に実装すべきはトップページやアーカイブなどで出てくる記事の一覧を表示するビューである。ブログでは多くの場合,記事は日付順に新しいものから並ぶ。IDは作った順に増えていくので,ID順でOKかとも思ったが,当然登録した日付は変更可能なように作ってあるので,それではダメだということに気が付いた。

 つまり一覧系の記事表示のために,記事を並べる順番を表現するインデックスのようなものが必要ということだ。これがDBMSを使っていれば,表示のたびに記事群をソートして選択してもよいのかもしれないが,今回はDBMSを使わないので個々にインデックスを作ってやる必要がある。まずは最低限必要な日付順のインデックスを作ることを考えよう。

 この要件から,必要なのは登録日付とIDを組みとして,登録日付順に並べること。Rubyでは配列データがソート可能なので,これを使えばインデックスを簡単に作ることができそうだ。同じようにして,全体のインデックスのほか,月別のインデックスや日別のインデックス,カテゴリ別のインデックスなどを持てばよいだろう。後々の利便性を考え,タイトルもインデックスに登録するようにした。例えば「Recent Entries」みたいな表示をする場合があるだろうと思ったからだ。

 ただ悩んだのは,インデックスをどこで更新するかだ。Entryオブジェクトをセーブするときか,それとも記事を登録するServletでやるべきか。インデックスの更新はEntryに関するものというより,システムレベルに近いと考えたのでServletに更新の指示を持たせるようにした。

 次の問題は記事更新時にどうやってインデックスを変えるかだ。記事を追加する時点でインデックス情報を更新するのは簡単だが,更新時は単純に実行するにしても一度前の情報を消す作業が必要となる。Rubyでは配列データから同じ値の要素を取り除く機能があるので,これを使えば「除いて,加える」ことができる。問題は「前の状態」をどうやって保持するかだ。ただ考えてみれば更新するときはdo_GETで前のデータを表示するし,ここでインデックスから取り除いて,do_POSTでインデックスを追加すればよさそうだ(これは後に大間違いであることに気づいたが)。

 インデックスの操作は追加と除去なので,メソッドを外から呼び出すだけだ。これを実現しやすいようにモジュールとして定義した(リスト4[拡大表示])。

第4ステップ
複数記事を表示するトップページ

 表示画面となるServletの実装にとりかかる。このServletの問題点は,まず繰り返す部分があること。面倒そうなので,この部分とほかの部分をテンプレートとして別に使うことにする。また,参考にしているライブドアBlogのテンプレートを見ると,「<IFxxx>~</IFxxx>」というタグがある。xxxの条件が成り立つときに表示する,といった処理だ。これには一種のインタプリタを実装するようなものだと思ったので,とりあえずこの時点では先送りにした。例えばXMLを解釈するエンジンが必要なのではないだろうかと,この時点では思っていた。

 まずは条件にかなう記事群をインデックスを参照して取り出し,配列に入れる。この配列に対してデータを埋め込む処理を実施する(リスト5[拡大表示])。Servletの肥大化を避けるため,データの埋め込み処理は別のモジュール(ContentRender)として切り分けた。

 ループの部分を別にしたとはいえ,全体のテンプレートからまずループの入る場所を識別しなければならない。このとき役に立ったのが正規表現に基づく文字列マッチングの機能である。マッチングした結果,それより前の部分を「$`」,該当した部分が「$&」,それより後の部分を「$'」で取り出せる。これを使ってテンプレートを二つに分割した(リスト6[拡大表示])。

 そして,適当にテンプレートに従って記事の内容を反映していけばよい。とりあえず妙なタグがあってもWebブラウザが無視してくれるのでまあよいかとも思ったが,やはり気に入らない。

 ここで,このマッチング機能を使えば「<$xxx$>」や「<IFxxx>」などのxxxの部分を取り出せることに気が付いた。xxxの部分さえ取り出せれば,その文字列に応じた処理を記述すればよい。幸いRubyには,文字列を評価して実行する機能(eval)があるので,これを呼び出してやれば簡単に記述できることに気が付いた。ループの部分に呼び出す個所があるので,記事を常に引き渡す形になる。ループの前では空の記事を渡せばよいだけの話だ。

 もう少し具体的に見ていこう。例えば「<$xxx$>」を取り出すには,「<\$[a-zA-Z]+\$>」と比較すればよい(リスト7[拡大表示])。$は特殊文字になるかもしれないので,エスケープ文字「\」を置いておく。xxxの部分は英文字しか来ないと考えてよいので,[a-zA-Z]でマッチさせる。それが1文字以上続く,ということだ。これで「<$xxx$>」を取り出し,ほかと分けることができる。

 該当する部分を受け取ったら,今度はその文字列に基づいて解釈することになる。前後2文字ずつを取り除いて,すべて小文字にしてやる。Rubyでは大文字で始まる名前は定数になるため,メソッド名は小文字で始まるからだ。先頭だけを小文字にする適当な方法がすぐに見つからなかったため,これでよしとした。

 そして記事の内容を保持した変数を引数として,その値を評価するメソッドを呼び出す。ここではFormatSupportという別のクラスを定義していて,xxxと名前が一致するメソッドを呼び出している。この処理を順次適用していき,マッチするものがなくなるまで続ける。

 「<IFxxx>」の処理も基本は同じである。違うのはxxxを評価した結果,成立したら値を返すのではなく「<IFxxx>」と「</IFxxx>」で囲まれた部分を返し,成立しなければ空の文字列を返すという点だ。

 そして,<IFxxx>に対応する処理を先に実行して,そこで該当するテンプレートだけに絞り込み,<$xxx$>で記事内容を反映するという手順にすればよい。このような解釈を実装することにより,一種のテンプレート・エンジンとして動くようになった。例えば「<$Article Title$>」というタグに対する操作がシステム全体で共通化できる。さらに,新しい何かを表示させたければ,テンプレートに「<$SomethingNew$>」と記述し,それに対応する表示処理を記述すれば,テンプレートを呼び出す場所を気にせずに実装可能と言うことになる。ある意味,ブログの本質はここに集約されているといっても過言ではない([拡大表示])。

 意外とこの解釈部分は簡単に作ることができた(画面2[拡大表示])。個々の記事のデータの埋め込みもこのエンジン部分を使うように変更した。

第5ステップ
RSSを配信する

 骨格としてはほぼ完成したようなものだが,まだまだブログとしては物足りないのも事実だ。基本機能として欲しいのは,RSS(RDF Site Summary)の配信とコメント,トラックバック。それに画像の表示・登録もできるようにしたい。あと,さすがにサイドバー表示がまったくないというのも寂しい感じがする。

 この中で最も簡単そうなものが,RSSの配信だ。自分自身のサイトの情報を,RSSのフォーマットに従って作成し,それをサイトのどこかに置くというのが基本である。したがってやるべきことは,第4ステップで作ったテンプレート処理と同じである。対象がHTMLではなく,RDF(Resource Description Format)になるだけだ(リスト8[拡大表示])。

 だが,RDFを調べてみたらループになる場所が2カ所あった。現在の作りでは対応できない。また第4ステップの時点のテンプレート処理では,適用するテンプレートが決め打ちである。せっかくここまで「エンジン」らしくしたのだから,汎用的に使えるように拡張したい。

 これまでテンプレートの適用などを実施していたContentRenderの部分は,どのテンプレートを使うかを指定するだけにする。そしてテンプレートを適用するクラスとしてTemplateMainを定義し,そのメソッド「processTemplate」で処理するようにした(リスト9[拡大表示])。処理の方針としては,<IFxxx>や<$xxx$>の処理と同じように,「<!-- Loop Start -->」の文字を扱えばよい。

 もう一つやるべきことは,その作成したRDF形式のファイルを返すためのServletを一つ実装することである。前回CSSファイルを配信したものと同じ構造にした。ファイルそのものを返す「FileServlet」がWebrickにはあるので,本来これを使えばよいはずだが,どちらかというとフォルダを対象としているのかとも思ったので今回は使わないことにした。単にテキスト・ファイルの内容を返して,それに適切な属性を与えればよいのでそれほど難しくはない(リスト10[拡大表示])。RDFファイルの生成は記事の登録・更新が完了した時に実行すればよい。

 次の最終回はコメントとトラックバックという,ブログの特徴的な機能に挑戦する。