CL-MARKUP - Modern HTML generator library for Common Lisp.

  • Fast (even faster if you compile it)
  • Safety
  • Support multiple document types (markup, xml, html, xhtml)
  • Output with doctype
  • Direct output to stream


License: LLGPL

Some experiments:

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