hiccl
2024-10-12
HTML generator for Common Lisp
Readme
HTML generator for Common Lisp
1Purpose
Spinneret is great but having to use macros to compose elements is less intuitive than simply having a function to render S-Expressions into HTML.Hiccl also aims to be easy to extend with generic functions.
2Usage
Hiccl exposes one macro, `render`, which wraps `render-forms` to use &body args for editor support. It takes an output argument (same as format) and any number of SXML expressions.- Symbols are raw (you can write literal HTML between `|` symbols)
- Strings are sanitized
- Lists are HTML nodes
Outputting to `nil` returns a string.
NOTE: In the following examples, the HTML has been formatted for the readme. Hiccl does not pretty print it's output.
(hiccl:render nil
`(:div :hi "world"
(:span "tag can be symbol")
"no </\"xss\"> allowed"
|symbols <are> "raw" </are>|
(:a :href "hTtPs://link.org" "clickme")))
<div hi="world">
<span>
tag can be symbol
</span>
no </"xss"> allowed
symbols <are> "raw" </are>
<a href="hTtPs://link.org">clickme</a>
</div>
2.1JSX style syntax
class and id tags can use the following shorthand notation:(hiccl:render nil
'(:div.c1#id1.c2 :class "c3" :id "id2"))
<div class="c3 c1 c2" id="id2 id1">
</div>
Order is not guarunteed at the moment, but I will consider adding it.
2.2Specifying output
Render to a stream by specifying the `out` argument, renders to string if nil.(hiccl:render *standard-output* sxml)
;; alternatively you can use `t` like in format
(hiccl:render t sxml)
;; to get a string
(hiccl:render nil sxml)
2.3Composing components
(defun hello-component (name)
`(:span ,(format nil "Hello ~a" name)))
(hiccl:render nil
`(:div
"hello-component:"
,(hello-component "garlic")))
<div>
hello-component:
<span>Hello garlic</span>
</div>
2.4Boolean attributes
To use boolean attributes, just make its value nil:(hiccl:render nil '(:div :bool nil))
<div bool>
</div>
2.5Extending the renderer
The renderer uses generic functions that you can extend to handle your objects:(defclass thing ()
((a :initarg :a :accessor thing-a)
(b :initarg :b :accessor thing-b)))
(defmethod hiccl::render-form (out (obj thing))
(hiccl:render out
`(:div.thing
(:div.a ,(thing-a obj))
(:div.b ,(thing-b obj)))))
(hiccl:render nil (make-instance 'thing :a "hi" :b "world"))
<div class="thing">
<div class="a">
hi
</div>
<div class="b">
world
</div>
</div>
You can also add special cases for HTML tags with methods:
(defmethod hiccl::apply-tag (out (tag (eql :my-comment)) body)
(format out "<!--~%SPECIAL COMMENT~%~{~a~%~}-->" body))
(hiccl:render nil '(:my-comment "hi"))
<!--
SPECIAL COMMENT
hi
-->
These are not exported, so remember to use hiccl::* to refer to them.