1 """Selector is a single Selector of a CSSStyleRule SelectorList.
2
3 Partly implements
4 http://www.w3.org/TR/css3-selectors/
5
6 TODO
7 - .contains(selector)
8 - .isSubselector(selector)
9
10 - ??? CSS2 gives a special meaning to the comma (,) in selectors.
11 However, since it is not known if the comma may acquire other
12 meanings in future versions of CSS, the whole statement should be
13 ignored if there is an error anywhere in the selector, even though
14 the rest of the selector may look reasonable in CSS2.
15
16 Illegal example(s):
17
18 For example, since the "&" is not a valid token in a CSS2 selector,
19 a CSS2 user agent must ignore the whole second line, and not set
20 the color of H3 to red:
21 """
22 __all__ = ['Selector']
23 __docformat__ = 'restructuredtext'
24 __author__ = '$LastChangedBy: cthedot $'
25 __date__ = '$LastChangedDate: 2007-12-25 21:53:11 +0100 (Di, 25 Dez 2007) $'
26 __version__ = '$LastChangedRevision: 723 $'
27
28 import xml.dom
29
30 import cssutils
31
33 """
34 (cssutils) a single selector in a SelectorList of a CSSStyleRule
35
36 Properties
37 ==========
38 selectorText
39 textual representation of this Selector
40 prefixes
41 a set which prefixes have been used in this selector
42 seq
43 sequence of Selector parts including comments
44
45 wellformed
46 if this selector is wellformed regarding the Selector spec
47
48 Format
49 ======
50 ::
51
52 # implemented in SelectorList
53 selectors_group
54 : selector [ COMMA S* selector ]*
55 ;
56
57 selector
58 : simple_selector_sequence [ combinator simple_selector_sequence ]*
59 ;
60
61 combinator
62 /* combinators can be surrounded by white space */
63 : PLUS S* | GREATER S* | TILDE S* | S+
64 ;
65
66 simple_selector_sequence
67 : [ type_selector | universal ]
68 [ HASH | class | attrib | pseudo | negation ]*
69 | [ HASH | class | attrib | pseudo | negation ]+
70 ;
71
72 type_selector
73 : [ namespace_prefix ]? element_name
74 ;
75
76 namespace_prefix
77 : [ IDENT | '*' ]? '|'
78 ;
79
80 element_name
81 : IDENT
82 ;
83
84 universal
85 : [ namespace_prefix ]? '*'
86 ;
87
88 class
89 : '.' IDENT
90 ;
91
92 attrib
93 : '[' S* [ namespace_prefix ]? IDENT S*
94 [ [ PREFIXMATCH |
95 SUFFIXMATCH |
96 SUBSTRINGMATCH |
97 '=' |
98 INCLUDES |
99 DASHMATCH ] S* [ IDENT | STRING ] S*
100 ]? ']'
101 ;
102
103 pseudo
104 /* '::' starts a pseudo-element, ':' a pseudo-class */
105 /* Exceptions: :first-line, :first-letter, :before and :after. */
106 /* Note that pseudo-elements are restricted to one per selector and */
107 /* occur only in the last simple_selector_sequence. */
108 : ':' ':'? [ IDENT | functional_pseudo ]
109 ;
110
111 functional_pseudo
112 : FUNCTION S* expression ')'
113 ;
114
115 expression
116 /* In CSS3, the expressions are identifiers, strings, */
117 /* or of the form "an+b" */
118 : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
119 ;
120
121 negation
122 : NOT S* negation_arg S* ')'
123 ;
124
125 negation_arg
126 : type_selector | universal | HASH | class | attrib | pseudo
127 ;
128
129 """
130 - def __init__(self, selectorText=None, readonly=False):
131 """
132 selectorText
133 initial value of this selector
134 readonly
135 default to False
136 """
137 super(Selector, self).__init__()
138
139
140 self.wellformed = False
141 self.seq = self._newseq()
142 self.prefixes = set()
143 if selectorText:
144 self.selectorText = selectorText
145 self._readonly = readonly
146
147
149 """
150 returns serialized format
151 """
152 return cssutils.ser.do_css_Selector(self)
153
154 - def _setSelectorText(self, selectorText):
155 """
156 sets this selectorText
157
158 TODO:
159 raises xml.dom.Exception
160 """
161 self._checkReadonly()
162 tokenizer = self._tokenize2(selectorText)
163 if not tokenizer:
164 self._log.error(u'Selector: No selectorText given.')
165 else:
166
167
168
169
170
171
172
173
174 tokens = []
175 for t in tokenizer:
176 typ, val, lin, col = t
177 if val == u':' and tokens and\
178 self._tokenvalue(tokens[-1]) == ':':
179
180 tokens[-1] = (typ, u'::', lin, col)
181
182 elif typ == 'IDENT' and tokens\
183 and self._tokenvalue(tokens[-1]) == u'.':
184
185 tokens[-1] = ('class', u'.'+val, lin, col)
186 elif typ == 'IDENT' and tokens and \
187 self._tokenvalue(tokens[-1]).startswith(u':') and\
188 not self._tokenvalue(tokens[-1]).endswith(u'('):
189
190 if self._tokenvalue(tokens[-1]).startswith(u'::'):
191 t = 'pseudo-element'
192 else:
193 t = 'pseudo-class'
194 tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
195
196 elif typ == 'FUNCTION' and val == u'not(' and tokens and \
197 u':' == self._tokenvalue(tokens[-1]):
198 tokens[-1] = ('negation', u':' + val, lin, tokens[-1][3])
199 elif typ == 'FUNCTION' and tokens\
200 and self._tokenvalue(tokens[-1]).startswith(u':'):
201
202 if self._tokenvalue(tokens[-1]).startswith(u'::'):
203 t = 'pseudo-element'
204 else:
205 t = 'pseudo-class'
206 tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
207
208 elif val == u'*' and tokens and\
209 self._type(tokens[-1]) == 'namespace_prefix' and\
210 self._tokenvalue(tokens[-1]).endswith(u'|'):
211
212 tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val,
213 lin, col)
214 elif val == u'*':
215
216 tokens.append(('universal', val, lin, col))
217
218 elif val == u'|' and tokens and\
219 self._type(tokens[-1]) in ('IDENT', 'universal') and\
220 self._tokenvalue(tokens[-1]).find(u'|') == -1:
221
222 tokens[-1] = ('namespace_prefix',
223 self._tokenvalue(tokens[-1])+u'|', lin, col)
224 elif val == u'|':
225
226 tokens.append(('namespace_prefix', val, lin, col))
227
228 else:
229 tokens.append(t)
230
231
232 tokenizer = (t for t in tokens)
233
234
235 new = {'wellformed': True,
236 'context': ['ROOT'],
237 'prefixes': set()
238 }
239
240 S = u' '
241
242 def append(seq, val, typ=None):
243 "appends to seq, may be expanded later"
244 seq.append(val, typ)
245
246
247 simple_selector_sequence = 'type_selector universal HASH class attrib pseudo negation '
248 simple_selector_sequence2 = 'HASH class attrib pseudo negation '
249
250 element_name = 'element_name'
251
252 negation_arg = 'type_selector universal HASH class attrib pseudo'
253 negationend = ')'
254
255 attname = 'prefix attribute'
256 attname2 = 'attribute'
257 attcombinator = 'combinator ]'
258 attvalue = 'value'
259 attend = ']'
260
261 expressionstart = 'PLUS - DIMENSION NUMBER STRING IDENT'
262 expression = expressionstart + ' )'
263
264 combinator = ' combinator'
265
266 def _COMMENT(expected, seq, token, tokenizer=None):
267 "special implementation for comment token"
268 append(seq, cssutils.css.CSSComment([token]))
269 return expected
270
271 def _S(expected, seq, token, tokenizer=None):
272
273 context = new['context'][-1]
274 val, typ = self._tokenvalue(token), self._type(token)
275 if context.startswith('pseudo-'):
276 append(seq, S, 'combinator')
277 return expected
278
279 elif context != 'attrib' and 'combinator' in expected:
280 append(seq, S, 'combinator')
281 return simple_selector_sequence + combinator
282
283 else:
284 return expected
285
286 def _universal(expected, seq, token, tokenizer=None):
287
288 context = new['context'][-1]
289 val, typ = self._tokenvalue(token), self._type(token)
290 if 'universal' in expected:
291 append(seq, val, typ)
292
293 newprefix = val.split(u'|')[0]
294 if newprefix and newprefix != u'*':
295 new['prefixes'].add(newprefix)
296
297 if 'negation' == context:
298 return negationend
299 else:
300 return simple_selector_sequence2 + combinator
301
302 else:
303 new['wellformed'] = False
304 self._log.error(
305 u'Selector: Unexpected universal.', token=token)
306 return expected
307
308 def _namespace_prefix(expected, seq, token, tokenizer=None):
309
310
311 context = new['context'][-1]
312 val, typ = self._tokenvalue(token), self._type(token)
313 if 'attrib' == context and 'prefix' in expected:
314
315 append(seq, val, typ)
316
317 newprefix = val.split(u'|')[0]
318 if newprefix and newprefix != u'*':
319 new['prefixes'].add(newprefix)
320
321 return attname2
322 elif 'type_selector' in expected:
323
324 append(seq, val, typ)
325
326 newprefix = val.split(u'|')[0]
327 if newprefix and newprefix != u'*':
328 new['prefixes'].add(newprefix)
329
330 return element_name
331 else:
332 new['wellformed'] = False
333 self._log.error(
334 u'Selector: Unexpected namespace prefix.', token=token)
335 return expected
336
337 def _pseudo(expected, seq, token, tokenizer=None):
338
339 """
340 /* '::' starts a pseudo-element, ':' a pseudo-class */
341 /* Exceptions: :first-line, :first-letter, :before and :after. */
342 /* Note that pseudo-elements are restricted to one per selector and */
343 /* occur only in the last simple_selector_sequence. */
344 """
345 context = new['context'][-1]
346 val, typ = self._tokenvalue(token, normalize=True), self._type(token)
347 if 'pseudo' in expected:
348 if val in (':first-line', ':first-letter', ':before', ':after'):
349
350 typ = 'pseudo-element'
351 append(seq, val, typ)
352
353 if val.endswith(u'('):
354
355 new['context'].append(typ)
356 return expressionstart
357 elif 'negation' == context:
358 return negationend
359 elif 'pseudo-element' == typ:
360
361 return combinator
362 else:
363 return simple_selector_sequence2 + combinator
364
365 else:
366 new['wellformed'] = False
367 self._log.error(
368 u'Selector: Unexpected start of pseudo.', token=token)
369 return expected
370
371 def _expression(expected, seq, token, tokenizer=None):
372
373 context = new['context'][-1]
374 val, typ = self._tokenvalue(token), self._type(token)
375 if context.startswith('pseudo-'):
376 append(seq, val, typ)
377 return expression
378 else:
379 new['wellformed'] = False
380 self._log.error(
381 u'Selector: Unexpected %s.' % typ, token=token)
382 return expected
383
384 def _attcombinator(expected, seq, token, tokenizer=None):
385
386
387
388 context = new['context'][-1]
389 val, typ = self._tokenvalue(token), self._type(token)
390 if 'attrib' == context and 'combinator' in expected:
391
392 append(seq, val)
393 return attvalue
394 else:
395 new['wellformed'] = False
396 self._log.error(
397 u'Selector: Unexpected %s.' % typ, token=token)
398 return expected
399
400 def _string(expected, seq, token, tokenizer=None):
401
402 context = new['context'][-1]
403 val, typ = self._tokenvalue(token), self._type(token)
404
405
406 if 'attrib' == context and 'value' in expected:
407
408 append(seq, val, typ)
409 return attend
410
411
412 elif context.startswith('pseudo-'):
413
414 append(seq, val, typ)
415 return expression
416
417 else:
418 new['wellformed'] = False
419 self._log.error(
420 u'Selector: Unexpected STRING.', token=token)
421 return expected
422
423 def _ident(expected, seq, token, tokenizer=None):
424
425 context = new['context'][-1]
426 val, typ = self._tokenvalue(token), self._type(token)
427
428
429 if 'attrib' == context and 'attribute' in expected:
430
431 append(seq, val, typ)
432 return attcombinator
433
434 elif 'attrib' == context and 'value' in expected:
435
436 append(seq, val, typ)
437 return attend
438
439
440 elif 'negation' == context:
441
442 append(seq, val, typ)
443 return negationend
444
445
446 elif context.startswith('pseudo-'):
447
448 append(seq, val, typ)
449 return expression
450
451 elif 'type_selector' in expected or element_name == expected:
452
453 append(seq, val, typ)
454 return simple_selector_sequence2 + combinator
455
456 else:
457 new['wellformed'] = False
458 self._log.error(
459 u'Selector: Unexpected IDENT.',
460 token=token)
461 return expected
462
463 def _class(expected, seq, token, tokenizer=None):
464
465 context = new['context'][-1]
466 val, typ = self._tokenvalue(token), self._type(token)
467 if 'class' in expected:
468 append(seq, val, typ)
469
470 if 'negation' == context:
471 return negationend
472 else:
473 return simple_selector_sequence2 + combinator
474
475 else:
476 new['wellformed'] = False
477 self._log.error(
478 u'Selector: Unexpected class.', token=token)
479 return expected
480
481 def _hash(expected, seq, token, tokenizer=None):
482
483 context = new['context'][-1]
484 val, typ = self._tokenvalue(token), self._type(token)
485 if 'HASH' in expected:
486 append(seq, val, typ)
487
488 if 'negation' == context:
489 return negationend
490 else:
491 return simple_selector_sequence2 + combinator
492
493 else:
494 new['wellformed'] = False
495 self._log.error(
496 u'Selector: Unexpected HASH.', token=token)
497 return expected
498
499 def _char(expected, seq, token, tokenizer=None):
500
501 context = new['context'][-1]
502 val, typ = self._tokenvalue(token), self._type(token)
503
504
505 if u']' == val and 'attrib' == context and ']' in expected:
506
507 append(seq, val)
508 context = new['context'].pop()
509 context = new['context'][-1]
510 if 'negation' == context:
511 return negationend
512 else:
513 return simple_selector_sequence2 + combinator
514
515 elif u'=' == val and 'attrib' == context and 'combinator' in expected:
516
517 append(seq, val)
518 return attvalue
519
520
521 elif u')' == val and 'negation' == context and u')' in expected:
522
523 append(seq, val)
524 new['context'].pop()
525 context = new['context'][-1]
526 return simple_selector_sequence + combinator
527
528
529 elif val in u'+-' and context.startswith('pseudo-'):
530
531 append(seq, val)
532 return expression
533
534 elif u')' == val and context.startswith('pseudo-') and\
535 expression == expected:
536
537 append(seq, val)
538 new['context'].pop()
539 if 'pseudo-element' == context:
540 return combinator
541 else:
542 return simple_selector_sequence + combinator
543
544
545 elif u'[' == val and 'attrib' in expected:
546
547 new['context'].append('attrib')
548 append(seq, val)
549 return attname
550
551 elif val in u'+>~' and 'combinator' in expected:
552
553 if seq and S == seq[-1]:
554 seq[-1] = (val, 'combinator')
555 else:
556 append(seq, val, 'combinator')
557 return simple_selector_sequence
558
559 elif u',' == val:
560
561 new['wellformed'] = False
562 self._log.error(
563 u'Selector: Single selector only.',
564 error=xml.dom.InvalidModificationErr,
565 token=token)
566
567 else:
568 new['wellformed'] = False
569 self._log.error(
570 u'Selector: Unexpected CHAR.', token=token)
571 return expected
572
573 def _negation(expected, seq, token, tokenizer=None):
574
575 val = self._tokenvalue(token, normalize=True)
576 if 'negation' in expected:
577 new['context'].append('negation')
578 append(seq, val, 'negation')
579 return negation_arg
580 else:
581 new['wellformed'] = False
582 self._log.error(
583 u'Selector: Unexpected negation.', token=token)
584 return expected
585
586
587 newseq = self._newseq()
588 wellformed, expected = self._parse(expected=simple_selector_sequence,
589 seq=newseq, tokenizer=tokenizer,
590 productions={'CHAR': _char,
591 'class': _class,
592 'HASH': _hash,
593 'STRING': _string,
594 'IDENT': _ident,
595 'namespace_prefix': _namespace_prefix,
596 'negation': _negation,
597 'pseudo-class': _pseudo,
598 'pseudo-element': _pseudo,
599 'universal': _universal,
600
601 'NUMBER': _expression,
602 'DIMENSION': _expression,
603
604 'PREFIXMATCH': _attcombinator,
605 'SUFFIXMATCH': _attcombinator,
606 'SUBSTRINGMATCH': _attcombinator,
607 'DASHMATCH': _attcombinator,
608 'INCLUDES': _attcombinator,
609
610 'S': _S,
611 'COMMENT': _COMMENT})
612 wellformed = wellformed and new['wellformed']
613
614
615 if len(new['context']) > 1:
616 wellformed = False
617 self._log.error(u'Selector: Incomplete selector: %s' %
618 self._valuestr(selectorText))
619
620 if expected == 'element_name':
621 wellformed = False
622 self._log.error(u'Selector: No element name found: %s' %
623 self._valuestr(selectorText))
624
625 if expected == simple_selector_sequence:
626 wellformed = False
627 self._log.error(u'Selector: Cannot end with combinator: %s' %
628 self._valuestr(selectorText))
629
630 if newseq and hasattr(newseq[-1], 'strip') and newseq[-1].strip() == u'':
631 del newseq[-1]
632
633
634 if wellformed:
635 self.wellformed = True
636 self.seq = newseq
637 self.prefixes = new['prefixes']
638
639 selectorText = property(_getSelectorText, _setSelectorText,
640 doc="(DOM) The parsable textual representation of the selector.")
641
643 return "cssutils.css.%s(selectorText=%r)" % (
644 self.__class__.__name__, self.selectorText)
645
647 return "<cssutils.css.%s object selectorText=%r at 0x%x>" % (
648 self.__class__.__name__, self.selectorText, id(self))
649