Package cssutils :: Module util
[hide private]
[frames] | no frames]

Source Code for Module cssutils.util

  1  """base classes for css and stylesheets packages 
  2  """ 
  3  __all__ = [] 
  4  __docformat__ = 'restructuredtext' 
  5  __author__ = '$LastChangedBy: cthedot $' 
  6  __date__ = '$LastChangedDate: 2008-02-02 23:52:55 +0100 (Sa, 02 Feb 2008) $' 
  7  __version__ = '$LastChangedRevision: 973 $' 
  8   
  9  from itertools import ifilter 
 10  import re 
 11  import types 
 12  import xml.dom 
 13  import cssutils 
 14  from tokenize2 import Tokenizer 
15 16 -class ListSeq(object):
17 """ 18 (EXPERIMENTAL) 19 A base class used for list classes like css.SelectorList or 20 stylesheets.MediaList 21 22 adds list like behaviour running on inhering class' property ``seq`` 23 24 - item in x => bool 25 - len(x) => integer 26 - get, set and del x[i] 27 - for item in x 28 - append(item) 29 30 some methods must be overwritten in inheriting class 31 """
32 - def __init__(self):
33 self.seq = [] # does not need to use ``Seq`` as simple list only
34
35 - def __contains__(self, item):
36 return item in self.seq
37
38 - def __delitem__(self, index):
39 del self.seq[index]
40
41 - def __getitem__(self, index):
42 return self.seq[index]
43
44 - def __iter__(self):
45 def gen(): 46 for x in self.seq: 47 yield x
48 return gen()
49
50 - def __len__(self):
51 return len(self.seq)
52
53 - def __setitem__(self, index, item):
54 "must be overwritten" 55 raise NotImplementedError
56
57 - def append(self, item):
58 "must be overwritten" 59 raise NotImplementedError
60
61 62 -class Base(object):
63 """ 64 Base class for most CSS and StyleSheets classes 65 66 **Superceded by Base2 which is used for new seq handling class.** 67 See cssutils.util.Base2 68 69 Contains helper methods for inheriting classes helping parsing 70 71 ``_normalize`` is static as used by Preferences. 72 """ 73 __tokenizer2 = Tokenizer() 74 _log = cssutils.log 75 _prods = cssutils.tokenize2.CSSProductions 76 77 # for more on shorthand properties see 78 # http://www.dustindiaz.com/css-shorthand/ 79 # format: shorthand: [(propname, mandatorycheck?)*] 80 _SHORTHANDPROPERTIES = { 81 u'background': [], 82 u'border': [], 83 u'border-left': [], 84 u'border-right': [], 85 u'border-top': [], 86 u'border-bottom': [], 87 u'border-color': [], 88 u'border-style': [], 89 u'border-width': [], 90 u'cue': [], 91 u'font': [], 92 # [('font-weight', True), 93 # ('font-size', True), 94 # ('line-height', False), 95 # ('font-family', True)], 96 u'list-style': [], 97 u'margin': [], 98 u'outline': [], 99 u'padding': [], 100 u'pause': [] 101 } 102 103 # simple escapes, all non unicodes 104 __escapes = re.compile(ur'(\\[^0-9a-fA-F])').sub 105 # all unicode (see cssproductions "unicode") 106 __unicodes = re.compile(ur'\\[0-9a-fA-F]{1,6}[\t|\r|\n|\f|\x20]?').sub 107 108 @staticmethod
109 - def _normalize(x):
110 """ 111 normalizes x, namely: 112 113 - remove any \ before non unicode sequences (0-9a-zA-Z) so for 114 x=="c\olor\" return "color" (unicode escape sequences should have 115 been resolved by the tokenizer already) 116 - lowercase 117 """ 118 if x: 119 def removeescape(matchobj): 120 return matchobj.group(0)[1:]
121 x = Base.__escapes(removeescape, x) 122 return x.lower() 123 else: 124 return x
125
126 - def _checkReadonly(self):
127 "raises xml.dom.NoModificationAllowedErr if rule/... is readonly" 128 if hasattr(self, '_readonly') and self._readonly: 129 raise xml.dom.NoModificationAllowedErr( 130 u'%s is readonly.' % self.__class__) 131 return True 132 return False
133
134 - def _splitNamespacesOff(self, text_namespaces_tuple):
135 """ 136 returns tuple (text, dict-of-namespaces) or if no namespaces are 137 in cssText returns (cssText, {}) 138 139 used in Selector, SelectorList, CSSStyleRule, CSSMediaRule and 140 CSSStyleSheet 141 """ 142 if isinstance(text_namespaces_tuple, tuple): 143 return text_namespaces_tuple[0], _SimpleNamespaces( 144 text_namespaces_tuple[1]) 145 else: 146 return text_namespaces_tuple, _SimpleNamespaces()
147
148 - def _tokenize2(self, textortokens):
149 """ 150 returns tokens of textortokens which may already be tokens in which 151 case simply returns input 152 """ 153 if not textortokens: 154 return None 155 elif isinstance(textortokens, basestring): 156 # needs to be tokenized 157 return self.__tokenizer2.tokenize( 158 textortokens) 159 elif types.GeneratorType == type(textortokens): 160 # already tokenized 161 return textortokens 162 elif isinstance(textortokens, tuple): 163 # a single token (like a comment) 164 return [textortokens] 165 else: 166 # already tokenized but return generator 167 return (x for x in textortokens)
168
169 - def _nexttoken(self, tokenizer, default=None):
170 "returns next token in generator tokenizer or the default value" 171 try: 172 return tokenizer.next() 173 except (StopIteration, AttributeError): 174 return default
175
176 - def _type(self, token):
177 "returns type of Tokenizer token" 178 if token: 179 return token[0] 180 else: 181 return None
182
183 - def _tokenvalue(self, token, normalize=False):
184 "returns value of Tokenizer token" 185 if token and normalize: 186 return Base._normalize(token[1]) 187 elif token: 188 return token[1] 189 else: 190 return None
191
192 - def _tokensupto2(self, 193 tokenizer, 194 starttoken=None, 195 blockstartonly=False, 196 blockendonly=False, 197 mediaendonly=False, 198 semicolon=False, 199 propertynameendonly=False, 200 propertyvalueendonly=False, 201 propertypriorityendonly=False, 202 selectorattendonly=False, 203 funcendonly=False, 204 listseponly=False, # , 205 keepEnd=True, 206 keepEOF=True):
207 """ 208 returns tokens upto end of atrule and end index 209 end is defined by parameters, might be ; } ) or other 210 211 default looks for ending "}" and ";" 212 """ 213 ends = u';}' 214 brace = bracket = parant = 0 # {}, [], () 215 216 if blockstartonly: # { 217 ends = u'{' 218 brace = -1 # set to 0 with first { 219 elif blockendonly: # } 220 ends = u'}' 221 brace = 1 222 elif mediaendonly: # } 223 ends = u'}' 224 brace = 1 # rules } and mediarules } 225 elif semicolon: 226 ends = u';' 227 elif propertynameendonly: # : and ; in case of an error 228 ends = u':;' 229 elif propertyvalueendonly: # ; or !important 230 ends = (u';', u'!') 231 elif propertypriorityendonly: # ; 232 ends = u';' 233 elif selectorattendonly: # ] 234 ends = u']' 235 if starttoken and self._tokenvalue(starttoken) == u'[': 236 bracket = 1 237 elif funcendonly: # ) 238 ends = u')' 239 parant = 1 240 elif listseponly: # , 241 ends = u',' 242 243 resulttokens = [] 244 if starttoken: 245 resulttokens.append(starttoken) 246 if tokenizer: 247 for token in tokenizer: 248 typ, val, line, col = token 249 if 'EOF' == typ: 250 if keepEOF and keepEnd: 251 resulttokens.append(token) 252 break 253 if u'{' == val: 254 brace += 1 255 elif u'}' == val: 256 brace -= 1 257 elif u'[' == val: 258 bracket += 1 259 elif u']' == val: 260 bracket -= 1 261 # function( or single ( 262 elif u'(' == val or \ 263 Base._prods.FUNCTION == typ: 264 parant += 1 265 elif u')' == val: 266 parant -= 1 267 268 if val in ends and (brace == bracket == parant == 0): 269 if keepEnd: 270 resulttokens.append(token) 271 break 272 else: 273 resulttokens.append(token) 274 275 return resulttokens
276
277 - def _valuestr(self, t):
278 """ 279 returns string value of t (t may be a string, a list of token tuples 280 or a single tuple in format (type, value, line, col). 281 Mainly used to get a string value of t for error messages. 282 """ 283 if not t: 284 return u'' 285 elif isinstance(t, basestring): 286 return t 287 else: 288 return u''.join([x[1] for x in t])
289
290 - def __adddefaultproductions(self, productions):
291 """ 292 adds default productions if not already present, used by 293 _parse only 294 295 each production should return the next expected token 296 normaly a name like "uri" or "EOF" 297 some have no expectation like S or COMMENT, so simply return 298 the current value of self.__expected 299 """ 300 def ATKEYWORD(expected, seq, token, tokenizer=None): 301 "TODO: add default impl for unexpected @rule?" 302 return expected
303 304 def COMMENT(expected, seq, token, tokenizer=None): 305 "default implementation for COMMENT token adds CSSCommentRule" 306 seq.append(cssutils.css.CSSComment([token])) 307 return expected 308 309 def S(expected, seq, token, tokenizer=None): 310 "default implementation for S token, does nothing" 311 return expected 312 313 def EOF(expected=None, seq=None, token=None, tokenizer=None): 314 "default implementation for EOF token" 315 return 'EOF' 316 317 p = {'ATKEYWORD': ATKEYWORD, 318 'COMMENT': COMMENT, 319 'S': S, 320 'EOF': EOF # only available if fullsheet 321 } 322 p.update(productions) 323 return p 324
325 - def _parse(self, expected, seq, tokenizer, productions, default=None):
326 """ 327 puts parsed tokens in seq by calling a production with 328 (seq, tokenizer, token) 329 330 expected 331 a name what token or value is expected next, e.g. 'uri' 332 seq 333 to add rules etc to 334 tokenizer 335 call tokenizer.next() to get next token 336 productions 337 callbacks {tokentype: callback} 338 default 339 default callback if tokentype not in productions 340 341 returns (wellformed, expected) which the last prod might have set 342 """ 343 wellformed = True 344 if tokenizer: 345 prods = self.__adddefaultproductions(productions) 346 for token in tokenizer: 347 p = prods.get(token[0], default) 348 if p: 349 expected = p(expected, seq, token, tokenizer) 350 else: 351 wellformed = False 352 self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token) 353 354 return wellformed, expected
355
356 357 -class Seq(object):
358 """ 359 property seq of Base2 inheriting classes, holds a list of Item objects. 360 361 used only by Selector for now 362 363 is normally readonly, only writable during parsing 364 """
365 - def __init__(self, readonly=True):
366 """ 367 only way to write to a Seq is to initialize it with new items 368 each itemtuple has (value, type, line) where line is optional 369 """ 370 self._seq = [] 371 self._readonly = readonly
372
373 - def __delitem__(self, i):
374 del self._seq[i]
375
376 - def __getitem__(self, i):
377 return self._seq[i]
378
379 - def __iter__(self):
380 return iter(self._seq)
381
382 - def __len__(self):
383 return len(self._seq)
384
385 - def append(self, val, typ, line=None):
386 "if not readonly add new Item()" 387 if self._readonly: 388 raise AttributeError('Seq is readonly.') 389 else: 390 self._seq.append(Item(val, typ, line))
391
392 - def replace(self, index=-1, val=None, typ=None, line=None):
393 """ 394 if not readonly replace Item at index with new Item or 395 simply replace value or type 396 """ 397 if self._readonly: 398 raise AttributeError('Seq is readonly.') 399 else: 400 self._seq[index] = Item(val, typ, line)
401
402 - def __repr__(self):
403 "returns a repr same as a list of tuples of (value, type)" 404 return u'cssutils.%s.%s([\n %s])' % (self.__module__, 405 self.__class__.__name__, 406 u',\n '.join([u'(%r, %r)' % (item.type, item.value) 407 for item in self._seq] 408 ))
409 - def __str__(self):
410 return "<cssutils.%s.%s object length=%r at 0x%x>" % ( 411 self.__module__, self.__class__.__name__, len(self), id(self))
412
413 -class Item(object):
414 """ 415 an item in the seq list of classes (successor to tuple items in old seq) 416 417 each item has attributes: 418 419 type 420 a sematic type like "element", "attribute" 421 value 422 the actual value which may be a string, number etc or an instance 423 of e.g. a CSSComment 424 *line* 425 **NOT IMPLEMENTED YET, may contain the line in the source later** 426 """
427 - def __init__(self, value, type, line=None):
428 self.__value = value 429 self.__type = type 430 self.__line = line
431 432 type = property(lambda self: self.__type) 433 value = property(lambda self: self.__value) 434 line = property(lambda self: self.__line) 435
436 - def __repr__(self):
437 return "cssutils.%s.%s(value=%r, type=%r, line=%r)" % ( 438 self.__module__, self.__class__.__name__, 439 self.__value, self.__type, self.__line)
440
441 -class Base2(Base):
442 """ 443 Base class for new seq handling, used by Selector for now only 444 """
445 - def __init__(self):
446 self._seq = Seq()
447
448 - def _setSeq(self, newseq):
449 """ 450 sets newseq and makes it readonly 451 """ 452 newseq._readonly = True 453 self._seq = newseq
454 455 seq = property(fget=lambda self: self._seq, 456 fset=_setSeq, 457 doc="seq for most classes") 458
459 - def _tempSeq(self, readonly=False):
460 "get a writeable Seq() which is added later" 461 return Seq(readonly=readonly)
462
463 464 -class Deprecated(object):
465 """This is a decorator which can be used to mark functions 466 as deprecated. It will result in a warning being emitted 467 when the function is used. 468 469 It accepts a single paramter ``msg`` which is shown with the warning. 470 It should contain information which function or method to use instead. 471 """
472 - def __init__(self, msg):
473 self.msg = msg
474
475 - def __call__(self, func):
476 def newFunc(*args, **kwargs): 477 import warnings 478 warnings.warn("Call to deprecated method %r. %s" % 479 (func.__name__, self.msg), 480 category=DeprecationWarning, 481 stacklevel=2) 482 return func(*args, **kwargs)
483 newFunc.__name__ = func.__name__ 484 newFunc.__doc__ = func.__doc__ 485 newFunc.__dict__.update(func.__dict__) 486 return newFunc
487
488 -class _Namespaces(object):
489 """ 490 A dictionary like wrapper for @namespace rules used in a CSSStyleSheet. 491 Works on effective namespaces, so e.g. if:: 492 493 @namespace p1 "uri"; 494 @namespace p2 "uri"; 495 496 only the second rule is effective and kept. 497 498 namespaces 499 a dictionary {prefix: namespaceURI} containing the effective namespaces 500 only. These are the latest set in the CSSStyleSheet. 501 parentStyleSheet 502 the parent CSSStyleSheet 503 """
504 - def __init__(self, parentStyleSheet, *args):
505 "no initial values are set, only the relevant sheet is" 506 self.parentStyleSheet = parentStyleSheet
507
508 - def __contains__(self, prefix):
509 return prefix in self.namespaces
510
511 - def __delitem__(self, prefix):
512 "deletes CSSNamespaceRule(s) with rule.prefix == prefix" 513 delrule = self.__findrule(prefix) 514 for i, rule in enumerate(ifilter(lambda r: r.type == r.NAMESPACE_RULE, 515 self.parentStyleSheet.cssRules)): 516 if rule == delrule: 517 self.parentStyleSheet.deleteRule(i) 518 return 519 520 raise xml.dom.NamespaceErr('Prefix %r not found.' % prefix)
521
522 - def __getitem__(self, prefix):
523 try: 524 return self.namespaces[prefix] 525 except KeyError, e: 526 raise xml.dom.NamespaceErr('Prefix %r not found.' % prefix)
527
528 - def __iter__(self):
529 return self.namespaces.__iter__()
530
531 - def __len__(self):
532 return len(self.namespaces)
533
534 - def __setitem__(self, prefix, namespaceURI):
535 "replaces prefix or sets new rule, may raise NoModificationAllowedErr" 536 rule = self.__findrule(prefix) 537 if not rule: 538 self.parentStyleSheet.insertRule(cssutils.css.CSSNamespaceRule( 539 prefix=prefix, 540 namespaceURI=namespaceURI), 541 inOrder=True) 542 else: 543 if prefix in self.namespaces: 544 rule.namespaceURI = namespaceURI # raises NoModificationAllowedErr 545 if namespaceURI in self.namespaces.values(): 546 rule.prefix = prefix
547
548 - def __findrule(self, prefix):
549 # returns namespace rule where prefix == key 550 for rule in ifilter(lambda r: r.type == r.NAMESPACE_RULE, 551 reversed(self.parentStyleSheet.cssRules)): 552 if rule.prefix == prefix: 553 return rule
554
555 - def __getNamespaces(self):
556 namespaces = {} 557 for rule in ifilter(lambda r: r.type == r.NAMESPACE_RULE, 558 reversed(self.parentStyleSheet.cssRules)): 559 if rule.namespaceURI not in namespaces.values(): 560 namespaces[rule.prefix] = rule.namespaceURI 561 return namespaces
562 563 namespaces = property(__getNamespaces, 564 doc=u'Holds only effetive @namespace rules in self.parentStyleSheets' 565 '@namespace rules.') 566 567 # def deleteByNamespaceURI(self, namespaceURI): 568 # """ 569 # Deletes CSSNamespaceRule(s) with rule.namespaceURI == namespaceURI 570 # 571 # Raises xml.dom.NamespaceErr if the namespace is in use but all 572 # namespacerules which have been set with the same uri are boiled down to 573 # the effective one 574 # """ 575 # deleted = False 576 # i = 0 577 # rules = self.parentStyleSheet.cssRules 578 # while i < len(rules): 579 # if rules[i].type == rules[i].NAMESPACE_RULE and \ 580 # rules[i].namespaceURI == namespaceURI: 581 # self.parentStyleSheet.deleteRule(i) 582 # deleted = True 583 # else: 584 # i += 1 585 # 586 # if not deleted: 587 # raise KeyError('Namespace URI "%s" not present in style sheet.' % 588 # namespaceURI) 589
590 - def get(self, prefix, default):
591 return self.namespaces.get(prefix, default)
592
593 - def items(self):
594 return self.namespaces.items()
595
596 - def keys(self):
597 return self.namespaces.keys()
598
599 - def values(self):
600 return self.namespaces.values()
601
602 - def prefixForNamespaceURI(self, namespaceURI):
603 """ 604 returns effective prefix for given namespaceURI or raises IndexError 605 if this cannot be found""" 606 for prefix, uri in self.namespaces.items(): 607 if uri == namespaceURI: 608 return prefix 609 raise IndexError(u'NamespaceURI %r not found.' % namespaceURI)
610
611 - def __str__(self):
612 return u"<cssutils.util.%s object parentStyleSheet=%r namespaces=%r "\ 613 u"at 0x%x>" % ( 614 self.__class__.__name__, str(self.parentStyleSheet), 615 self.namespaces, id(self))
616
617 618 -class _SimpleNamespaces(_Namespaces):
619 """ 620 namespaces used in objects like Selector as long as they are not connected 621 to a CSSStyleSheet 622 """
623 - def __init__(self, *args):
624 self.__namespaces = dict(*args)
625
626 - def __setitem__(self, prefix, namespaceURI):
627 self.__namespaces[prefix] = namespaceURI
628 629 namespaces = property(lambda self: self.__namespaces, 630 doc=u'Dict Wrapper for self.sheets @namespace rules.') 631
632 - def __str__(self):
633 return u"<cssutils.util.%s object namespaces=%r at 0x%x>" % ( 634 self.__class__.__name__, self.namespaces, id(self))
635