;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*- (declaim (optimize (speed 0) (safety 3) (debug 3))) ;; Lifted directly from araneida. (in-package :music-dispensary) ;;; XXX fix this, it's not correct (defun html-reserved-p (c) (member c '(#\< #\" #\> #\&))) (defun html-escape (html-string) (apply #'concatenate 'string (loop for c across html-string if (html-reserved-p c) collect (format nil "&#~A;" (char-code c)) else if (eql c #\Newline) collect "
" else collect (string c)))) (defun s. (&rest args) "Concatenate ARGS as strings" (declare (optimize (speed 3))) (let ((*print-pretty* nil)) (with-output-to-string (out) (dolist (arg args) (princ arg out))))) (defun html-escape-tag (tag attrs content) (declare (ignore tag attrs)) (s. (mapcar #'html-escape content))) (setf (get 'escape 'html-converter) #'html-escape-tag) (setf (get 'null 'html-converter) #'princ-to-string) (macrolet ((html-attr-body () `(with-output-to-string (o) (loop for (att val . rest) on attr by #'cddr do (cond #+parenscript((and (symbolp att) (equal (symbol-name 'css) (symbol-name att))) (progn (princ " " o) (princ "style=\"" o) (princ (val-printer (parenscript::css-inline-func val)) o) (princ "\"" o))) ((symbolp att) (progn (princ " " o) (princ (symbol-name att) o) (princ "=\"" o) (princ (val-printer val) o) (princ "\"" o))) (t (error "attribute ~S is not a symbol in attribute list ~S" att attr))))))) (defun html-attr (attr) (macrolet ((val-printer (val) val)) (html-attr-body))) (defun html-attr-escaped (attr) (macrolet ((val-printer (val) `(html-escape ,val))) (html-attr-body)))) (defun empty-element-p (tag) (member (intern (symbol-name tag) #.*package*) '())) (defmacro defhtmltag (tag (attributes-var content-var) &body body) "Define a custom HTML tag Useful for custom phrases, or even special constructs. Example: (defhtmltag coffee (attr content) (declare (ignore attr content)) \"c|_|\") So, saying (span \"Nice hot \" (coffee)) Would produce: Nice hot c|_| More in-depth use would be something such as: (even-odd-list (li \"one\") (li \"two\") (li \"three\")) Turning into: (ul ((li :class \"odd\") \"one\") ((li :class \"even\") \"two\") ((li :class \"odd\") \"odd\")) Your custom tag is expected to return either a string or an html construct like you'd pass to HTML-STREAM." (with-gensyms (throw-away) `(setf (get ',tag :html-converter) (lambda (,throw-away ,attributes-var ,content-var) (declare (ignore ,throw-away)) (funcall (lambda (,attributes-var ,content-var) ,@body) ,attributes-var ,content-var))))) (defun htmlp (html) "Returns t if html is a legal HTML list. NB: HTML and friends print out a superset of legal html lists. '(ul (li \"yes\") (li \"no\")) is legal 3 is not But HTML will print both of them" (and (consp html) (not (stringp (car html))))) (defmacro destructure-html ((tag-sym attrs-sym content-sym) html &body body) "Destructure an HTML construct. (destructure-html (tag attrs content) '((span :class \"strange\") \"A strange span!\") (format t \"<~A ~{~A~}>~{~A~}\" tag attrs content tag)) If the construct is invalid, it will cause an error" (once-only (html) `(if (htmlp ,html) (let ((,tag-sym (if (consp (car ,html)) (caar ,html) (car ,html))) (,attrs-sym (if (consp (car ,html)) (cdar ,html) nil)) (,content-sym (cdr ,html))) ,@body)))) ; I admit, this is a rather goofy way to write this. There's just so much code they have in common ; and this lets me modify them rather easily. (macrolet ((html-body () `(ret-block (cond ((htmlp things) (destructure-html (tag attrs content) things (let ((special-effect (get tag :html-converter))) (if special-effect (if (not (functionp special-effect)) (error "Tag ~A has :html-converter set, but NOT as a function." tag) (call-self stream (funcall special-effect tag attrs content) inline-elements)) (cond ((equal (symbol-name 'comment) (symbol-name tag)) (printing (format-out "~%"))) #+parenscript ((equal (symbol-name 'css) (symbol-name tag)) (printing (format-out ""))) #+parenscript ((equal (symbol-name 'js-script) (symbol-name tag)) (printing (format-out "