Diary?::2007-01-26

(01:07) 相変わらず檜山さんが神懸ってる件について書きたいが

もうこんな時間。読むの遅いよ、俺。 TODO: 明日中に「圏論やモナドが、どうして文書処理やXMLと関係するのですか?」の課題を全部やること。とりあえず 1 と 2 はやっつけだけど書いた。

def check_nest(string):
  escaped = False
  brackets = 0
  for v in string:
    if v == '\\':
      escaped = not escaped
    elif v == '{' and not escaped:
      brackets += 1
    elif v == '}' and not escaped:
      brackets -= 1
    elif escaped:
      escaped = not escaped
  if brackets != 0:
    print string + " is not well-formed."
    return False
  else:
    print string + " is well-formed."
    return True

def delete_bracket(string):
  if not check_nest(string):
    print "Not well-formed text"
    return ""
  escaped = False
  brackets = 0
  deleted = set()
  for i, v in enumerate(string):
    if v == '\\':
      escaped = not escaped
    elif v == '{' and not escaped:
      deleted.add(i)
    elif v == '}' and not escaped:
      deleted.add(i)
    elif escaped:
      escaped = not escaped
  return string + " => " + ''.join([v for i, v in enumerate(string) if i not in deleted])

で、実行例。

>>> wellformed = 'aaa{bbb}ccc{ddd}eee'
>>> not_wellformed = 'aaa{bbb}ccc{dddeee'
>>> print delete_bracket(wellformed)
>>> print delete_bracket(not_wellformed)
aaa{bbb}ccc{ddd}eee is well-formed.
aaa{bbb}ccc{ddd}eee => aaabbbcccdddeee
aaa{bbb}ccc{dddeee is not well-formed.

あ、 bracket じゃなくて curly bracket だった。面倒だからこのままで良いや。

ちなみに俺はテンプレートシステムを無理矢理正規表現ベースの処理で作るということをやったことがある。絶対に誰も使っていないであろうし、俺もメンテする気がなくなったというか新しいシステムをちょこちょこと書いてるんだけど (今は別のプログラムで忙しくて放置中)、罠だらけの薄汚い実装に我慢できる人だけどうぞ

追記: バグがあることに気がついて飛び起きた。以下の行が抜けてた

elif escaped:
  escaped = not escaped

やっぱ寝る直前にコードを書くもんじゃないな。

(19:52) また誤訳が見つかった

例の翻訳にね。ていうか一部指摘の反映を忘れてたし (ゴメンナサイ)。俺は巧遅よりは拙速なのだけど、拙速過ぎるのも考え物だな。

(20:32) 俺が思うにフレームワークってのは

How To 文書と同じで、本来ならわかってる人がわかった上で楽するために使うものだよな。だから決して、スキルのない奴でもシステムを作れるようにするためにあるわけじゃない。もちろんフレームワークってのは基本的なアプリケーションの構築手順なわけだから、フレームワークを教材に学ぶのは全然悪いことじゃないだろう。でもそこから何も学ばないのなら、フレームワークを使わせる意味がないとすら思う。

(21:02) 何で俺が日曜大工に取り組もうと思った途端に天気が崩れやがるのですか?

日頃の行い? いっとくがなあ、俺はここ数年悪いことはしてないぞ。多分。

(23:00) とりあえず檜山さんの課題に答えつつ解説などしてみる

流石に Python だと楽過ぎるかなとか思わなくもないけど、一応。課題 1 と 2 は面倒なので飛ばします。

つーわけで課題 3 から。

def process(tmpl, context):
  start_bracket = False
  placeholder = []
  for c in tmpl:
    if c == '{':
      start_bracket = True
    elif c == '}' and start_bracket:
      key = ''.join(placeholder)
      yield context.get(key, key)
      start_bracket = False
      placeholder = []
    else:
      if start_bracket:
        placeholder.append(c)
      else:
        yield c
tmpl = u"こんにちは、{お客様名}様。{来店日}にはご来店いただき、まことにありがとうございます。"
context = {
  u"お客様名" : u"板東トン吉",
  u"来店日"   : u"1月21日"
}
print ''.join(process(tmpl, context))

うわあ、見たまんまだ。 Python に限らず、ジェネレータとかコルーチンの書ける言語だとこういう芸当が可能になる。

  1. 開き括弧がきたらフラグを立てる
  2. 閉じ括弧がきたらフラグを戻し、 placeholder をキーに辞書から値を探す。なければキーをそのまま返す
  3. フラグが立っていなければ文字をそのまま返す。立っていたら placeholder に追加

解説するまでもない気がしなくもない。じゃあ続いて課題 4 に。

def search_terminal_block(tmpl):
  bracket = False
  point = 0
  for i, v in enumerate(tmpl):
    if v == '{':
      bracket = True
      point = i+1
    elif v == '}':
      if bracket:
        bracket = False
        yield tmpl[point:i]
        point = i+1

閉じ括弧に来たらフラグを戻せば、末端以外の閉じ括弧では処理が無視されるって寸法。知性の欠片もないですね。でも一番簡単な解が多分これだからなあ。もう少し込み入った話は次の課題 5 で。

さてこの課題 5 が結構面倒臭い。とりあえず入力と期待される出力を書くと、

入力
こんにちは、{お客様名}様。{{来店日}にはご来店いただき、まことにありがとうございます。}
出力
こんにちは、{お客様名}様。{来店日}にはご来店いただき、まことにありがとうございます。

さて、これをやるからには観念してツリー構造のデータを用意して処理をせねばなるまい。方針としては、

という感じで。なので例題のテキストをこのデータ構造に落とし込むと、

["こんにちは、", ["お客様名"], "様。", [["来店日"], "にはご来店いただき、まことにありがとうございます。"]]

という構造になる。この場合、「お客様名」と「来店日」のあるリストが末端ブロックとなる。あとは出力するときに、末端ブロックだけは {} でくくってやればいい。解答例は以下。

def reject_non_terminal_block(tmpl):
  def create_tree(s):
    point = 0
    depth = 0
    for i, v in enumerate(s):
      if v == '{':
        if depth == 0:
          yield s[point:i]
          point = i+1
        depth += 1
      elif v == '}':
        if depth == 1:
          yield tuple(create_tree(s[point:i]))
          point = i+1
        depth -= 1
    yield s[point:]
  
  def is_terminal_block(ls):
    return not True in [True for i in ls if isinstance(i, tuple)]
   
  def decorate_terminal_block(iterable):
    for block in iterable:
      if isinstance(block, tuple):
        if is_terminal_block(block):
          for b in block:
            yield "{%s}" % b
        else:
          for b in decorate_terminal_block(block):
            yield b
      else:
        yield block
 
  return ''.join(decorate_terminal_block(create_tree(tmpl)))

create_tree は再帰的にリストを作っては yield する関数。ポイントは閉じ括弧の出現回数を記録して最外殻の括弧のペアなのか判断しているところと、 yield するときにタプルに直しているところ (別にリストでも良いけど、別に副作用のあることはしないからタプルの方が好ましい)。普通にジェネレータとして返すと、思わぬところで消費されてしまう。

is_terminal_block は、リスト内にリストが無いか調べてるだけ。確かこれの一般化された関数が Python2.5 で導入されたはずだけど、俺はまだ使ってないんで。

decorate_terminal_block は末端ブロックを括弧で括る処理をしている。あとはそれをジェネレータで返して join して終わり。

課題 6 は簡単な話なのでパス。

課題 7 と課題 8 はやってる最中なのでまたあとで。しかしこれを解いてる最中に、全然関係ないところからテンプレートエンジンのアイディアが湧いてきてしまった。

Written by Kuwata Chikara
Creative Commons