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