paskvil

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 "&lt;" new-str)
                   (setf pos1 (+ pos2 2))
                   (incf pos2))
              (#\> (when (<= pos1 pos2)
                     (write-string (subseq str pos1 (1+ pos2)) new-str))
                   (write-string "&gt;" 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)))))))