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