Package cssutils :: Package css :: Module selector
[hide private]
[frames] | no frames]

Source Code for Module cssutils.css.selector

  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-24 23:33:21 +0100 (Sa, 24 Nov 2007) $' 
 26  __version__ = '$LastChangedRevision: 680 $' 
 27   
 28  import xml.dom 
 29   
 30  import cssutils 
 31   
32 -class Selector(cssutils.util.Base):
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
144 - def _getSelectorText(self):
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 # prepare tokenlist: 163 # "*" -> type "universal" 164 # "*"|IDENT + "|" -> combined to "namespace_prefix" 165 # "|" -> type "namespace_prefix" 166 # "." + IDENT -> combined to "class" 167 # ":" + IDENT, ":" + FUNCTION -> pseudo-class 168 # FUNCTION "not(" -> negation 169 # "::" + IDENT, "::" + FUNCTION -> pseudo-element 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 # combine ":" and ":" 176 tokens[-1] = (typ, u'::', lin, col) 177 178 elif typ == 'IDENT' and tokens\ 179 and self._tokenvalue(tokens[-1]) == u'.': 180 # class: combine to .IDENT 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 # pseudo-X: combine to :IDENT or ::IDENT but not ":a(" + "b" 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 # pseudo-X: combine to :FUNCTION( or ::FUNCTION( 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 # combine prefix|* 208 tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val, 209 lin, col) 210 elif val == u'*': 211 # universal: "*" 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 # namespace_prefix: "IDENT|" or "*|" 218 tokens[-1] = ('namespace_prefix', 219 self._tokenvalue(tokens[-1])+u'|', lin, col) 220 elif val == u'|': 221 # namespace_prefix: "|" 222 tokens.append(('namespace_prefix', val, lin, col)) 223 224 else: 225 tokens.append(t) 226 227 # TODO: back to generator but not elegant at all! 228 tokenizer = (t for t in tokens) 229 230 # for closures: must be a mutable 231 new = {'valid': True, 232 'context': ['ROOT'], # stack of: 'attrib', 'negation', 'pseudo' 233 'prefixes': set() 234 } 235 # used for equality checks and setting of a space combinator 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 # expected constants 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 ]' # optional 254 attvalue = 'value' # optional 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 # S 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 # *|* or prefix|* 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 # prefix| => element_name 305 # or prefix| => attribute_name if attrib 306 context = new['context'][-1] 307 val, typ = self._tokenvalue(token), self._type(token) 308 if 'attrib' == context and 'prefix' in expected: 309 # [PREFIX|att] 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 # PREFIX|* 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 # pseudo-class or pseudo-element :a ::a :a( ::a( 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 # always pseudo-element ??? 344 typ = 'pseudo-element' 345 append(seq, val, typ) 346 347 if val.endswith(u'('): 348 # function 349 new['context'].append(typ) # "pseudo-" "class" or "element" 350 return expressionstart 351 elif 'negation' == context: 352 return negationend 353 elif 'pseudo-element' == typ: 354 # only one per element, check at ) also! 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 # [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ 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 # context: attrib 378 # PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | INCLUDES | 379 # DASHMATCH 380 context = new['context'][-1] 381 val, typ = self._tokenvalue(token), self._type(token) 382 if 'attrib' == context and 'combinator' in expected: 383 # combinator in attrib 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 # identifier 393 context = new['context'][-1] 394 val, typ = self._tokenvalue(token), self._type(token) 395 396 # context: attrib 397 if 'attrib' == context and 'value' in expected: 398 # attrib: [...=VALUE] 399 append(seq, val, typ) 400 return attend 401 402 # context: pseudo 403 elif context.startswith('pseudo-'): 404 # :func(...) 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 # identifier 415 context = new['context'][-1] 416 val, typ = self._tokenvalue(token), self._type(token) 417 418 # context: attrib 419 if 'attrib' == context and 'attribute' in expected: 420 # attrib: [...|ATT...] 421 append(seq, val, typ) 422 return attcombinator 423 424 elif 'attrib' == context and 'value' in expected: 425 # attrib: [...=VALUE] 426 append(seq, val, typ) 427 return attend 428 429 # context: negation 430 elif 'negation' == context: 431 # negation: (prefix|IDENT) 432 append(seq, val, typ) 433 return negationend 434 435 # context: pseudo 436 elif context.startswith('pseudo-'): 437 # :func(...) 438 append(seq, val, typ) 439 return expression 440 441 elif 'type_selector' in expected or element_name == expected: 442 # element name after ns or complete type_selector 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 # .IDENT 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 # #IDENT 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 # + > ~ S 487 context = new['context'][-1] 488 val, typ = self._tokenvalue(token), self._type(token) 489 490 # context: attrib 491 if u']' == val and 'attrib' == context and ']' in expected: 492 # end of attrib 493 append(seq, val) 494 context = new['context'].pop() # attrib is done 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 # combinator in attrib 503 append(seq, val) 504 return attvalue 505 506 # context: negation 507 elif u')' == val and 'negation' == context and u')' in expected: 508 # not(negation_arg)" 509 append(seq, val) 510 new['context'].pop() # negation is done 511 context = new['context'][-1] 512 return simple_selector_sequence + combinator 513 514 # context: pseudo (at least one expression) 515 elif val in u'+-' and context.startswith('pseudo-'): 516 # :func(+ -)" 517 append(seq, val) 518 return expression 519 520 elif u')' == val and context.startswith('pseudo-') and\ 521 expression == expected: 522 # :func(expression)" 523 append(seq, val) 524 new['context'].pop() # pseudo is done 525 if 'pseudo-element' == context: 526 return combinator 527 else: 528 return simple_selector_sequence + combinator 529 530 # context: ROOT 531 elif u'[' == val and 'attrib' in expected: 532 # start of [attrib] 533 new['context'].append('attrib') 534 append(seq, val) 535 return attname 536 537 elif val in u'+>~' and 'combinator' in expected: 538 # no other combinator except S may be following 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 # not a selectorlist 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 # not( 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 # expected: only|not or mediatype, mediatype, feature, and 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 # pseudo 584 'NUMBER': _expression, 585 'DIMENSION': _expression, 586 # attribute 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 # post condition 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 if newseq and hasattr(newseq[-1], 'strip') and newseq[-1].strip() == u'': 614 del newseq[-1] 615 616 # set 617 if valid: 618 self.valid = True 619 self.seq = newseq 620 self.prefixes = new['prefixes'] 621 622 selectorText = property(_getSelectorText, _setSelectorText, 623 doc="(DOM) The parsable textual representation of the selector.") 624
625 - def __repr__(self):
626 return "cssutils.css.%s(selectorText=%r)" % ( 627 self.__class__.__name__, self.selectorText)
628
629 - def __str__(self):
630 return "<cssutils.css.%s object selectorText=%r at 0x%x>" % ( 631 self.__class__.__name__, self.selectorText, id(self))
632