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-10-18 19:38:00 +0200 (Do, 18 Okt 2007) $' 
 26  __version__ = '$LastChangedRevision: 499 $' 
 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(': 193 tokens.append(('negation', val, lin, col)) 194 elif typ == 'FUNCTION' and tokens\ 195 and self._tokenvalue(tokens[-1]).startswith(u':'): 196 # pseudo-X: combine to :FUNCTION( or ::FUNCTION( 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 # combine prefix|* 207 tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val, 208 lin, col) 209 elif val == u'*': 210 # universal: "*" 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 # namespace_prefix: "IDENT|" or "*|" 217 tokens[-1] = ('namespace_prefix', 218 self._tokenvalue(tokens[-1])+u'|', lin, col) 219 elif val == u'|': 220 # namespace_prefix: "|" 221 tokens.append(('namespace_prefix', val, lin, col)) 222 223 else: 224 tokens.append(t) 225 226 # TODO: back to generator but not elegant at all! 227 tokenizer = (t for t in tokens) 228 229 # for closures: must be a mutable 230 new = {'valid': True, 231 'context': ['ROOT'], # stack of: 'attrib', 'negation', 'pseudo' 232 'prefixes': set() 233 } 234 # used for equality checks and setting of a space combinator 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 # expected constants 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 ]' # optional 253 attvalue = 'value' # optional 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 # S 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 # *|* or prefix|* 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 # prefix| => element_name 304 # or prefix| => attribute_name if attrib 305 context = new['context'][-1] 306 val, typ = self._tokenvalue(token), self._type(token) 307 if 'attrib' == context and 'prefix' in expected: 308 # [PREFIX|att] 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 # PREFIX|* 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 # pseudo-class or pseudo-element :a ::a :a( ::a( 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 # always pseudo-element ??? 343 typ = 'pseudo-element' 344 append(seq, val, typ) 345 346 if val.endswith(u'('): 347 # function 348 new['context'].append(typ) # "pseudo-" "class" or "element" 349 return expressionstart 350 elif 'negation' == context: 351 return negationend 352 elif 'pseudo-element' == typ: 353 # only one per element, check at ) also! 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 # [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ 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 # context: attrib 377 # PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | INCLUDES | 378 # DASHMATCH 379 context = new['context'][-1] 380 val, typ = self._tokenvalue(token), self._type(token) 381 if 'attrib' == context and 'combinator' in expected: 382 # combinator in attrib 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 # identifier 392 context = new['context'][-1] 393 val, typ = self._tokenvalue(token), self._type(token) 394 395 # context: attrib 396 if 'attrib' == context and 'value' in expected: 397 # attrib: [...=VALUE] 398 append(seq, val, typ) 399 return attend 400 401 # context: pseudo 402 elif context.startswith('pseudo-'): 403 # :func(...) 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 # identifier 414 context = new['context'][-1] 415 val, typ = self._tokenvalue(token), self._type(token) 416 417 # context: attrib 418 if 'attrib' == context and 'attribute' in expected: 419 # attrib: [...|ATT...] 420 append(seq, val, typ) 421 return attcombinator 422 423 elif 'attrib' == context and 'value' in expected: 424 # attrib: [...=VALUE] 425 append(seq, val, typ) 426 return attend 427 428 # context: negation 429 elif 'negation' == context: 430 # negation: (prefix|IDENT) 431 append(seq, val, typ) 432 return negationend 433 434 # context: pseudo 435 elif context.startswith('pseudo-'): 436 # :func(...) 437 append(seq, val, typ) 438 return expression 439 440 elif 'type_selector' in expected or element_name == expected: 441 # element name after ns or complete type_selector 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 # .IDENT 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 # #IDENT 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 # + > ~ S 486 context = new['context'][-1] 487 val, typ = self._tokenvalue(token), self._type(token) 488 489 # context: attrib 490 if u']' == val and 'attrib' == context and ']' in expected: 491 # end of attrib 492 append(seq, val) 493 context = new['context'].pop() # attrib is done 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 # combinator in attrib 502 append(seq, val) 503 return attvalue 504 505 # context: negation 506 elif u')' == val and 'negation' == context and u')' in expected: 507 # not(negation_arg)" 508 append(seq, val) 509 new['context'].pop() # negation is done 510 context = new['context'][-1] 511 return simple_selector_sequence + combinator 512 513 # context: pseudo (at least one expression) 514 elif val in u'+-' and context.startswith('pseudo-'): 515 # :func(+ -)" 516 append(seq, val) 517 return expression 518 519 elif u')' == val and context.startswith('pseudo-') and\ 520 expression == expected: 521 # :func(expression)" 522 append(seq, val) 523 new['context'].pop() # pseudo is done 524 if 'pseudo-element' == context: 525 return combinator 526 else: 527 return simple_selector_sequence + combinator 528 529 # context: ROOT 530 elif u'[' == val and 'attrib' in expected: 531 # start of [attrib] 532 new['context'].append('attrib') 533 append(seq, val) 534 return attname 535 536 elif val in u'+>~' and 'combinator' in expected: 537 # no other combinator except S may be following 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 # not a selectorlist 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 # not( 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 # expected: only|not or mediatype, mediatype, feature, and 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 # pseudo 583 'NUMBER': _expression, 584 'DIMENSION': _expression, 585 # attribute 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 # post condition 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 # set 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
621 - def __repr__(self):
622 return "cssutils.css.%s(selectorText=%r)" % ( 623 self.__class__.__name__, self.selectorText)
624
625 - def __str__(self):
626 return "<cssutils.css.%s object selectorText=%r at 0x%x>" % ( 627 self.__class__.__name__, self.selectorText, id(self))
628