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-10-18 19:38:00 +0200 (Do, 18 Okt 2007) $'
26 __version__ = '$LastChangedRevision: 499 $'
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(':
193 tokens.append(('negation', val, lin, col))
194 elif typ == 'FUNCTION' and tokens\
195 and self._tokenvalue(tokens[-1]).startswith(u':'):
196
197 if self._tokenvalue(tokens[-1]).startswith(u'::'):
198 t = 'pseudo-element'
199 else:
200 t = 'pseudo-class'
201 tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
202
203 elif val == u'*' and tokens and\
204 self._type(tokens[-1]) == 'namespace_prefix' and\
205 self._tokenvalue(tokens[-1]).endswith(u'|'):
206
207 tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val,
208 lin, col)
209 elif val == u'*':
210
211 tokens.append(('universal', val, lin, col))
212
213 elif val == u'|' and tokens and\
214 self._type(tokens[-1]) in ('IDENT', 'universal') and\
215 self._tokenvalue(tokens[-1]).find(u'|') == -1:
216
217 tokens[-1] = ('namespace_prefix',
218 self._tokenvalue(tokens[-1])+u'|', lin, col)
219 elif val == u'|':
220
221 tokens.append(('namespace_prefix', val, lin, col))
222
223 else:
224 tokens.append(t)
225
226
227 tokenizer = (t for t in tokens)
228
229
230 new = {'valid': True,
231 'context': ['ROOT'],
232 'prefixes': set()
233 }
234
235 S = u' '
236
237 def append(seq, val, typ=None):
238 "appends to seq, may be expanded later"
239 seq.append(val, typ)
240
241
242 simple_selector_sequence = 'type_selector universal HASH class attrib pseudo negation '
243 simple_selector_sequence2 = 'HASH class attrib pseudo negation '
244
245 element_name = 'element_name'
246
247 negation_arg = 'type_selector universal HASH class attrib pseudo'
248 negationend = ')'
249
250 attname = 'prefix attribute'
251 attname2 = 'attribute'
252 attcombinator = 'combinator ]'
253 attvalue = 'value'
254 attend = ']'
255
256 expressionstart = 'PLUS - DIMENSION NUMBER STRING IDENT'
257 expression = expressionstart + ' )'
258
259 combinator = ' combinator'
260
261 def _COMMENT(expected, seq, token, tokenizer=None):
262 "special implementation for comment token"
263 append(seq, cssutils.css.CSSComment([token]))
264 return expected
265
266 def _S(expected, seq, token, tokenizer=None):
267
268 context = new['context'][-1]
269 val, typ = self._tokenvalue(token), self._type(token)
270 if context.startswith('pseudo-'):
271 append(seq, S, 'combinator')
272 return expected
273
274 elif context != 'attrib' and 'combinator' in expected:
275 append(seq, S, 'combinator')
276 return simple_selector_sequence + combinator
277
278 else:
279 return expected
280
281 def _universal(expected, seq, token, tokenizer=None):
282
283 context = new['context'][-1]
284 val, typ = self._tokenvalue(token), self._type(token)
285 if 'universal' in expected:
286 append(seq, val, typ)
287
288 newprefix = val.split(u'|')[0]
289 if newprefix and newprefix != u'*':
290 new['prefixes'].add(newprefix)
291
292 if 'negation' == context:
293 return negationend
294 else:
295 return simple_selector_sequence2 + combinator
296
297 else:
298 self._log.error(
299 u'Selector: Unexpected universal.', token=token)
300 return expected
301
302 def _namespace_prefix(expected, seq, token, tokenizer=None):
303
304
305 context = new['context'][-1]
306 val, typ = self._tokenvalue(token), self._type(token)
307 if 'attrib' == context and 'prefix' in expected:
308
309 append(seq, val, typ)
310
311 newprefix = val.split(u'|')[0]
312 if newprefix and newprefix != u'*':
313 new['prefixes'].add(newprefix)
314
315 return attname2
316 elif 'type_selector' in expected:
317
318 append(seq, val, typ)
319
320 newprefix = val.split(u'|')[0]
321 if newprefix and newprefix != u'*':
322 new['prefixes'].add(newprefix)
323
324 return element_name
325 else:
326 self._log.error(
327 u'Selector: Unexpected namespace prefix.', token=token)
328 return expected
329
330 def _pseudo(expected, seq, token, tokenizer=None):
331
332 """
333 /* '::' starts a pseudo-element, ':' a pseudo-class */
334 /* Exceptions: :first-line, :first-letter, :before and :after. */
335 /* Note that pseudo-elements are restricted to one per selector and */
336 /* occur only in the last simple_selector_sequence. */
337 """
338 context = new['context'][-1]
339 val, typ = self._tokenvalue(token, normalize=True), self._type(token)
340 if 'pseudo' in expected:
341 if val in (':first-line', ':first-letter', ':before', ':after'):
342
343 typ = 'pseudo-element'
344 append(seq, val, typ)
345
346 if val.endswith(u'('):
347
348 new['context'].append(typ)
349 return expressionstart
350 elif 'negation' == context:
351 return negationend
352 elif 'pseudo-element' == typ:
353
354 return combinator
355 else:
356 return simple_selector_sequence2 + combinator
357
358 else:
359 self._log.error(
360 u'Selector: Unexpected start of pseudo.', token=token)
361 return expected
362
363 def _expression(expected, seq, token, tokenizer=None):
364
365 context = new['context'][-1]
366 val, typ = self._tokenvalue(token), self._type(token)
367 if context.startswith('pseudo-'):
368 append(seq, val, typ)
369 return expression
370 else:
371 self._log.error(
372 u'Selector: Unexpected %s.' % typ, token=token)
373 return expected
374
375 def _attcombinator(expected, seq, token, tokenizer=None):
376
377
378
379 context = new['context'][-1]
380 val, typ = self._tokenvalue(token), self._type(token)
381 if 'attrib' == context and 'combinator' in expected:
382
383 append(seq, val)
384 return attvalue
385 else:
386 self._log.error(
387 u'Selector: Unexpected %s.' % typ, token=token)
388 return expected
389
390 def _string(expected, seq, token, tokenizer=None):
391
392 context = new['context'][-1]
393 val, typ = self._tokenvalue(token), self._type(token)
394
395
396 if 'attrib' == context and 'value' in expected:
397
398 append(seq, val, typ)
399 return attend
400
401
402 elif context.startswith('pseudo-'):
403
404 append(seq, val, typ)
405 return expression
406
407 else:
408 self._log.error(
409 u'Selector: Unexpected STRING.', token=token)
410 return expected
411
412 def _ident(expected, seq, token, tokenizer=None):
413
414 context = new['context'][-1]
415 val, typ = self._tokenvalue(token), self._type(token)
416
417
418 if 'attrib' == context and 'attribute' in expected:
419
420 append(seq, val, typ)
421 return attcombinator
422
423 elif 'attrib' == context and 'value' in expected:
424
425 append(seq, val, typ)
426 return attend
427
428
429 elif 'negation' == context:
430
431 append(seq, val, typ)
432 return negationend
433
434
435 elif context.startswith('pseudo-'):
436
437 append(seq, val, typ)
438 return expression
439
440 elif 'type_selector' in expected or element_name == expected:
441
442 append(seq, val, typ)
443 return simple_selector_sequence2 + combinator
444
445 else:
446 self._log.error(
447 u'Selector: Unexpected IDENT.', token=token)
448 return expected
449
450 def _class(expected, seq, token, tokenizer=None):
451
452 context = new['context'][-1]
453 val, typ = self._tokenvalue(token), self._type(token)
454 if 'class' in expected:
455 append(seq, val, typ)
456
457 if 'negation' == context:
458 return negationend
459 else:
460 return simple_selector_sequence2 + combinator
461
462 else:
463 self._log.error(
464 u'Selector: Unexpected class.', token=token)
465 return expected
466
467 def _hash(expected, seq, token, tokenizer=None):
468
469 context = new['context'][-1]
470 val, typ = self._tokenvalue(token), self._type(token)
471 if 'HASH' in expected:
472 append(seq, val, typ)
473
474 if 'negation' == context:
475 return negationend
476 else:
477 return simple_selector_sequence2 + combinator
478
479 else:
480 self._log.error(
481 u'Selector: Unexpected HASH.', token=token)
482 return expected
483
484 def _char(expected, seq, token, tokenizer=None):
485
486 context = new['context'][-1]
487 val, typ = self._tokenvalue(token), self._type(token)
488
489
490 if u']' == val and 'attrib' == context and ']' in expected:
491
492 append(seq, val)
493 context = new['context'].pop()
494 context = new['context'][-1]
495 if 'negation' == context:
496 return negationend
497 else:
498 return simple_selector_sequence2 + combinator
499
500 elif u'=' == val and 'attrib' == context and 'combinator' in expected:
501
502 append(seq, val)
503 return attvalue
504
505
506 elif u')' == val and 'negation' == context and u')' in expected:
507
508 append(seq, val)
509 new['context'].pop()
510 context = new['context'][-1]
511 return simple_selector_sequence + combinator
512
513
514 elif val in u'+-' and context.startswith('pseudo-'):
515
516 append(seq, val)
517 return expression
518
519 elif u')' == val and context.startswith('pseudo-') and\
520 expression == expected:
521
522 append(seq, val)
523 new['context'].pop()
524 if 'pseudo-element' == context:
525 return combinator
526 else:
527 return simple_selector_sequence + combinator
528
529
530 elif u'[' == val and 'attrib' in expected:
531
532 new['context'].append('attrib')
533 append(seq, val)
534 return attname
535
536 elif val in u'+>~' and 'combinator' in expected:
537
538 if seq and S == seq[-1]:
539 seq[-1] = (val, 'combinator')
540 else:
541 append(seq, val, 'combinator')
542 return simple_selector_sequence
543
544 elif u',' == val:
545
546 self._log.error(
547 u'Selector: Single selector only.',
548 error=xml.dom.InvalidModificationErr,
549 token=token)
550
551 else:
552 self._log.error(
553 u'Selector: Unexpected CHAR.', token=token)
554 return expected
555
556 def _negation(expected, seq, token, tokenizer=None):
557
558 val = self._tokenvalue(token, normalize=True)
559 if 'negation' in expected:
560 new['context'].append('negation')
561 append(seq, val, 'negation')
562 return negation_arg
563 else:
564 self._log.error(
565 u'Selector: Unexpected negation.', token=token)
566 return expected
567
568
569 newseq = self._newseq()
570 valid, expected = self._parse(expected=simple_selector_sequence,
571 seq=newseq, tokenizer=tokenizer,
572 productions={'CHAR': _char,
573 'class': _class,
574 'HASH': _hash,
575 'STRING': _string,
576 'IDENT': _ident,
577 'namespace_prefix': _namespace_prefix,
578 'negation': _negation,
579 'pseudo-class': _pseudo,
580 'pseudo-element': _pseudo,
581 'universal': _universal,
582
583 'NUMBER': _expression,
584 'DIMENSION': _expression,
585
586 'PREFIXMATCH': _attcombinator,
587 'SUFFIXMATCH': _attcombinator,
588 'SUBSTRINGMATCH': _attcombinator,
589 'DASHMATCH': _attcombinator,
590 'INCLUDES': _attcombinator,
591
592 'S': _S,
593 'COMMENT': _COMMENT})
594 valid = valid and new['valid']
595
596
597 if len(new['context']) > 1:
598 valid = False
599 self._log.error(u'Selector: Incomplete selector: %s' %
600 self._valuestr(selectorText))
601
602 if expected == 'element_name':
603 valid = False
604 self._log.error(u'Selector: No element name found: %s' %
605 self._valuestr(selectorText))
606
607 if expected == simple_selector_sequence:
608 valid = False
609 self._log.error(u'Selector: Cannot end with combinator: %s' %
610 self._valuestr(selectorText))
611
612
613 if valid:
614 self.valid = True
615 self.seq = newseq
616 self.prefixes = new['prefixes']
617
618 selectorText = property(_getSelectorText, _setSelectorText,
619 doc="(DOM) The parsable textual representation of the selector.")
620
622 return "cssutils.css.%s(selectorText=%r)" % (
623 self.__class__.__name__, self.selectorText)
624
626 return "<cssutils.css.%s object selectorText=%r at 0x%x>" % (
627 self.__class__.__name__, self.selectorText, id(self))
628