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-13 00:06:08 +0100 (So, 13 Jan 2008) $' 
  7  __version__ = '$LastChangedRevision: 838 $' 
  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 elif mediaendonly: # } 204 ends = u'}' 205 brace = 1 # rules } and mediarules } 206 elif semicolon: 207 ends = u';' 208 elif propertynameendonly: # : and ; in case of an error 209 ends = u':;' 210 elif propertyvalueendonly: # ; or !important 211 ends = (u';', u'!') 212 elif propertypriorityendonly: # ; 213 ends = u';' 214 elif selectorattendonly: # ] 215 ends = u']' 216 if starttoken and self._tokenvalue(starttoken) == u'[': 217 bracket = 1 218 elif funcendonly: # ) 219 ends = u')' 220 parant = 1 221 elif listseponly: # , 222 ends = u',' 223 224 resulttokens = [] 225 if starttoken: 226 resulttokens.append(starttoken) 227 if tokenizer: 228 for token in tokenizer: 229 typ, val, line, col = token 230 if 'EOF' == typ: 231 if keepEOF and keepEnd: 232 resulttokens.append(token) 233 break 234 if u'{' == val: 235 brace += 1 236 elif u'}' == val: 237 brace -= 1 238 elif u'[' == val: 239 bracket += 1 240 elif u']' == val: 241 bracket -= 1 242 # function( or single ( 243 elif u'(' == val or \ 244 Base._prods.FUNCTION == typ: 245 parant += 1 246 elif u')' == val: 247 parant -= 1 248 249 if val in ends and (brace == bracket == parant == 0): 250 if keepEnd: 251 resulttokens.append(token) 252 break 253 else: 254 resulttokens.append(token) 255 256 return resulttokens
257
258 - def _valuestr(self, t):
259 """ 260 returns string value of t (t may be a string, a list of token tuples 261 or a single tuple in format (type, value, line, col). 262 Mainly used to get a string value of t for error messages. 263 """ 264 if not t: 265 return u'' 266 elif isinstance(t, basestring): 267 return t 268 else: 269 return u''.join([x[1] for x in t])
270
271 - def __adddefaultproductions(self, productions):
272 """ 273 adds default productions if not already present, used by 274 _parse only 275 276 each production should return the next expected token 277 normaly a name like "uri" or "EOF" 278 some have no expectation like S or COMMENT, so simply return 279 the current value of self.__expected 280 """ 281 def ATKEYWORD(expected, seq, token, tokenizer=None): 282 "TODO: add default impl for unexpected @rule?" 283 return expected
284 285 def COMMENT(expected, seq, token, tokenizer=None): 286 "default implementation for COMMENT token adds CSSCommentRule" 287 seq.append(cssutils.css.CSSComment([token])) 288 return expected 289 290 def S(expected, seq, token, tokenizer=None): 291 "default implementation for S token, does nothing" 292 return expected 293 294 def EOF(expected=None, seq=None, token=None, tokenizer=None): 295 "default implementation for EOF token" 296 return 'EOF' 297 298 p = {'ATKEYWORD': ATKEYWORD, 299 'COMMENT': COMMENT, 300 'S': S, 301 'EOF': EOF # only available if fullsheet 302 } 303 p.update(productions) 304 return p 305
306 - def _parse(self, expected, seq, tokenizer, productions, default=None):
307 """ 308 puts parsed tokens in seq by calling a production with 309 (seq, tokenizer, token) 310 311 expected 312 a name what token or value is expected next, e.g. 'uri' 313 seq 314 to add rules etc to 315 tokenizer 316 call tokenizer.next() to get next token 317 productions 318 callbacks {tokentype: callback} 319 default 320 default callback if tokentype not in productions 321 322 returns (wellformed, expected) which the last prod might have set 323 """ 324 wellformed = True 325 if tokenizer: 326 prods = self.__adddefaultproductions(productions) 327 for token in tokenizer: 328 p = prods.get(token[0], default) 329 if p: 330 expected = p(expected, seq, token, tokenizer) 331 else: 332 wellformed = False 333 self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token) 334 335 return wellformed, expected
336 337
338 -class Seq(object):
339 """ 340 property seq of Base2 inheriting classes, holds a list of Item objects. 341 342 used only by Selector for now 343 344 is normally readonly, only writable during parsing 345 """
346 - def __init__(self, readonly=True):
347 """ 348 only way to write to a Seq is to initialize it with new items 349 each itemtuple has (value, type, line) where line is optional 350 """ 351 self.__seq = [] 352 self._readonly = readonly
353
354 - def __delitem__(self, i):
355 del self.__seq[i]
356
357 - def __getitem__(self, i):
358 return self.__seq[i]
359
360 - def __iter__(self):
361 return iter(self.__seq)
362
363 - def __len__(self):
364 return len(self.__seq)
365
366 - def append(self, val, typ, line=None):
367 "if not readonly add new Item()" 368 if self._readonly: 369 raise AttributeError('Seq is readonly.') 370 else: 371 self.__seq.append(Item(val, typ, line))
372
373 - def replace(self, index=-1, val=None, typ=None, line=None):
374 """ 375 if not readonly replace Item at index with new Item or 376 simply replace value or type 377 """ 378 if self._readonly: 379 raise AttributeError('Seq is readonly.') 380 else: 381 self.__seq[index] = Item(val, typ, line)
382
383 - def __repr__(self):
384 "returns a repr same as a list of tuples of (value, type)" 385 return u'cssutils.%s.%s([\n %s])' % (self.__module__, 386 self.__class__.__name__, 387 u',\n '.join([u'(%r, %r)' % (item.type, item.value) 388 for item in self.__seq] 389 ))
390 - def __str__(self):
391 return "<cssutils.%s.%s object length=%r at 0x%x>" % ( 392 self.__module__, self.__class__.__name__, len(self), id(self))
393
394 -class Item(object):
395 """ 396 an item in the seq list of classes (successor to tuple items in old seq) 397 398 each item has attributes: 399 400 type 401 a sematic type like "element", "attribute" 402 value 403 the actual value which may be a string, number etc or an instance 404 of e.g. a CSSComment 405 *line* 406 **NOT IMPLEMENTED YET, may contain the line in the source later** 407 """
408 - def __init__(self, val, typ, line=None):
409 self.__value = val 410 self.__type = typ 411 self.__line = line
412 413 type = property(lambda self: self.__type) 414 value = property(lambda self: self.__value) 415 line = property(lambda self: self.__line)
416 417
418 -class Base2(Base):
419 """ 420 Base class for new seq handling, used by Selector for now only 421 """
422 - def __init__(self):
423 self._seq = Seq()
424
425 - def _setSeq(self, newseq):
426 """ 427 sets newseq and makes it readonly 428 """ 429 newseq._readonly = True 430 self._seq = newseq
431 432 seq = property(fget=lambda self: self._seq, 433 fset=_setSeq, 434 doc="seq for most classes") 435
436 - def _tempSeq(self, readonly=False):
437 "get a writeable Seq() which is added later" 438 return Seq(readonly=readonly)
439 440
441 -class Deprecated(object):
442 """This is a decorator which can be used to mark functions 443 as deprecated. It will result in a warning being emitted 444 when the function is used. 445 446 It accepts a single paramter ``msg`` which is shown with the warning. 447 It should contain information which function or method to use instead. 448 """
449 - def __init__(self, msg):
450 self.msg = msg
451
452 - def __call__(self, func):
453 def newFunc(*args, **kwargs): 454 import warnings 455 warnings.warn("Call to deprecated method %r. %s" % 456 (func.__name__, self.msg), 457 category=DeprecationWarning, 458 stacklevel=2) 459 return func(*args, **kwargs)
460 newFunc.__name__ = func.__name__ 461 newFunc.__doc__ = func.__doc__ 462 newFunc.__dict__.update(func.__dict__) 463 return newFunc
464