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