1 ;;; -*- Mode: Lisp; Syntax: Common-Lisp; Package: CSS; Readtable: GLISP; Encoding: utf-8; -*-
2 ;;; ---------------------------------------------------------------------------
3 ;;; Title: CSS selectors
4 ;;; [Split off from css-parse.lisp]
5 ;;; Created: 2001-05-19
6 ;;; Author: Gilbert Baumann <gilbert@base-engineering.com>
7 ;;; License: MIT style (see below)
8 ;;; ---------------------------------------------------------------------------
9 ;;; (c) copyright 1998-2001 by Gilbert Baumann
11 ;;; Permission is hereby granted, free of charge, to any person obtaining
12 ;;; a copy of this software and associated documentation files (the
13 ;;; "Software"), to deal in the Software without restriction, including
14 ;;; without limitation the rights to use, copy, modify, merge, publish,
15 ;;; distribute, sublicense, and/or sell copies of the Software, and to
16 ;;; permit persons to whom the Software is furnished to do so, subject to
17 ;;; the following conditions:
19 ;;; The above copyright notice and this permission notice shall be
20 ;;; included in all copies or substantial portions of the Software.
22 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25 ;;; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26 ;;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
27 ;;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28 ;;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 (defun create-style-sheet (super-sheet &key
(name "Anonymous style sheet")
37 (warn "A proper style sheet has a base url; ~
38 (While creating style sheet ~S)." name
))
39 (make-style-sheet :super-sheet super-sheet
44 (defun style-sheet-relate (sheet selector slot value
&optional important-p
)
45 (cond ((gi-selection-p (car (style-sheet-rules sheet
)))
46 (style-sheet-relate-2 sheet selector slot value important-p
))
48 (let ((rule (find selector
(style-sheet-rules sheet
)
49 :test
#'equal
:key
#'rule-selector
)))
51 (setf rule
(make-rule :selector selector
52 :specifity
(css2-selector-specificity selector
)
55 (push rule
(style-sheet-rules sheet
)))
56 (let ((a (make-assignment
58 :importantp important-p
60 :specifity
(concatenate 'vector
65 (incf (style-sheet-j sheet
)))))
68 (push a
(rule-assignments rule
)) )))))
70 (defun style-sheet-relate-2 (sheet selector slot value important-p
)
71 (let ((q (car (style-sheet-rules sheet
)))
74 :importantp important-p
76 :specifity
(concatenate 'vector
77 (css2-selector-specificity selector
)
81 (incf (style-sheet-j sheet
)))))
84 (style-sheet-relate-3 selector q a
)))
86 (defun style-sheet-relate-3 (selector q a
)
87 (let ((gi (find 'gi selector
:key
#'car
)))
89 (maphash (lambda (key value
)
91 (setf (gethash key
(gi-selection-hashtable q
))
92 (augment-to-rules value selector selector a
))
94 (gi-selection-hashtable q
))
95 (setf (gi-selection-default q
)
96 (augment-to-rules (gi-selection-default q
) selector selector a
)))
98 (let ((x (gethash (cadr gi
) (gi-selection-hashtable q
))))
100 (setf (gethash (cadr gi
) (gi-selection-hashtable q
))
101 (augment-to-rules x
(remove gi selector
) selector a
)))
103 ;; hmm now we need to shovel all default rules back.
104 (setf (gethash (cadr gi
) (gi-selection-hashtable q
))
105 (augment-to-rules nil
(remove gi selector
) selector a
))
106 (dolist (y (prog1 (gi-selection-default q
)
107 (setf (gi-selection-default q
) nil
)))
108 (dolist (a (rule-assignments y
))
109 (style-sheet-relate-3 selector q a
))))))))))
111 (defun augment-to-rules (rules selector selector-orig a
)
112 (let ((rule (find selector rules
:test
#'equal
:key
#'rule-selector
)))
114 (setf rule
(make-rule :selector selector
115 :specifity
(css2-selector-specificity selector-orig
)
119 (push a
(rule-assignments rule
))
123 (defun style-sheet->assignments-list
(s)
124 (error "Do not call me")
126 (setf res
(reverse (style-sheet-assignments s
)))
128 (setq res
(stable-sort res
#'selector-lessp
:key
#'assignment-selector
))
129 '(assert (every (lambda (x) (member x res0
)) res
))
136 ;; das habe ich immer noch nicht ganz verstanden.
137 ;; ich meine bilden die important regel ein allererstes element fuer den
138 ;; specificity vector?!
139 ;; - style by `style' attribute
142 ;; is at start of author style sheet with specificity set to 0
145 ;; specificity = #(0 0 1 0 0 inf)
146 ;; origin after everything else
148 ;; This is both compatible with CSS-1
150 ;; Diese ganze explicite Unterscheidung zwischen Author/User/Default
151 ;; ist mir etwas zu speziell. (grade bei !important wird das alles
152 ;; doch etwas merkwürdig!).
154 (defun find-style (sheet element implicit-style
155 &optional
(origin 0) (p 0)
156 (res (make-array *n-attrs
* :initial-element nil
)))
157 ;; handle imported sheets
158 (dolist (im (style-sheet-imported-sheets sheet
))
159 (multiple-value-setq (res p
) (find-style im element nil origin p res
)))
161 (dolist (rule (style-sheet-rules sheet
))
163 (when (css2-selector-matches-p (rule-selector rule
) element
)
164 (dolist (a (rule-assignments rule
))
167 (augment-assignment-to-result* (assignment-slot a
)
169 (if (assignment-importantp a
) 1 0) ;CSS-1 modell
171 (svref (assignment-specifity a
) 0)
172 (svref (assignment-specifity a
) 1)
173 (svref (assignment-specifity a
) 2)
174 (+ p
(svref (assignment-specifity a
) 3))
176 ((gi-selection-p rule
)
177 (let ((q (gethash (element-gi element
)
178 (gi-selection-hashtable rule
)
179 (gi-selection-default rule
))))
181 ;; code duplication alert!
182 (when (css2-selector-matches-p (rule-selector rule
) element
)
183 (dolist (a (rule-assignments rule
))
186 (augment-assignment-to-result* (assignment-slot a
)
188 (if (assignment-importantp a
) 1 0) ;CSS-1 modell
190 (svref (assignment-specifity a
) 0)
191 (svref (assignment-specifity a
) 1)
192 (svref (assignment-specifity a
) 2)
193 (+ p
(svref (assignment-specifity a
) 3))
196 (incf p
(style-sheet-j sheet
))
197 ;; handle implicit style
198 (dolist (k implicit-style
)
199 (let ((prop (car k
)) (value (cdr k
)))
200 (setf res
(augment-assignment-to-result* prop value
201 0 origin
0 0 0 (prog1 p
(incf p
))
203 ;; recurse into super sheets
204 (cond ((style-sheet-super-sheet sheet
)
206 (find-style (style-sheet-super-sheet sheet
) element nil
(- origin
1) 0 res
))))
207 ;; return what we found
210 (defun augment-assignment-to-result (property value v res
)
211 (let ((x (svref res
(symbol-value property
))))
213 (setf (svref res
(symbol-value property
))
215 ((vector-greater-p v
(second x
))
216 (setf (first x
) value
220 (defun augment-assignment-to-result* (property value v1 v2 v3 v4 v5 v6 res
)
221 (let ((x (svref res
(symbol-value property
))))
223 (setf (svref res
(symbol-value property
))
224 (list value
(vector v1 v2 v3 v4 v5 v6
))))
225 ((vector-greater-p* v1 v2 v3 v4 v5 v6
(second x
))
226 (setf (first x
) value
227 (svref (second x
) 0) v1
228 (svref (second x
) 1) v2
229 (svref (second x
) 2) v3
230 (svref (second x
) 3) v4
231 (svref (second x
) 4) v5
232 (svref (second x
) 5) v6
)))
235 (defun css2-selector-matches-p (selector element
)
237 (cond ((and (pseudo-class-matches-p :first-line element
)
238 (not (find '(pclass #.
(rod "first-line")) selector
240 (return-from css2-selector-matches-p nil
))
241 ((and (pseudo-class-matches-p :before element
)
242 (not (find '(pclass #.
(rod "before")) selector
244 (return-from css2-selector-matches-p nil
))
245 ((and (pseudo-class-matches-p :after element
)
246 (not (find '(pclass #.
(rod "after")) selector
248 (return-from css2-selector-matches-p nil
))
249 ((and (pseudo-class-matches-p :first-letter element
)
250 (not (find '(pclass #.
(rod "first-letter")) selector
252 (return-from css2-selector-matches-p nil
)))
254 (dolist (pred selector t
)
257 ((gi) (css2-gi-match-p (cadr pred
) element
))
258 ((id) (css2-id-match-p (cadr pred
) element
))
259 ((class) (css2-class-match-p (cadr pred
) element
))
262 (not (null (element-attribute element
(intern-attribute-name (cadr pred
))))))
265 (attribute-equal-p element
(cadr pred
) (caddr pred
) nil
)) ;CS or CI??
268 (attribute-contains-p element
(cadr pred
) (caddr pred
) nil
))
270 ((attrib-contain-dash)
271 (attribute-contain-dash-p element
(cadr pred
) (caddr pred
) nil
))
274 (cond ((and (= (length (cdr pred
)) 1)
275 (rod-equal #.
(map 'vector
#'char-code
"first-child") (cadr pred
)))
276 (null (pt-predecessor element
)))
277 ((and (= (length (cdr pred
)) 1)
278 (rod-equal #.
(map 'vector
#'char-code
"link") (cadr pred
)))
279 (pseudo-class-matches-p :link element
))
280 ((and (= (length (cdr pred
)) 1)
281 (rod-equal #.
(map 'vector
#'char-code
"first-line") (cadr pred
)))
282 (pseudo-class-matches-p :first-line element
))
283 ((and (= (length (cdr pred
)) 1)
284 (rod-equal #.
(map 'vector
#'char-code
"first-letter") (cadr pred
)))
285 (pseudo-class-matches-p :first-letter element
))
286 ((and (= (length (cdr pred
)) 1)
287 (rod-equal #.
(map 'vector
#'char-code
"before") (cadr pred
)))
288 (pseudo-class-matches-p :before element
))
289 ((and (= (length (cdr pred
)) 1)
290 (rod-equal #.
(map 'vector
#'char-code
"after") (cadr pred
)))
291 (pseudo-class-matches-p :after element
))
294 ;; (print (rod-string (cadr pred)))
298 (css2-ancester-match-p (cdr pred
) element
))
301 (and (element-parent element
)
302 (css2-selector-matches-p (cdr pred
) (element-parent element
))))
304 (let ((prec (pt-predecessor element
)))
306 (css2-selector-matches-p (cdr pred
) prec
))))
311 (defun vector-greater-p (v1 v2
)
312 (dotimes (i (length v1
) nil
)
313 (let ((a (aref v1 i
))
315 (cond ((> a b
) (return t
))
316 ((< a b
) (return nil
))))))
318 (defun vector-greater-p* (v1 v2 v3 v4 v5 v6 w
)
322 (cond ((> a b
) (return t
))
323 ((< a b
) (return nil
))))
326 (cond ((> a b
) (return t
))
327 ((< a b
) (return nil
))))
330 (cond ((> a b
) (return t
))
331 ((< a b
) (return nil
))))
334 (cond ((> a b
) (return t
))
335 ((< a b
) (return nil
))))
338 (cond ((> a b
) (return t
))
339 ((< a b
) (return nil
))))
342 (cond ((> a b
) (return t
))
343 ((< a b
) (return nil
)))) ))
345 (defun pt-predecessor (pt)
346 (let ((par (element-parent pt
)))
349 (dolist (k (element-children par
))
352 (unless (text-element-p k
)
355 (defun css2-ancester-match-p (selector element
)
356 (and (element-parent element
)
357 (or (css2-selector-matches-p selector
(element-parent element
))
358 (css2-ancester-match-p selector
(element-parent element
)))))
360 ;; class, id are case-sensitive in HTML
362 (defun attribute-contains-p (element attribute string case-sensitive-p
)
363 (let ((v (element-attribute element
(intern-attribute-name attribute
))))
364 (and v
(rod-contains-p v string case-sensitive-p
))))
366 (defun attribute-equal-p (element attribute string case-sensitive-p
)
367 (let ((v (element-attribute element
(intern-attribute-name attribute
))))
371 (rod-equal v string
)))))
373 (defun rod-contains-p (haystack needle case-sensitive-p
)
374 ;; what should (rod-contains-p .. "" ..) yield?
375 (dotimes (i (- (length haystack
) (length needle
) -
1) nil
)
376 (when (and (or (= i
0)
377 (white-space-rune-p (rune haystack
(1- i
))))
378 (or (= (+ i
(length needle
)) (length haystack
))
379 (white-space-rune-p (rune haystack
(+ i
(length needle
))))))
380 (when (dotimes (j (length needle
) t
)
381 (unless (if case-sensitive-p
382 (rune= (rune needle j
) (rune haystack
(+ i j
)))
383 (rune-equal (rune needle j
) (rune haystack
(+ i j
))))
387 (defun attribute-contain-dash-p (element attribute string case-sensitive-p
)
388 (let ((v (element-attribute element
(intern-attribute-name attribute
))))
390 (>= (length v
) (length string
))
392 (rod= (subseq v
0 (length string
)) string
)
393 (rod-equal (subseq v
0 (length string
)) string
))
394 (or (= (length string
) (length v
))
395 (rune= (code-rune #.
(char-code #\-
)) (rune v
(length string
)))))))
397 (defun skip-group (seq p
&optional
(level 0))
398 (cond ((>= p
(length seq
))
400 ((= (aref seq p
) #.
(char-code #\
{))
401 (skip-group seq
(+ p
1) (+ level
1)))
402 ((= (aref seq p
) #.
(char-code #\
}))
403 (cond ((= level
1) p
)
404 ((skip-group seq
(+ p
1) (- level
1)))))
405 ((skip-group seq
(+ p
1) level
))))
407 (defun parse-at-rule-body (seq p0
)
408 ;; An at-rule consists of everything up to and including the next
409 ;; semicolon (;) or the next block (defined shortly), whichever comes
413 (warn "EOF before at-rule group.")
414 (values (length seq
)))
415 (cond ((= (aref seq i
) #.
(char-code #\
;))
416 (return (values (+ i
1))))
417 ((= (aref seq i
) #.
(char-code #\
{))
418 (let ((p1 (skip-group seq i
)))
420 (warn "EOF within at-rule group.")
421 (return (values nil
(length seq
))))
423 (return (values (+ p1
1))))))) )))
425 (defstruct import-rule
429 (defun parse-media-type-2 (toks)
430 (let ((r (p/comma-separated-list toks
(lambda (x)
431 (let ((r (p/ident x
)))
432 (and r
(cons (list (intern (string-upcase (car r
))
436 (cons (cons 'or
(car r
)) (cdr r
)))))
438 (defun parse-media-type (string)
440 (let ((r (parse-media-type-2
443 (cl-char-stream->gstream
444 (make-string-input-stream string
)))))))
445 (cond ((and r
(null (cdr r
)))
448 (warn "Bad media type: ~S." string
)
451 (defun parse-import-rule (seq)
452 (let ((toks (tokenize seq
))
454 (let ((r (or (p/string toks
)
457 (let ((s (parse-media-type-2 (cdr r
))))
458 (setf (cdr r
) (cdr s
)
459 media-type
(car s
))))
460 (cond ((and r
(null (cdr r
)))
462 (list (make-import-rule :url-str url
:media-type media-type
))))
464 (warn "CSS @import rule does not parse: ~S." (as-string seq
))
467 (defun parse-at-rule (seq start import-ok?
)
469 (assert (= (aref seq start
) #.
(char-code #\
@)))
470 (unless (setq p1
(parse-ident seq
(+ start
1)))
471 (warn "Bad syntax: An '@' must be followed by an identifier")
472 (setf p1
(+ start
1)))
473 (setq p2
(parse-at-rule-body seq p1
))
475 (let ((ident (as-string (subseq seq
(+ start
1) p1
))))
476 (cond ((string-equal ident
"import")
478 (values (parse-import-rule (subseq seq p1 p2
))
481 (parse-import-rule (subseq seq p1 p2
))
482 (warn "@import not at start of style sheet - ignored.")
484 ((string-equal ident
"media")
490 (values nil
(length seq
))) )))
492 (defun assignment-list-adjoin (new assignments
)
493 (cons (cons (reverse (car new
)) (cdr new
)) assignments
))
495 ;;;; new CSS-2 selectors
498 (or (<= #.
(char-code #\a) ch
#.
(char-code #\z
))
499 (<= #.
(char-code #\A
) ch
#.
(char-code #\Z
))
500 (<= #.
(char-code #\
0) ch
#.
(char-code #\
9))
501 (= ch
#.
(char-code #\-
))
504 (defun nmstart-p (ch)
505 (or (<= #.
(char-code #\a) ch
#.
(char-code #\z
))
506 (<= #.
(char-code #\A
) ch
#.
(char-code #\Z
))
509 (defun q-cons-rod (string start end
)
510 (let ((res (make-array (- end start
) :element-type
'rune
)))
511 (dotimes (i (- end start
))
512 (setf (%rune res i
) (code-rune
513 (logand #xFFFF
(aref string
(+ i start
))))))
516 (defun q-token (string start end
) ;;-> type semantic new-start
518 (cond ((>= start end
)
519 (values :eof nil start
))
521 (let ((c (aref string start
)))
522 (cond ((= c
#.
(char-code #\
>)) (values :> nil
(+ start
1)))
523 ((= c
#.
(char-code #\
+)) (values :+ nil
(+ start
1)))
524 ((= c
#.
(char-code #\
*)) (values :* nil
(+ start
1)))
525 ((= c
#.
(char-code #\
[)) (values :|
[| nil
(+ start
1)))
526 ((= c
#.
(char-code #\
])) (values :|
]| nil
(+ start
1)))
527 ((= c
#.
(char-code #\.
)) (values :|.| nil
(+ start
1)))
528 ((= c
#.
(char-code #\
=)) (values :|
=| nil
(+ start
1)))
529 ((= c
#.
(char-code #\
:)) (values :|
:| nil
(+ start
1)))
530 ((= c
#.
(char-code #\
()) (values :|
(| nil
(+ start
1)))
531 ((= c
#.
(char-code #\
))) (values :|
)| nil
(+ start
1)))
532 ((= c
#.
(char-code #\
,)) (values :|
,| nil
(+ start
1)))
533 ((and (= c
#.
(char-code #\~
))
535 (= (aref string
(+ start
1)) #.
(char-code #\
=)))
536 (values :|~
=| nil
(+ start
2)))
537 ((and (= c
#.
(char-code #\|
))
539 (= (aref string
(+ start
1)) #.
(char-code #\
=)))
540 (values :\|
= nil
(+ start
2)))
541 ((and (= c
#.
(char-code #\
#))
542 ;; ID selectors cannot start with an digit
544 (nmstart-p (aref string
(+ start
1)))
545 (setq p
(or (position-if-not #'nmchar-p string
:start
(+ start
1) :end end
)
548 (values :hash
(q-cons-rod string
(+ start
1) p
) p
))
550 (setq p
(or (position-if-not #'nmchar-p string
:start
(+ start
1) :end end
)
552 (values :ident
(q-cons-rod string start p
) p
))
553 ((and (= c
#.
(char-code #\'))
554 (setq p
(position #.
(char-code #\') string
:start
(+ start
1) :end end
)))
555 (values :string
(q-cons-rod string
(+ start
1) p
) (+ p
1)))
556 ((and (= c
#.
(char-code #\"))
557 (setq p
(position #.
(char-code #\") string
:start
(+ start
1) :end end
)))
558 (values :string
(q-cons-rod string
(+ start
1) p
) (+ p
1)))
559 ((member c
'(9 10 12 13 32))
560 (values :s nil
(+ start
1)))
562 (values :junk c
(+ start
1))) ))))))
564 ;;; traditional recursive descent parser for CSS-2 selectors
566 ;; Each selector is a simple list of predicates, which must turn for
567 ;; this selector to match; the predicates produced by the parser are:
570 ;; the GI of element must be 'string'
572 ;; the class attribute of the element must contain 'string'
574 ;; the ID attribute of the element must be 'string'
575 ;; (ATTRIB attr string)
576 ;; the attribute given by 'attr' must be 'string'
577 ;; (ATTRIB-EXISTS attr string)
578 ;; the attribute given by 'attr' must exist (been set)
579 ;; (ATTRIB-CONTAIN attr string)
580 ;; the attribute given by 'attr' must contain 'string'
581 ;; (ATTRIB-CONTAIN-DASH attr string)
582 ;; the attribute given by 'attr' must contain 'string' (dash variant)
583 ;; (PCLASS name [argument])
584 ;; the element must belong to the given pseudo class
585 ;; (ANCESTOR . predicates)
586 ;; there must be an anchestor matching the selector 'predicates'
587 ;; (PARENT . predicates)
588 ;; the element must have a parent matching the selector 'predicates'
589 ;; (PRECEDED-BY . predicates)
590 ;; the element must have be preceded by an element matching the selector 'predicates'
594 ;; "A B C[x]" => ((GI "C")
595 ;; (ATTRIB-EXISTS "x")
596 ;; (ANCESTOR (GI "B")
597 ;; (ANCESTOR (GI "A"))))
607 (defun parse-css2-selector-list (string &optional
(start 0) (end (length string
)))
617 (unless (eq (q/tok
) :eof
)
618 (error "Unexpected token ~S." (q/tok
)))))))
620 (mapcar (lambda (selector)
621 (let ((x (find 'gi selector
:key
#'car
)))
623 (cons x
(remove x selector
))
629 (defun q/tok
() *tok
*)
630 (defun q/sem
() *sem
*)
631 (defun q/at?
(tok) (eq *tok
* tok
))
634 (multiple-value-bind (tok sem j
) (q-token *s
* *i
* *e
*)
639 (defun q/selector-list
()
640 (let ((x (q/selector
)))
641 (cond ((eq (q/tok
) :|
,|
)
644 (cons x
(q/selector-list
)))
652 (let ((a (q/simple-selector
)))
655 (cond ((member (q/tok
) '(:ident
:hash
:|.|
:|
[|
:|
:|
:|
*|
))
666 (push 'preceded-by res
)
671 (parse-combinator-sequence res
)))
673 (defun parse-combinator-sequence (q)
674 (cond ((null (cdr q
)) (car q
))
678 (parse-combinator-sequence (cddr q
))))))))
682 (defun q/simple-selector
()
683 (multiple-value-bind (element presentp
) (q/maybe-element-name
)
684 (let ((modifiers nil
)
687 (setq x
(q/maybe-modifier
))
689 (error "When there is no element name at least one modifiers is required."))
690 (setf modifiers
(append modifiers
(list x
))))
692 (setq x
(q/maybe-modifier
))
694 (setf modifiers
(append modifiers
(list x
))))
698 ;; xxx does this apply to CSS2 as well.
699 ;; xxx also this looks like some bogus random implementation limitation.
700 (when (member-if-not (lambda (x) (and (consp x
) (eq (car x
) 'pclass
)))
701 (member-if (lambda (x) (and (consp x
) (eq (car x
) 'pclass
)))
703 (error "Psuedoclass in non trailing location: ~S." modifiers
))
705 (append element modifiers
))))
708 (while (q/at?
:s
) (q/consume
)))
710 (defun q/maybe-modifier
()
712 (:hash
(prog1 (list 'ID
(q/sem
)) (q/consume
)))
715 (cond ((eq (q/tok
) :ident
)
716 (prog1 (list 'CLASS
(q/sem
)) (q/consume
)))
718 (error "Parse error: Expected <ident> after \".\""))))
726 (defun q/expect
(tok)
727 (unless (eq (q/tok
) tok
)
728 (error "Expected ~A." tok
))
739 (setf x
(q/maybe-attribut-value name
))
743 (defun q/maybe-attribut-value
(name)
748 (prog1 (list 'attrib name
(q/maybe-attribut-value-2
))
753 (prog1 (list 'attrib-contain name
(q/maybe-attribut-value-2
))
758 (prog1 (list 'attrib-contain-dash name
(q/maybe-attribut-value-2
))
761 (list 'attrib-exists name
))))
766 (setf nam
(q/expect
:ident
))
768 (cond ((eq (q/tok
) :|
(|
)
771 (setf arg
(q/expect
:ident
))
779 (defun q/maybe-attribut-value-2
()
780 (unless (member (q/tok
) '(:ident
:string
))
781 (error "Expected either a string or an ident here."))
786 (defun q/maybe-element-name
()
787 (cond ((q/at?
:ident
)
788 (multiple-value-prog1
789 (values (list (list 'GI
(intern-gi (q/sem
)))) t
)
792 (multiple-value-prog1
800 (defun css2-selector-specificity (selector)
801 (let ((res (vector 0 0 0)))
804 ((gi) (incf (aref res
2)))
805 ((id) (incf (aref res
0)))
806 ((class attrib attrib-exists attrib-contain attrib-contain-dash pclass
)
808 ((ancestor parent preceded-by
)
809 (setf res
(map 'vector
#'+ res
(css2-selector-specificity (cdr p
)))))))
812 (defun parse-style-sheet* (seq &optional
(start 0) (import-ok? t
))
814 (when (setq p0
(position-if-not #'white-space-p
* seq
:start start
))
816 ((= (aref seq p0
) #.
(char-code #\
@))
817 (multiple-value-bind (v p1
) (parse-at-rule seq p0 import-ok?
)
818 (nconc v
(parse-style-sheet* seq p1 import-ok?
))))
819 ((setq p1
(position (char-code #\
{) seq
:start p0
))
820 (let ((p2 (skip-group seq p1
)))
822 (warn "EOF while parsing CSS group.")
825 (multiple-value-bind (sel-list condition
)
826 (ignore-errors (parse-css2-selector-list seq p0 p1
))
828 (warn "CSS selector list does not parse: `~A'."
829 (as-string (subseq seq p0 p1
)))
830 (setq sel-list nil
)))
831 (nconc (multiplex-selectors sel-list
832 (parse-assignment-list
833 (subseq seq
(+ p1
1) p2
)))
834 (parse-style-sheet* seq
(+ p2
1) nil
)) )))))
836 (warn "Bad css syntax: ~A" (as-string seq
))
840 (defun lookup-all-style (style-sheet pt is ss res
)
841 (multiple-value-bind (x p
) (find-style style-sheet pt is
)
842 (dolist (k (reverse ss
))
843 (let ((prop (assignment-slot k
))
844 (value (assignment-value k
))
845 (importantp (assignment-importantp k
)))
846 (setf x
(augment-assignment-to-result prop value
(vector (if importantp
1 0) 0 1 0 0
849 (dotimes (i (length x
))
850 (let ((q (svref x i
)))
852 (setf (svref res i
) (first q
)))))
858 (defparameter *media-types
*
861 (defmethod is-of-media-type-p ((medium T
) (media-type (eql :all
))) t
)
862 (defmethod is-of-media-type-p ((medium (eql :screen
)) (media-type (eql :screen
))) t
)
864 (defmethod is-of-media-type-p ((medium T
) (media-type T
))
867 (defmethod is-of-media-type-p ((medium T
) (media-type cons
))
868 (ecase (car media-type
)
870 (some (curry #'is-of-media-type-p medium
) media-type
))
872 (every (curry #'is-of-media-type-p medium
) media-type
))))
874 (defun intern-media-type (rod)
875 (intern-attribute-name rod
))
878 ;; - parse-media-type is borken/ugly
879 ;; - parse-style-sheet -- fixed :screen media type is borken
880 ;; - p/ident is not unicode safe!
881 ;; - some how we broke "CSS1 Test Suite: LINK and @import" ;-(