aboutsummaryrefslogtreecommitdiffstats
path: root/who.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'who.lisp')
-rw-r--r--who.lisp370
1 files changed, 93 insertions, 277 deletions
diff --git a/who.lisp b/who.lisp
index f154427..39ce788 100644
--- a/who.lisp
+++ b/who.lisp
@@ -1,7 +1,7 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package:CL-WHO; Base: 10 -*-
-;;; $Header: /usr/local/cvsrep/cl-who/who.lisp,v 1.35 2007/08/24 08:01:37 edi Exp $
+;;; $Header: /usr/local/cvsrep/cl-who/who.lisp,v 1.42 2009/01/26 11:10:49 edi Exp $
-;;; Copyright (c) 2003-2007, Dr. Edmund Weitz. All rights reserved.
+;;; Copyright (c) 2003-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
@@ -29,15 +29,8 @@
(in-package :cl-who)
-(defmacro n-spaces (n)
- "A string with N spaces - used by indentation."
- `(make-array ,n
- :element-type 'base-char
- :displaced-to +spaces+
- :displaced-index-offset 0))
-
(defun html-mode ()
- "Returns the current HTML mode. :SGML for (SGML-)HTML and
+ "Returns the current HTML mode. :SGML for \(SGML-)HTML and
:XML for XHTML."
*html-mode*)
@@ -54,100 +47,6 @@
*empty-tag-end* " />"
*prologue* "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"))))
-(declaim (inline escape-char))
-(defun escape-char (char &key (test *escape-char-p*))
- (declare (optimize speed))
- "Returns an escaped version of the character CHAR if CHAR satisfies
-the predicate TEST. Always returns a string."
- (if (funcall test char)
- (case char
- (#\< "&lt;")
- (#\> "&gt;")
- (#\& "&amp;")
- (#\' "&#039;")
- (#\" "&quot;")
- (t (format nil (if (eq *html-mode* :xml) "&#x~x;" "&#~d;")
- (char-code char))))
- (make-string 1 :initial-element char)))
-
-(defun escape-string (string &key (test *escape-char-p*))
- (declare (optimize speed))
- "Escape all characters in STRING which pass TEST. This function is
-not guaranteed to return a fresh string. Note that you can pass NIL
-for STRING which'll just be returned."
- (let ((first-pos (position-if test string))
- (format-string (if (eq *html-mode* :xml) "&#x~x;" "&#~d;")))
- (if (not first-pos)
- ;; nothing to do, just return STRING
- string
- (with-output-to-string (s)
- (loop with len = (length string)
- for old-pos = 0 then (1+ pos)
- for pos = first-pos
- then (position-if test string :start old-pos)
- ;; now the characters from OLD-POS to (excluding) POS
- ;; don't have to be escaped while the next character has to
- for char = (and pos (char string pos))
- while pos
- do (write-sequence string s :start old-pos :end pos)
- (case char
- ((#\<)
- (write-sequence "&lt;" s))
- ((#\>)
- (write-sequence "&gt;" s))
- ((#\&)
- (write-sequence "&amp;" s))
- ((#\')
- (write-sequence "&#039;" s))
- ((#\")
- (write-sequence "&quot;" s))
- (otherwise
- (format s format-string (char-code char))))
- while (< (1+ pos) len)
- finally (unless pos
- (write-sequence string s :start old-pos)))))))
-
-(flet ((minimal-escape-char-p (char) (find char "<>&")))
- (defun escape-char-minimal (char)
- "Escapes only #\<, #\>, and #\& characters."
- (escape-char char :test #'minimal-escape-char-p))
- (defun escape-string-minimal (string)
- "Escapes only #\<, #\>, and #\& in STRING."
- (escape-string string :test #'minimal-escape-char-p)))
-
-(flet ((minimal-plus-quotes-escape-char-p (char) (find char "<>&'\"")))
- (defun escape-char-minimal-plus-quotes (char)
- "Like ESCAPE-CHAR-MINIMAL but also escapes quotes."
- (escape-char char :test #'minimal-plus-quotes-escape-char-p))
- (defun escape-string-minimal-plus-quotes (string)
- "Like ESCAPE-STRING-MINIMAL but also escapes quotes."
- (escape-string string :test #'minimal-plus-quotes-escape-char-p)))
-
-(flet ((iso-8859-1-escape-char-p (char)
- (or (find char "<>&'\"")
- (> (char-code char) 255))))
- (defun escape-char-iso-8859-1 (char)
- "Escapes characters that aren't defined in ISO-8859-9."
- (escape-char char :test #'iso-8859-1-escape-char-p))
- (defun escape-string-iso-8859-1 (string)
- "Escapes all characters in STRING which aren't defined in ISO-8859-1."
- (escape-string string :test #'iso-8859-1-escape-char-p)))
-
-(defun escape-string-iso-8859 (string)
- "Identical to ESCAPE-STRING-8859-1. Kept for backward compatibility."
- (escape-string-iso-8859-1 string))
-
-(flet ((non-7bit-ascii-escape-char-p (char)
- (or (find char "<>&'\"")
- (> (char-code char) 127))))
- (defun escape-char-all (char)
- "Escapes characters which aren't in the 7-bit ASCII character set."
- (escape-char char :test #'non-7bit-ascii-escape-char-p))
- (defun escape-string-all (string)
- "Escapes all characters in STRING which aren't in the 7-bit ASCII
-character set."
- (escape-string string :test #'non-7bit-ascii-escape-char-p)))
-
(defun process-tag (sexp body-fn)
(declare (optimize speed space))
"Returns a string list corresponding to the `HTML' \(in CL-WHO
@@ -184,7 +83,6 @@ alist ATTR-LIST of attributes into a list of strings and/or Lisp
forms."
(declare (optimize speed space))
(loop with =var= = (gensym)
- with attribute-quote = (string *attribute-quote-char*)
for (orig-attr . val) in attr-list
for attr = (if *downcase-tokens-p*
(string-downcase orig-attr)
@@ -199,19 +97,14 @@ forms."
attr
(format nil "=~C" *attribute-quote-char*)
;; value of attribute
- (cond ((stringp val)
- ;; a string, just use it - this case is
- ;; actually not necessary because of
- ;; the last case
- val)
- ((eq val t)
+ (cond ((eq val t)
;; VAL is T, use attribute's name
attr)
(t
;; constant form, PRINC it -
;; EVAL is OK here because of CONSTANTP
(format nil "~A" (eval val))))
- attribute-quote)
+ (string *attribute-quote-char*))
end
else
;; do the same things as above but at runtime
@@ -219,19 +112,21 @@ forms."
(cond ((null ,=var=))
((eq ,=var= t)
,(case *html-mode*
- (:sgml
- `(htm ,(format nil " ~A" attr)))
- ;; otherwise default to :xml mode
- (t
- `(htm ,(format nil " ~A=~C~A~C"
- attr
- *attribute-quote-char*
- attr
- *attribute-quote-char*)))))
+ (:sgml
+ `(fmt " ~A" attr))
+ ;; otherwise default to :xml mode
+ (t
+ `(fmt " ~A=~C~A~C"
+ ,attr
+ *attribute-quote-char*
+ ,attr
+ *attribute-quote-char*))))
(t
- (htm ,(format nil " ~A=~C" attr *attribute-quote-char*)
- (str ,=var=)
- ,attribute-quote)))))))
+ (fmt " ~A=~C~A~C"
+ ,attr
+ *attribute-quote-char*
+ ,=var=
+ *attribute-quote-char*)))))))
(defgeneric convert-tag-to-string-list (tag attr-list body body-fn)
(:documentation "Used by PROCESS-TAG to convert `HTML' into a list
@@ -275,53 +170,21 @@ can use EQL specializers on the first argument."
(list *empty-tag-end*)
(list ">" "</" tag ">"))))))
-(defun apply-to-tree (function test tree)
- (declare (optimize speed space))
- (declare (type function function test))
- "Apply FUNCTION recursively to all elements of the tree TREE \(not
-only leaves) which pass TEST."
- (cond
- ((funcall test tree)
- (funcall function tree))
- ((consp tree)
- (cons
- (apply-to-tree function test (car tree))
- (apply-to-tree function test (cdr tree))))
- (t tree)))
-
-(defun replace-htm (tree transformation)
- (declare (optimize speed space))
- "Replace all subtrees of TREE starting with the symbol HTM with the
-same subtree after TRANSFORMATION has been applied to it. Utility
-function used by TREE-TO-TEMPLATE and TREE-TO-COMMANDS-AUX."
- (apply-to-tree #'(lambda (element)
- (cons 'htm (funcall transformation (cdr element))))
- #'(lambda (element)
- (and (consp element)
- (eq (car element) 'htm)))
- tree))
-
(defun tree-to-template (tree)
"Transforms an HTML tree into an intermediate format - mainly a
flattened list of strings. Utility function used by TREE-TO-COMMANDS-AUX."
(loop for element in tree
- nconc (cond ((or (keywordp element)
- (and (listp element)
- (keywordp (first element)))
- (and (listp element)
- (listp (first element))
- (keywordp (first (first element)))))
- ;; normal tag
- (process-tag element #'tree-to-template))
- ((listp element)
- ;; most likely a normal Lisp form - check if we
- ;; have nested HTM subtrees
- (list
- (replace-htm element #'tree-to-template)))
- (t
- (if *indent*
- (list +newline+ (n-spaces *indent*) element)
- (list element))))))
+ when (or (keywordp element)
+ (and (listp element)
+ (keywordp (first element)))
+ (and (listp element)
+ (listp (first element))
+ (keywordp (first (first element)))))
+ ;; the syntax for a tag - process it
+ nconc (process-tag element #'tree-to-template)
+ else
+ ;; something else - insert verbatim
+ collect element))
(defun string-list-to-string (string-list)
(declare (optimize speed space))
@@ -331,7 +194,9 @@ flattened list of strings. Utility function used by TREE-TO-COMMANDS-AUX."
(let ((total-size 0))
(dolist (string string-list)
(incf total-size (length string)))
- (let ((result-string (make-sequence 'simple-string total-size))
+ (let ((result-string (make-string total-size
+ #+:lispworks #+:lispworks
+ :element-type 'lw:simple-char))
(curr-pos 0))
(dolist (string string-list)
(replace result-string string :start1 curr-pos)
@@ -342,119 +207,83 @@ flattened list of strings. Utility function used by TREE-TO-COMMANDS-AUX."
"Concatenates all arguments which should be string into one string."
(funcall #'string-list-to-string string-list))
-(defun tree-to-commands-aux (tree stream)
+(defun tree-to-commands (tree stream &key prologue ((:indent *indent*) *indent*))
(declare (optimize speed space))
- "Transforms the intermediate representation of an HTML tree into
-Lisp code to print the HTML to STREAM. Utility function used by
-TREE-TO-COMMANDS."
- (let ((in-string t)
+ (when (and *indent*
+ (not (integerp *indent*)))
+ (setq *indent* 0))
+ (let ((in-string-p t)
collector
- string-collector)
+ string-collector
+ (template (tree-to-template tree)))
+ (when prologue
+ (push +newline+ template)
+ (when (eq prologue t)
+ (setq prologue *prologue*))
+ (push prologue template))
(flet ((emit-string-collector ()
"Generate a WRITE-STRING statement for what is currently
in STRING-COLLECTOR."
(list 'write-string
(string-list-to-string (nreverse string-collector))
- stream))
- (tree-to-commands-aux-internal (tree)
- "Same as TREE-TO-COMMANDS-AUX but with closed-over STREAM
-for REPLACE-HTM."
- (tree-to-commands-aux tree stream)))
- (unless (listp tree)
- (return-from tree-to-commands-aux tree))
- (loop for element in tree
- do (cond ((and in-string (stringp element))
- ;; this element is a string and the last one
- ;; also was (or this is the first element) -
- ;; collect into STRING-COLLECTOR
- (push element string-collector))
- ((stringp element)
- ;; the last one wasn't a string so we start
- ;; with an empty STRING-COLLECTOR
- (setq string-collector (list element)
- in-string t))
- (string-collector
- ;; not a string but STRING-COLLECTOR isn't
- ;; empty so we have to emit the collected
- ;; strings first
- (push (emit-string-collector) collector)
- (setq in-string nil
- string-collector '())
- ;; collect this element but walk down the
- ;; subtree first
- (push (replace-htm element #'tree-to-commands-aux-internal)
- collector))
- (t
- ;; not a string and empty STRING-COLLECTOR
- (push (replace-htm element #'tree-to-commands-aux-internal)
- collector)))
- finally (return (if string-collector
- ;; finally empty STRING-COLLECTOR if
- ;; there's something in it
- (nreverse (cons (emit-string-collector)
- collector))
- (nreverse collector)))))))
-
-(defun tree-to-commands (tree stream &optional prologue)
- (declare (optimize speed space))
- "Transforms an HTML tree into code to print the HTML to STREAM."
- ;; use TREE-TO-TEMPLATE, then TREE-TO-COMMANDS-AUX, and finally
- ;; replace the special symbols ESC, STR, FMT, and HTM
- (apply-to-tree #'(lambda (x)
- (case (first x)
- ((esc)
- ;; (ESC form ...)
- ;; --> (LET ((RESULT form))
- ;; (WHEN RESULT
- ;; (WRITE-STRING (ESCAPE-STRING RESULT STREAM))))
- (let ((result (gensym)))
- `(let ((,result ,(second x)))
- (when ,result (write-string (escape-string ,result) ,stream)))))
- ((str)
- ;; (STR form ...)
- ;; --> (LET ((RESULT form))
- ;; (WHEN RESULT (PRINC RESULT STREAM)))
- (let ((result (gensym)))
- `(let ((,result ,(second x)))
- (when ,result (princ ,result ,stream)))))
- ((fmt)
- ;; (FMT form*) --> (FORMAT STREAM form*)
- (list* 'format stream (rest x)))))
- #'(lambda (x)
- (and (consp x)
- (member (first x)
- '(esc str fmt)
- :test #'eq)))
- ;; wrap PROGN around the HTM forms
- (apply-to-tree (constantly 'progn)
- #'(lambda (x)
- (and (atom x)
- (eq x 'htm)))
- (tree-to-commands-aux
- (if prologue
- (list* 'htm prologue +newline+
- (tree-to-template tree))
- (cons 'htm (tree-to-template tree)))
- stream))))
+ stream)))
+ (dolist (element template)
+ (cond ((and in-string-p (stringp element))
+ ;; this element is a string and the last one
+ ;; also was (or this is the first element) -
+ ;; collect into STRING-COLLECTOR
+ (push element string-collector))
+ ((stringp element)
+ ;; the last one wasn't a string so we start
+ ;; with an empty STRING-COLLECTOR
+ (setq string-collector (list element)
+ in-string-p t))
+ (string-collector
+ ;; not a string but STRING-COLLECTOR isn't
+ ;; empty so we have to emit the collected
+ ;; strings first
+ (push (emit-string-collector) collector)
+ (setq in-string-p nil
+ string-collector '())
+ (push element collector))
+ (t
+ ;; not a string and empty STRING-COLLECTOR
+ (push element collector))))
+ (if string-collector
+ ;; finally empty STRING-COLLECTOR if
+ ;; there's something in it
+ (nreverse (cons (emit-string-collector)
+ collector))
+ (nreverse collector)))))
(defmacro with-html-output ((var &optional stream
- &key prologue
- ((:indent *indent*) *indent*))
+ &rest rest
+ &key prologue indent)
&body body)
"Transform the enclosed BODY consisting of HTML as s-expressions
into Lisp code to write the corresponding HTML as strings to VAR -
which should either hold a stream or which'll be bound to STREAM if
supplied."
- (when (and *indent*
- (not (integerp *indent*)))
- (setq *indent* 0))
- (when (eq prologue t)
- (setq prologue *prologue*))
+ (declare (ignore prologue))
`(let ((,var ,(or stream var)))
- ,(tree-to-commands body var prologue)))
+ (macrolet ((htm (&body body)
+ `(with-html-output (,',var nil :prologue nil :indent ,,indent)
+ ,@body))
+ (fmt (&rest args)
+ `(format ,',var ,@args))
+ (esc (thing)
+ (with-unique-names (result)
+ `(let ((,result ,thing))
+ (when ,result (write-string (escape-string ,result) ,',var)))))
+ (str (thing)
+ (with-unique-names (result)
+ `(let ((,result ,thing))
+ (when ,result (princ ,result ,',var))))))
+ ,@(apply 'tree-to-commands body var rest))))
(defmacro with-html-output-to-string ((var &optional string-form
- &key (element-type ''character)
+ &key (element-type #-:lispworks ''character
+ #+:lispworks ''lw:simple-char)
prologue
indent)
&body body)
@@ -466,19 +295,6 @@ into Lisp code which creates the corresponding HTML as a string."
(with-html-output (,var nil :prologue ,prologue :indent ,indent)
,@body)))
-(defmacro show-html-expansion ((var &optional stream
- &key prologue
- ((:indent *indent*) *indent*))
- &body body)
- "Show the macro expansion of WITH-HTML-OUTPUT."
- (when (and *indent*
- (not (integerp *indent*)))
- (setq *indent* 0))
- (when (eq prologue t)
- (setq prologue *prologue*))
- `(pprint '(let ((,var ,(or stream var)))
- ,(tree-to-commands body var prologue))))
-
;; stuff for Nikodemus Siivola's HYPERDOC
;; see <http://common-lisp.net/project/hyperdoc/>
;; and <http://www.cliki.net/hyperdoc>