views:

28

answers:

1

Hunchentoot/cl-who Page Composition

I'm trying to put together a few pages in hunchentoot as an experiment, and I'm running into an unexpected wall. As an example, I have the following template macro.

(defmacro page-template ((&key title) &body body)
  `(with-html-output-to-string 
    (*standard-output* nil :prologue t :indent t)
    (:html :xmlns "http://www.w3.org/1999/xhtml" :xml\:lang "en" :lang "en"
           (:head (:meta :http-equiv "Content-Type" :content "text/html;charset=utf-8")
                  (:title ,(format nil "~@[~A - ~]Test Site" title)))
           (:body ,@body))))

Now when I have a pure text page, or one filled with html literals like

(define-easy-handler (test-page :uri "/") ()
  (page-template (:title "Splash Page") (:p "Testing testing")))

everything is a-ok. The page outputs properly and I can see te efforts of my code instantly. However, when I have a page which is made up of redundant elements, it's not as simple. For example, lets say I have a page on which for whatever reason I want to display three RSS newsfeeds. This is a sufficiently complex component that I want to abstract it out, so to my minnd, I should be able to do something like

(define-easy-handler (test-feed :uri "/feeds") ()
  (page-template (:title "Splash Page") 
                 (publish-newsfeed "http://nf-one.html")
                 (publish-newsfeed "http://nf-two.html")
                 (publish-newsfeed "http://nf-three.html")))


(defmacro publish-newsfeed (url &optional (item-limit 5))
  (flet ((get-text (s-tree node-path) (car (last (xmls-tools:find-subtree s-tree node-path)))))
    (let ((rss-feed (xmls:parse (drakma:http-request url))))
      `(:div :class "rss-feed"
              (:a :href ,(get-text rss-feed '("channel" "link")) :target "_top" (:h1 ,(get-text rss-feed '("channel" "title"))))
              (:ul ,@(mapcar #'(lambda (item)
                                 `(:li (:a :href ,(get-text item '("link")) :target "_top" (:h2 ,(get-text item '("title"))))
                                       (:p :class "date" ,(get-text item '("pubDate")))
                                       (:p ,(get-text item '("description")))))
                             (let ((items (xmls-tools:find-all-children (xmls-tools:find-subtree rss-feed '("channel")) "item")))
                               (if (> (length items) item-limit) (subseq items 0 item-limit) items))))))))

But the result of the above is a "Server Error" page. I'm not quire sure why; page-template is a macro so the calls to publish-newsfeed shouldn't be expanded until they're in the context of with-html-output-to-string. Can anyone tell me what I'm doing wrong?

Also, on closer inspection of the various Hunchentoot/cl-who tutorials, none of them seems to do this kind of page composition. Can anyone with some Hunchentoot experience tell me what the correct/canonical way of decomposing a page into components is?


EDIT:

Correct response by Ramarren below; the with-html-output macros work under different evaluation rules. The version of publish-newsfeed that would actually work in this situation is actually

(defun publish-newsfeed (url &optional (item-limit 5))
  (flet ((get-text (s-tree node-path) (car (last (xmls-tools:find-subtree s-tree node-path)))))
    (let* ((rss-feed (xmls:parse (drakma:http-request url)))
           (items (xmls-tools:find-all-children (xmls-tools:find-subtree rss-feed '("channel")) "item"))
           (ltd-items (if (> (length items) item-limit) (subseq items 0 item-limit) items)))
      (with-html-output 
       (*standard-output* nil :indent t)
       (:div :class "rss-feed"
             (:a :href (get-text rss-feed '("channel" "link")) :target "_top" (:h1 (str (get-text rss-feed '("channel" "title")))))
             (:ul (dolist (item ltd-items)
                    (htm (:li (:h2 (:a :href (get-text item '("link")) :target "_top" (str (get-text item '("title")))))
                              (:p :class "date" (str (get-text item '("pubDate"))))
                              (:p (str (get-text item '("description")))))))))))))

Note the removal of mapcar for dolist (I'm a Schemer, don't give me too much of a hard time about liking lambdas, but they weren't the right choice here), and the use of htm to escape blocks of html s-exps (h-exps?) that wouldn't otherwise be in context for with-html-output. Finally, I had to wrap text but NOT :href properties in (str ) to get them to expand dynamically.

+1  A: 

The macro with-html-output-to-string expands its body using special evaluation rules. In particular, any not recognized forms are left as is, which means macros are not expanded before the html generating code is generated, which means by the time your publish-newsfeed macro is expanded by the standard compiler it is no longer in context ofwith-html-output-to-string. This is clear when expanding the macro manually, especially using slime macroexpansion feature.

To make it work you should make publish-newsfeed a function and use with-html-output inside it with the same stream (either assume `standard-output everywhere or pass the stream explicitly).

Ramarren