CL-MARKUP - Modern markup generation library for Common Lisp

About

Usage

(html
 (:body
  (:p :id "title" "aiueo")))
;=> "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"
;    \"http://www.w3.org/TR/html4/loose.dtd\">
;    <html><body><p id=\"title\">aiueo</p></body></html>"

Installation

Really fast this?

Macros, provided by CL-MARKUP, generates really efficient code. They convert into just a calling `write-string' as possible. See following two examples and how CL-MARKUP expands them.

Example A:

;; Example A
(let ((*output-stream* t))
   (loop for (link . title) in '(("http://zappa.com/" . "Frank Zappa")
                                 ("http://marcusmiller.com/" . "Marcus Miller")
                                 ("http://www.milesdavis.com/" . "Miles Davis"))
         do (markup (:a :href link
                        (:b title))
                    (:br))))

;; Example A: generated by CL-MARKUP
(let ((*output-stream* t))
  (loop for (link . title) in '(("http://zappa.com/" . "Frank Zappa")
                                ("http://marcusmiller.com/" . "Marcus Miller")
                                ("http://www.milesdavis.com/" . "Miles Davis"))
        do (if *output-stream*
               (progn (write-string "<a href=\"" *output-stream*)
                      (write-string (escape-string (cl-markup::ensure-string link))
                                    *output-stream*)
                      (write-string "\"><b>" *output-stream*)
                      (write-string (escape-string (cl-markup::ensure-string title))
                                    *output-stream*)
                      (write-string "</b></a><br />" *output-stream*))
               (with-output-to-string (#:G0)
                 (write-string "<a href=\"" #:G0)
                 (write-string (escape-string (cl-markup::ensure-string link)) #:G0)
                 (write-string "\"><b>" #:G0)
                 (write-string (escape-string (cl-markup::ensure-string title)) #:G0)
                 (write-string "</b></a><br />" #:G0)))))

Example B:

;; Example B
(markup
 (:table :border 0 :cellpadding 4
         (loop for i below 25 by 5
               collect
               (markup
                 (:tr :align "right"
                      (loop for j from i below (+ i 5)
                            collect
                            (markup
                              (:td :bgcolor
                                   (if (oddp j)
                                       "pink"
                                       "green")
                                   (format nil "~@R" (1+ j))))))))))

;; Example B: generated by CL-MARKUP
(if *output-stream*
    (progn (write-string "<table border=\"0\" cellpadding=\"4\">"
                         *output-stream*)
           (write-string (let ((#:G0
                                (loop for i below 25 by 5
                                      collect (markup
                                               (:tr
                                                :align
                                                "right"
                                                (loop for j
                                                      from
                                                      i
                                                      below
                                                      (+ i 5)
                                                 collect (markup
                                                          (:td
                                                           :bgcolor
                                                           (if
                                                            (oddp j)
                                                            "pink"
                                                            "green")
                                                           (format
                                                            nil
                                                            "~@r"
                                                            (1+ j))))))))))
                                (if (consp #:G0)
                                    (with-output-to-string (#:G1)
                                      (dolist (#:G2 #:G0)
                                        (write-string #:G2 #:G1)))
                                    #:G0))
                               *output-stream*)
                           (write-string "</table>" *output-stream*))
                         (with-output-to-string (#:G0)
                           (write-string "<table border=\"0\" cellpadding=\"4\">"
                                         #:G0)
                           (write-string (let
                                          ((#:G0
                                            (loop for i below 25 by 5
                                             collect (markup
                                                      (:tr
                                                       :align
                                                       "right"
                                                       (loop for j
                                                             from
                                                             i
                                                             below
                                                             (+ i 5)
                                                        collect (markup
                                                                 (:td
                                                                  :bgcolor
                                                                  (if
                                                                   (oddp j)
                                                                   "pink"
                                                                   "green")
                                                                  (format
                                                                   nil
                                                                   "~@r"
                                                                   (1+
                                                                    j))))))))))
                                            (if
                                             (consp #:G0)
                                             (with-output-to-string
                                              (#:G1)
                                              (dolist
                                               (#:G2 #:G0)
                                               (write-string #:G2 #:G1)))
                                             #:G0))
                                           #:G0)
                                          (write-string "</table>" #:G0)))

The generated code looks more complicate than CL-WHO's one, because CL-MARKUP decide where to output the result in run-time.

Markup language

markup is the most simple way to generate HTML.

(markup (:p "あいうえお"))
;=> "<p>あいうえお</p>"

CL-MARKUP outputs tags in XHTML style by default.

(markup (:br))
;=> "<br />"

You can change the markup language to set *markup-language* to the other one.

(eval-when (:compile-toplevel :load-toplevel :execute)
  (setf *markup-language* :html))

Don't forget to wrap setf with eval-when, because markup needs it to optimize it's expanded code heavily in compile-time.

This also means, you CAN'T write the following code.

;; THIS IS A WRONG EXAMPLE!!
(let ((*markup-language* :html))
  (markup (:br)))
;=> "<br />"

If you really want to delay the decision until run-time, use markup*, a function version of markup.

;; This is a correct one.
;; But I don't recommend this for performance.
(let ((*markup-language* :xhtml))
  (markup* '(:br)))
;=> "<br>"

Other macros html, xhtml and xml, outputs DOCTYPE before markup.

(html (:p "あいうえお") (:br))
;=> "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><p>あいうえお</p><br></html>"

(xhtml (:p "あいうえお") (:br))
;=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><p>あいうえお</p><br /></html>"

(xml (:p "あいうえお") (:br))
;=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><p>あいうえお</p><br />"

Escape

Embedded strings will be escaped automatically.

(markup (:p "Tiffany & Co."))
;=> "<p>Tiffany &amp; Co.</p>"

If you don't hope this behavior, set *auto-escape* nil or use raw in order to suppress it temporally.

(let ((*auto-escape* nil))
  (markup (:p "Tiffany & Co.")))
;=> "<p>Tiffany & Co.</p>"

(markup (:p (raw "Tiffany & Co.")))
;=> "<p>Tiffany & Co.</p>"

In a contrasting case, esc is available about it.

Direct output to stream

Markup macros returns html as a string. You can customize this behavior using *output-stream*.

;; Default behavior
(let (*output-stream*)
  (markup (:p "hoge"))
;=> "<p>hoge</p>"

;; Output to *standard-output* directly
(let ((*output-stream* t))
  (markup (:p "hoge")))
;;=> <p>hoge</p>
;=> "<p>hoge</p>"

Markup syntax

You can embeded Lisp code in a tag body.

(markup (:ul (loop for item in '(1 2 3) collect (markup (:li item)))))

But, markup is too long to embed. CL-MARKUP provides an usefull syntax to write more shortly. To enable it, put (enable-markup-syntax) before.

(enable-markup-syntax)
#M(:ul (loop for item in '(1 2 3) collect #M(:li item))))

License

Copyright (c) 2011 Eitarow Fukamachi.
Licensed under the LLGPL License.