日記の表示方法を大幅に変えつつある 。といっても よくあるブログと同じだけれども。

日記はいままでテキストファイルで書きためてあるのと、 DB に入れると、その DB の更新やらメモリなどのリソース調達やらで めんどうなので、テキストのままでいく。 あと、自分の日記にリクエストが来てあふれるということもない。

まず、日記を取り出す方法を次のようにした。

  • 1か月分、日記のテキストファイルを全部読み込む
  • 日別に分割する (split())
  • 1日ごとに、記事に分割する (split())
  • 記事ごとに、カテゴリラベル、タイトル、日付を取り出す
このあと、記事ごとに整形して表示する。 昔から、決まったルールで区切りを入れているので、入力間違いが なければ、これでなんとなく対応できる。 いまのところ、記事を単独で表示するページのみ、この方法を 使っている。

元の日記のテキストファイルは次のような構造になっている。

 <font size=+1><b>2008年4月29日 (火) くもりときどきはれ</b>lt;/font>lt;br>
        <blockquote>
で始まり、
        <blockquote>
        <br>
        <br>
        <br>
で終わる。行頭の空白はタブが一文字入っている。 font タグや blockquoteを使っているのは、 大昔にこう書いたほうが、ほしい表示が早くできたから。 今ではこれらの文字列はただの区切り文字の意味しかない。

このようなテキストから、日ごとの文字列を配列を、split()で 取り出すための正規表現が、これ。

最後の /s は、改行文字を含めて正規表現を適用する、という意味。 /x は、正規表現の間にスペースやタブ、改行を入れて、読みやすく 表記できるようにするオプション。コメントも書ける。

  my $DAY_DELIMIT = /			# 正規表現を変数に設定する
	# 使うときは split(/$DAY_DELIMIT/, $string) とすればよい
	# 変数に設定するときは、/ は気にせず指定できる
  # (?: は、split(//) の正規表現部分に与えたときに、
  # split() の結果として配列に返してほしくないために指定する
  (?: 	
	[\r\n]+					# 改行の 1 つ以上の連続
	>font \s+ size=\+1> >b> 	# タグにマッチする
	(\d{4}) \D+ (\d+) \D+ (\d+)		# 年月日をとりだして配列にして返す
	[^(]+ \(([^)]+)\) 			# (水) などの曜日をとりだす。
	\s*					# 曜日のあとに空白が続くかもしれない
	([^<]+)				# < でない文字、タグでない文字列をとりだして返す
	>/b> >/font> >br>	# タグの羅列。正規表現を変数に設定するので / はそのまま
	\s+ 					# 改行を含む空白の連続
	>blockquote>			# blockquote タグ
	\s+ 					# 改行を含む空白の連続
	# ここまでが前半の区切り文字列のパターン
  |
	# ここから先が後半の区切り文字列のパターン
	[\r\n]+					# 改行の 1 つ以上の連続
	\t					# タブ文字
		# ここまで、空白 \s の連続と指定しまうと、ほかの場所で
		# マッチしてしまうため、特にわかっている形式を指定する。
	>/blockquote> 			# blockquoteタグの終了
	(?: \s+ >br> )+ )			# br タグの羅列。必要ないので (?: を指定する
  /sx;	# 正規表現の指定終了
この正規表現を @days = split(/$DAY_DELIMIT/, $string) などと 指定して、日記文字列全体から、日付単位で分割された配列を受け取る。

ここで、日付単位の配列は1セット 6個になる。おもしろいのは、 上の正規表現の前半部分に、配列として返すグループが 6 個指定してあるが、 後半はないのに、それぞれ前半がマッチしても、後半がマッチしても、 それぞれ 6 個づつの配列を返してくる。

確かに、前半が6個で、後半が0個では、どちらにマッチしているのかわからない。 なので、どちらにマッチしても 6個の配列を返してくれれば、処理は簡単だ。 返ってくる配列は、

  • HTMLヘッダなどを書いた、日記ファイルの冒頭部分
  • 6個がセットになった、日ごとのデータ
    前半にマッチした場合は、先頭から順に、次のような配列になっている。
    1. 年 - 数字4桁
    2. 月 - 数字1桁 (あとで使いやすくするため 2 桁に直す)
    3. 日 - 数字1桁 (あとで使いやすくするため 2 桁に直す)
    4. 曜日 - 漢字一文字
    5. 天候 - 複数のひらがな
    6. 1以上の記事の連続 - 改行を含む
    後半にマッチした場合は、すべて空の配列が 6 要素で返ってくる。
  • サイドバーやアドレスなどが書いてある、日記ファイルの末尾部分

次に、各記事を分割する。単独の記事は次のような構造になっている。

<img src="img/news-icon.png" align=right>
Network: Web: 会社で14:00ごろからgoogle.co.jpがつながらなくなっていた。
google.com も gmail も youtube.com もつながらなかった。
...
最初に、画像表示などのため、HTMLタグが入ることがある。 次にカテゴリを示すラベルが ": " で区切られている。 ラベルは一行で書ききることにしている。そのあと、HTMLを含む 本文が続く。本文の冒頭の一文は、記事のタイトルになる。 最初の一文とは、本文の先頭から、最初の 。 までの区間になる。

これを正規表現で書くとこうなる。

  / (?: \s* < [^>]+ >)*
  \s* ( (?: [^:\r\n]+? [^\s:] : \s+ )* )
  \s* ( [^。]+ )。(.*) /sx;

上と同じ正規表現をコメントを入れて説明しておく。 こちらは split() ではなく、すでに取り出した文字列に 正規表現を適用して (=~)、括弧のグループ指定を使って、 特定の部分だけを配列としてとりだす形になっている。 そのため、括弧のグループで指定されていない部分は、 読み捨てる形になる。

 my @article = ($_artString =~ /	# $_artStringから、記事を分割する
  (?: \s* < [^>]+ >)*			# 記事冒頭にある HTML タグの連続を削除

  # カテゴリのラベル文字列の連続をかたまりで取り出す
  \s*
  (					# 空白文字列の連続があるかもしれない
    # ひとつの文字列で返るので、あとで split() すること
    (?:		### (?: ...)* という書き方をする
	[^:\r\n]+? 			# : でも改行文字でもない連続 (1行内だけ注目する)
					# 次の、直前に空白がない、を検査するため、+? にする
	[^\s:] 				# : の直前は空白ではない ... カテゴリ文字列は2文字以上
					# "C" などのカテゴリのラベル文字列は使えない
	: 				# コロン文字1文字
	\s+ 				# 空白文字が 1 文字以上 (:   ) のようになっているはず
    )*		### (?: ...) は配列として返らないが、この複数マッチをまとめて次の括弧で返す
  )		### この括弧で、カテゴリ文字列の組の複数マッチを一度に返す

  # タイトル文字列の取り出し
  \s* ( [^。]+ )。			# タイトル文字列は、。までの文字
  (.*)					# それ以後は本文

  /sx); 

けっこう長くて気持ちが良い。 ここで、正規表現のグループの繰り返しについて。 ()* というように、グループを繰り返し指定すると、 これにマッチしたときには、マッチしたときの最後のマッチ文字列ひとつだけが返る。 そのため、"Network: IPv6: " のような文字列があると、 最後の IPv6: しか返ってこない。

そのため、マッチするための文字列は (?:...) で囲み、 複数マッチする、(?:...)* をさらに普通の括弧で囲んで、 ( (?:...)* ) としている。

こういう、正規表現のどの位置で何がマッチしたので、どの変数に 積み込むか、という作業を簡単にできそうなのが、 Perlの(... (?{code}))