You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1252 lines
51 KiB

12 years ago
"""CSSValue related classes
- CSSValue implements DOM Level 2 CSS CSSValue
- CSSPrimitiveValue implements DOM Level 2 CSS CSSPrimitiveValue
- CSSValueList implements DOM Level 2 CSS CSSValueList
"""
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor',
'CSSVariable']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.prodparser import *
import cssutils
import cssutils.helper
import math
import re
import xml.dom
class CSSValue(cssutils.util._NewBase):
"""The CSSValue interface represents a simple or a complex value.
A CSSValue object only occurs in a context of a CSS property.
"""
# The value is inherited and the cssText contains "inherit".
CSS_INHERIT = 0
# The value is a CSSPrimitiveValue.
CSS_PRIMITIVE_VALUE = 1
# The value is a CSSValueList.
CSS_VALUE_LIST = 2
# The value is a custom value.
CSS_CUSTOM = 3
# The value is a CSSVariable.
CSS_VARIABLE = 4
_typestrings = {0: 'CSS_INHERIT' ,
1: 'CSS_PRIMITIVE_VALUE',
2: 'CSS_VALUE_LIST',
3: 'CSS_CUSTOM',
4: 'CSS_VARIABLE'}
def __init__(self, cssText=None, parent=None, readonly=False):
"""
:param cssText:
the parsable cssText of the value
:param readonly:
defaults to False
"""
super(CSSValue, self).__init__()
self._cssValueType = None
self.wellformed = False
self.parent = parent
if cssText is not None: # may be 0
if isinstance(cssText, int):
cssText = unicode(cssText) # if it is an integer
elif isinstance(cssText, float):
cssText = u'%f' % cssText # if it is a floating point number
self.cssText = cssText
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(%r)" % (self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object cssValueTypeString=%r cssText=%r at "\
u"0x%x>" % (self.__class__.__name__,
self.cssValueTypeString,
self.cssText,
id(self))
def _setCssText(self, cssText):
"""
Format::
unary_operator
: '-' | '+'
;
operator
: '/' S* | ',' S* | /* empty */
;
expr
: term [ operator term ]*
;
term
: unary_operator?
[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* |
ANGLE S* | TIME S* | FREQ S* ]
| STRING S* | IDENT S* | URI S* | hexcolor | function
| UNICODE-RANGE S*
;
function
: FUNCTION S* expr ')' S*
;
/*
* There is a constraint on the color that it must
* have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
* after the "#"; e.g., "#000" is OK, but "#abcd" is not.
*/
hexcolor
: HASH S*
;
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
(according to the attached property) or is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
TODO: Raised if the specified CSS string value represents a
different type of values than the values allowed by the CSS
property.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this value is readonly.
"""
self._checkReadonly()
# used as operator is , / or S
nextSor = u',/'
term = Choice(Sequence(PreDef.unary(),
Choice(PreDef.number(nextSor=nextSor),
PreDef.percentage(nextSor=nextSor),
PreDef.dimension(nextSor=nextSor))),
PreDef.string(nextSor=nextSor),
PreDef.ident(nextSor=nextSor),
PreDef.uri(nextSor=nextSor),
PreDef.hexcolor(nextSor=nextSor),
PreDef.unicode_range(nextSor=nextSor),
# special case IE only expression
Prod(name='expression',
match=lambda t, v: t == self._prods.FUNCTION and (
cssutils.helper.normalize(v) in (u'expression(',
u'alpha(',
u'blur(',
u'chroma(',
u'dropshadow(',
u'fliph(',
u'flipv(',
u'glow(',
u'gray(',
u'invert(',
u'mask(',
u'shadow(',
u'wave(',
u'xray(') or
v.startswith(u'progid:DXImageTransform.Microsoft.')
),
nextSor=nextSor,
toSeq=lambda t, tokens: (ExpressionValue._functionName,
ExpressionValue(
cssutils.helper.pushtoken(t, tokens),
parent=self)
)
),
# CSS Variable var(
PreDef.variable(nextSor=nextSor,
toSeq=lambda t, tokens: ('CSSVariable',
CSSVariable(
cssutils.helper.pushtoken(t, tokens),
parent=self)
)
),
# calc(
PreDef.calc(nextSor=nextSor,
toSeq=lambda t, tokens: (CalcValue._functionName,
CalcValue(
cssutils.helper.pushtoken(t, tokens),
parent=self)
)
),
# TODO:
# # rgb/rgba(
# Prod(name='RGBColor',
# match=lambda t, v: t == self._prods.FUNCTION and (
# cssutils.helper.normalize(v) in (u'rgb(',
# u'rgba('
# )
# ),
# nextSor=nextSor,
# toSeq=lambda t, tokens: (RGBColor._functionName,
# RGBColor(
# cssutils.helper.pushtoken(t, tokens),
# parent=self)
# )
# ),
# other functions like rgb( etc
PreDef.function(nextSor=nextSor,
toSeq=lambda t, tokens: ('FUNCTION',
CSSFunction(
cssutils.helper.pushtoken(t, tokens),
parent=self)
)
)
)
operator = Choice(PreDef.S(),
PreDef.char('comma', ',',
toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('slash', '/',
toSeq=lambda t, tokens: ('operator', t[1])),
optional=True)
# CSSValue PRODUCTIONS
valueprods = Sequence(term,
Sequence(operator, # mayEnd this Sequence if whitespace
# TODO: only when setting via other class
# used by variabledeclaration currently
PreDef.char('END', ';',
stopAndKeep=True,
optional=True),
term,
minmax=lambda: (0, None)))
# parse
wellformed, seq, store, notused = ProdParser().parse(cssText,
u'CSSValue',
valueprods,
keepS=True)
if wellformed:
# - count actual values and set firstvalue which is used later on
# - combine comma separated list, e.g. font-family to a single item
# - remove S which should be an operator but is no needed
count, firstvalue = 0, ()
newseq = self._tempSeq()
i, end = 0, len(seq)
while i < end:
item = seq[i]
if item.type == self._prods.S:
pass
elif (item.value, item.type) == (u',', 'operator'):
# , separared counts as a single STRING for now
# URI or STRING value might be a single CHAR too!
newseq.appendItem(item)
count -= 1
if firstvalue:
# list of IDENTs is handled as STRING!
if firstvalue[1] == self._prods.IDENT:
firstvalue = firstvalue[0], 'STRING'
elif item.value == u'/':
# / separated items count as one
newseq.appendItem(item)
elif item.value == u'-' or item.value == u'+':
# combine +- and following number or other
i += 1
try:
next = seq[i]
except IndexError:
firstvalue = () # raised later
break
newval = item.value + next.value
newseq.append(newval, next.type,
item.line, item.col)
if not firstvalue:
firstvalue = (newval, next.type)
count += 1
elif item.type != cssutils.css.CSSComment:
newseq.appendItem(item)
if not firstvalue:
firstvalue = (item.value, item.type)
count += 1
else:
newseq.appendItem(item)
i += 1
if not firstvalue:
self._log.error(
u'CSSValue: Unknown syntax or no value: %r.' %
self._valuestr(cssText))
else:
# ok and set
self._setSeq(newseq)
self.wellformed = wellformed
if hasattr(self, '_value'):
# only in case of CSSPrimitiveValue, else remove!
del self._value
if count == 1:
# inherit, primitive or variable
if isinstance(firstvalue[0], basestring) and\
u'inherit' == cssutils.helper.normalize(firstvalue[0]):
self.__class__ = CSSValue
self._cssValueType = CSSValue.CSS_INHERIT
elif 'CSSVariable' == firstvalue[1]:
self.__class__ = CSSVariable
self._value = firstvalue
# TODO: remove major hack!
self._name = firstvalue[0]._name
else:
self.__class__ = CSSPrimitiveValue
self._value = firstvalue
elif count > 1:
# valuelist
self.__class__ = CSSValueList
# change items in list to specific type (primitive etc)
newseq = self._tempSeq()
commalist = []
nexttocommalist = False
def itemValue(item):
"Reserialized simple item.value"
if self._prods.STRING == item.type:
return cssutils.helper.string(item.value)
elif self._prods.URI == item.type:
return cssutils.helper.uri(item.value)
elif self._prods.FUNCTION == item.type or\
'CSSVariable' == item.type:
return item.value.cssText
else:
return item.value
def saveifcommalist(commalist, newseq):
"""
saves items in commalist to seq and items
if anything in there
"""
if commalist:
newseq.replace(-1,
CSSPrimitiveValue(cssText=u''.join(
commalist)),
CSSPrimitiveValue,
newseq[-1].line,
newseq[-1].col)
del commalist[:]
for i, item in enumerate(self._seq):
if issubclass(type(item.value), CSSValue):
# set parent of CSSValueList items to the lists
# parent
item.value.parent = self.parent
if item.type in (self._prods.DIMENSION,
self._prods.FUNCTION,
self._prods.HASH,
self._prods.IDENT,
self._prods.NUMBER,
self._prods.PERCENTAGE,
self._prods.STRING,
self._prods.URI,
self._prods.UNICODE_RANGE,
'CSSVariable'):
if nexttocommalist:
# wait until complete
commalist.append(itemValue(item))
else:
saveifcommalist(commalist, newseq)
# append new item
if hasattr(item.value, 'cssText'):
newseq.append(item.value,
item.value.__class__,
item.line, item.col)
else:
newseq.append(CSSPrimitiveValue(
itemValue(item)),
CSSPrimitiveValue,
item.line, item.col)
nexttocommalist = False
elif u',' == item.value:
if not commalist:
# save last item to commalist
commalist.append(itemValue(self._seq[i - 1]))
commalist.append(u',')
nexttocommalist = True
else:
if nexttocommalist:
commalist.append(item.value.cssText)
else:
newseq.appendItem(item)
saveifcommalist(commalist, newseq)
self._setSeq(newseq)
else:
# should not happen...
self.__class__ = CSSValue
self._cssValueType = CSSValue.CSS_CUSTOM
cssText = property(lambda self: cssutils.ser.do_css_CSSValue(self),
_setCssText,
doc="A string representation of the current value.")
cssValueType = property(lambda self: self._cssValueType,
doc="A (readonly) code defining the type of the value.")
cssValueTypeString = property(
lambda self: CSSValue._typestrings.get(self.cssValueType, None),
doc="(readonly) Name of cssValueType.")
class CSSPrimitiveValue(CSSValue):
"""Represents a single CSS Value. May be used to determine the value of a
specific style property currently set in a block or to set a specific
style property explicitly within the block. Might be obtained from the
getPropertyCSSValue method of CSSStyleDeclaration.
Conversions are allowed between absolute values (from millimeters to
centimeters, from degrees to radians, and so on) but not between
relative values. (For example, a pixel value cannot be converted to a
centimeter value.) Percentage values can't be converted since they are
relative to the parent value (or another property value). There is one
exception for color percentage values: since a color percentage value
is relative to the range 0-255, a color percentage value can be
converted to a number; (see also the RGBColor interface).
"""
# constant: type of this CSSValue class
cssValueType = CSSValue.CSS_PRIMITIVE_VALUE
__types = cssutils.cssproductions.CSSProductions
# An integer indicating which type of unit applies to the value.
CSS_UNKNOWN = 0 # only obtainable via cssText
CSS_NUMBER = 1
CSS_PERCENTAGE = 2
CSS_EMS = 3
CSS_EXS = 4
CSS_PX = 5
CSS_CM = 6
CSS_MM = 7
CSS_IN = 8
CSS_PT = 9
CSS_PC = 10
CSS_DEG = 11
CSS_RAD = 12
CSS_GRAD = 13
CSS_MS = 14
CSS_S = 15
CSS_HZ = 16
CSS_KHZ = 17
CSS_DIMENSION = 18
CSS_STRING = 19
CSS_URI = 20
CSS_IDENT = 21
CSS_ATTR = 22
CSS_COUNTER = 23
CSS_RECT = 24
CSS_RGBCOLOR = 25
# NOT OFFICIAL:
CSS_RGBACOLOR = 26
CSS_UNICODE_RANGE = 27
_floattypes = (CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS,
CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC,
CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S,
CSS_HZ, CSS_KHZ, CSS_DIMENSION)
_stringtypes = (CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI)
_countertypes = (CSS_COUNTER,)
_recttypes = (CSS_RECT,)
_rbgtypes = (CSS_RGBCOLOR, CSS_RGBACOLOR)
_lengthtypes = (CSS_NUMBER, CSS_EMS, CSS_EXS,
CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC)
# oldtype: newType: converterfunc
_converter = {
# cm <-> mm <-> in, 1 inch is equal to 2.54 centimeters.
# pt <-> pc, the points used by CSS 2.1 are equal to 1/72nd of an inch.
# pc: picas - 1 pica is equal to 12 points
(CSS_CM, CSS_MM): lambda x: x * 10,
(CSS_MM, CSS_CM): lambda x: x / 10,
(CSS_PT, CSS_PC): lambda x: x * 12,
(CSS_PC, CSS_PT): lambda x: x / 12,
(CSS_CM, CSS_IN): lambda x: x / 2.54,
(CSS_IN, CSS_CM): lambda x: x * 2.54,
(CSS_MM, CSS_IN): lambda x: x / 25.4,
(CSS_IN, CSS_MM): lambda x: x * 25.4,
(CSS_IN, CSS_PT): lambda x: x / 72,
(CSS_PT, CSS_IN): lambda x: x * 72,
(CSS_CM, CSS_PT): lambda x: x / 2.54 / 72,
(CSS_PT, CSS_CM): lambda x: x * 72 * 2.54,
(CSS_MM, CSS_PT): lambda x: x / 25.4 / 72,
(CSS_PT, CSS_MM): lambda x: x * 72 * 25.4,
(CSS_IN, CSS_PC): lambda x: x / 72 / 12,
(CSS_PC, CSS_IN): lambda x: x * 12 * 72,
(CSS_CM, CSS_PC): lambda x: x / 2.54 / 72 / 12,
(CSS_PC, CSS_CM): lambda x: x * 12 * 72 * 2.54,
(CSS_MM, CSS_PC): lambda x: x / 25.4 / 72 / 12,
(CSS_PC, CSS_MM): lambda x: x * 12 * 72 * 25.4,
# hz <-> khz
(CSS_KHZ, CSS_HZ): lambda x: x * 1000,
(CSS_HZ, CSS_KHZ): lambda x: x / 1000,
# s <-> ms
(CSS_S, CSS_MS): lambda x: x * 1000,
(CSS_MS, CSS_S): lambda x: x / 1000,
(CSS_RAD, CSS_DEG): lambda x: math.degrees(x),
(CSS_DEG, CSS_RAD): lambda x: math.radians(x),
# TODO: convert grad <-> deg or rad
#(CSS_RAD, CSS_GRAD): lambda x: math.degrees(x),
#(CSS_DEG, CSS_GRAD): lambda x: math.radians(x),
#(CSS_GRAD, CSS_RAD): lambda x: math.radians(x),
#(CSS_GRAD, CSS_DEG): lambda x: math.radians(x)
}
def __init__(self, cssText=None, parent=None, readonly=False):
"""See CSSPrimitiveValue.__init__()"""
super(CSSPrimitiveValue, self).__init__(cssText=cssText,
parent=parent,
readonly=readonly)
def __str__(self):
return u"<cssutils.css.%s object primitiveType=%s cssText=%r at 0x%x>"\
% (self.__class__.__name__,
self.primitiveTypeString,
self.cssText,
id(self))
_unitnames = ['CSS_UNKNOWN',
'CSS_NUMBER', 'CSS_PERCENTAGE',
'CSS_EMS', 'CSS_EXS',
'CSS_PX',
'CSS_CM', 'CSS_MM',
'CSS_IN',
'CSS_PT', 'CSS_PC',
'CSS_DEG', 'CSS_RAD', 'CSS_GRAD',
'CSS_MS', 'CSS_S',
'CSS_HZ', 'CSS_KHZ',
'CSS_DIMENSION',
'CSS_STRING', 'CSS_URI', 'CSS_IDENT',
'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT',
'CSS_RGBCOLOR', 'CSS_RGBACOLOR',
'CSS_UNICODE_RANGE'
]
_reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I | re.U | re.X)
def _unitDIMENSION(value):
"""Check val for dimension name."""
units = {'em': 'CSS_EMS', 'ex': 'CSS_EXS',
'px': 'CSS_PX',
'cm': 'CSS_CM', 'mm': 'CSS_MM',
'in': 'CSS_IN',
'pt': 'CSS_PT', 'pc': 'CSS_PC',
'deg': 'CSS_DEG', 'rad': 'CSS_RAD', 'grad': 'CSS_GRAD',
'ms': 'CSS_MS', 's': 'CSS_S',
'hz': 'CSS_HZ', 'khz': 'CSS_KHZ'
}
val, dim = CSSPrimitiveValue._reNumDim.findall(cssutils.helper.normalize(value))[0]
return units.get(dim, 'CSS_DIMENSION')
def _unitFUNCTION(value):
"""Check val for function name."""
units = {'attr(': 'CSS_ATTR',
'counter(': 'CSS_COUNTER',
'rect(': 'CSS_RECT',
'rgb(': 'CSS_RGBCOLOR',
'rgba(': 'CSS_RGBACOLOR',
}
return units.get(re.findall(ur'^(.*?\()',
cssutils.helper.normalize(value.cssText),
re.U)[0],
'CSS_UNKNOWN')
__unitbytype = {
__types.NUMBER: 'CSS_NUMBER',
__types.PERCENTAGE: 'CSS_PERCENTAGE',
__types.STRING: 'CSS_STRING',
__types.UNICODE_RANGE: 'CSS_UNICODE_RANGE',
__types.URI: 'CSS_URI',
__types.IDENT: 'CSS_IDENT',
__types.HASH: 'CSS_RGBCOLOR',
__types.DIMENSION: _unitDIMENSION,
__types.FUNCTION: _unitFUNCTION
}
def __set_primitiveType(self):
"""primitiveType is readonly but is set lazy if accessed"""
# TODO: check unary and font-family STRING a, b, "c"
val, type_ = self._value
# try get by type_
pt = self.__unitbytype.get(type_, 'CSS_UNKNOWN')
if callable(pt):
# multiple options, check value too
pt = pt(val)
self._primitiveType = getattr(self, pt)
def _getPrimitiveType(self):
if not hasattr(self, '_primitivetype'):
self.__set_primitiveType()
return self._primitiveType
primitiveType = property(_getPrimitiveType,
doc="(readonly) The type of the value as defined "
"by the constants in this class.")
def _getPrimitiveTypeString(self):
return self._unitnames[self.primitiveType]
primitiveTypeString = property(_getPrimitiveTypeString,
doc="Name of primitive type of this value.")
def _getCSSPrimitiveTypeString(self, type):
"get TypeString by given type which may be unknown, used by setters"
try:
return self._unitnames[type]
except (IndexError, TypeError):
return u'%r (UNKNOWN TYPE)' % type
def _getNumDim(self, value=None):
"Split self._value in numerical and dimension part."
if value is None:
value = cssutils.helper.normalize(self._value[0])
try:
val, dim = CSSPrimitiveValue._reNumDim.findall(value)[0]
except IndexError:
val, dim = value, u''
try:
val = float(val)
if val == int(val):
val = int(val)
except ValueError:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: No float value %r' % self._value[0])
return val, dim
def getFloatValue(self, unitType=None):
"""(DOM) This method is used to get a float value in a
specified unit. If this CSS value doesn't contain a float value
or can't be converted into the specified unit, a DOMException
is raised.
:param unitType:
to get the float value. The unit code can only be a float unit type
(i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM,
CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS,
CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION) or None in which case
the current dimension is used.
:returns:
not necessarily a float but some cases just an integer
e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0``
Conversions might return strange values like 1.000000000001
"""
if unitType is not None and unitType not in self._floattypes:
raise xml.dom.InvalidAccessErr(
u'unitType Parameter is not a float type')
val, dim = self._getNumDim()
if unitType is not None and self.primitiveType != unitType:
# convert if needed
try:
val = self._converter[self.primitiveType, unitType](val)
except KeyError:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
% (self.primitiveTypeString,
self._getCSSPrimitiveTypeString(unitType)))
if val == int(val):
val = int(val)
return val
def setFloatValue(self, unitType, floatValue):
"""(DOM) A method to set the float value with a specified unit.
If the property attached with this value can not accept the
specified unit or the float value, the value will be unchanged and
a DOMException will be raised.
:param unitType:
a unit code as defined above. The unit code can only be a float
unit type
:param floatValue:
the new float value which does not have to be a float value but
may simple be an int e.g. if setting::
setFloatValue(CSS_PX, 1)
:exceptions:
- :exc:`~xml.dom.InvalidAccessErr`:
Raised if the attached property doesn't
support the float value or the unit type.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this property is readonly.
"""
self._checkReadonly()
if unitType not in self._floattypes:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: unitType %r is not a float type' %
self._getCSSPrimitiveTypeString(unitType))
try:
val = float(floatValue)
except ValueError, e:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: floatValue %r is not a float' %
floatValue)
oldval, dim = self._getNumDim()
if self.primitiveType != unitType:
# convert if possible
try:
val = self._converter[unitType, self.primitiveType](val)
except KeyError:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
% (self.primitiveTypeString,
self._getCSSPrimitiveTypeString(unitType)))
if val == int(val):
val = int(val)
self.cssText = '%s%s' % (val, dim)
def getStringValue(self):
"""(DOM) This method is used to get the string value. If the
CSS value doesn't contain a string value, a DOMException is raised.
Some properties (like 'font-family' or 'voice-family')
convert a whitespace separated list of idents to a string.
Only the actual value is returned so e.g. all the following return the
actual value ``a``: url(a), attr(a), "a", 'a'
"""
if self.primitiveType not in self._stringtypes:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue %r is not a string type'
% self.primitiveTypeString)
if CSSPrimitiveValue.CSS_ATTR == self.primitiveType:
return self._value[0].cssText[5:-1]
else:
return self._value[0]
def setStringValue(self, stringType, stringValue):
"""(DOM) A method to set the string value with the specified
unit. If the property attached to this value can't accept the
specified unit or the string value, the value will be unchanged and
a DOMException will be raised.
:param stringType:
a string code as defined above. The string code can only be a
string unit type (i.e. CSS_STRING, CSS_URI, CSS_IDENT, and
CSS_ATTR).
:param stringValue:
the new string value
Only the actual value is expected so for (CSS_URI, "a") the
new value will be ``url(a)``. For (CSS_STRING, "'a'")
the new value will be ``"\\'a\\'"`` as the surrounding ``'`` are
not part of the string value
:exceptions:
- :exc:`~xml.dom.InvalidAccessErr`:
Raised if the CSS value doesn't contain a
string value or if the string value can't be converted into
the specified unit.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this property is readonly.
"""
self._checkReadonly()
# self not stringType
if self.primitiveType not in self._stringtypes:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue %r is not a string type'
% self.primitiveTypeString)
# given stringType is no StringType
if stringType not in self._stringtypes:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: stringType %s is not a string type'
% self._getCSSPrimitiveTypeString(stringType))
if self._primitiveType != stringType:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r'
% (self.primitiveTypeString,
self._getCSSPrimitiveTypeString(stringType)))
if CSSPrimitiveValue.CSS_STRING == self._primitiveType:
self.cssText = cssutils.helper.string(stringValue)
elif CSSPrimitiveValue.CSS_URI == self._primitiveType:
self.cssText = cssutils.helper.uri(stringValue)
elif CSSPrimitiveValue.CSS_ATTR == self._primitiveType:
self.cssText = u'attr(%s)' % stringValue
else:
self.cssText = stringValue
self._primitiveType = stringType
def getCounterValue(self):
"""(DOM) This method is used to get the Counter value. If
this CSS value doesn't contain a counter value, a DOMException
is raised. Modification to the corresponding style property
can be achieved using the Counter interface.
**Not implemented.**
"""
if not self.CSS_COUNTER == self.primitiveType:
raise xml.dom.InvalidAccessErr(u'Value is not a counter type')
# TODO: use Counter class
raise NotImplementedError()
def getRGBColorValue(self):
"""(DOM) This method is used to get the RGB color. If this
CSS value doesn't contain a RGB color value, a DOMException
is raised. Modification to the corresponding style property
can be achieved using the RGBColor interface.
"""
if self.primitiveType not in self._rbgtypes:
raise xml.dom.InvalidAccessErr(u'Value is not a RGBColor value')
return RGBColor(self._value[0])
def getRectValue(self):
"""(DOM) This method is used to get the Rect value. If this CSS
value doesn't contain a rect value, a DOMException is raised.
Modification to the corresponding style property can be achieved
using the Rect interface.
**Not implemented.**
"""
if self.primitiveType not in self._recttypes:
raise xml.dom.InvalidAccessErr(u'value is not a Rect value')
# TODO: use Rect class
raise NotImplementedError()
def _getCssText(self):
"""Overwrites CSSValue."""
return cssutils.ser.do_css_CSSPrimitiveValue(self)
def _setCssText(self, cssText):
"""Use CSSValue."""
return super(CSSPrimitiveValue, self)._setCssText(cssText)
cssText = property(_getCssText, _setCssText,
doc="A string representation of the current value.")
class CSSValueList(CSSValue):
"""The CSSValueList interface provides the abstraction of an ordered
collection of CSS values.
Some properties allow an empty list into their syntax. In that case,
these properties take the none identifier. So, an empty list means
that the property has the value none.
The items in the CSSValueList are accessible via an integral index,
starting from 0.
"""
cssValueType = CSSValue.CSS_VALUE_LIST
def __init__(self, cssText=None, parent=None, readonly=False):
"""Init a new CSSValueList"""
super(CSSValueList, self).__init__(cssText=cssText,
parent=parent,
readonly=readonly)
self._items = []
def __iter__(self):
"CSSValueList is iterable."
for item in self.__items():
yield item.value
def __str__(self):
return u"<cssutils.css.%s object cssValueType=%r cssText=%r length=%r "\
u"at 0x%x>" % (self.__class__.__name__,
self.cssValueTypeString,
self.cssText,
self.length,
id(self))
def __items(self):
return [item for item in self._seq
if isinstance(item.value, CSSValue)]
def item(self, index):
"""(DOM) Retrieve a CSSValue by ordinal `index`. The
order in this collection represents the order of the values in the
CSS style property. If `index` is greater than or equal to the number
of values in the list, this returns ``None``.
"""
try:
return self.__items()[index].value
except IndexError:
return None
length = property(lambda self: len(self.__items()),
doc=u"(DOM attribute) The number of CSSValues in the "
u"list.")
class CSSFunction(CSSPrimitiveValue):
"""A CSS function value like rect() etc."""
_functionName = u'CSSFunction'
primitiveType = CSSPrimitiveValue.CSS_UNKNOWN
def __init__(self, cssText=None, parent=None, readonly=False):
"""
Init a new CSSFunction
:param cssText:
the parsable cssText of the value
:param readonly:
defaults to False
"""
super(CSSFunction, self).__init__(parent=parent)
self._funcType = None
self.valid = False
self.wellformed = False
if cssText is not None:
self.cssText = cssText
self._readonly = readonly
def _productiondefinition(self):
"""Return definition used for parsing."""
types = self._prods # rename!
value = Sequence(PreDef.unary(),
Prod(name='PrimitiveValue',
match=lambda t, v: t in (types.DIMENSION,
types.HASH,
types.IDENT,
types.NUMBER,
types.PERCENTAGE,
types.STRING),
toSeq=lambda t, tokens: (t[0],
CSSPrimitiveValue(t[1]))
)
)
valueOrFunc = Choice(value,
# FUNC is actually not in spec but used in e.g. Prince
PreDef.function(toSeq=lambda t,
tokens: ('FUNCTION',
CSSFunction(
cssutils.helper.pushtoken(t, tokens))
)
)
)
funcProds = Sequence(Prod(name='FUNC',
match=lambda t, v: t == types.FUNCTION,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))),
Choice(Sequence(valueOrFunc,
# more values starting with Comma
# should use store where colorType is saved to
# define min and may, closure?
Sequence(PreDef.comma(),
valueOrFunc,
minmax=lambda: (0, None)),
PreDef.funcEnd(stop=True)),
PreDef.funcEnd(stop=True))
)
return funcProds
def _setCssText(self, cssText):
self._checkReadonly()
# store: colorType, parts
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
self._functionName,
self._productiondefinition(),
keepS=True)
if wellformed:
# combine +/- and following CSSPrimitiveValue, remove S
newseq = self._tempSeq()
i, end = 0, len(seq)
while i < end:
item = seq[i]
if item.type == self._prods.S:
pass
elif item.value == u'+' or item.value == u'-':
i += 1
next = seq[i]
newval = next.value
if isinstance(newval, CSSPrimitiveValue):
newval.setFloatValue(newval.primitiveType,
float(item.value + str(newval.getFloatValue())))
newseq.append(newval, next.type,
item.line, item.col)
else:
# expressions only?
newseq.appendItem(item)
newseq.appendItem(next)
else:
newseq.appendItem(item)
i += 1
self.wellformed = True
self._setSeq(newseq)
self._funcType = newseq[0].value
cssText = property(lambda self: cssutils.ser.do_css_FunctionValue(self),
_setCssText)
funcType = property(lambda self: self._funcType)
class RGBColor(CSSFunction):
"""A CSS color like RGB, RGBA or a simple value like `#000` or `red`."""
_functionName = u'Function rgb()'
def __init__(self, cssText=None, parent=None, readonly=False):
"""
Init a new RGBColor
:param cssText:
the parsable cssText of the value
:param readonly:
defaults to False
"""
super(CSSFunction, self).__init__(parent=parent)
self._colorType = None
self.valid = False
self.wellformed = False
if cssText is not None:
try:
# if it is a Function object
cssText = cssText.cssText
except AttributeError:
pass
self.cssText = cssText
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(%r)" % (self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object colorType=%r cssText=%r at 0x%x>" % (
self.__class__.__name__,
self.colorType,
self.cssText,
id(self))
def _setCssText(self, cssText):
self._checkReadonly()
types = self._prods # rename!
valueProd = Prod(name='value',
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
toSeq=lambda t, v: (CSSPrimitiveValue, CSSPrimitiveValue(v)),
toStore='parts'
)
# COLOR PRODUCTION
funccolor = Sequence(Prod(name='FUNC',
match=lambda t, v: t == types.FUNCTION and cssutils.helper.normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla('),
toSeq=lambda t, v: (t, v),#cssutils.helper.normalize(v)),
toStore='colorType'),
PreDef.unary(),
valueProd,
# 2 or 3 more values starting with Comma
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (2, 3)),
PreDef.funcEnd()
)
colorprods = Choice(funccolor,
PreDef.hexcolor('colorType'),
Prod(name='named color',
match=lambda t, v: t == types.IDENT,
toStore='colorType'
)
)
# store: colorType, parts
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'RGBColor',
colorprods,
keepS=True,
store={'parts': []})
if wellformed:
self.wellformed = True
if store['colorType'].type == self._prods.HASH:
self._colorType = 'HEX'
elif store['colorType'].type == self._prods.IDENT:
self._colorType = 'Named Color'
else:
self._colorType = store['colorType'].value[:-1]
#self._colorType = cssutils.helper.normalize(store['colorType'].value)[:-1]
self._setSeq(seq)
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
_setCssText)
colorType = property(lambda self: self._colorType)
class CalcValue(CSSFunction):
"""Calc Function"""
_functionName = u'Function calc()'
def _productiondefinition(self):
"""Return defintion used for parsing."""
types = self._prods # rename!
def toSeq(t, tokens):
"Do not normalize function name!"
return t[0], t[1]
funcProds = Sequence(Prod(name='calc',
match=lambda t, v: t == types.FUNCTION,
toSeq=toSeq
),
Sequence(Choice(Prod(name='nested function',
match=lambda t, v: t == self._prods.FUNCTION,
toSeq=lambda t, tokens: (CSSFunction._functionName,
CSSFunction(cssutils.helper.pushtoken(t,
tokens)))
),
Prod(name='part',
match=lambda t, v: v != u')',
toSeq=lambda t, tokens: (t[0], t[1])),
),
minmax=lambda: (0, None)),
PreDef.funcEnd(stop=True))
return funcProds
def _getCssText(self):
return cssutils.ser.do_css_CalcValue(self)
def _setCssText(self, cssText):
return super(CalcValue, self)._setCssText(cssText)
cssText = property(_getCssText, _setCssText,
doc=u"A string representation of the current value.")
class ExpressionValue(CSSFunction):
"""Special IE only CSSFunction which may contain *anything*.
Used for expressions and ``alpha(opacity=100)`` currently."""
_functionName = u'Expression (IE only)'
def _productiondefinition(self):
"""Return defintion used for parsing."""
types = self._prods # rename!
def toSeq(t, tokens):
"Do not normalize function name!"
return t[0], t[1]
funcProds = Sequence(Prod(name='expression',
match=lambda t, v: t == types.FUNCTION,
toSeq=toSeq
),
Sequence(Choice(Prod(name='nested function',
match=lambda t, v: t == self._prods.FUNCTION,
toSeq=lambda t, tokens: (ExpressionValue._functionName,
ExpressionValue(cssutils.helper.pushtoken(t,
tokens)))
),
Prod(name='part',
match=lambda t, v: v != u')',
toSeq=lambda t, tokens: (t[0], t[1])),
),
minmax=lambda: (0, None)),
PreDef.funcEnd(stop=True))
return funcProds
def _getCssText(self):
return cssutils.ser.do_css_ExpressionValue(self)
def _setCssText(self, cssText):
#self._log.warn(u'CSSValue: Unoffial and probably invalid MS value used!')
return super(ExpressionValue, self)._setCssText(cssText)
cssText = property(_getCssText, _setCssText,
doc=u"A string representation of the current value.")
class CSSVariable(CSSValue):
"""The CSSVariable represents a call to CSS Variable."""
def __init__(self, cssText=None, parent=None, readonly=False):
"""Init a new CSSVariable.
:param cssText:
the parsable cssText of the value, e.g. ``var(x)``
:param readonly:
defaults to False
"""
self._name = None
super(CSSVariable, self).__init__(cssText=cssText,
parent=parent,
readonly=readonly)
def __repr__(self):
return u"cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
def __str__(self):
return u"<cssutils.css.%s object name=%r value=%r at 0x%x>" % (
self.__class__.__name__,
self.name,
self.value,
id(self))
def _setCssText(self, cssText):
self._checkReadonly()
types = self._prods # rename!
funcProds = Sequence(Prod(name='var',
match=lambda t, v: t == types.FUNCTION
),
PreDef.ident(toStore='ident'),
PreDef.funcEnd(stop=True))
# store: name of variable
store = {'ident': None}
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'CSSVariable',
funcProds,
keepS=True)
if wellformed:
self._name = store['ident'].value
self._setSeq(seq)
self.wellformed = True
cssText = property(lambda self: cssutils.ser.do_css_CSSVariable(self),
_setCssText,
doc=u"A string representation of the current variable.")
cssValueType = CSSValue.CSS_VARIABLE
# TODO: writable? check if var (value) available?
name = property(lambda self: self._name)
def _getValue(self):
"Find contained sheet and @variables there"
try:
variables = self.parent.parent.parentRule.parentStyleSheet.variables
except AttributeError:
return None
else:
try:
return variables[self.name]
except KeyError:
return None
value = property(_getValue)