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-12-25 21:53:11 +0100 (Di, 25 Dez 2007) $' 
 26  __version__ = '$LastChangedRevision: 723 $' 
 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 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 #self.valid = False # not used anymore 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
148 - def _getSelectorText(self):
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 # prepare tokenlist: 167 # "*" -> type "universal" 168 # "*"|IDENT + "|" -> combined to "namespace_prefix" 169 # "|" -> type "namespace_prefix" 170 # "." + IDENT -> combined to "class" 171 # ":" + IDENT, ":" + FUNCTION -> pseudo-class 172 # FUNCTION "not(" -> negation 173 # "::" + IDENT, "::" + FUNCTION -> pseudo-element 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 # combine ":" and ":" 180 tokens[-1] = (typ, u'::', lin, col) 181 182 elif typ == 'IDENT' and tokens\ 183 and self._tokenvalue(tokens[-1]) == u'.': 184 # class: combine to .IDENT 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 # pseudo-X: combine to :IDENT or ::IDENT but not ":a(" + "b" 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 # pseudo-X: combine to :FUNCTION( or ::FUNCTION( 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 # combine prefix|* 212 tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val, 213 lin, col) 214 elif val == u'*': 215 # universal: "*" 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 # namespace_prefix: "IDENT|" or "*|" 222 tokens[-1] = ('namespace_prefix', 223 self._tokenvalue(tokens[-1])+u'|', lin, col) 224 elif val == u'|': 225 # namespace_prefix: "|" 226 tokens.append(('namespace_prefix', val, lin, col)) 227 228 else: 229 tokens.append(t) 230 231 # TODO: back to generator but not elegant at all! 232 tokenizer = (t for t in tokens) 233 234 # for closures: must be a mutable 235 new = {'wellformed': True, 236 'context': ['ROOT'], # stack of: 'attrib', 'negation', 'pseudo' 237 'prefixes': set() 238 } 239 # used for equality checks and setting of a space combinator 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 # expected constants 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 ]' # optional 258 attvalue = 'value' # optional 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 # S 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 # *|* or prefix|* 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 # prefix| => element_name 310 # or prefix| => attribute_name if attrib 311 context = new['context'][-1] 312 val, typ = self._tokenvalue(token), self._type(token) 313 if 'attrib' == context and 'prefix' in expected: 314 # [PREFIX|att] 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 # PREFIX|* 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 # pseudo-class or pseudo-element :a ::a :a( ::a( 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 # always pseudo-element ??? 350 typ = 'pseudo-element' 351 append(seq, val, typ) 352 353 if val.endswith(u'('): 354 # function 355 new['context'].append(typ) # "pseudo-" "class" or "element" 356 return expressionstart 357 elif 'negation' == context: 358 return negationend 359 elif 'pseudo-element' == typ: 360 # only one per element, check at ) also! 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 # [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ 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 # context: attrib 386 # PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | INCLUDES | 387 # DASHMATCH 388 context = new['context'][-1] 389 val, typ = self._tokenvalue(token), self._type(token) 390 if 'attrib' == context and 'combinator' in expected: 391 # combinator in attrib 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 # identifier 402 context = new['context'][-1] 403 val, typ = self._tokenvalue(token), self._type(token) 404 405 # context: attrib 406 if 'attrib' == context and 'value' in expected: 407 # attrib: [...=VALUE] 408 append(seq, val, typ) 409 return attend 410 411 # context: pseudo 412 elif context.startswith('pseudo-'): 413 # :func(...) 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 # identifier 425 context = new['context'][-1] 426 val, typ = self._tokenvalue(token), self._type(token) 427 428 # context: attrib 429 if 'attrib' == context and 'attribute' in expected: 430 # attrib: [...|ATT...] 431 append(seq, val, typ) 432 return attcombinator 433 434 elif 'attrib' == context and 'value' in expected: 435 # attrib: [...=VALUE] 436 append(seq, val, typ) 437 return attend 438 439 # context: negation 440 elif 'negation' == context: 441 # negation: (prefix|IDENT) 442 append(seq, val, typ) 443 return negationend 444 445 # context: pseudo 446 elif context.startswith('pseudo-'): 447 # :func(...) 448 append(seq, val, typ) 449 return expression 450 451 elif 'type_selector' in expected or element_name == expected: 452 # element name after ns or complete type_selector 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 # .IDENT 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 # #IDENT 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 # + > ~ S 501 context = new['context'][-1] 502 val, typ = self._tokenvalue(token), self._type(token) 503 504 # context: attrib 505 if u']' == val and 'attrib' == context and ']' in expected: 506 # end of attrib 507 append(seq, val) 508 context = new['context'].pop() # attrib is done 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 # combinator in attrib 517 append(seq, val) 518 return attvalue 519 520 # context: negation 521 elif u')' == val and 'negation' == context and u')' in expected: 522 # not(negation_arg)" 523 append(seq, val) 524 new['context'].pop() # negation is done 525 context = new['context'][-1] 526 return simple_selector_sequence + combinator 527 528 # context: pseudo (at least one expression) 529 elif val in u'+-' and context.startswith('pseudo-'): 530 # :func(+ -)" 531 append(seq, val) 532 return expression 533 534 elif u')' == val and context.startswith('pseudo-') and\ 535 expression == expected: 536 # :func(expression)" 537 append(seq, val) 538 new['context'].pop() # pseudo is done 539 if 'pseudo-element' == context: 540 return combinator 541 else: 542 return simple_selector_sequence + combinator 543 544 # context: ROOT 545 elif u'[' == val and 'attrib' in expected: 546 # start of [attrib] 547 new['context'].append('attrib') 548 append(seq, val) 549 return attname 550 551 elif val in u'+>~' and 'combinator' in expected: 552 # no other combinator except S may be following 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 # not a selectorlist 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 # not( 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 # expected: only|not or mediatype, mediatype, feature, and 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 # pseudo 601 'NUMBER': _expression, 602 'DIMENSION': _expression, 603 # attribute 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 # post condition 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 # set 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
642 - def __repr__(self):
643 return "cssutils.css.%s(selectorText=%r)" % ( 644 self.__class__.__name__, self.selectorText)
645
646 - def __str__(self):
647 return "<cssutils.css.%s object selectorText=%r at 0x%x>" % ( 648 self.__class__.__name__, self.selectorText, id(self))
649