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-11-03 22:42:49 +0100 (Sa, 03 Nov 2007) $'
      12  __version__ = '$LastChangedRevision: 630 $'
      13
      14  import re
      15  import xml.dom
      16  import cssutils
      17  import cssproperties
      18
      19 -class CSSValue(cssutils.util.Base):
      20      """
      21      The CSSValue interface represents a simple or a complex value.
      22      A CSSValue object only occurs in a context of a CSS property
      23
      24      Properties
      25      ==========
      26      cssText
      27          A string representation of the current value.
      28      cssValueType
      29          A (readonly) code defining the type of the value.
      30
      31      seq: a list (cssutils)
      32          All parts of this style declaration including CSSComments
      33      valid: boolean
      34          if the value is valid at all, False for e.g. color: #1
      35      wellformed
      36          if this Property is syntactically ok
      37
      38      _value (INTERNAL!)
      39          value without any comments, used 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          property
      74              used to validate this value in the context of a property
      75          """
      76          super(CSSValue, self).__init__()
      77
      78          self.seq = []
      79          self.valid = False
      80          self.wellformed = False
      81          self._valueValue = u''
      82          self._linetoken = None # used for line report only
      83          self._propertyName = _propertyName
      84
      85          if cssText is not None: # may be 0
      86              if type(cssText) in (int, float):
      87                  cssText = unicode(cssText) # if it is a number
      88              self.cssText = cssText
      89
      90          self._readonly = readonly
      91
      92 -    def _getValue(self):
      93          v = []
      94          for x in self.seq:
      95              if isinstance(x, cssutils.css.CSSComment):
      96                  continue
      97              elif isinstance(x, basestring):
      98                  v.append(x)
      99              else: # maybe CSSPrimitiveValue
     100                  v.append(x.cssText)
     101          return u''.join(v).strip()
     102
     103 -    def _setValue(self, value):
     104          "overwritten by CSSValueList!"
     105          self._valueValue = value
     106
     107      _value = property(_getValue, _setValue,
     108                  doc="Actual cssText value of this CSSValue.")
     109
     110 -    def _getCssText(self):
     111          return cssutils.ser.do_css_CSSValue(self)
     112
     113 -    def _setCssText(self, cssText):
     114          """
     115          Format
     116          ======
     117          ::
     118
     119              unary_operator
     120                : '-' | '+'
     121                ;
     122              operator
     123                : '/' S* | ',' S* | /* empty */
     124                ;
     125              expr
     126                : term [ operator term ]*
     127                ;
     128              term
     129                : unary_operator?
     130                  [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
     131                    TIME S* | FREQ S* ]
     132                | STRING S* | IDENT S* | URI S* | hexcolor | function
     133                ;
     134              function
     135                : FUNCTION S* expr ')' S*
     136                ;
     137              /*
     138               * There is a constraint on the color that it must
     139               * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
     140               * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
     141               */
     142              hexcolor
     143                : HASH S*
     144                ;
     145
     146          DOMException on setting
     147
     148          - SYNTAX_ERR: (self)
     149            Raised if the specified CSS string value has a syntax error
     150            (according to the attached property) or is unparsable.
     151          - TODO: INVALID_MODIFICATION_ERR:
     152            Raised if the specified CSS string value represents a different
     153            type of values than the values allowed by the CSS property.
     154          - NO_MODIFICATION_ALLOWED_ERR: (self)
     155            Raised if this value is readonly.
     156          """
     157          self._checkReadonly()
     158
     159          # for closures: must be a mutable
     160          new = {'values': [],
     161                 'commas': 0,
     162                 'valid': True,
     163                 'wellformed': True }
     164
     165          def _S(expected, seq, token, tokenizer=None):
     166              val = self._tokenvalue(token)
     167              if expected.endswith('operator'):
     168                  seq.append(u' ')
     169                  return 'term or operator'
     170              elif expected.endswith('S'):
     171                  return 'term or S'
     172              else:
     173                  return expected
     174
     175          def _char(expected, seq, token, tokenizer=None):
     176              val = self._tokenvalue(token)
     177              if 'funcend' == expected and u')' == val:
     178                  # end of FUNCTION
     179                  seq[-1] += val
     180                  new['values'].append(seq[-1])
     181                  return 'operator'
     182
     183              elif expected in (')', ']', '}') and expected == val:
     184                  # end of any block: (), [], {}
     185                  seq[-1] += val
     186                  return 'operator'
     187
     188              elif expected in ('funcend', ')', ']', '}'):
     189                  # content of func or block: (), [], {}
     190                  seq[-1] += val
     191                  return expected
     192
     193              elif expected.endswith('operator') and ',' == val:
     194                  # term , term
     195                  new['commas'] += 1
     196                  if seq and seq[-1] == u' ':
     197                      seq[-1] = val
     198                  else:
     199                      seq.append(val)
     200                  return 'term or S'
     201
     202              elif expected.endswith('operator') and '/' == val:
     203                  # term / term
     204                  if seq and seq[-1] == u' ':
     205                      seq[-1] = val
     206                  else:
     207                      seq.append(val)
     208                  return 'term or S'
     209
     210              elif expected.startswith('term') and u'(' == val:
     211                  # start of ( any* ) block
     212                  seq.append(val)
     213                  return ')'
     214              elif expected.startswith('term') and u'[' == val:
     215                  # start of [ any* ] block
     216                  seq.append(val)
     217                  return ']'
     218              elif expected.startswith('term') and u'{' == val:
     219                  # start of { any* } block
     220                  seq.append(val)
     221                  return '}'
     222              elif expected.startswith('term') and u'-' == val or u'+' == 'val':
     223                  # unary operator
     224                  seq.append(val)
     225                  new['values'].append(val)
     226                  return 'number percentage dimension'
     227              elif expected.startswith('term') and u'/' == val:
     228                  # font-size/line-height separator
     229                  seq.append(val)
     230                  new['values'].append(val)
     231                  return 'number percentage dimension'
     232              else:
     233                  new['wellformed'] = False
     234                  self._log.error(u'CSSValue: Unexpected char.', token)
     235                  return expected
     236
     237          def _number_percentage_dimension(expected, seq, token, tokenizer=None):
     238              # NUMBER PERCENTAGE DIMENSION after -/+ or operator
     239              if expected.startswith('term') or expected == 'number percentage dimension':
     240                  # normal value
     241                  val = self._tokenvalue(token)
     242                  if new['values'] and new['values'][-1] in (u'-', u'+'):
     243                      new['values'][-1] += val
     244                  else:
     245                      new['values'].append(val)
     246                  seq.append(val)
     247                  return 'operator'
     248              elif expected in ('funcend', ')', ']', '}'):
     249                  # a block
     250                  seq[-1] += self._tokenvalue(token)
     251                  return expected
     252              else:
     253                  new['wellformed'] = False
     254                  self._log.error(u'CSSValue: Unexpected token.', token)
     255                  return expected
     256
     257          def _string_ident_uri_hexcolor(expected, seq, token, tokenizer=None):
     258              # STRING IDENT URI HASH
     259              if expected.startswith('term'):
     260                  # normal value
     261                  val = self._tokenvalue(token)
     262                  new['values'].append(val)
     263                  seq.append(val)
     264                  return 'operator'
     265              elif expected in ('funcend', ')', ']', '}'):
     266                  # a block
     267                  seq[-1] += self._tokenvalue(token)
     268                  return expected
     269              else:
     270                  new['wellformed'] = False
     271                  self._log.error(u'CSSValue: Unexpected token.', token)
     272                  return expected
     273
     274          def _function(expected, seq, token, tokenizer=None):
     275              # FUNCTION
     276              if expected.startswith('term'):
     277                  # normal value but add if funcend if found
     278                  seq.append(self._tokenvalue(token))
     279                  return 'funcend'
     280              elif expected in ('funcend', ')', ']', '}'):
     281                  # a block
     282                  seq[-1] += self._tokenvalue(token)
     283                  return expected
     284              else:
     285                  new['wellformed'] = False
     286                  self._log.error(u'CSSValue: Unexpected token.', token)
     287                  return expected
     288
     289          tokenizer = self._tokenize2(cssText)
     290
     291          linetoken = self._nexttoken(tokenizer)
     292          if not linetoken:
     293              self._log.error(u'CSSValue: Unknown syntax or no value: "%s".' %
     294                  self._valuestr(cssText))
     295          else:
     296              # TODO: not very efficient tokenizing twice!
     297              tokenizer = self._tokenize2(cssText)
     298              newseq = []
     299              wellformed, expected = self._parse(expected='term',
     300                  seq=newseq, tokenizer=tokenizer,
     301                  productions={'S': _S,
     302                               'CHAR': _char,
     303
     304                               'NUMBER': _number_percentage_dimension,
     305                               'PERCENTAGE': _number_percentage_dimension,
     306                               'DIMENSION': _number_percentage_dimension,
     307
     308                               'STRING': _string_ident_uri_hexcolor,
     309                               'IDENT': _string_ident_uri_hexcolor,
     310                               'URI': _string_ident_uri_hexcolor,
     311                               'HASH': _string_ident_uri_hexcolor,
     312                               'UNICODE-RANGE': _string_ident_uri_hexcolor, #?
     313
     314                               'FUNCTION': _function
     315                                })
     316
     317              wellformed = wellformed and new['wellformed']
     318
     319              # post conditions
     320              if expected.startswith('term') and newseq and newseq[-1] != u' '  or (
     321                 expected in ('funcend', ')', ']', '}')):
     322                  wellformed = False
     323                  self._log.error(u'CSSValue: Incomplete value: %r.' %
     324                  self._valuestr(cssText))
     325
     326              if not new['values']:
     327                  wellformed = False
     328                  self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
     329                  self._valuestr(cssText))
     330
     331              else:
     332                  self._linetoken = linetoken # used for line report
     333                  self.seq = newseq
     334                  self.valid = False
     335
     336                  self._validate()
     337
     338                  if len(new['values']) == 1 and new['values'][0] == u'inherit':
     339                      self._value = u'inherit'
     340                      self._cssValueType = CSSValue.CSS_INHERIT
     341                      self.__class__ = CSSValue # reset
     342                  elif len(new['values']) == 1:
     343                      self.__class__ = CSSPrimitiveValue
     344                      self._init() #inits CSSPrimitiveValue
     345                  elif len(new['values']) > 1 and\
     346                       len(new['values']) == new['commas'] + 1:
     347                      # e.g. value for font-family: a, b
     348                      self.__class__ = CSSPrimitiveValue
     349                      self._init() #inits CSSPrimitiveValue
     350                  elif len(new['values']) > 1:
     351                      # separated by S
     352                      self.__class__ = CSSValueList
     353                      self._init() # inits CSSValueList
     354                  else:
     355                      self._cssValueType = CSSValue.CSS_CUSTOM
     356                      self.__class__ = CSSValue # reset
     357
     358              self.wellformed = wellformed
     359
     360      cssText = property(_getCssText, _setCssText,
     361          doc="A string representation of the current value.")
     362
     363 -    def _getCssValueType(self):
     364          if hasattr(self, '_cssValueType'):
     365              return self._cssValueType
     366
     367      cssValueType = property(_getCssValueType,
     368          doc="A (readonly) code defining the type of the value as defined above.")
     369
     370 -    def _getCssValueTypeString(self):
     371          t = self.cssValueType
     372          if t is not None: # may be 0!
     373              return CSSValue._typestrings[t]
     374          else:
     375              return None
     376
     377      cssValueTypeString = property(_getCssValueTypeString,
     378          doc="cssutils: Name of cssValueType of this CSSValue (readonly).")
     379
     380 -    def _validate(self):
     381          """
     382          validates value against _propertyName context if given
     383          """
     384          if self._value:
     385              if self._propertyName in cssproperties.cssvalues:
     386                  if cssproperties.cssvalues[self._propertyName](self._value):
     387                      self.valid = True
     388                  else:
     389                      self.valid = False
     390                      self._log.warn(
     391                          u'CSSValue: Invalid value for CSS2 property %r: %s' %
     392                          (self._propertyName, self._value), neverraise=True)
     393              else:
     394                  self._log.debug(
     395                      u'CSSValue: Unable to validate as no or unknown property context set for this value: %r'
     396                      % self._value, neverraise=True)
     397
     398 -    def _get_propertyName(self):
     399          return self.__propertyName
     400
     401 -    def _set_propertyName(self, _propertyName):
     402          self.__propertyName = _propertyName
     403          self._validate()
     404
     405      _propertyName = property(_get_propertyName, _set_propertyName,
     406          doc="cssutils: Property this values is validated against")
     407
     408 -    def __repr__(self):
     409          return "cssutils.css.%s(%r, _propertyName=%r)" % (
     410                  self.__class__.__name__, self.cssText, self._propertyName)
     411
     412 -    def __str__(self):
     413          return "<cssutils.css.%s object cssValueType=%r cssText=%r propname=%r valid=%r at 0x%x>" % (
     414                  self.__class__.__name__, self.cssValueTypeString,
     415                  self.cssText, self._propertyName, self.valid, id(self))
     416
     417
     418 -class CSSPrimitiveValue(CSSValue):
     419      """
     420      represents a single CSS Value.  May be used to determine the value of a
     421      specific style property currently set in a block or to set a specific
     422      style property explicitly within the block. Might be obtained from the
     423      getPropertyCSSValue method of CSSStyleDeclaration.
     424
     425      Conversions are allowed between absolute values (from millimeters to
     426      centimeters, from degrees to radians, and so on) but not between
     427      relative values. (For example, a pixel value cannot be converted to a
     428      centimeter value.) Percentage values can't be converted since they are
     429      relative to the parent value (or another property value). There is one
     430      exception for color percentage values: since a color percentage value
     431      is relative to the range 0-255, a color percentage value can be
     432      converted to a number; (see also the RGBColor interface).
     433      """
     434      # constant: type of this CSSValue class
     435      cssValueType = CSSValue.CSS_PRIMITIVE_VALUE
     436
     437      # An integer indicating which type of unit applies to the value.
     438      CSS_UNKNOWN = 0 # only obtainable via cssText
     439      CSS_NUMBER = 1
     440      CSS_PERCENTAGE = 2
     441      CSS_EMS = 3
     442      CSS_EXS = 4
     443      CSS_PX = 5
     444      CSS_CM = 6
     445      CSS_MM = 7
     446      CSS_IN = 8
     447      CSS_PT = 9
     448      CSS_PC = 10
     449      CSS_DEG = 11
     450      CSS_RAD = 12
     451      CSS_GRAD = 13
     452      CSS_MS = 14
     453      CSS_S = 15
     454      CSS_HZ = 16
     455      CSS_KHZ = 17
     456      CSS_DIMENSION = 18
     457      CSS_STRING = 19
     458      CSS_URI = 20
     459      CSS_IDENT = 21
     460      CSS_ATTR = 22
     461      CSS_COUNTER = 23
     462      CSS_RECT = 24
     463      CSS_RGBCOLOR = 25
     464      # NOT OFFICIAL:
     465      CSS_RGBACOLOR = 26
     466
     467      _floattypes = [CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS,
     468                     CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC,
     469                     CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S,
     470                     CSS_HZ, CSS_KHZ, CSS_DIMENSION
     471                     ]
     472      _stringtypes = [CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI]
     473      _countertypes = [CSS_COUNTER]
     474      _recttypes = [CSS_RECT]
     475      _rbgtypes = [CSS_RGBCOLOR, CSS_RGBACOLOR]
     476
     477      _reNumDim = re.compile(ur'^(.*?)([a-z]+|%)$', re.I| re.U|re.X)
     478
     479      # oldtype: newType: converterfunc
     480      _converter = {
     481          # cm <-> mm <-> in, 1 inch is equal to 2.54 centimeters.
     482          # pt <-> pc, the points used by CSS 2.1 are equal to 1/72nd of an inch.
     483          # pc: picas - 1 pica is equal to 12 points
     484          (CSS_CM, CSS_MM): lambda x: x * 10,
     485          (CSS_MM, CSS_CM): lambda x: x / 10,
     486
     487          (CSS_PT, CSS_PC): lambda x: x * 12,
     488          (CSS_PC, CSS_PT): lambda x: x / 12,
     489
     490          (CSS_CM, CSS_IN): lambda x: x / 2.54,
     491          (CSS_IN, CSS_CM): lambda x: x * 2.54,
     492          (CSS_MM, CSS_IN): lambda x: x / 25.4,
     493          (CSS_IN, CSS_MM): lambda x: x * 25.4,
     494
     495          (CSS_IN, CSS_PT): lambda x: x / 72,
     496          (CSS_PT, CSS_IN): lambda x: x * 72,
     497          (CSS_CM, CSS_PT): lambda x: x / 2.54 / 72,
     498          (CSS_PT, CSS_CM): lambda x: x * 72 * 2.54,
     499          (CSS_MM, CSS_PT): lambda x: x / 25.4 / 72,
     500          (CSS_PT, CSS_MM): lambda x: x * 72 * 25.4,
     501
     502          (CSS_IN, CSS_PC): lambda x: x / 72 / 12,
     503          (CSS_PC, CSS_IN): lambda x: x * 12 * 72,
     504          (CSS_CM, CSS_PC): lambda x: x / 2.54 / 72 / 12,
     505          (CSS_PC, CSS_CM): lambda x: x * 12 * 72 * 2.54,
     506          (CSS_MM, CSS_PC): lambda x: x / 25.4 / 72 / 12,
     507          (CSS_PC, CSS_MM): lambda x: x * 12 * 72 * 25.4,
     508
     509          # hz <-> khz
     510          (CSS_KHZ, CSS_HZ): lambda x: x * 1000,
     511          (CSS_HZ, CSS_KHZ): lambda x: x / 1000,
     512          # s <-> ms
     513          (CSS_S, CSS_MS): lambda x: x * 1000,
     514          (CSS_MS, CSS_S): lambda x: x / 1000
     515
     516          # TODO: convert deg <-> rad <-> grad
     517      }
     518
     519 -    def __init__(self, cssText=None, readonly=False, _propertyName=None):
     520          """
     521          see CSSPrimitiveValue.__init__()
     522          """
     523          super(CSSPrimitiveValue, self).__init__(cssText=cssText,
     524                                         readonly=readonly,
     525                                         _propertyName=_propertyName)
     526
     527          #(String representation for unit types, token type of unit type, detail)
     528          # used to detect primitiveType and for __repr__
     529          self._init()
     530
     531 -    def _init(self):
     532          # _unitinfos must be set here as self._prods is not known before
     533          self._unitinfos = [
     534              ('CSS_UNKNOWN', None, None),
     535              ('CSS_NUMBER', self._prods.NUMBER, None),
     536              ('CSS_PERCENTAGE', self._prods.PERCENTAGE, None),
     537              ('CSS_EMS', self._prods.DIMENSION, 'em'),
     538              ('CSS_EXS', self._prods.DIMENSION, 'ex'),
     539              ('CSS_PX', self._prods.DIMENSION, 'px'),
     540              ('CSS_CM', self._prods.DIMENSION, 'cm'),
     541              ('CSS_MM', self._prods.DIMENSION, 'mm'),
     542              ('CSS_IN', self._prods.DIMENSION, 'in'),
     543              ('CSS_PT', self._prods.DIMENSION, 'pt'),
     544              ('CSS_PC', self._prods.DIMENSION, 'pc'),
     545              ('CSS_DEG', self._prods.DIMENSION, 'deg'),
     546              ('CSS_RAD', self._prods.DIMENSION, 'rad'),
     547              ('CSS_GRAD', self._prods.DIMENSION, 'grad'),
     548              ('CSS_MS', self._prods.DIMENSION, 'ms'),
     549              ('CSS_S', self._prods.DIMENSION, 's'),
     550              ('CSS_HZ', self._prods.DIMENSION, 'hz'),
     551              ('CSS_KHZ', self._prods.DIMENSION, 'khz'),
     552              ('CSS_DIMENSION', self._prods.DIMENSION, None),
     553              ('CSS_STRING', self._prods.STRING, None),
     554              ('CSS_URI', self._prods.URI, None),
     555              ('CSS_IDENT', self._prods.IDENT, None),
     556              ('CSS_ATTR', self._prods.FUNCTION, 'attr('),
     557              ('CSS_COUNTER', self._prods.FUNCTION, 'counter('),
     558              ('CSS_RECT', self._prods.FUNCTION, 'rect('),
     559              ('CSS_RGBCOLOR', self._prods.FUNCTION, 'rgb('),
     560              ('CSS_RGBACOLOR', self._prods.FUNCTION, 'rgba('),
     561              ]
     562
     563 -    def __set_primitiveType(self):
     564          """
     565          primitiveType is readonly but is set lazy if accessed
     566          no value is given as self._value is used
     567          """
     568          primitiveType = self.CSS_UNKNOWN
     569          _floatType = False # if unary expect NUMBER DIMENSION or PERCENTAGE
     570          tokenizer = self._tokenize2(self._value, aslist=True)
     571          try:
     572              t = tokenizer[0] #self._nexttoken(tokenizer)
     573          except IndexError:
     574              self._log.error(u'CSSPrimitiveValue: No value.')
     575
     576          # unary operator:
     577          if self._tokenvalue(t) in (u'-', u'+'):
     578              try:
     579                  t = tokenizer[1] #self._nexttoken(tokenizer)
     580              except IndexError:
     581                  self._log.error(u'CSSPrimitiveValue: No value.')
     582
     583              _floatType = True
     584
     585
     586          #if self.valid == False:
     587          #    primitiveType = CSSPrimitiveValue.CSS_UNKNOWN
     588
     589          # check for font1, "font2" etc which is treated a ONE string
     590          fontstring = 0 # should be at leayst 2
     591          expected = 'ident or string'
     592          for x in tokenizer:
     593              val, typ = self._tokenvalue(x, normalize=True), self._type(x)
     594              if expected == 'ident or string' and typ in (
     595                          self._prods.IDENT, self._prods.STRING):
     596                  expected = 'comma'
     597                  fontstring += 1
     598              elif expected == 'comma' and typ == self._prods.CHAR and val == ',':
     599                  expected = 'ident or string'
     600                  fontstring += 1
     601              elif typ in (self._prods.S, self._prods.COMMENT):
     602                  continue
     603              else:
     604                  fontstring = False
     605                  break
     606
     607          if fontstring > 2:
     608              # special case: e.g. for font-family: a, b; only COMMA IDENT and STRING
     609              primitiveType = CSSPrimitiveValue.CSS_STRING
     610          elif self._type(t) == self._prods.HASH:
     611              # special case, maybe should be converted to rgb in any case?
     612              primitiveType = CSSPrimitiveValue.CSS_RGBCOLOR
     613          else:
     614              for i, (name, tokentype, search) in enumerate(self._unitinfos):
     615                  val, typ = self._tokenvalue(t, normalize=True), self._type(t)
     616                  if typ == tokentype:
     617                      if typ == self._prods.DIMENSION:
     618                          if not search:
     619                              primitiveType = i
     620                              break
     621                          elif re.match(ur'^[^a-z]*(%s)$' % search, val):
     622                              primitiveType = i
     623                              break
     624                      elif typ == self._prods.FUNCTION:
     625                          if not search:
     626                              primitiveType = i
     627                              break
     628                          elif val.startswith(search):
     629                              primitiveType = i
     630                              break
     631                      else:
     632                          primitiveType = i
     633                          break
     634
     635          if _floatType and primitiveType not in self._floattypes:
     636              # - or + only expected before floattype
     637              primitiveType = self.CSS_UNKNOWN
     638
     639          self._primitiveType = primitiveType
     640
     641 -    def _getPrimitiveType(self):
     642          if not hasattr(self, '_primitivetype'):
     643              self.__set_primitiveType()
     644          return self._primitiveType
     645
     646      primitiveType = property(_getPrimitiveType,
     647          doc="READONLY: The type of the value as defined by the constants specified above.")
     648
     649 -    def _getPrimitiveTypeString(self):
     650          return self._unitinfos[self.primitiveType][0]
     651
     652      primitiveTypeString = property(_getPrimitiveTypeString,
     653                                     doc="Name of primitive type of this value.")
     654
     655 -    def _getCSSPrimitiveTypeString(self, type):
     656          "get TypeString by given type which may be unknown, used by setters"
     657          try:
     658              return self._unitinfos[type][0]
     659          except (IndexError, TypeError):
     660              return u'%r (UNKNOWN TYPE)' % type
     661
     662 -    def __getValDim(self):
     663          "splits self._value in numerical and dimension part"
     664          try:
     665              val, dim = self._reNumDim.findall(self._value)[0]
     666          except IndexError:
     667              val, dim = self._value, u''
     668          try:
     669              val = float(val)
     670          except ValueError:
     671              raise xml.dom.InvalidAccessErr(
     672                  u'CSSPrimitiveValue: No float value %s'
     673                  % (self._value))
     674
     675          return val, dim
     676
     677 -    def getFloatValue(self, unitType):
     678          """
     679          (DOM method) This method is used to get a float value in a
     680          specified unit. If this CSS value doesn't contain a float value
     681          or can't be converted into the specified unit, a DOMException
     682          is raised.
     683
     684          unitType
     685              to get the float value. The unit code can only be a float unit type
     686              (i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM,
     687              CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS,
     688              CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION).
     689
     690          returns not necessarily a float but some cases just an int
     691          e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0``
     692
     693          conversions might return strange values like 1.000000000001
     694          """
     695          if unitType not in self._floattypes:
     696              raise xml.dom.InvalidAccessErr(
     697                  u'unitType Parameter is not a float type')
     698
     699          val, dim = self.__getValDim()
     700
     701          if self.primitiveType != unitType:
     702              try:
     703                  val = self._converter[self.primitiveType, unitType](val)
     704              except KeyError:
     705                  raise xml.dom.InvalidAccessErr(
     706                  u'CSSPrimitiveValue: Cannot coerce primitiveType %s to %s'
     707                  % (self.primitiveTypeString,
     708                     self._getCSSPrimitiveTypeString(unitType)))
     709
     710          if val == int(val):
     711              val = int(val)
     712
     713          return val
     714
     715 -    def setFloatValue(self, unitType, floatValue):
     716          """
     717          (DOM method) A method to set the float value with a specified unit.
     718          If the property attached with this value can not accept the
     719          specified unit or the float value, the value will be unchanged and
     720          a DOMException will be raised.
     721
     722          unitType
     723              a unit code as defined above. The unit code can only be a float
     724              unit type
     725          floatValue
     726              the new float value which does not have to be a float value but
     727              may simple be an int e.g. if setting::
     728
     729                  setFloatValue(CSS_PX, 1)
     730
     731          raises DOMException
     732              - INVALID_ACCESS_ERR: Raised if the attached property doesn't
     733                  support the float value or the unit type.
     734              - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
     735          """
     736          self._checkReadonly()
     737          if unitType not in self._floattypes:
     738              raise xml.dom.InvalidAccessErr(
     739                 u'CSSPrimitiveValue: unitType %s is not a float type' %
     740                 self._getCSSPrimitiveTypeString(unitType))
     741          try:
     742              val = float(floatValue)
     743          except ValueError, e:
     744              raise xml.dom.InvalidAccessErr(
     745                 u'CSSPrimitiveValue: floatValue "%s" is not a float' %
     746                 floatValue)
     747
     748          oldval, dim = self.__getValDim()
     749
     750          if self.primitiveType != unitType:
     751              # convert if possible
     752              try:
     753                  val = self._converter[
     754                      unitType, self.primitiveType](val)
     755              except KeyError:
     756                  raise xml.dom.InvalidAccessErr(
     757                  u'CSSPrimitiveValue: Cannot coerce primitiveType %s to %s'
     758                  % (self.primitiveTypeString,
     759                     self._getCSSPrimitiveTypeString(unitType)))
     760
     761          if val == int(val):
     762              val = int(val)
     763
     764          self.cssText = '%s%s' % (val, dim)
     765
     766 -    def getStringValue(self):
     767          """
     768          (DOM method) This method is used to get the string value. If the
     769          CSS value doesn't contain a string value, a DOMException is raised.
     770
     771          Some properties (like 'font-family' or 'voice-family')
     772          convert a whitespace separated list of idents to a string.
     773
     774          Only the actual value is returned so e.g. all the following return the
     775          actual value ``a``: url(a), attr(a), "a", 'a'
     776          """
     777          if self.primitiveType not in self._stringtypes:
     778              raise xml.dom.InvalidAccessErr(
     779                  u'CSSPrimitiveValue %s is not a string type'
     780                  % self.primitiveTypeString)
     781
     782          if CSSPrimitiveValue.CSS_STRING == self.primitiveType:
     783              return self._value[1:-1]
     784          elif CSSPrimitiveValue.CSS_URI == self.primitiveType:
     785              url = self._value[4:-1]
     786              if url and url[0] in ('"', "'") and url[0] == url[-1]:
     787                  return url[1:-1]
     788              else:
     789                  return url
     790          elif CSSPrimitiveValue.CSS_ATTR == self.primitiveType:
     791              return self._value[5:-1]
     792          else:
     793              return self._value
     794
     795 -    def setStringValue(self, stringType, stringValue):
     796          """
     797          (DOM method) A method to set the string value with the specified
     798          unit. If the property attached to this value can't accept the
     799          specified unit or the string value, the value will be unchanged and
     800          a DOMException will be raised.
     801
     802          stringType
     803              a string code as defined above. The string code can only be a
     804              string unit type (i.e. CSS_STRING, CSS_URI, CSS_IDENT, and
     805              CSS_ATTR).
     806          stringValue
     807              the new string value
     808              Only the actual value is expected so for (CSS_URI, "a") the
     809              new value will be ``url(a)``. For (CSS_STRING, "'a'")
     810              the new value will be ``"\\'a\\'"`` as the surrounding ``'`` are
     811              not part of the string value
     812
     813          raises
     814              DOMException
     815
     816              - INVALID_ACCESS_ERR: Raised if the CSS value doesn't contain a
     817                string value or if the string value can't be converted into
     818                the specified unit.
     819
     820              - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
     821          """
     822          self._checkReadonly()
     823          # self not stringType
     824          if self.primitiveType not in self._stringtypes:
     825              raise xml.dom.InvalidAccessErr(
     826                  u'CSSPrimitiveValue %s is not a string type'
     827                  % self.primitiveTypeString)
     828          # given stringType is no StringType
     829          if stringType not in self._stringtypes:
     830              raise xml.dom.InvalidAccessErr(
     831                  u'CSSPrimitiveValue: stringType %s is not a string type'
     832                  % self._getCSSPrimitiveTypeString(stringType))
     833
     834          if self._primitiveType != stringType:
     835              raise xml.dom.InvalidAccessErr(
     836                  u'CSSPrimitiveValue: Cannot coerce primitiveType %s to %s'
     837                  % (self.primitiveTypeString,
     838                     self._getCSSPrimitiveTypeString(stringType)))
     839
     840          if CSSPrimitiveValue.CSS_STRING == self._primitiveType:
     841              self.cssText = u'"%s"' % stringValue.replace(u'"', ur'\\"')
     842          elif CSSPrimitiveValue.CSS_URI == self._primitiveType:
     843              # Some characters appearing in an unquoted URI, such as
     844              # parentheses, commas, whitespace characters, single quotes
     845              # (') and double quotes ("), must be escaped with a backslash
     846              # so that the resulting URI value is a URI token:
     847              # '\(', '\)', '\,'.
     848              #
     849              # Here the URI is set in quotes alltogether!
     850              if u'(' in stringValue or\
     851                 u')' in stringValue or\
     852                 u',' in stringValue or\
     853                 u'"' in stringValue or\
     854                 u'\'' in stringValue or\
     855                 u'\n' in stringValue or\
     856                 u'\t' in stringValue or\
     857                 u'\r' in stringValue or\
     858                 u'\f' in stringValue or\
     859                 u' ' in stringValue:
     860                  stringValue = '"%s"' % stringValue.replace(u'"', ur'\"')
     861              self.cssText = u'url(%s)' % stringValue
     862          elif CSSPrimitiveValue.CSS_ATTR == self._primitiveType:
     863              self.cssText = u'attr(%s)' % stringValue
     864          else:
     865              self.cssText = stringValue
     866          self._primitiveType = stringType
     867
     868 -    def getCounterValue(self):
     869          """
     870          (DOM method) This method is used to get the Counter value. If
     871          this CSS value doesn't contain a counter value, a DOMException
     872          is raised. Modification to the corresponding style property
     873          can be achieved using the Counter interface.
     874          """
     875          if not self.CSS_COUNTER == self.primitiveType:
     876              raise xml.dom.InvalidAccessErr(u'Value is not a counter type')
     877          # TODO: use Counter class
     878          raise NotImplementedError()
     879
     880 -    def getRGBColorValue(self):
     881          """
     882          (DOM method) This method is used to get the RGB color. If this
     883          CSS value doesn't contain a RGB color value, a DOMException
     884          is raised. Modification to the corresponding style property
     885          can be achieved using the RGBColor interface.
     886          """
     887          # TODO: what about coercing #000 to RGBColor?
     888          if self.primitiveType not in self._rbgtypes:
     889              raise xml.dom.InvalidAccessErr(u'Value is not a RGB value')
     890          # TODO: use RGBColor class
     891          raise NotImplementedError()
     892
     893 -    def getRectValue(self):
     894          """
     895          (DOM method) This method is used to get the Rect value. If this CSS
     896          value doesn't contain a rect value, a DOMException is raised.
     897          Modification to the corresponding style property can be achieved
     898          using the Rect interface.
     899          """
     900          if self.primitiveType not in self._recttypes:
     901              raise xml.dom.InvalidAccessErr(u'value is not a Rect value')
     902          # TODO: use Rect class
     903          raise NotImplementedError()
     904
     905 -    def __str__(self):
     906          return "<cssutils.css.%s object primitiveType=%s cssText=%r _propertyName=%r valid=%r at 0x%x>" % (
     907                  self.__class__.__name__, self.primitiveTypeString,
     908                  self.cssText, self._propertyName, self.valid, id(self))
     909
     910
     911 -class CSSValueList(CSSValue):
     912      """
     913      The CSSValueList interface provides the abstraction of an ordered
     914      collection of CSS values.
     915
     916      Some properties allow an empty list into their syntax. In that case,
     917      these properties take the none identifier. So, an empty list means
     918      that the property has the value none.
     919
     920      The items in the CSSValueList are accessible via an integral index,
     921      starting from 0.
     922      """
     923      cssValueType = CSSValue.CSS_VALUE_LIST
     924
     925 -    def __init__(self, cssText=None, readonly=False, _propertyName=None):
     926          """
     927          inits a new CSSValueList
     928          """
     929          super(CSSValueList, self).__init__(cssText=cssText,
     930                                         readonly=readonly,
     931                                         _propertyName=_propertyName)
     932          self._init()
     933
     934 -    def _init(self):
     935          "called by CSSValue if newly identified as CSSValueList"
     936          # defines which values
     937          ivalueseq, valueseq = 0, self._SHORTHANDPROPERTIES.get(
     938                                                      self._propertyName, [])
     939          self._items = []
     940          newseq = []
     941          i, max = 0, len(self.seq)
     942          minus = None
     943          while i < max:
     944              v = self.seq[i]
     945
     946              if u'-' == v:
     947                  if minus: # 2 "-" after another
     948                      self._log.error(
     949                          u'CSSValueList: Unknown syntax: "%s".'
     950                              % u''.join(self.seq))
     951                  else:
     952                      minus = v
     953
     954              elif isinstance(v, basestring) and not v.strip() == u'' and\
     955                 not u'/' == v:
     956                  if minus:
     957                      v = minus + v
     958                      minus = None
     959                  # TODO: complete
     960                  # if shorthand get new propname
     961                  if ivalueseq < len(valueseq):
     962                      propname, mandatory = valueseq[ivalueseq]
     963                      if mandatory:
     964                          ivalueseq += 1
     965                      else:
     966                          propname = None
     967                          ivalueseq = len(valueseq) # end
     968                  else:
     969                      propname = self._propertyName
     970
     971                  # TODO: more (do not check individual values for these props)
     972                  if propname in ('background', 'background-position',):
     973                      propname = None
     974
     975                  if i+1 < max and self.seq[i+1] == u',':
     976                      # a comma separated list of values as ONE value
     977                      # e.g. font-family: a,b
     978                      fullvalue = [v]
     979
     980                      expected = 'comma' # or 'value'
     981                      for j in range(i+1, max):
     982                          testv = self.seq[j]
     983                          if u' ' == testv: # a single value follows
     984                              break
     985                          elif testv in ('-', '+') and expected == 'value':
     986                              # unary modifier
     987                              fullvalue.append(testv)
     988                              expected = 'value'
     989                          elif u',' == testv and expected == 'comma':
     990                              fullvalue.append(testv)
     991                              expected = 'value'
     992                          elif u',' != testv and expected == 'value':
     993                              fullvalue.append(testv)
     994                              expected = 'comma'
     995                          else:
     996                              self._log.error(
     997                                  u'CSSValueList: Unknown syntax: "%s".'
     998                                  % testv)
     999                              return
    1000                      if expected == 'value':
    1001                          self._log.error(
    1002                              u'CSSValueList: Unknown syntax: "%s".'
    1003                              % u''.join(self.seq))
    1004                          return
    1005                      # setting _propertyName this way does not work
    1006                      # for compound props like font!
    1007                      i += len(fullvalue) - 1
    1008                      o = CSSValue(cssText=u''.join(fullvalue),
    1009                               _propertyName=propname)
    1010                  else:
    1011                      # a single value, u' ' or nothing should be following
    1012                      o = CSSValue(cssText=v, _propertyName=propname)
    1013
    1014                  self._items.append(o)
    1015                  newseq.append(o)
    1016
    1017              else:
    1018                  # S (or TODO: comment?)
    1019                  newseq.append(v)
    1020
    1021              i += 1
    1022
    1023          self.seq = newseq
    1024
    1025 -    def _getLength(self):
    1026          return len(self._items)
    1027
    1028      length = property(_getLength,
    1029                  doc="(DOM attribute) The number of CSSValues in the list.")
    1030
    1031 -    def item(self, index):
    1032          """
    1033          (DOM method) Used to retrieve a CSSValue by ordinal index. The
    1034          order in this collection represents the order of the values in the
    1035          CSS style property. If index is greater than or equal to the number
    1036          of values in the list, this returns None.
    1037          """
    1038          try:
    1039              return self._items[index]
    1040          except IndexError:
    1041              return None
    1042
    1043 -    def __iter__(self):
    1044          "CSSValueList is iterable"
    1045          return CSSValueList.__items(self)
    1046
    1047 -    def __items(self):
    1048          "the iterator"
    1049          for i in range (0, self.length):
    1050              yield self.item(i)
    1051
    1052 -    def __str_(self):
    1053          return "<cssutils.css.%s object length=%s at 0x%x>" % (
    1054                  self.__class__.__name__, self.length, id(self))
    1055