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