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