2010年1月7日木曜日

Rendering HTML

webブラウザがアプリのURLを訪ねると、ブラウザはリクエスト構造を造って、webアプリにそれを送ります。手始めに、リクエストを受け取り、レスポンスを生成する機能を作りましょう。レスポンスの基本はHTMLページを表示する事です。

(define html-response/c
(or/c string?
(or/c (cons/c symbol? (listof html-response/c))
(cons/c symbol?
(cons/c (listof (list/c symbol? string?))
(listof html-response/c))))))

例:

"hello"はHTMLではhelloと表示されます。

<p>This is an example</p>は

'(p "This is an example")

で生成されます。

<a href="link.html">Past</a>は

'(a ((href "link.html")) "Past")

で生成されます。

<p>This is <div class="emph">another</div> example.</p>は

'(p "This is " (div ((class "emph")) "another") " example.")

で生成されます。

これらhtml-responseは直接conslistで作成できます。しかしながら、それでは厳しい表記となるでしょう。比較してみてください:

(list 'html (list 'head (list 'title "Some title"))
(list 'body (list 'p "This is a simple static page.")))


対:


'(html (head (title "Some title"))
(body (p "This is a simple static page.")))

両者とも同じhtml-responseを生成しますが、後者の方が記述も解読もはるかに簡単です。ここでは、How to Design Program:13章で解説された拡張リスト省略表記を使っています。先頭の引用符はリスト構造を表していて、これを使えば自信を持って静的なhtmlレスポンスを作る事が出来るのです。

しかしながら、動的コンテンツでは、単純なリスト省略表記を使うと問題が生じます。html-response構造に式を挿し入れたい場合、単純なリスト省略表記のアプローチは使えません。と言うのも、それらはリテラルなリスト構造の一部として扱われてしまうからです!

欲しいのは、構造の一部だけは普通の式として扱えるオプション付きの、クオートされたリスト省略記法の簡易性を備えた表記法、です。つまり、簡易表記可能な、動的に埋め込めるプレースホルダー付きのテンプレートを定義したいわけです。

Schemeはこのテンプレート機能を逆引用符として提供しています。逆引用とは、全体構造の直前にバッククオートを使う事です。普通のクオートされたリスト省略記法のように、殆どのリスト構造はネストされていてもリテラルに保護されます。一部の式の評価値を挿入したい場所で、その式の直前にクオート解除の為のコンマを挿し入れます。例えば:

; render-greeting: string -> html-response
; name を受け取り、動的 html-response を生成する。
(define (render-greeting a-name)
`(html (head (title "Welcome"))
(body (p ,(string-append "Hello " a-name)))))

Exercise.(listof post?)を受け取り、そのコンテンツのhtml-responseを生成する機能、render-postsを書いてください。

render-posts : ((listof post?) . -> . html-response/c)

例えば:

(render-posts empty)

は次を生成します:

'(div ((class "posts")))

一方、

(render-posts (list (make-post "Post 1" "Body 1")
(make-post "Post 2" "Body 2")))

は次を生成します:

'(div ((class "posts"))
(div ((class "post")) "Post 1" "Body 1")
(div ((class "post")) "Post 2" "Body 2"))



render-posts機能を入手したので、webアプリに戻ってhtml-responseを返すstart機能を実装しましょう。

#lang web-server/insta

; ブログは (listof post)
; 投稿は (make-post title body)
(define-struct post (title body))

; BLOG: blog
; 静的ブログ
(define BLOG
(list (make-post "First Post" "This is my first post")
(make-post "Second Post" "This is another post")))

; start: request -> html-response
; リクエストを受け取り、webコンテンツの全てを
; 表示するページを生成する
(define (start request)
(render-blog-page BLOG request))

; render-blog-page: blog request -> html-response
; ブログとリクエストを受け取り、ブログのコンテンツの
; html-response を生成する
(define (render-blog-page a-blog request)
`(html (head (title "My Blog"))
(body (h1 "My Blog")
,(render-posts a-blog))))

; render-post: post -> html-response
; 投稿を受け取り、投稿のhtml-response要素を生成する
(define (render-post a-post)
`(div ((class "post"))
,(post-title a-post)
(p ,(post-body a-post))))

; render-posts: blog -> html-response
; ブログを受け取り、全投稿のhtml-response要素を
; 生成する
(define (render-posts a-blog)
`(div ((class "posts"))
,@(map render-post a-blog)))

Runを押せば、webブラウザがブログの投稿を表示します。

0 件のコメント:

コメントを投稿