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