mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-11-25 07:45:01 -05:00
415 lines
14 KiB
Python
415 lines
14 KiB
Python
"""Property is a single CSS property in a CSSStyleDeclaration
|
|
|
|
Internal use only, may be removed in the future!
|
|
"""
|
|
__all__ = ['Property']
|
|
__docformat__ = 'restructuredtext'
|
|
__version__ = '$Id: property.py 1444 2008-08-31 18:45:35Z cthedot $'
|
|
|
|
import xml.dom
|
|
import cssutils
|
|
#import cssproperties
|
|
from cssutils.profiles import profiles
|
|
from cssvalue import CSSValue
|
|
from cssutils.helper import Deprecated
|
|
|
|
class Property(cssutils.util.Base):
|
|
"""
|
|
(cssutils) a CSS property in a StyleDeclaration of a CSSStyleRule
|
|
|
|
Properties
|
|
==========
|
|
cssText
|
|
a parsable textual representation of this property
|
|
name
|
|
normalized name of the property, e.g. "color" when name is "c\olor"
|
|
(since 0.9.5)
|
|
literalname (since 0.9.5)
|
|
original name of the property in the source CSS which is not normalized
|
|
e.g. "C\\OLor"
|
|
cssValue
|
|
the relevant CSSValue instance for this property
|
|
value
|
|
the string value of the property, same as cssValue.cssText
|
|
priority
|
|
of the property (currently only u"important" or None)
|
|
literalpriority
|
|
original priority of the property in the source CSS which is not
|
|
normalized e.g. "IM\portant"
|
|
seqs
|
|
combination of a list for seq of name, a CSSValue object, and
|
|
a list for seq of priority (empty or [!important] currently)
|
|
valid
|
|
if this Property is valid
|
|
wellformed
|
|
if this Property is syntactically ok
|
|
|
|
DEPRECATED normalname (since 0.9.5)
|
|
normalized name of the property, e.g. "color" when name is "c\olor"
|
|
|
|
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):
|
|
"""
|
|
inits property
|
|
|
|
name
|
|
a property name string (will be normalized)
|
|
value
|
|
a property value string
|
|
priority
|
|
an optional priority string which currently must be u'',
|
|
u'!important' or u'important'
|
|
_mediaQuery boolean
|
|
if True value is optional as used by MediaQuery objects
|
|
"""
|
|
super(Property, self).__init__()
|
|
|
|
self.seqs = [[], None, []]
|
|
self.valid = False
|
|
self.wellformed = False
|
|
self._mediaQuery = _mediaQuery
|
|
|
|
if name:
|
|
self.name = name
|
|
else:
|
|
self._name = u''
|
|
self._literalname = u''
|
|
self.__normalname = u'' # DEPRECATED
|
|
|
|
if value:
|
|
self.cssValue = value
|
|
else:
|
|
self.seqs[1] = CSSValue()
|
|
|
|
if priority:
|
|
self.priority = priority
|
|
else:
|
|
self._priority = u''
|
|
self._literalpriority = u''
|
|
|
|
def _getCssText(self):
|
|
"""
|
|
returns serialized property cssText
|
|
"""
|
|
return cssutils.ser.do_Property(self)
|
|
|
|
def _setCssText(self, cssText):
|
|
"""
|
|
DOMException on setting
|
|
|
|
- NO_MODIFICATION_ALLOWED_ERR: (CSSRule)
|
|
Raised if the rule is readonly.
|
|
- SYNTAX_ERR: (self)
|
|
Raised if the specified CSS string value has a syntax error and
|
|
is unparsable.
|
|
"""
|
|
# 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.cssValue = 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: %r' %
|
|
self._valuestr(cssText), colontoken)
|
|
elif not nametokens:
|
|
wellformed = False
|
|
self._log.error(u'Property: No property name found: %r.' %
|
|
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: %r.' %
|
|
self._valuestr(cssText), colontoken)
|
|
|
|
if wellformed:
|
|
self.wellformed = True
|
|
self.name = nametokens
|
|
self.cssValue = valuetokens
|
|
self.priority = prioritytokens
|
|
|
|
else:
|
|
self._log.error(u'Property: No property name found: %r.' %
|
|
self._valuestr(cssText))
|
|
|
|
cssText = property(fget=_getCssText, fset=_setCssText,
|
|
doc="A parsable textual representation.")
|
|
|
|
def _setName(self, name):
|
|
"""
|
|
DOMException on setting
|
|
|
|
- SYNTAX_ERR: (self)
|
|
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]
|
|
else:
|
|
token = None
|
|
|
|
if not new['literalname']:
|
|
wellformed = False
|
|
self._log.error(u'Property: No name found: %r' %
|
|
self._valuestr(name), token=token)
|
|
|
|
if wellformed:
|
|
self.wellformed = True
|
|
self._literalname = new['literalname']
|
|
self._name = self._normalize(self._literalname)
|
|
self.__normalname = self._name # DEPRECATED
|
|
self.seqs[0] = newseq
|
|
|
|
# validate
|
|
if self._name not in profiles.propertiesByProfile():
|
|
self.valid = False
|
|
tokenizer=self._tokenize2(name)
|
|
self._log.warn(u'Property: Unknown Property: %r.' %
|
|
new['literalname'], token=token, neverraise=True)
|
|
else:
|
|
self.valid = True
|
|
if self.cssValue:
|
|
self.cssValue._propertyName = self._name
|
|
self.valid = self.cssValue.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 _getCSSValue(self):
|
|
return self.seqs[1]
|
|
|
|
def _setCSSValue(self, cssText):
|
|
"""
|
|
see css.CSSValue
|
|
|
|
DOMException on setting?
|
|
|
|
- SYNTAX_ERR: (self)
|
|
Raised if the specified CSS string value has a syntax error
|
|
(according to the attached property) or is unparsable.
|
|
- TODO: INVALID_MODIFICATION_ERR:
|
|
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] = CSSValue()
|
|
else:
|
|
if not self.seqs[1]:
|
|
self.seqs[1] = CSSValue()
|
|
|
|
cssvalue = self.seqs[1]
|
|
cssvalue._propertyName = self.name
|
|
cssvalue.cssText = cssText
|
|
if cssvalue._value and cssvalue.wellformed:
|
|
self.seqs[1] = cssvalue
|
|
self.valid = self.valid and cssvalue.valid
|
|
self.wellformed = self.wellformed and cssvalue.wellformed
|
|
|
|
cssValue = property(_getCSSValue, _setCSSValue,
|
|
doc="(cssutils) CSSValue object of this property")
|
|
|
|
def _getValue(self):
|
|
if self.cssValue:
|
|
return self.cssValue._value
|
|
else:
|
|
return u''
|
|
|
|
def _setValue(self, value):
|
|
self.cssValue.cssText = value
|
|
self.valid = self.valid and self.cssValue.valid
|
|
self.wellformed = self.wellformed and self.cssValue.wellformed
|
|
|
|
value = property(_getValue, _setValue,
|
|
doc="The textual value of this Properties cssValue.")
|
|
|
|
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;}
|
|
|
|
DOMException on setting
|
|
|
|
- SYNTAX_ERR: (self)
|
|
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 - 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)
|
|
normalval = self._tokenvalue(token, normalize=True)
|
|
if 'important' == expected == normalval:
|
|
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: %r.' %
|
|
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
|
|
if self._priority not in (u'', u'important'):
|
|
self.valid = False
|
|
self._log.info(u'Property: No CSS2 priority value: %r.' %
|
|
self._priority, neverraise=True)
|
|
|
|
priority = property(lambda self: self._priority, _setPriority,
|
|
doc="(cssutils) Priority of this property")
|
|
|
|
literalpriority = property(lambda self: self._literalpriority,
|
|
doc="Readonly literal (not normalized) priority of this property")
|
|
|
|
def __repr__(self):
|
|
return "cssutils.css.%s(name=%r, value=%r, priority=%r)" % (
|
|
self.__class__.__name__,
|
|
self.literalname, self.cssValue.cssText, self.priority)
|
|
|
|
def __str__(self):
|
|
return "<%s.%s object name=%r value=%r priority=%r at 0x%x>" % (
|
|
self.__class__.__module__, self.__class__.__name__,
|
|
self.name, self.cssValue.cssText, self.priority, id(self))
|
|
|
|
@Deprecated(u'Use property ``name`` instead (since cssutils 0.9.5).')
|
|
def _getNormalname(self):
|
|
return self.__normalname
|
|
normalname = property(_getNormalname,
|
|
doc="DEPRECATED since 0.9.5, use name instead") |