Mercurial > hg > xemacs-beta
view lisp/w3/css.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 source
;;; css.el -- Cascading Style Sheet parser ;; Author: wmperry ;; Created: 1996/12/26 16:49:58 ;; Version: 1.18 ;; Keywords: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Copyright (c) 1996 by William M. Perry (wmperry@cs.indiana.edu) ;;; Copyright (c) 1996 Free Software Foundation, Inc. ;;; ;;; This file is not part of GNU Emacs, but the same permissions apply. ;;; ;;; 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. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (eval-and-compile (require 'cl) (require 'font) ) ;; CBI = Cant Be Implemented - due to limitations in emacs/xemacs ;; NYI = Not Yet Implemented - due to limitations of space/time ;; NYPI = Not Yet Partially Implemented - possible partial support, eventually (defconst css-properties '(;; Property name Inheritable? Type of data [font-family nil string-list] [font-style nil string] [font-variant nil symbol-list] [font-weight nil weight] [font-size nil length] [font nil font] [color nil color] [background nil color] [word-spacing nil length] ; CBI [letter-spacing nil length] ; CBI [text-decoration nil symbol-list] [vertical-align nil symbol] ; CBI [text-transform nil string] [text-align t symbol] [text-indent t length] ; NYI [line-height t length] ; CBI [margin nil margin] [margin-left nil margin] [margin-right nil margin] [margin-top nil margin] [margin-bottom nil margin] [padding nil padding] [padding-left nil padding] [padding-right nil padding] [padding-top nil padding] [padding-bottom nil padding] [border nil border] [border-left nil border] [border-right nil border] [border-top nil border] [border-bottom nil border] [width nil length] ; NYPI [height nil length] ; NYPI [float nil symbol] [clear nil symbol] [display nil symbol] [list-style t symbol] ;!! can't specify 'inside|outside' [white-space t symbol] ;; These are for specifying speech properties [voice-family t string] [gain t integer] [left-volume t integer] [right-volume t integer] [pitch t integer] [pitch-range t integer] [stress t integer] [richness t integer] ) "A description of the various CSS properties and how to interpret them.") (mapcar (lambda (entry) (put (aref entry 0) 'css-inherit (aref entry 1)) (put (aref entry 0) 'css-type (aref entry 2))) css-properties) (defconst css-weights '(nil ;never used :extra-light :light :demi-light :medium :normal :demi-bold :bold :extra-bold ) "List of CSS font weights.") (defvar css-syntax-table (copy-syntax-table emacs-lisp-mode-syntax-table) "The syntax table for parsing stylesheets") (modify-syntax-entry ?' "\"" css-syntax-table) (modify-syntax-entry ?` "\"" css-syntax-table) (modify-syntax-entry ?{ "(" css-syntax-table) (modify-syntax-entry ?} ")" css-syntax-table) (eval-when-compile (defvar css-scratch-val nil) (defvar css-scratch-id nil) (defvar css-scratch-class nil) (defvar css-scratch-possibles nil) (defvar css-scratch-current nil) (defvar css-scratch-classes nil) (defvar css-scratch-class-match nil) (defvar css-scratch-current-rule nil) (defvar css-scratch-current-value nil) ) (defconst css-running-xemacs (string-match "XEmacs" (emacs-version)) "Whether we are running in XEmacs or not.") (defvar css-ie-compatibility t "Whether we want to do Internet Explorer 3.0 compatible parsing of CSS stylesheets.") (defsubst css-replace-regexp (regexp to-string) (goto-char (point-min)) (while (re-search-forward regexp nil t) (replace-match to-string t nil))) (defun css-contextual-match (rule stack) (let ((ancestor) (p-args) (p-class) (matched t)) (while rule (setq ancestor (assq (caar rule) stack)) (if (not ancestor) (setq rule nil matched nil) (setq p-args (cdr ancestor) p-class (or (cdr-safe (assq 'class p-args)) t)) (if (not (equal p-class (cdar rule))) (setq matched nil rule nil))) (setq rule (cdr rule))) matched)) (defsubst css-get-internal (tag args) (declare (special tag sheet element-stack default)) (setq css-scratch-id (or (cdr-safe (assq 'id args)) (cdr-safe (assq 'name args))) css-scratch-class (or (cdr-safe (assq 'class args)) t) css-scratch-possibles (cl-gethash tag sheet)) (while css-scratch-possibles (setq css-scratch-current (car css-scratch-possibles) css-scratch-current-rule (car css-scratch-current) css-scratch-current-value (cdr css-scratch-current) css-scratch-classes (if (listp (car css-scratch-current-rule)) (cdar css-scratch-current-rule) (cdr css-scratch-current-rule)) css-scratch-class-match t css-scratch-possibles (cdr css-scratch-possibles)) (if (eq t css-scratch-classes) (setq css-scratch-classes nil)) (if (eq t css-scratch-class) (setq css-scratch-class nil)) (while css-scratch-classes (if (not (member (pop css-scratch-classes) css-scratch-class)) (setq css-scratch-class-match nil css-scratch-classes nil))) (cond ((and (listp (car css-scratch-current-rule)) css-scratch-class-match) ;; Contextual! (setq css-scratch-current-rule (cdr css-scratch-current-rule)) (if (css-contextual-match css-scratch-current-rule element-stack) (setq css-scratch-val (append css-scratch-val css-scratch-current-value))) ) (css-scratch-class-match (setq css-scratch-val (append css-scratch-val css-scratch-current-value)) ) (t nil)) ) ) (defsubst css-get (tag args &optional sheet element-stack) (setq css-scratch-val nil css-scratch-class (or (cdr-safe (assq 'class args)) t)) ;; check for things without the class (if (listp css-scratch-class) (css-get-internal tag nil)) ;; check for global class values (css-get-internal '*document args) ;; Now check for things with the class - they will be stuck on the front ;; of the list, which will mean we do the right thing (css-get-internal tag args) ;; Defaults are up to the calling application to provide css-scratch-val) (defun css-ancestor-get (info ancestors sheet) ;; Inheritable property, check ancestors (let (cur) (while ancestors (setq cur (car ancestors) css-scratch-val (css-get info (car cur) (cdr cur) sheet) ancestors (if css-scratch-val nil (cdr ancestors))))) css-scratch-val) (defun css-split-selector (tag) ;; Return a list (cond ((string-match " " tag) ; contextual (let ((tags (split-string tag "[ \t]+")) (result nil)) (while tags (setq result (cons (css-split-selector (car tags)) result) tags (cdr tags))) result)) ((string-match "[:\\.]" tag) (let ((tag (if (= (match-beginning 0) 0) '*document (intern (downcase (substring tag 0 (match-beginning 0)))))) (rest (substring tag (match-beginning 0) nil)) (classes nil)) (while (string-match "^[:\\.][^:\\.]+" rest) (if (= ?. (aref rest 0)) (setq classes (cons (substring rest 1 (match-end 0)) classes)) (setq classes (cons (substring rest 0 (match-end 0)) classes))) (setq rest (substring rest (match-end 0) nil))) (setq classes (sort classes 'string-lessp)) (cons tag classes))) ((string-match "^#" tag) ; id selector (cons '*document tag)) (t (cons (intern (downcase tag)) t) ) ) ) (defun css-applies-to (st nd) (let ((results nil) (save-pos nil)) (narrow-to-region st nd) (goto-char st) (skip-chars-forward " \t\r\n") (while (not (eobp)) (setq save-pos (point)) (skip-chars-forward "^,") (skip-chars-backward " \r\t\n") (setq results (cons (css-split-selector (buffer-substring save-pos (point))) results)) (skip-chars-forward ", \t\r\n")) (widen) results)) (defun css-split-font-shorthand (font) ;; [<font-weight> || <font-style>]? <font-size> [ / <line-height> ]? <font-family> (let (weight size height family retval) (if (not (string-match " *\\([0-9.]+[^ /]+\\)" font)) (error "Malformed font shorthand: %s" font)) (setq weight (if (/= 0 (match-beginning 0)) (substring font 0 (match-beginning 0))) size (match-string 1 font) font (substring font (match-end 0) nil)) (if (string-match " */ *\\([^ ]+\\) *" font) ;; they specified a line-height as well (setq height (match-string 1 font) family (substring font (match-end 0) nil)) (if (string-match "^[ \t]+" font) (setq family (substring font (match-end 0) nil)) (setq family font))) (if weight (setq retval (cons (cons 'font-weight weight) retval))) (if size (setq retval (cons (cons 'font-size size) retval))) (if height (setq retval (cons (cons 'line-height height) retval))) (if family (setq retval (cons (cons 'font-family family) retval))) retval)) (defun css-expand-length (spec) (cond ((not (stringp spec)) spec) ((string-equal spec "auto") nil) ((string-match "\([0-9]+\)%" spec) ; A percentage nil) ((string-match "\([0-9]+\)e[mn]" spec) ; Character based (string-to-int (substring spec (match-beginning 1) (match-end 1)))) (t (truncate (font-spatial-to-canonical spec))) ) ) (defsubst css-unhex-char (x) (if (> x ?9) (if (>= x ?a) (+ 10 (- x ?a)) (+ 10 (- x ?A))) (- x ?0))) (defsubst css-pow (x n) (apply '* (make-list n x))) (defun css-unhex (x) (let ((ord (length x)) (rval 0)) (while (> ord 0) (setq rval (+ rval (* (css-pow 16 (- (length x) ord)) (css-unhex-char (aref x (1- ord))))) ord (1- ord))) rval)) (defun css-expand-color (color) (cond ((string-match "^#" color) (let (r g b) (cond ((string-match "^#...$" color) ;; 3-char rgb spec, expand out to six chars by replicating ;; digits, not adding zeros. (setq r (css-unhex (make-string 2 (aref color 1))) g (css-unhex (make-string 2 (aref color 2))) b (css-unhex (make-string 2 (aref color 3))))) ((string-match "^#\\(..\\)\\(..\\)\\(..\\)$" color) (setq r (css-unhex (match-string 1 color)) g (css-unhex (match-string 2 color)) b (css-unhex (match-string 3 color)))) (t (setq color (substring color 1)) (let* ((n (/ (length color) 3)) (max (float (css-pow 16 n)))) (setq r (css-unhex (substring color 0 n)) g (css-unhex (substring color n (* n 2))) b (css-unhex (substring color (* n 2) (* n 3))) r (round (* (/ r max) 255)) g (round (* (/ g max) 255)) b (round (* (/ b max) 255)))))) (setq color (vector 'rgb r g b)))) ((string-match "^rgb *( *\\([0-9]+\\)[, ]+\\([0-9]+\\)[, ]+\\([0-9]+\\) *) *$" color) ;; rgb(r,g,b) 0 - 255, cutting off at 255 (setq color (vector 'rgb (min (string-to-int (match-string 1 color)) 255) (min (string-to-int (match-string 2 color)) 255) (min (string-to-int (match-string 3 color)) 255)))) ((string-match "^rgb *( *\\([0-9]+\\) *%[, ]+\\([0-9]+\\) *%[, ]+\\([0-9]+\\) *% *) *$" color) ;; rgb(r%,g%,b%) 0 - 100%, cutting off at 100% (let ((r (min (string-to-number (match-string 1 color)) 100.0)) (g (min (string-to-number (match-string 2 color)) 100.0)) (b (min (string-to-number (match-string 3 color)) 100.0))) (setq r (round (* r 2.55)) g (round (* g 2.55)) b (round (* b 2.55)) color (vector 'rgb r g b)))) ((string-match "url *(\\([^ )]+\\) *)" color) ;; A picture in the background (let ((pixmap (match-string 1 color)) (attributes nil)) (setq color (concat (substring color 0 (match-beginning 0)) (substring color (match-end 0) nil)) attributes (split-string color " ")) ) ) (t ;; Hmmm... pass it through unmangled and hope the underlying ;; windowing system can handle it. ) ) color ) (defun css-expand-value (type value) (case type ((symbol integer) ; Read it in (setq value (read (downcase value)))) (symbol-list (setq value (downcase value) value (split-string value "[ ,]+") value (mapcar 'intern value))) (string-list (setq value (split-string value " *, *"))) (color ; A color, possibly with URLs (setq value (css-expand-color value))) (length ; Pixels, picas, ems, etc. (setq value (css-expand-length value))) (font ; Font shorthand (setq value (css-split-font-shorthand value))) ((margin padding) ; length|percentage|auto {1,4} (setq value (split-string value "[ ,]+")) (if (/= 1 (length value)) ;; More than one value - a shortcut (let* ((top (intern (format "%s-top" type))) (bottom (intern (format "%s-bottom" type))) (left (intern (format "%s-left" type))) (right (intern (format "%s-right" type)))) (setq top (cons top (css-expand-length (nth 0 value))) right (cons right (css-expand-length (nth 1 value))) bottom (cons bottom (css-expand-length (nth 2 value))) left (cons left (css-expand-length (nth 3 value))) value (list top right bottom left))) (setq value (css-expand-length (car value))))) (border (cond ((member (downcase value) '("none" "dotted" "dashed" "solid" "double" "groove" "ridge" "inset" "outset")) (setq value (intern (downcase value)))) ((string-match "^[0-9]+" value) (setq value (font-spatial-to-canonical value))) (t nil))) (weight ; normal|bold|bolder|lighter|[1-9]00 (if (string-match "^[0-9]+" value) (setq value (/ (read value) 100) value (or (nth value css-weights) :bold)) (setq value (intern (downcase (concat ":" value)))))) (otherwise ; Leave it as is t) ) value ) (defun css-parse-args (st &optional nd) ;; Return an assoc list of attribute/value pairs from a CSS style entry (let ( name ; From name= value ; its value results ; Assoc list of results name-pos ; Start of XXXX= position val-pos ; Start of value position ) (save-excursion (if (stringp st) (progn (set-buffer (get-buffer-create " *css-style-temp*")) (set-syntax-table css-syntax-table) (erase-buffer) (insert st) (setq st (point-min) nd (point-max))) (set-syntax-table css-syntax-table)) (save-restriction (narrow-to-region st nd) (goto-char (point-min)) (while (not (eobp)) (skip-chars-forward ";, \n\t") (setq name-pos (point)) (skip-chars-forward "^ \n\t:=,;") (downcase-region name-pos (point)) (setq name (intern (buffer-substring name-pos (point)))) (skip-chars-forward " \t\n") (if (not (eq (char-after (point)) ?:)) ; There is no value (setq value nil) (skip-chars-forward " \t\n:") (setq val-pos (point) value (cond ((or (= (or (char-after val-pos) 0) ?\") (= (or (char-after val-pos) 0) ?')) (buffer-substring (1+ val-pos) (condition-case () (prog2 (forward-sexp 1) (1- (point)) (skip-chars-forward "\"")) (error (skip-chars-forward "^ \t\n") (point))))) (t (buffer-substring val-pos (progn (if css-ie-compatibility (skip-chars-forward "^;") (skip-chars-forward "^,;")) (skip-chars-backward " \t") (point))))))) (setq value (css-expand-value (get name 'css-type) value)) (if (eq (get name 'css-type) 'font) (setq results (append value results)) (setq results (cons (cons name value) results))) (skip-chars-forward ";, \n\t")) results)))) (defun css-handle-import () (let ((url nil) (save-pos (point))) (if (looking-at "'\"") (condition-case () (forward-sexp 1) (error (skip-chars-forward "^ \t\r\n;"))) (skip-chars-forward "^ \t\r\n;")) (setq url (url-expand-file-name (buffer-substring save-pos (point)))) (skip-chars-forward "\"; \t\r\n") (setq save-pos (point)) (let ((url-working-buffer (generate-new-buffer-name " *styleimport*")) (url-mime-accept-string "text/css ; level=2") (sheet nil)) (save-excursion (set-buffer (get-buffer-create url-working-buffer)) (setq url-be-asynchronous nil) (url-retrieve url) (css-clean-buffer) (setq sheet (buffer-string)) (set-buffer-modified-p nil) (kill-buffer (current-buffer))) (insert sheet) (goto-char save-pos)))) (defun css-clean-buffer () ;; Nuke comments, etc. (goto-char (point-min)) (let ((save-pos nil)) (while (search-forward "/*" nil t) (setq save-pos (- (point) 2)) (delete-region save-pos (if (search-forward "*/" nil t) (point) (end-of-line) (point))))) (goto-char (point-min)) (delete-matching-lines "^[ \t\r]*$") ; Nuke blank lines (css-replace-regexp "^[ \t\r]+" "") ; Nuke whitespace at beg. of line (css-replace-regexp "[ \t\r]+$" "") ; Nuke whitespace at end of line (goto-char (point-min))) (defun css-active-device-types (&optional device) (let ((types (list 'normal 'default (if css-running-xemacs 'xemacs 'emacs))) (type (device-type device))) (cond ((featurep 'emacspeak) (setq types (cons 'speech types))) ((eq type 'tty) (if (and (fboundp 'tty-color-list) (/= 0 (length (tty-color-list)))) (setq types (cons 'ansi-tty types)) (setq types (cons 'tty types)))) ((eq 'color (device-class)) (if (not (device-bitplanes)) (setq types (cons 'color types)) (setq types (append (list (intern (format "%dbit-color" (device-bitplanes))) (intern (format "%dbit" (device-bitplanes))) 'color) types)) (if (= 24 (device-bitplanes)) (setq types (cons 'truecolor types))))) ((eq 'grayscale (device-class)) (setq types (append (list (intern (format "%dbit-grayscale" (device-bitplanes))) 'grayscale) types))) ((eq 'mono (device-class)) (setq types (append (list 'mono 'monochrome) types))) (t (setq types (cons 'unknown types)))) types)) (defmacro css-rule-specificity-internal (rule) (` (progn (setq tmp (cdr (, rule))) (if (listp tmp) (while tmp (if (= ?# (aref (car tmp) 0)) (incf a) (incf b)) (setq tmp (cdr tmp))))))) (defsubst css-specificity (rule) ;; To find specificity, according to the september 1996 CSS draft ;; a = # of ID attributes in the selector ;; b = # of class attributes in the selector ;; c = # of tag names in the selector (let ((a 0) (b 0) (c 0) cur tmp) (if (not (listp (car rule))) (css-rule-specificity-internal rule) (setq c (length rule)) (while rule (css-rule-specificity-internal (pop rule)))) (+ (* 100 a) (* 10 b) c) ) ) (defun css-copy-stylesheet (sheet) (let ((new (make-hash-table :size (hash-table-count sheet)))) (cl-maphash (function (lambda (k v) (cl-puthash k (copy-tree v) new))) sheet) new)) (defsubst css-store-rule (attrs applies-to) (declare (special sheet)) (let (rules cur tag node) (while applies-to (setq cur (pop applies-to) tag (car cur)) (if (listp tag) (setq tag (car tag))) (setq rules (cl-gethash tag sheet)) (cond ((null rules) ;; First rule for this tag. Create new ruleset (cl-puthash tag (list (cons cur attrs)) sheet)) ((setq node (assoc cur rules)) ;; Similar rule already exists, splice in our information (setcdr node (append attrs (cdr node)))) (t ;; First rule for this particular combination of tag/ancestors/class. ;; Slap it onto the existing set of rules and push back into sheet. (setq rules (cons (cons cur attrs) rules)) (cl-puthash tag rules sheet)) ) ) ) ) (defun css-parse (fname &optional string inherit) (let ( (url-mime-accept-string "text/css ; level=2") (save-pos nil) (applies-to nil) ; List of tags to apply style to (attrs nil) ; List of name/value pairs (att nil) (cur nil) (val nil) (device-type nil) (active-device-types (css-active-device-types (selected-device))) (sheet inherit)) (if (not sheet) (setq sheet (make-hash-table :size 13 :test 'eq))) (save-excursion (set-buffer (get-buffer-create (generate-new-buffer-name " *style*"))) (set-syntax-table css-syntax-table) (erase-buffer) (if fname (url-insert-file-contents fname)) (goto-char (point-max)) (if string (insert string)) (css-clean-buffer) (goto-char (point-min)) (while (not (eobp)) (setq save-pos (point)) (cond ;; *sigh* SGML comments are being used to 'hide' data inlined ;; with the <style> tag from older browsers. ((or (looking-at "<!--+") ; begin (looking-at "--+>")) ; end (goto-char (match-end 0))) ;; C++ style comments, and we are doing IE compatibility ((and (looking-at "//") css-ie-compatibility) (end-of-line)) ;; Pre-Processor directives ((looking-at "[ \t\r]*@\\([^ \t\r\n]\\)") (let ((directive nil)) (skip-chars-forward " @\t\r") ; Past any leading whitespace (setq save-pos (point)) (skip-chars-forward "^ \t\r\n") ; Past the @ directive (downcase-region save-pos (point)) (setq directive (buffer-substring save-pos (point))) (skip-chars-forward " \t\r") ; Past any trailing whitespace (setq save-pos (point)) (cond ((string= directive "import") (css-handle-import)) (t (message "Unknown directive in stylesheet: @%s" directive))))) ;; Giving us some output device information ((looking-at "[ \t\r]*:\\([^: \n]+\\):") (downcase-region (match-beginning 1) (match-end 1)) (setq device-type (intern (buffer-substring (match-beginning 1) (match-end 1)))) (goto-char (match-end 0)) (if (not (memq device-type active-device-types)) ;; Not applicable to us... skip the info (progn (if (re-search-forward ":[^:{ ]*:" nil t) (goto-char (match-beginning 0)) (goto-char (point-max)))))) ;; Default is to treat it like a stylesheet declaration (t (skip-chars-forward "^{") ;;(downcase-region save-pos (point)) (setq applies-to (css-applies-to save-pos (point))) (skip-chars-forward "^{") (setq save-pos (point)) (condition-case () (forward-sexp 1) (error (goto-char (point-max)))) (end-of-line) (skip-chars-backward "\r}") (subst-char-in-region save-pos (point) ?\n ? ) (subst-char-in-region save-pos (point) ?\r ? ) ;; This is for not choking on garbage at the end of the buffer. ;; I get bit by this every once in a while when going through my ;; socks gateway. (if (eobp) nil (setq attrs (css-parse-args (1+ save-pos) (point))) (skip-chars-forward "}\r\n") (css-store-rule attrs applies-to)) ) ) (skip-chars-forward " \t\r\n")) (set-buffer-modified-p nil) (kill-buffer (current-buffer))) sheet) ) ;; Tools for pretty-printing an existing stylesheet. (defun css-rule-name (rule) (cond ((listp (car rule)) ; Contextual (mapconcat 'css-rule-name (reverse rule) " ")) ((listp (cdr rule)) ; More than one class (let ((classes (cdr rule)) (rval (symbol-name (car rule)))) (while classes (setq rval (concat rval (if (= (aref (car classes) 0) ?:) (pop classes) (concat "." (pop classes)))))) rval)) (t (symbol-name (car rule))))) (defun css-display (sheet) (with-output-to-temp-buffer "CSS Stylesheet" (set-buffer standard-output) (indented-text-mode) (insert "# Stylesheet auto-regenerated by css.el\n#\n" "# This is a mixture of the default stylesheet and any\n" "# styles specified by the document. The rules are in no\n" "# particular order.\n\n") (let (tmp cur goal-col) (cl-maphash (function (lambda (k v) (while v (setq cur (pop v)) (insert (css-rule-name (car cur))) (insert " { ") (setq goal-col (point)) (insert "\n") ;; Display the rules (setq tmp (cdr cur)) (let (prop val) (while tmp (setq prop (caar tmp) val (cdar tmp) tmp (cdr tmp)) (case (get prop 'css-type) (symbol-list (setq val (mapconcat 'symbol-name val ","))) (weight (setq val (substring (symbol-name val) 1 nil))) (otherwise nil) ) (insert (format " %s: %s;\n" prop val)))) (insert "}\n\n"); ))) sheet)))) (provide 'css)