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  __version__ = '$Id: cssstylesheet.py 1152 2008-03-18 22:36:39Z cthedot $' 
 14   
 15  import xml.dom 
 16  import cssutils.stylesheets 
 17  from cssutils.util import _Namespaces, _SimpleNamespaces, Deprecated 
18 19 -class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
20 """ 21 The CSSStyleSheet interface represents a CSS style sheet. 22 23 Properties 24 ========== 25 CSSOM 26 ----- 27 cssRules 28 of type CSSRuleList, (DOM readonly) 29 encoding 30 reflects the encoding of an @charset rule or 'utf-8' (default) 31 if set to ``None`` 32 ownerRule 33 of type CSSRule, readonly. If this sheet is imported this is a ref 34 to the @import rule that imports it. 35 36 Inherits properties from stylesheet.StyleSheet 37 38 cssutils 39 -------- 40 cssText: string 41 a textual representation of the stylesheet 42 namespaces 43 reflects set @namespace rules of this rule. 44 A dict of {prefix: namespaceURI} mapping. 45 46 Format 47 ====== 48 stylesheet 49 : [ CHARSET_SYM S* STRING S* ';' ]? 50 [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* 51 [ namespace [S|CDO|CDC]* ]* # according to @namespace WD 52 [ [ ruleset | media | page ] [S|CDO|CDC]* ]* 53 """ 54 55 type = 'text/css' 56
57 - def __init__(self, href=None, media=None, title=u'', disabled=None, 58 ownerNode=None, parentStyleSheet=None, readonly=False, 59 ownerRule=None):
60 """ 61 init parameters are the same as for stylesheets.StyleSheet 62 """ 63 super(CSSStyleSheet, self).__init__( 64 self.type, href, media, title, disabled, 65 ownerNode, parentStyleSheet) 66 67 self._ownerRule = ownerRule 68 self.cssRules = cssutils.css.CSSRuleList() 69 self.cssRules.append = self.insertRule 70 self.cssRules.extend = self.insertRule 71 self._namespaces = _Namespaces(parentStyleSheet=self) 72 self._readonly = readonly
73
74 - def __iter__(self):
75 "generator which iterates over cssRules." 76 for rule in self.cssRules: 77 yield rule
78
79 - def _cleanNamespaces(self):
80 "removes all namespace rules with same namespaceURI but last one set" 81 rules = self.cssRules 82 namespaceitems = self.namespaces.items() 83 i = 0 84 while i < len(rules): 85 rule = rules[i] 86 if rule.type == rule.NAMESPACE_RULE and \ 87 (rule.prefix, rule.namespaceURI) not in namespaceitems: 88 self.deleteRule(i) 89 else: 90 i += 1
91
92 - def _getUsedURIs(self):
93 "returns set of URIs used in the sheet" 94 useduris = set() 95 for r1 in self: 96 if r1.STYLE_RULE == r1.type: 97 useduris.update(r1.selectorList._getUsedUris()) 98 elif r1.MEDIA_RULE == r1.type: 99 for r2 in r1: 100 if r2.type == r2.STYLE_RULE: 101 useduris.update(r2.selectorList._getUsedUris()) 102 return useduris
103
104 - def _getCssText(self):
105 return cssutils.ser.do_CSSStyleSheet(self)
106
107 - def _setCssText(self, cssText):
108 """ 109 (cssutils) 110 Parses ``cssText`` and overwrites the whole stylesheet. 111 112 :param cssText: 113 a parseable string or a tuple of (cssText, dict-of-namespaces) 114 :Exceptions: 115 - `NAMESPACE_ERR`: 116 If a namespace prefix is found which is not declared. 117 - `NO_MODIFICATION_ALLOWED_ERR`: (self) 118 Raised if the rule is readonly. 119 - `SYNTAX_ERR`: 120 Raised if the specified CSS string value has a syntax error and 121 is unparsable. 122 """ 123 self._checkReadonly() 124 125 cssText, namespaces = self._splitNamespacesOff(cssText) 126 if not namespaces: 127 namespaces = _SimpleNamespaces() 128 129 tokenizer = self._tokenize2(cssText) 130 newseq = [] #cssutils.css.CSSRuleList() 131 132 # for closures: must be a mutable 133 new = { 'namespaces': namespaces} 134 135 def S(expected, seq, token, tokenizer=None): 136 # @charset must be at absolute beginning of style sheet 137 if expected == 0: 138 return 1 139 else: 140 return expected
141 142 def COMMENT(expected, seq, token, tokenizer=None): 143 "special: sets parent*" 144 comment = cssutils.css.CSSComment([token], 145 parentStyleSheet=self.parentStyleSheet) 146 seq.append(comment) 147 return expected
148 149 def charsetrule(expected, seq, token, tokenizer): 150 rule = cssutils.css.CSSCharsetRule(parentStyleSheet=self) 151 rule.cssText = self._tokensupto2(tokenizer, token) 152 if expected > 0 or len(seq) > 0: 153 self._log.error( 154 u'CSSStylesheet: CSSCharsetRule only allowed at beginning of stylesheet.', 155 token, xml.dom.HierarchyRequestErr) 156 else: 157 if rule.wellformed: 158 seq.append(rule) 159 return 1 160 161 def importrule(expected, seq, token, tokenizer): 162 rule = cssutils.css.CSSImportRule(parentStyleSheet=self) 163 rule.cssText = self._tokensupto2(tokenizer, token) 164 if expected > 1: 165 self._log.error( 166 u'CSSStylesheet: CSSImportRule not allowed here.', 167 token, xml.dom.HierarchyRequestErr) 168 else: 169 if rule.wellformed: 170 seq.append(rule) 171 return 1 172 173 def namespacerule(expected, seq, token, tokenizer): 174 rule = cssutils.css.CSSNamespaceRule( 175 cssText=self._tokensupto2(tokenizer, token), 176 parentStyleSheet=self) 177 if expected > 2: 178 self._log.error( 179 u'CSSStylesheet: CSSNamespaceRule not allowed here.', 180 token, xml.dom.HierarchyRequestErr) 181 else: 182 if rule.wellformed: 183 seq.append(rule) 184 # temporary namespaces given to CSSStyleRule and @media 185 new['namespaces'][rule.prefix] = rule.namespaceURI 186 return 2 187 188 def fontfacerule(expected, seq, token, tokenizer): 189 rule = cssutils.css.CSSFontFaceRule(parentStyleSheet=self) 190 rule.cssText = self._tokensupto2(tokenizer, token) 191 if rule.wellformed: 192 seq.append(rule) 193 return 3 194 195 def mediarule(expected, seq, token, tokenizer): 196 rule = cssutils.css.CSSMediaRule() 197 rule.cssText = (self._tokensupto2(tokenizer, token), 198 new['namespaces']) 199 if rule.wellformed: 200 rule._parentStyleSheet=self 201 for r in rule: 202 r._parentStyleSheet=self 203 seq.append(rule) 204 return 3 205 206 def pagerule(expected, seq, token, tokenizer): 207 rule = cssutils.css.CSSPageRule(parentStyleSheet=self) 208 rule.cssText = self._tokensupto2(tokenizer, token) 209 if rule.wellformed: 210 seq.append(rule) 211 return 3 212 213 def unknownrule(expected, seq, token, tokenizer): 214 rule = cssutils.css.CSSUnknownRule(parentStyleSheet=self) 215 rule.cssText = self._tokensupto2(tokenizer, token) 216 if rule.wellformed: 217 seq.append(rule) 218 return expected 219 220 def ruleset(expected, seq, token, tokenizer): 221 rule = cssutils.css.CSSStyleRule() 222 rule.cssText = (self._tokensupto2(tokenizer, token), 223 new['namespaces']) 224 if rule.wellformed: 225 rule._parentStyleSheet=self 226 seq.append(rule) 227 return 3 228 229 # expected: 230 # ['CHARSET', 'IMPORT', 'NAMESPACE', ('PAGE', 'MEDIA', ruleset)] 231 wellformed, expected = self._parse(0, newseq, tokenizer, 232 {'S': S, 233 'COMMENT': COMMENT, 234 'CDO': lambda *ignored: None, 235 'CDC': lambda *ignored: None, 236 'CHARSET_SYM': charsetrule, 237 'FONT_FACE_SYM': fontfacerule, 238 'IMPORT_SYM': importrule, 239 'NAMESPACE_SYM': namespacerule, 240 'PAGE_SYM': pagerule, 241 'MEDIA_SYM': mediarule, 242 'ATKEYWORD': unknownrule 243 }, 244 default=ruleset) 245 246 if wellformed: 247 del self.cssRules[:] 248 for rule in newseq: 249 self.insertRule(rule, _clean=False) 250 self._cleanNamespaces() 251 252 cssText = property(_getCssText, _setCssText, 253 "(cssutils) a textual representation of the stylesheet") 254
255 - def _setEncoding(self, encoding):
256 """ 257 sets encoding of charset rule if present or inserts new charsetrule 258 with given encoding. If encoding if None removes charsetrule if 259 present. 260 """ 261 try: 262 rule = self.cssRules[0] 263 except IndexError: 264 rule = None 265 if rule and rule.CHARSET_RULE == rule.type: 266 if encoding: 267 rule.encoding = encoding 268 else: 269 self.deleteRule(0) 270 elif encoding: 271 self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0)
272
273 - def _getEncoding(self):
274 "return encoding if @charset rule if given or default of 'utf-8'" 275 try: 276 return self.cssRules[0].encoding 277 except (IndexError, AttributeError): 278 return 'utf-8'
279 280 encoding = property(_getEncoding, _setEncoding, 281 "(cssutils) reflects the encoding of an @charset rule or 'UTF-8' (default) if set to ``None``") 282 283 namespaces = property(lambda self: self._namespaces, 284 doc="Namespaces used in this CSSStyleSheet.") 285
286 - def add(self, rule):
287 """ 288 Adds rule to stylesheet at appropriate position. 289 Same as ``sheet.insertRule(rule, inOrder=True)``. 290 """ 291 return self.insertRule(rule, index=None, inOrder=True)
292
293 - def deleteRule(self, index):
294 """ 295 Used to delete a rule from the style sheet. 296 297 :param index: 298 of the rule to remove in the StyleSheet's rule list. For an 299 index < 0 **no** INDEX_SIZE_ERR is raised but rules for 300 normal Python lists are used. E.g. ``deleteRule(-1)`` removes 301 the last rule in cssRules. 302 :Exceptions: 303 - `INDEX_SIZE_ERR`: (self) 304 Raised if the specified index does not correspond to a rule in 305 the style sheet's rule list. 306 - `NAMESPACE_ERR`: (self) 307 Raised if removing this rule would result in an invalid StyleSheet 308 - `NO_MODIFICATION_ALLOWED_ERR`: (self) 309 Raised if this style sheet is readonly. 310 """ 311 self._checkReadonly() 312 313 try: 314 rule = self.cssRules[index] 315 except IndexError: 316 raise xml.dom.IndexSizeErr( 317 u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % ( 318 index, self.cssRules.length)) 319 else: 320 if rule.type == rule.NAMESPACE_RULE: 321 # check all namespacerules if used 322 uris = [r.namespaceURI for r in self if r.type == r.NAMESPACE_RULE] 323 useduris = self._getUsedURIs() 324 if rule.namespaceURI in useduris and\ 325 uris.count(rule.namespaceURI) == 1: 326 raise xml.dom.NoModificationAllowedErr( 327 u'CSSStyleSheet: NamespaceURI defined in this rule is used, cannot remove.') 328 return 329 330 rule._parentStyleSheet = None # detach 331 del self.cssRules[index] # delete from StyleSheet
332
333 - def insertRule(self, rule, index=None, inOrder=False, _clean=True):
334 """ 335 Used to insert a new rule into the style sheet. The new rule now 336 becomes part of the cascade. 337 338 :Parameters: 339 rule 340 a parsable DOMString, in cssutils also a CSSRule or a 341 CSSRuleList 342 index 343 of the rule before the new rule will be inserted. 344 If the specified index is equal to the length of the 345 StyleSheet's rule collection, the rule will be added to the end 346 of the style sheet. 347 If index is not given or None rule will be appended to rule 348 list. 349 inOrder 350 if True the rule will be put to a proper location while 351 ignoring index but without raising HIERARCHY_REQUEST_ERR. 352 The resulting index is returned nevertheless 353 :returns: the index within the stylesheet's rule collection 354 :Exceptions: 355 - `HIERARCHY_REQUEST_ERR`: (self) 356 Raised if the rule cannot be inserted at the specified index 357 e.g. if an @import rule is inserted after a standard rule set 358 or other at-rule. 359 - `INDEX_SIZE_ERR`: (self) 360 Raised if the specified index is not a valid insertion point. 361 - `NO_MODIFICATION_ALLOWED_ERR`: (self) 362 Raised if this style sheet is readonly. 363 - `SYNTAX_ERR`: (rule) 364 Raised if the specified rule has a syntax error and is 365 unparsable. 366 """ 367 self._checkReadonly() 368 369 # check position 370 if index is None: 371 index = len(self.cssRules) 372 elif index < 0 or index > self.cssRules.length: 373 raise xml.dom.IndexSizeErr( 374 u'CSSStyleSheet: Invalid index %s for CSSRuleList with a length of %s.' % ( 375 index, self.cssRules.length)) 376 return 377 378 if isinstance(rule, basestring): 379 # parse a new rule 380 tempsheet = CSSStyleSheet() 381 tempsheet.cssText = (rule, self._namespaces) 382 if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and 383 not isinstance(tempsheet.cssRules[0], cssutils.css.CSSRule)): 384 self._log.error(u'CSSStyleSheet: Invalid Rule: %s' % rule) 385 return 386 rule = tempsheet.cssRules[0] 387 rule._parentStyleSheet = None 388 389 elif isinstance(rule, cssutils.css.CSSRuleList): 390 # insert all rules 391 for i, r in enumerate(rule): 392 self.insertRule(r, index + i) 393 return index 394 395 if not rule.wellformed: 396 self._log.error(u'CSSStyleSheet: Invalid rules cannot be added.') 397 return 398 399 # CHECK HIERARCHY 400 # @charset 401 if rule.type == rule.CHARSET_RULE: 402 if inOrder: 403 index = 0 404 # always first and only 405 if (self.cssRules and self.cssRules[0].type == rule.CHARSET_RULE): 406 self.cssRules[0].encoding = rule.encoding 407 else: 408 self.cssRules.insert(0, rule) 409 elif index != 0 or (self.cssRules and 410 self.cssRules[0].type == rule.CHARSET_RULE): 411 self._log.error( 412 u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.', 413 error=xml.dom.HierarchyRequestErr) 414 return 415 else: 416 self.cssRules.insert(index, rule) 417 418 # @unknown or comment 419 elif rule.type in (rule.UNKNOWN_RULE, rule.COMMENT) and not inOrder: 420 if index == 0 and self.cssRules and\ 421 self.cssRules[0].type == rule.CHARSET_RULE: 422 self._log.error( 423 u'CSSStylesheet: @charset must be the first rule.', 424 error=xml.dom.HierarchyRequestErr) 425 return 426 else: 427 self.cssRules.insert(index, rule) 428 429 # @import 430 elif rule.type == rule.IMPORT_RULE: 431 if inOrder: 432 # automatic order 433 if rule.type in (r.type for r in self): 434 # find last of this type 435 for i, r in enumerate(reversed(self.cssRules)): 436 if r.type == rule.type: 437 index = len(self.cssRules) - i 438 break 439 else: 440 # find first point to insert 441 if self.cssRules and self.cssRules[0].type in (rule.CHARSET_RULE, 442 rule.COMMENT): 443 index = 1 444 else: 445 index = 0 446 else: 447 # after @charset 448 if index == 0 and self.cssRules and\ 449 self.cssRules[0].type == rule.CHARSET_RULE: 450 self._log.error( 451 u'CSSStylesheet: Found @charset at index 0.', 452 error=xml.dom.HierarchyRequestErr) 453 return 454 # before @namespace, @page, @font-face, @media and stylerule 455 for r in self.cssRules[:index]: 456 if r.type in (r.NAMESPACE_RULE, r.MEDIA_RULE, r.PAGE_RULE, 457 r.STYLE_RULE, r.FONT_FACE_RULE): 458 self._log.error( 459 u'CSSStylesheet: Cannot insert @import here, found @namespace, @media, @page or CSSStyleRule before index %s.' % 460 index, 461 error=xml.dom.HierarchyRequestErr) 462 return 463 self.cssRules.insert(index, rule) 464 465 # @namespace 466 elif rule.type == rule.NAMESPACE_RULE: 467 if inOrder: 468 if rule.type in (r.type for r in self): 469 # find last of this type 470 for i, r in enumerate(reversed(self.cssRules)): 471 if r.type == rule.type: 472 index = len(self.cssRules) - i 473 break 474 else: 475 # find first point to insert 476 for i, r in enumerate(self.cssRules): 477 if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, 478 r.FONT_FACE_RULE): 479 index = i # before these 480 break 481 else: 482 # after @charset and @import 483 for r in self.cssRules[index:]: 484 if r.type in (r.CHARSET_RULE, r.IMPORT_RULE): 485 self._log.error( 486 u'CSSStylesheet: Cannot insert @namespace here, found @charset or @import after index %s.' % 487 index, 488 error=xml.dom.HierarchyRequestErr) 489 return 490 # before @media and stylerule 491 for r in self.cssRules[:index]: 492 if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, 493 r.FONT_FACE_RULE): 494 self._log.error( 495 u'CSSStylesheet: Cannot insert @namespace here, found @media, @page or CSSStyleRule before index %s.' % 496 index, 497 error=xml.dom.HierarchyRequestErr) 498 return 499 500 if not (rule.prefix in self.namespaces and 501 self.namespaces[rule.prefix] == rule.namespaceURI): 502 # no doublettes 503 self.cssRules.insert(index, rule) 504 if _clean: 505 self._cleanNamespaces() 506 507 # all other 508 else: 509 if inOrder: 510 # after last of this kind or at end of sheet 511 if rule.type in (r.type for r in self): 512 # find last of this type 513 for i, r in enumerate(reversed(self.cssRules)): 514 if r.type == rule.type: 515 index = len(self.cssRules) - i 516 break 517 self.cssRules.insert(index, rule) 518 else: 519 self.cssRules.append(rule) # to end as no same present 520 else: 521 for r in self.cssRules[index:]: 522 if r.type in (r.CHARSET_RULE, r.IMPORT_RULE, r.NAMESPACE_RULE): 523 self._log.error( 524 u'CSSStylesheet: Cannot insert rule here, found @charset, @import or @namespace before index %s.' % 525 index, 526 error=xml.dom.HierarchyRequestErr) 527 return 528 self.cssRules.insert(index, rule) 529 530 rule._parentStyleSheet = self 531 if rule.MEDIA_RULE == rule.type: 532 for r in rule: 533 r._parentStyleSheet = self 534 return index
535 536 ownerRule = property(lambda self: self._ownerRule, 537 doc="(DOM attribute) NOT IMPLEMENTED YET") 538 539 @Deprecated('Use cssutils.replaceUrls(sheet, replacer) instead.')
540 - def replaceUrls(self, replacer):
541 """ 542 **EXPERIMENTAL** 543 544 Utility method to replace all ``url(urlstring)`` values in 545 ``CSSImportRules`` and ``CSSStyleDeclaration`` objects (properties). 546 547 ``replacer`` must be a function which is called with a single 548 argument ``urlstring`` which is the current value of url() 549 excluding ``url(`` and ``)``. It still may have surrounding 550 single or double quotes though. 551 """ 552 cssutils.replaceUrls(self, replacer)
553
554 - def setSerializer(self, cssserializer):
555 """ 556 Sets the global Serializer used for output of all stylesheet 557 output. 558 """ 559 if isinstance(cssserializer, cssutils.CSSSerializer): 560 cssutils.ser = cssserializer 561 else: 562 raise ValueError(u'Serializer must be an instance of cssutils.CSSSerializer.')
563
564 - def setSerializerPref(self, pref, value):
565 """ 566 Sets Preference of CSSSerializer used for output of this 567 stylesheet. See cssutils.serialize.Preferences for possible 568 preferences to be set. 569 """ 570 cssutils.ser.prefs.__setattr__(pref, value)
571
572 - def __repr__(self):
573 return "cssutils.css.%s(href=%r, title=%r)" % ( 574 self.__class__.__name__, self.href, self.title)
575
576 - def __str__(self):
577 return "<cssutils.css.%s object encoding=%r href=%r "\ 578 "namespaces=%r title=%r at 0x%x>" % ( 579 self.__class__.__name__, self.encoding, self.href, 580 self.namespaces.namespaces, self.title, id(self))
581