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