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-01-15 21:59:49 +0100 (Di, 15 Jan 2008) $' 
  7  __version__ = '$LastChangedRevision: 863 $' 
  8   
  9  import re 
 10  import types 
 11  import xml.dom 
 12  import cssutils 
 13  from tokenize2 import Tokenizer 
 14   
15 -class ListSeq(object):
16 """ 17 (EXPERIMENTAL) 18 A base class used for list classes like css.SelectorList or 19 stylesheets.MediaList 20 21 adds list like behaviour running on inhering class' property ``seq`` 22 23 - item in x => bool 24 - len(x) => integer 25 - get, set and del x[i] 26 - for item in x 27 - append(item) 28 29 some methods must be overwritten in inheriting class 30 """
31 - def __init__(self):
32 self.seq = [] # does not need to use ``Seq`` as simple list only
33
34 - def __contains__(self, item):
35 return item in self.seq
36
37 - def __delitem__(self, index):
38 del self.seq[index]
39
40 - def __getitem__(self, index):
41 return self.seq[index]
42
43 - def __iter__(self):
44 return iter(self.seq)
45
46 - def __len__(self):
47 return len(self.seq)
48
49 - def __setitem__(self, index, item):
50 "must be overwritten" 51 raise NotImplementedError
52
53 - def append(self, item):
54 "must be overwritten" 55 raise NotImplementedError
56 57
58 -class Base(object):
59 """ 60 Base class for most CSS and StyleSheets classes 61 62 **Superceded by Base2 which is used for new seq handling class.** 63 See cssutils.util.Base2 64 65 Contains helper methods for inheriting classes helping parsing 66 67 ``_normalize`` is static as used by Preferences. 68 """ 69 __tokenizer2 = Tokenizer() 70 _log = cssutils.log 71 _prods = cssutils.tokenize2.CSSProductions 72 73 # for more on shorthand properties see 74 # http://www.dustindiaz.com/css-shorthand/ 75 # format: shorthand: [(propname, mandatorycheck?)*] 76 _SHORTHANDPROPERTIES = { 77 u'background': [], 78 u'border': [], 79 u'border-left': [], 80 u'border-right': [], 81 u'border-top': [], 82 u'border-bottom': [], 83 u'border-color': [], 84 u'border-style': [], 85 u'border-width': [], 86 u'cue': [], 87 u'font': [], 88 # [('font-weight', True), 89 # ('font-size', True), 90 # ('line-height', False), 91 # ('font-family', True)], 92 u'list-style': [], 93 u'margin': [], 94 u'outline': [], 95 u'padding': [], 96 u'pause': [] 97 } 98 99 # simple escapes, all non unicodes 100 __escapes = re.compile(ur'(\\[^0-9a-fA-F])').sub 101 # all unicode (see cssproductions "unicode") 102 __unicodes = re.compile(ur'\\[0-9a-fA-F]{1,6}[\t|\r|\n|\f|\x20]?').sub 103 104 @staticmethod
105 - def _normalize(x):
106 """ 107 normalizes x, namely: 108 109 - remove any \ before non unicode sequences (0-9a-zA-Z) so for 110 x=="c\olor\" return "color" (unicode escape sequences should have 111 been resolved by the tokenizer already) 112 - lowercase 113 """ 114 if x: 115 def removeescape(matchobj): 116 return matchobj.group(0)[1:]
117 x = Base.__escapes(removeescape, x) 118 return x.lower() 119 else: 120 return x
121
122 - def _checkReadonly(self):
123 "raises xml.dom.NoModificationAllowedErr if rule/... is readonly" 124 if hasattr(self, '_readonly') and self._readonly: 125 raise xml.dom.NoModificationAllowedErr( 126 u'%s is readonly.' % self.__class__) 127 return True 128 return False
129
130 - def _tokenize2(self, textortokens):
131 """ 132 returns tokens of textortokens which may already be tokens in which 133 case simply returns input 134 """ 135 if not textortokens: 136 return None 137 elif isinstance(textortokens, basestring): 138 # needs to be tokenized 139 return self.__tokenizer2.tokenize( 140 textortokens) 141 elif types.GeneratorType == type(textortokens): 142 # already tokenized 143 return textortokens 144 elif isinstance(textortokens, tuple): 145 # a single token (like a comment) 146 return [textortokens] 147 else: 148 # already tokenized but return generator 149 return (x for x in textortokens)
150
151 - def _nexttoken(self, tokenizer, default=None):
152 "returns next token in generator tokenizer or the default value" 153 try: 154 return tokenizer.next() 155 except (StopIteration, AttributeError): 156 return default
157
158 - def _type(self, token):
159 "returns type of Tokenizer token" 160 if token: 161 return token[0] 162 else: 163 return None
164
165 - def _tokenvalue(self, token, normalize=False):
166 "returns value of Tokenizer token" 167 if token and normalize: 168 return Base._normalize(token[1]) 169 elif token: 170 return token[1] 171 else: 172 return None
173
174 - def _tokensupto2(self, 175 tokenizer, 176 starttoken=None, 177 blockstartonly=False, 178 blockendonly=False, 179 mediaendonly=False, 180 semicolon=False, 181 propertynameendonly=False, 182 propertyvalueendonly=False, 183 propertypriorityendonly=False, 184 selectorattendonly=False, 185 funcendonly=False, 186 listseponly=False, # , 187 keepEnd=True, 188 keepEOF=True):
189 """ 190 returns tokens upto end of atrule and end index 191 end is defined by parameters, might be ; } ) or other 192 193 default looks for ending "}" and ";" 194 """ 195 ends = u';}' 196 brace = bracket = parant = 0 # {}, [], () 197 198 if blockstartonly: # { 199 ends = u'{' 200 brace = -1 # set to 0 with first { 201 elif blockendonly: # } 202 ends = u'}' 203 brace = 1 204 elif mediaendonly: # } 205 ends = u'}' 206 brace = 1 # rules } and mediarules } 207 elif semicolon: 208 ends = u';' 209 elif propertynameendonly: # : and ; in case of an error 210 ends = u':;' 211 elif propertyvalueendonly: # ; or !important 212 ends = (u';', u'!') 213 elif propertypriorityendonly: # ; 214 ends = u';' 215 elif selectorattendonly: # ] 216 ends = u']' 217 if starttoken and self._tokenvalue(starttoken) == u'[': 218 bracket = 1 219 elif funcendonly: # ) 220 ends = u')' 221 parant = 1 222 elif listseponly: # , 223 ends = u',' 224 225 resulttokens = [] 226 if starttoken: 227 resulttokens.append(starttoken) 228 if tokenizer: 229 for token in tokenizer: 230 typ, val, line, col = token 231 if 'EOF' == typ: 232 if keepEOF and keepEnd: 233 resulttokens.append(token) 234 break 235 if u'{' == val: 236 brace += 1 237 elif u'}' == val: 238 brace -= 1 239 elif u'[' == val: 240 bracket += 1 241 elif u']' == val: 242 bracket -= 1 243 # function( or single ( 244 elif u'(' == val or \ 245 Base._prods.FUNCTION == typ: 246 parant += 1 247 elif u')' == val: 248 parant -= 1 249 250 if val in ends and (brace == bracket == parant == 0): 251 if keepEnd: 252 resulttokens.append(token) 253 break 254 else: 255 resulttokens.append(token) 256 257 return resulttokens
258
259 - def _valuestr(self, t):
260 """ 261 returns string value of t (t may be a string, a list of token tuples 262 or a single tuple in format (type, value, line, col). 263 Mainly used to get a string value of t for error messages. 264 """ 265 if not t: 266 return u'' 267 elif isinstance(t, basestring): 268 return t 269 else: 270 return u''.join([x[1] for x in t])
271
272 - def __adddefaultproductions(self, productions):
273 """ 274 adds default productions if not already present, used by 275 _parse only 276 277 each production should return the next expected token 278 normaly a name like "uri" or "EOF" 279 some have no expectation like S or COMMENT, so simply return 280 the current value of self.__expected 281 """ 282 def ATKEYWORD(expected, seq, token, tokenizer=None): 283 "TODO: add default impl for unexpected @rule?" 284 return expected
285 286 def COMMENT(expected, seq, token, tokenizer=None): 287 "default implementation for COMMENT token adds CSSCommentRule" 288 seq.append(cssutils.css.CSSComment([token])) 289 return expected 290 291 def S(expected, seq, token, tokenizer=None): 292 "default implementation for S token, does nothing" 293 return expected 294 295 def EOF(expected=None, seq=None, token=None, tokenizer=None): 296 "default implementation for EOF token" 297 return 'EOF' 298 299 p = {'ATKEYWORD': ATKEYWORD, 300 'COMMENT': COMMENT, 301 'S': S, 302 'EOF': EOF # only available if fullsheet 303 } 304 p.update(productions) 305 return p 306
307 - def _parse(self, expected, seq, tokenizer, productions, default=None):
308 """ 309 puts parsed tokens in seq by calling a production with 310 (seq, tokenizer, token) 311 312 expected 313 a name what token or value is expected next, e.g. 'uri' 314 seq 315 to add rules etc to 316 tokenizer 317 call tokenizer.next() to get next token 318 productions 319 callbacks {tokentype: callback} 320 default 321 default callback if tokentype not in productions 322 323 returns (wellformed, expected) which the last prod might have set 324 """ 325 wellformed = True 326 if tokenizer: 327 prods = self.__adddefaultproductions(productions) 328 for token in tokenizer: 329 p = prods.get(token[0], default) 330 if p: 331 expected = p(expected, seq, token, tokenizer) 332 else: 333 wellformed = False 334 self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token) 335 336 return wellformed, expected
337 338
339 -class Seq(object):
340 """ 341 property seq of Base2 inheriting classes, holds a list of Item objects. 342 343 used only by Selector for now 344 345 is normally readonly, only writable during parsing 346 """
347 - def __init__(self, readonly=True):
348 """ 349 only way to write to a Seq is to initialize it with new items 350 each itemtuple has (value, type, line) where line is optional 351 """ 352 self.__seq = [] 353 self._readonly = readonly
354
355 - def __delitem__(self, i):
356 del self.__seq[i]
357
358 - def __getitem__(self, i):
359 return self.__seq[i]
360
361 - def __iter__(self):
362 return iter(self.__seq)
363
364 - def __len__(self):
365 return len(self.__seq)
366
367 - def append(self, val, typ, line=None):
368 "if not readonly add new Item()" 369 if self._readonly: 370 raise AttributeError('Seq is readonly.') 371 else: 372 self.__seq.append(Item(val, typ, line))
373
374 - def replace(self, index=-1, val=None, typ=None, line=None):
375 """ 376 if not readonly replace Item at index with new Item or 377 simply replace value or type 378 """ 379 if self._readonly: 380 raise AttributeError('Seq is readonly.') 381 else: 382 self.__seq[index] = Item(val, typ, line)
383
384 - def __repr__(self):
385 "returns a repr same as a list of tuples of (value, type)" 386 return u'cssutils.%s.%s([\n %s])' % (self.__module__, 387 self.__class__.__name__, 388 u',\n '.join([u'(%r, %r)' % (item.type, item.value) 389 for item in self.__seq] 390 ))
391 - def __str__(self):
392 return "<cssutils.%s.%s object length=%r at 0x%x>" % ( 393 self.__module__, self.__class__.__name__, len(self), id(self))
394
395 -class Item(object):
396 """ 397 an item in the seq list of classes (successor to tuple items in old seq) 398 399 each item has attributes: 400 401 type 402 a sematic type like "element", "attribute" 403 value 404 the actual value which may be a string, number etc or an instance 405 of e.g. a CSSComment 406 *line* 407 **NOT IMPLEMENTED YET, may contain the line in the source later** 408 """
409 - def __init__(self, val, typ, line=None):
410 self.__value = val 411 self.__type = typ 412 self.__line = line
413 414 type = property(lambda self: self.__type) 415 value = property(lambda self: self.__value) 416 line = property(lambda self: self.__line)
417 418
419 -class Base2(Base):
420 """ 421 Base class for new seq handling, used by Selector for now only 422 """
423 - def __init__(self):
424 self._seq = Seq()
425
426 - def _setSeq(self, newseq):
427 """ 428 sets newseq and makes it readonly 429 """ 430 newseq._readonly = True 431 self._seq = newseq
432 433 seq = property(fget=lambda self: self._seq, 434 fset=_setSeq, 435 doc="seq for most classes") 436
437 - def _tempSeq(self, readonly=False):
438 "get a writeable Seq() which is added later" 439 return Seq(readonly=readonly)
440 441
442 -class Deprecated(object):
443 """This is a decorator which can be used to mark functions 444 as deprecated. It will result in a warning being emitted 445 when the function is used. 446 447 It accepts a single paramter ``msg`` which is shown with the warning. 448 It should contain information which function or method to use instead. 449 """
450 - def __init__(self, msg):
451 self.msg = msg
452
453 - def __call__(self, func):
454 def newFunc(*args, **kwargs): 455 import warnings 456 warnings.warn("Call to deprecated method %r. %s" % 457 (func.__name__, self.msg), 458 category=DeprecationWarning, 459 stacklevel=2) 460 return func(*args, **kwargs)
461 newFunc.__name__ = func.__name__ 462 newFunc.__doc__ = func.__doc__ 463 newFunc.__dict__.update(func.__dict__) 464 return newFunc
465