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