with-html-output adds string when using :li - common-lisp

I'm working my way through Lisp For The Web by Adam Tornhill and I'm stuck at generating a html page with an li element in it.
(with-html-output (*standard-output* nil :prologue t :indent t)
(htm
(:li (:a :href "Link" "Vote!")
)))
When I compile it the following output is printed to the REPL
(with-html-output (*standard-output* nil :prologue t :indent t)
(htm
(:li (:a :href "Link" "Vote!")
)))
<!DOCTYPE html>
<li>
<a href='Link'>Vote!
</a>
</li>
"
<li>
<a href='Link'>Vote!
</a>
</li>"
The string at the end of the output usually is not added and a site including this does not render in hunchentoot. Adding :ol around :li does not help and I wanted to keep the example minimal.
The code from the book as reference:
(define-easy-handler (retro-games :uri "/retro-games") ()
(standard-page (:title "Top Retro Games")
(:h1 "Vote on your all time favourite retro games!")
(:p "Missing a game? Make it available for votes " (:a :href "new-game" "here"))
(:h2 "Current stand")
(:div :id "chart" ; Used for CSS styling of the links.
(:ol
(dolist (game (games))
(htm
(:li (:a :href (format nil "vote?name=~a" (escape-string ; avoid injection attacks
(name game))) "Vote!")
(fmt "~A with ~d votes" (name game) (votes game)))))))))

What you see first is what the form prints to *standard-output* when evaluated. The string seen after that is the form's result, printed by the REPL. As your Hunchentoot handler is only interested in what goes to the output stream, the result does not matter.
To simply get the result as string, you can use with-html-output-to-string:
(with-html-output-to-string (str nil :prologue t :indent t)
(htm
(:li (:a :href "Link" "Vote!"))))
On the other hand, to suppress the result string and only see the document as written out, you can do something like this:
(progn
(with-html-output (*standard-output* nil :prologue t :indent t)
(htm
(:li (:a :href "Link" "Vote!"))))
(values))

Related

How to produce HTML from a list

The usual way to generate HTML using CL-WHO is by using the macros with-html-output and with-html-output-to-string. This uses special syntax. For example:
(let ((id "greeting")
(message "Hello!"))
(cl-who:with-html-output-to-string (*standard-output*)
((:p :id id) message)))
Is it possible to write the data ((:p :id id) message) as a list instead of using the macro syntax shown above? For example, I would like to define the HTML as a list like this:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
;; What should I do here to generate HTML using CL-WHO?
)
Can CL-WHO take a normal Lisp list and produce HTML from the list?
You want to insert code into an expression.
Actually you would need eval:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
(eval `(cl-who:with-html-output-to-string (*standard-output*)
,the-html)))
But this is not good to use eval.
But a macro contains an implicite eval.
I would define a macro for this and call the macro:
(defun html (&body body)
`(cl-who:with-html-output-to-string (*standard-output*)
,#body))
;; but still one doesn't get rid of the `eval` ...
;; one has to call:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
(eval `(html ,the-html)))

Output prompts are on top of each other

I am reading the book Practical Common Lisp. I typed the simple CD database shown in Chapter 3. See below. When I run the (add-cds) program the result is a prompt containing two prompts on top of each other (more precisely, one prompt after another, on the same line):
(add-cds)
=> Title: Artist:
Why is it doing this? The program should give me the Title: prompt first and the Artist: prompt only after I've typed in a value for Title: followed by newline. I am pretty sure that I typed in the program faithfully. How do I fix this?
(defvar *db* nil)
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
(defun add-record (cd) (push cd *db*))
(defun prompt-read (prompt)
(format *query-io* "~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))
(defun prompt-for-cd ()
(make-cd
(prompt-read "Title")
(prompt-read "Artist")
(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
(y-or-n-p "Ripped [y/n]")))
(defun add-cds ()
(loop (add-record (prompt-for-cd))
(if (not (y-or-n-p "Another? [y/n]: ")) (return))))
What's happening is that the newline after (add-cds) is being left in the input stream (because the REPL stops reading as soon as it sees the matching close parenthesis), so the first read-line is reading that as a blank line and returning immediately. Call clear-input before calling read-line to ignore this and wait for new input.
(defun prompt-read (prompt)
(format *query-io* "~a: " prompt)
(force-output *query-io*)
(clear-input *query-io*)
(read-line *query-io*))

Parenscipt not compiling valid expression?

I have this parenscript macro:
;;; Parenscript macro for showModal() and close() methods for pop-up dialogs.
;;;Takes the dialog's id, button for opening the dialog's id, and closing button's id.
(defpsmacro open-close-modal-dialog (dialog-id element-id-1 element-id-2 &key open close open-args close-args)
(let ((dialog (ps-gensym)))
`(progn
(setf ,dialog (chain document (get-element-by-id ,dialog-id)))
(setf (chain document (get-element-by-id ,element-id-1) onclick)
(lambda (,#open-args)
(progn
,#open
(funcall (chain ,dialog show-modal)))))
(setf (chain document (get-element-by-id ,element-id-2) onclick)
(lambda (,#close-args)
(progn
,#close
(funcall (chain ,dialog close))))))))
And I'm using it in a Hunchentoot handler like so:
(define-easy-handler (student-name :uri "/student-info") (name)
(let ((student (student-from-name name)))
(standard-page (:title "Ashtanga Yoga Osaka | Student Page"
:script (ps
(defun init ()
(open-close-modal-dialog "editPassDialog" "getPass" "submitPass"
;; This is the pop-up dialog
:open (ps(defvar frm (chain document (get-element-by-id "editPass"))))))
(setf (chain window onload) init)))
;; Main form
(:form :action "/edit-student" :method "post" :id "editStudent"
(:p "Name" (:input :type "text" :name "name" :class "txt" :value (format nil "~A" (name student))))
(:P "Email" (:input :type "email" :name "email" :class "txt" :value (format nil "~A" (email student))))
(:p "Passes" (:select :name "passlist"
(dolist (pass (pass student))
(htm
(:option :id "pass" :value (pass->json pass)
(fmt "~A ~A" (print-month (getf pass :date))
(print-year (getf pass :date)))))))
(:button :type "button" :id "getPass" :class "btn" "Get Pass"))
(:input :type "hidden" :name "previous" :value nil) ; previous value of the pass
(:input :type "hidden" :name "old-name" :value name) ; old name of student, used for retrieving the correct instance
(:p (:input :type "submit" :value "Edit Info" :class "btn")))
;; Pop-up dialog for editing passes
(:dialog :id "editPassDialog"
(:h1 "Edit Pass")
(:form :action "#" :method "post" :id "editPass"
(:p "Date bought" (:input :type "text" :name "date" :class "txt"))
(:p "Type" (:input :type "text" :name "type" :class "txt"))
(:p "Amount Paid" (:input :type "text" :name "amt"))
(:p (:button :type "button" :class "btn" :id "submitPass" "Edit Pass")))))))
Now when I load the system through Quicklisp, I get this error:
; caught ERROR:
; during macroexpansion of
; (PS
; (DEFUN INIT # ...)
; (SETF #)).
; Use *BREAK-ON-SIGNALS* to intercept.
;
; The Parenscript form (DEFVAR FRM
; (CHAIN DOCUMENT
; (GET-ELEMENT-BY-ID
; editPass))) cannot be compiled into an expression.
Which is strange, because I can define this form in the REPL:
SHALA-SYS> (macroexpand-1 '(ps (defvar frm (chain document (GET-ELEMENT-BY-ID "editPass")))))
"var frm = document.getElementById('editPass');"
And if I remove the :open and it's arguments, the system loads, then I add :open and args back in and recompile the handler and it compiles without a problem.
Any thoughts?
This will happen if you have both the defpsmacro and the use of the defined macro in the same file. I haven't had the time to dig into parenscript code deep enough to understand what exactly goes wrong with the order of evaluation, but the end result is that, when compiling the file, the macro definition does not exist at the time of the 'compilation' of the ps form.
As a solution, move your parenscript macros into a separate file and make your other code files depend on it.
As a side note, the (ps ...) form on the :open keyword argument is unnecessary - the open parameter is already expanded inside parenscript code in your macro. Additionally, ,# is incorrect at the expansion of open as well - and these two bugs happen to cancel each other.
(progn
,#open
(funcall (chain ,dialog show-modal)))
;; with :open (ps (foo)) expands to
"ps; foo(); dialog.showModal();"
;; and with :open (foo) expands to
"foo; dialog.showModal();"
(progn
,open
(funcall (chain ,dialog show-modal)))
;; would expand :open (ps (foo)) to
"ps(foo()); dialog.showModal();"
;; and :open (foo) to
"foo(); dialog.showModal();"
;; which is what you intended.
Also, funcall is not necessary in that piece of code; you could simply use (chain ,dialog (show-modal)).
Ok so it seems I didn't (still don't) understand how the defpsmacro works. Changing that with a normal defmacro wrapped in a ps form solved the problem.
So the macro code is now:
(ps (defmacro open-close-modal-dialog (dialog-id element-id-1 element-id-2 &key open close open-args close-args)
(let ((dialog (ps-gensym)))
`(progn
(setf ,dialog (chain document (get-element-by-id ,dialog-id)))
(setf (chain document (get-element-by-id ,element-id-1) onclick)
(lambda (,#open-args)
(progn
,#open
(funcall (chain ,dialog show-modal)))))
(setf (chain document (get-element-by-id ,element-id-2) onclick)
(lambda (,#close-args)
(progn
,#close
(funcall (chain ,dialog close)))))))))
If any ParenScript expert can explain what was wrong I'd be much obliged.

How to parse XML with cxml and stp containing ampersand

I want to parse the following XML-Code:
(cxml:parse "<BEGIN><URL>www.some.de/url?some=data&bad=stuff</URL></BEGIN>" (stp:make-builder))
this results in
#<CXML:WELL-FORMEDNESS-VIOLATION "~A" {1003C5E163}>
as '&' is a XML special character. But if I use &? instead the result is:
(cxml:parse "<BEGIN><URL>www.some.de/url?some=data&bad=stuff</URL></BEGIN>" (stp:make-builder))
=>#.(CXML-STP-IMPL::DOCUMENT
:CHILDREN '(#.(CXML-STP:ELEMENT
#| :PARENT of type DOCUMENT |#
:CHILDREN '(#.(CXML-STP:ELEMENT
#| :PARENT of type ELEMENT |#
:CHILDREN '(#.(CXML-STP:TEXT
#| :PARENT of type ELEMENT |#
:DATA "www.some.de/url?some=data")
#.(CXML-STP:TEXT
#| :PARENT of type ELEMENT |#
:DATA "&")
#.(CXML-STP:TEXT
#| :PARENT of type ELEMENT |#
:DATA "bad=stuff"))
:LOCAL-NAME "URL"))
:LOCAL-NAME "BEGIN")))
Which is not exactly what I expected as there should only be one CXML-STP:TEXT child with DATA "www.some.de/url?some=data&bad=stuff"
How can I fix this wrong(?) behavior?
This behavior, although, not very convenient, is, actually, present in many other XML parsers as well. Probably the reason for it is to be able to parse arbitrary ​XML entities and apply some user-defined rules to them. Although, it may be just a by-product of the parser implementation. I couldn't find out yet.
For the SAX variant of the parser I came to the following approach:
(defclass my-sax (sax:sax-parser-mixin)
((title :accessor title :initform nil)
(tag :accessor tag :initform nil)
(text :accessor text :initform "")))
(defmethod sax:start-element ((sax my-sax) namespace-uri local-name
qname attributes)
(with-slots (tag tagcount text) sax
(setf tag local-name
text "")))
(defmethod sax:characters ((sax my-sax) data)
(with-slots (title tag text) sax
(switch (tag :test 'string=)
("text" (setf text (conatenate 'string text data)))
("title" (setf title data)))))
(defmethod sax:end-element ((sax my-sax) namespace-uri local-name qname)
(with-slots (title tag text) sax
(when (string= "text" local-name)
;; process (text sax)
)))
I.e. I collect the text in sax:characters and process it in sax:end-element. In STP you, probably, can get away even easier by just concatenating neighboring text elements.

Hunchentoot/cl-who page composition

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

Resources