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