テキストのテンプレート処理を行うシンプルな Python モジュールです。条件分岐や反復処理が可能です。
__escape__(value)
関数が定義されている時はそれがデフォルトのエスケープ関数として使用されるようになった。{{ x }}
とするだけで、__escape__(x)
の結果が適用・展開される。__escape__
を定義すると自動的に __nonescape__
も定義され、{{ __nonescape__(x) }}
と書くと x
はエスケープ処理されなくなる。Python には、テキストのテンプレート処理用のライブラリが星の数ほどあります。こんな辺境のライブラリに手を出す前に、ほかのライブラリも検討してみましょう。「ウノウラボ Unoh Labs: Pythonのテンプレートエンジン」に、Cheetah、Django、Jinja をはじめとする代表的な Python 用のテンプレートシステムが紹介されています。標準モジュールのテンプレート文字列処理機能(string.Template
)もお忘れなく。
強いて本ライブラリの利点を挙げるとするなら、
といったところでしょうか。
どちらも中身は同じです。
texttemplate.py はスクリプトとして使用することもできます。
template にはテンプレートを記述したファイル名を指定します。与えられたテンプレートファイルを評価して、結果を標準出力に表示します。
モジュールとして、以下のクラスや関数を利用できます。
{# ... #} で囲まれた部分はコメントです。評価時には空文字列になります。コメントは入れ子にできません。
{{...}} で囲まれた部分は、式として評価されその結果に置き換わります。式として有効な任意の Python コードを書くことができます。
>>> import texttemplate # 以降の例では省略 >>> code = 'hello, {{ name }}!' >>> template = texttemplate.Template(code) >>> template.evaluate(name='world') 'hello world!'
>>> code = '{{ a + b * c }}' >>> template = texttemplate.Template(code) >>> template.evaluate(a=1, b=2, c=3) '7'
>>> code = "{{ d['A'] }}, {{ d.get('Z', 0) }}" >>> template = texttemplate.Template(code) >>> template.evaluate(d={'A': 1, 'B': 2, 'C': 3}) '1, 0' >>> template.evaluate(d={'A': 'X'}) 'X, 0'
{%...%} で囲まれた部分は、ブロックを指定するタグとして認識されます。ブロックには後述の種類があります。ブロックの終了は、{% end %} で指定します。ブロックは入れ子にできます。
式 expr を評価して、真であればブロックの中身を評価します。偽の場合には空文字列になります。{% else %} 節を付けて、偽の場合に評価される中身を指定できます。Python 同様、elif 節も付けることができます。
>>> code = '{% if a == b %}equal{% else %}different{% end %}!' >>> template = texttemplate.Template(code) >>> template.evaluate(a=1, b=1) 'equal!' >>> template.evaluate(a=1, b=2) 'different!' >>>
iterable として渡された列挙可能なオブジェクトに対して要素をひとつずつ取り出して target に挙げられた変数に代入し、ブロックの中身を繰り返し評価します。Python の for 構文とほぼ同じですが、break, continue, else に相当する機能はありません。テンプレートを宣言的なコードにしたいのであえてそうなっています。
>>> code = '{% for i in range(3) %}{{ i+1 }}! {% end %}' >>> template = texttemplate.Template(code) >>> template.evaluate() '1! 2! 3! '
>>> code = '{% for i, name in enumerate(names) %}{{ i+1 }}:{{ name }} {% end %}' >>> template = texttemplate.Template(code) >>> template.evaluate(names=['apple', 'banana', 'orange']) '1:apple 2:banana 3:orange '
exec ブロック内には、テンプレート用の文字列ではなく、Python のコードを書きます。このブロックは Template インスタンスの生成時にコードとして評価され、コードの実行結果がテンプレートのインスタンスに格納されます。テンプレートの評価時には、このブロックは空文字列として扱われます。
「テンプレート先頭に exec ブロックを置いて、そのテンプレート固有の定数やエスケープ用関数を定義する」というのが、テンプレートファイルの一般的な書き方になります。
>>> code = """{% exec %} ... def escape(s): ... return s.replace('<', '<').replace('>', '>') ... ... {% end %}{{ escape(data) }}""" >>> template = texttemplate.Template(code) >>> template.evaluate(data='<i>like this</i>') '<i>like this</i>'
(上記の escape 関数は説明のために間に合わせで載せた欠陥品なのでこのまま使わないように!)
このブロックで囲まれた文字列は、コンパイル時にテンプレートのエンコーディング名として認識されます。これによりテンプレートは指定されたエンコーディングによってデコードされ、Unicode 文字列に変換されます。エンコーディング名は、Template インスタンスの encoding 属性に格納されます。評価時にはブロック内の文字列がそのまま出力されます。
テンプレートはエンコーディング情報をデータのデコードに使うだけで、評価時に結果文字列をエンコードするのには使いません。必要であれば、template.evaluate(namespace).encode(template.encoding)
のように、明示的にエンコードしてください。
コメント、変数、ブロックのタグ以外の文字列は本文としてそのまま出力結果に使われます。ただし、改行の直前に \ を書くと、その行は後続の行と連結されます。また、行頭の空白に続けて \ を書くと、行頭からそこまでの空白が除去されます。この仕組みは、可読性を上げるためにブロック指定を行分けして書く時などに、結果に余分な改行が入らないようにしたい場合に使います。
今のところ、texttemplate.py のテンプレートは Django やその影響を受けた Jinja に似たものになっています。しかし個人的には {% foo %}こういうタグ{% end %} はあまり好きになれないので、将来変更するかもしれません。この点に関しては、web.py の Aaron Swartz のテンプレート論のほうに同意。
samples ディレクトリに簡単なテンプレートファイルのサンプルが入っています。参考にしてください。コマンドラインから次のように実行すると、結果を確認できます。
C:\...\texttemplate>python texttemplate.py samples\listdir.txt.tmpl - samples - texttemplate.html - texttemplate.py
{{ '{{' }}
や {{ '{%' }}
を使ってください。
こちらはちょっと面倒くさいです。ひとつは {{ '}'+'}' }}
とする方法があります。もうひとつの方法として、テンプレート冒頭で {% exec %}var_etag = '}}'{% end %}
などとしておくか、テンプレートを使用するスクリプトの側で template.evaluate(var_etag='}}')
としておいて、{{ var_etag }}
とすることでも実現できます。
前者の方法の応用としてじつは {{ '}''}' }}
と書くこともできます。
{% def tmpl_frag(x, y, z) %} ... {% end %}
として {% apply tmpl_frag(a, b, c) %}{% end %}
みたいなのはどうか?{# encoding: shift_jis #}
のような指定を認識するようにするといいかも?{{ escape(...) }}
と明示的に指定するのはわかりやすいが、うっかり忘れやすい。{% exec %} ... {% end %} 内で __escape__(s) のような関数を定義しておくとデフォルトでそれが使われ、{{ __unsafe__(...) }}
とするとエスケープ処理が行なわれない、というような仕様はどうか。パブリックドメインです。好きに使ってください。
なお、本モジュールを使って生じた損害等についての責任は負いかねますのであしからず。
$Date: 2012-09-09 23:39:16 +0900 (日, 09 9 2012) $