Subject: Re: easily embedding html into Lisp
From: rpw3@rpw3.org (Rob Warnock)
Date: Thu, 16 Sep 2004 05:00:55 -0500
Newsgroups: comp.lang.lisp
Message-ID: <1NqdnV4opIrK-dTcRVn-jg@speakeasy.net>
Pascal Bourguignon  <spam@mouse-potato.com> wrote:
+---------------
| rpw3@rpw3.org (Rob Warnock) writes:
| > You should look at <http://www.cliki.net/Lisp%20Markup%20Languages>,
| > which lists several readily-available packages which already do
| > this (or similar). I tend to use HTOUT, but CL-WHO is also nice.
| > Also see <http://www.cliki.net/HTML-from-sexpr>.
...
| > (defun list-html-table (lists &key (stream *standard-output*))
| >   (let ((column-names (car lists))
| >         (rows (cdr lists)))
| > 	(with-html-output (s stream)
| > 	  (:h3 (fmt "Results: ~d rows" (length rows)))
| > 	  (lfd)
| > 	  ((:table :border 1 :cellspacing 0 :cellpadding 1) ; make compact
| > 	    (:tr (lfd)
| > 	      (loop for name in column-names do
| > 		(htm ((:th :nowrap) name) (lfd))))
| > 	    (loop for row in rows do
| > 	      (htm (:tr (lfd)
| > 		     (loop for v in row do
| > 		       (let ((vv (if (or (null v) (string-equal v ""))
| > 				   "&nbsp;"
| > 				   (escape-string v))))
| > 			 (htm ((:td :nowrap) vv) (lfd))))))))
| > 	  (lfd))))
...
| Also, since HTOUT binds locally an output stream, and doesn't take it
| as argument to the non function keyword tags, I don't see how you
| could split the description of a page over serveral functions. On the
| other hand, for now I have to use a global context to generate HTML,
| but this allow be to define methods to insert items on pages such as
| headers, feets, forms, etc.  This could be easily done with Andreas'
| solution.  Would HTM work outside of the lexical context of
| WITH-HTML-OUTPUT?
+---------------

The answer to the latter question is "no", but why does that matter?
You can easily "split the description of a page over several functions"
by simply passing the stream as a parameter to each function and then
opening another WITH-HTML-OUTPUT lexical context in the called function,
exactly as the example LIST-HTML-TABLE function I gave above did.

You can also pass closures to other routines to let them do "callbacks"
to customize their behavior.

E.g., the actual LIST-HTML-TABLE function I use in production is
somewhat more complicated than the above example, in that it allows
you to provide a callback function which is called at various points
in generating the HTML table, so that the callback function can add
extra rows, extra columns, etc. For example, the above LIST-HTML-TABLE
is well-suited to displaying a list of lists which are the results
of an SQL query, but by adding a callback to the invocation, pages
can add "Edit" or "Delete" buttons (say) on each line of the query
results *without* having to copy/mutate the list of list structure.

I also tend to use a lot of "template page" functions which take one
or more closures and wrap standardized decoration (headers, nav bars,
footers, etc.) around the specific page content passed in the closures.

Yes, called functions (including any callbacks you pass) have to do
another WITH-HTML-OUTPUT, but that's just compile-time overhead -- the
run-time overhead is almost zero [one LET].


-Rob

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607