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