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