Kid言語仕様
Python製のWebアプリケーションフレームワーク,TurboGearsなどで利用されているテンプレートエンジン「Kid」の仕様書の邦訳です。
KidはシンプルなXMLベースのテンプレート言語です。ロジックを記述するための組み込み言語にはPythonを利用しています。構文規則は,XSLTやTAL,PHPなどの多くの既存テンプレート言語に影響を受けています。
この文書では,テンプレート言語の文法について言及しています。Kidテンプレートを開発する際のリファレンスとして利用すると便利でしょう。コマンドライン上,またはWebアプリケーションなど,Pythonからテンプレートを利用する方法については,ユーザーズガイド(未訳)をご覧下さい。
1 概要
<?python
title = "Kidのテスト用ドキュメント"
fruits = [u"りんご", u"オレンジ", u"キウィ", u"M&M"]
from platform import system
?>
<html xmlns:py="http://purl.org/kid/ns#">
<head>
<title py:content="title">この部分は置換されます</title>
</head>
<body>
<p>私の好きな果物:</p>
<ul>
<li py:for="fruit in fruits">
私は ${fruit}s が好き。
</li>
</ul>
<p py:if="system() == 'Linux'">
偉いぞ!
</p>
</body>
</html>
このテンプレートは以下のように変換されます:
<?xml version="1.0" encoding="utf-8"?>
<html>
<head>
<title>Kidのテスト用ドキュメント</title>
</head>
<body>
<p>These are some of my favorite fruits:</p>
<ul>
<li>私は りんご が好きです</li>
<li>私は オレンジ が好きです</li>
<li>私は キウィ が好きです</li>
<li>私は M&M が好きです</li>
</ul>
<p>
偉いぞ!
</p>
</body>
</html>
2 KidのXML名前空間
この文書に記述されているXMLのアトリビュートは,すべて以下のXML名前空間に定義されています:
">http://purl.org/kid/ns#
"py"から始まる名前空間は,Kid/Python名前空間に属するものとして利用しています。
3 コードブロックを埋め込む (<?python?>)
- <?python?> 処理命令が,文書レベルのコードを含むエレメント(ルートエレメントのような)の外にある場合。テンプレートを読み込んだとき,または外部から呼び出されたとき,コードを一回だけ実行します。
- <?python?> 処理命令がXML文書のエレメントの中にある場合。処理命令が存在するスコープが評価される場合,外部から呼び出された時に毎回実行します。
文書レベルにあるコードは,Pythonのモジュールレベルにあるコードと同じように動作します。ローカルレベルのコードは,Pythonモジュールの関数レベルにあるコードと同じように動作します。例えば,以下のようなテンプレートを見てみましょう:
<?python
x = 0
y = 0
?>
<html xmlns:py="http://purl.org/kid/ns#">
<?python
x = 1
if x == 1:
x = 10
?>
<p py:content="x"/>
<?python
global y
y = 30
?>
<p py:content="y"/>
</html>
このコードは,以下のようなPythonのモジュールと同じと考えられます:
x = 0
y = 0
def expand(handler):
handler.startDocument()
handler.startElement('html')
x = 1
if x == 1:
x = 10
handler.element('p', content=x) # output p element with x as value
global y
y = 30
handler.element('p', content=y) # output p element with value of y
handler.endElement('html')
handler.endDocument()
<?python
class Adder:
def __init__(self, x, y):
self.x = x
self.y = y
def doit(self):
return self.x + self.y
foo = Adder(x=400, y=20)
x = foo.doit()
?>
一行で <?python?> 処理命令を記述することもできます:
<?python x = 10 ?>
4 コンテンツを生成するための構造
テンプレートからコンテンツを出力するための方法には何種類かの方法があります。「py:content」や「py:replace」, 「py:attrs」などが利用できますし,または「${}」を利用できます。それぞれの文法には,Pythonの式からどんな結果を得るかによって、規則が存在します。
文字列,ユニコード文字列
XMLのCDATAとして挿入される文字列です。構造をパースせず,マークアップを含まない文字列のデータです。シリアライズされたとき,'<','&'の文字はXMLエンティティとしてエンコードされます。内容を持つアトリビュート値は二重引用符(")でエンコードされます。
ElementTree.Element
ElementTree.Elementがコンテンツ出力中に参照されると,要素をそのまま挿入します。テキストとしてエンコードされず,XMLの構造の一部となります。
XML()という関数を使うと,文字列を構造のあるコンテンツに変換できます。document()という関数は,URLからXML文書を取得し,コンテンツとして埋め込むことができます。
注意すべきことは,アトリビュートの値はここで埋めるべきではない,ということです。py:attrや${}などを利用するようにしてください。
シーケンス
シーケンスタイプ(リスト,タプルやどiterableなオブジェクト)を参照した場合,シーケンスのそれぞれの要素にルールを適用します。例えば, For example, you could reference a list containing an Element and a string.
その他
評価された結果がここに挙げたタイプでない場合は,unicode(expr)を実行してユニコードに強制的に変換しようとします。オブジェクトが内部的にはユニコード文字列,または文字列であったこととして処理を続けます。
5 Pythonの式による置換 (${expr})
Kidの名前空間に存在していないアトリビュートや,テキストの内部を動的に置換するためには,Pythonの式を埋め込むと良いでしょう。Pythonの式は,ドル記号の後に中括弧で囲み埋め込みます。置換の結果は,「コンテンツを生成するための構造」に記述したものに準じます。
<?python
verb = 'ran'
noun = 'store'
?>
<a title="I ${verb} to the ${noun}">...
この例は以下のようになります:
<a title="I ran to the store">...
アトリビュートの値がPythonの式のみで記述されていて,Pythonの式がNoneだった場合には,アトリビュートを削除します。この動作は,強制的に長さゼロの文字列('')を返すようにすることで防げます。以下が例です。
<?python
# set something to None
x = None
?>
<a title="${x}">...
この例は以下のようになります:
<a>...
一方,次の例は:
<?python x = None?>
<a title="${x or ''}">...
以下のように出力します:
<a title="">...
5.1 識別子を埋め込む ($name)
変数名やオブジェクトアクセスのドット記号を含む場合は,中括弧を省略できます。
<a href="http://example.com/$page" title="$title">
「ドット」も記述できます: $object.another.attribute
</a>
しかしながら,他の文字列から置換を行うPythonの式を明確に区切りたいとき,見た目で何が起こっているかを分かりやすくするためには,中括弧を使うのもよいでしょう。
5.2 エスケープ ($$)
$$はエスケープに利用できます。"$${bla}" は "${bla}" という文字列を出力します。
6 デフォルトで利用できるモジュール
全てのテンプレートには,デフォルトで利用できるモジュールがあります。
6.1 XML() 関数
Pythonの式による置換,py:content,およびpy:replaceは文字列をテキストとしてエンコードします。テキストはXMLの仕様に従ってエンコードされますので,"<"や"&"といった記号は実体参照文字列に置換されます。文字列としてXML自体を保持していて,テキストとしてではなくXMLとして出力したい場合,XML関数を通す必要があります。
例えば,helloという関数があり,その関数はテンプレートに埋め込む(<hello>world</hello>のような)XMLテータを返すとしましょう。以下のようにするとどうなるでしょうか。
<p>${hello()}</p>
結果は以下のようになります:
<p><hello>world<hello></p>
XML関数を呼ぶことで,望むような結果を得ることができます:
<p>${XML(hello())}</p>
<p><hello>world</hello></p>
6.2 document() 関数
document関数はXML文書をファイルやURLから読み込み,テンプレートに置換して出力します:
<div py:content="document('header.html')"></div>
document関数は,テンプレートファイルがあるパスから相対的にパスを解釈します(テンプレートのパスが存在する場合)。
6.3 defined(name) と value_of(name, default) 関数
defined関数は,Kidの名前空間に引数として渡した名前(name)が存在していれば真を返します。value_of関数は,引数として渡した名前(name)の値を返します。名前空間に名前(name)が存在しなかった場合は,オプション引数のデフォルト値(default)を返します。二つのシンプルで便利な関数は,Pythonの組み込み関数hasattr(self,name)やgetattr(self,name,default)と同じ機能を提供していますが,より直感的に利用できます。
7 アトリビュート言語
7.1 繰り返し/イテレーション (py:for)
<element py:for="target_list in expression_list" />
上記のコードはPythonのfor文とまったく同じ動作をします。
"py:for"アトリビュートは複数回評価したいエレメントに記述します。シーケンスが持つ値についてそれぞれ繰り返しを行います。
<p py:for="num in bottles">
<span py:content="num">X</span> 本のビール瓶が床にあります,
一本飲み干し,片づけると<span py:content="num - 1">X - 1</span>
本になります。
</p>
7.2 条件分岐 (py:if)
<element py:if="expr" />
"py:if"アトリビュートは,式が真になった場合にのみ出力したいアトリビュートに記述します。子供のエレメントも表示/非表示の影響を受けます。:
<p py:if="5 * 5 == 25">
Pythonは正しくかけ算を行えるようだ
</p>
"py:if"アトリビュートは"py:for"アトリビュートの後に評価し,繰り返しの最中に毎回評価します。評価結果がPythonで偽として扱われる値の場合,これ以上"py:"アトリビュートの評価を行いません。
- 注意
- Pythonでは,None,False,[],(),{},0や''はすべて偽と見なされます。
7.3 動的コンテンツ (py:content)
<element py:content="expr" />
このアトリビュートは,Pythonの式として評価した結果を,子供のエレメント部分に入れ替えたい場合にエレメントに記述します。
<p py:content="time.strftime('%C %c')">The Time</p>
上記のコードは以下のように出力します:
<p>Tues, Jun 26, 2004 02:03:53 AM</p>
7.4 コンテンツの置換 (py:replace)
"py:content"は「コンテンツを生成するための構造」のひとつです。文字列と構造のあるデータを出力できます。
<element py:replace='expr' />
"py:replace"を使うと,"py:content"と"py:strip="True""を同じエレメントに表記した場合と同じ挙動を,より短く記述できます:
<?python
x = 10
?>
<p><span py:replace="x">...</span></p>
上記コードは以下のように出力します:
<p>10</p>
また,次のコードと同じになります:
<?python #
x = 10
?>
<p><span py:strip="" py:content="x">...</span></p>
"py:replace"アトリビュートは,"py:for"や"py:if"アトリビュートの後に評価します。"py:strip"と"py:content"アトリビュートは評価されず,無視されます。
7.5 タグを省略する (py:strip)
"py:replace"は「コンテンツを生成するための構造」のひとつです。文字列と構造のあるデータを出力できます。
<element py:strip="expr" />
"py:strip"アトリビュートは内包するエレメントを出力から除外したい場合に,エレメントに記述し利用します。アトリビュートの値が空白(式を書かない)であるか,式を評価して真だった場合に,エレメントを出力しません。続くエレメントは通常通り評価します。もし式が空白でなく,評価した値が偽の場合は,アトリビュートを無視して評価を続けます。
"py:strip"アトリビュートは他のkidアトリビュートを伴って記述します。"py:replace"と"py:strip"が同じエレメントにある場合は,"py:strip"アトリビュートは無視します。
"py:strip"アトリビュートは"py:for"と"py:if"の後に評価します。"py:strip"が機能した場合でも,"py:content"アトリビュートは機能します。ただし,アトリビュート(タグ)自体は出力されません。
7.6 動的なアトリビュート置換 (py:attrs)
<element py:attrs="expr" />
"py:attrs"アトリビュートは,エレメント中のアトリビュートを変更したい歳に利用します。式は次に挙げるうちのどれかである必要があります:
辞書(dict)
アトリビュート名と値を記述した辞書(dictionary)。辞書に記載された内容をもとに,"py:attrs"の置かれたエレメントに対しアトリビュートの置き換えを行います。中括弧({,})は省略することができます。
リスト
アトリビュート名と値の組となるタプルのリストを記述することもできます。リストを元に,"py:attrs"の置かれたエレメントに対しアトリビュートの置き換えを行います。
キーワード引数
カンマ区切りのキーワード引数でアトリビュートを指定することもできます。「名前=値」のように記述します。
以下が記述の具体例です:
<elem py:attrs="{'a':1, 'ns:b':2}" />
<elem py:attrs="'a':1, 'ns:b':2" />
<elem py:attrs="(('a',1), ('ns:b',2))" />
<elem py:attrs="a=1, ns:b=2" />
上記の例は,どれも次のような出力になります:
<elem a="1" ns:b="2" />
アトリビュートの値としてNoneが指定されると,アトリビュート自体が削除されますので注意が必要です。空のアトリビュートを挿入したい場合は,空の文字列('')を利用して下さい。
アトリビュートの値に相当する部分が空の辞書,または空のリストの場合は,アトリビュートの変更は行われません。
"py:attrs"は「コンテンツを生成するための構造」ですが,文字列データのみを出力します。XMLのエレメントやタグを含む構造は出力しません。
7.7 テンプレート関数 (py:def)
<element py:def="template_name(arg_list)" />
"py:def" アトリビュートは「テンプレート関数」を作成したいエレメントに設置します。"py:def"の存在するエレメントが内包するエレメントは,通常出力されません。別の「コンテンツを生成するための構造」から呼び出されたときに,マークアップとして出力されます。
通常のPython関数と同様,テンプレート関数には変数やキーワード引数を渡すことができます。
テンプレート関数は,通常のPython関数と同じように呼び出されます。"py:content"のような「コンテンツを生成するための構造」から呼び出されることが多いでしょう。
テンプレート関数は以下のようにして定義します。以下の例では,"display_list"と"display_dict"という関数を定義しています。display_list関数はシーケンスを引数にとり,display_dict関数は辞書を引数に取ります。同じテンプレート内にある「コンテンツを生成するための構造」から呼び出すことができます:
<html xmlns:py="http://purl.org/kid/ns#">
<body>
<ul py:def="display_list(seq)">
<li py:for="item in seq" py:content="item" />
</ul>
<table py:def="display_dict(mapping)">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr py:for="key, value in mapping.items()">
<td py:content="key" />
<td py:content="value" />
</tr>
</table>
${display_list(['apple', 'orange', 'kiwi'])}
<div py:replace="display_dict({'x' : 'y', 'p' : 'q'})">
Key/Value Table replaces this text
</div>
</body>
</html>
XMLとして評価すると,以下のような出力になります:
<?xml version="1.0" encoding="utf-8"?>
<html>
<body>
<ul>
<li>apple</li><li>orange</li><li>kiwi</li>
</ul>
<table>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr>
<td>x</td>
<td>y</td>
</tr>
<tr>
<td>p</td>
<td>q</td>
</tr>
</table>
</body>
</html>
7.8 マッチテンプレート (py:match)
<element py:match="expr" />
"py:match" アトリビュートは「マッチテンプレート」を作成したい場所に設置します。マッチテンプレートが内包するマークアップは通常出力されません。その代わり,コンテンツを出力するためのフィルタを作成します。
マッチテンプレートは,特定のパターンを持ったコンテンツを動的にテンプレートに埋め込む目的で利用されます。JSPのtablibsやXSLTにある「カスタムタグ」のようなものです。
マッチテンプレートには,「マッチ式」と「本文("py:match"のあるエレメントと内包するエレメント)」という二つのパートから構成されています。
マッチテンプレートは以下のように処理されます:
- テンプレート上の個々のエレメントが,マッチテンプレートのフィルタを通ります
- マッチテンプレートのフィルタは,現在利用しているテンプレート,および呼び出しているテンプレート上に定義されているマッチテンプレートを調べ,評価します
- 「マッチ式」が真を返した場合,マッチテンプレートを出力し,エレメント,および内包するエレメントを置き換えます
マッチ式,およびマッチテンプレートの内部では,出力しようとしているエレメントにアイテム名をバインドします。ただし,個々のフェーズでアクセスできるものについては制限があります。:
- マッチ式を評価中は,アイテムの要素のみが利用でき,内包する要素は利用できません。マッチ式では,テスト用にエレメントのタグとアトリビュートしか利用できないということを意味しています(注:1)。
- マッチテンプレートを展開中(マッチ式が真だったとき),エレメントが内包するエレメントを利用でき,文字列などを出力するために「コンテンツを生成するための構造」から利用できます。
(注:1) この制限は,Kidプロセッサの処理の流れから来ています。通常のテンプレートを展開中は,テンプレート上のすべてのツリーがメモリ上にあるわけではないのです。
7.8.1 例
以下に,カスタムタグを作るための簡単な例を示します。<greeting>というタグは,タグに提供されている2つの値のうち,ひとつを時間によってを選び,出力します。
<?xml version="1.0" encoding="utf-8"?>
<?python
from time import localtime
def timeofday():
"""Get time of day ('am' or 'pm')"""
return localtime().tm_hour < 12 and 'am' or 'pm'
?>
<html xmlns:py="http://purl.org/kid/ns#">
<!-- define the greeting match template -->
<span py:match="item.tag == 'greeting'"
py:replace="item.get(timeofday())">
</span&