Mercurial > hg > xemacs-beta
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/w3/css.el Mon Aug 13 08:48:42 2007 +0200 @@ -0,0 +1,786 @@ +;;; 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)