Generation of HTML markup with Common Lisp using cl-markup.

(defpackage :cl-markup-test (use :cl))
(in-package :cl-markup-test)
;; central feature is MARKUP:
(markup (:p 123))
=> "<p>123</p>"
;; MARKUP expects list arguments, atoms are beeing rejected
(markup "abc")
=> The value "abc" is not of type LIST.
   [Condition of type TYPE-ERROR]
;; arguments can be multiple lists
(markup (:p 123) (:p 234))
=> "<p>123</p><p>234</p>"
;; first element of list is used as tag, not bothering what type it is
;; and wether it smells like HTML
(markup ("p" 123))
=> "<p>123</p>"
(markup (p 123))
=> "<p>123</p>"
(markup ('p 123))
=> "<'p>123</'p>" ; oops
(markup (123 p))
=> error: The variable P is unbound.
   [Condition of type UNBOUND-VARIABLE]
(markup (123 "p"))
=> "<123>p</123>"  ; !!
;; whole HTML pages can be produced with cl-markup macros HTML, HTML5,
;; XHTML and XML.
(html5 (:p 42))
=> "<!DOCTYPE html><html><p>42</p></html>"
;; lisp expressions can be inserted everywhere except first position of list
(markup (:p (concatenate 'string "1" "2" "3")))
=> "<p>123</p>"
;; these expressions are considered good citizens when they produce
;; string results, else:
(markup (:p (+ 100 20 3)))
=> The value 123 is not of type STRING.
   [Condition of type TYPE-ERROR]
;; weird things seem to happen when combining snippets
(let ((snip (markup (:p "abc"))))
  (markup (:div snip)))
=> "<div>&lt;p&gt;abc&lt;/p&gt;</div>" ; !!
;; This is a feature called auto-escape and it provides correct
;; solutions for these kind of tasks:
(markup (:p "1<3"))
=> "<p>1&lt;3</p>"
(markup (:p "R&B"))
=> "<p>R&amp;B</p>"
;; auto-escaping can be turned off like this
(let* ((*auto-escape* nil)
       (snip (markup (:p "abc"))))
  (markup (:div snip)))
=> "<div><p>abc</p></div>"
;; another way to shelter strings against greedy auto-escape
;; is wrapping them in a list
(let ((snip (markup (:p "abc"))))
  (markup (:div (list snip))))
=> "<div><p>abc</p></div>"
;; same result using backquote syntax
(let ((snip (markup (:p "abc"))))
  (markup (:div `(,snip))))
=> "<div><p>abc</p></div>"
;; and this can also be done with CL-MARKUPs RAW macro.  RAW sounds
;; like a kind of strange name for a list-wrapping feature.  Maybe the
;; name tries to express that this procedure treats the string
;; like the raw markup from which it evolved.
(let ((snip (markup (:p "abc"))))
  (markup (:div (raw snip))))
=> "<div><p>abc</p></div>"
;; and now for the best of all: it is possible to write cl macros
;; producing HTML snippets.
(defmacro snip (name)
  `(markup (:p ,name)))
(snip "foo")
=> "<p>foo</p>"
;; It is save to handle string results.
;; Trying to pass markup asks for trouble
(defmacro snip-markup (name)
  `(:p ,name))
(markup (snip-markup "foo"))
=> "<snip-markup>foo</snip-markup>" ;; oops
;; MARKUP is a macro, it does not evaluate its arguments

Leave a Reply

Your email address will not be published. Required fields are marked *