1
"""CSSValue related classes
3
- CSSValue implements DOM Level 2 CSS CSSValue
4
- CSSPrimitiveValue implements DOM Level 2 CSS CSSPrimitiveValue
5
- CSSValueList implements DOM Level 2 CSS CSSValueList
8
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'CSSColor']
9
__docformat__ = 'restructuredtext'
10
__version__ = '$Id: cssvalue.py 1473 2008-09-15 21:15:54Z cthedot $'
15
from cssutils.profiles import profiles
16
from cssutils.prodparser import *
19
class CSSValue(cssutils.util.Base2):
21
The CSSValue interface represents a simple or a complex value.
22
A CSSValue object only occurs in a context of a CSS property
27
A string representation of the current value.
29
A (readonly) code defining the type of the value.
31
seq: a list (cssutils)
32
All parts of this style declaration including CSSComments
34
if the value is valid at all, False for e.g. color: #1
36
if this Property is syntactically ok
39
value without any comments, used to validate
44
The value is inherited and the cssText contains "inherit".
46
CSS_PRIMITIVE_VALUE = 1
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.
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.
60
The value is a custom value.
62
_typestrings = ['CSS_INHERIT' , 'CSS_PRIMITIVE_VALUE', 'CSS_VALUE_LIST',
65
def __init__(self, cssText=None, readonly=False, _propertyName=None):
70
the parsable cssText of the value
74
used to validate this value in the context of a property
76
super(CSSValue, self).__init__()
80
self.wellformed = False
81
self._valueValue = u''
82
self._linetoken = None # used for line report only
83
self._propertyName = _propertyName
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
90
self._readonly = readonly
96
type_, val = item.type, item.value
97
if isinstance(val, cssutils.css.CSSComment):
100
elif 'STRING' == type_:
101
v.append(cssutils.ser._string(val))
103
v.append(cssutils.ser._uri(val))
108
elif isinstance(val, basestring):
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
118
def _setValue(self, value):
119
"overwritten by CSSValueList!"
120
self._valueValue = value
122
_value = property(_getValue, _setValue,
123
doc="Actual cssText value of this CSSValue.")
125
def _getCssText(self):
126
return cssutils.ser.do_css_CSSValue(self)
128
def _setCssText(self, cssText):
138
: '/' S* | ',' S* | /* empty */
141
: term [ operator term ]*
145
[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
147
| STRING S* | IDENT S* | URI S* | hexcolor | function
150
: FUNCTION S* expr ')' S*
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.
161
DOMException on setting
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.
172
self._checkReadonly()
174
# for closures: must be a mutable
175
new = {'rawvalues': [], # used for validation
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'):
192
def _char(expected, seq, token, tokenizer=None):
193
type_, val, line, col = token
194
new['rawvalues'].append(val)
196
if 'funcend' == expected and u')' == val:
199
new['values'].append(seq[-1])
202
elif expected in (')', ']', '}') and expected == val:
203
# end of any block: (), [], {}
207
elif expected in ('funcend', ')', ']', '}'):
208
# content of func or block: (), [], {}
212
elif expected.endswith('operator') and ',' == val:
213
# term, term; remove all WS between terms!!!
215
if seq and seq[-1].type == 'separator':
216
seq.replace(-1, val, type_, line=line, col=col)
218
seq.append(val, type_, line=line, col=col)
221
elif expected.endswith('operator') and '/' == val:
223
if seq and seq[-1].value == u' ':
225
seq.replace(-1, val, old.type, old.line, old.col)
228
seq.append(val, type_, line=line, col=col)
231
elif expected.startswith('term') and u'(' == val:
232
# start of ( any* ) block
233
seq.append(val, type_, line=line, col=col)
235
elif expected.startswith('term') and u'[' == val:
236
# start of [ any* ] block
237
seq.append(val, type_, line=line, col=col)
239
elif expected.startswith('term') and u'{' == val:
240
# start of { any* } block
241
seq.append(val, type_, line=line, col=col)
243
elif expected.startswith('term') and u'+' == val:
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:
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'
259
new['wellformed'] = False
260
self._log.error(u'CSSValue: Unexpected char.', token)
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':
269
if new['values'] and new['values'][-1] in (u'-', u'+'):
270
new['values'][-1] += val
272
new['values'].append(val)
273
seq.append(val, type_, line=line, col=col)
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
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)
285
elif expected in ('funcend', ')', ']', '}'):
290
new['wellformed'] = False
291
self._log.error(u'CSSValue: Unexpected token.', token)
294
def _string_ident_uri(expected, seq, token, tokenizer=None):
296
type_, val, line, col = token
298
new['rawvalues'].append(val)
299
if expected.startswith('term'):
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)
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)
319
elif expected in ('funcend', ')', ']', '}'):
324
new['wellformed'] = False
325
self._log.error(u'CSSValue: Unexpected token.', token)
328
def _hash(expected, seq, token, tokenizer=None):
330
type_, val, line, col = token
331
new['rawvalues'].append(val)
333
val = CSSColor(cssText=token)
335
if expected.startswith('term'):
337
new['values'].append(val)
338
seq.append(val, type_, line=line, col=col)
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)
347
elif expected in ('funcend', ')', ']', '}'):
352
new['wellformed'] = False
353
self._log.error(u'CSSValue: Unexpected token.', token)
356
def _function(expected, seq, token, tokenizer=None):
357
# FUNCTION incl colors
358
type_, val, line, col = token
360
if self._normalize(val) in ('rgb(', 'rgba(', 'hsl(', 'hsla('):
362
val = CSSColor(cssText=(token, tokenizer))
364
seq.append(val, type_, line=line, col=col)
365
new['values'].append(val)
366
new['rawvalues'].append(val.cssText)
369
new['rawvalues'].append(val)
371
if expected.startswith('term'):
372
# normal value but add if funcend is found
373
seq.append(val, type_, line=line, col=col)
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)
380
elif expected in ('funcend', ')', ']', '}'):
385
new['wellformed'] = False
386
self._log.error(u'CSSValue: Unexpected token.', token)
389
tokenizer = self._tokenize2(cssText)
391
linetoken = self._nexttoken(tokenizer)
393
self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
394
self._valuestr(cssText))
396
newseq = self._tempSeq() # []
397
wellformed, expected = self._parse(expected='term',
398
seq=newseq, tokenizer=tokenizer,initialtoken=linetoken,
399
productions={'S': _S,
402
'NUMBER': _number_percentage_dimension,
403
'PERCENTAGE': _number_percentage_dimension,
404
'DIMENSION': _number_percentage_dimension,
406
'STRING': _string_ident_uri,
407
'IDENT': _string_ident_uri,
408
'URI': _string_ident_uri,
410
'UNICODE-RANGE': _string_ident_uri, #?
412
'FUNCTION': _function
415
wellformed = wellformed and new['wellformed']
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
425
lastpos, lastval = lastseqvalue(newseq)
427
if expected.startswith('term') and lastval != u' ' or (
428
expected in ('funcend', ')', ']', '}')):
430
self._log.error(u'CSSValue: Incomplete value: %r.' %
431
self._valuestr(cssText))
433
if not new['values']:
435
self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
436
self._valuestr(cssText))
439
# remove last token if 'separator'
443
self._linetoken = linetoken # used for line report
446
self.valid = self._validate(u''.join(new['rawvalues']))
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:
462
self.__class__ = CSSValueList
463
self._init() # inits CSSValueList
465
self._cssValueType = CSSValue.CSS_CUSTOM
466
self.__class__ = CSSValue # reset
468
self.wellformed = wellformed
470
cssText = property(_getCssText, _setCssText,
471
doc="A string representation of the current value.")
473
def _getCssValueType(self):
474
if hasattr(self, '_cssValueType'):
475
return self._cssValueType
477
cssValueType = property(_getCssValueType,
478
doc="A (readonly) code defining the type of the value as defined above.")
480
def _getCssValueTypeString(self):
481
t = self.cssValueType
482
if t is not None: # may be 0!
483
return CSSValue._typestrings[t]
487
cssValueTypeString = property(_getCssValueTypeString,
488
doc="cssutils: Name of cssValueType of this CSSValue (readonly).")
490
def _validate(self, value=None, profile=None):
492
validates value against _propertyName context if given
496
if self._propertyName and self._propertyName in profiles.propertiesByProfile():
497
valid, validprofile = \
498
profiles.validateWithProfile(self._propertyName,
499
self._normalize(self._value))
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:
510
u'CSSValue: Invalid value for %s property "%s: %s" but valid %s property.' %
511
(profile, self._propertyName, self._value,
512
validprofile), neverraise=True)
515
u'CSSValue: Found valid %s property "%s: %s".' %
516
(validprofile, self._propertyName, self._value),
519
self._log.debug(u'CSSValue: Unable to validate as no or unknown property context set for value: %r'
520
% self._value, neverraise=True)
523
# if value is given this should not be saved
527
def _get_propertyName(self):
528
return self.__propertyName
530
def _set_propertyName(self, _propertyName):
531
self.__propertyName = _propertyName
534
_propertyName = property(_get_propertyName, _set_propertyName,
535
doc="cssutils: Property this values is validated against")
538
return "cssutils.css.%s(%r, _propertyName=%r)" % (
539
self.__class__.__name__, self.cssText, self._propertyName)
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))
547
class CSSPrimitiveValue(CSSValue):
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.
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).
563
# constant: type of this CSSValue class
564
cssValueType = CSSValue.CSS_PRIMITIVE_VALUE
566
# An integer indicating which type of unit applies to the value.
567
CSS_UNKNOWN = 0 # only obtainable via cssText
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
601
_stringtypes = [CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI]
602
_countertypes = [CSS_COUNTER]
603
_recttypes = [CSS_RECT]
604
_rbgtypes = [CSS_RGBCOLOR, CSS_RGBACOLOR]
606
_reNumDim = re.compile(ur'^(.*?)([a-z]+|%)$', re.I| re.U|re.X)
608
# oldtype: newType: converterfunc
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,
616
(CSS_PT, CSS_PC): lambda x: x * 12,
617
(CSS_PC, CSS_PT): lambda x: x / 12,
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,
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,
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,
639
(CSS_KHZ, CSS_HZ): lambda x: x * 1000,
640
(CSS_HZ, CSS_KHZ): lambda x: x / 1000,
642
(CSS_S, CSS_MS): lambda x: x * 1000,
643
(CSS_MS, CSS_S): lambda x: x / 1000
645
# TODO: convert deg <-> rad <-> grad
648
def __init__(self, cssText=None, readonly=False, _propertyName=None):
650
see CSSPrimitiveValue.__init__()
652
super(CSSPrimitiveValue, self).__init__(cssText=cssText,
654
_propertyName=_propertyName)
656
#(String representation for unit types, token type of unit type, detail)
657
# used to detect primitiveType and for __repr__
661
# _unitinfos must be set here as self._prods is not known before
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('),
692
def __set_primitiveType(self):
694
primitiveType is readonly but is set lazy if accessed
695
no value is given as self._value is used
697
primitiveType = self.CSS_UNKNOWN
699
for item in self.seq:
700
if item.type == self._prods.URI:
701
primitiveType = self.CSS_URI
703
elif item.type == self._prods.STRING:
704
primitiveType = self.CSS_STRING
708
_floatType = False # if unary expect NUMBER DIMENSION or PERCENTAGE
709
tokenizer = self._tokenize2(self._value)
710
t = self._nexttoken(tokenizer)
712
self._log.error(u'CSSPrimitiveValue: No value.')
715
if self._tokenvalue(t) in (u'-', u'+'):
716
t = self._nexttoken(tokenizer)
718
self._log.error(u'CSSPrimitiveValue: No value.')
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):
732
elif expected == 'comma' and val == ',':
733
expected = 'ident or string'
735
elif typ in ('separator', self._prods.S, self._prods.COMMENT):
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
748
for i, (name, tokentype, search) in enumerate(self._unitinfos):
749
val, typ = self._tokenvalue(t, normalize=True), self._type(t)
751
if typ == self._prods.DIMENSION:
755
elif re.match(ur'^[^a-z]*(%s)$' % search, val):
758
elif typ == self._prods.FUNCTION:
762
elif val.startswith(search):
769
if _floatType and primitiveType not in self._floattypes:
770
# - or + only expected before floattype
771
primitiveType = self.CSS_UNKNOWN
773
self._primitiveType = primitiveType
775
def _getPrimitiveType(self):
776
if not hasattr(self, '_primitivetype'):
777
self.__set_primitiveType()
778
return self._primitiveType
780
primitiveType = property(_getPrimitiveType,
781
doc="READONLY: The type of the value as defined by the constants specified above.")
783
def _getPrimitiveTypeString(self):
784
return self._unitinfos[self.primitiveType][0]
786
primitiveTypeString = property(_getPrimitiveTypeString,
787
doc="Name of primitive type of this value.")
789
def _getCSSPrimitiveTypeString(self, type):
790
"get TypeString by given type which may be unknown, used by setters"
792
return self._unitinfos[type][0]
793
except (IndexError, TypeError):
794
return u'%r (UNKNOWN TYPE)' % type
796
def __getValDim(self):
797
"splits self._value in numerical and dimension part"
799
val, dim = self._reNumDim.findall(self._value)[0]
801
val, dim = self._value, u''
805
raise xml.dom.InvalidAccessErr(
806
u'CSSPrimitiveValue: No float value %r'
811
def getFloatValue(self, unitType=None):
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
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.
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``
828
conversions might return strange values like 1.000000000001
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')
834
val, dim = self.__getValDim()
836
if unitType is not None and self.primitiveType != unitType:
839
val = self._converter[self.primitiveType, unitType](val)
841
raise xml.dom.InvalidAccessErr(
842
u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
843
% (self.primitiveTypeString,
844
self._getCSSPrimitiveTypeString(unitType)))
851
def setFloatValue(self, unitType, floatValue):
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.
859
a unit code as defined above. The unit code can only be a float
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::
865
setFloatValue(CSS_PX, 1)
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.
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))
878
val = float(floatValue)
879
except ValueError, e:
880
raise xml.dom.InvalidAccessErr(
881
u'CSSPrimitiveValue: floatValue %r is not a float' %
884
oldval, dim = self.__getValDim()
886
if self.primitiveType != unitType:
887
# convert if possible
889
val = self._converter[
890
unitType, self.primitiveType](val)
892
raise xml.dom.InvalidAccessErr(
893
u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
894
% (self.primitiveTypeString,
895
self._getCSSPrimitiveTypeString(unitType)))
900
self.cssText = '%s%s' % (val, dim)
902
def getStringValue(self):
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.
907
Some properties (like 'font-family' or 'voice-family')
908
convert a whitespace separated list of idents to a string.
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'
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)
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]
929
def setStringValue(self, stringType, stringValue):
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.
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
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
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
954
- NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
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))
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)))
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:
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\
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
999
self.cssText = stringValue
1000
self._primitiveType = stringType
1002
def getCounterValue(self):
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.
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()
1014
def getRGBColorValue(self):
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.
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()
1027
def getRectValue(self):
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.
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()
1039
def _getCssText(self):
1040
"""overwritten from CSSValue"""
1041
return cssutils.ser.do_css_CSSPrimitiveValue(self)
1043
def _setCssText(self, cssText):
1044
"""use CSSValue's implementation"""
1045
return super(CSSPrimitiveValue, self)._setCssText(cssText)
1047
cssText = property(_getCssText, _setCssText,
1048
doc="A string representation of the current value.")
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))
1056
class CSSValueList(CSSValue):
1058
The CSSValueList interface provides the abstraction of an ordered
1059
collection of CSS values.
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.
1065
The items in the CSSValueList are accessible via an integral index,
1068
cssValueType = CSSValue.CSS_VALUE_LIST
1070
def __init__(self, cssText=None, readonly=False, _propertyName=None):
1072
inits a new CSSValueList
1074
super(CSSValueList, self).__init__(cssText=cssText,
1076
_propertyName=_propertyName)
1080
"called by CSSValue if newly identified as CSSValueList"
1081
# defines which values
1082
ivalueseq, valueseq = 0, self._SHORTHANDPROPERTIES.get(
1083
self._propertyName, [])
1085
newseq = self._tempSeq(False)
1086
i, max = 0, len(self.seq)
1090
type_, val, line, col = item.type, item.value, item.line, item.col
1092
if minus: # 2 "-" after another
1093
self._log.error( # TODO:
1094
u'CSSValueList: Unknown syntax: %r.'
1095
% u''.join(self.seq))
1099
elif isinstance(val, basestring) and not type_ == 'separator' and\
1105
# if shorthand get new propname
1106
if ivalueseq < len(valueseq):
1107
propname, mandatory = valueseq[ivalueseq]
1112
ivalueseq = len(valueseq) # end
1114
propname = self._propertyName
1116
# TODO: more (do not check individual values for these props)
1117
if propname in self._SHORTHANDPROPERTIES:
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!
1126
expected = 'comma' # or 'value'
1127
for j in range(i+1, max):
1129
typ2, val2, line2, col2 = (item2.type, item2.value,
1130
item2.line, item2.col)
1132
# end or a single value follows
1134
elif 'value' == expected and val2 in u'-+':
1136
fullvalue.append(val2)
1138
elif 'comma' == expected and u',' == val2:
1139
fullvalue.append(val2)
1141
elif 'value' == expected and u',' != val2:
1142
if 'STRING' == typ2:
1143
val2 = cssutils.ser._string(val2)
1144
fullvalue.append(val2)
1148
u'CSSValueList: Unknown syntax: %r.'
1151
if expected == 'value':
1152
self._log.error( # TODO:
1153
u'CSSValueList: Unknown syntax: %r.'
1154
% u''.join(self.seq))
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)
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)
1168
obj = CSSValue(cssText=val, _propertyName=propname)
1170
self._items.append(obj)
1171
newseq.append(obj, CSSValue)
1173
elif CSSColor == type_:
1174
self._items.append(val)
1175
newseq.append(val, CSSColor)
1178
# S (or TODO: comment?)
1179
newseq.append(val, type_)
1183
self._setSeq(newseq)
1185
length = property(lambda self: len(self._items),
1186
doc="(DOM attribute) The number of CSSValues in the list.")
1188
def item(self, index):
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.
1196
return self._items[index]
1201
"CSSValueList is iterable"
1202
return CSSValueList.__items(self)
1206
for i in range (0, self.length):
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))
1216
class CSSFunction(CSSPrimitiveValue):
1217
"""A CSS function value like rect() etc."""
1219
def __init__(self, cssText=None, readonly=False):
1221
Init a new CSSFunction
1224
the parsable cssText of the value
1228
super(CSSColor, self).__init__()
1230
self.wellformed = False
1231
if cssText is not None:
1232
self.cssText = cssText
1234
self._funcType = None
1236
self._readonly = readonly
1238
def _setCssText(self, cssText):
1239
self._checkReadonly()
1243
types = self._prods # rename!
1244
valueProd = Prod(name='value',
1245
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
1246
toSeq=CSSPrimitiveValue,
1250
funcProds = Sequence([
1252
match=lambda t, v: t == types.FUNCTION,
1253
toStore='funcType' ),
1254
Prod(**PreDef.sign),
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),
1262
minmax=lambda: (2, 2)),
1263
Prod(**PreDef.funcEnd)
1265
# store: colorType, parts
1266
wellformed, seq, store, unusedtokens = ProdsParser().parse(cssText,
1272
self.wellformed = True
1274
self._funcType = self._normalize(store['colorType'].value[:-1])
1276
cssText = property(lambda self: cssutils.ser.do_css_CSSColor(self),
1279
funcType = property(lambda self: self._funcType)
1282
return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
1285
return "<cssutils.css.%s object colorType=%r cssText=%r at 0x%x>" % (
1286
self.__class__.__name__, self.colorType, self.cssText,
1292
class CSSColor(CSSPrimitiveValue):
1293
"""A CSS color like RGB, RGBA or a simple value like `#000` or `red`."""
1295
def __init__(self, cssText=None, readonly=False):
1300
the parsable cssText of the value
1304
super(CSSColor, self).__init__()
1305
self._colorType = None
1307
self.wellformed = False
1308
if cssText is not None:
1309
self.cssText = cssText
1311
self._readonly = readonly
1313
def _setCssText(self, cssText):
1314
self._checkReadonly()
1318
types = self._prods # rename!
1319
valueProd = Prod(name='value',
1320
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
1321
toSeq=CSSPrimitiveValue,
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' ),
1331
# 2 or 3 more values starting with Comma
1332
Sequence([PreDef.comma(),
1335
minmax=lambda: (2,3)),
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,
1345
Prod(name='named color',
1346
match=lambda t, v: t == types.IDENT,
1351
# store: colorType, parts
1352
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
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'
1364
self._colorType = self._normalize(store['colorType'].value)[:-1]
1368
cssText = property(lambda self: cssutils.ser.do_css_CSSColor(self),
1371
colorType = property(lambda self: self._colorType)
1374
return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
1377
return "<cssutils.css.%s object colorType=%r cssText=%r at 0x%x>" % (
1378
self.__class__.__name__, self.colorType, self.cssText,