Hunchentoot / cl-who страница композиции - PullRequest
4 голосов
/ 25 сентября 2010

Композиция страницы Hunchentoot / cl-who

Я пытаюсь собрать несколько страниц в hunchentoot в качестве эксперимента, и я сталкиваюсь с неожиданной стеной. В качестве примера у меня есть следующий шаблон макроса.

(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))))

Теперь, когда у меня есть чисто текстовая страница или страница, заполненная HTML-литералами, такими как

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

все в порядке. Страница выводится правильно, и я сразу вижу усилия моего кода. Однако, когда у меня есть страница, которая состоит из избыточных элементов, это не так просто. Например, допустим, у меня есть страница, на которой по какой-либо причине я хочу отобразить три RSS-канала. Это достаточно сложный компонент, который я хочу абстрагировать, поэтому, на мой взгляд, я смогу сделать что-то вроде

(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))))))))

Но результатом вышеописанного является страница «Ошибка сервера». Я не совсем уверен, почему; page-template - это макрос, поэтому вызовы publish-newsfeed не следует расширять до тех пор, пока они не попадут в контекст with-html-output-to-string. Может кто-нибудь сказать мне, что я делаю не так?

Кроме того, при ближайшем рассмотрении различных учебных пособий Hunchentoot / cl-who, ни один из них, по-видимому, не выполняет такую ​​композицию страниц. Может кто-нибудь с некоторым опытом Hunchentoot сказать мне, каков правильный / канонический способ разложения страницы на компоненты?


РЕДАКТИРОВАТЬ:

Правильный ответ Рамаррена ниже; макросы with-html-output работают по разным правилам оценки. Версия publish-newsfeed, которая действительно работала бы в этой ситуации, на самом деле

(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")))))))))))))

Обратите внимание на удаление mapcar для dolist (я - Схемер, не беспокойтесь о том, чтобы мне нравились лямбды, но здесь они не были правильным выбором), и использование htm для экранирования блоков html s-exps (h-exps?), которые иначе не были бы в контексте with-html-output. Наконец, мне пришлось обернуть текст, но НЕ :href свойства в (str ), чтобы заставить их динамически расширяться.

1 Ответ

5 голосов
/ 25 сентября 2010

Макрос with-html-output-to-string расширяет свое тело, используя специальные правила оценки . В частности, любые нераспознанные формы остаются как есть, что означает, что макросы не раскрываются до создания кода, генерирующего html, а это означает, что к тому моменту, когда ваш макрос publish-newsfeed раскрывается стандартным компилятором, он больше не находится в контексте with-html-output-to-string. Это ясно при расширении макроса вручную , особенно при использовании функции макроразложения слизи.

Чтобы это работало, вы должны сделать publish-newsfeed функцией и использовать внутри нее with-html-output с одним и тем же потоком (либо предположить, что ` стандартный вывод везде, либо явно передать поток).

...