- Fast (even faster if you compile it)
- Safety
- Support multiple document types (markup, xml, html, xhtml)
- Output with doctype
- Direct output to stream
Homepage: https://github.com/arielnetworks/cl-markup
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><p>abc</p></div>" ; !! ;; This is a feature called auto-escape and it provides correct ;; solutions for these kind of tasks: (markup (:p "1<3")) => "<p>1<3</p>" (markup (:p "R&B")) => "<p>R&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