~ubuntu-branches/ubuntu/jaunty/calibre/jaunty-backports

« back to all changes in this revision

Viewing changes to src/cssutils/css/cssvalue.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-01-20 17:14:02 UTC
  • Revision ID: james.westby@ubuntu.com-20090120171402-8y3znf6nokwqe80k
Tags: upstream-0.4.125+dfsg
ImportĀ upstreamĀ versionĀ 0.4.125+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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', 'CSSColor']
 
9
__docformat__ = 'restructuredtext'
 
10
__version__ = '$Id: cssvalue.py 1473 2008-09-15 21:15:54Z cthedot $'
 
11
 
 
12
import re
 
13
import xml.dom
 
14
import cssutils
 
15
from cssutils.profiles import profiles
 
16
from cssutils.prodparser import *
 
17
 
 
18
 
 
19
class CSSValue(cssutils.util.Base2):
 
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
        # TODO:
 
94
        v = []
 
95
        for item in self.seq:
 
96
            type_, val = item.type, item.value
 
97
            if isinstance(val, cssutils.css.CSSComment):
 
98
                # only value here
 
99
                continue
 
100
            elif 'STRING' == type_:
 
101
                v.append(cssutils.ser._string(val))
 
102
            elif 'URI' == type_:
 
103
                v.append(cssutils.ser._uri(val))
 
104
            elif u',' == val:
 
105
                # list of items
 
106
                v.append(u' ')
 
107
                v.append(val)
 
108
            elif isinstance(val, basestring):
 
109
                v.append(val)
 
110
            else: 
 
111
                # maybe CSSPrimitiveValue
 
112
                v.append(val.cssText)
 
113
        if v and u'' == v[-1].strip():
 
114
            # simple strip of joined string does not work for escaped spaces
 
115
            del v[-1]
 
116
        return u''.join(v)
 
117
 
 
118
    def _setValue(self, value):
 
119
        "overwritten by CSSValueList!"
 
120
        self._valueValue = value
 
121
 
 
122
    _value = property(_getValue, _setValue,
 
123
                doc="Actual cssText value of this CSSValue.")
 
124
 
 
125
    def _getCssText(self):
 
126
        return cssutils.ser.do_css_CSSValue(self)
 
127
 
 
128
    def _setCssText(self, cssText):
 
129
        """
 
130
        Format
 
131
        ======
 
132
        ::
 
133
 
 
134
            unary_operator
 
135
              : '-' | '+'
 
136
              ;
 
137
            operator
 
138
              : '/' S* | ',' S* | /* empty */
 
139
              ;
 
140
            expr
 
141
              : term [ operator term ]*
 
142
              ;
 
143
            term
 
144
              : unary_operator?
 
145
                [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
 
146
                  TIME S* | FREQ S* ]
 
147
              | STRING S* | IDENT S* | URI S* | hexcolor | function
 
148
              ;
 
149
            function
 
150
              : FUNCTION S* expr ')' S*
 
151
              ;
 
152
            /*
 
153
             * There is a constraint on the color that it must
 
154
             * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
 
155
             * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
 
156
             */
 
157
            hexcolor
 
158
              : HASH S*
 
159
              ;
 
160
 
 
161
        DOMException on setting
 
162
 
 
163
        - SYNTAX_ERR: (self)
 
164
          Raised if the specified CSS string value has a syntax error
 
165
          (according to the attached property) or is unparsable.
 
166
        - TODO: INVALID_MODIFICATION_ERR:
 
167
          Raised if the specified CSS string value represents a different
 
168
          type of values than the values allowed by the CSS property.
 
169
        - NO_MODIFICATION_ALLOWED_ERR: (self)
 
170
          Raised if this value is readonly.
 
171
        """
 
172
        self._checkReadonly()
 
173
 
 
174
        # for closures: must be a mutable
 
175
        new = {'rawvalues': [], # used for validation
 
176
               'values': [],
 
177
               'commas': 0,
 
178
               'valid': True,
 
179
               'wellformed': True }
 
180
 
 
181
        def _S(expected, seq, token, tokenizer=None):
 
182
            type_, val, line, col = token
 
183
            new['rawvalues'].append(u' ')
 
184
            if expected == 'operator': #expected.endswith('operator'):
 
185
                seq.append(u' ', 'separator', line=line, col=col)
 
186
                return 'term or operator'
 
187
            elif expected.endswith('S'):
 
188
                return 'term or S'
 
189
            else:
 
190
                return expected
 
191
 
 
192
        def _char(expected, seq, token, tokenizer=None):
 
193
            type_, val, line, col = token
 
194
            new['rawvalues'].append(val)
 
195
            
 
196
            if 'funcend' == expected and u')' == val:
 
197
                # end of FUNCTION
 
198
                seq.appendToVal(val)
 
199
                new['values'].append(seq[-1])
 
200
                return 'operator'
 
201
 
 
202
            elif expected in (')', ']', '}') and expected == val:
 
203
                # end of any block: (), [], {}
 
204
                seq.appendToVal(val)
 
205
                return 'operator'
 
206
 
 
207
            elif expected in ('funcend', ')', ']', '}'):
 
208
                # content of func or block: (), [], {}
 
209
                seq.appendToVal(val)
 
210
                return expected
 
211
 
 
212
            elif expected.endswith('operator') and ',' == val:
 
213
                # term, term; remove all WS between terms!!!
 
214
                new['commas'] += 1
 
215
                if seq and seq[-1].type == 'separator':
 
216
                    seq.replace(-1, val, type_, line=line, col=col)
 
217
                else:
 
218
                    seq.append(val, type_, line=line, col=col)
 
219
                return 'term or S'
 
220
 
 
221
            elif expected.endswith('operator') and '/' == val:
 
222
                # term / term
 
223
                if seq and seq[-1].value == u' ':
 
224
                    old = seq[-1]
 
225
                    seq.replace(-1, val, old.type, old.line, old.col)
 
226
                    #seq[-1] = val
 
227
                else:
 
228
                    seq.append(val, type_, line=line, col=col)
 
229
                return 'term or S'
 
230
 
 
231
            elif expected.startswith('term') and u'(' == val:
 
232
                # start of ( any* ) block
 
233
                seq.append(val, type_, line=line, col=col)
 
234
                return ')'
 
235
            elif expected.startswith('term') and u'[' == val:
 
236
                # start of [ any* ] block
 
237
                seq.append(val, type_, line=line, col=col)
 
238
                return ']'
 
239
            elif expected.startswith('term') and u'{' == val:
 
240
                # start of { any* } block
 
241
                seq.append(val, type_, line=line, col=col)
 
242
                return '}'
 
243
            elif expected.startswith('term') and u'+' == val:
 
244
                # unary operator "+"
 
245
                seq.append(val, type_, line=line, col=col)
 
246
                new['values'].append(val)
 
247
                return 'number percentage dimension'
 
248
            elif expected.startswith('term') and u'-' == val:
 
249
                # unary "-" operator
 
250
                seq.append(val, type_, line=line, col=col)
 
251
                new['values'].append(val)
 
252
                return 'number percentage dimension'
 
253
            elif expected.startswith('term') and u'/' == val:
 
254
                # font-size/line-height separator
 
255
                seq.append(val, type_, line=line, col=col)
 
256
                new['values'].append(val)
 
257
                return 'number percentage dimension'
 
258
            else:
 
259
                new['wellformed'] = False
 
260
                self._log.error(u'CSSValue: Unexpected char.', token)
 
261
                return expected
 
262
 
 
263
        def _number_percentage_dimension(expected, seq, token, tokenizer=None):
 
264
            # NUMBER PERCENTAGE DIMENSION after -/+ or operator
 
265
            type_, val, line, col = token
 
266
            new['rawvalues'].append(val)
 
267
            if expected.startswith('term') or expected == 'number percentage dimension':
 
268
                # normal value
 
269
                if new['values'] and new['values'][-1] in (u'-', u'+'):
 
270
                    new['values'][-1] += val
 
271
                else:
 
272
                    new['values'].append(val)
 
273
                seq.append(val, type_, line=line, col=col)
 
274
                return 'operator'
 
275
            elif 'operator' == expected:
 
276
                # expected S but token which is ok
 
277
                if new['values'] and new['values'][-1] in (u'-', u'+'):
 
278
                    new['values'][-1] += val
 
279
                else:
 
280
                    new['values'].append(u' ')
 
281
                    seq.append(u' ', 'separator') # self._prods.S
 
282
                    new['values'].append(val)
 
283
                seq.append(val, type_, line=line, col=col)
 
284
                return 'operator'
 
285
            elif expected in ('funcend', ')', ']', '}'):
 
286
                # a block
 
287
                seq.appendToVal(val)
 
288
                return expected
 
289
            else:
 
290
                new['wellformed'] = False
 
291
                self._log.error(u'CSSValue: Unexpected token.', token)
 
292
                return expected
 
293
 
 
294
        def _string_ident_uri(expected, seq, token, tokenizer=None):
 
295
            # STRING IDENT URI
 
296
            type_, val, line, col = token
 
297
 
 
298
            new['rawvalues'].append(val)
 
299
            if expected.startswith('term'):
 
300
                # normal value
 
301
                if self._prods.STRING == type_:
 
302
                    val = self._stringtokenvalue(token)
 
303
                elif self._prods.URI == type_:
 
304
                    val = self._uritokenvalue(token)
 
305
                new['values'].append(val)
 
306
                seq.append(val, type_, line=line, col=col)
 
307
                return 'operator'
 
308
            elif 'operator' == expected:
 
309
                # expected S but still ok
 
310
                if self._prods.STRING == type_:
 
311
                    val = self._stringtokenvalue(token)
 
312
                elif self._prods.URI == type_:
 
313
                    val = self._uritokenvalue(token)
 
314
                new['values'].append(u' ')
 
315
                new['values'].append(val)
 
316
                seq.append(u' ', 'separator') # self._prods.S
 
317
                seq.append(val, type_, line=line, col=col)
 
318
                return 'operator'
 
319
            elif expected in ('funcend', ')', ']', '}'):
 
320
                # a block
 
321
                seq.appendToVal(val)
 
322
                return expected
 
323
            else:
 
324
                new['wellformed'] = False
 
325
                self._log.error(u'CSSValue: Unexpected token.', token)
 
326
                return expected
 
327
 
 
328
        def _hash(expected, seq, token, tokenizer=None):
 
329
            #  HASH
 
330
            type_, val, line, col = token
 
331
            new['rawvalues'].append(val)
 
332
 
 
333
            val = CSSColor(cssText=token)
 
334
            type_ = type(val)
 
335
            if expected.startswith('term'):
 
336
                # normal value
 
337
                new['values'].append(val)
 
338
                seq.append(val, type_, line=line, col=col)
 
339
                return 'operator'
 
340
            elif 'operator' == expected:
 
341
                # expected S but still ok
 
342
                new['values'].append(u' ')
 
343
                new['values'].append(val)
 
344
                seq.append(u' ', 'separator') # self._prods.S
 
345
                seq.append(val, type_, line=line, col=col)
 
346
                return 'operator'
 
347
            elif expected in ('funcend', ')', ']', '}'):
 
348
                # a block
 
349
                seq.appendToVal(val)
 
350
                return expected
 
351
            else:
 
352
                new['wellformed'] = False
 
353
                self._log.error(u'CSSValue: Unexpected token.', token)
 
354
                return expected
 
355
            
 
356
        def _function(expected, seq, token, tokenizer=None):
 
357
            # FUNCTION incl colors
 
358
            type_, val, line, col = token
 
359
            
 
360
            if self._normalize(val) in ('rgb(', 'rgba(', 'hsl(', 'hsla('):
 
361
                # a CSSColor
 
362
                val = CSSColor(cssText=(token, tokenizer))
 
363
                type_ = type(val)
 
364
                seq.append(val, type_, line=line, col=col)
 
365
                new['values'].append(val)
 
366
                new['rawvalues'].append(val.cssText)
 
367
                return 'operator'
 
368
                
 
369
            new['rawvalues'].append(val)
 
370
 
 
371
            if expected.startswith('term'):
 
372
                # normal value but add if funcend is found
 
373
                seq.append(val, type_, line=line, col=col)
 
374
                return 'funcend'
 
375
            elif 'operator' == expected:
 
376
                # normal value but add if funcend is found
 
377
                seq.append(u' ', 'separator') # self._prods.S
 
378
                seq.append(val, type_, line=line, col=col)
 
379
                return 'funcend'
 
380
            elif expected in ('funcend', ')', ']', '}'):
 
381
                # a block
 
382
                seq.appendToVal(val)
 
383
                return expected
 
384
            else:
 
385
                new['wellformed'] = False
 
386
                self._log.error(u'CSSValue: Unexpected token.', token)
 
387
                return expected
 
388
 
 
389
        tokenizer = self._tokenize2(cssText)
 
390
        
 
391
        linetoken = self._nexttoken(tokenizer)
 
392
        if not linetoken:
 
393
            self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
 
394
                self._valuestr(cssText))
 
395
        else:
 
396
            newseq = self._tempSeq() # []
 
397
            wellformed, expected = self._parse(expected='term',
 
398
                seq=newseq, tokenizer=tokenizer,initialtoken=linetoken,
 
399
                productions={'S': _S,
 
400
                             'CHAR': _char,
 
401
 
 
402
                             'NUMBER': _number_percentage_dimension,
 
403
                             'PERCENTAGE': _number_percentage_dimension,
 
404
                             'DIMENSION': _number_percentage_dimension,
 
405
 
 
406
                             'STRING': _string_ident_uri,
 
407
                             'IDENT': _string_ident_uri,
 
408
                             'URI': _string_ident_uri,
 
409
                             'HASH': _hash,
 
410
                             'UNICODE-RANGE': _string_ident_uri, #?
 
411
 
 
412
                             'FUNCTION': _function
 
413
                              })
 
414
 
 
415
            wellformed = wellformed and new['wellformed']
 
416
 
 
417
            # post conditions
 
418
            def lastseqvalue(seq):
 
419
                """find last actual value in seq, not COMMENT!"""
 
420
                for i, item in enumerate(reversed(seq)):
 
421
                    if 'COMMENT' != item.type:
 
422
                        return len(seq)-1-i, item.value
 
423
                else:
 
424
                    return 0, None
 
425
            lastpos, lastval = lastseqvalue(newseq)
 
426
            
 
427
            if expected.startswith('term') and lastval != u' '  or (
 
428
               expected in ('funcend', ')', ']', '}')):
 
429
                wellformed = False
 
430
                self._log.error(u'CSSValue: Incomplete value: %r.' %
 
431
                self._valuestr(cssText))
 
432
 
 
433
            if not new['values']:
 
434
                wellformed = False
 
435
                self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
 
436
                self._valuestr(cssText))
 
437
 
 
438
            else:
 
439
                # remove last token if 'separator'
 
440
                if lastval == u' ':
 
441
                    del newseq[lastpos]
 
442
                
 
443
                self._linetoken = linetoken # used for line report
 
444
                self._setSeq(newseq)
 
445
                                
 
446
                self.valid = self._validate(u''.join(new['rawvalues']))
 
447
 
 
448
                if len(new['values']) == 1 and new['values'][0] == u'inherit':
 
449
                    self._value = u'inherit'
 
450
                    self._cssValueType = CSSValue.CSS_INHERIT
 
451
                    self.__class__ = CSSValue # reset
 
452
                elif len(new['values']) == 1:
 
453
                    self.__class__ = CSSPrimitiveValue
 
454
                    self._init() #inits CSSPrimitiveValue
 
455
                elif len(new['values']) > 1 and\
 
456
                     len(new['values']) == new['commas'] + 1:
 
457
                    # e.g. value for font-family: a, b
 
458
                    self.__class__ = CSSPrimitiveValue
 
459
                    self._init() #inits CSSPrimitiveValue
 
460
                elif len(new['values']) > 1:
 
461
                    # separated by S
 
462
                    self.__class__ = CSSValueList
 
463
                    self._init() # inits CSSValueList
 
464
                else:
 
465
                    self._cssValueType = CSSValue.CSS_CUSTOM
 
466
                    self.__class__ = CSSValue # reset
 
467
 
 
468
            self.wellformed = wellformed
 
469
 
 
470
    cssText = property(_getCssText, _setCssText,
 
471
        doc="A string representation of the current value.")
 
472
 
 
473
    def _getCssValueType(self):
 
474
        if hasattr(self, '_cssValueType'):
 
475
            return self._cssValueType
 
476
 
 
477
    cssValueType = property(_getCssValueType,
 
478
        doc="A (readonly) code defining the type of the value as defined above.")
 
479
 
 
480
    def _getCssValueTypeString(self):
 
481
        t = self.cssValueType
 
482
        if t is not None: # may be 0!
 
483
            return CSSValue._typestrings[t]
 
484
        else:
 
485
            return None
 
486
 
 
487
    cssValueTypeString = property(_getCssValueTypeString,
 
488
        doc="cssutils: Name of cssValueType of this CSSValue (readonly).")
 
489
 
 
490
    def _validate(self, value=None, profile=None):
 
491
        """
 
492
        validates value against _propertyName context if given
 
493
        """
 
494
        valid = False
 
495
        if self._value:
 
496
            if self._propertyName and self._propertyName in profiles.propertiesByProfile():
 
497
                valid, validprofile = \
 
498
                        profiles.validateWithProfile(self._propertyName,
 
499
                                                     self._normalize(self._value))
 
500
                if not validprofile:
 
501
                    validprofile = u''
 
502
                    
 
503
                if not valid:
 
504
                    self._log.warn(
 
505
                        u'CSSValue: Invalid value for %s property "%s: %s".' %
 
506
                        (validprofile, self._propertyName, 
 
507
                         self._value), neverraise=True)
 
508
                elif profile and validprofile != profile:
 
509
                    self._log.warn(
 
510
                        u'CSSValue: Invalid value for %s property "%s: %s" but valid %s property.' %
 
511
                        (profile, self._propertyName, self._value, 
 
512
                         validprofile), neverraise=True)
 
513
                else:
 
514
                    self._log.debug(
 
515
                        u'CSSValue: Found valid %s property "%s: %s".' %
 
516
                        (validprofile, self._propertyName, self._value), 
 
517
                        neverraise=True)
 
518
            else:
 
519
                self._log.debug(u'CSSValue: Unable to validate as no or unknown property context set for value: %r'
 
520
                                % self._value, neverraise=True)
 
521
        
 
522
        if not value:
 
523
            # if value is given this should not be saved
 
524
            self.valid = valid
 
525
        return valid
 
526
 
 
527
    def _get_propertyName(self):
 
528
        return self.__propertyName
 
529
 
 
530
    def _set_propertyName(self, _propertyName):
 
531
        self.__propertyName = _propertyName
 
532
        self._validate()
 
533
 
 
534
    _propertyName = property(_get_propertyName, _set_propertyName,
 
535
        doc="cssutils: Property this values is validated against")
 
536
 
 
537
    def __repr__(self):
 
538
        return "cssutils.css.%s(%r, _propertyName=%r)" % (
 
539
                self.__class__.__name__, self.cssText, self._propertyName)
 
540
 
 
541
    def __str__(self):
 
542
        return "<cssutils.css.%s object cssValueType=%r cssText=%r propname=%r valid=%r at 0x%x>" % (
 
543
                self.__class__.__name__, self.cssValueTypeString,
 
544
                self.cssText, self._propertyName, self.valid, id(self))
 
545
 
 
546
 
 
547
class CSSPrimitiveValue(CSSValue):
 
548
    """
 
549
    represents a single CSS Value.  May be used to determine the value of a
 
550
    specific style property currently set in a block or to set a specific
 
551
    style property explicitly within the block. Might be obtained from the
 
552
    getPropertyCSSValue method of CSSStyleDeclaration.
 
553
 
 
554
    Conversions are allowed between absolute values (from millimeters to
 
555
    centimeters, from degrees to radians, and so on) but not between
 
556
    relative values. (For example, a pixel value cannot be converted to a
 
557
    centimeter value.) Percentage values can't be converted since they are
 
558
    relative to the parent value (or another property value). There is one
 
559
    exception for color percentage values: since a color percentage value
 
560
    is relative to the range 0-255, a color percentage value can be
 
561
    converted to a number; (see also the RGBColor interface).
 
562
    """
 
563
    # constant: type of this CSSValue class
 
564
    cssValueType = CSSValue.CSS_PRIMITIVE_VALUE
 
565
 
 
566
    # An integer indicating which type of unit applies to the value.
 
567
    CSS_UNKNOWN = 0 # only obtainable via cssText
 
568
    CSS_NUMBER = 1
 
569
    CSS_PERCENTAGE = 2
 
570
    CSS_EMS = 3
 
571
    CSS_EXS = 4
 
572
    CSS_PX = 5
 
573
    CSS_CM = 6
 
574
    CSS_MM = 7
 
575
    CSS_IN = 8
 
576
    CSS_PT = 9
 
577
    CSS_PC = 10
 
578
    CSS_DEG = 11
 
579
    CSS_RAD = 12
 
580
    CSS_GRAD = 13
 
581
    CSS_MS = 14
 
582
    CSS_S = 15
 
583
    CSS_HZ = 16
 
584
    CSS_KHZ = 17
 
585
    CSS_DIMENSION = 18
 
586
    CSS_STRING = 19
 
587
    CSS_URI = 20
 
588
    CSS_IDENT = 21
 
589
    CSS_ATTR = 22
 
590
    CSS_COUNTER = 23
 
591
    CSS_RECT = 24
 
592
    CSS_RGBCOLOR = 25
 
593
    # NOT OFFICIAL:
 
594
    CSS_RGBACOLOR = 26
 
595
 
 
596
    _floattypes = [CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS,
 
597
                   CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC,
 
598
                   CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S,
 
599
                   CSS_HZ, CSS_KHZ, CSS_DIMENSION
 
600
                   ]
 
601
    _stringtypes = [CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI]
 
602
    _countertypes = [CSS_COUNTER]
 
603
    _recttypes = [CSS_RECT]
 
604
    _rbgtypes = [CSS_RGBCOLOR, CSS_RGBACOLOR]
 
605
 
 
606
    _reNumDim = re.compile(ur'^(.*?)([a-z]+|%)$', re.I| re.U|re.X)
 
607
 
 
608
    # oldtype: newType: converterfunc
 
609
    _converter = {
 
610
        # cm <-> mm <-> in, 1 inch is equal to 2.54 centimeters.
 
611
        # pt <-> pc, the points used by CSS 2.1 are equal to 1/72nd of an inch.
 
612
        # pc: picas - 1 pica is equal to 12 points
 
613
        (CSS_CM, CSS_MM): lambda x: x * 10,
 
614
        (CSS_MM, CSS_CM): lambda x: x / 10,
 
615
 
 
616
        (CSS_PT, CSS_PC): lambda x: x * 12,
 
617
        (CSS_PC, CSS_PT): lambda x: x / 12,
 
618
 
 
619
        (CSS_CM, CSS_IN): lambda x: x / 2.54,
 
620
        (CSS_IN, CSS_CM): lambda x: x * 2.54,
 
621
        (CSS_MM, CSS_IN): lambda x: x / 25.4,
 
622
        (CSS_IN, CSS_MM): lambda x: x * 25.4,
 
623
 
 
624
        (CSS_IN, CSS_PT): lambda x: x / 72,
 
625
        (CSS_PT, CSS_IN): lambda x: x * 72,
 
626
        (CSS_CM, CSS_PT): lambda x: x / 2.54 / 72,
 
627
        (CSS_PT, CSS_CM): lambda x: x * 72 * 2.54,
 
628
        (CSS_MM, CSS_PT): lambda x: x / 25.4 / 72,
 
629
        (CSS_PT, CSS_MM): lambda x: x * 72 * 25.4,
 
630
 
 
631
        (CSS_IN, CSS_PC): lambda x: x / 72 / 12,
 
632
        (CSS_PC, CSS_IN): lambda x: x * 12 * 72,
 
633
        (CSS_CM, CSS_PC): lambda x: x / 2.54 / 72 / 12,
 
634
        (CSS_PC, CSS_CM): lambda x: x * 12 * 72 * 2.54,
 
635
        (CSS_MM, CSS_PC): lambda x: x / 25.4 / 72 / 12,
 
636
        (CSS_PC, CSS_MM): lambda x: x * 12 * 72 * 25.4,
 
637
 
 
638
        # hz <-> khz
 
639
        (CSS_KHZ, CSS_HZ): lambda x: x * 1000,
 
640
        (CSS_HZ, CSS_KHZ): lambda x: x / 1000,
 
641
        # s <-> ms
 
642
        (CSS_S, CSS_MS): lambda x: x * 1000,
 
643
        (CSS_MS, CSS_S): lambda x: x / 1000
 
644
 
 
645
        # TODO: convert deg <-> rad <-> grad
 
646
    }
 
647
 
 
648
    def __init__(self, cssText=None, readonly=False, _propertyName=None):
 
649
        """
 
650
        see CSSPrimitiveValue.__init__()
 
651
        """
 
652
        super(CSSPrimitiveValue, self).__init__(cssText=cssText,
 
653
                                       readonly=readonly,
 
654
                                       _propertyName=_propertyName)
 
655
 
 
656
        #(String representation for unit types, token type of unit type, detail)
 
657
        # used to detect primitiveType and for __repr__
 
658
        self._init()
 
659
 
 
660
    def _init(self):
 
661
        # _unitinfos must be set here as self._prods is not known before
 
662
        self._unitinfos = [
 
663
            ('CSS_UNKNOWN', None, None),
 
664
            ('CSS_NUMBER', self._prods.NUMBER, None),
 
665
            ('CSS_PERCENTAGE', self._prods.PERCENTAGE, None),
 
666
            ('CSS_EMS', self._prods.DIMENSION, 'em'),
 
667
            ('CSS_EXS', self._prods.DIMENSION, 'ex'),
 
668
            ('CSS_PX', self._prods.DIMENSION, 'px'),
 
669
            ('CSS_CM', self._prods.DIMENSION, 'cm'),
 
670
            ('CSS_MM', self._prods.DIMENSION, 'mm'),
 
671
            ('CSS_IN', self._prods.DIMENSION, 'in'),
 
672
            ('CSS_PT', self._prods.DIMENSION, 'pt'),
 
673
            ('CSS_PC', self._prods.DIMENSION, 'pc'),
 
674
            ('CSS_DEG', self._prods.DIMENSION, 'deg'),
 
675
            ('CSS_RAD', self._prods.DIMENSION, 'rad'),
 
676
            ('CSS_GRAD', self._prods.DIMENSION, 'grad'),
 
677
            ('CSS_MS', self._prods.DIMENSION, 'ms'),
 
678
            ('CSS_S', self._prods.DIMENSION, 's'),
 
679
            ('CSS_HZ', self._prods.DIMENSION, 'hz'),
 
680
            ('CSS_KHZ', self._prods.DIMENSION, 'khz'),
 
681
            ('CSS_DIMENSION', self._prods.DIMENSION, None),
 
682
            ('CSS_STRING', self._prods.STRING, None),
 
683
            ('CSS_URI', self._prods.URI, None),
 
684
            ('CSS_IDENT', self._prods.IDENT, None),
 
685
            ('CSS_ATTR', self._prods.FUNCTION, 'attr('),
 
686
            ('CSS_COUNTER', self._prods.FUNCTION, 'counter('),
 
687
            ('CSS_RECT', self._prods.FUNCTION, 'rect('),
 
688
            ('CSS_RGBCOLOR', self._prods.FUNCTION, 'rgb('),
 
689
            ('CSS_RGBACOLOR', self._prods.FUNCTION, 'rgba('),
 
690
            ]
 
691
 
 
692
    def __set_primitiveType(self):
 
693
        """
 
694
        primitiveType is readonly but is set lazy if accessed
 
695
        no value is given as self._value is used
 
696
        """
 
697
        primitiveType = self.CSS_UNKNOWN
 
698
        
 
699
        for item in self.seq:
 
700
            if item.type == self._prods.URI:
 
701
                primitiveType = self.CSS_URI
 
702
                break
 
703
            elif item.type == self._prods.STRING:
 
704
                primitiveType = self.CSS_STRING
 
705
                break
 
706
        else:
 
707
            
 
708
            _floatType = False # if unary expect NUMBER DIMENSION or PERCENTAGE
 
709
            tokenizer = self._tokenize2(self._value)
 
710
            t = self._nexttoken(tokenizer)
 
711
            if not t:
 
712
                self._log.error(u'CSSPrimitiveValue: No value.')
 
713
    
 
714
            # unary operator:
 
715
            if self._tokenvalue(t) in (u'-', u'+'):
 
716
                t = self._nexttoken(tokenizer)
 
717
                if not t:
 
718
                    self._log.error(u'CSSPrimitiveValue: No value.')
 
719
    
 
720
                _floatType = True
 
721
    
 
722
            # check for font1, "font2" etc which is treated as ONE string
 
723
            fontstring = 0 # should be at leayst 2
 
724
            expected = 'ident or string'
 
725
            tokenizer = self._tokenize2(self._value) # add used tokens again
 
726
            for token in tokenizer:
 
727
                val, typ = self._tokenvalue(token, normalize=True), self._type(token)
 
728
                if expected == 'ident or string' and typ in (
 
729
                            self._prods.IDENT, self._prods.STRING):
 
730
                    expected = 'comma'
 
731
                    fontstring += 1
 
732
                elif expected == 'comma' and val == ',':
 
733
                    expected = 'ident or string'
 
734
                    fontstring += 1
 
735
                elif typ in ('separator', self._prods.S, self._prods.COMMENT):
 
736
                    continue
 
737
                else:
 
738
                    fontstring = False
 
739
                    break
 
740
    
 
741
            if fontstring > 2:
 
742
                # special case: e.g. for font-family: a, b; only COMMA IDENT and STRING
 
743
                primitiveType = CSSPrimitiveValue.CSS_STRING
 
744
            elif self._type(t) == self._prods.HASH:
 
745
                # special case, maybe should be converted to rgb in any case?
 
746
                primitiveType = CSSPrimitiveValue.CSS_RGBCOLOR
 
747
            else:
 
748
                for i, (name, tokentype, search) in enumerate(self._unitinfos):
 
749
                    val, typ = self._tokenvalue(t, normalize=True), self._type(t)
 
750
                    if typ == tokentype:
 
751
                        if typ == self._prods.DIMENSION:
 
752
                            if not search:
 
753
                                primitiveType = i
 
754
                                break
 
755
                            elif re.match(ur'^[^a-z]*(%s)$' % search, val):
 
756
                                primitiveType = i
 
757
                                break
 
758
                        elif typ == self._prods.FUNCTION:
 
759
                            if not search:
 
760
                                primitiveType = i
 
761
                                break
 
762
                            elif val.startswith(search):
 
763
                                primitiveType = i
 
764
                                break
 
765
                        else:
 
766
                            primitiveType = i
 
767
                            break
 
768
    
 
769
            if _floatType and primitiveType not in self._floattypes:
 
770
                # - or + only expected before floattype
 
771
                primitiveType = self.CSS_UNKNOWN
 
772
 
 
773
        self._primitiveType = primitiveType
 
774
 
 
775
    def _getPrimitiveType(self):
 
776
        if not hasattr(self, '_primitivetype'):
 
777
            self.__set_primitiveType()
 
778
        return self._primitiveType
 
779
 
 
780
    primitiveType = property(_getPrimitiveType,
 
781
        doc="READONLY: The type of the value as defined by the constants specified above.")
 
782
 
 
783
    def _getPrimitiveTypeString(self):
 
784
        return self._unitinfos[self.primitiveType][0]
 
785
 
 
786
    primitiveTypeString = property(_getPrimitiveTypeString,
 
787
                                   doc="Name of primitive type of this value.")
 
788
 
 
789
    def _getCSSPrimitiveTypeString(self, type):
 
790
        "get TypeString by given type which may be unknown, used by setters"
 
791
        try:
 
792
            return self._unitinfos[type][0]
 
793
        except (IndexError, TypeError):
 
794
            return u'%r (UNKNOWN TYPE)' % type
 
795
 
 
796
    def __getValDim(self):
 
797
        "splits self._value in numerical and dimension part"
 
798
        try:
 
799
            val, dim = self._reNumDim.findall(self._value)[0]
 
800
        except IndexError:
 
801
            val, dim = self._value, u''
 
802
        try:
 
803
            val = float(val)
 
804
        except ValueError:
 
805
            raise xml.dom.InvalidAccessErr(
 
806
                u'CSSPrimitiveValue: No float value %r'
 
807
                % (self._value))
 
808
 
 
809
        return val, dim
 
810
 
 
811
    def getFloatValue(self, unitType=None):
 
812
        """
 
813
        (DOM method) This method is used to get a float value in a
 
814
        specified unit. If this CSS value doesn't contain a float value
 
815
        or can't be converted into the specified unit, a DOMException
 
816
        is raised.
 
817
 
 
818
        unitType
 
819
            to get the float value. The unit code can only be a float unit type
 
820
            (i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM,
 
821
            CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS,
 
822
            CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION) or None in which case
 
823
            the current dimension is used.
 
824
 
 
825
        returns not necessarily a float but some cases just an integer
 
826
        e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0``
 
827
 
 
828
        conversions might return strange values like 1.000000000001
 
829
        """
 
830
        if unitType is not None and unitType not in self._floattypes:
 
831
            raise xml.dom.InvalidAccessErr(
 
832
                u'unitType Parameter is not a float type')
 
833
 
 
834
        val, dim = self.__getValDim()
 
835
 
 
836
        if unitType is not None and self.primitiveType != unitType:
 
837
            # convert if needed
 
838
            try:
 
839
                val = self._converter[self.primitiveType, unitType](val)
 
840
            except KeyError:
 
841
                raise xml.dom.InvalidAccessErr(
 
842
                u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
 
843
                % (self.primitiveTypeString,
 
844
                   self._getCSSPrimitiveTypeString(unitType)))
 
845
 
 
846
        if val == int(val):
 
847
            val = int(val)
 
848
 
 
849
        return val
 
850
 
 
851
    def setFloatValue(self, unitType, floatValue):
 
852
        """
 
853
        (DOM method) A method to set the float value with a specified unit.
 
854
        If the property attached with this value can not accept the
 
855
        specified unit or the float value, the value will be unchanged and
 
856
        a DOMException will be raised.
 
857
 
 
858
        unitType
 
859
            a unit code as defined above. The unit code can only be a float
 
860
            unit type
 
861
        floatValue
 
862
            the new float value which does not have to be a float value but
 
863
            may simple be an int e.g. if setting::
 
864
 
 
865
                setFloatValue(CSS_PX, 1)
 
866
 
 
867
        raises DOMException
 
868
            - INVALID_ACCESS_ERR: Raised if the attached property doesn't
 
869
                support the float value or the unit type.
 
870
            - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
 
871
        """
 
872
        self._checkReadonly()
 
873
        if unitType not in self._floattypes:
 
874
            raise xml.dom.InvalidAccessErr(
 
875
               u'CSSPrimitiveValue: unitType %r is not a float type' %
 
876
               self._getCSSPrimitiveTypeString(unitType))
 
877
        try:
 
878
            val = float(floatValue)
 
879
        except ValueError, e:
 
880
            raise xml.dom.InvalidAccessErr(
 
881
               u'CSSPrimitiveValue: floatValue %r is not a float' %
 
882
               floatValue)
 
883
 
 
884
        oldval, dim = self.__getValDim()
 
885
 
 
886
        if self.primitiveType != unitType:
 
887
            # convert if possible
 
888
            try:
 
889
                val = self._converter[
 
890
                    unitType, self.primitiveType](val)
 
891
            except KeyError:
 
892
                raise xml.dom.InvalidAccessErr(
 
893
                u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
 
894
                % (self.primitiveTypeString,
 
895
                   self._getCSSPrimitiveTypeString(unitType)))
 
896
 
 
897
        if val == int(val):
 
898
            val = int(val)
 
899
 
 
900
        self.cssText = '%s%s' % (val, dim)
 
901
 
 
902
    def getStringValue(self):
 
903
        """
 
904
        (DOM method) This method is used to get the string value. If the
 
905
        CSS value doesn't contain a string value, a DOMException is raised.
 
906
 
 
907
        Some properties (like 'font-family' or 'voice-family')
 
908
        convert a whitespace separated list of idents to a string.
 
909
 
 
910
        Only the actual value is returned so e.g. all the following return the
 
911
        actual value ``a``: url(a), attr(a), "a", 'a'
 
912
        """
 
913
        if self.primitiveType not in self._stringtypes:
 
914
            raise xml.dom.InvalidAccessErr(
 
915
                u'CSSPrimitiveValue %r is not a string type'
 
916
                % self.primitiveTypeString)
 
917
 
 
918
        if CSSPrimitiveValue.CSS_STRING == self.primitiveType:
 
919
            # _stringtokenvalue expects tuple with at least 2
 
920
            return self._stringtokenvalue((None,self._value))
 
921
        elif CSSPrimitiveValue.CSS_URI == self.primitiveType:
 
922
            # _uritokenvalue expects tuple with at least 2
 
923
            return self._uritokenvalue((None, self._value))
 
924
        elif CSSPrimitiveValue.CSS_ATTR == self.primitiveType:
 
925
            return self._value[5:-1]
 
926
        else:
 
927
            return self._value
 
928
 
 
929
    def setStringValue(self, stringType, stringValue):
 
930
        """
 
931
        (DOM method) A method to set the string value with the specified
 
932
        unit. If the property attached to this value can't accept the
 
933
        specified unit or the string value, the value will be unchanged and
 
934
        a DOMException will be raised.
 
935
 
 
936
        stringType
 
937
            a string code as defined above. The string code can only be a
 
938
            string unit type (i.e. CSS_STRING, CSS_URI, CSS_IDENT, and
 
939
            CSS_ATTR).
 
940
        stringValue
 
941
            the new string value
 
942
            Only the actual value is expected so for (CSS_URI, "a") the
 
943
            new value will be ``url(a)``. For (CSS_STRING, "'a'")
 
944
            the new value will be ``"\\'a\\'"`` as the surrounding ``'`` are
 
945
            not part of the string value
 
946
 
 
947
        raises
 
948
            DOMException
 
949
 
 
950
            - INVALID_ACCESS_ERR: Raised if the CSS value doesn't contain a
 
951
              string value or if the string value can't be converted into
 
952
              the specified unit.
 
953
 
 
954
            - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
 
955
        """
 
956
        self._checkReadonly()
 
957
        # self not stringType
 
958
        if self.primitiveType not in self._stringtypes:
 
959
            raise xml.dom.InvalidAccessErr(
 
960
                u'CSSPrimitiveValue %r is not a string type'
 
961
                % self.primitiveTypeString)
 
962
        # given stringType is no StringType
 
963
        if stringType not in self._stringtypes:
 
964
            raise xml.dom.InvalidAccessErr(
 
965
                u'CSSPrimitiveValue: stringType %s is not a string type'
 
966
                % self._getCSSPrimitiveTypeString(stringType))
 
967
 
 
968
        if self._primitiveType != stringType:
 
969
            raise xml.dom.InvalidAccessErr(
 
970
                u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
 
971
                % (self.primitiveTypeString,
 
972
                   self._getCSSPrimitiveTypeString(stringType)))
 
973
 
 
974
        if CSSPrimitiveValue.CSS_STRING == self._primitiveType:
 
975
            self.cssText = u'"%s"' % stringValue.replace(u'"', ur'\\"')
 
976
        elif CSSPrimitiveValue.CSS_URI == self._primitiveType:
 
977
            # Some characters appearing in an unquoted URI, such as
 
978
            # parentheses, commas, whitespace characters, single quotes
 
979
            # (') and double quotes ("), must be escaped with a backslash
 
980
            # so that the resulting URI value is a URI token:
 
981
            # '\(', '\)', '\,'.
 
982
            #
 
983
            # Here the URI is set in quotes alltogether!
 
984
            if u'(' in stringValue or\
 
985
               u')' in stringValue or\
 
986
               u',' in stringValue or\
 
987
               u'"' in stringValue or\
 
988
               u'\'' in stringValue or\
 
989
               u'\n' in stringValue or\
 
990
               u'\t' in stringValue or\
 
991
               u'\r' in stringValue or\
 
992
               u'\f' in stringValue or\
 
993
               u' ' in stringValue:
 
994
                stringValue = '"%s"' % stringValue.replace(u'"', ur'\"')
 
995
            self.cssText = u'url(%s)' % stringValue
 
996
        elif CSSPrimitiveValue.CSS_ATTR == self._primitiveType:
 
997
            self.cssText = u'attr(%s)' % stringValue
 
998
        else:
 
999
            self.cssText = stringValue
 
1000
        self._primitiveType = stringType
 
1001
 
 
1002
    def getCounterValue(self):
 
1003
        """
 
1004
        (DOM method) This method is used to get the Counter value. If
 
1005
        this CSS value doesn't contain a counter value, a DOMException
 
1006
        is raised. Modification to the corresponding style property
 
1007
        can be achieved using the Counter interface.
 
1008
        """
 
1009
        if not self.CSS_COUNTER == self.primitiveType:
 
1010
            raise xml.dom.InvalidAccessErr(u'Value is not a counter type')
 
1011
        # TODO: use Counter class
 
1012
        raise NotImplementedError()
 
1013
 
 
1014
    def getRGBColorValue(self):
 
1015
        """
 
1016
        (DOM method) This method is used to get the RGB color. If this
 
1017
        CSS value doesn't contain a RGB color value, a DOMException
 
1018
        is raised. Modification to the corresponding style property
 
1019
        can be achieved using the RGBColor interface.
 
1020
        """
 
1021
        # TODO: what about coercing #000 to RGBColor?
 
1022
        if self.primitiveType not in self._rbgtypes:
 
1023
            raise xml.dom.InvalidAccessErr(u'Value is not a RGB value')
 
1024
        # TODO: use RGBColor class
 
1025
        raise NotImplementedError()
 
1026
 
 
1027
    def getRectValue(self):
 
1028
        """
 
1029
        (DOM method) This method is used to get the Rect value. If this CSS
 
1030
        value doesn't contain a rect value, a DOMException is raised.
 
1031
        Modification to the corresponding style property can be achieved
 
1032
        using the Rect interface.
 
1033
        """
 
1034
        if self.primitiveType not in self._recttypes:
 
1035
            raise xml.dom.InvalidAccessErr(u'value is not a Rect value')
 
1036
        # TODO: use Rect class
 
1037
        raise NotImplementedError()
 
1038
 
 
1039
    def _getCssText(self):
 
1040
        """overwritten from CSSValue"""
 
1041
        return cssutils.ser.do_css_CSSPrimitiveValue(self)
 
1042
 
 
1043
    def _setCssText(self, cssText):
 
1044
        """use CSSValue's implementation"""
 
1045
        return super(CSSPrimitiveValue, self)._setCssText(cssText)
 
1046
    
 
1047
    cssText = property(_getCssText, _setCssText,
 
1048
        doc="A string representation of the current value.")
 
1049
 
 
1050
    def __str__(self):
 
1051
        return "<cssutils.css.%s object primitiveType=%s cssText=%r _propertyName=%r valid=%r at 0x%x>" % (
 
1052
                self.__class__.__name__, self.primitiveTypeString,
 
1053
                self.cssText, self._propertyName, self.valid, id(self))
 
1054
 
 
1055
 
 
1056
class CSSValueList(CSSValue):
 
1057
    """
 
1058
    The CSSValueList interface provides the abstraction of an ordered
 
1059
    collection of CSS values.
 
1060
 
 
1061
    Some properties allow an empty list into their syntax. In that case,
 
1062
    these properties take the none identifier. So, an empty list means
 
1063
    that the property has the value none.
 
1064
 
 
1065
    The items in the CSSValueList are accessible via an integral index,
 
1066
    starting from 0.
 
1067
    """
 
1068
    cssValueType = CSSValue.CSS_VALUE_LIST
 
1069
 
 
1070
    def __init__(self, cssText=None, readonly=False, _propertyName=None):
 
1071
        """
 
1072
        inits a new CSSValueList
 
1073
        """
 
1074
        super(CSSValueList, self).__init__(cssText=cssText,
 
1075
                                       readonly=readonly,
 
1076
                                       _propertyName=_propertyName)
 
1077
        self._init()
 
1078
 
 
1079
    def _init(self):
 
1080
        "called by CSSValue if newly identified as CSSValueList"
 
1081
        # defines which values
 
1082
        ivalueseq, valueseq = 0, self._SHORTHANDPROPERTIES.get(
 
1083
                                                    self._propertyName, [])
 
1084
        self._items = []
 
1085
        newseq = self._tempSeq(False)
 
1086
        i, max = 0, len(self.seq)
 
1087
        minus = None
 
1088
        while i < max:
 
1089
            item = self.seq[i]
 
1090
            type_, val, line, col = item.type, item.value, item.line, item.col
 
1091
            if u'-' == val:
 
1092
                if minus: # 2 "-" after another
 
1093
                    self._log.error( # TODO:
 
1094
                        u'CSSValueList: Unknown syntax: %r.'
 
1095
                            % u''.join(self.seq))
 
1096
                else:
 
1097
                    minus = val
 
1098
 
 
1099
            elif isinstance(val, basestring) and not type_ == 'separator' and\
 
1100
               not u'/' == val:
 
1101
                if minus:
 
1102
                    val = minus + val
 
1103
                    minus = None
 
1104
                # TODO: complete
 
1105
                # if shorthand get new propname
 
1106
                if ivalueseq < len(valueseq):
 
1107
                    propname, mandatory = valueseq[ivalueseq]
 
1108
                    if mandatory:
 
1109
                        ivalueseq += 1
 
1110
                    else:
 
1111
                        propname = None
 
1112
                        ivalueseq = len(valueseq) # end
 
1113
                else:
 
1114
                    propname = self._propertyName
 
1115
 
 
1116
                # TODO: more (do not check individual values for these props)
 
1117
                if propname in self._SHORTHANDPROPERTIES:
 
1118
                    propname = None
 
1119
 
 
1120
                if i+1 < max and self.seq[i+1].value == u',':
 
1121
                    # a comma separated list of values as ONE value
 
1122
                    # e.g. font-family: a,b
 
1123
                    # CSSValue already has removed extra S tokens!
 
1124
                    fullvalue = [val]
 
1125
 
 
1126
                    expected = 'comma' # or 'value'
 
1127
                    for j in range(i+1, max):
 
1128
                        item2 = self.seq[j]
 
1129
                        typ2, val2, line2, col2 = (item2.type, item2.value, 
 
1130
                                                   item2.line, item2.col)
 
1131
                        if u' ' == val2: 
 
1132
                            # end or a single value follows
 
1133
                            break
 
1134
                        elif 'value' == expected and val2 in u'-+':
 
1135
                            # unary modifier
 
1136
                            fullvalue.append(val2)
 
1137
                            expected = 'value'
 
1138
                        elif 'comma' == expected and  u',' == val2:
 
1139
                            fullvalue.append(val2)
 
1140
                            expected = 'value'
 
1141
                        elif 'value' == expected and u',' != val2:
 
1142
                            if 'STRING' == typ2:
 
1143
                                val2 = cssutils.ser._string(val2)
 
1144
                            fullvalue.append(val2)
 
1145
                            expected = 'comma'
 
1146
                        else:
 
1147
                            self._log.error(
 
1148
                                u'CSSValueList: Unknown syntax: %r.'
 
1149
                                % val2)
 
1150
                            return
 
1151
                    if expected == 'value':
 
1152
                        self._log.error( # TODO:
 
1153
                            u'CSSValueList: Unknown syntax: %r.'
 
1154
                            % u''.join(self.seq))
 
1155
                        return
 
1156
                    # setting _propertyName this way does not work
 
1157
                    # for compound props like font!
 
1158
                    i += len(fullvalue) - 1
 
1159
                    obj = CSSValue(cssText=u''.join(fullvalue),
 
1160
                                 _propertyName=propname)
 
1161
                else:
 
1162
                    # a single value, u' ' or nothing should be following
 
1163
                    if 'STRING' == type_:
 
1164
                        val = cssutils.ser._string(val)
 
1165
                    elif 'URI' == type_:
 
1166
                        val = cssutils.ser._uri(val)                
 
1167
                    
 
1168
                    obj = CSSValue(cssText=val, _propertyName=propname)
 
1169
 
 
1170
                self._items.append(obj)
 
1171
                newseq.append(obj, CSSValue)
 
1172
 
 
1173
            elif CSSColor == type_:
 
1174
                self._items.append(val)
 
1175
                newseq.append(val, CSSColor)
 
1176
 
 
1177
            else:
 
1178
                # S (or TODO: comment?)
 
1179
                newseq.append(val, type_)
 
1180
 
 
1181
            i += 1
 
1182
 
 
1183
        self._setSeq(newseq)
 
1184
 
 
1185
    length = property(lambda self: len(self._items),
 
1186
                doc="(DOM attribute) The number of CSSValues in the list.")
 
1187
 
 
1188
    def item(self, index):
 
1189
        """
 
1190
        (DOM method) Used to retrieve a CSSValue by ordinal index. The
 
1191
        order in this collection represents the order of the values in the
 
1192
        CSS style property. If index is greater than or equal to the number
 
1193
        of values in the list, this returns None.
 
1194
        """
 
1195
        try:
 
1196
            return self._items[index]
 
1197
        except IndexError:
 
1198
            return None
 
1199
 
 
1200
    def __iter__(self):
 
1201
        "CSSValueList is iterable"
 
1202
        return CSSValueList.__items(self)
 
1203
 
 
1204
    def __items(self):
 
1205
        "the iterator"
 
1206
        for i in range (0, self.length):
 
1207
            yield self.item(i)
 
1208
 
 
1209
    def __str__(self):
 
1210
        return "<cssutils.css.%s object cssValueType=%r cssText=%r length=%r propname=%r valid=%r at 0x%x>" % (
 
1211
                self.__class__.__name__, self.cssValueTypeString,
 
1212
                self.cssText, self.length, self._propertyName, 
 
1213
                self.valid, id(self))
 
1214
 
 
1215
 
 
1216
class CSSFunction(CSSPrimitiveValue):
 
1217
    """A CSS function value like rect() etc."""
 
1218
    
 
1219
    def __init__(self, cssText=None, readonly=False):
 
1220
        """
 
1221
        Init a new CSSFunction
 
1222
 
 
1223
        cssText
 
1224
            the parsable cssText of the value
 
1225
        readonly
 
1226
            defaults to False
 
1227
        """
 
1228
        super(CSSColor, self).__init__()
 
1229
        self.valid = False
 
1230
        self.wellformed = False
 
1231
        if cssText is not None:
 
1232
            self.cssText = cssText
 
1233
 
 
1234
        self._funcType = None
 
1235
 
 
1236
        self._readonly = readonly
 
1237
    
 
1238
    def _setCssText(self, cssText):
 
1239
        self._checkReadonly()
 
1240
        if False:
 
1241
            pass
 
1242
        else:            
 
1243
            types = self._prods # rename!
 
1244
            valueProd = Prod(name='value', 
 
1245
                         match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE), 
 
1246
                         toSeq=CSSPrimitiveValue,
 
1247
                         toStore='parts'
 
1248
                         )
 
1249
            # COLOR PRODUCTION
 
1250
            funcProds = Sequence([
 
1251
                                  Prod(name='FUNC', 
 
1252
                                       match=lambda t, v: t == types.FUNCTION, 
 
1253
                                       toStore='funcType' ),
 
1254
                                       Prod(**PreDef.sign), 
 
1255
                                       valueProd,
 
1256
                                  # more values starting with Comma
 
1257
                                  # should use store where colorType is saved to 
 
1258
                                  # define min and may, closure?
 
1259
                                  Sequence([Prod(**PreDef.comma), 
 
1260
                                            Prod(**PreDef.sign), 
 
1261
                                            valueProd], 
 
1262
                                           minmax=lambda: (2, 2)), 
 
1263
                                  Prod(**PreDef.funcEnd)
 
1264
             ])
 
1265
            # store: colorType, parts
 
1266
            wellformed, seq, store, unusedtokens = ProdsParser().parse(cssText, 
 
1267
                                                                u'CSSFunction', 
 
1268
                                                                funcProds,
 
1269
                                                                {'parts': []})
 
1270
            
 
1271
            if wellformed:
 
1272
                self.wellformed = True
 
1273
                self._setSeq(seq)
 
1274
                self._funcType = self._normalize(store['colorType'].value[:-1])
 
1275
 
 
1276
    cssText = property(lambda self: cssutils.ser.do_css_CSSColor(self), 
 
1277
                       _setCssText)
 
1278
    
 
1279
    funcType = property(lambda self: self._funcType)
 
1280
    
 
1281
    def __repr__(self):
 
1282
        return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
 
1283
 
 
1284
    def __str__(self):
 
1285
        return "<cssutils.css.%s object colorType=%r cssText=%r at 0x%x>" % (
 
1286
                self.__class__.__name__, self.colorType, self.cssText,
 
1287
                id(self))
 
1288
 
 
1289
 
 
1290
 
 
1291
 
 
1292
class CSSColor(CSSPrimitiveValue):
 
1293
    """A CSS color like RGB, RGBA or a simple value like `#000` or `red`."""
 
1294
    
 
1295
    def __init__(self, cssText=None, readonly=False):
 
1296
        """
 
1297
        Init a new CSSColor
 
1298
 
 
1299
        cssText
 
1300
            the parsable cssText of the value
 
1301
        readonly
 
1302
            defaults to False
 
1303
        """
 
1304
        super(CSSColor, self).__init__()
 
1305
        self._colorType = None
 
1306
        self.valid = False
 
1307
        self.wellformed = False
 
1308
        if cssText is not None:
 
1309
            self.cssText = cssText
 
1310
 
 
1311
        self._readonly = readonly
 
1312
    
 
1313
    def _setCssText(self, cssText):
 
1314
        self._checkReadonly()
 
1315
        if False:
 
1316
            pass
 
1317
        else:            
 
1318
            types = self._prods # rename!
 
1319
            valueProd = Prod(name='value', 
 
1320
                         match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE), 
 
1321
                         toSeq=CSSPrimitiveValue,
 
1322
                         toStore='parts'
 
1323
                         )
 
1324
            # COLOR PRODUCTION
 
1325
            funccolor = Sequence([Prod(name='FUNC', 
 
1326
                                       match=lambda t, v: self._normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla(') and t == types.FUNCTION,
 
1327
                                       toSeq=lambda v: self._normalize(v), 
 
1328
                                       toStore='colorType' ),
 
1329
                                       PreDef.unary(), 
 
1330
                                       valueProd,
 
1331
                                  # 2 or 3 more values starting with Comma
 
1332
                                  Sequence([PreDef.comma(), 
 
1333
                                            PreDef.unary(), 
 
1334
                                            valueProd], 
 
1335
                                           minmax=lambda: (2,3)), 
 
1336
                                  PreDef.funcEnd()
 
1337
                                 ]
 
1338
            )
 
1339
            colorprods = Choice([funccolor,
 
1340
                                 Prod(name='HEX color', 
 
1341
                                      match=lambda t, v: t == types.HASH and 
 
1342
                                      len(v) == 4 or len(v) == 7,
 
1343
                                      toStore='colorType'
 
1344
                                 ),
 
1345
                                 Prod(name='named color', 
 
1346
                                      match=lambda t, v: t == types.IDENT,
 
1347
                                      toStore='colorType'
 
1348
                                 ),
 
1349
                                ]
 
1350
            )     
 
1351
            # store: colorType, parts
 
1352
            wellformed, seq, store, unusedtokens = ProdParser().parse(cssText, 
 
1353
                                                                u'CSSColor', 
 
1354
                                                                colorprods,
 
1355
                                                                {'parts': []})
 
1356
            
 
1357
            if wellformed:
 
1358
                self.wellformed = True
 
1359
                if store['colorType'].type == self._prods.HASH:
 
1360
                    self._colorType = 'HEX'
 
1361
                elif store['colorType'].type == self._prods.IDENT:
 
1362
                    self._colorType = 'Named Color'
 
1363
                else:
 
1364
                    self._colorType = self._normalize(store['colorType'].value)[:-1]
 
1365
                    
 
1366
                self._setSeq(seq)
 
1367
 
 
1368
    cssText = property(lambda self: cssutils.ser.do_css_CSSColor(self), 
 
1369
                       _setCssText)
 
1370
    
 
1371
    colorType = property(lambda self: self._colorType)
 
1372
    
 
1373
    def __repr__(self):
 
1374
        return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
 
1375
 
 
1376
    def __str__(self):
 
1377
        return "<cssutils.css.%s object colorType=%r cssText=%r at 0x%x>" % (
 
1378
                self.__class__.__name__, self.colorType, self.cssText,
 
1379
                id(self))