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