Package cssutils :: Package css :: Module cssstylesheet
[hide private]
[frames] | no frames]

Source Code for Module cssutils.css.cssstylesheet

  1  """ 
  2  CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet. 
  3   
  4  Partly also: 
  5      - http://dev.w3.org/csswg/cssom/#the-cssstylesheet 
  6      - http://www.w3.org/TR/2006/WD-css3-namespace-20060828/ 
  7   
  8  TODO: 
  9      - ownerRule and ownerNode 
 10  """ 
 11  __all__ = ['CSSStyleSheet'] 
 12  __docformat__ = 'restructuredtext' 
 13  __author__ = '$LastChangedBy: cthedot $' 
 14  __date__ = '$LastChangedDate: 2007-10-19 00:31:34 +0200 (Fr, 19 Okt 2007) $' 
 15  __version__ = '$LastChangedRevision: 518 $' 
 16   
 17  import xml.dom 
 18  import cssutils.stylesheets 
 19   
20 -class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
21 """ 22 The CSSStyleSheet interface represents a CSS style sheet. 23 24 Properties 25 ========== 26 CSSOM 27 ----- 28 cssRules 29 of type CSSRuleList, (DOM readonly) 30 ownerRule 31 of type CSSRule, readonly (NOT IMPLEMENTED YET) 32 33 Inherits properties from stylesheet.StyleSheet 34 35 cssutils 36 -------- 37 cssText: string 38 a textual representation of the stylesheet 39 prefixes: set 40 A set of declared prefixes via @namespace rules. Each 41 CSSStyleRule is checked if it uses additional prefixes which are 42 not declared. If they do they are "invalidated". 43 44 Format 45 ====== 46 stylesheet 47 : [ CHARSET_SYM S* STRING S* ';' ]? 48 [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* 49 [ namespace [S|CDO|CDC]* ]* # according to @namespace WD 50 [ [ ruleset | media | page ] [S|CDO|CDC]* ]* 51 """ 52 type = 'text/css' 53
54 - def __init__(self, href=None, media=None, 55 title=u'', disabled=None, 56 ownerNode=None, parentStyleSheet=None, 57 readonly=False):
58 59 super(CSSStyleSheet, self).__init__( 60 self.type, href, media, title, disabled, 61 ownerNode, parentStyleSheet) 62 63 self.cssRules = cssutils.css.CSSRuleList() 64 self.prefixes = set() 65 self._readonly = readonly
66
67 - def _getCssText(self):
68 return cssutils.ser.do_CSSStyleSheet(self)
69
70 - def _setCssText(self, cssText):
71 """ 72 (cssutils) 73 Parses ``cssText`` and overwrites the whole stylesheet. 74 75 DOMException on setting 76 77 - NO_MODIFICATION_ALLOWED_ERR: (self) 78 Raised if the rule is readonly. 79 - SYNTAX_ERR: 80 Raised if the specified CSS string value has a syntax error and 81 is unparsable. 82 - NAMESPACE_ERR: 83 If a namespace prefix is found which is not declared. 84 """ 85 # stylesheet : [ CDO | CDC | S | statement ]*; 86 self._checkReadonly() 87 tokenizer = self._tokenize2(cssText) 88 newseq = cssutils.css.CSSRuleList() 89 # for closures: must be a mutable 90 new = { 'prefixes': set() } 91 92 def S(expected, seq, token, tokenizer=None): 93 # @charset must be at absolute beginning of style sheet 94 if expected == 0: 95 return 1 96 else: 97 return expected
98 99 def charsetrule(expected, seq, token, tokenizer): 100 rule = cssutils.css.CSSCharsetRule() 101 rule.cssText = self._tokensupto2(tokenizer, token) 102 if expected > 0 or len(seq) > 0: 103 self._log.error( 104 u'CSSStylesheet: CSSCharsetRule only allowed at beginning of stylesheet.', 105 token, xml.dom.HierarchyRequestErr) 106 else: 107 if rule.valid: 108 seq.append(rule) 109 return 1
110 111 def importrule(expected, seq, token, tokenizer): 112 rule = cssutils.css.CSSImportRule() 113 rule.cssText = self._tokensupto2(tokenizer, token) 114 if expected > 1: 115 self._log.error( 116 u'CSSStylesheet: CSSImportRule not allowed here.', 117 token, xml.dom.HierarchyRequestErr) 118 else: 119 if rule.valid: 120 seq.append(rule) 121 return 1 122 123 def namespacerule(expected, seq, token, tokenizer): 124 rule = cssutils.css.CSSNamespaceRule() 125 rule.cssText = self._tokensupto2(tokenizer, token) 126 if expected > 2: 127 self._log.error( 128 u'CSSStylesheet: CSSNamespaceRule not allowed here.', 129 token, xml.dom.HierarchyRequestErr) 130 else: 131 if rule.valid: 132 seq.append(rule) 133 new['prefixes'].add(rule.prefix) 134 return 2 135 136 def pagerule(expected, seq, token, tokenizer): 137 rule = cssutils.css.CSSPageRule() 138 rule.cssText = self._tokensupto2(tokenizer, token) 139 if rule.valid: 140 seq.append(rule) 141 return 3 142 143 def mediarule(expected, seq, token, tokenizer): 144 rule = cssutils.css.CSSMediaRule() 145 rule.cssText = self._tokensupto2(tokenizer, token) 146 if rule.valid: 147 seq.append(rule) 148 return 3 149 150 def unknownrule(expected, seq, token, tokenizer): 151 rule = cssutils.css.CSSUnknownRule() 152 rule.cssText = self._tokensupto2(tokenizer, token) 153 if rule.valid: 154 seq.append(rule) 155 return expected 156 157 def ruleset(expected, seq, token, tokenizer): 158 rule = cssutils.css.CSSStyleRule() 159 rule.cssText = self._tokensupto2(tokenizer, token) 160 161 # check namespaces 162 notdeclared = set() 163 for selector in rule.selectorList.seq: 164 for prefix in selector.prefixes: 165 if not prefix in new['prefixes']: 166 notdeclared.add(prefix) 167 if notdeclared: 168 rule.valid = False 169 self._log.error( 170 u'CSSStylesheet: CSSStyleRule uses undeclared namespace prefixes: %s.' % 171 u', '.join(notdeclared), error=xml.dom.NamespaceErr) 172 173 if rule.valid: 174 seq.append(rule) 175 return 3 176 177 # expected: 178 # ['CHARSET', 'IMPORT', 'NAMESPACE', ('PAGE', 'MEDIA', ruleset)] 179 valid, expected = self._parse(0, newseq, tokenizer, 180 {'S': S, 181 'CDO': lambda *ignored: None, 182 'CDC': lambda *ignored: None, 183 'CHARSET_SYM': charsetrule, 184 'IMPORT_SYM': importrule, 185 'NAMESPACE_SYM': namespacerule, 186 'PAGE_SYM': pagerule, 187 'MEDIA_SYM': mediarule, 188 'ATKEYWORD': unknownrule 189 }, 190 default=ruleset) 191 192 self.cssRules = newseq 193 self.prefixes = new['prefixes'] 194 for r in self.cssRules: 195 r.parentStyleSheet = self 196 197 cssText = property(_getCssText, _setCssText, 198 "(cssutils) a textual representation of the stylesheet") 199
200 - def deleteRule(self, index):
201 """ 202 Used to delete a rule from the style sheet. 203 204 index 205 of the rule to remove in the StyleSheet's rule list. For an 206 index < 0 **no** INDEX_SIZE_ERR is raised but rules for 207 normal Python lists are used. E.g. ``deleteRule(-1)`` removes 208 the last rule in cssRules. 209 210 DOMException 211 212 - INDEX_SIZE_ERR: (self) 213 Raised if the specified index does not correspond to a rule in 214 the style sheet's rule list. 215 - NO_MODIFICATION_ALLOWED_ERR: (self) 216 Raised if this style sheet is readonly. 217 """ 218 self._checkReadonly() 219 220 try: 221 self.cssRules[index].parentStyleSheet = None # detach 222 del self.cssRules[index] # delete from StyleSheet 223 except IndexError: 224 raise xml.dom.IndexSizeErr( 225 u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % ( 226 index, self.cssRules.length))
227
228 - def insertRule(self, rule, index=None):
229 """ 230 Used to insert a new rule into the style sheet. The new rule now 231 becomes part of the cascade. 232 233 Rule may be a string or a valid CSSRule. 234 235 rule 236 a parsable DOMString (cssutils: or CSSRule object) 237 index 238 of the rule before the new rule will be inserted. 239 If the specified index is equal to the length of the 240 StyleSheet's rule collection, the rule will be added to the end 241 of the style sheet. 242 If index is not given or None rule will be appended to rule 243 list. 244 245 returns the index within the stylesheet's rule collection 246 247 DOMException 248 249 - HIERARCHY_REQUEST_ERR: (self) 250 Raised if the rule cannot be inserted at the specified index 251 e.g. if an @import rule is inserted after a standard rule set 252 or other at-rule. 253 - INDEX_SIZE_ERR: (self) 254 Raised if the specified index is not a valid insertion point. 255 - NO_MODIFICATION_ALLOWED_ERR: (self) 256 Raised if this style sheet is readonly. 257 - SYNTAX_ERR: (rule) 258 Raised if the specified rule has a syntax error and is 259 unparsable. 260 """ 261 self._checkReadonly() 262 263 # check position 264 if index is None: 265 index = len(self.cssRules) 266 elif index < 0 or index > self.cssRules.length: 267 raise xml.dom.IndexSizeErr( 268 u'CSSStyleSheet: Invalid index %s for CSSRuleList with a length of %s.' % ( 269 index, self.cssRules.length)) 270 271 # parse 272 if isinstance(rule, basestring): 273 tempsheet = CSSStyleSheet() 274 tempsheet.cssText = rule 275 if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and 276 not isinstance(tempsheet.cssRules[0], cssutils.css.CSSRule)): 277 self._log.error(u'CSSStyleSheet: Invalid Rule: %s' % rule) 278 return 279 rule = tempsheet.cssRules[0] 280 elif not isinstance(rule, cssutils.css.CSSRule): 281 self._log.error(u'CSSStyleSheet: Not a CSSRule: %s' % rule) 282 return 283 284 # CHECK HIERARCHY 285 # @charset 286 if isinstance(rule, cssutils.css.CSSCharsetRule): 287 if index != 0 or (self.cssRules and 288 isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule)): 289 self._log.error( 290 u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.', 291 error=xml.dom.HierarchyRequestErr) 292 return 293 else: 294 self.cssRules.insert(index, rule) 295 rule.parentStyleSheet = self 296 297 # @unknown or comment 298 elif isinstance(rule, cssutils.css.CSSUnknownRule) or \ 299 isinstance(rule, cssutils.css.CSSComment): 300 if index == 0 and self.cssRules and \ 301 isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule): 302 self._log.error( 303 u'CSSStylesheet: @charset must be the first rule.', 304 error=xml.dom.HierarchyRequestErr) 305 return 306 else: 307 self.cssRules.insert(index, rule) 308 rule.parentStyleSheet = self 309 310 # @import 311 elif isinstance(rule, cssutils.css.CSSImportRule): 312 # after @charset 313 if index == 0 and self.cssRules and \ 314 isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule): 315 self._log.error( 316 u'CSSStylesheet: Found @charset at index 0.', 317 error=xml.dom.HierarchyRequestErr) 318 return 319 # before @namespace, @media and stylerule 320 for r in self.cssRules[:index]: 321 if isinstance(r, cssutils.css.CSSNamespaceRule) or \ 322 isinstance(r, cssutils.css.CSSMediaRule) or \ 323 isinstance(r, cssutils.css.CSSPageRule) or \ 324 isinstance(r, cssutils.css.CSSStyleRule): 325 self._log.error( 326 u'CSSStylesheet: Found @namespace, @media, @page or StyleRule before index %s.' % 327 index, 328 error=xml.dom.HierarchyRequestErr) 329 return 330 self.cssRules.insert(index, rule) 331 rule.parentStyleSheet = self 332 333 # @namespace 334 elif isinstance(rule, cssutils.css.CSSNamespaceRule): 335 # after @charset and @import 336 for r in self.cssRules[index:]: 337 if isinstance(r, cssutils.css.CSSCharsetRule) or \ 338 isinstance(r, cssutils.css.CSSImportRule): 339 self._log.error( 340 u'CSSStylesheet: Found @charset or @import after index %s.' % 341 index, 342 error=xml.dom.HierarchyRequestErr) 343 return 344 # before @media and stylerule 345 for r in self.cssRules[:index]: 346 if isinstance(r, cssutils.css.CSSMediaRule) or \ 347 isinstance(r, cssutils.css.CSSPageRule) or \ 348 isinstance(r, cssutils.css.CSSStyleRule): 349 self._log.error( 350 u'CSSStylesheet: Found @media, @page or StyleRule before index %s.' % 351 index, 352 error=xml.dom.HierarchyRequestErr) 353 return 354 self.cssRules.insert(index, rule) 355 rule.parentStyleSheet = self 356 self.prefixes.add(rule.prefix) 357 358 # all other 359 else: 360 for r in self.cssRules[index:]: 361 if isinstance(r, cssutils.css.CSSCharsetRule) or \ 362 isinstance(r, cssutils.css.CSSImportRule) or \ 363 isinstance(r, cssutils.css.CSSNamespaceRule): 364 self._log.error( 365 u'CSSStylesheet: Found @charset, @import or @namespace before index %s.' % 366 index, 367 error=xml.dom.HierarchyRequestErr) 368 return 369 self.cssRules.insert(index, rule) 370 rule.parentStyleSheet = self 371 372 return index
373
374 - def _getsetOwnerRuleDummy(self):
375 """ 376 NOT IMPLEMENTED YET 377 378 CSSOM 379 ----- 380 The ownerRule attribute, on getting, must return the CSSImportRule 381 that caused this style sheet to be imported (if any). Otherwise, if 382 no CSSImportRule caused it to be imported the attribute must return 383 null. 384 """ 385 raise NotImplementedError()
386 387 ownerRule = property(_getsetOwnerRuleDummy, _getsetOwnerRuleDummy, 388 doc="(DOM attribute) NOT IMPLEMENTED YET") 389
390 - def replaceUrls(self, replacer):
391 """ 392 **EXPERIMENTAL** 393 394 Utility method to replace all url(urlstring) values in 395 CSSImportRules and CSSStyleDeclaration objects (properties). 396 397 ``replacer`` must be a function which is called with a single 398 argument ``urlstring`` which is the current value of url() 399 excluding ``url(`` and ``)``. It still may have surrounding 400 single or double quotes though. 401 """ 402 for importrule in [ 403 r for r in self.cssRules if hasattr(r, 'href')]: 404 importrule.href = replacer(importrule.href) 405 406 def setProperty(v): 407 if v.CSS_PRIMITIVE_VALUE == v.cssValueType and\ 408 v.CSS_URI == v.primitiveType: 409 v.setStringValue(v.CSS_URI, 410 replacer(v.getStringValue()))
411 412 def styleDeclarations(base): 413 "recurive function to find all CSSStyleDeclarations" 414 styles = [] 415 if hasattr(base, 'cssRules'): 416 for rule in base.cssRules: 417 styles.extend(styleDeclarations(rule)) 418 elif hasattr(base, 'style'): 419 styles.append(base.style) 420 return styles 421 422 for style in styleDeclarations(self): 423 for p in style: 424 v = p.cssValue 425 if v.CSS_VALUE_LIST == v.cssValueType: 426 for item in v: 427 setProperty(item) 428 elif v.CSS_PRIMITIVE_VALUE == v.cssValueType: 429 setProperty(v) 430
431 - def setSerializer(self, cssserializer):
432 """ 433 Sets Serializer used for output of this stylesheet 434 """ 435 if isinstance(cssserializer, cssutils.CSSSerializer): 436 cssutils.ser = cssserializer 437 else: 438 raise ValueError(u'Serializer must be an instance of cssutils.CSSSerializer.')
439
440 - def setSerializerPref(self, pref, value):
441 """ 442 Sets Preference of CSSSerializer used for output of this 443 stylesheet. See cssutils.serialize.Preferences for possible 444 preferences to be set. 445 """ 446 cssutils.ser.prefs.__setattr__(pref, value)
447
448 - def __repr__(self):
449 return "cssutils.css.%s(href=%r, title=%r)" % ( 450 self.__class__.__name__, self.href, self.title)
451
452 - def __str__(self):
453 return "<cssutils.css.%s object title=%r href=%r at 0x%x>" % ( 454 self.__class__.__name__, self.title, self.href, id(self))
455