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