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: doerwalter $' 
 25  __date__ = '$LastChangedDate: 2007-08-02 22:58:23 +0200 (Do, 02 Aug 2007) $' 
 26  __version__ = '0.9.2b3 $LastChangedRevision: 160 $' 
 27   
 28  import xml.dom 
 29   
 30  import cssutils 
 31   
 32   
33 -class Selector(cssutils.util.Base):
34 """ 35 (cssutils) a single selector in a SelectorList of a CSSStyleRule 36 37 Properties 38 ========== 39 selectorText 40 textual representation of this Selector 41 namespaces 42 a set which namespaces have been used in this selector 43 seq 44 sequence of Selector parts including comments 45 46 Format 47 ====== 48 combinator 49 : PLUS S* | GREATER S* | S+ ; 50 selector 51 : simple_selector [ combinator simple_selector ]* ; 52 simple_selector 53 : element_name [ HASH | class | attrib | pseudo ]* 54 | [ HASH | class | attrib | pseudo ]+ ; 55 class 56 : '.' IDENT ; 57 element_name 58 : IDENT | '*' ; 59 attrib 60 : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* 61 [ IDENT | STRING ] S* ]? ']' 62 ; 63 pseudo 64 : ':' [ IDENT | FUNCTION S* IDENT? S* ')' ] ; 65 66 plus some from http://www.w3.org/TR/css3-selectors/ 67 """
68 - def __init__(self, selectorText=None, readonly=False):
69 """ 70 selectorText 71 initial value of this selector 72 readonly 73 default to False 74 """ 75 super(Selector, self).__init__() 76 77 self.seq = [] 78 self.namespaces = set() 79 if selectorText: 80 self.selectorText = selectorText 81 self._readonly = readonly
82 83
84 - def _getSelectorText(self):
85 """ 86 returns serialized format 87 """ 88 return cssutils.ser.do_css_Selector(self)
89
90 - def _setSelectorText(self, selectorText):
91 """ 92 DOMException on setting 93 94 - INVALID_MODIFICATION_ERR: (self) 95 - INVALID_MODIFICATION_ERR: (self) 96 Raised if the specified CSS string value represents a different 97 type than the current one, e.g. a SelectorList. 98 - NO_MODIFICATION_ALLOWED_ERR: (self) 99 Raised if this rule is readonly. 100 - SYNTAX_ERR: (self) 101 Raised if the specified CSS string value has a syntax error 102 and is unparsable. 103 """ 104 def preprocess(tokens): 105 """ 106 remove S at start and end, 107 if around a combinator (ignoring comments) 108 or in att [] 109 """ 110 # remove starting S 111 if tokens[0].type == self._ttypes.S: 112 del tokens[0] 113 COMBINATORS_NO_S = u'+>~' 114 r = [] 115 attlevel = 0 116 i = 0 117 118 i, imax = 0, len(tokens) # imax may be changed later! 119 while i < imax: 120 t = tokens[i] 121 if self._ttypes.S == t.type and attlevel > 0: 122 # remove all WS in atts 1st level 123 i += 1 124 continue 125 126 elif t.value in COMBINATORS_NO_S and attlevel == 0: 127 # remove preceding S from found except token!=Comment 128 before = len(r) 129 while before >= 0: 130 before -= 1 131 if r[before].type == self._ttypes.S: 132 del r[before] 133 elif r[before].type != self._ttypes.COMMENT: 134 break 135 # remove following S from tokens except token!=Comment 136 after = i+1 137 while after < imax: 138 if tokens[after].type == self._ttypes.S: 139 del tokens[after] 140 after -= 1 # BECAUSE DELETED ONE! 141 imax = len(tokens) # new imax! 142 elif tokens[after].type != self._ttypes.COMMENT: 143 break 144 after += 1 145 elif t.value == u'[': 146 attlevel += 1 147 elif t.value == u']': 148 attlevel -= 1 149 r.append(t) 150 i += 1 151 152 # remove ending S 153 if r and r[-1].type == self._ttypes.S: 154 del r[-1] 155 return r
156 157 def addprefix(seq): 158 """ 159 checks if last item in seq was a namespace prefix 160 and if yes adds it to newnamespaces 161 """ 162 try: 163 # may also be comment... 164 if isinstance(seq[-1], dict) and \ 165 seq[-1]['type'] in ('type', 'attributename'): 166 newnamespaces.add(seq[-1]['value']) 167 seq[-1]['type'] = 'prefix' 168 else: 169 newnamespaces.add('') 170 except IndexError: 171 pass
172 173 def getAttrib(atttokens): 174 """ 175 atttokens 176 a sequence of tokens without any S which are preprocessed 177 178 returns seq of selector attribute 179 180 Format 181 ====== 182 attrib 183 : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* 184 [ IDENT | STRING ] S* ]? ']' ; 185 186 CSS3 additionally: 187 ^= PREFIXMATCH 188 $= SUFFIXMATCH 189 *= SUBSTRINGMATCH 190 """ 191 attseq = [] 192 expected = "[" # att, comb or ], value, pipe 193 i, imax = 0, len(atttokens) 194 while i < imax: 195 t = atttokens[i] 196 if self._ttypes.COMMENT == t.type: # just add 197 attseq.append(cssutils.css.CSSComment(t)) 198 199 # [ start 200 elif expected == '[' and t.value == u'[': 201 attseq.append({'type': 'attribute selector', 'value': t.value}) 202 expected = 'att' 203 elif expected == '[': 204 self._log.error(u'Selector: No Attrib start "[" found.', t) 205 return 206 207 # ] end 208 elif expected in ('comb or ]', ']') and t.value == u']': 209 attseq.append({'type': 'attribute selector end', 'value': t.value}) 210 assert i == len(atttokens) - 1 # should really be end 211 break 212 elif expected == 'pipe' and t.value == u']': 213 self._log.error(u'Selector: No attribute name for namespace start found.', t) 214 return 215 216 # * (must be followed by |) 217 elif expected == 'att' and t.type == self._ttypes.UNIVERSAL: 218 attseq.append({'type': 'type', 'value': t.value}) 219 expected = 'pipe' 220 221 # | namespace pipe 222 elif expected in ('pipe', 'att', 'comb or ]') and \ 223 t.value == u'|': 224 addprefix(attseq) 225 attseq.append({'type': 'type', 'value': t.value}) 226 expected = 'att' 227 228 elif expected == 'att' and t.type == self._ttypes.IDENT: 229 attseq.append({'type': 'attributename', 'value': t.value}) 230 expected = 'comb or ]' 231 elif expected == 'att': 232 self._log.error(u'Selector: No Attrib found.', t) 233 return 234 235 # CSS3 selectors added 236 elif expected == 'comb or ]' and t.value in ( 237 u'=', u'~=', u'|=', u'^=', u'$=', u'*='): 238 attseq.append({'type': 'combinator', 'value': t.value}) 239 expected = 'val' 240 elif expected == 'comb or ]': 241 self._log.error(u'Selector: No Attrib combinator found.', t) 242 return 243 244 elif expected == 'val' and t.type in ( 245 self._ttypes.IDENT, self._ttypes.STRING): 246 attseq.append({'type': 'attributevalue', 'value': t.value}) 247 expected = ']' 248 elif expected == 'val': 249 self._log.error(u'Selector: No Attrib value found.', t) 250 return 251 252 else: 253 self._log.error(u'Selector: Unknown Attrib syntax.', t) 254 return 255 256 i += 1 257 258 else: # in case of ] not found 259 self._log.error(u'Selector: Incomplete Attrib.', t) 260 return 261 262 return attseq 263 264 def getFunc(functokens): 265 """ 266 functokens 267 a sequence of tokens 268 269 returns seq of selector functional_pseudo 270 271 Format 272 ====== 273 functional_pseudo 274 : FUNCTION S* expression ')' 275 ; 276 """ 277 funcseq = [{'type': 'function', 'value': functokens[0].value}] 278 expected = ")" # expr or ) 279 i, imax = 1, len(functokens) 280 while i < imax: 281 t = functokens[i] 282 283 if self._ttypes.COMMENT == t.type: # just add 284 funcseq.append(cssutils.css.CSSComment(t)) 285 286 elif t.value == u')': 287 funcseq.append( 288 {'type': 'function end', 'value': t.value}) 289 expected = None 290 assert i == len(functokens) - 1 # should really be end 291 break 292 293 else: 294 funcseq.append({'type': 'functionvalue', 'value': t.value}) 295 296 i += 1 297 298 if expected: 299 self._log.error( 300 u'Selector: Incomplete functional pseudo "%s".' % 301 self._valuestr(functokens)) 302 return 303 304 return funcseq 305 306 self._checkReadonly() 307 308 tokens = self._tokenize(selectorText) 309 if tokens: 310 COMBINATORS = u' >+~' 311 tokens = preprocess(tokens) 312 313 newseq = [] 314 newnamespaces = set() 315 found1 = False 316 # simple_selector | combinator: u' >+~' | 317 # pseudoclass after : | pseudoelement after :: 318 expected = 'simple_selector' 319 i, imax = 0, len(tokens) 320 while i < imax: 321 t = tokens[i] 322 ##print t.value, t.type, expected 323 # , invalid SelectorList 324 if self._ttypes.COMMA == t.type: 325 self._log.error(u'Selector: Possible SelectorList found.', t, 326 error=xml.dom.InvalidModificationErr) 327 return 328 329 # /* comments */ 330 elif self._ttypes.COMMENT == t.type and expected not in ( 331 'classname', 'pseudoclass', 'pseudoelement'): 332 # just add 333 newseq.append(cssutils.css.CSSComment(t)) 334 335 # . class 336 elif expected.startswith('simple_selector') and \ 337 t.type == self._ttypes.CLASS: 338 newseq.append({'value': t.value, 'type': 'class'}) 339 found1 = True 340 expected = 'classname' 341 elif expected == 'classname' and t.type == self._ttypes.IDENT: 342 newseq.append({'value': t.value, 'type': 'classname'}) 343 expected = 'simple_selector or combinator' 344 345 # pseudoclass ":" or pseudoelement "::" 346 elif expected.startswith('simple_selector') and \ 347 t.value == u':' or t.value == u'::': 348 expected = { 349 ':': 'pseudoclass', '::': 'pseudoelement'}[t.value] 350 newseq.append({'value': t.value, 'type': expected}) 351 found1 = True 352 elif expected.startswith('pseudo') and \ 353 t.type == self._ttypes.IDENT: 354 newseq.append({'value': t.value, 'type': 'pseudoname'}) 355 expected = 'simple_selector or combinator' 356 elif expected == 'pseudoclass' and \ 357 t.type == self._ttypes.FUNCTION: 358 # pseudo "function" like lang(...) 359 functokens, endi = self._tokensupto( 360 tokens[i:], funcendonly=True) 361 i += endi 362 funcseq = getFunc(functokens) 363 if not funcseq: 364 self._log.error( 365 u'Selector: Invalid functional pseudo.', t) 366 return 367 newseq.extend(funcseq) 368 expected = 'simple_selector or combinator' 369 elif expected == 'pseudoclass': 370 self._log.error(u'Selector: Pseudoclass name expected.', t) 371 return 372 elif expected == 'pseudoelement': 373 self._log.error(u'Selector: Pseudoelement name expected.', t) 374 return 375 376 # #hash 377 elif expected.startswith('simple_selector') and \ 378 t.type == self._ttypes.HASH: 379 newseq.append({'value': t.value, 'type': 'id'}) 380 found1 = True 381 expected = 'simple_selector or combinator' 382 383 # | namespace pipe 384 elif expected.startswith('simple_selector') and \ 385 t.value == u'|': 386 addprefix(newseq) 387 newseq.append({'type': 'pipe', 'value': t.value}) 388 found1 = True 389 expected = 'namespaced element' 390 391 # element, * 392 elif (expected.startswith('simple_selector') or 393 expected == 'namespaced element') and \ 394 t.type in (self._ttypes.IDENT, self._ttypes.UNIVERSAL): 395 newseq.append({'value': t.value, 'type': 'type'}) 396 found1 = True 397 expected = 'simple_selector or combinator' 398 # element, * 399 elif expected == 'namespaced element' and \ 400 t.type not in (self._ttypes.IDENT, self._ttypes.UNIVERSAL): 401 self._log.error(u'Selector: Namespaced element expected.', t) 402 return 403 404 # attribute selector 405 elif expected.startswith('simple_selector') and t.value == u'[': 406 atttokens, endi = self._tokensupto( 407 tokens[i:], selectorattendonly=True) 408 i += endi 409 attseq = getAttrib(atttokens) 410 if not attseq: 411 self._log.warn( 412 u'Selector: Empty attribute selector.', t) 413 return 414 newseq.extend(attseq) 415 found1 = True 416 expected = 'simple_selector or combinator' 417 418 # CSS3 combinator ~ 419 elif expected == 'simple_selector or combinator' and \ 420 t.value in COMBINATORS or t.type == self._ttypes.S: 421 if t.type == self._ttypes.S: 422 newseq.append({'value': t.normalvalue, 'type': 'combinator'}) # single u' ' 423 else: 424 newseq.append({'value': t.value, 'type': 'combinator'}) 425 expected = 'simple_selector' # or [ 426 elif expected == 'simple_selector or combinator': 427 self._log.error(u'Selector: No combinator found.', t) 428 return 429 430 else: 431 self._log.error(u'Selector: Expected simple selector.', t) 432 return 433 434 i += 1 435 436 if expected == 'classname': 437 self._log.error(u'Selector: Class name expected: %s.' % 438 selectorText) 439 return 440 elif expected == 'namespaced element': 441 self._log.error(u'Selector: No element found for namespace start: %s.' % 442 selectorText) 443 return 444 elif expected == 'pseudoclass': 445 self._log.error(u'Selector: Pseudoclass name expected: %s.' % 446 selectorText) 447 return 448 elif expected == 'pseudoelement': 449 self._log.error(u'Selector: Pseudoelement name expected: %s.' % 450 selectorText) 451 return 452 elif newseq and (( 453 isinstance(newseq[-1], basestring) and \ 454 newseq[-1] in COMBINATORS 455 ) or ( # may be a dict 456 isinstance(newseq[-1], dict) and 457 isinstance(newseq[-1]['value'], basestring) and \ 458 newseq[-1]['value'] in COMBINATORS)): 459 self._log.error(u'Selector: Cannot end with combinator: %s.' % 460 selectorText) 461 return 462 463 if found1: 464 self.seq = newseq 465 self.namespaces = newnamespaces 466 ## import pprint;pprint.pprint(newseq) 467 ## print self.namespaces 468 else: 469 self._log.error( 470 u'Selector: No selector found: %s.' % selectorText) 471 else: 472 self._log.error(u'Selector: Empty selector.') 473 474 selectorText = property(_getSelectorText, _setSelectorText, 475 doc="(DOM) The parsable textual representation of the selector.") 476 477 478 if __name__ == '__main__': 479 s = Selector() 480 s.selectorText = u'n1|a[n2|x]' #u'::a:b()' 481 print s.selectorText 482