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