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.
 
 
 
 
 

510 lines
18 KiB

"""Property is a single CSS property in a CSSStyleDeclaration."""
__all__ = ['Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.helper import Deprecated
from value import PropertyValue
import cssutils
import xml.dom
class Property(cssutils.util.Base):
"""A CSS property in a StyleDeclaration of a CSSStyleRule (cssutils).
Format::
property = name
: IDENT S*
;
expr = value
: term [ operator term ]*
;
term
: unary_operator?
[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* |
ANGLE S* | TIME S* | FREQ S* | function ]
| STRING S* | IDENT S* | URI S* | hexcolor
;
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*
;
prio
: IMPORTANT_SYM S*
;
"""
def __init__(self, name=None, value=None, priority=u'',
_mediaQuery=False, parent=None):
"""
:param name:
a property name string (will be normalized)
:param value:
a property value string
:param priority:
an optional priority string which currently must be u'',
u'!important' or u'important'
:param _mediaQuery:
if ``True`` value is optional (used by MediaQuery)
:param parent:
the parent object, normally a
:class:`cssutils.css.CSSStyleDeclaration`
"""
super(Property, self).__init__()
self.seqs = [[], None, []]
self.wellformed = False
self._mediaQuery = _mediaQuery
self.parent = parent
self.__nametoken = None
self._name = u''
self._literalname = u''
self.seqs[1] = PropertyValue(parent=self)
if name:
self.name = name
self.propertyValue = value
self._priority = u''
self._literalpriority = u''
if priority:
self.priority = priority
def __repr__(self):
return u"cssutils.css.%s(name=%r, value=%r, priority=%r)" % (
self.__class__.__name__,
self.literalname,
self.propertyValue.cssText,
self.priority)
def __str__(self):
return u"<%s.%s object name=%r value=%r priority=%r valid=%r at 0x%x>" \
% (self.__class__.__module__,
self.__class__.__name__,
self.name,
self.propertyValue.cssText,
self.priority,
self.valid,
id(self))
def _isValidating(self):
"""Return True if validation is enabled."""
try:
return self.parent.validating
except AttributeError:
# default (no parent)
return True
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_Property(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
# check and prepare tokenlists for setting
tokenizer = self._tokenize2(cssText)
nametokens = self._tokensupto2(tokenizer, propertynameendonly=True)
if nametokens:
wellformed = True
valuetokens = self._tokensupto2(tokenizer,
propertyvalueendonly=True)
prioritytokens = self._tokensupto2(tokenizer,
propertypriorityendonly=True)
if self._mediaQuery and not valuetokens:
# MediaQuery may consist of name only
self.name = nametokens
self.propertyValue = None
self.priority = None
return
# remove colon from nametokens
colontoken = nametokens.pop()
if self._tokenvalue(colontoken) != u':':
wellformed = False
self._log.error(u'Property: No ":" after name found: %s' %
self._valuestr(cssText), colontoken)
elif not nametokens:
wellformed = False
self._log.error(u'Property: No property name found: %s' %
self._valuestr(cssText), colontoken)
if valuetokens:
if self._tokenvalue(valuetokens[-1]) == u'!':
# priority given, move "!" to prioritytokens
prioritytokens.insert(0, valuetokens.pop(-1))
else:
wellformed = False
self._log.error(u'Property: No property value found: %s' %
self._valuestr(cssText), colontoken)
if wellformed:
self.wellformed = True
self.name = nametokens
self.propertyValue = valuetokens
self.priority = prioritytokens
# also invalid values are set!
if self._isValidating():
self.validate()
else:
self._log.error(u'Property: No property name found: %s' %
self._valuestr(cssText))
cssText = property(fget=_getCssText, fset=_setCssText,
doc="A parsable textual representation.")
def _setName(self, name):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified name has a syntax error and is
unparsable.
"""
# for closures: must be a mutable
new = {'literalname': None,
'wellformed': True}
def _ident(expected, seq, token, tokenizer=None):
# name
if 'name' == expected:
new['literalname'] = self._tokenvalue(token).lower()
seq.append(new['literalname'])
return 'EOF'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected ident.', token)
return expected
newseq = []
wellformed, expected = self._parse(expected='name',
seq=newseq,
tokenizer=self._tokenize2(name),
productions={'IDENT': _ident})
wellformed = wellformed and new['wellformed']
# post conditions
# define a token for error logging
if isinstance(name, list):
token = name[0]
self.__nametoken = token
else:
token = None
if not new['literalname']:
wellformed = False
self._log.error(u'Property: No name found: %s' %
self._valuestr(name), token=token)
if wellformed:
self.wellformed = True
self._literalname = new['literalname']
self._name = self._normalize(self._literalname)
self.seqs[0] = newseq
# validate
if self._isValidating() and self._name not in cssutils.profile.knownNames:
# self.valid = False
self._log.warn(u'Property: Unknown Property name.',
token=token, neverraise=True)
else:
pass
# self.valid = True
# if self.propertyValue:
# self.propertyValue._propertyName = self._name
# #self.valid = self.propertyValue.valid
else:
self.wellformed = False
name = property(lambda self: self._name, _setName,
doc="Name of this property.")
literalname = property(lambda self: self._literalname,
doc="Readonly literal (not normalized) name "
"of this property")
def _setPropertyValue(self, cssText):
"""
See css.PropertyValue
: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.
"""
if self._mediaQuery and not cssText:
self.seqs[1] = PropertyValue(parent=self)
else:
self.seqs[1].cssText = cssText
self.wellformed = self.wellformed and self.seqs[1].wellformed
propertyValue = property(lambda self: self.seqs[1],
_setPropertyValue,
doc=u"(cssutils) PropertyValue object of property")
def _getValue(self):
if self.propertyValue:
# value without comments
return self.propertyValue.value
else:
return u''
def _setValue(self, value):
self._setPropertyValue(value)
value = property(_getValue, _setValue,
doc="The textual value of this Properties propertyValue.")
def _setPriority(self, priority):
"""
priority
a string, currently either u'', u'!important' or u'important'
Format::
prio
: IMPORTANT_SYM S*
;
"!"{w}"important" {return IMPORTANT_SYM;}
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified priority has a syntax error and is
unparsable.
In this case a priority not equal to None, "" or "!{w}important".
As CSSOM defines CSSStyleDeclaration.getPropertyPriority resulting
in u'important' this value is also allowed to set a Properties
priority
"""
if self._mediaQuery:
self._priority = u''
self._literalpriority = u''
if priority:
self._log.error(u'Property: No priority in a MediaQuery - '
u'ignored.')
return
if isinstance(priority, basestring) and\
u'important' == self._normalize(priority):
priority = u'!%s' % priority
# for closures: must be a mutable
new = {'literalpriority': u'',
'wellformed': True}
def _char(expected, seq, token, tokenizer=None):
# "!"
val = self._tokenvalue(token)
if u'!' == expected == val:
seq.append(val)
return 'important'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected char.', token)
return expected
def _ident(expected, seq, token, tokenizer=None):
# "important"
val = self._tokenvalue(token)
if 'important' == expected:
new['literalpriority'] = val
seq.append(val)
return 'EOF'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected ident.', token)
return expected
newseq = []
wellformed, expected = self._parse(expected='!',
seq=newseq,
tokenizer=self._tokenize2(priority),
productions={'CHAR': _char,
'IDENT': _ident})
wellformed = wellformed and new['wellformed']
# post conditions
if priority and not new['literalpriority']:
wellformed = False
self._log.info(u'Property: Invalid priority: %s' %
self._valuestr(priority))
if wellformed:
self.wellformed = self.wellformed and wellformed
self._literalpriority = new['literalpriority']
self._priority = self._normalize(self.literalpriority)
self.seqs[2] = newseq
# validate priority
if self._priority not in (u'', u'important'):
self._log.error(u'Property: No CSS priority value: %s' %
self._priority)
priority = property(lambda self: self._priority, _setPriority,
doc="Priority of this property.")
literalpriority = property(lambda self: self._literalpriority,
doc="Readonly literal (not normalized) priority of this property")
def _setParent(self, parent):
self._parent = parent
parent = property(lambda self: self._parent, _setParent,
doc="The Parent Node (normally a CSSStyledeclaration) of this "
"Property")
def validate(self):
"""Validate value against `profiles` which are checked dynamically.
properties in e.g. @font-face rules are checked against
``cssutils.profile.CSS3_FONT_FACE`` only.
For each of the following cases a message is reported:
- INVALID (so the property is known but not valid)
``ERROR Property: Invalid value for "{PROFILE-1[/PROFILE-2...]"
property: ...``
- VALID but not in given profiles or defaultProfiles
``WARNING Property: Not valid for profile "{PROFILE-X}" but valid
"{PROFILE-Y}" property: ...``
- VALID in current profile
``DEBUG Found valid "{PROFILE-1[/PROFILE-2...]" property...``
- UNKNOWN property
``WARNING Unknown Property name...`` is issued
so for example::
cssutils.log.setLevel(logging.DEBUG)
parser = cssutils.CSSParser()
s = parser.parseString('''body {
unknown-property: x;
color: 4;
color: rgba(1,2,3,4);
color: red
}''')
# Log output:
WARNING Property: Unknown Property name. [2:9: unknown-property]
ERROR Property: Invalid value for "CSS Color Module Level 3/CSS Level 2.1" property: 4 [3:9: color]
DEBUG Property: Found valid "CSS Color Module Level 3" value: rgba(1, 2, 3, 4) [4:9: color]
DEBUG Property: Found valid "CSS Level 2.1" value: red [5:9: color]
and when setting an explicit default profile::
cssutils.profile.defaultProfiles = cssutils.profile.CSS_LEVEL_2
s = parser.parseString('''body {
unknown-property: x;
color: 4;
color: rgba(1,2,3,4);
color: red
}''')
# Log output:
WARNING Property: Unknown Property name. [2:9: unknown-property]
ERROR Property: Invalid value for "CSS Color Module Level 3/CSS Level 2.1" property: 4 [3:9: color]
WARNING Property: Not valid for profile "CSS Level 2.1" but valid "CSS Color Module Level 3" value: rgba(1, 2, 3, 4) [4:9: color]
DEBUG Property: Found valid "CSS Level 2.1" value: red [5:9: color]
"""
valid = False
profiles = None
try:
# if @font-face use that profile
rule = self.parent.parentRule
except AttributeError:
pass
else:
if rule is not None:
if rule.type == rule.FONT_FACE_RULE:
profiles = [cssutils.profile.CSS3_FONT_FACE]
#TODO: same for @page
if self.name and self.value:
cv = self.propertyValue
# TODO
# if cv.cssValueType == cv.CSS_VARIABLE and not cv.value:
# # TODO: false alarms too!
# cssutils.log.warn(u'No value for variable "%s" found, keeping '
# u'variable.' % cv.name, neverraise=True)
if self.name in cssutils.profile.knownNames:
# add valid, matching, validprofiles...
valid, matching, validprofiles = \
cssutils.profile.validateWithProfile(self.name,
self.value,
profiles)
if not valid:
self._log.error(u'Property: Invalid value for '
u'"%s" property: %s'
% (u'/'.join(validprofiles), self.value),
token=self.__nametoken,
neverraise=True)
# TODO: remove logic to profiles!
elif valid and not matching:#(profiles and profiles not in validprofiles):
if not profiles:
notvalidprofiles = u'/'.join(cssutils.profile.defaultProfiles)
else:
notvalidprofiles = profiles
self._log.warn(u'Property: Not valid for profile "%s" '
u'but valid "%s" value: %s '
% (notvalidprofiles, u'/'.join(validprofiles),
self.value),
token = self.__nametoken,
neverraise=True)
valid = False
elif valid:
self._log.debug(u'Property: Found valid "%s" value: %s'
% (u'/'.join(validprofiles), self.value),
token = self.__nametoken,
neverraise=True)
if self._priority not in (u'', u'important'):
valid = False
return valid
valid = property(validate, doc=u"Check if value of this property is valid "
u"in the properties context.")
@Deprecated(u'Use ``property.propertyValue`` instead.')
def _getCSSValue(self):
return self.propertyValue
@Deprecated(u'Use ``property.propertyValue`` instead.')
def _setCSSValue(self, cssText):
self._setPropertyValue(cssText)
cssValue = property(_getCSSValue, _setCSSValue,
doc="(DEPRECATED) Use ``property.propertyValue`` instead.")