File: Tour_ja

Tour_ja |
Path: | docs/Tour_ja
Modified: | Wed Nov 06 15:02:26 JST 2002

# Amrita ツアー

## HTML要素の属性を変更する

href="..." のような属性の値を変更する方法を説明します。

コード:

      require "amrita/template"
      include Amrita

      tmpl = TemplateText.new <<END
      <table border="1">
        <tr><th>name</th><th>author</th><th>webpage</tr>
        <tr id=table1>
          <td id="name"></td>
          <td id="author"></td>
          <td><a id="webpage"></a></td>
        </tr>
      </table>
      END

      data = {
         :table1=>[
          {
            :name=>"Ruby",
            :author=>"matz" ,
            :webpage=> a(:href=>"http://www.ruby-lang.org/") { "Ruby Home Page" },
          },
          {
            :name=>"perl",
            :author=>"Larry Wall" ,
            :webpage=> a(:href=>"http://www.perl.com/") { "Perl.com" },
          },
          {
            :name=>"python",
            :author=>"Guido van Rossum" ,
            :webpage=> a(:href=>"http://www.python.org/") { "Python Language Website" },
          },
         ]
      }
      tmpl.prettyprint = true
      tmpl.use_compiler = true
      tmpl.expand(STDOUT, data)

出力:

       <table border="1">
         <tr>
         <th>name</th>
         <th>author</th>
         <th>webpage</th>
         </tr>
         <tr>
         <td>Ruby</td>
         <td>matz</td>
         <td><a href="http://www.ruby-lang.org/">Ruby Home Page</a></td>
         </tr>
         <tr>
         <td>perl</td>
         <td>Larry Wall</td>
         <td><a href="http://www.perl.com/">Perl.com</a></td>
         </tr>
         <tr>
         <td>python</td>
         <td>Guido van Rossum</td>
         <td><a href="http://www.python.org/">Python Language Website</a></td>
         </tr>
       </table>

### 説明

Amrita#a() というメソッドは Amrita::AttrArray という特別なオブジェクトを生成します。

        a(:href=>"http://www.ruby-lang.org/") { "Ruby Home Page" },

このオブジェクトをモデルデータとして使用すると、HTML要素の属性が変更されます。 例えば、次のようなテンプレートにこのデータを与えたとすると

        <td><a id="webpage"></a></td>

出力は次のようになります。

        <td><a href="http://www.ruby-lang.org/">Ruby Home Page</a></td>

docs/XML_ja で説明している filelist.rb というサンプルもAttrArrayを使用しています。

なお、属性の展開は別の方法もあります。詳しくは docs/Tour2の expand_attr を参照してください。

## Procオブジェクト

モデルデータと`id`属性のマッチングというamrita独特の方法は、 シンプルで見通しのよいコードを可能にします。

しかし、そのような方法ではきれいに処理しきれないケースも稀には存在します。 `Proc` オブジェクトをモデルデータとして与えると、 手続き的にテンプレートを変更することができます。

### コードと出力

コード:

      require "amrita/template"
      include Amrita

      tmpl = TemplateText.new <<END
      <ul>
        <li id=list><font id=data></font>
      </ul>
      END

      languages = %w(java Ruby perl python c++ c sml cobol fortran ada lisp)
      i = 0
      data = {
        :list => languages.collect do |l|
          {
            :data => proc do |elem|
              if l == "Ruby" # Ruby is special language to me!
                # use Amrita::Element's methods to edit
                elem[:color] = "red"
                elem[:size] = "big"
                elem.set_text("I love Ruby!")
                # e() is Amrita's method that generates Element
                e(:em) { elem }
              else
                i = i + 1 # i is shared by all procs
                elem[:color] = i%2 == 0 ? "blue" : "black"
                elem.set_text(l)
                elem
              end
            end
          }
        end
      }

      tmpl.prettyprint = true
      tmpl.expand(STDOUT, data)

出力:

       <ul>
         <li><font color="black">java</font> </li>
         <li><em><font color="red" size="big">I love Ruby!</font></em> </li>
         <li><font color="blue">perl</font> </li>
         ...
       </ul>

### 説明

モデルデータとして Proc オブジェクトが渡されると、amrita は、 テンプレート展開時に、そのProc を呼び出します。 その際、パラメータとして、対応する `id` のついたHTML要素を Amrita::Element オブジェクトとして 渡します。 そして、その Proc の結果とテンプレートを置き換えます。

この Proc の中で、次のようなメソッドを利用して自由にテンプレートを編集することができます。

属性値の設定

         elem[:color] = "red"

要素にテキストを設定する

         elem.set_text("I love Ruby!")

Amrita#e メソッドによって、新しいHTML要素を生成する

         e(:em) { elem }

## 既存のクラスをモデルデータとして使用する

HashやArrayだけでなく、 既存のクラス(Rubyの標準クラスやユーザ作成のクラス)のオブジェクトを そのまま、amrita のモデルデータとして使用する例です。

### コードと出力

コード:

      require "amrita/template"
      include Amrita

      tmpl = TemplateText.new <<END
      <span id="time">
        <span id="year"></span>/<span id="month"></span>/<span id="day"></span>
      </span>
      END

      t = Time.now
      t.extend Amrita::ExpandByMember

      data = { :time=>t }
      tmpl.compact_space = true
      tmpl.expand(STDOUT, data)

出力:

      2002/7/17

### 説明

もし、モデルデータが、Amrita::ExpandByMember というモジュールをincludeしていたら、 amritaは `id` 属性の値をメソッド名と見なして、そのメソッドを呼び出します。

このサンプルでは、+:time+に対応するデータは、Rubyの標準のTimeオブジェクトですが、 ExpandByMember モジュールを `extend` しています。 それで `id`属性の値である `year` をメソッド名とみなし、amritaは `t` に対してそのメソッドの呼出しを行います。

その結果 `<span id="year"></span>` という部分は、 `t.year`の結果 "2002" と展開され、他の部分も同様に処理されて 次のように展開されます。

      <span><span>2002</span>/<span>7</span>/<span>17</span></span>

amrita は 属性のない `<span>` 要素は削除しますので、最終的な出力は

      2002/7/17

となります。

## プリコンパイル

amrita は HTML テンプレートを Ruby のコードにコンパイルすることができます。

### コードと出力

コード(table.rbにコンパイラを利用するために追加した分) :

      tmpl = TemplateText.new(TEMPLATE)
      tmpl.use_compiler = true
      tmpl.set_hint_by_sample_data(data) # これを追加するとそのデータに最適化します
      tmpl.expand(STDOUT, data)  #
      puts "----code generated by Amrita -----------"
      puts tmpl.src
      puts "----code generated by Amrita end -------"

出力はtable.rbと同じですが、 コンパイラの出力したコードとベンチマークが追加されています。

私のCrusoe TM5600マシン(NEC Lavie MX)での出力は次のようになります。

      43.068354 seconds for 1000 times without compiling
      5.078764 seconds for 1000 times with pre-compiled code

### 説明

基本的には、次の処理を追加するだけでコンパイラを使用できます。

      tmpl.use_compiler = true

これ以降、`expand` はコンパイルされたRubyコードで実行されます。 prettyprintの機能はサポートされませんが、それ以外は同じ結果になります。

サンプルデータを利用して、最適化を行なうには次の処理を追加します。

And optionally give a sample data to amrita.

      tmpl.set_hint_by_sample_data(data)

amritaのコンパイラは、このデータを出力するRubyコードの最適化のために使用します。 従って、渡すモデルデータの構造が変化したら、再度、その新しいデータで `set_hint_by_sample_data` を呼ぶ必要があります。

amritaのコンパイラは、部分的にインタプリターモードを含めることができます。 部分的に構造が変化するデータに対して、コンパイラを利用する場合は、 サンプルデータの対応する部分(変化するデータの部分)に、`nil` を渡す必要があります。

コンパイラは、Element::expandを使用するようなコードを対応する個所に挿入します。

このようにして、スピードと柔軟性のトレードオフを自由に取ることができます。

## サニタイジン -- XSS(クロスサイトスクリプティング)対策

amritaには、XSS対策として、Amrita::Sanitizer というモジュールが組込まれています。 Amrita::Formatter は自動的にこのモジュールを使用します。

I will provide interface to controle sanitizer through Amrita::Template in future release.

### コードと出力

      require "amrita/template"
      include Amrita

      tmpl = TemplateText.new %q[<p id=body>xxx</p>]

      data = {
         :body=>"I want to insert new line.<br>But I can't"
      }

      tmpl.expand(STDOUT, data) # <p>I want to insert new line.&lt;br&gt;But I can't</p>
      puts

      data = {
          :body=>noescape { "I can insert new line <br>with escape { ... } <br>But it may be dangerous" }
      }

      tmpl.expand(STDOUT, data) # <p>I can insert new line <br>with escape { ... } <br>But it may be dangerous</p>
      puts

      data = {
          # The attacker expected amrita to print <p yyy=""></p>XSS attack<p>But amrita sanitize it!</p>
          :body=>a(:yyy=>%q["></p>XSS attack here<p]) { "But amrita sanitize it!" }
      }
      tmpl.expand(STDOUT, data) # <p yyy="&quot;&gt;&lt;/p&gt;XSS attack here&lt;p">But amrita sanitize it!</p>
      puts

      tmpl = TemplateText.new %q[<a id=body>href is treated in a special way</a>]

      data = {
          :body=>a(:href=>%q[javascript:alert('hello')])
      }
      tmpl.expand(STDOUT, data) # <a href="">href is treated in a special way</a>
      puts

### 説明

#### テキスト

xhtml/html 内のテキストとして危険な文字、(<>&) は自動的にエスケープされます。

        "<abc>" => "&lt;abc&gt;"

#### 属性値

属性値として危険な文字(<>&"')は自動的にエスケープされます。

#### URL用属性の扱い

<a>要素のhref属性のように、URLを値として持つ属性値は、特別扱いされます。

どの属性値を特別扱いするかの詳細については tag.rb を参照してください。

これらの属性値は、次のようにさらに厳しくチェックされます。

      * これらの属性値はURLとして許されない文字を持つことはできません
      * これらの属性値は許されないスキーム(プロトコル指定)を持つことはできません

この条件に違反したら、属性値はnilで置きかえられて`<a href="">....</a>` のように表示されます。

次のように`setup_taginfo` メソッドを再定義することで、 どの属性をこのように扱うか(扱わないか)をカスタマイズすることができます。

        t = TemplateFile.new ...

        def t.setup_taginfo
          ret = TagInfo.new
          ret[:aaa].set_url_attr(:bbb)
          ret
        end

この場合は、 `aaa`要素の`bbb`属性は、URLとしてサニタイズされます。

#### サニタイズを無効にする

Amrita::SanitizedString オブジェクトをモデルデータに含めることで、 この機能を無効にすることができます。

        t = TemplateText.new '<p id="a">sample_text</p>'
        t.expand(STDOUT, { :a=>"<xxx>" })                  # => <p>&lt;xxx&gt;</p>
        t.expand(result, { :a=>SanitizedString["<xxx>"] }) # => <p><xxx></p>

この機能は、XSSについて理解した上で、充分注意して利用してください。

なお、もうひとつ同様の効果を得る方法として、`escape {...}` で モデルデータを囲むという方法もあります。