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 1116 2008-03-05 13:52:23Z 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 expected in ('funcend', ')', ']', '}'):
     250                  # a block
     251                  seq[-1] += self._tokenvalue(token)
     252                  return expected
     253              else:
     254                  new['wellformed'] = False
     255                  self._log.error(u'CSSValue: Unexpected token.', token)
     256                  return expected
     257
     258          def _string_ident_uri_hexcolor(expected, seq, token, tokenizer=None):
     259              # STRING IDENT URI HASH
     260              if expected.startswith('term'):
     261                  # normal value
     262
     263                  # TODO: use actual values, probably needs Base2 for this
     264                  typ = self._type(token)
     265                  if self._prods.STRING == typ:
     266                      val = u'"%s"' % self._stringtokenvalue(token)
     267  #                elif 'URI' == typ:
     268  #                    val = u'url(%s)' % self._uritokenvalue(token)
     269                  else:
     270                      val = self._tokenvalue(token)
     271
     272                  new['values'].append(val)
     273                  seq.append(val)
     274                  return 'operator'
     275              elif expected in ('funcend', ')', ']', '}'):
     276                  # a block
     277                  seq[-1] += self._tokenvalue(token)
     278                  return expected
     279              else:
     280                  new['wellformed'] = False
     281                  self._log.error(u'CSSValue: Unexpected token.', token)
     282                  return expected
     283
     284          def _function(expected, seq, token, tokenizer=None):
     285              # FUNCTION
     286              if expected.startswith('term'):
     287                  # normal value but add if funcend if found
     288                  seq.append(self._tokenvalue(token))
     289                  return 'funcend'
     290              elif expected in ('funcend', ')', ']', '}'):
     291                  # a block
     292                  seq[-1] += self._tokenvalue(token)
     293                  return expected
     294              else:
     295                  new['wellformed'] = False
     296                  self._log.error(u'CSSValue: Unexpected token.', token)
     297                  return expected
     298
     299          tokenizer = self._tokenize2(cssText)
     300
     301          linetoken = self._nexttoken(tokenizer)
     302          if not linetoken:
     303              self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
     304                  self._valuestr(cssText))
     305          else:
     306              # TODO: not very efficient tokenizing twice!
     307              tokenizer = self._tokenize2(cssText)
     308              newseq = []
     309              wellformed, expected = self._parse(expected='term',
     310                  seq=newseq, tokenizer=tokenizer,
     311                  productions={'S': _S,
     312                               'CHAR': _char,
     313
     314                               'NUMBER': _number_percentage_dimension,
     315                               'PERCENTAGE': _number_percentage_dimension,
     316                               'DIMENSION': _number_percentage_dimension,
     317
     318                               'STRING': _string_ident_uri_hexcolor,
     319                               'IDENT': _string_ident_uri_hexcolor,
     320                               'URI': _string_ident_uri_hexcolor,
     321                               'HASH': _string_ident_uri_hexcolor,
     322                               'UNICODE-RANGE': _string_ident_uri_hexcolor, #?
     323
     324                               'FUNCTION': _function
     325                                })
     326
     327              wellformed = wellformed and new['wellformed']
     328
     329              # post conditions
     330              if expected.startswith('term') and newseq and newseq[-1] != u' '  or (
     331                 expected in ('funcend', ')', ']', '}')):
     332                  wellformed = False
     333                  self._log.error(u'CSSValue: Incomplete value: %r.' %
     334                  self._valuestr(cssText))
     335
     336              if not new['values']:
     337                  wellformed = False
     338                  self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
     339                  self._valuestr(cssText))
     340
     341              else:
     342                  self._linetoken = linetoken # used for line report
     343                  self.seq = newseq
     344                  self.valid = False
     345
     346                  self._validate()
     347
     348                  if len(new['values']) == 1 and new['values'][0] == u'inherit':
     349                      self._value = u'inherit'
     350                      self._cssValueType = CSSValue.CSS_INHERIT
     351                      self.__class__ = CSSValue # reset
     352                  elif len(new['values']) == 1:
     353                      self.__class__ = CSSPrimitiveValue
     354                      self._init() #inits CSSPrimitiveValue
     355                  elif len(new['values']) > 1 and\
     356                       len(new['values']) == new['commas'] + 1:
     357                      # e.g. value for font-family: a, b
     358                      self.__class__ = CSSPrimitiveValue
     359                      self._init() #inits CSSPrimitiveValue
     360                  elif len(new['values']) > 1:
     361                      # separated by S
     362                      self.__class__ = CSSValueList
     363                      self._init() # inits CSSValueList
     364                  else:
     365                      self._cssValueType = CSSValue.CSS_CUSTOM
     366                      self.__class__ = CSSValue # reset
     367
     368              self.wellformed = wellformed
     369
     370      cssText = property(_getCssText, _setCssText,
     371          doc="A string representation of the current value.")
     372
     373 -    def _getCssValueType(self):
     374          if hasattr(self, '_cssValueType'):
     375              return self._cssValueType
     376
     377      cssValueType = property(_getCssValueType,
     378          doc="A (readonly) code defining the type of the value as defined above.")
     379
     380 -    def _getCssValueTypeString(self):
     381          t = self.cssValueType
     382          if t is not None: # may be 0!
     383              return CSSValue._typestrings[t]
     384          else:
     385              return None
     386
     387      cssValueTypeString = property(_getCssValueTypeString,
     388          doc="cssutils: Name of cssValueType of this CSSValue (readonly).")
     389
     390 -    def _validate(self):
     391          """
     392          validates value against _propertyName context if given
     393          """
     394          if self._value:
     395              if self._propertyName in cssproperties.cssvalues:
     396                  if cssproperties.cssvalues[self._propertyName](self._value):
     397                      self.valid = True
     398                  else:
     399                      self.valid = False
     400                      self._log.warn(
     401                          u'CSSValue: Invalid value for CSS2 property %r: %r' %
     402                          (self._propertyName, self._value), neverraise=True)
     403              else:
     404                  self._log.debug(
     405                      u'CSSValue: Unable to validate as no or unknown property context set for this value: %r'
     406                      % self._value, neverraise=True)
     407
     408 -    def _get_propertyName(self):
     409          return self.__propertyName
     410
     411 -    def _set_propertyName(self, _propertyName):
     412          self.__propertyName = _propertyName
     413          self._validate()
     414
     415      _propertyName = property(_get_propertyName, _set_propertyName,
     416          doc="cssutils: Property this values is validated against")
     417
     418 -    def __repr__(self):
     419          return "cssutils.css.%s(%r, _propertyName=%r)" % (
     420                  self.__class__.__name__, self.cssText, self._propertyName)
     421
     422 -    def __str__(self):
     423          return "<cssutils.css.%s object cssValueType=%r cssText=%r propname=%r valid=%r at 0x%x>" % (
     424                  self.__class__.__name__, self.cssValueTypeString,
     425                  self.cssText, self._propertyName, self.valid, id(self))
     426
     427
     428 -class CSSPrimitiveValue(CSSValue):
     429      """
     430      represents a single CSS Value.  May be used to determine the value of a
     431      specific style property currently set in a block or to set a specific
     432      style property explicitly within the block. Might be obtained from the
     433      getPropertyCSSValue method of CSSStyleDeclaration.
     434
     435      Conversions are allowed between absolute values (from millimeters to
     436      centimeters, from degrees to radians, and so on) but not between
     437      relative values. (For example, a pixel value cannot be converted to a
     438      centimeter value.) Percentage values can't be converted since they are
     439      relative to the parent value (or another property value). There is one
     440      exception for color percentage values: since a color percentage value
     441      is relative to the range 0-255, a color percentage value can be
     442      converted to a number; (see also the RGBColor interface).
     443      """
     444      # constant: type of this CSSValue class
     445      cssValueType = CSSValue.CSS_PRIMITIVE_VALUE
     446
     447      # An integer indicating which type of unit applies to the value.
     448      CSS_UNKNOWN = 0 # only obtainable via cssText
     449      CSS_NUMBER = 1
     450      CSS_PERCENTAGE = 2
     451      CSS_EMS = 3
     452      CSS_EXS = 4
     453      CSS_PX = 5
     454      CSS_CM = 6
     455      CSS_MM = 7
     456      CSS_IN = 8
     457      CSS_PT = 9
     458      CSS_PC = 10
     459      CSS_DEG = 11
     460      CSS_RAD = 12
     461      CSS_GRAD = 13
     462      CSS_MS = 14
     463      CSS_S = 15
     464      CSS_HZ = 16
     465      CSS_KHZ = 17
     466      CSS_DIMENSION = 18
     467      CSS_STRING = 19
     468      CSS_URI = 20
     469      CSS_IDENT = 21
     470      CSS_ATTR = 22
     471      CSS_COUNTER = 23
     472      CSS_RECT = 24
     473      CSS_RGBCOLOR = 25
     474      # NOT OFFICIAL:
     475      CSS_RGBACOLOR = 26
     476
     477      _floattypes = [CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS,
     478                     CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC,
     479                     CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S,
     480                     CSS_HZ, CSS_KHZ, CSS_DIMENSION
     481                     ]
     482      _stringtypes = [CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI]
     483      _countertypes = [CSS_COUNTER]
     484      _recttypes = [CSS_RECT]
     485      _rbgtypes = [CSS_RGBCOLOR, CSS_RGBACOLOR]
     486
     487      _reNumDim = re.compile(ur'^(.*?)([a-z]+|%)$', re.I| re.U|re.X)
     488
     489      # oldtype: newType: converterfunc
     490      _converter = {
     491          # cm <-> mm <-> in, 1 inch is equal to 2.54 centimeters.
     492          # pt <-> pc, the points used by CSS 2.1 are equal to 1/72nd of an inch.
     493          # pc: picas - 1 pica is equal to 12 points
     494          (CSS_CM, CSS_MM): lambda x: x * 10,
     495          (CSS_MM, CSS_CM): lambda x: x / 10,
     496
     497          (CSS_PT, CSS_PC): lambda x: x * 12,
     498          (CSS_PC, CSS_PT): lambda x: x / 12,
     499
     500          (CSS_CM, CSS_IN): lambda x: x / 2.54,
     501          (CSS_IN, CSS_CM): lambda x: x * 2.54,
     502          (CSS_MM, CSS_IN): lambda x: x / 25.4,
     503          (CSS_IN, CSS_MM): lambda x: x * 25.4,
     504
     505          (CSS_IN, CSS_PT): lambda x: x / 72,
     506          (CSS_PT, CSS_IN): lambda x: x * 72,
     507          (CSS_CM, CSS_PT): lambda x: x / 2.54 / 72,
     508          (CSS_PT, CSS_CM): lambda x: x * 72 * 2.54,
     509          (CSS_MM, CSS_PT): lambda x: x / 25.4 / 72,
     510          (CSS_PT, CSS_MM): lambda x: x * 72 * 25.4,
     511
     512          (CSS_IN, CSS_PC): lambda x: x / 72 / 12,
     513          (CSS_PC, CSS_IN): lambda x: x * 12 * 72,
     514          (CSS_CM, CSS_PC): lambda x: x / 2.54 / 72 / 12,
     515          (CSS_PC, CSS_CM): lambda x: x * 12 * 72 * 2.54,
     516          (CSS_MM, CSS_PC): lambda x: x / 25.4 / 72 / 12,
     517          (CSS_PC, CSS_MM): lambda x: x * 12 * 72 * 25.4,
     518
     519          # hz <-> khz
     520          (CSS_KHZ, CSS_HZ): lambda x: x * 1000,
     521          (CSS_HZ, CSS_KHZ): lambda x: x / 1000,
     522          # s <-> ms
     523          (CSS_S, CSS_MS): lambda x: x * 1000,
     524          (CSS_MS, CSS_S): lambda x: x / 1000
     525
     526          # TODO: convert deg <-> rad <-> grad
     527      }
     528
     529 -    def __init__(self, cssText=None, readonly=False, _propertyName=None):
     530          """
     531          see CSSPrimitiveValue.__init__()
     532          """
     533          super(CSSPrimitiveValue, self).__init__(cssText=cssText,
     534                                         readonly=readonly,
     535                                         _propertyName=_propertyName)
     536
     537          #(String representation for unit types, token type of unit type, detail)
     538          # used to detect primitiveType and for __repr__
     539          self._init()
     540
     541 -    def _init(self):
     542          # _unitinfos must be set here as self._prods is not known before
     543          self._unitinfos = [
     544              ('CSS_UNKNOWN', None, None),
     545              ('CSS_NUMBER', self._prods.NUMBER, None),
     546              ('CSS_PERCENTAGE', self._prods.PERCENTAGE, None),
     547              ('CSS_EMS', self._prods.DIMENSION, 'em'),
     548              ('CSS_EXS', self._prods.DIMENSION, 'ex'),
     549              ('CSS_PX', self._prods.DIMENSION, 'px'),
     550              ('CSS_CM', self._prods.DIMENSION, 'cm'),
     551              ('CSS_MM', self._prods.DIMENSION, 'mm'),
     552              ('CSS_IN', self._prods.DIMENSION, 'in'),
     553              ('CSS_PT', self._prods.DIMENSION, 'pt'),
     554              ('CSS_PC', self._prods.DIMENSION, 'pc'),
     555              ('CSS_DEG', self._prods.DIMENSION, 'deg'),
     556              ('CSS_RAD', self._prods.DIMENSION, 'rad'),
     557              ('CSS_GRAD', self._prods.DIMENSION, 'grad'),
     558              ('CSS_MS', self._prods.DIMENSION, 'ms'),
     559              ('CSS_S', self._prods.DIMENSION, 's'),
     560              ('CSS_HZ', self._prods.DIMENSION, 'hz'),
     561              ('CSS_KHZ', self._prods.DIMENSION, 'khz'),
     562              ('CSS_DIMENSION', self._prods.DIMENSION, None),
     563              ('CSS_STRING', self._prods.STRING, None),
     564              ('CSS_URI', self._prods.URI, None),
     565              ('CSS_IDENT', self._prods.IDENT, None),
     566              ('CSS_ATTR', self._prods.FUNCTION, 'attr('),
     567              ('CSS_COUNTER', self._prods.FUNCTION, 'counter('),
     568              ('CSS_RECT', self._prods.FUNCTION, 'rect('),
     569              ('CSS_RGBCOLOR', self._prods.FUNCTION, 'rgb('),
     570              ('CSS_RGBACOLOR', self._prods.FUNCTION, 'rgba('),
     571              ]
     572
     573 -    def __set_primitiveType(self):
     574          """
     575          primitiveType is readonly but is set lazy if accessed
     576          no value is given as self._value is used
     577          """
     578          primitiveType = self.CSS_UNKNOWN
     579          _floatType = False # if unary expect NUMBER DIMENSION or PERCENTAGE
     580          tokenizer = self._tokenize2(self._value)
     581          t = self._nexttoken(tokenizer)
     582          if not t:
     583              self._log.error(u'CSSPrimitiveValue: No value.')
     584
     585          # unary operator:
     586          if self._tokenvalue(t) in (u'-', u'+'):
     587              t = self._nexttoken(tokenizer)
     588              if not t:
     589                  self._log.error(u'CSSPrimitiveValue: No value.')
     590
     591              _floatType = True
     592
     593          # check for font1, "font2" etc which is treated as ONE string
     594          fontstring = 0 # should be at leayst 2
     595          expected = 'ident or string'
     596          tokenizer = self._tokenize2(self._value) # add used tokens again
     597          for token in tokenizer:
     598              val, typ = self._tokenvalue(token, normalize=True), self._type(token)
     599              if expected == 'ident or string' and typ in (
     600                          self._prods.IDENT, self._prods.STRING):
     601                  expected = 'comma'
     602                  fontstring += 1
     603              elif expected == 'comma' and val == ',':
     604                  expected = 'ident or string'
     605                  fontstring += 1
     606              elif typ in (self._prods.S, self._prods.COMMENT):
     607                  continue
     608              else:
     609                  fontstring = False
     610                  break
     611
     612          if fontstring > 2:
     613              # special case: e.g. for font-family: a, b; only COMMA IDENT and STRING
     614              primitiveType = CSSPrimitiveValue.CSS_STRING
     615          elif self._type(t) == self._prods.HASH:
     616              # special case, maybe should be converted to rgb in any case?
     617              primitiveType = CSSPrimitiveValue.CSS_RGBCOLOR
     618          else:
     619              for i, (name, tokentype, search) in enumerate(self._unitinfos):
     620                  val, typ = self._tokenvalue(t, normalize=True), self._type(t)
     621                  if typ == tokentype:
     622                      if typ == self._prods.DIMENSION:
     623                          if not search:
     624                              primitiveType = i
     625                              break
     626                          elif re.match(ur'^[^a-z]*(%s)$' % search, val):
     627                              primitiveType = i
     628                              break
     629                      elif typ == self._prods.FUNCTION:
     630                          if not search:
     631                              primitiveType = i
     632                              break
     633                          elif val.startswith(search):
     634                              primitiveType = i
     635                              break
     636                      else:
     637                          primitiveType = i
     638                          break
     639
     640          if _floatType and primitiveType not in self._floattypes:
     641              # - or + only expected before floattype
     642              primitiveType = self.CSS_UNKNOWN
     643
     644          self._primitiveType = primitiveType
     645
     646 -    def _getPrimitiveType(self):
     647          if not hasattr(self, '_primitivetype'):
     648              self.__set_primitiveType()
     649          return self._primitiveType
     650
     651      primitiveType = property(_getPrimitiveType,
     652          doc="READONLY: The type of the value as defined by the constants specified above.")
     653
     654 -    def _getPrimitiveTypeString(self):
     655          return self._unitinfos[self.primitiveType][0]
     656
     657      primitiveTypeString = property(_getPrimitiveTypeString,
     658                                     doc="Name of primitive type of this value.")
     659
     660 -    def _getCSSPrimitiveTypeString(self, type):
     661          "get TypeString by given type which may be unknown, used by setters"
     662          try:
     663              return self._unitinfos[type][0]
     664          except (IndexError, TypeError):
     665              return u'%r (UNKNOWN TYPE)' % type
     666
     667 -    def __getValDim(self):
     668          "splits self._value in numerical and dimension part"
     669          try:
     670              val, dim = self._reNumDim.findall(self._value)[0]
     671          except IndexError:
     672              val, dim = self._value, u''
     673          try:
     674              val = float(val)
     675          except ValueError:
     676              raise xml.dom.InvalidAccessErr(
     677                  u'CSSPrimitiveValue: No float value %r'
     678                  % (self._value))
     679
     680          return val, dim
     681
     682 -    def getFloatValue(self, unitType):
     683          """
     684          (DOM method) This method is used to get a float value in a
     685          specified unit. If this CSS value doesn't contain a float value
     686          or can't be converted into the specified unit, a DOMException
     687          is raised.
     688
     689          unitType
     690              to get the float value. The unit code can only be a float unit type
     691              (i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM,
     692              CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS,
     693              CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION).
     694
     695          returns not necessarily a float but some cases just an int
     696          e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0``
     697
     698          conversions might return strange values like 1.000000000001
     699          """
     700          if unitType not in self._floattypes:
     701              raise xml.dom.InvalidAccessErr(
     702                  u'unitType Parameter is not a float type')
     703
     704          val, dim = self.__getValDim()
     705
     706          if self.primitiveType != unitType:
     707              try:
     708                  val = self._converter[self.primitiveType, unitType](val)
     709              except KeyError:
     710                  raise xml.dom.InvalidAccessErr(
     711                  u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
     712                  % (self.primitiveTypeString,
     713                     self._getCSSPrimitiveTypeString(unitType)))
     714
     715          if val == int(val):
     716              val = int(val)
     717
     718          return val
     719
     720 -    def setFloatValue(self, unitType, floatValue):
     721          """
     722          (DOM method) A method to set the float value with a specified unit.
     723          If the property attached with this value can not accept the
     724          specified unit or the float value, the value will be unchanged and
     725          a DOMException will be raised.
     726
     727          unitType
     728              a unit code as defined above. The unit code can only be a float
     729              unit type
     730          floatValue
     731              the new float value which does not have to be a float value but
     732              may simple be an int e.g. if setting::
     733
     734                  setFloatValue(CSS_PX, 1)
     735
     736          raises DOMException
     737              - INVALID_ACCESS_ERR: Raised if the attached property doesn't
     738                  support the float value or the unit type.
     739              - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
     740          """
     741          self._checkReadonly()
     742          if unitType not in self._floattypes:
     743              raise xml.dom.InvalidAccessErr(
     744                 u'CSSPrimitiveValue: unitType %r is not a float type' %
     745                 self._getCSSPrimitiveTypeString(unitType))
     746          try:
     747              val = float(floatValue)
     748          except ValueError, e:
     749              raise xml.dom.InvalidAccessErr(
     750                 u'CSSPrimitiveValue: floatValue %r is not a float' %
     751                 floatValue)
     752
     753          oldval, dim = self.__getValDim()
     754
     755          if self.primitiveType != unitType:
     756              # convert if possible
     757              try:
     758                  val = self._converter[
     759                      unitType, self.primitiveType](val)
     760              except KeyError:
     761                  raise xml.dom.InvalidAccessErr(
     762                  u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
     763                  % (self.primitiveTypeString,
     764                     self._getCSSPrimitiveTypeString(unitType)))
     765
     766          if val == int(val):
     767              val = int(val)
     768
     769          self.cssText = '%s%s' % (val, dim)
     770
     771 -    def getStringValue(self):
     772          """
     773          (DOM method) This method is used to get the string value. If the
     774          CSS value doesn't contain a string value, a DOMException is raised.
     775
     776          Some properties (like 'font-family' or 'voice-family')
     777          convert a whitespace separated list of idents to a string.
     778
     779          Only the actual value is returned so e.g. all the following return the
     780          actual value ``a``: url(a), attr(a), "a", 'a'
     781          """
     782          if self.primitiveType not in self._stringtypes:
     783              raise xml.dom.InvalidAccessErr(
     784                  u'CSSPrimitiveValue %r is not a string type'
     785                  % self.primitiveTypeString)
     786
     787          if CSSPrimitiveValue.CSS_STRING == self.primitiveType:
     788              # _stringtokenvalue expects tuple with at least 2
     789              return self._stringtokenvalue((None,self._value))
     790          elif CSSPrimitiveValue.CSS_URI == self.primitiveType:
     791              # _uritokenvalue expects tuple with at least 2
     792              return self._uritokenvalue((None, self._value))
     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 self._SHORTHANDPROPERTIES:
     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      length = property(lambda self: len(self._items),
    1029                  doc="(DOM attribute) The number of CSSValues in the list.")
    1030
    1031 -    def item(self, index):
    1032          """
    1033          (DOM method) Used to retrieve a CSSValue by ordinal index. The
    1034          order in this collection represents the order of the values in the
    1035          CSS style property. If index is greater than or equal to the number
    1036          of values in the list, this returns None.
    1037          """
    1038          try:
    1039              return self._items[index]
    1040          except IndexError:
    1041              return None
    1042
    1043 -    def __iter__(self):
    1044          "CSSValueList is iterable"
    1045          return CSSValueList.__items(self)
    1046
    1047 -    def __items(self):
    1048          "the iterator"
    1049          for i in range (0, self.length):
    1050              yield self.item(i)
    1051
    1052 -    def __str_(self):
    1053          return "<cssutils.css.%s object length=%s at 0x%x>" % (
    1054                  self.__class__.__name__, self.length, id(self))
    1055