石上です。
業務でRadiusタグが使いたくなったので簡単に調べてみました。
Radius Quick Start を参考にまとめたものです。ただし、原文のコードのままでは動かない部分があったので少し修正しました。
タグの定義
テンプレートの中で使用するためのタグの定義は以下のように行います。
require 'rubygems'
require 'radius'
context = Radius::Context.new
context.define_tag "hello" do |tag|
"Hello #{tag.attr['name'] || 'World'}!"
end
一度タグを作ってcontextを定義すると簡単にパーサーを作る事が出来ます。
parser = Radius::Parser.new(context, :tag_prefix => 'r')
puts parser.parse('<p><r:hello /></p>')
puts parser.parse('<p><r:hello name="John" /></p>')
結果:
<p>Hello World!</p>
<p>Hello John!</p>
コンテナタグ(Container Tags)
Radiusはコンテナタグを定義することができます。コンテナタグとは開始タグと終了タグがあり、タグの間に他のタグやコンテンツを挟む事が出来ます。以下では、RedClothを利用してWiki記法であるTextileを出力してみたいと思います。
require 'redcloth'
context.define_tag "textile" do |tag|
contents = tag.expand
RedCloth.new(contents).to_html
end
# (tag.expandは、開始タグと終了タグの間の部分を返します。)
parser.parse('<r:textile>h1. Hello **World**!</r:textile>')
結果:
<h1>Hello <strong>World</strong>!</h1>
ネストされたタグ(Nested Tags)
さらに良い方法があります。コレクションをイテレートする際に、コンテナタグの間に囲まれた部分を扱う事が出来ます。
context = Radius::Context.new
context.define_tag "stooge" do |tag|
content = ''
["Larry", "Moe", "Curly"].each do |name|
tag.locals.name = name
content << tag.expand
end
content
end
context.define_tag "stooge:name" do |tag|
tag.locals.name
end
parser = Radius::Parser.new(context, :tag_prefix => 'r')
template = <<-TEMPLATE
<ul>
<r:stooge>
<li><r:name /></li>
</r:stooge>
</ul>
TEMPLATE
puts parser.parse(template)
結果:
<ul>
<li>Larry</li>
<li>Moe</li>
<li>Curly</li>
</ul>
nameタグの頭に"stooge:"が付いています。これは、nameタグは必ずstoogeタグの内側で利用するということを意味しています。単にnameというタグを作成した場合、stoogeタグの外側で使用してください。
オブジェクトをテンプレートに展開する(Exposing Objects to Templates)
テンプレートの中に、特定のオブジェクトを展開する事が可能です。define_tagメソッドとforオプションを利用します。
context.define_tag "count", :for => 1
テンプレートにオブジェクト1をcountタグとして展開しました。以下のようにも書けます。
context.define_tag("count") { 1 }
タイプ数が多いね。また、オブジェクト上にメソッドを展開することもできます。
context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
4つのタグをcontextに追加する事ができました。userがその1つです。そこにexposeの3つのメソッドがそれぞれについています。ユーザの名前をテンプレートの中で取得するには以下のようにします。
<r:user><r:name /></r:user>
もし、user.nameに"John"があれば、"John"と描写します。
タグの速記(Tag Shorthand)
上の例では、user.nameを参照する際に、以下のようにタグを記述する必要がありました。
<r:user><r:name /></r:user>
以下のようにコロンを使用してnameを参照します。
<r:user:name />
Radiusでは、このような速記を全てのタグに許可しています。
タグプレフィクスを変更する(Changing the Tag Prefix)
デフォルトの状態では、Radiusタグは"radius"と頭に付けなければなりません。tag_prefix属性を変更することによって変えることが出来ます。
parser = Radius::Parser.new(context, :tag_prefix => 'r')
全てのタグの頭に"radius"の代わりに"r"を付けなければならなくなりました。
未定義タグの振る舞いを指定する(Custom Behavior for Undefined Tags)
Context#tag_missingは、Object#method_missingに似ており、Contextで定義されていないタグが使われた場合の動作を記述することが出来ます。
class LazyContext < Radius::Context
def tag_missing(tag, attr, &block)
"<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
end
end
parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
puts parser.parse('<lazy:weird value="true" />')
結果:
<strong>ERROR: Undefined tag `weird' with attributes {"value"=>"true"}</strong>
定義されていないタグが含まれていた場合、通常はUndefinedTagErrorを出力します。
タグ・バインディング(Tag Bindings)
Radiusは、Context#define_tagメソッドのブロックに、TagBindingを渡します。このタグのバインディングは、いくつかの仕事に役立ちます。このタグのバインディングは、タグの内容を処理して、結果を返すメソッドを持つインスタンスを持ちます。例えば、属性のハッシュを返すattrメソッドを持ちます。また、TagBinding#single?とTagBinding#double?の2つメソッドを持ちます。コンテナタグかどうかをtrueまたはfalseで返します。詳しい事は、"Radius::TagBinding":http://radius.rubyforge.org/classes/Radius/TagBinding.html を参照してください。
Tag Binding Locals, Globals, and Context Sensitive Tags
TagBinding#globalsは、すべてのタグにアクセス可能な変数を保存することに役立ちます。
context.define_tag "inc" do |tag|
tag.globals.count ||= 0
tag.globals.count += 1
end
context.define_tag "count" do |tag|
tag.globals.count || 0
end
TagBinding#localsはTagBinding#globalsにある変数を映すが、子のタグが変数を再定義するのを許可します。文脈依存タグを定める際に貴重です。
require 'radius'
class Person
attr_accessor :name, :friend
def initialize(name)
@name = name
end
end
jack = Person.new('Jack')
jill = Person.new('Jill')
jack.friend = jill
jill.friend = jack
context = Radius::Context.new do |c|
c.define_tag "jack" do |tag|
tag.locals.person = jack
tag.expand
end
c.define_tag "jill" do |tag|
tag.locals.person = jill
tag.expand
end
c.define_tag "name" do |tag|
tag.locals.person.name rescue tag.missing!
end
c.define_tag "friend" do |tag|
tag.locals.person = tag.locals.person.friend rescue tag.missing!
tag.expand
end
end
parser = Radius::Parser.new(context, :tag_prefix => 'r')
parser.parse('<r:jack:name />') #=> "Jack"
parser.parse('<r:jill:name />') #=> "Jill"
parser.parse('<r:jill:friend:name />') #=> "Jack"
parser.parse('<r:jill:friend:friend:name />') #=> "Jack"
parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
parser.parse('<r:name />') # raises an UndefinedTagError exception
TagBinding#localsには賢いネスティングを使う事が出来ます。"<r:jill:name />"は"Jill"と評価されます。"<r:jill:friend:name />"は"Jack"と評価されます。localeは、タグになるや否やスコープを失いますが、globalsの方はスコープを失いません。
例の最後の行では、TagMissing errorが出ています。これは、nameタグが以下のように定義されているからです。
tag.locals.person.name rescue tag.missing!
もし、personをlocalsに定義しないと、nilが返ってきます。さらにnameを参照するとNoMethodErrorエラーが発生します。‘rescue’のおかげで、TagBinding#missing!はContext#tag_missingによって呼び出されます。
標準では、Context#tag_missingは、UndefinedTagErrorを発生させます。‘rescue tag.missing!’は、エラーをチェックするのに便利です。
タグの特性(Tag Specificity)
2つの同じ名前で、ネスティングが異なるタグが出た場合、RadiusはCSSと同じアルゴリズムを使用して優先順位をつけます。以下のようなタグが定義されていた場合、
- nesting
- extra:nesting
- parent:child:nesting
テンプレートで以下のようになっている場合、
<r:parent:extra:child:nesting />
Radiusは、以下のように計算します。
- nesting => 1.0.0.0
- extra:nesting => 1.0.1.0
- parent:child:nesting => 1.1.0.1
parent:child:nestingが勝ちました。もし、テンプレートに以下のように書いてあったら、
<r:parent:child:extra:nesting />
Radiusは、以下のように計算します。
- nesting => 1.0.0.0
- extra:nesting => 1.1.0.0
- parent:child:nesting => 1.0.1.1
extra:nestingは、勝ちました。
値は、右から左にポイントをタグの各々に割り当てることによって割り当てられます。テンプレートの中に4レベルのネストされたタグがあれば、タグは以下のように点数が付けられます。
- 1.1.1.1
それぞれのレベルに1ポイントが与えられます。
Radiusが私たちの予想通りにタグを分解してくれるので必要ありませんが、万が一の場合はこの項目を思い出してください。
サンプル
<r:pages>
<h1><r:title /></h1>
<p><r:content /></p>
</r:pages>
サンプルコード を作成しました。併せてご利用ください。