(defparameter *html-entities* '((#\< . "lt") (#\> . "gt") (#\& . "amp") (#\" . "quot"))) (defun html-entity-char-p (char) (assoc char *html-entities* :test #'eql)) (defun html-entity-position (string &key (start 0)) (position-if #'html-entity-char-p string :start start)) (defun html-entity-substitute (char) (cdr (assoc char *html-entities* :test #'eql))) (defun html-escape (string) (html-escape-aux string 0 nil)) ;; Don't cons up a whole new string if we end up not doing any ;; substitution. (defun html-escape-aux (string start buffer) (let ((pos (html-entity-position string :start start))) (if (null pos) (if (null buffer) string (concatenate 'string buffer (subseq string start))) (html-escape-aux string (+ pos 1) (concatenate 'string (or buffer "") (subseq string start pos) "&" (html-entity-substitute (char string pos)) ";"))))) (defclass escape-html-stream (buffered-output-stream) ((output-stream :initarg :output-stream :accessor escape-html-stream-output-stream))) (defmethod buffered-output-stream-write-buffer ((stream escape-html-stream) &optional force-p) (let ((b (buffered-output-stream-buffer stream)) (out (escape-html-stream-output-stream stream))) (if force-p ;; Not sure of semantics of force-p. Just transform the whole ;; buffer and write it out? May be more consy. (progn (write-sequence (html-escape b) out) (length b)) (let ((pos (html-entity-position b))) (if (null pos) ;; No entities to substitute. (length (write-sequence b out)) ;; Found one. (let ((entity (html-entity-substitute (elt b pos)))) (write-sequence b out :end pos) (write-char #\& out) (write-string entity out) (write-char #\; out) (+ pos 1)))))))