;; Copyright (c) 2020 Mark Polyakov ;; Released under the WTFPL (Do What The Fuck You Want To Public License) ;; https://gist.github.com/markasoftware/ab357f1b967b3f656d026e33fec3bc0e (defvar *html-void-tags* '(:area :base :br :col :embed :hr :img :input :link :meta :param :source :track :wbr) "String designators for self-closing/void tags. https://html.spec.whatwg.org/multipage/syntax.html#void-elements") (defvar *html-escapes* '()) (defun escape (str) (declare (string str)) (with-output-to-string (stream) (loop for ch across str for escaped = (getf *html-escapes* ch) do (if escaped (write-string escaped stream) (write-char ch stream))))) (defun html->string (html) "The argument should be of the form (tag-name (attr-name attr-val) child1 child2 ...). Attributes and children are optional. (html->string '(html () (head () (title () \"My awesome website!\")) (body () \"Hello! I'm Mark.\" ;; No attributes or children: (br) (a (href \"https://github.com/markasoftware\") \"My stuff\") (br) ;; No children: (img (src \"/cats.jpg\" alt \"My cute cats!\"))))) Since the argument must be quoted, you can use backquote notation to interleave html and lisp: `(div () \"My name is \" ,*my-name* \", But you can call me:\" (br) (ul () ,@(mapcar (lambda (name) `(li () ,name)) *my-nicknames*))) All text and attribute values are escaped properly. You can use keyword symbols for tag and attribute names if you'd like. There's a hardcoded list of self-closing tags, such as br and img. Unescaped HTML can be inserted as a string using (:noescape \"