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://www.w3.org/TR/2006/WD-css3-namespace-20060828/
      6
      7  TODO:
      8      - ownerRule and ownerNode
      9  """
     10  __all__ = ['CSSStyleSheet']
     11  __docformat__ = 'restructuredtext'
     12  __author__ = '$LastChangedBy: doerwalter $'
     13  __date__ = '$LastChangedDate: 2007-08-27 12:07:28 +0200 (Mo, 27 Aug 2007) $'
     14  __version__ = '$LastChangedRevision: 287 $'
     15
     16  import xml.dom
     17
     18  import cssutils.stylesheets
     19  import cssutils.tokenize
     20
     21
     22 -class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
     23      """
     24       The CSSStyleSheet interface is a concrete interface used to represent
     25       a CSS style sheet i.e., a style sheet whose content type is
     26       "text/css".
     27
     28      Properties
     29      ==========
     30      cssRules: of type CSSRuleList, (DOM readonly)
     31          The list of all CSS rules contained within the style sheet. This
     32          includes both rule sets and at-rules.
     33      namespaces: set
     34          A set of declared namespaces via @namespace rules. Each
     35          CSSStyleRule is checked if it uses additional prefixes which are
     36          not declared. If they are "invalidated".
     37      ownerRule: of type CSSRule, readonly
     38          If this style sheet comes from an @import rule, the ownerRule
     39          attribute will contain the CSSImportRule. In that case, the
     40          ownerNode attribute in the StyleSheet interface will be None. If
     41          the style sheet comes from an element or a processing instruction,
     42          the ownerRule attribute will be None and the ownerNode attribute
     43          will contain the Node.
     44
     45      Inherits properties from stylesheet.StyleSheet
     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.namespaces = set()
     68          self._readonly = readonly
     69
     70
     71 -    def __checknamespaces(self, stylerule, namespaces):
     72          """
     73          checks if all namespaces used in stylerule have been declared
     74          """
     75          notdeclared = set()
     76          for s in stylerule.selectorList:
     77              for prefix in s.namespaces:
     78                  if not prefix in namespaces:
     79                      notdeclared.add(prefix)
     80          return notdeclared
     81
     82
     83 -    def _getCssText(self):
     84          return cssutils.ser.do_CSSStyleSheet(self)
     85
     86 -    def _setCssText(self, cssText):
     87          """
     88          (cssutils)
     89          Parses cssText and overwrites the whole stylesheet.
     90
     91          cssText
     92              textual text to set
     93
     94          DOMException on setting
     95
     96          - NO_MODIFICATION_ALLOWED_ERR: (self)
     97            Raised if the rule is readonly.
     98          - SYNTAX_ERR:
     99            Raised if the specified CSS string value has a syntax error and
    100            is unparsable.
    101          - NAMESPACE_ERR:
    102            If a namespace prefix is found which is not declared.
    103          """
    104          self._checkReadonly()
    105
    106          tokens = self._tokenize(cssText, _fullSheet=True)
    107
    108          newcssRules = cssutils.css.CSSRuleList()
    109          newnamespaces = set()
    110
    111          # @charset only once at beginning
    112          if tokens and tokens[0].type == self._ttypes.CHARSET_SYM:
    113              charsetruletokens, endi = self._tokensupto(tokens)
    114              charsetrule = cssutils.css.CSSCharsetRule()
    115              charsetrule.cssText = charsetruletokens
    116              newcssRules.append(charsetrule)
    117              i = endi + 1 # ?
    118          else:
    119              i = 0
    120          expected = '@import' # @namespace | any
    121          imax = len(tokens)
    122          while i < imax:
    123              t = tokens[i]
    124
    125              if t.type == self._ttypes.EOF:
    126                  break
    127
    128              # ignore
    129              if t.type in (self._ttypes.S, self._ttypes.CDO, self._ttypes.CDC):
    130                  pass
    131
    132              # unexpected: } ;
    133              elif t.type in (self._ttypes.SEMICOLON,
    134                              self._ttypes.LBRACE, self._ttypes.RBRACE):
    135                  self._log.error(u'CSSStyleSheet: Syntax Error',t)
    136
    137              # simply add
    138              elif self._ttypes.COMMENT == t.type:
    139                  newcssRules.append(cssutils.css.CSSComment(t))
    140
    141              # @charset only at beginning
    142              elif self._ttypes.CHARSET_SYM == t.type:
    143                  atruletokens, endi = self._tokensupto(tokens[i:])
    144                  i += endi
    145                  self._log.error(u'CSSStylesheet: CSSCharsetRule only allowed at beginning of stylesheet.',
    146                      t, xml.dom.HierarchyRequestErr)
    147
    148              # @import before any StyleRule and @rule except @charset
    149              elif self._ttypes.IMPORT_SYM == t.type:
    150                  atruletokens, endi = self._tokensupto(tokens[i:])
    151                  if expected == '@import':
    152                      atrule = cssutils.css.CSSImportRule()
    153                      atrule.cssText = atruletokens
    154                      newcssRules.append(atrule)
    155                  else:
    156                      self._log.error(
    157                          u'CSSStylesheet: CSSImportRule not allowed here.',
    158                          t, xml.dom.HierarchyRequestErr)
    159                  i += endi
    160
    161              # @namespace before any StyleRule and
    162              #  before @rule except @charset, @import
    163              elif self._ttypes.NAMESPACE_SYM == t.type:
    164                  atruletokens, endi = self._tokensupto(tokens[i:])
    165                  if expected in ('@import', '@namespace'):
    166                      atrule = cssutils.css.CSSNamespaceRule()
    167                      atrule.cssText = atruletokens
    168                      newcssRules.append(atrule)
    169                      newnamespaces.add(atrule.prefix)
    170                  else:
    171                      self._log.error(
    172                          u'CSSStylesheet: CSSNamespaceRule not allowed here.',
    173                          t, xml.dom.HierarchyRequestErr)
    174                  i += endi
    175                  expected = '@namespace' # now no @import anymore!
    176
    177              # @media
    178              elif self._ttypes.MEDIA_SYM == t.type:
    179                  atruletokens, endi = self._tokensupto(tokens[i:])
    180                  atrule = cssutils.css.CSSMediaRule()
    181                  atrule.cssText = atruletokens
    182                  newcssRules.append(atrule)
    183                  i += endi
    184                  expected = 'any'
    185
    186              # @page
    187              elif self._ttypes.PAGE_SYM == t.type:
    188                  atruletokens, endi = self._tokensupto(tokens[i:])
    189                  atrule = cssutils.css.CSSPageRule()
    190                  atrule.cssText = atruletokens
    191                  newcssRules.append(atrule)
    192                  i += endi
    193                  expected = 'any'
    194
    195              # @unknown, insert in any case (but after @charset)
    196              elif self._ttypes.ATKEYWORD == t.type:
    197                  atruletokens, endi = self._tokensupto(tokens[i:])
    198                  atrule = cssutils.css.CSSUnknownRule()
    199                  atrule.cssText = atruletokens
    200                  newcssRules.append(atrule)
    201                  i += endi
    202
    203              else: # probable StyleRule
    204                  ruletokens, endi = self._tokensupto(
    205                      tokens[i:], blockendonly=True)
    206                  rule = cssutils.css.CSSStyleRule()
    207                  rule.cssText = ruletokens
    208                  notdeclared = self.__checknamespaces(rule, newnamespaces)
    209                  if notdeclared:
    210                      rule.valid = False
    211                      self._log.error(
    212                          u'CSSStylesheet: CSSStyleRule uses undeclared namespace prefixes: %s.' %
    213                          ', '.join(notdeclared),
    214                          t, xml.dom.NamespaceErr)
    215                  newcssRules.append(rule)
    216                  i += endi
    217                  expected = 'any'
    218
    219              i += 1
    220
    221          self.cssRules = newcssRules
    222          for r in self.cssRules:
    223              r.parentStyleSheet = self
    224          self.namespaces = newnamespaces
    225
    226      cssText = property(_getCssText, _setCssText,
    227              "(cssutils) a textual representation of the stylesheet")
    228
    229
    230 -    def deleteRule(self, index):
    231          """
    232          Used to delete a rule from the style sheet.
    233
    234          index
    235              of the rule to remove in the StyleSheet's rule list
    236
    237          DOMException
    238
    239          - INDEX_SIZE_ERR: (self)
    240            Raised if the specified index does not correspond to a rule in
    241            the style sheet's rule list.
    242          - NO_MODIFICATION_ALLOWED_ERR: (self)
    243            Raised if this style sheet is readonly.
    244          """
    245          self._checkReadonly()
    246
    247          try:
    248              self.cssRules[index].parentStyleSheet = None # detach
    249              del self.cssRules[index] # delete from StyleSheet
    250          except IndexError:
    251              raise xml.dom.IndexSizeErr(
    252                  u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % (
    253                  index, self.cssRules.length))
    254
    255
    256 -    def insertRule(self, rule, index=None):
    257          """
    258          Used to insert a new rule into the style sheet. The new rule now
    259          becomes part of the cascade.
    260
    261          Rule may be a string or a valid CSSRule.
    262
    263          rule
    264              a parsable DOMString (cssutils: or Rule object)
    265          index
    266              of the rule before the new rule will be inserted.
    267              If the specified index is equal to the length of the
    268              StyleSheet's rule collection, the rule will be added to the end
    269              of the style sheet.
    270              If index is not given or None rule will be appended to rule
    271              list.
    272
    273          returns the index within the stylesheet's rule collection
    274
    275          DOMException
    276
    277          - HIERARCHY_REQUEST_ERR: (self)
    278            Raised if the rule cannot be inserted at the specified index
    279            e.g. if an @import rule is inserted after a standard rule set
    280            or other at-rule.
    281          - INDEX_SIZE_ERR: (not raised at all)
    282            Raised if the specified index is not a valid insertion point.
    283          - NO_MODIFICATION_ALLOWED_ERR: (self)
    284            Raised if this style sheet is readonly.
    285          - SYNTAX_ERR: (rule)
    286            Raised if the specified rule has a syntax error and is
    287            unparsable.
    288          """
    289          self._checkReadonly()
    290
    291          # check position
    292          if index is None:
    293              index = len(self.cssRules)
    294          elif index < 0 or index > self.cssRules.length:
    295              raise xml.dom.IndexSizeErr(
    296                  u'CSSStyleSheet: Invalid index %s for CSSRuleList with a length of %s.' % (
    297                      index, self.cssRules.length))
    298
    299          # parse
    300          if isinstance(rule, basestring):
    301              tempsheet = CSSStyleSheet()
    302              tempsheet.cssText = rule
    303              if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and
    304               not isinstance(tempsheet.cssRules[0], cssutils.css.CSSRule)):
    305                  self._log.error(u'CSSStyleSheet: Invalid Rule: %s' % rule)
    306                  return
    307              rule = tempsheet.cssRules[0]
    308          elif not isinstance(rule, cssutils.css.CSSRule):
    309              self._log.error(u'CSSStyleSheet: Not a CSSRule: %s' % rule)
    310              return
    311
    312          # CHECK HIERARCHY
    313          # @charset
    314          if isinstance(rule, cssutils.css.CSSCharsetRule):
    315              if index != 0 or (self.cssRules and
    316               isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule)):
    317                  self._log.error(
    318                      u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.',
    319                      error=xml.dom.HierarchyRequestErr)
    320                  return
    321              else:
    322                  self.cssRules.insert(index, rule)
    323                  rule.parentStyleSheet = self
    324
    325          # @unknown or comment
    326          elif isinstance(rule, cssutils.css.CSSUnknownRule) or \
    327             isinstance(rule, cssutils.css.CSSComment):
    328              if index == 0 and self.cssRules and \
    329               isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule):
    330                  self._log.error(
    331                      u'CSSStylesheet: @charset must be the first rule.',
    332                      error=xml.dom.HierarchyRequestErr)
    333                  return
    334              else:
    335                  self.cssRules.insert(index, rule)
    336                  rule.parentStyleSheet = self
    337
    338          # @import
    339          elif isinstance(rule, cssutils.css.CSSImportRule):
    340              # after @charset
    341              if index == 0 and self.cssRules and \
    342                 isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule):
    343                  self._log.error(
    344                      u'CSSStylesheet: Found @charset at index 0.',
    345                      error=xml.dom.HierarchyRequestErr)
    346                  return
    347              # before @namespace, @media and stylerule
    348              for r in self.cssRules[:index]:
    349                  if isinstance(r, cssutils.css.CSSNamespaceRule) or \
    350                     isinstance(r, cssutils.css.CSSMediaRule) or \
    351                     isinstance(r, cssutils.css.CSSPageRule) or \
    352                     isinstance(r, cssutils.css.CSSStyleRule):
    353                      self._log.error(
    354                          u'CSSStylesheet: Found @namespace, @media, @page or StyleRule before index %s.' %
    355                          index,
    356                          error=xml.dom.HierarchyRequestErr)
    357                      return
    358              self.cssRules.insert(index, rule)
    359              rule.parentStyleSheet = self
    360
    361          # @namespace
    362          elif isinstance(rule, cssutils.css.CSSNamespaceRule):
    363              # after @charset and @import
    364              for r in self.cssRules[index:]:
    365                  if isinstance(r, cssutils.css.CSSCharsetRule) or \
    366                     isinstance(r, cssutils.css.CSSImportRule):
    367                      self._log.error(
    368                          u'CSSStylesheet: Found @charset or @import after index %s.' %
    369                          index,
    370                          error=xml.dom.HierarchyRequestErr)
    371                      return
    372              # before @media and stylerule
    373              for r in self.cssRules[:index]:
    374                  if isinstance(r, cssutils.css.CSSMediaRule) or \
    375                      isinstance(r, cssutils.css.CSSPageRule) or \
    376                      isinstance(r, cssutils.css.CSSStyleRule):
    377                      self._log.error(
    378                          u'CSSStylesheet: Found @media, @page or StyleRule before index %s.' %
    379                          index,
    380                          error=xml.dom.HierarchyRequestErr)
    381                      return
    382              self.cssRules.insert(index, rule)
    383              rule.parentStyleSheet = self
    384              self.namespaces.add(rule.prefix)
    385
    386          # all other
    387          else:
    388              for r in self.cssRules[index:]:
    389                  if isinstance(r, cssutils.css.CSSCharsetRule) or \
    390                     isinstance(r, cssutils.css.CSSImportRule) or \
    391                     isinstance(r, cssutils.css.CSSNamespaceRule):
    392                      self._log.error(
    393                          u'CSSStylesheet: Found @charset, @import or @namespace before index %s.' %
    394                          index,
    395                          error=xml.dom.HierarchyRequestErr)
    396                      return
    397              self.cssRules.insert(index, rule)
    398              rule.parentStyleSheet = self
    399
    400          return index
    401
    402
    403 -    def addRule(self, rule):
    404          "DEPRECATED, use appendRule instead"
    405          import warnings
    406          warnings.warn(
    407              '``addRule`` is deprecated: Use ``insertRule(rule)``.',
    408              DeprecationWarning)
    409          return self.insertRule(rule)
    410
    411
    412 -    def _getsetOwnerRuleDummy(self):
    413          """
    414          NOT IMPLEMENTED YET
    415          If this style sheet comes from an @import rule, the ownerRule
    416          attribute will contain the CSSImportRule. In that case, the
    417          ownerNode attribute in the StyleSheet interface will be null. If
    418          the style sheet comes from an element or a processing instruction,
    419          the ownerRule attribute will be null and the ownerNode attribute
    420          will contain the Node.
    421          """
    422          raise NotImplementedError()
    423
    424      ownerRule = property(_getsetOwnerRuleDummy, _getsetOwnerRuleDummy,
    425                           doc="(DOM attribute) NOT IMPLEMENTED YET")
    426
    427 -    def replaceUrls(self, replacer):
    428          """
    429          **EXPERIMENTAL**
    430
    431          utility function to replace all url(urlstring) values in
    432          CSSImportRules and CSSStyleDeclaration objects.
    433
    434          replacer must be a function which is called with a single
    435          argument ``urlstring`` which is the current value of url()
    436          excluding "url(" and ")". It still may have surrounding single or
    437          double quotes
    438          """
    439          for importrule in [
    440              r for r in self.cssRules if hasattr(r, 'href')]:
    441              importrule.href = replacer(importrule.href)
    442
    443          def setProperty(v):
    444              if v.CSS_PRIMITIVE_VALUE == v.cssValueType and\
    445                 v.CSS_URI == v.primitiveType:
    446                      v.setStringValue(v.CSS_URI,
    447                                       replacer(v.getStringValue()))
    448
    449          def styleDeclarations(base):
    450              "recurive function to find all CSSStyleDeclarations"
    451              styles = []
    452              if hasattr(base, 'cssRules'):
    453                  for rule in base.cssRules:
    454                      styles.extend(styleDeclarations(rule))
    455              elif hasattr(base, 'style'):
    456                  styles.append(base.style)
    457              return styles
    458
    459          for style in styleDeclarations(self):
    460              snpl = [x for x in style.seq
    461                     if isinstance(x, cssutils.css.SameNamePropertyList)]
    462              for pl in snpl:
    463                  for p in pl:
    464                      v = p.cssValue
    465                      if v.CSS_VALUE_LIST == v.cssValueType:
    466                          for item in v:
    467                              setProperty(item)
    468                      elif v.CSS_PRIMITIVE_VALUE == v.cssValueType:
    469                          setProperty(v)
    470
    471 -    def setSerializer(self, cssserializer):
    472          """
    473          Sets Serializer used for output of this stylesheet
    474          """
    475          if isinstance(cssserializer, cssutils.CSSSerializer):
    476              cssutils.ser = cssserializer
    477          else:
    478              raise ValueError(u'Serializer must be an instance of cssutils.CSSSerializer.')
    479
    480 -    def setSerializerPref(self, pref, value):
    481          """
    482          Sets Preference of CSSSerializer used for output of this
    483          stylesheet. See cssutils.serialize.Preferences for possible
    484          preferences to be set.
    485          """
    486          cssutils.ser.prefs.__setattr__(pref, value)
    487
    488 -    def __repr__(self):
    489          return "cssutils.css.%s(href=%r, title=%r)" % (
    490                  self.__class__.__name__, self.href, self.title)
    491
    492 -    def __str__(self):
    493          return "<cssutils.css.%s object title=%r href=%r at 0x%x>" % (
    494                  self.__class__.__name__, self.title, self.href, id(self))
    495
    496
    497  if __name__ == '__main__':
    498      print "CSSStyleSheet"
    499      c = CSSStyleSheet(href=None,
    500                          #
    501                          media="all",
    502                          title="test",
    503                          disabled=None,
    504                          ownerNode=None,
    505                          parentStyleSheet=None)
    506      c.cssText = u'''@\\namespace n1 "utf-8";
    507         |a[n1|b], n2|c {}
    508      '''
    509      print c.cssText
    510  ##    print "title: ", c.title
    511  ##    print "type: ", c.type
    512  ##    print c.cssRules
    513  ##    c.addRule(cssutils.css.CSSImportRule('x'))
    514