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