Amrita2のマクロテンプレート
Amrita2はだいぶ進んでいるけど、ドキュメントを書くヒマがありません。
そこで、面白そうな部分のRSpecのコードとごく簡単な説明をメモ程度にここに書いていきます。なお、以下のRSpecは全て動いてます。
まず、マクロテンプレートについて。
context "Macro can contain dynamic element" do class Hello2 < Amrita2::Macro::Base include Amrita2 ElementName = "hello" TemplateText = <<-END.gsub(/\s+/m, ' ') <span macro:src="hello"> <span macro:src="greeting" />, macro world! <span target_src="msg" /> </span> END end specify " " do text = <<-END <span><hello greeting='Hello'/> </span> END t = Amrita2::TemplateText.new(text) t.add_macro(Hello2.new) t.test_with(:msg => "Enjoy it!") do |result| result.should_be_samexml_as "Hello, macro world! Enjoy it! \n" end end end
マクロテンプレートとは、テンプレートの一部を部品化する機能。Amrita2のマクロの特色は、
- マクロの定義も参照もwell-formed XMLで行なえる
- マクロの展開もテンプレートの展開も同じ、Amrita2のロジックで行なう
上記の例では、class Hello2がマクロの定義、テンプレート内の
macro:src属性のある要素は、テンプレート展開前に展開される。その後、展開されたテンプレートの中で、target_src属性のあるものが、データによって展開される。
もう少し実用的な例。
context "Amazon Link Macro" do class Amazon < Amrita2::Macro::Base include Amrita2 ElementName = "book" TemplateText = <<-END <span> <a macro:src = "book|NVar[:isbn, :content]" target_src = "book|NVar[:$1, :$2]" href="http://www.amazon.co.jp/exec/obidos/ASIN/$$1/"> $$2 </a> </span> END end specify " " do text = <<-END <div> <book isbn="isbn" content="title" /> </div> END # # after macro expansion # <div><a href='http://www.amazon.co.jp/exec/obidos/ASIN/$1/' # am:src='book|NVar[:isbn_no, :title]'>$2</a></div> # t = Amrita2::TemplateText.new(text) do |e, name, filters| filters << Amrita2::Filters::MacroFilter[Amazon] end data = { :book=>{ :isbn=>'4798013951', :title=>'Ruby on Rails for dummies' } } #t.set_trace(STDOUT) t.test_with(data) do |result| result.should_be_samexml_as <<-END <div> <a href="http://www.amazon.co.jp/exec/obidos/ASIN/4798013951/">Ruby on Rails for dummies</a> </div> END end end end
NVar[:isbn_no, :title]というのは、フィルターの一つで、対応するデータを$1,$2 ...と置きかえるもの。
macro:src = "book|NVar[:isbn, :content]" によって、$1,$2が、テンプレート内の
これによって、「target_src = "book|NVar[:$1, :$2]」という属性が、「am:src='book|NVar[:isbn_no, :title]」という属性に置き換わる。つまり
<book isbn="isbn" content="title" />
というマクロ参照が、マクロ展開によって
<a am:src='book|NVar[:isbn_no, :title]' href='http://www.amazon.co.jp/exec/obidos/ASIN/$1/'>$2</a>
に置き換わり、それにデータを与えると、テンプレート展開によって
<a href="http://www.amazon.co.jp/exec/obidos/ASIN/4798013951/">Ruby on Rails for dummies</a>
という形になる。
この、「マクロ展開」「テンプレート展開」という二つの段階が、共に同じAmrita2のロジックで行なわれる。
これをテーブル展開のマクロと組合せると次のようなことができる。
context "Table and Amazon" do specify " " do text = <<-END <m:table> <column header='No' column_id='no' /> <column header='Title'> <contents><book isbn="isbn" content="title" /></contents> </column> </m:table> END t = Amrita2::TemplateText.new(text) t.add_macro(Table.new) t.add_macro(Amazon.new) #<table> # <tr><th>No</th><th>Title</th></tr> # <tr am:src='detail'> # <td am:src='no'/> # <td> # <a href='http://www.amazon.co.jp/exec/obidos/ASIN/$1/' am:src='book|NVar[:isbn, :title]'>$2</a> # </td> # </tr> #</table> data = { :detail => [ { :no=>'1', :book=>{ :isbn=>'isbn1', :title=>'b1'} }, { :no=>'2', :book=>{ :isbn=>'isbn2', :title=>'b2'} }, { :no=>'3', :book=>{ :isbn=>'isbn3', :title=>'b3'} }, ] } t.test_with(data) do |result| #IO.popen("w3m -T text/html", "w") { |f| f.puts result} result.should_be_samexml_as <<-END <table> <tr><th>No</th><th>Title</th></tr> <tr> <td>1</td> <td> <a href='http://www.amazon.co.jp/exec/obidos/ASIN/isbn1/'>b1</a> </td> </tr> <tr> <td>2</td> <td> <a href='http://www.amazon.co.jp/exec/obidos/ASIN/isbn2/'>b2</a> </td> </tr> <tr> <td>3</td> <td> <a href='http://www.amazon.co.jp/exec/obidos/ASIN/isbn3/'>b3</a> </td> </tr> </table> END end end end
テーブルのマクロ定義は、以下の通り。
class Table < Amrita2::Macro::Base include Amrita2 ElementName = "m:table" TemplateText = <<-END <table macro:src="table"> <tr><th macro:src='header'/></tr> <tr target_src='detail'> <td macro:src="column|Attr[:target_src=>'column_id']"> <span macro:src="contents" /> </td> </tr> </table> END def preprocess_element(mt, element) data = element.as_amrita_dictionary(:use_child=>'column', :use_child_contents=>'//contents') data[:table][:header] = data[:table][:column].collect do |c| c[:header] end #p data #mt.set_trace(STDOUT) mt.render_with(data) end end
preprocess_elementというメソッドは、マクロ呼び出しのXMLを、Amrita2で処理できるデータに変換してから、マクロ展開を行なっている。ここはちょっと難しいかもしれないが、TemplateText内のHTMLを修正すれば(borderやclassの追加等)、それが、そのまま最終出力結果(このTableというマクロを参照している全ての処理)に反映される。
つまり、プログラマーでなくても、ある程度、マクロの(見た目の)調整が行なえる。