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-24 23:32:10 +0100 (Sa, 24 Nov 2007) $'
      12  __version__ = '$LastChangedRevision: 678 $'
      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, aslist=True)
     574          try:
     575              t = tokenizer[0] #self._nexttoken(tokenizer)
     576          except IndexError:
     577              self._log.error(u'CSSPrimitiveValue: No value.')
     578
     579          # unary operator:
     580          if self._tokenvalue(t) in (u'-', u'+'):
     581              try:
     582                  t = tokenizer[1] #self._nexttoken(tokenizer)
     583              except IndexError:
     584                  self._log.error(u'CSSPrimitiveValue: No value.')
     585
     586              _floatType = True
     587
     588
     589          #if self.valid == False:
     590          #    primitiveType = CSSPrimitiveValue.CSS_UNKNOWN
     591
     592          # check for font1, "font2" etc which is treated a ONE string
     593          fontstring = 0 # should be at leayst 2
     594          expected = 'ident or string'
     595          for x in tokenizer:
     596              val, typ = self._tokenvalue(x, normalize=True), self._type(x)
     597              if expected == 'ident or string' and typ in (
     598                          self._prods.IDENT, self._prods.STRING):
     599                  expected = 'comma'
     600                  fontstring += 1
     601              elif expected == 'comma' and typ == self._prods.CHAR and val == ',':
     602                  expected = 'ident or string'
     603                  fontstring += 1
     604              elif typ in (self._prods.S, self._prods.COMMENT):
     605                  continue
     606              else:
     607                  fontstring = False
     608                  break
     609
     610          if fontstring > 2:
     611              # special case: e.g. for font-family: a, b; only COMMA IDENT and STRING
     612              primitiveType = CSSPrimitiveValue.CSS_STRING
     613          elif self._type(t) == self._prods.HASH:
     614              # special case, maybe should be converted to rgb in any case?
     615              primitiveType = CSSPrimitiveValue.CSS_RGBCOLOR
     616          else:
     617              for i, (name, tokentype, search) in enumerate(self._unitinfos):
     618                  val, typ = self._tokenvalue(t, normalize=True), self._type(t)
     619                  if typ == tokentype:
     620                      if typ == self._prods.DIMENSION:
     621                          if not search:
     622                              primitiveType = i
     623                              break
     624                          elif re.match(ur'^[^a-z]*(%s)$' % search, val):
     625                              primitiveType = i
     626                              break
     627                      elif typ == self._prods.FUNCTION:
     628                          if not search:
     629                              primitiveType = i
     630                              break
     631                          elif val.startswith(search):
     632                              primitiveType = i
     633                              break
     634                      else:
     635                          primitiveType = i
     636                          break
     637
     638          if _floatType and primitiveType not in self._floattypes:
     639              # - or + only expected before floattype
     640              primitiveType = self.CSS_UNKNOWN
     641
     642          self._primitiveType = primitiveType
     643
     644 -    def _getPrimitiveType(self):
     645          if not hasattr(self, '_primitivetype'):
     646              self.__set_primitiveType()
     647          return self._primitiveType
     648
     649      primitiveType = property(_getPrimitiveType,
     650          doc="READONLY: The type of the value as defined by the constants specified above.")
     651
     652 -    def _getPrimitiveTypeString(self):
     653          return self._unitinfos[self.primitiveType][0]
     654
     655      primitiveTypeString = property(_getPrimitiveTypeString,
     656                                     doc="Name of primitive type of this value.")
     657
     658 -    def _getCSSPrimitiveTypeString(self, type):
     659          "get TypeString by given type which may be unknown, used by setters"
     660          try:
     661              return self._unitinfos[type][0]
     662          except (IndexError, TypeError):
     663              return u'%r (UNKNOWN TYPE)' % type
     664
     665 -    def __getValDim(self):
     666          "splits self._value in numerical and dimension part"
     667          try:
     668              val, dim = self._reNumDim.findall(self._value)[0]
     669          except IndexError:
     670              val, dim = self._value, u''
     671          try:
     672              val = float(val)
     673          except ValueError:
     674              raise xml.dom.InvalidAccessErr(
     675                  u'CSSPrimitiveValue: No float value %r'
     676                  % (self._value))
     677
     678          return val, dim
     679
     680 -    def getFloatValue(self, unitType):
     681          """
     682          (DOM method) This method is used to get a float value in a
     683          specified unit. If this CSS value doesn't contain a float value
     684          or can't be converted into the specified unit, a DOMException
     685          is raised.
     686
     687          unitType
     688              to get the float value. The unit code can only be a float unit type
     689              (i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM,
     690              CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS,
     691              CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION).
     692
     693          returns not necessarily a float but some cases just an int
     694          e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0``
     695
     696          conversions might return strange values like 1.000000000001
     697          """
     698          if unitType not in self._floattypes:
     699              raise xml.dom.InvalidAccessErr(
     700                  u'unitType Parameter is not a float type')
     701
     702          val, dim = self.__getValDim()
     703
     704          if self.primitiveType != unitType:
     705              try:
     706                  val = self._converter[self.primitiveType, unitType](val)
     707              except KeyError:
     708                  raise xml.dom.InvalidAccessErr(
     709                  u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
     710                  % (self.primitiveTypeString,
     711                     self._getCSSPrimitiveTypeString(unitType)))
     712
     713          if val == int(val):
     714              val = int(val)
     715
     716          return val
     717
     718 -    def setFloatValue(self, unitType, floatValue):
     719          """
     720          (DOM method) A method to set the float value with a specified unit.
     721          If the property attached with this value can not accept the
     722          specified unit or the float value, the value will be unchanged and
     723          a DOMException will be raised.
     724
     725          unitType
     726              a unit code as defined above. The unit code can only be a float
     727              unit type
     728          floatValue
     729              the new float value which does not have to be a float value but
     730              may simple be an int e.g. if setting::
     731
     732                  setFloatValue(CSS_PX, 1)
     733
     734          raises DOMException
     735              - INVALID_ACCESS_ERR: Raised if the attached property doesn't
     736                  support the float value or the unit type.
     737              - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
     738          """
     739          self._checkReadonly()
     740          if unitType not in self._floattypes:
     741              raise xml.dom.InvalidAccessErr(
     742                 u'CSSPrimitiveValue: unitType %r is not a float type' %
     743                 self._getCSSPrimitiveTypeString(unitType))
     744          try:
     745              val = float(floatValue)
     746          except ValueError, e:
     747              raise xml.dom.InvalidAccessErr(
     748                 u'CSSPrimitiveValue: floatValue %r is not a float' %
     749                 floatValue)
     750
     751          oldval, dim = self.__getValDim()
     752
     753          if self.primitiveType != unitType:
     754              # convert if possible
     755              try:
     756                  val = self._converter[
     757                      unitType, self.primitiveType](val)
     758              except KeyError:
     759                  raise xml.dom.InvalidAccessErr(
     760                  u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
     761                  % (self.primitiveTypeString,
     762                     self._getCSSPrimitiveTypeString(unitType)))
     763
     764          if val == int(val):
     765              val = int(val)
     766
     767          self.cssText = '%s%s' % (val, dim)
     768
     769 -    def getStringValue(self):
     770          """
     771          (DOM method) This method is used to get the string value. If the
     772          CSS value doesn't contain a string value, a DOMException is raised.
     773
     774          Some properties (like 'font-family' or 'voice-family')
     775          convert a whitespace separated list of idents to a string.
     776
     777          Only the actual value is returned so e.g. all the following return the
     778          actual value ``a``: url(a), attr(a), "a", 'a'
     779          """
     780          if self.primitiveType not in self._stringtypes:
     781              raise xml.dom.InvalidAccessErr(
     782                  u'CSSPrimitiveValue %r is not a string type'
     783                  % self.primitiveTypeString)
     784
     785          if CSSPrimitiveValue.CSS_STRING == self.primitiveType:
     786              return self._value[1:-1]
     787          elif CSSPrimitiveValue.CSS_URI == self.primitiveType:
     788              url = self._value[4:-1]
     789              if url and url[0] in ('"', "'") and url[0] == url[-1]:
     790                  return url[1:-1]
     791              else:
     792                  return url
     793          elif CSSPrimitiveValue.CSS_ATTR == self.primitiveType:
     794              return self._value[5:-1]
     795          else:
     796              return self._value
     797
     798 -    def setStringValue(self, stringType, stringValue):
     799          """
     800          (DOM method) A method to set the string value with the specified
     801          unit. If the property attached to this value can't accept the
     802          specified unit or the string value, the value will be unchanged and
     803          a DOMException will be raised.
     804
     805          stringType
     806              a string code as defined above. The string code can only be a
     807              string unit type (i.e. CSS_STRING, CSS_URI, CSS_IDENT, and
     808              CSS_ATTR).
     809          stringValue
     810              the new string value
     811              Only the actual value is expected so for (CSS_URI, "a") the
     812              new value will be ``url(a)``. For (CSS_STRING, "'a'")
     813              the new value will be ``"\\'a\\'"`` as the surrounding ``'`` are
     814              not part of the string value
     815
     816          raises
     817              DOMException
     818
     819              - INVALID_ACCESS_ERR: Raised if the CSS value doesn't contain a
     820                string value or if the string value can't be converted into
     821                the specified unit.
     822
     823              - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
     824          """
     825          self._checkReadonly()
     826          # self not stringType
     827          if self.primitiveType not in self._stringtypes:
     828              raise xml.dom.InvalidAccessErr(
     829                  u'CSSPrimitiveValue %r is not a string type'
     830                  % self.primitiveTypeString)
     831          # given stringType is no StringType
     832          if stringType not in self._stringtypes:
     833              raise xml.dom.InvalidAccessErr(
     834                  u'CSSPrimitiveValue: stringType %s is not a string type'
     835                  % self._getCSSPrimitiveTypeString(stringType))
     836
     837          if self._primitiveType != stringType:
     838              raise xml.dom.InvalidAccessErr(
     839                  u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
     840                  % (self.primitiveTypeString,
     841                     self._getCSSPrimitiveTypeString(stringType)))
     842
     843          if CSSPrimitiveValue.CSS_STRING == self._primitiveType:
     844              self.cssText = u'"%s"' % stringValue.replace(u'"', ur'\\"')
     845          elif CSSPrimitiveValue.CSS_URI == self._primitiveType:
     846              # Some characters appearing in an unquoted URI, such as
     847              # parentheses, commas, whitespace characters, single quotes
     848              # (') and double quotes ("), must be escaped with a backslash
     849              # so that the resulting URI value is a URI token:
     850              # '\(', '\)', '\,'.
     851              #
     852              # Here the URI is set in quotes alltogether!
     853              if u'(' in stringValue or\
     854                 u')' in stringValue or\
     855                 u',' in stringValue or\
     856                 u'"' in stringValue or\
     857                 u'\'' in stringValue or\
     858                 u'\n' in stringValue or\
     859                 u'\t' in stringValue or\
     860                 u'\r' in stringValue or\
     861                 u'\f' in stringValue or\
     862                 u' ' in stringValue:
     863                  stringValue = '"%s"' % stringValue.replace(u'"', ur'\"')
     864              self.cssText = u'url(%s)' % stringValue
     865          elif CSSPrimitiveValue.CSS_ATTR == self._primitiveType:
     866              self.cssText = u'attr(%s)' % stringValue
     867          else:
     868              self.cssText = stringValue
     869          self._primitiveType = stringType
     870
     871 -    def getCounterValue(self):
     872          """
     873          (DOM method) This method is used to get the Counter value. If
     874          this CSS value doesn't contain a counter value, a DOMException
     875          is raised. Modification to the corresponding style property
     876          can be achieved using the Counter interface.
     877          """
     878          if not self.CSS_COUNTER == self.primitiveType:
     879              raise xml.dom.InvalidAccessErr(u'Value is not a counter type')
     880          # TODO: use Counter class
     881          raise NotImplementedError()
     882
     883 -    def getRGBColorValue(self):
     884          """
     885          (DOM method) This method is used to get the RGB color. If this
     886          CSS value doesn't contain a RGB color value, a DOMException
     887          is raised. Modification to the corresponding style property
     888          can be achieved using the RGBColor interface.
     889          """
     890          # TODO: what about coercing #000 to RGBColor?
     891          if self.primitiveType not in self._rbgtypes:
     892              raise xml.dom.InvalidAccessErr(u'Value is not a RGB value')
     893          # TODO: use RGBColor class
     894          raise NotImplementedError()
     895
     896 -    def getRectValue(self):
     897          """
     898          (DOM method) This method is used to get the Rect value. If this CSS
     899          value doesn't contain a rect value, a DOMException is raised.
     900          Modification to the corresponding style property can be achieved
     901          using the Rect interface.
     902          """
     903          if self.primitiveType not in self._recttypes:
     904              raise xml.dom.InvalidAccessErr(u'value is not a Rect value')
     905          # TODO: use Rect class
     906          raise NotImplementedError()
     907
     908 -    def __str__(self):
     909          return "<cssutils.css.%s object primitiveType=%s cssText=%r _propertyName=%r valid=%r at 0x%x>" % (
     910                  self.__class__.__name__, self.primitiveTypeString,
     911                  self.cssText, self._propertyName, self.valid, id(self))
     912
     913
     914 -class CSSValueList(CSSValue):
     915      """
     916      The CSSValueList interface provides the abstraction of an ordered
     917      collection of CSS values.
     918
     919      Some properties allow an empty list into their syntax. In that case,
     920      these properties take the none identifier. So, an empty list means
     921      that the property has the value none.
     922
     923      The items in the CSSValueList are accessible via an integral index,
     924      starting from 0.
     925      """
     926      cssValueType = CSSValue.CSS_VALUE_LIST
     927
     928 -    def __init__(self, cssText=None, readonly=False, _propertyName=None):
     929          """
     930          inits a new CSSValueList
     931          """
     932          super(CSSValueList, self).__init__(cssText=cssText,
     933                                         readonly=readonly,
     934                                         _propertyName=_propertyName)
     935          self._init()
     936
     937 -    def _init(self):
     938          "called by CSSValue if newly identified as CSSValueList"
     939          # defines which values
     940          ivalueseq, valueseq = 0, self._SHORTHANDPROPERTIES.get(
     941                                                      self._propertyName, [])
     942          self._items = []
     943          newseq = []
     944          i, max = 0, len(self.seq)
     945          minus = None
     946          while i < max:
     947              v = self.seq[i]
     948
     949              if u'-' == v:
     950                  if minus: # 2 "-" after another
     951                      self._log.error(
     952                          u'CSSValueList: Unknown syntax: %r.'
     953                              % u''.join(self.seq))
     954                  else:
     955                      minus = v
     956
     957              elif isinstance(v, basestring) and not v.strip() == u'' and\
     958                 not u'/' == v:
     959                  if minus:
     960                      v = minus + v
     961                      minus = None
     962                  # TODO: complete
     963                  # if shorthand get new propname
     964                  if ivalueseq < len(valueseq):
     965                      propname, mandatory = valueseq[ivalueseq]
     966                      if mandatory:
     967                          ivalueseq += 1
     968                      else:
     969                          propname = None
     970                          ivalueseq = len(valueseq) # end
     971                  else:
     972                      propname = self._propertyName
     973
     974                  # TODO: more (do not check individual values for these props)
     975                  if propname in ('background', 'background-position',):
     976                      propname = None
     977
     978                  if i+1 < max and self.seq[i+1] == u',':
     979                      # a comma separated list of values as ONE value
     980                      # e.g. font-family: a,b
     981                      fullvalue = [v]
     982
     983                      expected = 'comma' # or 'value'
     984                      for j in range(i+1, max):
     985                          testv = self.seq[j]
     986                          if u' ' == testv: # a single value follows
     987                              break
     988                          elif testv in ('-', '+') and expected == 'value':
     989                              # unary modifier
     990                              fullvalue.append(testv)
     991                              expected = 'value'
     992                          elif u',' == testv and expected == 'comma':
     993                              fullvalue.append(testv)
     994                              expected = 'value'
     995                          elif u',' != testv and expected == 'value':
     996                              fullvalue.append(testv)
     997                              expected = 'comma'
     998                          else:
     999                              self._log.error(
    1000                                  u'CSSValueList: Unknown syntax: %r.'
    1001                                  % testv)
    1002                              return
    1003                      if expected == 'value':
    1004                          self._log.error(
    1005                              u'CSSValueList: Unknown syntax: %r.'
    1006                              % u''.join(self.seq))
    1007                          return
    1008                      # setting _propertyName this way does not work
    1009                      # for compound props like font!
    1010                      i += len(fullvalue) - 1
    1011                      o = CSSValue(cssText=u''.join(fullvalue),
    1012                               _propertyName=propname)
    1013                  else:
    1014                      # a single value, u' ' or nothing should be following
    1015                      o = CSSValue(cssText=v, _propertyName=propname)
    1016
    1017                  self._items.append(o)
    1018                  newseq.append(o)
    1019
    1020              else:
    1021                  # S (or TODO: comment?)
    1022                  newseq.append(v)
    1023
    1024              i += 1
    1025
    1026          self.seq = newseq
    1027
    1028 -    def _getLength(self):
    1029          return len(self._items)
    1030
    1031      length = property(_getLength,
    1032                  doc="(DOM attribute) The number of CSSValues in the list.")
    1033
    1034 -    def item(self, index):
    1035          """
    1036          (DOM method) Used to retrieve a CSSValue by ordinal index. The
    1037          order in this collection represents the order of the values in the
    1038          CSS style property. If index is greater than or equal to the number
    1039          of values in the list, this returns None.
    1040          """
    1041          try:
    1042              return self._items[index]
    1043          except IndexError:
    1044              return None
    1045
    1046 -    def __iter__(self):
    1047          "CSSValueList is iterable"
    1048          return CSSValueList.__items(self)
    1049
    1050 -    def __items(self):
    1051          "the iterator"
    1052          for i in range (0, self.length):
    1053              yield self.item(i)
    1054
    1055 -    def __str_(self):
    1056          return "<cssutils.css.%s object length=%s at 0x%x>" % (
    1057                  self.__class__.__name__, self.length, id(self))
    1058