Package cssutils :: Package css :: Module cssstyledeclaration |
[hide private]
[frames] | no frames]

# Source Code for Module cssutils.css.cssstyledeclaration

      1  """CSSStyleDeclaration implements DOM Level 2 CSS CSSStyleDeclaration and
      2  inherits CSS2Properties
      3
      4  see
      5      http://www.w3.org/TR/1998/REC-CSS2-19980512/syndata.html#parsing-errors
      6
      7  Unknown properties
      8  ------------------
      9  User agents must ignore a declaration with an unknown property.
     10  For example, if the style sheet is::
     11
     12      H1 { color: red; rotation: 70minutes }
     13
     14  the user agent will treat this as if the style sheet had been::
     15
     16      H1 { color: red }
     17
     18  Cssutils gives a WARNING about an unknown CSS2 Property "rotation" but
     19  keeps any property (if syntactical correct).
     20
     21  Illegal values
     22  --------------
     23  User agents must ignore a declaration with an illegal value. For example::
     24
     25      IMG { float: left }       /* correct CSS2 */
     26      IMG { float: left here }  /* "here" is not a value of 'float' */
     27      IMG { background: "red" } /* keywords cannot be quoted in CSS2 */
     28      IMG { border-width: 3 }   /* a unit must be specified for length values */
     29
     30  A CSS2 parser would honor the first rule and ignore the rest, as if the
     31  style sheet had been::
     32
     33      IMG { float: left }
     34      IMG { }
     35      IMG { }
     36      IMG { }
     37
     38  Cssutils again will issue WARNING about invalid CSS2 property values.
     39
     40  TODO:
     41      This interface is also used to provide a read-only access to the
     42      computed values of an element. See also the ViewCSS interface.
     43
     44      - return computed values and not literal values
     45      - simplify unit pairs/triples/quadruples
     46        2px 2px 2px 2px -> 2px for border/padding...
     47      - normalize compound properties like:
     48        background: no-repeat left url()  #fff
     49        -> background: #fff url() no-repeat left
     50  """
     51  __all__ = ['CSSStyleDeclaration', 'Property']
     52  __docformat__ = 'restructuredtext'
     53  __author__ = '$LastChangedBy: cthedot $'
     54  __date__ = '$LastChangedDate: 2007-11-03 22:43:18 +0100 (Sa, 03 Nov 2007) $'
     55  __version__ = '$LastChangedRevision: 631 $'
     56
     57  import xml.dom
     58  import cssutils
     59  from cssproperties import CSS2Properties
     60  from property import Property
     61
     62 -class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base):
     63      """
     64      The CSSStyleDeclaration class represents a single CSS declaration
     65      block. This class may be used to determine the style properties
     66      currently set in a block or to set style properties explicitly
     67      within the block.
     68
     69      While an implementation may not recognize all CSS properties within
     70      a CSS declaration block, it is expected to provide access to all
     71      specified properties in the style sheet through the
     72      CSSStyleDeclaration interface.
     73      Furthermore, implementations that support a specific level of CSS
     74      should correctly handle CSS shorthand properties for that level. For
     75      a further discussion of shorthand properties, see the CSS2Properties
     76      interface.
     77
     78      Additionally the CSS2Properties interface is implemented.
     79
     80      Properties
     81      ==========
     82      cssText: of type DOMString
     83          The parsable textual representation of the declaration block
     84          (excluding the surrounding curly braces). Setting this attribute
     85          will result in the parsing of the new value and resetting of the
     86          properties in the declaration block. It also allows the insertion
     87          of additional properties and their values into the block.
     88      length: of type unsigned long, readonly
     89          The number of properties that have been explicitly set in this
     90          declaration block. The range of valid indices is 0 to length-1
     91          inclusive.
     92      parentRule: of type CSSRule, readonly
     93          The CSS rule that contains this declaration block or None if this
     94          CSSStyleDeclaration is not attached to a CSSRule.
     95      seq: a list (cssutils)
     96          All parts of this style declaration including CSSComments
     97      valid
     98          if this declaration is valid, currently to CSS 2.1 (?)
     99      wellformed
    100          if this declaration is syntactically ok
    101
    102      $css2propertyname
    103          All properties defined in the CSS2Properties class are available
    104          as direct properties of CSSStyleDeclaration with their respective
    105          DOM name, so e.g. ``fontStyle`` for property 'font-style'.
    106
    107          These may be used as::
    108
    109              >>> style = CSSStyleDeclaration(cssText='color: red')
    110              >>> style.color = 'green'
    111              >>> print style.color
    112              green
    113              >>> del style.color
    114              >>> print style.color # print empty string
    115
    116      Format
    117      ======
    118      [Property: Value;]* Property: Value?
    119      """
    120 -    def __init__(self, parentRule=None, cssText=u'', readonly=False):
    121          """
    122          parentRule
    123              The CSS rule that contains this declaration block or
    124              None if this CSSStyleDeclaration is not attached to a CSSRule.
    125          readonly
    126              defaults to False
    127          """
    128          super(CSSStyleDeclaration, self).__init__()
    129          self.valid = False
    130          self.wellformed = False
    131          self.seq = []
    132          self.parentRule = parentRule
    133          self.cssText = cssText
    134          self._readonly = readonly
    135
    136 -    def __setattr__(self, n, v):
    137          """
    138          Prevent setting of unknown properties on CSSStyleDeclaration
    139          which would not work anyway. For these
    140          ``CSSStyleDeclaration.setProperty`` MUST be called explicitly!
    141
    142          TODO:
    143              implementation of known is not really nice, any alternative?
    144          """
    145          known = ['_tokenizer', '_log', '_ttypes',
    146                   'valid', 'wellformed',
    147                   'seq', 'parentRule', '_parentRule', 'cssText',
    148                   '_readonly']
    149          known.extend(CSS2Properties._properties)
    150          if n in known:
    151              super(CSSStyleDeclaration, self).__setattr__(n, v)
    152          else:
    153              raise AttributeError(
    154                  'Unknown CSS Property, ``CSSStyleDeclaration.setProperty("%s")`` MUST be used.'
    155                  % n)
    156
    157 -    def __iter__(self):
    158          "CSSStyleDeclaration is iterable, see __items()"
    159          return CSSStyleDeclaration.__items(self)
    160
    161 -    def __items(self):
    162          """
    163          the iterator
    164
    165          returns in contrast to calling item(index) all property objects so
    166          effectively the same as ``getProperties(all=True)``
    167          """
    168          properties = self.getProperties(all=True)
    169          for property in properties:
    170              yield property
    171
    172      # overwritten accessor functions for CSS2Properties' properties
    173 -    def _getP(self, CSSName):
    174          """
    175          (DOM CSS2Properties)
    176          Overwritten here and effectively the same as
    177          ``self.getPropertyValue(CSSname)``.
    178
    179          Parameter is in CSSname format ('font-style'), see CSS2Properties.
    180
    181          Example::
    182
    183              >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
    184              >>> print style.fontStyle
    185              italic
    186
    187          """
    188          return self.getPropertyValue(CSSName)
    189
    190 -    def _setP(self, CSSName, value):
    191          """
    192          (DOM CSS2Properties)
    193          Overwritten here and effectively the same as
    194          ``self.setProperty(CSSname, value)``.
    195
    196          Only known CSS2Properties may be set this way, otherwise an
    197          AttributeError is raised.
    198          For these unknown properties ``setPropertyValue(CSSname, value)``
    199          has to be called explicitly.
    200          Also setting the priority of properties needs to be done with a
    201          call like ``setPropertyValue(CSSname, value, priority)``.
    202
    203          Example::
    204
    205              >>> style = CSSStyleDeclaration()
    206              >>> style.fontStyle = 'italic'
    207              >>> # or
    208              >>> style.setProperty('font-style', 'italic', '!important')
    209
    210          """
    211          self.setProperty(CSSName, value)
    212          # TODO:
    213  #        if 'background-image' == CSSName:
    214  #            for p in self._properties():
    215  #                if p.name == 'background':
    216  #                    print p
    217  #            self.setProperty(CSSName, value)
    218  #        else:
    219  #            self.setProperty(CSSName, value)
    220
    221 -    def _delP(self, CSSName):
    222          """
    223          (cssutils only)
    224          Overwritten here and effectively the same as
    225          ``self.removeProperty(CSSname)``.
    226
    227          Example::
    228
    229              >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
    230              >>> del style.fontStyle
    231              >>> print style.fontStyle # prints u''
    232
    233          """
    234          self.removeProperty(CSSName)
    235
    236 -    def _getCssText(self):
    237          """
    238          returns serialized property cssText
    239          """
    240          return cssutils.ser.do_css_CSSStyleDeclaration(self)
    241
    242 -    def _setCssText(self, cssText):
    243          """
    244          Setting this attribute will result in the parsing of the new value
    245          and resetting of all the properties in the declaration block
    246          including the removal or addition of properties.
    247
    248          DOMException on setting
    249
    250          - NO_MODIFICATION_ALLOWED_ERR: (self)
    251            Raised if this declaration is readonly or a property is readonly.
    252          - SYNTAX_ERR: (self)
    253            Raised if the specified CSS string value has a syntax error and
    254            is unparsable.
    255          """
    256          self._checkReadonly()
    257          tokenizer = self._tokenize2(cssText)
    258
    259          # for closures: must be a mutable
    260          new = {'valid': True,
    261                 'wellformed': True,
    262                 'char': None
    263          }
    264          def ident(expected, seq, token, tokenizer=None):
    265              # a property
    266              if new['char']:
    267                  # maybe an IE hack?
    268                  token = (token[0], u'%s%s' % (new['char'], token[1]),
    269                           token[2], token[3])
    270
    271              tokens = self._tokensupto2(tokenizer, starttoken=token,
    272                                         semicolon=True)
    273              if self._tokenvalue(tokens[-1]) == u';':
    274                  tokens.pop()
    275              property = Property()
    276              property.cssText = tokens
    277              if property.wellformed:
    278                  seq.append(property)
    279              else:
    280                  self._log.error(u'CSSStyleDeclaration: Syntax Error in Property: %s'
    281                                  % self._valuestr(tokens))
    282
    283              new['char'] = None
    284              return expected
    285
    286          def char(expected, seq, token, tokenizer=None):
    287              # maybe an IE hack?
    288              new['valid'] = False # wellformed is set later
    289              self._log.error(u'CSSStyleDeclaration: Unexpected CHAR.', token)
    290              c = self._tokenvalue(token)
    291              if c in u'$':
    292                  self._log.info(u'Trying to use (invalid) CHAR %r in Property name' %
    293                                    c)
    294                  new['char'] = c
    295
    296          # [Property: Value;]* Property: Value?
    297          newseq = []
    298          wellformed, expected = self._parse(expected=None,
    299              seq=newseq, tokenizer=tokenizer,
    300              productions={'IDENT': ident, 'CHAR': char})
    301          valid = new['valid']
    302
    303          # wellformed set by parse
    304          # post conditions
    305          if new['char']:
    306              valid =wellformed = False
    307              self._log.error(u'Could not use unexpected CHAR %r' % new['char'])
    308
    309          if wellformed:
    310              self.seq = newseq
    311              self.wellformed = wellformed
    312              self.valid = valid
    313
    314      cssText = property(_getCssText, _setCssText,
    315          doc="(DOM) A parsable textual representation of the declaration\
    316          block excluding the surrounding curly braces.")
    317
    318 -    def getCssText(self, separator=None):
    319          """
    320          returns serialized property cssText, each property separated by
    321          given ``separator`` which may e.g. be u'' to be able to use
    322          cssText directly in an HTML style attribute. ";" is always part of
    323          each property (except the last one) and can **not** be set with
    324          separator!
    325          """
    326          return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
    327
    328 -    def _getParentRule(self):
    329          return self._parentRule
    330
    331 -    def _setParentRule(self, parentRule):
    332          self._parentRule = parentRule
    333
    334      parentRule = property(_getParentRule, _setParentRule,
    335          doc="(DOM) The CSS rule that contains this declaration block or\
    336          None if this CSSStyleDeclaration is not attached to a CSSRule.")
    337
    338 -    def getProperties(self, name=None, all=False):
    339          """
    340          returns a list of Property objects set in this declaration in order
    341          they have been set e.g. in the original stylesheet
    342
    343          name
    344              optional name of properties which are requested (a filter).
    345              Only properties with this (normalized) name are returned.
    346          all=False
    347              if False (DEFAULT) only the effective properties (the ones set
    348              last) are returned. So if a name is given only one property
    349              is returned.
    350
    351              if True all properties including properties set multiple times with
    352              different values or priorities for different UAs are returned.
    353          """
    354          nname = self._normalize(name)
    355          properties = []
    356          done = set()
    357          for x in reversed(self.seq):
    358              if isinstance(x, Property):
    359                  isname = (bool(nname) == False) or (x.normalname == nname)
    360                  stilltodo = x.normalname not in done
    361                  if isname and stilltodo:
    362                      properties.append(x)
    363                      if not all:
    364                          done.add(x.normalname)
    365
    366          properties.reverse()
    367          return properties
    368
    369 -    def getPropertyCSSValue(self, name, normalize=True):
    370          """
    371          (DOM)
    372          Used to retrieve the object representation of the value of a CSS
    373          property if it has been explicitly set within this declaration
    374          block. This method returns None if the property is a shorthand
    375          property. Shorthand property values can only be accessed and
    376          modified as strings, using the getPropertyValue and setProperty
    377          methods.
    378
    379          name
    380              of the CSS property
    381
    382              The name will be normalized (lowercase, no simple escapes) so
    383              "color", "COLOR" or "C\olor" are all equivalent
    384
    385          returns CSSValue, the value of the property if it has been
    386          explicitly set for this declaration block. Returns None if the
    387          property has not been set.
    388
    389          for more on shorthand properties see
    390              http://www.dustindiaz.com/css-shorthand/
    391          """
    392          nname = self._normalize(name)
    393          if nname in self._SHORTHANDPROPERTIES:
    394              self._log.debug(
    395                  u'CSSValue for shorthand property "%s" should be None, this may be implemented later.' %
    396                  nname, neverraise=True)
    397
    398          properties = self.getProperties(name, all=(not normalize))
    399          for property in reversed(properties):
    400              if normalize and property.normalname == nname:
    401                  return property.cssValue
    402              elif property.name == name:
    403                  return property.cssValue
    404          return None
    405
    406 -    def getPropertyValue(self, name, normalize=True):
    407          """
    408          (DOM)
    409          Used to retrieve the value of a CSS property if it has been
    410          explicitly set within this declaration block.
    411
    412          name
    413              of the CSS property
    414
    415              The name will be normalized (lowercase, no simple escapes) so
    416              "color", "COLOR" or "C\olor" are all equivalent
    417
    418          returns the value of the property if it has been explicitly set
    419          for this declaration block. Returns the empty string if the
    420          property has not been set.
    421          """
    422          nname = self._normalize(name)
    423          properties = self.getProperties(name, all=(not normalize))
    424          for property in reversed(properties):
    425              if normalize and property.normalname == nname:
    426                  return property.value
    427              elif property.name == name:
    428                  return property.value
    429          return u''
    430
    431 -    def getPropertyPriority(self, name, normalize=True):
    432          """
    433          (DOM)
    434          Used to retrieve the priority of a CSS property (e.g. the
    435          "important" qualifier) if the property has been explicitly set in
    436          this declaration block.
    437
    438          name
    439              of the CSS property
    440
    441              The name will be normalized (lowercase, no simple escapes) so
    442              "color", "COLOR" or "C\olor" are all equivalent
    443
    444          returns a string representing the priority (e.g. "important") if
    445          one exists. The empty string if none exists.
    446          """
    447          nname = self._normalize(name)
    448          properties = self.getProperties(name, all=(not normalize))
    449          for property in reversed(properties):
    450              if normalize and property.normalname == nname:
    451                  return property.priority
    452              elif property.name == name:
    453                  return property.priority
    454          return u''
    455
    456 -    def removeProperty(self, name, normalize=True):
    457          """
    458          (DOM)
    459          Used to remove a CSS property if it has been explicitly set within
    460          this declaration block.
    461
    462          name
    463              of the CSS property to remove
    464
    465              The name will be normalized (lowercase, no simple escapes) so
    466              "color", "COLOR" or "C\olor" are all equivalent
    467
    468          returns the value of the property if it has been explicitly set for
    469          this declaration block. Returns the empty string if the property
    470          has not been set or the property name does not correspond to a
    471          known CSS property
    472
    473          raises DOMException
    474
    475          - NO_MODIFICATION_ALLOWED_ERR: (self)
    476            Raised if this declaration is readonly or the property is
    477            readonly.
    478          """
    479          self._checkReadonly()
    480          nname = self._normalize(name)
    481          newseq = []
    482          r = u''
    483          isdone = False
    484          for x in reversed(self.seq):
    485              if isinstance(x, Property):
    486                  if not isdone and normalize and x.normalname == nname:
    487                      r = x.cssValue.cssText
    488                      isdone = True
    489                  elif not isdone and x.name == name:
    490                      r = x.cssValue.cssText
    491                      isdone = True
    492                  else:
    493                      newseq.append(x)
    494              else:
    495                  newseq.append(x)
    496          newseq.reverse()
    497          self.seq = newseq
    498          return r
    499
    500 -    def setProperty(self, name, value, priority=u'', normalize=True):
    501          """
    502          (DOM)
    503          Used to set a property value and priority within this declaration
    504          block.
    505
    506          name
    507              of the CSS property to set (in W3C DOM the parameter is called
    508              "propertyName")
    509
    510              If a property with this name is present it will be reset
    511
    512              The name is normalized if normalize=True
    513
    514          value
    515              the new value of the property
    516          priority
    517              the optional priority of the property (e.g. "important")
    518
    519          DOMException on setting
    520
    521          - SYNTAX_ERR: (self)
    522            Raised if the specified value has a syntax error and is
    523            unparsable.
    524          - NO_MODIFICATION_ALLOWED_ERR: (self)
    525            Raised if this declaration is readonly or the property is
    526            readonly.
    527          """
    528          self._checkReadonly()
    529
    530          newp = Property(name, value, priority)
    531          if not newp.wellformed:
    532              self._log.warn(u'Invalid Property: %s: %s %s'
    533                      % (name, value, priority))
    534          else:
    535              nname = self._normalize(name)
    536              properties = self.getProperties(name, all=(not normalize))
    537              for property in reversed(properties):
    538                  if normalize and property.normalname == nname:
    539                      property.cssValue = newp.cssValue.cssText
    540                      property.priority = newp.priority
    541                      break
    542                  elif property.name == name:
    543                      property.cssValue = newp.cssValue.cssText
    544                      property.priority = newp.priority
    545                      break
    546              else:
    547                  self.seq.append(newp)
    548
    549 -    def __nnames(self):
    550          nnames = set()
    551          for x in self.seq:
    552              if isinstance(x, Property):
    553                  nnames.add(x.normalname)
    554          return nnames
    555
    556 -    def item(self, index):
    557          """
    558          **NOTE**:
    559          Compare to ``for property in declaration`` which works on **all**
    560          properties set in this declaration and not just the effecitve ones.
    561
    562          (DOM)
    563          Used to retrieve the properties that have been explicitly set in
    564          this declaration block. The order of the properties retrieved using
    565          this method does not have to be the order in which they were set.
    566          This method can be used to iterate over all properties in this
    567          declaration block.
    568
    569          index
    570              of the property to retrieve, negative values behave like
    571              negative indexes on Python lists, so -1 is the last element
    572
    573          returns the name of the property at this ordinal position. The
    574          empty string if no property exists at this position.
    575
    576          ATTENTION:
    577          Only properties with a different normalname are counted. If two
    578          properties with the same normalname are present in this declaration
    579          only the last set (and effectively *in style*) is used.
    580
    581          ``item()`` and ``length`` work on the same set here.
    582          """
    583          nnames = self.__nnames()
    584          orderednnames = []
    585          for x in reversed(self.seq):
    586              nname = x.normalname
    587              if isinstance(x, Property) and nname in nnames:
    588                  nnames.remove(nname)
    589                  orderednnames.append(nname)
    590          orderednnames.reverse()
    591          try:
    592              return orderednnames[index]
    593          except IndexError:
    594              return u''
    595
    596 -    def _getLength(self):
    597          return len(self.__nnames())
    598
    599      length = property(_getLength,
    600          doc="(DOM) The number of distince properties that have been explicitly\
    601          in this declaration block. The range of valid indices is 0 to\
    602          length-1 inclusive. These are properties with the same ``normalname``\
    603          only. ``item()`` and ``length`` work on the same set here.")
    604
    605 -    def __repr__(self):
    606          return "cssutils.css.%s()" % (
    607                  self.__class__.__name__)
    608
    609 -    def __str__(self):
    610          return "<cssutils.css.%s object length=%r (all: %r) at 0x%x>" % (
    611                  self.__class__.__name__, self.length,
    612                  len(self.getProperties(all=True)), id(self))
    613