hsx/src/element.lisp

143 lines
4.1 KiB
Common Lisp
Raw Normal View History

2024-05-25 12:48:35 +00:00
(defpackage #:hsx/element
2024-05-25 03:00:39 +00:00
(:use #:cl)
2024-05-31 09:39:25 +00:00
(:import-from #:hsx/escaper
#:escape-html-attribute
#:escape-html-text-content)
2024-06-01 11:14:17 +00:00
(:import-from #:hsx/group
2024-06-01 12:40:45 +00:00
#:self-closing-tag-p
#:non-escaping-tag-p)
(:export #:element
#:tag
#:html-tag
#:fragment
#:component
#:create-element
2024-05-28 11:15:29 +00:00
#:element-type
2024-05-25 03:00:39 +00:00
#:element-props
2024-05-25 15:57:06 +00:00
#:element-children
#:expand-component
2024-06-01 13:49:15 +00:00
#:render-to-string))
2024-05-25 12:48:35 +00:00
(in-package #:hsx/element)
2024-05-25 03:00:39 +00:00
2024-05-26 14:29:30 +00:00
;;;; class definitions
2024-05-25 03:00:39 +00:00
(defclass element ()
2024-05-25 16:29:58 +00:00
((type
:reader element-type
:initarg :type)
2024-05-25 03:00:39 +00:00
(props
:reader element-props
2024-05-25 15:57:06 +00:00
:initarg :props)
(children
:reader element-children
:initarg :children)))
2024-05-25 03:00:39 +00:00
2024-05-28 11:48:39 +00:00
(defclass tag (element) ())
2024-05-26 14:29:30 +00:00
2024-05-28 11:48:39 +00:00
(defclass html-tag (tag) ())
2024-05-26 14:29:30 +00:00
2024-05-29 03:46:57 +00:00
(defclass fragment (tag) ())
2024-05-26 14:29:30 +00:00
2024-05-28 11:48:39 +00:00
(defclass component (element) ())
2024-05-26 14:29:30 +00:00
;;;; factory
2024-05-26 14:29:30 +00:00
(defun create-element (type props children)
2024-06-04 23:22:14 +00:00
(make-instance
(cond ((functionp type) 'component)
((eq type :<>) 'fragment)
((eq type :html) 'html-tag)
((keywordp type) 'tag)
(t (error "element-type must be a keyword or a function.")))
:type type
:props props
:children (flatten children)))
2024-06-08 05:29:00 +00:00
2024-05-25 03:00:39 +00:00
(defun flatten (x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec
(car x)
(rec (cdr x) acc))))))
(rec x nil)))
2024-05-26 04:57:51 +00:00
2024-05-26 14:29:30 +00:00
;;;; methods
2024-06-01 13:49:15 +00:00
(defmethod render-to-string ((element element) &key pretty)
2024-05-29 03:57:51 +00:00
(with-output-to-string (stream)
2024-06-01 13:49:15 +00:00
(write element :stream stream :pretty pretty)))
2024-05-29 03:57:51 +00:00
(defmethod print-object ((element tag) stream)
2024-05-26 14:29:30 +00:00
(with-accessors ((type element-type)
(props element-props)
(children element-children)) element
2024-06-07 01:07:37 +00:00
(let ((type-str (string-downcase type))
(props-str (render-props props)))
(if children
2024-05-28 12:42:28 +00:00
(format stream
2024-06-07 01:07:37 +00:00
(if (or (rest children)
(typep (first children) 'element))
"~@<<~a~a>~2I~:@_~<~@{~a~^~:@_~}~:>~0I~:@_</~a>~:>"
"~@<<~a~a>~2I~:_~<~a~^~:@_~:>~0I~_</~a>~:>")
type-str
2024-06-07 01:07:37 +00:00
props-str
(escape-children type children)
type-str)
2024-05-28 12:42:28 +00:00
(format stream
2024-06-01 11:14:17 +00:00
(if (self-closing-tag-p type)
"<~a~a>"
"<~a~a></~a>")
type-str
2024-06-07 01:07:37 +00:00
props-str
type-str)))))
2024-05-26 14:29:30 +00:00
2024-06-07 01:07:37 +00:00
(defun render-props (props)
2024-05-26 14:29:30 +00:00
(with-output-to-string (stream)
(loop
:for (key value) :on props :by #'cddr
2024-05-28 12:42:28 +00:00
:do (let ((key-str (string-downcase key)))
(if (typep value 'boolean)
(format stream
"~@[ ~a~]"
(and value key-str))
(format stream
" ~a=\"~a\""
key-str
2024-05-31 09:39:25 +00:00
(escape-html-attribute value)))))))
2024-05-26 14:29:30 +00:00
2024-06-07 01:07:37 +00:00
(defun escape-children (type children)
(mapcar (lambda (child)
(if (and (not (non-escaping-tag-p type))
(stringp child))
(escape-html-text-content child)
child))
children))
(defmethod print-object ((element html-tag) stream)
2024-05-26 14:29:30 +00:00
(format stream "<!DOCTYPE html>~%")
(call-next-method))
(defmethod print-object ((element fragment) stream)
(with-accessors ((children element-children)) element
2024-05-26 14:29:30 +00:00
(if children
2024-05-28 12:42:28 +00:00
(format stream
(if (rest children)
"~<~@{~a~^~:@_~}~:>"
"~<~a~:>")
2024-05-26 14:29:30 +00:00
children))))
(defmethod print-object ((element component) stream)
(print-object (expand-component element) stream))
2024-05-27 07:03:23 +00:00
(defmethod expand-component ((element component))
2024-05-27 07:03:23 +00:00
(with-accessors ((type element-type)
(props element-props)
(children element-children)) element
2024-05-27 07:03:23 +00:00
(apply type (merge-children-into-props props children))))
(defun merge-children-into-props (props children)
(append props
(and children
(list :children children))))