diff lisp/w3/dsssl.el @ 14:9ee227acff29 r19-15b90

Import from CVS: tag r19-15b90
author cvs
date Mon, 13 Aug 2007 08:48:42 +0200
parents
children 0293115a14e9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/w3/dsssl.el	Mon Aug 13 08:48:42 2007 +0200
@@ -0,0 +1,499 @@
+;;; dsssl.el --- DSSSL parser
+;; Author: wmperry
+;; Created: 1996/12/18 21:10:58
+;; Version: 1.11
+;; Keywords: 
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Copyright (c) 1996 by William M. Perry (wmperry@cs.indiana.edu)
+;;;
+;;; This file is part of GNU Emacs.
+;;;
+;;; GNU Emacs is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2, or (at your option)
+;;; any later version.
+;;;
+;;; GNU Emacs is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;;; Boston, MA 02111-1307, USA.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(require 'cl)
+
+(if (not (fboundp 'cl-copy-hashtable))
+    (defun cl-copy-hashtable (h)
+      (let ((new (make-hash-table)))
+	(cl-maphash (function (lambda (k v) (cl-puthash k v new))) h)
+	new)))
+		  
+;; We need to have this up at the top to avoid compilation warnings in
+;; 'make' in dsssl-eval.  Call me anal.
+(defstruct flow-object
+  (name 'unknown :read-only t)		; Name of this flow object
+  (properties nil)
+  (children nil)
+  (parent nil)
+  )
+
+(defconst dsssl-builtin-functions
+  '(not boolean\?  case equal\?  null\?  list\?  list length append
+	reverse list-tail list-ref member symbol\?  keyword\?  quantity\?
+	number\?  real\?  integer\?  = < > <= >= + * - / max min abs quotient
+	modulo remainder floor ceiling truncate round number->string
+	string->number char\?  char=\?  char-property string\?  string
+	string-length string-ref string=\?  substring string-append
+	procedure\?  apply external-procedure make time time->string quote
+	char-downcase indentity error let)
+  "A list of all the builtin DSSSL functions that we support.")
+
+(defsubst dsssl-check-args (args expected)
+  ;; Signal an error if we don't have the expected # of arguments
+  (or (= (length args) expected)
+      (error "Wrong # arguments (expected %d): %d" expected (length args))))
+
+(defsubst dsssl-min-args (args min)
+  (or (>= (length args) min)
+      (error "Wrong # arguments (expected at least %d): %d" min
+	     (length args))))
+
+(defun dsssl-call-function (func args)
+  (declare (special defines units))
+  (let ((old-defines nil)
+	(old-units nil)
+	(func-args (nth 1 func))
+	(real-func (nth 2 func))
+	(retval nil))
+    ;; Make sure we got the right # of arguments
+    (dsssl-check-args args (length func-args))
+
+    ;; make sure we evaluate all the arguments in the old environment
+    (setq args (mapcar 'dsssl-eval args))
+
+    ;; Save the old environment
+    (setq old-defines (cl-copy-hashtable defines)
+	  old-units (cl-copy-hashtable units))
+    
+    ;; Create the function's environment
+    (while func-args
+      (cl-puthash (car func-args) (car args) defines)
+      (setq func-args (cdr func-args)
+	    args (cdr args)))
+
+    ;; Now evaluate the function body, returning the value of the last one
+    (while real-func
+      (setq retval (dsssl-eval (car real-func))
+	    real-func (cdr real-func)))
+
+    ;; Restore the previous environment
+    (setq defines old-defines
+	  units old-units)
+
+    ;; And we are out of here baby!
+    retval))
+
+(defun dsssl-eval (form)
+  ;; We expect to have a 'defines' and 'units' hashtable floating around
+  ;; from higher up the call stack.
+  (declare (special defines units))
+  (cond
+   ((consp form)			; A function call
+    (let ((func (car form))
+	  (args (cdr form)))
+      (case func
+	(cons
+	 (dsssl-check-args args 2)
+	 (cons (dsssl-eval (pop args)) (dsssl-eval (pop args))))
+	(cdr
+	 (dsssl-check-args args 1)
+	 (cdr (dsssl-eval (pop args))))
+	(car
+	 (dsssl-check-args args 1)
+	 (car (dsssl-eval (pop args))))
+	(not
+	 (dsssl-check-args args 1)
+	 (not (dsssl-eval (car args))))
+	(boolean\?
+	 (dsssl-check-args args 1)
+	 (and (symbolp (car args))
+	      (memq (car args) '(\#f \#t))))
+	(if
+	 (dsssl-min-args args 2)
+	 (let ((val (dsssl-eval (pop args))))
+	   (if val
+	       (dsssl-eval (nth 0 args))
+	     (if (nth 1 args)
+		 (dsssl-eval (nth 1 args))))))
+	(let				; FIXME
+	 )
+	(case
+	 (dsssl-min-args args 2)
+	 (let* ((val (dsssl-eval (pop args)))
+		(conditions args)
+		(done nil)
+		(possibles nil)
+		(cur nil))
+	   (while (and conditions (not done))
+	     (setq cur (pop conditions)
+		   possibles (nth 0 cur))
+	     (if (or (and (listp possibles)
+			  (member val possibles))
+		     (equal val possibles)
+		     (memq possibles '(default otherwise)))
+		 (setq done (dsssl-eval (nth 1 cur)))))
+	   done))
+	(equal\?
+	 (dsssl-check-args args 2)
+	 (equal (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	(null\?
+	 (dsssl-check-args args 1)
+	 (null (dsssl-eval (car args))))
+	(list\?
+	 (dsssl-check-args args 1)
+	 (listp (dsssl-eval (car args))))
+	(list
+	 (mapcar 'dsssl-eval args))
+	(length
+	 (dsssl-check-args args 1)
+	 (length (dsssl-eval (car args))))
+	(append
+	 (apply 'append (mapcar 'dsssl-eval args)))
+	(reverse
+	 (dsssl-check-args args 1)
+	 (reverse (dsssl-eval (car args))))
+	(list-tail
+	 (dsssl-check-args args 2)
+	 (nthcdr (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	(list-ref
+	 (dsssl-check-args args 2)
+	 (nth (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	(member
+	 (dsssl-check-args args 2)
+	 (member (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	(symbol\?
+	 (dsssl-check-args args 1)
+	 (symbolp (dsssl-eval (car args))))
+	(keyword\?
+	 (dsssl-check-args args 1)
+	 (keywordp (dsssl-eval (car args))))
+	(quantity\?
+	 (dsssl-check-args args 1)
+	 (error "%s not implemented yet." func))
+	(number\?
+	 (dsssl-check-args args 1)
+	 (numberp (dsssl-eval (car args))))
+	(real\?
+	 (dsssl-check-args args 1)
+	 (let ((rval (dsssl-eval (car args))))
+	   (and (numberp rval)
+		(/= (truncate rval) rval))))
+	(integer\?
+	 (dsssl-check-args args 1)
+	 (let ((rval (dsssl-eval (car args))))
+	   (and (numberp rval)
+		(= (truncate rval) rval))))
+	((= < > <= >=)
+	 (dsssl-min-args args 2)
+	 (let ((not-done t)
+	       (initial (dsssl-eval (car args)))
+	       (next nil))
+	   (setq args (cdr args))
+	   (while (and args not-done)
+	     (setq next (dsssl-eval (car args))
+		   args (cdr args)
+		   not-done (funcall func initial next)
+		   initial next))
+	   not-done))
+	((+ *)
+	 (dsssl-min-args args 2)
+	 (let ((acc (dsssl-eval (car args))))
+	   (setq args (cdr args))
+	   (while args
+	     (setq acc (funcall func acc (dsssl-eval (car args)))
+		   args (cdr args)))
+	   acc))
+	(-
+	 (dsssl-min-args args 1)
+	 (apply func (mapcar 'dsssl-eval args)))
+	(/
+	 (dsssl-min-args args 1)
+	 (if (= (length args) 1)
+	     (/ 1 (dsssl-eval (car args)))
+	   (apply func (mapcar 'dsssl-eval args))))
+	((max min)
+	 (apply func (mapcar 'dsssl-eval args)))
+	(abs
+	 (dsssl-check-args args 1)
+	 (abs (dsssl-eval (car args))))
+	(quotient			; FIXME
+	 (error "`%s' not implemented yet!" func))
+	(modulo
+	 (dsssl-check-args args 2)
+	 (mod (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	(remainder
+	 (dsssl-check-args args 2)
+	 (% (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	((floor ceiling truncate round)
+	 (dsssl-check-args args 1)
+	 (funcall func (dsssl-eval (car args))))
+	(number->string
+	 (dsssl-min-args args 1)
+	 (if (= (length args) 1)
+	     (number-to-string (dsssl-eval (car args)))
+	   (if (= (length args) 2)	; They gave us a radix
+	       (error "Radix arg not supported yet.")
+	     (dsssl-check-args args 1))))
+	(string->number
+	 (dsssl-min-args args 1)
+	 (if (= (length args) 1)
+	     (string-to-number (dsssl-eval (car args)))
+	   (if (= (length args) 2)	; They gave us a radix
+	       (error "Radix arg not supported yet.")
+	     (dsssl-check-args args 1))))
+	(char\?
+	 (dsssl-check-args args 1)
+	 (characterp (dsssl-eval (car args))))
+	(char=\?
+	 (dsssl-check-args args 2)
+	 (char-equal (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	(char-downcase
+	 (dsssl-check-args args 1)
+	 (downcase (dsssl-eval (car args))))
+	(char-property			; FIXME
+	 (error "`%s' not implemented yet!" func))
+	(string\?
+	 (dsssl-check-args args 1)
+	 (stringp (dsssl-eval (car args))))
+	(string
+	 (dsssl-min-args args 1)
+	 (mapconcat 'char-to-string (mapcar 'dsssl-eval args) ""))
+	(string-length
+	 (dsssl-check-args args 1)
+	 (length (dsssl-eval (car args))))
+	(string-ref
+	 (dsssl-check-args args 2)
+	 (aref (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	(string=\?
+	 (dsssl-check-args args 2)
+	 (string= (dsssl-eval (car args)) (dsssl-eval (cadr args))))
+	(substring
+	 (substring (dsssl-eval (pop args))
+		    (dsssl-eval (pop args))
+		    (dsssl-eval (pop args))))
+	(string-append
+	 (let ((rval ""))
+	   (while args
+	     (setq rval (concat rval (dsssl-eval (pop args)))))
+	   rval))
+	(procedure\?
+	 (dsssl-check-args args 1)
+	 (let* ((sym (dsssl-eval (car args)))
+		(def (cl-gethash sym defines)))
+	   (or (memq sym dsssl-builtin-functions)
+	       (and def (listp def) (eq (car def) 'lambda)))))
+	(apply				; FIXME
+	 )
+	(external-procedure		; FIXME
+	 )
+	(make
+	 (let* ((type (dsssl-eval (pop args)))
+		(symname nil)
+		(props nil)
+		(tail nil)
+		(children nil)
+		(temp nil)
+		)
+	   ;; Massage :children into the last slot
+	   (setq props (mapcar 'dsssl-eval args)
+		 tail (last props)
+		 children (car tail))
+	   (if (consp tail) 
+	       (setcar tail nil))
+	   (if (not (car props))
+	       (setq props nil))
+	   (setq temp (- (length props) 1))
+	   ;; Not sure if we should really bother with this or not, but
+	   ;; it does at least make it look more common-lispy keywordish
+	   ;; and such.  DSSSL keywords look like font-weight:, this makes
+	   ;; it :font-weight
+	   (while (>= temp 0)
+	     (setq symname (symbol-name (nth temp props)))
+	     (if (string-match "^\\(.*\\):$" symname)
+		 (setf (nth temp props) 
+		       (intern (concat ":" (match-string 1 symname)))))
+	     (setq temp (- temp 2)))
+
+	   ;; Create the actual flow object
+	   (make-flow-object :name type
+			     :children children
+			     :properties props)
+	   )
+	 )
+	(time
+	 (mapconcat 'int-to-string (current-time) ":"))
+	(time->string
+	 (dsssl-check-args args 1)
+	 (current-time-string
+	  (mapcar 'string-to-int
+		  (split-string (dsssl-eval (car args)) ":"))))
+	(quote
+	 (dsssl-check-args args 1)
+	 (car args))
+	(identity
+	 (dsssl-check-args args 1)
+	 (dsssl-eval (car args)))
+	(error
+	 (apply 'error (mapcar 'dsssl-eval args)))
+	(otherwise
+	 ;; A non-built-in function - look it up
+	 (let ((def (cl-gethash func defines)))
+	   (if (and def (listp def) (eq (car def) 'lambda))
+	       (dsssl-call-function def args)
+	     (error "Symbol's function definition is void: %s" func))))
+	)
+      )
+    )
+   ((symbolp form)			; A variable
+    ;; A DSSSL keyword!
+    (if (string-match ":$" (symbol-name form))
+	form
+      (let ((val (cl-gethash form defines 'ThIS-Is_A_BOgUs-VariuhhBBLE)))
+	(if (not (eq val 'ThIS-Is_A_BOgUs-VariuhhBBLE))
+	    val
+	  ;; Ok, we got a bogus variable, but maybe it is really a UNIT
+	  ;; dereference.  Check.
+	  (let ((name (symbol-name form))
+		(the-units nil)
+		(number nil)
+		(conversion nil))
+	    (if (not (string-match "^\\([0-9.]+\\)\\([a-zA-Z]+\\)$" name))
+		(error "Symbol's value as variable is void: %s" form)
+	      (setq number (string-to-int (match-string 1 name))
+		    the-units  (intern (downcase (match-string 2 name)))
+		    conversion (cl-gethash the-units units))
+	      (if (or (not conversion) (not (numberp conversion)))
+		  (error "Symbol's value as variable is void: %s" form)
+		(* number conversion))))))))
+   (t
+    form)
+   )
+  )
+
+(defsubst dsssl-predeclared ()
+  (declare (special defines units))
+  (cl-puthash '\#f nil defines)
+  (cl-puthash 'nil nil defines)
+  (cl-puthash '\#t t defines)
+  ;; NOTE: All units are stored internally as points.
+  (cl-puthash 'in (float 72) units)
+  (cl-puthash 'mm (float (* 72 25.4)) units)
+  (cl-puthash 'cm (float (* 72 2.54)) units)
+  )
+
+(defun dsssl-parse (buf)
+  ;; Return the full representation of the DSSSL stylesheet as a series
+  ;; of LISP objects.
+  (let ((defines (make-hash-table :size 13))
+	(units   (make-hash-table :size 13))
+	(buf-contents nil))
+    (dsssl-predeclared)
+    (save-excursion
+      (setq buf-contents (if (or (bufferp buf) (get-buffer buf))
+			     (progn
+			       (set-buffer buf)
+			       (buffer-string))
+			   buf))
+      (set-buffer (generate-new-buffer " *dsssl-style*"))
+      (insert buf-contents)
+      (goto-char (point-min))
+      (skip-chars-forward " \t\n\r")
+      (if (looking-at "<!")		; DOCTYPE present
+	  (progn
+	    ;; This should _DEFINITELY_ be smarter
+	    (search-forward ">" nil t)
+	    ))
+      (let ((result nil)
+	    (temp nil)
+	    (save-pos nil))
+	(while (not (eobp))
+	  (condition-case ()
+	      (setq save-pos (point)
+		    temp (read (current-buffer)))
+	    (invalid-read-syntax
+	     ;; This disgusting hack is in here so that we can basically
+	     ;; extend the lisp reader to gracefully deal with converting
+	     ;; DSSSL #\A to Emacs-Lisp ?A notation.  If you know of a
+	     ;; better way, please feel free to send me some email.
+	     (setq temp nil)
+	     (backward-char 1)
+	     (if (looking-at "#\\\\")
+		 (replace-match "?")
+	       (insert "\\"))
+	     (goto-char save-pos))
+	    (error nil))
+	  (cond
+	   ((null temp)
+	    nil)
+	   ((listp temp)
+	    (case (car temp)
+		  (define-unit
+		    (cl-puthash (cadr temp) (dsssl-eval (caddr temp))
+				units))
+		  (define
+		    (if (listp (cadr temp))
+			;; A function
+			(cl-puthash (caadr temp)
+				     (list 'lambda
+					   (cdadr temp)
+					   (cddr temp)) defines)
+		      ;; A normal define
+		      (cl-puthash (cadr temp)
+				   (dsssl-eval (caddr temp)) defines)))
+		  (otherwise
+		   (setq result (cons temp result)))))
+	   (t
+	    (setq result (cons temp result))))
+	  (skip-chars-forward " \t\n\r"))
+	(kill-buffer (current-buffer))
+	(list defines units (nreverse result))))))
+
+(defun dsssl-test (x)
+  (let* ((result (dsssl-parse x))
+	 (defines (nth 0 result))
+	 (units   (nth 1 result))
+	 (forms   (nth 2 result)))
+    (mapcar 'dsssl-eval forms)))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; The flow object classes.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro flow-object-property (obj prop &optional default)
+  "Return property PROP of the DSSSL flow object OBJ.
+OBJ can be any flow object class, as long as it was properly derived
+from the base `flow-object' class."
+  (` (plist-get (flow-object-properties (, obj)) (, prop) (, default))))
+
+;; Now for specific types of flow objects
+;; Still to do:
+;;; display-group
+;;; paragraph
+;;; sequence
+;;; line-field
+;;; paragraph-break
+;;; simple-page-sequence
+;;; score
+;;; table
+;;; table-row
+;;; table-cell
+;;; rule
+;;; external-graphic
+
+
+(provide 'dsssl)