119 lines
3.9 KiB
Common Lisp
119 lines
3.9 KiB
Common Lisp
(in-package :piccolo)
|
|
|
|
(defun plist-alist (plist)
|
|
(loop for (k v) on plist by #'cddr
|
|
collect (cons k v)))
|
|
|
|
(defun alist-plist (alist)
|
|
(mapcan (lambda (kv)
|
|
(list (string-downcase (car kv))
|
|
(cdr kv)))
|
|
alist))
|
|
|
|
(defstruct !expanded list)
|
|
|
|
(defun tree-leaves%% (tree test result)
|
|
(if tree
|
|
(if (listp tree)
|
|
(let ((car-result (tree-leaves%% (car tree) test result))
|
|
(cdr-result (tree-leaves%% (cdr tree) test result)))
|
|
(if (!expanded-p car-result)
|
|
(append (!expanded-list car-result) cdr-result)
|
|
(cons car-result cdr-result)))
|
|
(if (funcall test tree)
|
|
(funcall result tree)
|
|
tree))))
|
|
|
|
(defmacro tree-leaves (tree test result)
|
|
`(tree-leaves%%
|
|
,tree
|
|
(lambda (x)
|
|
(declare (ignorable x))
|
|
,test)
|
|
(lambda (x)
|
|
(declare (ignorable x))
|
|
,result)))
|
|
|
|
(defun utf8-html-escape-char-p (char)
|
|
(find char "<>&"))
|
|
|
|
(defun ascii-html-escape-char-p (char)
|
|
(or (utf8-html-escape-char-p char)
|
|
(> (char-code char) 127)))
|
|
|
|
(defun attr-value-escape-char-p (char)
|
|
(eql char #\"))
|
|
|
|
(defun escape-char (char)
|
|
(case char
|
|
(#\< "<")
|
|
(#\> ">")
|
|
(#\& "&")
|
|
(#\' "'")
|
|
(#\" """)
|
|
(t (format nil "&#~d;" (char-code char)))))
|
|
|
|
(defun escape-string (string &optional (test #'utf8-html-escape-char-p))
|
|
(if (stringp string)
|
|
(with-output-to-string (s)
|
|
(loop
|
|
for c across string
|
|
do (write (if (funcall test c) (escape-char c) c) :stream s :escape nil)))
|
|
string))
|
|
|
|
(defun escape-attrs-alist (alist)
|
|
(mapcar (lambda (kv)
|
|
(cons (car kv)
|
|
(escape-string (cdr kv) #'attr-value-escape-char-p)))
|
|
alist))
|
|
|
|
(defun escape-children (children)
|
|
(mapcar (lambda (child)
|
|
(if (stringp child)
|
|
(case *escape-html*
|
|
(:utf8 (escape-string child))
|
|
(:ascii (escape-string child #'ascii-html-escape-char-p))
|
|
(otherwise child))
|
|
child))
|
|
children))
|
|
|
|
(defun split-attrs-and-children (attrs-and-children)
|
|
(cond
|
|
((attrs-p (first attrs-and-children))
|
|
(values (first attrs-and-children) (flatten (rest attrs-and-children))))
|
|
((alistp (first attrs-and-children))
|
|
(values (make-attrs :alist (first attrs-and-children))
|
|
(flatten (rest attrs-and-children))))
|
|
((listp (first attrs-and-children))
|
|
(values (make-attrs :alist (plist-alist (first attrs-and-children)))
|
|
(flatten (rest attrs-and-children))))
|
|
((hash-table-p (first attrs-and-children))
|
|
(values (make-attrs :alist (hash-alist (first attrs-and-children)))
|
|
(flatten (rest attrs-and-children))))
|
|
((and (vectorp (first attrs-and-children))
|
|
(keywordp (aref (first attrs-and-children) 0)))
|
|
(append-inline-attrs attrs-and-children))
|
|
((keywordp (first attrs-and-children))
|
|
(loop for thing on attrs-and-children by #'cddr
|
|
for (k v) = thing
|
|
when (and (keywordp k) v)
|
|
collect (cons k v) into attrs
|
|
when (not (keywordp k))
|
|
return (values (make-attrs :alist attrs) (flatten thing))
|
|
finally (return (values (make-attrs :alist attrs) nil))))
|
|
(t
|
|
(values (make-attrs :alist nil) (flatten attrs-and-children)))))
|
|
|
|
(defun append-inline-attrs (attrs-and-children)
|
|
(let* ((inline-attrs (coerce (first attrs-and-children) 'list))
|
|
(id (getf inline-attrs :id))
|
|
(class (getf inline-attrs :class)))
|
|
(multiple-value-bind (attrs children)
|
|
(split-attrs-and-children (rest attrs-and-children))
|
|
(when (and id (not (attr attrs :id)))
|
|
(setf (attr attrs :id) id))
|
|
(when class
|
|
(if-let (other-class (attr attrs :class))
|
|
(setf (attr attrs :class) (format nil "~a ~a" class other-class))
|
|
(setf (attr attrs :class) class)))
|
|
(values attrs children))))
|