com.paskvil.uhttp-html.lisp
lisp/uhttp/com.paskvil.uhttp-html.lisp download
;;; -*- Lisp -*- mode
(in-package #:com.paskvil.uhttp)
(defun str-replace (str tr-rules)
(with-output-to-string (new-str)
(let (tr)
(loop for c across str
do (if (setf tr (assoc c tr-rules))
(write-string (cdr tr) new-str)
(write-char c new-str))))))
(defun escape-html-chars (str)
(with-output-to-string (new-str)
(let ((pos1 0)
(pos2 -1))
(loop for c across str
do (case c
(#\< (when (<= pos1 pos2)
(write-string (subseq str pos1 (1+ pos2)) new-str))
(write-string "<" new-str)
(setf pos1 (+ pos2 2))
(incf pos2))
(#\> (when (<= pos1 pos2)
(write-string (subseq str pos1 (1+ pos2)) new-str))
(write-string ">" new-str)
(setf pos1 (+ pos2 2))
(incf pos2))
(t (incf pos2))))
(when (<= pos1 pos2)
(write-string (subseq str pos1 (1+ pos2)) new-str)))))
(defparameter *html-tags*
'(:a :applet :area :b :big :blockquote :body :br :button
:caption :center :code :dd :del :div :em :font :form :frame
:h1 :h2 :h3 :h4 :h5 :h6 :head :hr :html :i :iframe :img :input
:labe :li :map :meta :noscript :object :ol :option
:p :param :pre :s :script :select :small :span :strike
:strong :style :sub :sup :table :tbody :td :textarea :tfoot
:th :thead :title :tr :tt :u :ul :var))
(defparameter *html-single-tags*
'(:a :area :br :frame :hr :img :input :meta :param))
(defun to-html-string (obj)
(escape-html-chars (format nil "~a" obj)))
(defmacro html (forms)
"Emits HTML code based on provided forms.
If the form starts with a keyword that denotes HTML tag,
appropriate HTML code is printed to *html-stream* stream.
If it's any other designator, the whole form is considered
to be a form to execute, potentially containing more html.
Example usage:
(html (:p \"hello\"))
(html ((:p :style \"font-size: 12px;\") \"hello\"))
(html (:img :src \"/images/img.jpg\"))
(html (:body (:ul (dotimes (x 10) (html (:li x)))))"
(labels ((is-html-tag (tag) (find tag *html-tags*))
(is-html-single-tag (tag) (find tag *html-single-tags*)))
(cond ((atom forms) ; atom - presume it's contents to print
`(format t "~a" (to-html-string ,forms)))
((and forms (listp forms)) ; list - either tag definition, or code
(let ((fhead (car forms))
(ftail (cdr forms)))
(cond ((and (listp fhead) ; starts with list - should be tag with attributes
(keywordp (car fhead))
(is-html-tag (car fhead)))
`(progn
(format t "<~a" (car ',fhead))
,@(do ((res '())
(att (cdr fhead) (cddr att)))
((null att) (nreverse res))
(push `(format t " ~a='~a'" (car ',att) (to-html-string (cadr ',att))) res))
(format t ">")
,@(loop for x in ftail collecting `(html ,x))
(format t "</~a>~%" (car ',fhead))))
((and (atom fhead) ; single tag - followed by attributes
(keywordp fhead)
(is-html-single-tag fhead))
`(progn
(format t "<~a" ,fhead)
,@(do ((res '())
(att ftail (cddr att)))
((null att) (nreverse res))
(push `(format t " ~a='~a'" (car ',att) (to-html-string (cadr ',att))) res))
(format t "/>~%")))
((and (atom fhead) ; normal tag, followed by contents
(keywordp fhead)
(is-html-tag fhead))
`(progn
(format t "<~a>" ,fhead)
,@(loop for x in ftail collecting `(html ,x))
(format t "</~a>~%" ,fhead)))
(t ; presume it's a code to execute
forms)))))))