Package cssutils :: Package css :: Module cssvalue |
[hide private]
[frames] | no frames]

# Source Code for Module cssutils.css.cssvalue

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