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

# Source Code for Module cssutils.css.cssvalue

      1  """CSSValue related classes
      2
      3  - CSSValue implements DOM Level 2 CSS CSSValue
      4  - CSSPrimitiveValue implements DOM Level 2 CSS CSSPrimitiveValue
      5  - CSSValueList implements DOM Level 2 CSS CSSValueList
      6
      7  """
      8  __all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList']
      9  __docformat__ = 'restructuredtext'
     10  __author__ = '$LastChangedBy: cthedot $'
     11  __date__ = '$LastChangedDate: 2007-09-01 18:44:48 +0200 (Sa, 01 Sep 2007) $'
     12  __version__ = '$LastChangedRevision: 306 $'
     13
     14  import re
     15  import types
     16  import xml.dom
     17  import cssutils
     18  import cssproperties
     19  from cssutils.token import Token
     20
     21 -class CSSValue(cssutils.util.Base):
     22      """
     23      The CSSValue interface represents a simple or a complex value.
     24      A CSSValue object only occurs in a context of a CSS property
     25
     26      Properties
     27      ==========
     28      cssText
     29          A string representation of the current value.
     30      cssValueType
     31          A (readonly) code defining the type of the value.
     32
     33      seq: a list (cssutils)
     34          All parts of this style declaration including CSSComments
     35      valid: boolean
     36          if the value is valid at all, False for e.g. color: #1
     37
     38      _value
     39          value without any comments, used by Property to validate
     40      """
     41
     42      CSS_INHERIT = 0
     43      """
     44      The value is inherited and the cssText contains "inherit".
     45      """
     46      CSS_PRIMITIVE_VALUE = 1
     47      """
     48      The value is a primitive value and an instance of the
     49      CSSPrimitiveValue interface can be obtained by using binding-specific
     50      casting methods on this instance of the CSSValue interface.
     51      """
     52      CSS_VALUE_LIST = 2
     53      """
     54      The value is a CSSValue list and an instance of the CSSValueList
     55      interface can be obtained by using binding-specific casting
     56      methods on this instance of the CSSValue interface.
     57      """
     58      CSS_CUSTOM = 3
     59      """
     60      The value is a custom value.
     61      """
     62      _typestrings = ['CSS_INHERIT' , 'CSS_PRIMITIVE_VALUE', 'CSS_VALUE_LIST',
     63                       'CSS_CUSTOM']
     64
     65 -    def __init__(self, cssText=None, readonly=False, _propertyName=None):
     66          """
     67          inits a new CSS Value
     68
     69          cssText
     70              the parsable cssText of the value
     71          readonly
     72              defaults to False
     73          _propertyName
     74              used to validate this value in the context of a property
     75              the name must be normalized: lowercase with no escapes
     76          """
     77          super(CSSValue, self).__init__()
     78
     79          self.seq = []
     80          self.valid = False
     81          self._value = u''
     82          self._linetoken = None # used for line report only
     83
     84          self._propertyName = _propertyName
     85
     86          if cssText is not None: # may be 0
     87              self.cssText = cssText
     88
     89          self._readonly = readonly
     90
     91 -    def _getValue(self):
     92          v = []
     93          for x in self.seq:
     94              if isinstance(x, cssutils.css.CSSComment):
     95                  continue
     96              elif type(x) in types.StringTypes:
     97                  v.append(x)
     98              else: # maybe CSSPrimitiveValue
     99                  v.append(x.cssText)
    100          return u''.join(v).strip()
    101
    102 -    def _setValue(self, value):
    103          "overwritten by CSSValueList!"
    104          self._valueValue = value
    105
    106      _value = property(_getValue, _setValue,
    107                  doc="Actual cssText value of this CSSValue.")
    108
    109 -    def _getCssText(self):
    110          return cssutils.ser.do_css_CSSvalue(self)
    111
    112 -    def _setCssText(self, cssText):
    113          """
    114          Format
    115          ======
    116          ::
    117
    118              expr = value
    119                : term [ operator term ]*
    120                ;
    121              term
    122                : unary_operator?
    123                  [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* |
    124                    ANGLE S* | TIME S* | FREQ S* | function ]
    125                | STRING S* | IDENT S* | URI S* | hexcolor
    126                ;
    127              function
    128                : FUNCTION S* expr ')' S*
    129                ;
    130              /*
    131               * There is a constraint on the color that it must
    132               * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
    133               * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
    134               */
    135              hexcolor
    136                : HASH S*
    137                ;
    138
    139          DOMException on setting
    140
    141          - SYNTAX_ERR: (self)
    142            Raised if the specified CSS string value has a syntax error
    143            (according to the attached property) or is unparsable.
    144          - TODO: INVALID_MODIFICATION_ERR:
    145            Raised if the specified CSS string value represents a different
    146            type of values than the values allowed by the CSS property.
    147          - NO_MODIFICATION_ALLOWED_ERR: (self)
    148            Raised if this value is readonly.
    149          """
    150          def invalidToken(tokens, x):
    151              """
    152              raises SyntaxErr if an INVALID token in tokens
    153
    154              x
    155                  used for error message
    156
    157              returns True if INVALID found, else False
    158              """
    159              for t in tokens:
    160                  if t.type == self._ttypes.INVALID:
    161                      return u'Invalid token found in %s.' % x, t
    162              return False
    163
    164          self._checkReadonly()
    165          tokens = self._tokenize(cssText)
    166          msg = invalidToken(tokens, 'value')
    167
    168          if msg:
    169              self._log.error(
    170                  u'CSSValue: Unknown value syntax: "%s". (%s)' % (
    171                      self._valuestr(cssText), msg))
    172              return
    173
    174          numvalues = numcommas = 0
    175          newseq = []
    176          i, imax = 0, len(tokens)
    177          while i < imax:
    178              t = tokens[i]
    179              if self._ttypes.S == t.type: # add single space
    180                  if not newseq or newseq[-1] in (u', ', u' '):
    181                      # not 1st and no 2 spaces after each other
    182                      # also no space after a comma
    183                      pass
    184                  else:
    185                      newseq.append(u' ')
    186              elif self._ttypes.COMMENT == t.type: # just add
    187                  newseq.append(cssutils.css.CSSComment(t))
    188              elif t.type == self._ttypes.COMMA:
    189                  if newseq and newseq[-1] == u' ':
    190                      # TODO: COMMA is set as ", " and used as this later
    191                      # SHOULD be in serializer...
    192                      # so only a space OR a comma between values
    193                      newseq[-1] = u', '
    194                  else:
    195                      newseq.append(u', ')
    196                  numcommas += 1
    197              elif t.type in (self._ttypes.IDENT,
    198                              self._ttypes.STRING,
    199                              self._ttypes.HASH,
    200                              self._ttypes.NUMBER,
    201                              self._ttypes.PERCENTAGE,
    202                              self._ttypes.DIMENSION,
    203                              self._ttypes.URI):
    204                  newseq.append(t.value)
    205                  numvalues += 1
    206              elif self._ttypes.FUNCTION == t.type:
    207                  _functokens, endi = self._tokensupto(
    208                      tokens, funcendonly=True)
    209                  _func = []
    210                  for _t in _functokens:
    211                      _func.append(_t.value)
    212                  newseq.append(u''.join(_func))
    213                  i += endi #-1
    214                  numvalues += 1
    215  #            elif t.type in (
    216  #                self._ttypes.SEMICOLON, self._ttypes.IMPORTANT_SYM) or\
    217  #                u':' == t.value:
    218  #                self._log.error(u'CSSValue: Syntax error.', t)
    219  #                return
    220              else:
    221                  self._log.error(u'CSSValue: Unknown value: "%s".' % t)
    222
    223              i += 1
    224
    225          if numvalues:
    226              if tokens:
    227                  self._linetoken = tokens[0] # used for line report
    228              self.seq = newseq
    229              self.valid = False
    230              # validate if known
    231              if self._propertyName and\
    232                 self._propertyName in cssproperties.cssvalues:
    233                  if cssproperties.cssvalues[self._propertyName](self._value):
    234                      self.valid = True
    235                  else:
    236                      self._log.warn(
    237                          u'CSSValue: Invalid value for CSS2 property %s: %s' %
    238                          (self._propertyName, self._value),
    239                          self._linetoken, neverraise=True)
    240              else:
    241                  self._log.info(
    242                      u'CSSValue: Unable to validate as no property context set or unknown property: "%s"'
    243                      % self._value, neverraise=True)
    244
    245              if self._value == u'inherit':
    246                  self._cssValueType = CSSValue.CSS_INHERIT
    247                  self.__class__ = CSSValue # reset
    248              elif numvalues == 1:
    249                  self.__class__ = CSSPrimitiveValue
    250              elif numvalues > 1 and numcommas == numvalues - 1:
    251                  # e.g. value for font-family: a, b
    252                  self.__class__ = CSSPrimitiveValue
    253              elif numvalues > 1:
    254                  # separated by S
    255                  self.__class__ = CSSValueList
    256                  self._init() # inits CSSValueList
    257              else:
    258                  self._cssValueType = CSSValue.CSS_CUSTOM
    259                  self.__class__ = CSSValue # reset
    260
    261          else:
    262              self._log.error(
    263                  u'CSSValue: Unknown syntax or no value: "%s".' % self._valuestr(
    264                      cssText).strip())
    265
    266      cssText = property(_getCssText, _setCssText,
    267          doc="A string representation of the current value.")
    268
    269 -    def _getCssValueType(self):
    270          if hasattr(self, '_cssValueType'):
    271              return self._cssValueType
    272
    273      cssValueType = property(_getCssValueType,
    274          doc="A (readonly) code defining the type of the value as defined above.")
    275
    276 -    def _getCssValueTypeString(self):
    277          t = self.cssValueType
    278          if t is not None: # may be 0!
    279              return CSSValue._typestrings[t]
    280          else:
    281              return None
    282
    283      cssValueTypeString = property(_getCssValueTypeString,
    284          doc="cssutils: Name of cssValueType of this CSSValue (readonly).")
    285
    286 -    def __repr__(self):
    287          return "cssutils.css.%s(%r, _propertyName=%r)" % (
    288                  self.__class__.__name__, self.cssText, self._propertyName)
    289
    290 -    def __str__(self):
    291          return "<cssutils.css.%s object cssValueType=%r cssText=%r propname=%r valid=%r at 0x%x>" % (
    292                  self.__class__.__name__, self.cssValueTypeString,
    293                  self.cssText, self._propertyName, self.valid, id(self))
    294
    295
    296 -class CSSPrimitiveValue(CSSValue):
    297      """
    298      represents a single CSS Value.  May be used to determine the value of a
    299      specific style property currently set in a block or to set a specific
    300      style property explicitly within the block. Might be obtained from the
    301      getPropertyCSSValue method of CSSStyleDeclaration.
    302
    303      Conversions are allowed between absolute values (from millimeters to
    304      centimeters, from degrees to radians, and so on) but not between
    305      relative values. (For example, a pixel value cannot be converted to a
    306      centimeter value.) Percentage values can't be converted since they are
    307      relative to the parent value (or another property value). There is one
    308      exception for color percentage values: since a color percentage value
    309      is relative to the range 0-255, a color percentage value can be
    310      converted to a number; (see also the RGBColor interface).
    311      """
    312      # constant: type of this CSSValue class
    313      cssValueType = CSSValue.CSS_PRIMITIVE_VALUE
    314
    315      # An integer indicating which type of unit applies to the value.
    316      CSS_UNKNOWN = 0 # only obtainable via cssText
    317      CSS_NUMBER = 1
    318      CSS_PERCENTAGE = 2
    319      CSS_EMS = 3
    320      CSS_EXS = 4
    321      CSS_PX = 5
    322      CSS_CM = 6
    323      CSS_MM = 7
    324      CSS_IN = 8
    325      CSS_PT = 9
    326      CSS_PC = 10
    327      CSS_DEG = 11
    328      CSS_RAD = 12
    329      CSS_GRAD = 13
    330      CSS_MS = 14
    331      CSS_S = 15
    332      CSS_HZ = 16
    333      CSS_KHZ = 17
    334      CSS_DIMENSION = 18
    335      CSS_STRING = 19
    336      CSS_URI = 20
    337      CSS_IDENT = 21
    338      CSS_ATTR = 22
    339      CSS_COUNTER = 23
    340      CSS_RECT = 24
    341      CSS_RGBCOLOR = 25
    342      # NOT OFFICIAL:
    343      CSS_RGBACOLOR = 26
    344
    345      _floattypes = [CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS,
    346                     CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC,
    347                     CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S,
    348                     CSS_HZ, CSS_KHZ, CSS_DIMENSION
    349                     ]
    350      _stringtypes = [CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI]
    351      _countertypes = [CSS_COUNTER]
    352      _recttypes = [CSS_RECT]
    353      _rbgtypes = [CSS_RGBCOLOR, CSS_RGBACOLOR]
    354
    355      #(String representation for unit types, token type of unit type, detail)
    356      # used to detect primitiveType and for __repr__
    357      _unitinfos = [
    358          ('CSS_UNKNOWN', None, None),
    359          ('CSS_NUMBER', Token.NUMBER, None),
    360          ('CSS_PERCENTAGE', Token.PERCENTAGE, None),
    361          ('CSS_EMS', Token.DIMENSION, 'em'),
    362          ('CSS_EXS', Token.DIMENSION, 'ex'),
    363          ('CSS_PX', Token.DIMENSION, 'px'),
    364          ('CSS_CM', Token.DIMENSION, 'cm'),
    365          ('CSS_MM', Token.DIMENSION, 'mm'),
    366          ('CSS_IN', Token.DIMENSION, 'in'),
    367          ('CSS_PT', Token.DIMENSION, 'pt'),
    368          ('CSS_PC', Token.DIMENSION, 'pc'),
    369          ('CSS_DEG', Token.DIMENSION, 'deg'),
    370          ('CSS_RAD', Token.DIMENSION, 'rad'),
    371          ('CSS_GRAD', Token.DIMENSION, 'grad'),
    372          ('CSS_MS', Token.DIMENSION, 'ms'),
    373          ('CSS_S', Token.DIMENSION, 's'),
    374          ('CSS_HZ', Token.DIMENSION, 'hz'),
    375          ('CSS_KHZ', Token.DIMENSION, 'khz'),
    376          ('CSS_DIMENSION', Token.DIMENSION, None),
    377          ('CSS_STRING', Token.STRING, None),
    378          ('CSS_URI', Token.URI, None),
    379          ('CSS_IDENT', Token.IDENT, None),
    380          ('CSS_ATTR', Token.FUNCTION, 'attr('),
    381          ('CSS_COUNTER', Token.FUNCTION, 'counter('),
    382          ('CSS_RECT', Token.FUNCTION, 'rect('),
    383          ('CSS_RGBCOLOR', Token.FUNCTION, 'rgb('),
    384          ('CSS_RGBACOLOR', Token.FUNCTION, 'rgba('),
    385          ]
    386
    387      _reNumDim = re.compile(ur'^(.*?)([a-z]+|%)$', re.I| re.U|re.X)
    388
    389      # oldtype: newType: converterfunc
    390      _converter = {
    391          # cm <-> mm <-> in, 1 inch is equal to 2.54 centimeters.
    392          # pt <-> pc, the points used by CSS 2.1 are equal to 1/72nd of an inch.
    393          # pc: picas - 1 pica is equal to 12 points
    394          (CSS_CM, CSS_MM): lambda x: x * 10,
    395          (CSS_MM, CSS_CM): lambda x: x / 10,
    396
    397          (CSS_PT, CSS_PC): lambda x: x * 12,
    398          (CSS_PC, CSS_PT): lambda x: x / 12,
    399
    400          (CSS_CM, CSS_IN): lambda x: x / 2.54,
    401          (CSS_IN, CSS_CM): lambda x: x * 2.54,
    402          (CSS_MM, CSS_IN): lambda x: x / 25.4,
    403          (CSS_IN, CSS_MM): lambda x: x * 25.4,
    404
    405          (CSS_IN, CSS_PT): lambda x: x / 72,
    406          (CSS_PT, CSS_IN): lambda x: x * 72,
    407          (CSS_CM, CSS_PT): lambda x: x / 2.54 / 72,
    408          (CSS_PT, CSS_CM): lambda x: x * 72 * 2.54,
    409          (CSS_MM, CSS_PT): lambda x: x / 25.4 / 72,
    410          (CSS_PT, CSS_MM): lambda x: x * 72 * 25.4,
    411
    412          (CSS_IN, CSS_PC): lambda x: x / 72 / 12,
    413          (CSS_PC, CSS_IN): lambda x: x * 12 * 72,
    414          (CSS_CM, CSS_PC): lambda x: x / 2.54 / 72 / 12,
    415          (CSS_PC, CSS_CM): lambda x: x * 12 * 72 * 2.54,
    416          (CSS_MM, CSS_PC): lambda x: x / 25.4 / 72 / 12,
    417          (CSS_PC, CSS_MM): lambda x: x * 12 * 72 * 25.4,
    418
    419          # hz <-> khz
    420          (CSS_KHZ, CSS_HZ): lambda x: x * 1000,
    421          (CSS_HZ, CSS_KHZ): lambda x: x / 1000,
    422          # s <-> ms
    423          (CSS_S, CSS_MS): lambda x: x * 1000,
    424          (CSS_MS, CSS_S): lambda x: x / 1000
    425
    426          # TODO: convert deg <-> rad <-> grad
    427      }
    428
    429 -    def __set_primitiveType(self):
    430          """
    431          primitiveType is readonly but is set lazy if accessed
    432          no value is given as self._value is used
    433          """
    434          primitiveType = self.CSS_UNKNOWN
    435          tokens = self._tokenize(self._value)
    436          try:
    437              t = tokens[0]
    438          except IndexError:
    439              self._log.error(u'CSSPrimitiveValue: No value.')
    440
    441          #if self.valid == False:
    442          #    primitiveType = CSSPrimitiveValue.CSS_UNKNOWN
    443          alltypes = set([x.type for x in tokens])
    444          commalisttypes = set([Token.COMMA, Token.IDENT, Token.STRING])
    445          if commalisttypes.issubset(alltypes):
    446              # special case: e.g. for font-family: a, b;
    447              primitiveType = CSSPrimitiveValue.CSS_STRING
    448          elif t.type == Token.HASH:
    449              # special case, maybe should be converted to rgb in any case?
    450              primitiveType = CSSPrimitiveValue.CSS_RGBCOLOR
    451          else:
    452              for i, (name, tokentype, search) in enumerate(CSSPrimitiveValue._unitinfos):
    453                  if t.type == tokentype:
    454                      if tokentype == Token.DIMENSION:
    455                          if not search:
    456                              primitiveType = i
    457                              break
    458                          elif re.match(ur'^[^a-z]*(%s)$' % search, t.value):
    459                              primitiveType = i
    460                              break
    461                      elif tokentype == Token.FUNCTION:
    462                          if not search:
    463                              primitiveType = i
    464                              break
    465                          elif t.value.startswith(search):
    466                              primitiveType = i
    467                              break
    468                      else:
    469                          primitiveType = i
    470                          break
    471          self._primitiveType = primitiveType
    472
    473 -    def _getPrimitiveType(self):
    474          if not hasattr(self, '_primitivetype'):
    475              self.__set_primitiveType()
    476          return self._primitiveType
    477
    478      primitiveType = property(_getPrimitiveType,
    479          doc="READONLY: The type of the value as defined by the constants specified above.")
    480
    481 -    def _getPrimitiveTypeString(self):
    482          return CSSPrimitiveValue._unitinfos[self.primitiveType][0]
    483
    484      primitiveTypeString = property(_getPrimitiveTypeString,
    485                                     doc="Name of primitive type of this value.")
    486
    487 -    def _getCSSPrimitiveTypeString(self, type):
    488          "get TypeString by given type which may be unknown, used by setters"
    489          try:
    490              return CSSPrimitiveValue._unitinfos[type][0]
    491          except (IndexError, TypeError):
    492              return u'%r (UNKNOWN TYPE)' % type
    493
    494 -    def __getValDim(self):
    495          "splits self._value in numerical and dimension part"
    496          try:
    497              val, dim = self._reNumDim.findall(self._value)[0]
    498          except IndexError:
    499              val, dim = self._value, u''
    500          try:
    501              val = float(val)
    502          except ValueError:
    503              raise xml.dom.InvalidAccessErr(
    504                  u'CSSPrimitiveValue: No float value %s'
    505                  % (self._value))
    506
    507          return val, dim
    508
    509 -    def getFloatValue(self, unitType):
    510          """
    511          (DOM method) This method is used to get a float value in a
    512          specified unit. If this CSS value doesn't contain a float value
    513          or can't be converted into the specified unit, a DOMException
    514          is raised.
    515
    516          unitType
    517              to get the float value. The unit code can only be a float unit type
    518              (i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM,
    519              CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS,
    520              CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION).
    521
    522          returns not necessarily a float but some cases just an int
    523          e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0``
    524
    525          conversions might return strange values like 1.000000000001
    526          """
    527          if unitType not in self._floattypes:
    528              raise xml.dom.InvalidAccessErr(
    529                  u'unitType Parameter is not a float type')
    530
    531          val, dim = self.__getValDim()
    532
    533          if self.primitiveType != unitType:
    534              try:
    535                  val = self._converter[self.primitiveType, unitType](val)
    536              except KeyError:
    537                  raise xml.dom.InvalidAccessErr(
    538                  u'CSSPrimitiveValue: Cannot coerce primitiveType %s to %s'
    539                  % (self.primitiveTypeString,
    540                     self._getCSSPrimitiveTypeString(unitType)))
    541
    542          if val == int(val):
    543              val = int(val)
    544
    545          return val
    546
    547 -    def setFloatValue(self, unitType, floatValue):
    548          """
    549          (DOM method) A method to set the float value with a specified unit.
    550          If the property attached with this value can not accept the
    551          specified unit or the float value, the value will be unchanged and
    552          a DOMException will be raised.
    553
    554          unitType
    555              a unit code as defined above. The unit code can only be a float
    556              unit type
    557          floatValue
    558              the new float value which does not have to be a float value but
    559              may simple be an int e.g. if setting::
    560
    561                  setFloatValue(CSS_PX, 1)
    562
    563          raises DOMException
    564              - INVALID_ACCESS_ERR: Raised if the attached property doesn't
    565                  support the float value or the unit type.
    566              - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
    567          """
    568          self._checkReadonly()
    569          if unitType not in self._floattypes:
    570              raise xml.dom.InvalidAccessErr(
    571                 u'CSSPrimitiveValue: unitType %s is not a float type' %
    572                 self._getCSSPrimitiveTypeString(unitType))
    573          try:
    574              val = float(floatValue)
    575          except ValueError, e:
    576              raise xml.dom.InvalidAccessErr(
    577                 u'CSSPrimitiveValue: floatValue "%s" is not a float' %
    578                 floatValue)
    579
    580          oldval, dim = self.__getValDim()
    581
    582          if self.primitiveType != unitType:
    583              # convert if possible
    584              try:
    585                  val = self._converter[
    586                      unitType, self.primitiveType](val)
    587              except KeyError:
    588                  raise xml.dom.InvalidAccessErr(
    589                  u'CSSPrimitiveValue: Cannot coerce primitiveType %s to %s'
    590                  % (self.primitiveTypeString,
    591                     self._getCSSPrimitiveTypeString(unitType)))
    592
    593          if val == int(val):
    594              val = int(val)
    595
    596          self.cssText = '%s%s' % (val, dim)
    597
    598 -    def getStringValue(self):
    599          """
    600          (DOM method) This method is used to get the string value. If the
    601          CSS value doesn't contain a string value, a DOMException is raised.
    602
    603          Some properties (like 'font-family' or 'voice-family')
    604          convert a whitespace separated list of idents to a string.
    605
    606          Only the actual value is returned so e.g. all the following return the
    607          actual value ``a``: url(a), attr(a), "a", 'a'
    608          """
    609          if self.primitiveType not in self._stringtypes:
    610              raise xml.dom.InvalidAccessErr(
    611                  u'CSSPrimitiveValue %s is not a string type'
    612                  % self.primitiveTypeString)
    613
    614          if CSSPrimitiveValue.CSS_STRING == self.primitiveType:
    615              return self._value[1:-1]
    616          elif CSSPrimitiveValue.CSS_URI == self.primitiveType:
    617              url = self._value[4:-1]
    618              if url and url[0] in ('"', "'") and url[0] == url[-1]:
    619                  return url[1:-1]
    620              else:
    621                  return url
    622          elif CSSPrimitiveValue.CSS_ATTR == self.primitiveType:
    623              return self._value[5:-1]
    624          else:
    625              return self._value
    626
    627 -    def setStringValue(self, stringType, stringValue):
    628          """
    629          (DOM method) A method to set the string value with the specified
    630          unit. If the property attached to this value can't accept the
    631          specified unit or the string value, the value will be unchanged and
    632          a DOMException will be raised.
    633
    634          stringType
    635              a string code as defined above. The string code can only be a
    636              string unit type (i.e. CSS_STRING, CSS_URI, CSS_IDENT, and
    637              CSS_ATTR).
    638          stringValue
    639              the new string value
    640              Only the actual value is expected so for (CSS_URI, "a") the
    641              new value will be ``url(a)``. For (CSS_STRING, "'a'")
    642              the new value will be ``"\\'a\\'"`` as the surrounding ``'`` are
    643              not part of the string value
    644
    645          raises
    646              DOMException
    647              - INVALID_ACCESS_ERR: Raised if the CSS value doesn't contain a
    648                  string value or if the string value can't be converted into
    649                  the specified unit.
    650              - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
    651          """
    652          self._checkReadonly()
    653          # self not stringType
    654          if self.primitiveType not in self._stringtypes:
    655              raise xml.dom.InvalidAccessErr(
    656                  u'CSSPrimitiveValue %s is not a string type'
    657                  % self.primitiveTypeString)
    658          # given stringType is no StringType
    659          if stringType not in self._stringtypes:
    660              raise xml.dom.InvalidAccessErr(
    661                  u'CSSPrimitiveValue: stringType %s is not a string type'
    662                  % self._getCSSPrimitiveTypeString(stringType))
    663
    664          if self._primitiveType != stringType:
    665              raise xml.dom.InvalidAccessErr(
    666                  u'CSSPrimitiveValue: Cannot coerce primitiveType %s to %s'
    667                  % (self.primitiveTypeString,
    668                     self._getCSSPrimitiveTypeString(stringType)))
    669
    670          if CSSPrimitiveValue.CSS_STRING == self._primitiveType:
    671              self.cssText = u'"%s"' % stringValue.replace(u'"', ur'\\"')
    672          elif CSSPrimitiveValue.CSS_URI == self._primitiveType:
    673              # Some characters appearing in an unquoted URI, such as
    674              # parentheses, commas, whitespace characters, single quotes
    675              # (') and double quotes ("), must be escaped with a backslash
    676              # so that the resulting URI value is a URI token:
    677              # '\(', '\)', '\,'.
    678              #
    679              # Here the URI is set in quotes alltogether!
    680              if u'(' in stringValue or\
    681                 u')' in stringValue or\
    682                 u',' in stringValue or\
    683                 u'"' in stringValue or\
    684                 u'\'' in stringValue or\
    685                 u'\n' in stringValue or\
    686                 u'\t' in stringValue or\
    687                 u'\r' in stringValue or\
    688                 u'\f' in stringValue or\
    689                 u' ' in stringValue:
    690                  stringValue = '"%s"' % stringValue.replace(u'"', ur'\"')
    691              self.cssText = u'url(%s)' % stringValue
    692          elif CSSPrimitiveValue.CSS_ATTR == self._primitiveType:
    693              self.cssText = u'attr(%s)' % stringValue
    694          else:
    695              self.cssText = stringValue
    696          self._primitiveType = stringType
    697
    698 -    def getCounterValue(self):
    699          """
    700          (DOM method) This method is used to get the Counter value. If
    701          this CSS value doesn't contain a counter value, a DOMException
    702          is raised. Modification to the corresponding style property
    703          can be achieved using the Counter interface.
    704          """
    705          if not self.CSS_COUNTER == self.primitiveType:
    706              raise xml.dom.InvalidAccessErr(u'Value is not a counter type')
    707          # TODO: use Counter class
    708          raise NotImplementedError()
    709
    710 -    def getRGBColorValue(self):
    711          """
    712          (DOM method) This method is used to get the RGB color. If this
    713          CSS value doesn't contain a RGB color value, a DOMException
    714          is raised. Modification to the corresponding style property
    715          can be achieved using the RGBColor interface.
    716          """
    717          # TODO: what about coercing #000 to RGBColor?
    718          if self.primitiveType not in self._rbgtypes:
    719              raise xml.dom.InvalidAccessErr(u'Value is not a RGB value')
    720          # TODO: use RGBColor class
    721          raise NotImplementedError()
    722
    723 -    def getRectValue(self):
    724          """
    725          (DOM method) This method is used to get the Rect value. If this CSS
    726          value doesn't contain a rect value, a DOMException is raised.
    727          Modification to the corresponding style property can be achieved
    728          using the Rect interface.
    729          """
    730          if self.primitiveType not in self._recttypes:
    731              raise xml.dom.InvalidAccessErr(u'value is not a Rect value')
    732          # TODO: use Rect class
    733          raise NotImplementedError()
    734
    735 -    def __str__(self):
    736          return "<cssutils.css.%s object primitiveType=%s cssText=%r _propertyName=%r valid=%r at 0x%x>" % (
    737                  self.__class__.__name__, self.primitiveTypeString,
    738                  self.cssText, self._propertyName, self.valid, id(self))
    739
    740
    741 -class CSSValueList(CSSValue):
    742      """
    743      The CSSValueList interface provides the abstraction of an ordered
    744      collection of CSS values.
    745
    746      Some properties allow an empty list into their syntax. In that case,
    747      these properties take the none identifier. So, an empty list means
    748      that the property has the value none.
    749
    750      The items in the CSSValueList are accessible via an integral index,
    751      starting from 0.
    752      """
    753      cssValueType = CSSValue.CSS_VALUE_LIST
    754      __ws = re.compile(ur'^\s*$')
    755
    756 -    def __init__(self, cssText=None, readonly=False, _propertyName=None):
    757          """
    758          inits a new CSSValueList
    759          """
    760          super(CSSValueList, self).__init__(cssText=cssText,
    761                                         readonly=readonly,
    762                                         _propertyName=_propertyName)
    763          self._init()
    764
    765 -    def _init(self):
    766          "called by CSSValue if newly identified as CSSValueList"
    767          self._items = []
    768          newseq = []
    769          i, max = 0, len(self.seq)
    770          while i < max:
    771              v = self.seq[i]
    772              if type(v) in types.StringTypes and not self.__ws.match(v):
    773                  if i+1 < max and self.seq[i+1] == u', ':
    774                      # a comma separated list of values as ONE value
    775                      # e.g. font-family: a,b
    776                      fullvalue = [v]
    777                      expected = 'comma' # or 'value'
    778                      for j in range(i+1, max):
    779                          testv = self.seq[j]
    780                          if u' ' == testv: # a single value follows
    781                              break
    782                          elif u', ' == testv and expected == 'comma':
    783                              fullvalue.append(testv)
    784                              expected = 'value'
    785                          elif u', ' != testv and expected == 'value':
    786                              fullvalue.append(testv)
    787                              expected = 'comma'
    788                          else:
    789                              self._log.error(
    790                                  u'CSSValueList: Unknown syntax: "%s".'
    791                                  % testv)
    792                              return
    793                      if expected == 'value':
    794                          self._log.error(
    795                              u'CSSValueList: Unknown syntax: "%s".'
    796                              % u''.join(self.seq))
    797                          return
    798                      # setting _propertyName this way does not work
    799                      # for compound props like font!
    800                      i += len(fullvalue) - 1
    801                      o = CSSValue(cssText=u''.join(fullvalue),
    802                               _propertyName=self._propertyName)
    803                  else:
    804                      # a single value, u' ' or nothing should be following
    805                      o = CSSValue(cssText=v, _propertyName=self._propertyName)
    806
    807                  self._items.append(o)
    808                  newseq.append(o)
    809
    810              else:
    811                  # S (or TODO: comment?)
    812                  newseq.append(v)
    813
    814              i += 1
    815
    816          self.seq = newseq
    817
    818 -    def _getLength(self):
    819          return len(self._items)
    820
    821      length = property(_getLength,
    822                  doc="(DOM attribute) The number of CSSValues in the list.")
    823
    824 -    def item(self, index):
    825          """
    826          (DOM method) Used to retrieve a CSSValue by ordinal index. The
    827          order in this collection represents the order of the values in the
    828          CSS style property. If index is greater than or equal to the number
    829          of values in the list, this returns None.
    830          """
    831          try:
    832              return self._items[index]
    833          except IndexError:
    834              return None
    835
    836 -    def __iter__(self):
    837          "CSSValueList is iterable"
    838          return CSSValueList.__items(self)
    839
    840 -    def __items(self):
    841          "the iterator"
    842          for i in range (0, self.length):
    843              yield self.item(i)
    844
    845 -    def __str_(self):
    846          return "<cssutils.css.%s object length=%s at 0x%x>" % (
    847                  self.__class__.__name__, self.length, id(self))
    848