Modern markup (HTML) generation library for Common Lisp
Common Lisp
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
src
test
.gitignore
README.markdown
cl-markup-test.asd
cl-markup.asd

README.markdown

CL-MARKUP - Modern markup generation library for Common Lisp

Features & advantages

  • Fast (even faster through compiling the code)
  • Safety
  • Support for multiple document types (markup, xml, html, html5, xhtml)
  • Output with doctype
  • Direct output to stream

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

Is this really fast?

Generally CL-MARKUP generates efficient codes which mainly consists of series of `write-string's as much as possible. See how following two examples are expanded by macro expansion.

As you can see, the codes are a bit more complicated than that of CL-WHO because CL-MARKUP alters the destination of output in run-time.

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)))

Markup language

markup is the simplest way to generate HTML.

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

By default, CL-MARKUP follows XHTML valid styling.

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

You can configure the style by setting *markup-language*.

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

Don't forget to wrap setf with eval-when since it is used in compile-time in order to expand markup. This also means you are NOT allowed to write codes like this:

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

In case you really want to delay the decision until run-time, use markup*, a functional 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 such as html, xhtml, html5, and xml output 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>"

(html5 (:p "あいうえお") (:br))
;=> "<!DOCTYPE html><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 are escaped automatically.

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

If you don't want this behavior, set *auto-escape* nil or use raw for temporal suppression.

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

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

Also, when you want to ensure a certain code to be escaped (maybe inside raw) use esc, which has the similar syntax as that of raw.

Direct output to stream

Markup macros returns html as a string. This behavior can be customized by modifying *output-stream* which is defaulted to *standard-output*.

;; 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 embed Lisp code in the body of each tag.

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

For more readability, CL-MARKUP provides a reader macro #M which can be enabled by (enable-markup-syntax).

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

License

Copyright (c) 2011 Eitarow Fukamachi.

Contributors:

m7d - HTML5 support, April 21, 2012.

Licensed under the LLGPL License.