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', 'SameNamePropertyList']
     52  __docformat__ = 'restructuredtext'
     53  __author__ = '$LastChangedBy: cthedot $'
     54  __date__ = '$LastChangedDate: 2007-08-29 13:51:12 +0200 (Mi, 29 Aug 2007) $'
     55  __version__ = '$LastChangedRevision: 296 $'
     56
     57  import xml.dom
     58
     59  import cssutils
     60  from cssproperties import CSS2Properties
     61  from property import _Property as Property
     62
     63 -class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base):
     64      """
     65      The CSSStyleDeclaration class represents a single CSS declaration
     66      block. This class may be used to determine the style properties
     67      currently set in a block or to set style properties explicitly
     68      within the block.
     69
     70      While an implementation may not recognize all CSS properties within
     71      a CSS declaration block, it is expected to provide access to all
     72      specified properties in the style sheet through the
     73      CSSStyleDeclaration interface.
     74      Furthermore, implementations that support a specific level of CSS
     75      should correctly handle CSS shorthand properties for that level. For
     76      a further discussion of shorthand properties, see the CSS2Properties
     77      interface.
     78
     79      Additionally the CSS2Properties interface is implemented.
     80
     81      Properties
     82      ==========
     83      cssText: of type DOMString
     84          The parsable textual representation of the declaration block
     85          (excluding the surrounding curly braces). Setting this attribute
     86          will result in the parsing of the new value and resetting of the
     87          properties in the declaration block. It also allows the insertion
     88          of additional properties and their values into the block.
     89      length: of type unsigned long, readonly
     90          The number of properties that have been explicitly set in this
     91          declaration block. The range of valid indices is 0 to length-1
     92          inclusive.
     93      parentRule: of type CSSRule, readonly
     94          The CSS rule that contains this declaration block or None if this
     95          CSSStyleDeclaration is not attached to a CSSRule.
     96      seq: a list (cssutils)
     97          All parts of this style declaration including CSSComments
     98
     99      $css2propertyname
    100          All properties defined in the CSS2Properties class are available
    101          as direct properties of CSSStyleDeclaration with their respective
    102          DOM name, so e.g. ``fontStyle`` for property 'font-style'.
    103
    104          These may be used as::
    105
    106              >>> style = CSSStyleDeclaration(cssText='color: red')
    107              >>> style.color = 'green'
    108              >>> print style.color
    109              green
    110              >>> del style.color
    111              >>> print style.color # print empty string
    112
    113      Format
    114      ======
    115      [Property: Value;]* Property: Value?
    116      """
    117 -    def __init__(self, parentRule=None, cssText=u'', readonly=False):
    118          """
    119          parentRule
    120              The CSS rule that contains this declaration block or
    121              None if this CSSStyleDeclaration is not attached to a CSSRule.
    122          readonly
    123              defaults to False
    124          """
    125          super(CSSStyleDeclaration, self).__init__()
    126
    127          self.valid = True
    128
    129          self.seq = []
    130          self.parentRule = parentRule
    131          self.cssText = cssText
    132          self._readonly = readonly
    133
    134
    135 -    def __setattr__(self, n, v):
    136          """
    137          Prevent setting of unknown properties on CSSStyleDeclaration
    138          which would not work anyway. For these
    139          ``CSSStyleDeclaration.setProperty`` MUST be called explicitly!
    140
    141          TODO:
    142              implementation of known is not really nice, any alternative?
    143          """
    144          known = ['_tokenizer', '_log', '_ttypes',
    145                   'valid', 'seq', 'parentRule', '_parentRule', 'cssText',
    146                   '_readonly']
    147          known.extend(CSS2Properties._properties)
    148          if n in known:
    149              super(CSSStyleDeclaration, self).__setattr__(n, v)
    150          else:
    151              raise AttributeError(
    152                  'Unknown CSS Property, ``CSSStyleDeclaration.setProperty("%s")`` MUST be used.'
    153                  % n)
    154
    155 -    def __iter__(self):
    156          "CSSStyleDeclaration is iterable"
    157          return CSSStyleDeclaration.__items(self)
    158
    159 -    def __items(self):
    160          """
    161          the iterator
    162
    163          returns in contrast to calling item(index) property objects
    164          """
    165          props = []
    166          for x in self.seq:
    167              if isinstance(x, SameNamePropertyList):
    168                  for y in x:
    169                      props.append(y)
    170          le = len(props)
    171          for i in range (0, le):
    172              yield props[i]
    173
    174      # overwritten accessor functions for CSS2Properties' properties
    175 -    def _getP(self, CSSName):
    176          """
    177          (DOM CSS2Properties)
    178          Overwritten here and effectively the same as
    179          ``self.getPropertyValue(CSSname)``.
    180
    181          Parameter is in CSSname format ('font-style'), see CSS2Properties.
    182
    183          Example::
    184
    185              >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
    186              >>> print style.fontStyle
    187              italic
    188
    189          """
    190          return self.getPropertyValue(CSSName)
    191
    192 -    def _setP(self, CSSName, value):
    193          """
    194          (DOM CSS2Properties)
    195          Overwritten here and effectively the same as
    196          ``self.setProperty(CSSname, value)``.
    197
    198          Only known CSS2Properties may be set this way, otherwise an
    199          AttributeError is raised.
    200          For these unknown properties ``setPropertyValue(CSSname, value)``
    201          has to be called explicitly.
    202          Also setting the priority of properties needs to be done with a
    203          call like ``setPropertyValue(CSSname, value, priority)``.
    204
    205          Example::
    206
    207              >>> style = CSSStyleDeclaration()
    208              >>> style.fontStyle = 'italic'
    209              >>> # or
    210              >>> style.setProperty('font-style', 'italic', '!important')
    211
    212          """
    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
    237 -    def _getCssText(self):
    238          """
    239          returns serialized property cssText
    240          """
    241          return cssutils.ser.do_css_CSSStyleDeclaration(self)
    242
    243 -    def _setCssText(self, cssText):
    244          """
    245          Setting this attribute will result in the parsing of the new value
    246          and resetting of all the properties in the declaration block
    247          including the removal or addition of properties.
    248
    249          DOMException on setting
    250
    251          - NO_MODIFICATION_ALLOWED_ERR: (self)
    252            Raised if this declaration is readonly or a property is readonly.
    253          - SYNTAX_ERR: (self)
    254            Raised if the specified CSS string value has a syntax error and
    255            is unparsable.
    256          """
    257          def ignoreuptopropend(i, tokens):
    258              "returns position of ending ;"
    259              ignoredtokens, endi = self._tokensupto(
    260                      tokens[i:], propertypriorityendonly=True)
    261              if ignoredtokens:
    262                  ignored = ''.join([x.value for x in ignoredtokens])
    263                  self._log.error(u'CSSStyleDeclaration: Ignored: "%s".' %
    264                            self._valuestr(ignoredtokens), t)
    265              return endi + 1
    266
    267          self._checkReadonly()
    268          tokens = self._tokenize(cssText)
    269
    270          newseq = []
    271          i, imax = 0, len(tokens)
    272          while i < imax:
    273              t = tokens[i]
    274              if self._ttypes.S == t.type: # ignore
    275                  pass
    276              elif self._ttypes.COMMENT == t.type: # just add
    277                  newseq.append(cssutils.css.CSSComment(t))
    278              else:
    279                  # name upto ":" (or ; -> error)
    280                  nametokens, endi = self._tokensupto(
    281                      tokens[i:], propertynameendonly=True)
    282                  i += endi
    283
    284                  shouldbecolon = nametokens.pop()
    285                  if shouldbecolon.value == u':': # OK: exclude ending :
    286                      i += 1
    287                  elif shouldbecolon.value == u';': # ERROR: premature ;
    288                      self._log.error(
    289                          u'CSSStyleDeclaration: Incomplete Property starting here: %s.' %
    290                          self._valuestr(tokens[i-1:]), t)
    291                      i += 1 # exclude ending :
    292                      continue
    293                  else: # ERROR: no :
    294                      self._log.error(
    295                          u'CSSStyleDeclaration: No Propertyname and/or ":" found: %s.' %
    296                                self._valuestr(tokens[i:]), t)
    297                      i += ignoreuptopropend(i, tokens)
    298                      continue
    299
    300                  for x in nametokens:
    301                      if x.type == self._ttypes.IDENT:
    302                          break
    303                  else: # ERROR: no name
    304                      self._log.error(
    305                          u'CSSStyleDeclaration: No Propertyname found: %s.'
    306                          % self._valuestr(tokens[i-1:]), t)
    307                      i += ignoreuptopropend(i, tokens)
    308                      continue
    309
    310                  # value upto ";" or "!important" or end
    311                  valuetokens, endi = self._tokensupto(
    312                      tokens[i:], propertyvalueendonly=True)
    313                  i += endi
    314                  if valuetokens and \
    315                     valuetokens[-1].type == self._ttypes.SEMICOLON:
    316                      del valuetokens[-1] # exclude ending ;
    317                      prioritytokens = None
    318                  elif valuetokens and \
    319                       valuetokens[-1].type == self._ttypes.IMPORTANT_SYM:
    320                      del valuetokens[-1] # exclude !important
    321
    322                      # priority upto ; or end
    323                      prioritytokens, endi = self._tokensupto(
    324                              tokens[i:], propertypriorityendonly=True)
    325                      i += endi
    326
    327                      if prioritytokens and prioritytokens[-1].type == \
    328                         self._ttypes.SEMICOLON:
    329                          del prioritytokens[-1] # exclude ending ;
    330                  elif not valuetokens:
    331                      self._log.error(u'CSSStyleDeclaration: No property value: %s'
    332                                % self._valuestr(cssText))
    333                      i += ignoreuptopropend(i, tokens)
    334                      continue
    335                  else:
    336                      prioritytokens = None
    337
    338                  self.setProperty(nametokens, valuetokens, prioritytokens,
    339                                   overwrite=False, _seq=newseq)
    340              i += 1
    341
    342          self.seq = newseq
    343
    344      cssText = property(_getCssText, _setCssText,
    345          doc="(DOM) The parsable textual representation of the declaration\
    346          block excluding the surrounding curly braces.")
    347
    348 -    def getCssText(self, separator=None):
    349          """
    350          returns serialized property cssText, each property separated by
    351          given ``separator`` which may e.g. be u'' to be able to use
    352          cssText directly in an HTML style attribute. ";" is always part of
    353          each property (except the last one) and can **not** be set with
    354          separator!
    355          """
    356          return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
    357
    358 -    def _getLength(self):
    359          return len([x for x in self.seq if isinstance(x, SameNamePropertyList)])
    360
    361      length = property(_getLength,
    362          doc="(DOM) the number of properties that have been explicitly set\
    363          in this declaration block. The range of valid indices is 0 to\
    364          length-1 inclusive.")
    365
    366 -    def _getParentRule(self):
    367          return self._parentRule
    368
    369 -    def _setParentRule(self, parentRule):
    370          self._parentRule = parentRule
    371
    372      parentRule = property(_getParentRule, _setParentRule,
    373          doc="(DOM) The CSS rule that contains this declaration block or\
    374          None if this CSSStyleDeclaration is not attached to a CSSRule.")
    375
    376 -    def getPropertyCSSValue(self, name):
    377          """
    378          (DOM)
    379          Used to retrieve the object representation of the value of a CSS
    380          property if it has been explicitly set within this declaration
    381          block. This method returns None if the property is a shorthand
    382          property. Shorthand property values can only be accessed and
    383          modified as strings, using the getPropertyValue and setProperty
    384          methods.
    385
    386          name
    387              of the CSS property
    388
    389              The name will be normalized (lowercase, no simple escapes) so
    390              "color", "COLOR" or "C\olor" are all equivalent
    391
    392          returns CSSValue, the value of the property if it has been
    393          explicitly set for this declaration block. Returns None if the
    394          property has not been set.
    395
    396          for more on shorthand properties see
    397              http://www.dustindiaz.com/css-shorthand/
    398          """
    399          SHORTHAND = [
    400              u'background',
    401              u'border',
    402              u'border-left', u'border-right',
    403              u'border-top', u'border-bottom',
    404              u'border-color', u'border-style', u'border-width',
    405              u'cue',
    406              u'font',
    407              u'list-style',
    408              u'margin',
    409              u'outline',
    410              u'padding',
    411              u'pause']
    412
    413          normalname = self._normalize(name)
    414
    415          if normalname in SHORTHAND:
    416              self._log.debug(
    417                  u'CSSValue for shorthand property "%s" should be None, this may be implemented later.' %
    418                  normalname, neverraise=True)
    419
    420          for pl in self.seq:
    421              if isinstance(pl, SameNamePropertyList) and \
    422                 pl.name == normalname:
    423                  return pl[pl._currentIndex()].cssValue
    424
    425 -    def getPropertyValue(self, name):
    426          """
    427          (DOM)
    428          Used to retrieve the value of a CSS property if it has been
    429          explicitly set within this declaration block.
    430
    431          name
    432              of the CSS property
    433
    434              The name will be normalized (lowercase, no simple escapes) so
    435              "color", "COLOR" or "C\olor" are all equivalent
    436
    437          returns the value of the property if it has been explicitly set
    438          for this declaration block. Returns the empty string if the
    439          property has not been set.
    440          """
    441          normalname = self._normalize(name)
    442
    443          for pl in self.seq:
    444              if isinstance(pl, SameNamePropertyList) and \
    445                 pl.name == normalname:
    446                  return pl[pl._currentIndex()].cssValue._value
    447          return u''
    448
    449 -    def getPropertyPriority(self, name):
    450          """
    451          (DOM)
    452          Used to retrieve the priority of a CSS property (e.g. the
    453          "important" qualifier) if the property has been explicitly set in
    454          this declaration block.
    455
    456          name
    457              of the CSS property
    458
    459              The name will be normalized (lowercase, no simple escapes) so
    460              "color", "COLOR" or "C\olor" are all equivalent
    461
    462          returns a string representing the priority (e.g. "important") if
    463          one exists. The empty string if none exists.
    464          """
    465          normalname = self._normalize(name)
    466
    467          for pl in self.seq:
    468              if isinstance(pl, SameNamePropertyList) and \
    469                 pl.name == normalname:
    470                  return pl[pl._currentIndex()].priority
    471          return u''
    472
    473 -    def getSameNamePropertyList(self, name):
    474          """
    475          (cssutils) EXPERIMENTAL
    476          Used to retrieve all properties set with this name. For cases where
    477          a property is set multiple times with different values or
    478          priorities for different UAs::
    479
    480              background: url(1.gif) fixed;
    481              background: url(2.gif) scroll;
    482
    483          name
    484              of the CSS property
    485
    486              The name will be normalized (lowercase, no simple escapes) so
    487              "color", "COLOR" or "C\olor" are all equivalent
    488
    489          Returns the SameNamePropertyList object if available for the given
    490          property name, else returns ``None``.
    491          """
    492          normalname = self._normalize(name)
    493
    494          for pl in self.seq:
    495              if isinstance(pl, SameNamePropertyList) and \
    496                 pl.name == normalname:
    497                  return pl
    498
    499 -    def item(self, index):
    500          """
    501          (DOM)
    502          Used to retrieve the properties that have been explicitly set in
    503          this declaration block. The order of the properties retrieved using
    504          this method does not have to be the order in which they were set.
    505          This method can be used to iterate over all properties in this
    506          declaration block.
    507
    508          index
    509              of the property to retrieve, negative values behave like
    510              negative indexes on Python lists, so -1 is the last element
    511
    512          returns the name of the property at this ordinal position. The
    513          empty string if no property exists at this position.
    514          """
    515          properties = [x.name for x in self.seq
    516                        if isinstance(x, SameNamePropertyList)]
    517          try:
    518              return properties[index]
    519          except IndexError:
    520              return u''
    521
    522 -    def removeProperty(self, name):
    523          """
    524          (DOM)
    525          Used to remove a CSS property if it has been explicitly set within
    526          this declaration block.
    527
    528          name
    529              of the CSS property to remove
    530
    531              The name will be normalized (lowercase, no simple escapes) so
    532              "color", "COLOR" or "C\olor" are all equivalent
    533
    534          returns the value of the property if it has been explicitly set for
    535          this declaration block. Returns the empty string if the property
    536          has not been set or the property name does not correspond to a
    537          known CSS property
    538
    539          raises DOMException
    540
    541          - NO_MODIFICATION_ALLOWED_ERR: (self)
    542            Raised if this declaration is readonly or the property is
    543            readonly.
    544          """
    545          self._checkReadonly()
    546
    547          normalname = self._normalize(name)
    548
    549          r = u''
    550          for i, pl in enumerate(self.seq):
    551              if isinstance(pl, SameNamePropertyList) and \
    552                 pl.name == normalname:
    553                  r = pl[pl._currentIndex()].cssValue._value
    554                  del self.seq[i]
    555                  break
    556
    557          return r
    558
    559 -    def setProperty(self, name, value, priority=None, overwrite=True,
    560                      _seq=None):
    561          """
    562          (DOM)
    563          Used to set a property value and priority within this declaration
    564          block.
    565
    566          name
    567              of the CSS property to set
    568              (in W3C DOM the parameter is called "propertyName")
    569              will be normalized in the Property
    570
    571          value
    572              the new value of the property
    573          priority
    574              the optional priority of the property (e.g. "important")
    575          _seq
    576              used by self._setCssText only as in temp seq
    577
    578          DOMException on setting
    579
    580          - SYNTAX_ERR: (self)
    581            Raised if the specified value has a syntax error and is
    582            unparsable.
    583          - NO_MODIFICATION_ALLOWED_ERR: (self)
    584            Raised if this declaration is readonly or the property is
    585            readonly.
    586          """
    587          self._checkReadonly()
    588
    589          if _seq is None: # seq to update
    590              _seq = self.seq
    591
    592          newp = Property(name, value, priority)
    593          if newp.name and newp.cssValue:
    594              newnormalname = newp.normalname
    595
    596              # index of pl to exchange or append to, maybe
    597              index = -1
    598              for i, pl in enumerate(_seq):
    599                  if isinstance(pl, SameNamePropertyList) and \
    600                     pl.name == newnormalname:
    601                      index = i
    602                      break
    603
    604              if index == -1:
    605                  # not yet in _seq -> append
    606                  newpl = SameNamePropertyList(newnormalname)
    607                  newpl.append(newp)
    608                  _seq.append(newpl)
    609              else:
    610                  if overwrite:
    611                      # empty proplist and add new property
    612                      del _seq[index][:]
    613                      _seq[index].append(newp)
    614                  else:
    615                      # append to pl
    616                      _seq[index].append(newp)
    617
    618 -    def __repr__(self):
    619          return "cssutils.css.%s()" % (
    620                  self.__class__.__name__)
    621
    622 -    def __str__(self):
    623          return "<cssutils.css.%s object length=%r at 0x%x>" % (
    624                  self.__class__.__name__, self.length, id(self))
    625
    626
    627 -class SameNamePropertyList(list):
    628      """
    629      (cssutils) EXPERIMENTAL
    630
    631      A list of properties with the same normalname. Used for equivelant
    632      properties like color, c\olor, co\lor.
    633      To get the actual Property (latest with the highest priority) use
    634      SameNamePropertyList[SameNamePropertyList._currentIndex()].
    635
    636      Properties
    637      ==========
    638      name
    639          normalized Property name (e.g. ``color``)
    640
    641          see ``token.Token`` for details on "normalized"
    642
    643      Useful only if all similar named properties should be kept after
    644      parsing. See ``Serializer.prefs.keepAllProperties``
    645      """
    646 -    def __init__(self, name):
    647          self.name = cssutils.util.Base._normalize(name)
    648
    649 -    def _currentIndex(self):
    650          """
    651          returns index of current value of this property
    652          used by serializer and getValue and getPriority
    653          """
    654          importants = [i for (i, p) in enumerate(self)
    655                        if p.priority]
    656          if importants:
    657              return importants[-1]
    658          else:
    659              normals = [i for (i, p) in enumerate(self)
    660                        if not p.priority]
    661              if normals:
    662                  return normals[-1]
    663
    664 -    def __repr__(self):
    665          return "cssutils.css.%s(name=%r)" % (
    666                  self.__class__.__name__, self.name)
    667
    668 -    def __str__(self):
    669          return "<cssutils.css.%s object name=%r at 0x%x>" % (
    670                  self.__class__.__name__, self.name, id(self))
    671
    672
    673  if __name__ == '__main__':
    674      pass
    675