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