mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Update cssutils to latest
This commit is contained in:
parent
5ff51fc3f9
commit
1a1cf7f1b9
@ -70,11 +70,11 @@ Usage may be::
|
||||
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
|
||||
__date__ = '$LastChangedDate:: 2009-02-16 12:05:02 -0800 #$:'
|
||||
__date__ = '$LastChangedDate:: 2009-05-09 13:59:54 -0700 #$:'
|
||||
|
||||
VERSION = '0.9.6a1'
|
||||
VERSION = '0.9.6a5'
|
||||
|
||||
__version__ = '%s $Id: __init__.py 1669 2009-02-16 20:05:02Z cthedot $' % VERSION
|
||||
__version__ = '%s $Id: __init__.py 1747 2009-05-09 20:59:54Z cthedot $' % VERSION
|
||||
|
||||
import codec
|
||||
import xml.dom
|
||||
@ -92,6 +92,9 @@ from parse import CSSParser
|
||||
from serialize import CSSSerializer
|
||||
ser = CSSSerializer()
|
||||
|
||||
from profiles import Profiles
|
||||
profile = Profiles(log=log)
|
||||
|
||||
# used by Selector defining namespace prefix '*'
|
||||
_ANYNS = -1
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule."""
|
||||
__all__ = ['CSSMediaRule']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: cssmediarule.py 1641 2009-01-13 21:05:37Z cthedot $'
|
||||
__version__ = '$Id: cssmediarule.py 1743 2009-05-09 20:33:15Z cthedot $'
|
||||
|
||||
import cssrule
|
||||
import cssutils
|
||||
@ -131,8 +131,15 @@ class CSSMediaRule(cssrule.CSSRule):
|
||||
mediaendonly=True,
|
||||
separateEnd=True)
|
||||
nonetoken = self._nexttoken(tokenizer, None)
|
||||
if (u'}' != self._tokenvalue(braceOrEOF) and
|
||||
'EOF' != self._type(braceOrEOF)):
|
||||
if 'EOF' == self._type(braceOrEOF):
|
||||
# HACK!!!
|
||||
# TODO: Not complete, add EOF to rule and } to @media
|
||||
cssrulestokens.append(braceOrEOF)
|
||||
braceOrEOF = ('CHAR', '}', 0, 0)
|
||||
self._log.debug(u'CSSMediaRule: Incomplete, adding "}".',
|
||||
token=braceOrEOF, neverraise=True)
|
||||
|
||||
if u'}' != self._tokenvalue(braceOrEOF):
|
||||
self._log.error(u'CSSMediaRule: No "}" found.',
|
||||
token=braceOrEOF)
|
||||
elif nonetoken:
|
||||
|
@ -51,7 +51,7 @@ TODO:
|
||||
"""
|
||||
__all__ = ['CSSStyleDeclaration', 'Property']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: cssstyledeclaration.py 1658 2009-02-07 18:24:40Z cthedot $'
|
||||
__version__ = '$Id: cssstyledeclaration.py 1710 2009-04-18 15:46:20Z cthedot $'
|
||||
|
||||
from cssproperties import CSS2Properties
|
||||
from property import Property
|
||||
@ -613,7 +613,7 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
|
||||
except IndexError:
|
||||
return u''
|
||||
|
||||
length = property(lambda self: len(self.__nnames()),
|
||||
length = property(lambda self: len(list(self.__nnames())),
|
||||
doc="(DOM) The number of distinct properties that have been explicitly "
|
||||
"in this declaration block. The range of valid indices is 0 to "
|
||||
"length-1 inclusive. These are properties with a different ``name`` "
|
||||
|
@ -7,10 +7,9 @@
|
||||
"""
|
||||
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: cssvalue.py 1638 2009-01-13 20:39:33Z cthedot $'
|
||||
__version__ = '$Id: cssvalue.py 1684 2009-03-01 18:26:21Z cthedot $'
|
||||
|
||||
from cssutils.prodparser import *
|
||||
from cssutils.profiles import profiles
|
||||
import cssutils
|
||||
import cssutils.helper
|
||||
import re
|
||||
@ -121,7 +120,8 @@ class CSSValue(cssutils.util._NewBase):
|
||||
# special case IE only expression
|
||||
Prod(name='expression',
|
||||
match=lambda t, v: t == self._prods.FUNCTION and
|
||||
cssutils.helper.normalize(v) == 'expression(',
|
||||
cssutils.helper.normalize(v) in (u'expression(',
|
||||
u'alpha('),
|
||||
nextSor=nextSor,
|
||||
toSeq=lambda t, tokens: (ExpressionValue.name,
|
||||
ExpressionValue(cssutils.helper.pushtoken(t,
|
||||
@ -968,7 +968,8 @@ class RGBColor(CSSPrimitiveValue):
|
||||
|
||||
|
||||
class ExpressionValue(CSSFunction):
|
||||
"""Special IE only CSSFunction which may contain *anything*."""
|
||||
"""Special IE only CSSFunction which may contain *anything*.
|
||||
Used for expressions and ``alpha(opacity=100)`` currently"""
|
||||
name = u'Expression (IE only)'
|
||||
|
||||
def _productiondefinition(self):
|
||||
|
@ -1,10 +1,9 @@
|
||||
"""Property is a single CSS property in a CSSStyleDeclaration."""
|
||||
__all__ = ['Property']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: property.py 1664 2009-02-07 22:47:09Z cthedot $'
|
||||
__version__ = '$Id: property.py 1685 2009-03-01 18:26:48Z cthedot $'
|
||||
|
||||
from cssutils.helper import Deprecated
|
||||
from cssutils.profiles import profiles
|
||||
from cssvalue import CSSValue
|
||||
import cssutils
|
||||
import xml.dom
|
||||
@ -67,6 +66,7 @@ class Property(cssutils.util.Base):
|
||||
self._mediaQuery = _mediaQuery
|
||||
self._parent = _parent
|
||||
|
||||
self.__nametoken = None
|
||||
self._name = u''
|
||||
self._literalname = u''
|
||||
if name:
|
||||
@ -193,6 +193,7 @@ class Property(cssutils.util.Base):
|
||||
# define a token for error logging
|
||||
if isinstance(name, list):
|
||||
token = name[0]
|
||||
self.__nametoken = token
|
||||
else:
|
||||
token = None
|
||||
|
||||
@ -208,9 +209,9 @@ class Property(cssutils.util.Base):
|
||||
self.seqs[0] = newseq
|
||||
|
||||
# # validate
|
||||
if self._name not in profiles.knownnames:
|
||||
if self._name not in cssutils.profile.knownNames:
|
||||
# self.valid = False
|
||||
self._log.warn(u'Property: Unknown Property.',
|
||||
self._log.warn(u'Property: Unknown Property name.',
|
||||
token=token, neverraise=True)
|
||||
else:
|
||||
pass
|
||||
@ -354,7 +355,7 @@ class Property(cssutils.util.Base):
|
||||
# validate priority
|
||||
if self._priority not in (u'', u'important'):
|
||||
self._log.error(u'Property: No CSS priority value: %r.' %
|
||||
self._priority)
|
||||
self._priority)
|
||||
|
||||
priority = property(lambda self: self._priority, _setPriority,
|
||||
doc="Priority of this property.")
|
||||
@ -362,42 +363,101 @@ class Property(cssutils.util.Base):
|
||||
literalpriority = property(lambda self: self._literalpriority,
|
||||
doc="Readonly literal (not normalized) priority of this property")
|
||||
|
||||
def validate(self, profile=None):
|
||||
"""Validate value against `profile`.
|
||||
def validate(self, profiles=None):
|
||||
"""Validate value against `profiles`.
|
||||
|
||||
:param profile:
|
||||
A profile name used for validating. If no `profile` is given
|
||||
``Property.profiles
|
||||
:param profiles:
|
||||
A list of profile names used for validating. If no `profiles`
|
||||
is given ``cssutils.profile.defaultProfiles`` is used
|
||||
|
||||
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
|
||||
|
||||
if self.name and self.value:
|
||||
if profile is None:
|
||||
usedprofile = cssutils.profiles.defaultprofile
|
||||
else:
|
||||
usedprofile = profile
|
||||
|
||||
if self.name in profiles.knownnames:
|
||||
valid, validprofiles = profiles.validateWithProfile(self.name,
|
||||
self.value,
|
||||
usedprofile)
|
||||
|
||||
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 "%s" property: %s: %s'
|
||||
% (u'/'.join(validprofiles),
|
||||
self.name,
|
||||
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)
|
||||
elif valid and (usedprofile and usedprofile not in validprofiles):
|
||||
self._log.warn(u'Property: Not valid for profile "%s": %s: %s'
|
||||
% (usedprofile, self.name, self.value),
|
||||
neverraise=True)
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
self._log.info(u'Property: Found valid "%s" property: %s: %s'
|
||||
% (u'/'.join(validprofiles),
|
||||
self.name,
|
||||
self.value),
|
||||
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'):
|
||||
|
@ -7,7 +7,7 @@ TODO
|
||||
"""
|
||||
__all__ = ['Selector']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: selector.py 1638 2009-01-13 20:39:33Z cthedot $'
|
||||
__version__ = '$Id: selector.py 1741 2009-05-09 18:20:20Z cthedot $'
|
||||
|
||||
from cssutils.util import _SimpleNamespaces
|
||||
import cssutils
|
||||
@ -701,6 +701,14 @@ class Selector(cssutils.util.Base2):
|
||||
u'Selector: Unexpected negation.', token=token)
|
||||
return expected
|
||||
|
||||
def _atkeyword(expected, seq, token, tokenizer=None):
|
||||
"invalidates selector"
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected ATKEYWORD.', token=token)
|
||||
return expected
|
||||
|
||||
|
||||
# expected: only|not or mediatype, mediatype, feature, and
|
||||
newseq = self._tempSeq()
|
||||
|
||||
@ -727,7 +735,8 @@ class Selector(cssutils.util.Base2):
|
||||
'INCLUDES': _attcombinator,
|
||||
|
||||
'S': _S,
|
||||
'COMMENT': _COMMENT})
|
||||
'COMMENT': _COMMENT,
|
||||
'ATKEYWORD': _atkeyword})
|
||||
wellformed = wellformed and new['wellformed']
|
||||
|
||||
# post condition
|
||||
|
@ -12,14 +12,14 @@ open issues
|
||||
"""
|
||||
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: cssproductions.py 1537 2008-12-03 14:37:10Z cthedot $'
|
||||
__version__ = '$Id: cssproductions.py 1738 2009-05-02 13:03:28Z cthedot $'
|
||||
|
||||
# a complete list of css3 macros
|
||||
MACROS = {
|
||||
'nonascii': r'[^\0-\177]',
|
||||
'unicode': r'\\[0-9a-f]{1,6}(?:{nl}|{s})?',
|
||||
# 'escape': r'{unicode}|\\[ -~\200-\4177777]',
|
||||
'escape': r'{unicode}|\\[ -~\200-\777]',
|
||||
#'escape': r'{unicode}|\\[ -~\200-\777]',
|
||||
'escape': r'{unicode}|\\[^\n\r\f0-9a-f]',
|
||||
'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}',
|
||||
'nmchar': r'[-_a-zA-Z0-9]|{nonascii}|{escape}',
|
||||
'string1': r'"([^\n\r\f\\"]|\\{nl}|{escape})*"',
|
||||
|
@ -16,7 +16,7 @@ log
|
||||
"""
|
||||
__all__ = ['ErrorHandler']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: errorhandler.py 1560 2008-12-14 16:13:16Z cthedot $'
|
||||
__version__ = '$Id: errorhandler.py 1728 2009-05-01 20:35:25Z cthedot $'
|
||||
|
||||
from helper import Deprecated
|
||||
import logging
|
||||
@ -89,9 +89,9 @@ class _ErrorHandler(object):
|
||||
elif issubclass(error, xml.dom.DOMException):
|
||||
error.line = line
|
||||
error.col = col
|
||||
raise error(msg)
|
||||
else:
|
||||
raise error(msg)
|
||||
# raise error(msg, line, col)
|
||||
# else:
|
||||
raise error(msg)
|
||||
else:
|
||||
self._logcall(msg)
|
||||
|
||||
|
@ -68,6 +68,9 @@ def string(value):
|
||||
u'\f', u'\\c ').replace(
|
||||
u'"', u'\\"')
|
||||
|
||||
if value.endswith(u'\\'):
|
||||
value = value[:-1] + u'\\\\'
|
||||
|
||||
return u'"%s"' % value
|
||||
|
||||
def stringvalue(string):
|
||||
@ -77,7 +80,7 @@ def stringvalue(string):
|
||||
|
||||
``'a \'string'`` => ``a 'string``
|
||||
"""
|
||||
return string.replace('\\'+string[0], string[0])[1:-1]
|
||||
return string.replace(u'\\'+string[0], string[0])[1:-1]
|
||||
|
||||
_match_forbidden_in_uri = re.compile(ur'''.*?[\(\)\s\;,'"]''', re.U).match
|
||||
def uri(value):
|
||||
|
@ -1,41 +1,340 @@
|
||||
"""CSS profiles.
|
||||
|
||||
css2 is based on cssvalues
|
||||
contributed by Kevin D. Smith, thanks!
|
||||
|
||||
"cssvalues" is used as a property validator.
|
||||
it is an importable object that contains a dictionary of compiled regular
|
||||
expressions. The keys of this dictionary are all of the valid CSS property
|
||||
names. The values are compiled regular expressions that can be used to
|
||||
validate the values for that property. (Actually, the values are references
|
||||
to the 'match' method of a compiled regular expression, so that they are
|
||||
simply called like functions.)
|
||||
"""CSS profiles.
|
||||
|
||||
Profiles is based on code by Kevin D. Smith, orginally used as cssvalues,
|
||||
thanks!
|
||||
"""
|
||||
__all__ = ['profiles']
|
||||
__all__ = ['Profiles']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $'
|
||||
|
||||
import cssutils
|
||||
import re
|
||||
|
||||
class NoSuchProfileException(Exception):
|
||||
"""Raised if no profile with given name is found"""
|
||||
pass
|
||||
|
||||
|
||||
class Profiles(object):
|
||||
"""
|
||||
All profiles used for validation. ``cssutils.profile`` is a
|
||||
preset object of this class and used by all properties for validation.
|
||||
|
||||
Predefined profiles are (use
|
||||
:meth:`~cssutils.profiles.Profiles.propertiesByProfile` to
|
||||
get a list of defined properties):
|
||||
|
||||
:attr:`~cssutils.profiles.Profiles.CSS_LEVEL_2`
|
||||
Properties defined by CSS2.1
|
||||
:attr:`~cssutils.profiles.Profiles.CSS3_COLOR`
|
||||
CSS 3 color properties
|
||||
:attr:`~cssutils.profiles.Profiles.CSS3_BOX`
|
||||
Currently overflow related properties only
|
||||
:attr:`~cssutils.profiles.Profiles.CSS3_PAGED_MEDIA`
|
||||
As defined at http://www.w3.org/TR/css3-page/ (at 090307)
|
||||
|
||||
Predefined macros are:
|
||||
|
||||
:attr:`~cssutils.profiles.Profiles._TOKEN_MACROS`
|
||||
Macros containing the token values as defined to CSS2
|
||||
:attr:`~cssutils.profiles.Profiles._MACROS`
|
||||
Additional general macros.
|
||||
|
||||
If you want to redefine any of these macros do this in your custom
|
||||
macros.
|
||||
"""
|
||||
CSS_LEVEL_2 = 'CSS Level 2.1'
|
||||
CSS3_COLOR = CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3'
|
||||
CSS3_BOX = CSS_BOX_LEVEL_3 = 'CSS Box Module Level 3'
|
||||
CSS3_PAGED_MEDIA = 'CSS3 Paged Media Module'
|
||||
|
||||
_TOKEN_MACROS = {
|
||||
'ident': r'[-]?{nmstart}{nmchar}*',
|
||||
'name': r'{nmchar}+',
|
||||
'nmstart': r'[_a-z]|{nonascii}|{escape}',
|
||||
'nonascii': r'[^\0-\177]',
|
||||
'unicode': r'\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?',
|
||||
'escape': r'{unicode}|\\[ -~\200-\777]',
|
||||
# 'escape': r'{unicode}|\\[ -~\200-\4177777]',
|
||||
'int': r'[-]?\d+',
|
||||
'nmchar': r'[\w-]|{nonascii}|{escape}',
|
||||
'num': r'[-]?\d+|[-]?\d*\.\d+',
|
||||
'number': r'{num}',
|
||||
'string': r'{string1}|{string2}',
|
||||
'string1': r'"(\\\"|[^\"])*"',
|
||||
'uri': r'url\({w}({string}|(\\\)|[^\)])+){w}\)',
|
||||
'string2': r"'(\\\'|[^\'])*'",
|
||||
'nl': r'\n|\r\n|\r|\f',
|
||||
'w': r'\s*',
|
||||
}
|
||||
_MACROS = {
|
||||
'hexcolor': r'#[0-9a-f]{3}|#[0-9a-f]{6}',
|
||||
'rgbcolor': r'rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
|
||||
'namedcolor': r'(transparent|orange|maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray)',
|
||||
'uicolor': r'(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)',
|
||||
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{uicolor}',
|
||||
#'color': r'(maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray|ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)|#[0-9a-f]{3}|#[0-9a-f]{6}|rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
|
||||
'integer': r'{int}',
|
||||
'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
|
||||
'angle': r'0|{num}(deg|grad|rad)',
|
||||
'time': r'0|{num}m?s',
|
||||
'frequency': r'0|{num}k?Hz',
|
||||
'percentage': r'{num}%',
|
||||
}
|
||||
|
||||
def __init__(self, log=None):
|
||||
"""A few known profiles are predefined."""
|
||||
self._log = log
|
||||
self._profileNames = [] # to keep order, REFACTOR!
|
||||
self._profiles = {}
|
||||
self._defaultProfiles = None
|
||||
|
||||
self.addProfile(self.CSS_LEVEL_2,
|
||||
properties[self.CSS_LEVEL_2],
|
||||
macros[self.CSS_LEVEL_2])
|
||||
self.addProfile(self.CSS3_BOX,
|
||||
properties[self.CSS3_BOX],
|
||||
macros[self.CSS3_BOX])
|
||||
self.addProfile(self.CSS3_COLOR,
|
||||
properties[self.CSS3_COLOR],
|
||||
macros[self.CSS3_COLOR])
|
||||
self.addProfile(self.CSS3_PAGED_MEDIA,
|
||||
properties[self.CSS3_PAGED_MEDIA],
|
||||
macros[self.CSS3_PAGED_MEDIA])
|
||||
|
||||
self.__update_knownNames()
|
||||
|
||||
def _expand_macros(self, dictionary, macros):
|
||||
"""Expand macros in token dictionary"""
|
||||
def macro_value(m):
|
||||
return '(?:%s)' % macros[m.groupdict()['macro']]
|
||||
for key, value in dictionary.items():
|
||||
if not hasattr(value, '__call__'):
|
||||
while re.search(r'{[a-z][a-z0-9-]*}', value):
|
||||
value = re.sub(r'{(?P<macro>[a-z][a-z0-9-]*)}',
|
||||
macro_value, value)
|
||||
dictionary[key] = value
|
||||
return dictionary
|
||||
|
||||
def _compile_regexes(self, dictionary):
|
||||
"""Compile all regular expressions into callable objects"""
|
||||
for key, value in dictionary.items():
|
||||
if not hasattr(value, '__call__'):
|
||||
value = re.compile('^(?:%s)$' % value, re.I).match
|
||||
dictionary[key] = value
|
||||
|
||||
return dictionary
|
||||
|
||||
def __update_knownNames(self):
|
||||
self._knownNames = []
|
||||
for properties in self._profiles.values():
|
||||
self._knownNames.extend(properties.keys())
|
||||
|
||||
def _getDefaultProfiles(self):
|
||||
"If not explicitly set same as Profiles.profiles but in reverse order."
|
||||
if not self._defaultProfiles:
|
||||
return self.profiles#list(reversed(self.profiles))
|
||||
else:
|
||||
return self._defaultProfiles
|
||||
|
||||
def _setDefaultProfiles(self, profiles):
|
||||
"profiles may be a single or a list of profile names"
|
||||
if isinstance(profiles, basestring):
|
||||
self._defaultProfiles = (profiles,)
|
||||
else:
|
||||
self._defaultProfiles = profiles
|
||||
|
||||
defaultProfiles = property(_getDefaultProfiles,
|
||||
_setDefaultProfiles,
|
||||
doc=u"Names of profiles to use for validation."
|
||||
u"To use e.g. the CSS2 profile set "
|
||||
u"``cssutils.profile.defaultProfiles = "
|
||||
u"cssutils.profile.CSS_LEVEL_2``")
|
||||
|
||||
profiles = property(lambda self: self._profileNames,
|
||||
doc=u'Names of all profiles in order as defined.')
|
||||
|
||||
knownNames = property(lambda self: self._knownNames,
|
||||
doc="All known property names of all profiles.")
|
||||
|
||||
def addProfile(self, profile, properties, macros=None):
|
||||
"""Add a new profile with name `profile` (e.g. 'CSS level 2')
|
||||
and the given `properties`.
|
||||
|
||||
:param profile:
|
||||
the new `profile`'s name
|
||||
:param properties:
|
||||
a dictionary of ``{ property-name: propery-value }`` items where
|
||||
property-value is a regex which may use macros defined in given
|
||||
``macros`` or the standard macros Profiles.tokens and
|
||||
Profiles.generalvalues.
|
||||
|
||||
``propery-value`` may also be a function which takes a single
|
||||
argument which is the value to validate and which should return
|
||||
True or False.
|
||||
Any exceptions which may be raised during this custom validation
|
||||
are reported or raised as all other cssutils exceptions depending
|
||||
on cssutils.log.raiseExceptions which e.g during parsing normally
|
||||
is False so the exceptions would be logged only.
|
||||
:param macros:
|
||||
may be used in the given properties definitions. There are some
|
||||
predefined basic macros which may always be used in
|
||||
:attr:`Profiles._TOKEN_MACROS` and :attr:`Profiles._MACROS`.
|
||||
"""
|
||||
if not macros:
|
||||
macros = {}
|
||||
m = Profiles._TOKEN_MACROS.copy()
|
||||
m.update(Profiles._MACROS)
|
||||
m.update(macros)
|
||||
properties = self._expand_macros(properties, m)
|
||||
self._profileNames.append(profile)
|
||||
self._profiles[profile] = self._compile_regexes(properties)
|
||||
|
||||
self.__update_knownNames()
|
||||
|
||||
def removeProfile(self, profile=None, all=False):
|
||||
"""Remove `profile` or remove `all` profiles.
|
||||
|
||||
:param profile:
|
||||
profile name to remove
|
||||
:param all:
|
||||
if ``True`` removes all profiles to start with a clean state
|
||||
:exceptions:
|
||||
- :exc:`cssutils.profiles.NoSuchProfileException`:
|
||||
If given `profile` cannot be found.
|
||||
"""
|
||||
if all:
|
||||
self._profiles.clear()
|
||||
del self._profileNames[:]
|
||||
else:
|
||||
try:
|
||||
del self._profiles[profile]
|
||||
del self._profileNames[self._profileNames.index(profile)]
|
||||
except KeyError:
|
||||
raise NoSuchProfileException(u'No profile %r.' % profile)
|
||||
|
||||
self.__update_knownNames()
|
||||
|
||||
def propertiesByProfile(self, profiles=None):
|
||||
"""Generator: Yield property names, if no `profiles` is given all
|
||||
profile's properties are used.
|
||||
|
||||
:param profiles:
|
||||
a single profile name or a list of names.
|
||||
"""
|
||||
if not profiles:
|
||||
profiles = self.profiles
|
||||
elif isinstance(profiles, basestring):
|
||||
profiles = (profiles, )
|
||||
try:
|
||||
for profile in sorted(profiles):
|
||||
for name in sorted(self._profiles[profile].keys()):
|
||||
yield name
|
||||
except KeyError, e:
|
||||
raise NoSuchProfileException(e)
|
||||
|
||||
def validate(self, name, value):
|
||||
"""Check if `value` is valid for given property `name` using **any**
|
||||
profile.
|
||||
|
||||
:param name:
|
||||
a property name
|
||||
:param value:
|
||||
a CSS value (string)
|
||||
:returns:
|
||||
if the `value` is valid for the given property `name` in any
|
||||
profile
|
||||
"""
|
||||
for profile in self.profiles:
|
||||
if name in self._profiles[profile]:
|
||||
try:
|
||||
# custom validation errors are caught
|
||||
r = bool(self._profiles[profile][name](value))
|
||||
except Exception, e:
|
||||
self._log.error(e, error=Exception)
|
||||
return False
|
||||
if r:
|
||||
return r
|
||||
return False
|
||||
|
||||
def validateWithProfile(self, name, value, profiles=None):
|
||||
"""Check if `value` is valid for given property `name` returning
|
||||
``(valid, profile)``.
|
||||
|
||||
:param name:
|
||||
a property name
|
||||
:param value:
|
||||
a CSS value (string)
|
||||
:param profiles:
|
||||
internal parameter used by Property.validate only
|
||||
:returns:
|
||||
``valid, matching, profiles`` where ``valid`` is if the `value`
|
||||
is valid for the given property `name` in any profile,
|
||||
``matching==True`` if it is valid in the given `profiles`
|
||||
and ``profiles`` the profile names for which the value is valid
|
||||
(or ``[]`` if not valid at all)
|
||||
|
||||
Example::
|
||||
|
||||
>>> cssutils.profile.defaultProfiles = cssutils.profile.CSS_LEVEL_2
|
||||
>>> print cssutils.profile.validateWithProfile('color', 'rgba(1,1,1,1)')
|
||||
(True, False, Profiles.CSS3_COLOR)
|
||||
"""
|
||||
if name not in self.knownNames:
|
||||
return False, False, []
|
||||
else:
|
||||
if not profiles:
|
||||
profiles = self.defaultProfiles
|
||||
elif isinstance(profiles, basestring):
|
||||
profiles = (profiles, )
|
||||
|
||||
for profilename in profiles:
|
||||
# check given profiles
|
||||
if name in self._profiles[profilename]:
|
||||
validate = self._profiles[profilename][name]
|
||||
try:
|
||||
if validate(value):
|
||||
return True, True, [profilename]
|
||||
except Exception, e:
|
||||
self._log.error(e, error=Exception)
|
||||
|
||||
for profilename in (p for p in self._profileNames
|
||||
if p not in profiles):
|
||||
# check remaining profiles as well
|
||||
if name in self._profiles[profilename]:
|
||||
validate = self._profiles[profilename][name]
|
||||
try:
|
||||
if validate(value):
|
||||
return True, False, [profilename]
|
||||
except Exception, e:
|
||||
self._log.error(e, error=Exception)
|
||||
|
||||
names = []
|
||||
for profilename, properties in self._profiles.items():
|
||||
# return profile to which name belongs
|
||||
if name in properties.keys():
|
||||
names.append(profilename)
|
||||
names.sort()
|
||||
return False, False, names
|
||||
|
||||
|
||||
properties = {}
|
||||
macros = {}
|
||||
"""
|
||||
Define some regular expression fragments that will be used as
|
||||
macros within the CSS property value regular expressions.
|
||||
"""
|
||||
css2macros = {
|
||||
macros[Profiles.CSS_LEVEL_2] = {
|
||||
'border-style': 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset',
|
||||
'border-color': '{color}',
|
||||
'border-width': '{length}|thin|medium|thick',
|
||||
|
||||
'background-color': r'{color}|transparent|inherit',
|
||||
'background-image': r'{uri}|none|inherit',
|
||||
'background-position': r'({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right))|((left|center|right)\s*(top|center|bottom))|inherit',
|
||||
#'background-position': r'({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?)|inherit',
|
||||
'background-position': r'({percentage}|{length}|left|center|right)(\s*({percentage}|{length}|top|center|bottom))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?)|inherit',
|
||||
'background-repeat': r'repeat|repeat-x|repeat-y|no-repeat|inherit',
|
||||
'background-attachment': r'scroll|fixed|inherit',
|
||||
|
||||
|
||||
'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)',
|
||||
'counter': r'counter\({w}{identifier}{w}(?:,{w}{list-style-type}{w})?\)',
|
||||
'identifier': r'{ident}',
|
||||
@ -72,7 +371,7 @@ css2macros = {
|
||||
"""
|
||||
Define the regular expressions for validation all CSS values
|
||||
"""
|
||||
properties['css2'] = {
|
||||
properties[Profiles.CSS_LEVEL_2] = {
|
||||
'azimuth': r'{angle}|(behind\s+)?(left-side|far-left|left|center-left|center|center-right|right|far-right|right-side)(\s+behind)?|behind|leftwards|rightwards|inherit',
|
||||
'background-attachment': r'{background-attachment}',
|
||||
'background-color': r'{background-color}',
|
||||
@ -108,7 +407,7 @@ properties['css2'] = {
|
||||
'clear': r'none|left|right|both|inherit',
|
||||
'clip': r'{shape}|auto|inherit',
|
||||
'color': r'{color}|inherit',
|
||||
'content': r'normal|{content}(\s+{content})*|inherit',
|
||||
'content': r'none|normal|{content}(\s+{content})*|inherit',
|
||||
'counter-increment': r'({identifier}(\s+{integer})?)(\s+({identifier}(\s+{integer})))*|none|inherit',
|
||||
'counter-reset': r'({identifier}(\s+{integer})?)(\s+({identifier}(\s+{integer})))*|none|inherit',
|
||||
'cue-after': r'{uri}|none|inherit',
|
||||
@ -191,288 +490,47 @@ properties['css2'] = {
|
||||
'z-index': r'auto|{integer}|inherit',
|
||||
}
|
||||
|
||||
# CSS Box Module Level 3
|
||||
macros[Profiles.CSS3_BOX] = {
|
||||
'overflow': macros[Profiles.CSS_LEVEL_2]['overflow']
|
||||
}
|
||||
properties[Profiles.CSS3_BOX] = {
|
||||
'overflow': '{overflow}\s?{overflow}?|inherit',
|
||||
'overflow-x': '{overflow}|inherit',
|
||||
'overflow-y': '{overflow}|inherit'
|
||||
}
|
||||
|
||||
# CSS Color Module Level 3
|
||||
css3colormacros = {
|
||||
macros[Profiles.CSS3_COLOR] = {
|
||||
# orange and transparent in CSS 2.1
|
||||
'namedcolor': r'(currentcolor|transparent|orange|black|green|silver|lime|gray|olive|white|yellow|maroon|navy|red|blue|purple|teal|fuchsia|aqua)',
|
||||
'namedcolor': r'(currentcolor|transparent|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)',
|
||||
# orange?
|
||||
'rgbacolor': r'rgba\({w}{int}{w},{w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgba\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w},{w}{num}{w}\)',
|
||||
'hslcolor': r'hsl\({w}{int}{w},{w}{num}%{w},{w}{num}%{w}\)|hsla\({w}{int}{w},{w}{num}%{w},{w}{num}%{w},{w}{num}{w}\)',
|
||||
'x11color': r'aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen',
|
||||
'uicolor': r'(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)',
|
||||
|
||||
}
|
||||
properties['css3color'] = {
|
||||
properties[Profiles.CSS3_COLOR] = {
|
||||
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{rgbacolor}|{hslcolor}|inherit',
|
||||
'opacity': r'{num}|inherit'
|
||||
}
|
||||
|
||||
# CSS Box Module Level 3
|
||||
properties['css3box'] = {
|
||||
'overflow': '{overflow}\s?{overflow}?',
|
||||
'overflow-x': '{overflow}',
|
||||
'overflow-y': '{overflow}'
|
||||
# CSS3 Paged Media
|
||||
macros[Profiles.CSS3_PAGED_MEDIA] = {
|
||||
'pagesize': 'a5|a4|a3|b5|b4|letter|legal|ledger',
|
||||
'pagebreak': 'auto|always|avoid|left|right'
|
||||
}
|
||||
properties[Profiles.CSS3_PAGED_MEDIA] = {
|
||||
'fit': 'fill|hidden|meet|slice',
|
||||
'fit-position': r'auto|(({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?))',
|
||||
'image-orientation': 'auto|{angle}',
|
||||
'orphans': r'{integer}|inherit',
|
||||
'page': 'auto|{ident}',
|
||||
'page-break-before': '{pagebreak}|inherit',
|
||||
'page-break-after': '{pagebreak}|inherit',
|
||||
'page-break-inside': 'auto|avoid|inherit',
|
||||
'size': '({length}{w}){1,2}|auto|{pagesize}{w}(?:portrait|landscape)',
|
||||
'widows': r'{integer}|inherit'
|
||||
}
|
||||
|
||||
class NoSuchProfileException(Exception):
|
||||
"""Raised if no profile with given name is found"""
|
||||
pass
|
||||
|
||||
|
||||
class Profiles(object):
|
||||
"""
|
||||
All profiles used for validation. ``cssutils.profiles.profiles`` is a
|
||||
preset object of this class and used by all properties for validation.
|
||||
|
||||
Predefined profiles are (use
|
||||
:meth:`~cssutils.profiles.Profiles.propertiesByProfile` to
|
||||
get a list of defined properties):
|
||||
|
||||
:attr:`~cssutils.profiles.Profiles.Profiles.CSS_LEVEL_2`
|
||||
Properties defined by CSS2.1
|
||||
:attr:`~cssutils.profiles.Profiles.Profiles.CSS_COLOR_LEVEL_3`
|
||||
CSS 3 color properties
|
||||
:attr:`~cssutils.profiles.Profiles.Profiles.CSS_BOX_LEVEL_3`
|
||||
Currently overflow related properties only
|
||||
|
||||
"""
|
||||
CSS_LEVEL_2 = 'CSS Level 2.1'
|
||||
CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3'
|
||||
CSS_BOX_LEVEL_3 = 'CSS Box Module Level 3'
|
||||
|
||||
basicmacros = {
|
||||
'ident': r'[-]?{nmstart}{nmchar}*',
|
||||
'name': r'{nmchar}+',
|
||||
'nmstart': r'[_a-z]|{nonascii}|{escape}',
|
||||
'nonascii': r'[^\0-\177]',
|
||||
'unicode': r'\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?',
|
||||
'escape': r'{unicode}|\\[ -~\200-\777]',
|
||||
# 'escape': r'{unicode}|\\[ -~\200-\4177777]',
|
||||
'int': r'[-]?\d+',
|
||||
'nmchar': r'[\w-]|{nonascii}|{escape}',
|
||||
'num': r'[-]?\d+|[-]?\d*\.\d+',
|
||||
'number': r'{num}',
|
||||
'string': r'{string1}|{string2}',
|
||||
'string1': r'"(\\\"|[^\"])*"',
|
||||
'uri': r'url\({w}({string}|(\\\)|[^\)])+){w}\)',
|
||||
'string2': r"'(\\\'|[^\'])*'",
|
||||
'nl': r'\n|\r\n|\r|\f',
|
||||
'w': r'\s*',
|
||||
}
|
||||
generalmacros = {
|
||||
'hexcolor': r'#[0-9a-f]{3}|#[0-9a-f]{6}',
|
||||
'rgbcolor': r'rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
|
||||
'namedcolor': r'(transparent|orange|maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray)',
|
||||
'uicolor': r'(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)',
|
||||
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{uicolor}',
|
||||
#'color': r'(maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray|ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)|#[0-9a-f]{3}|#[0-9a-f]{6}|rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
|
||||
'integer': r'{int}',
|
||||
'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
|
||||
'angle': r'0|{num}(deg|grad|rad)',
|
||||
'time': r'0|{num}m?s',
|
||||
'frequency': r'0|{num}k?Hz',
|
||||
'percentage': r'{num}%',
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
"""A few known profiles are predefined."""
|
||||
self._log = cssutils.log
|
||||
self._profilenames = [] # to keep order, REFACTOR!
|
||||
self._profiles = {}
|
||||
|
||||
self.addProfile(self.CSS_LEVEL_2, properties['css2'], css2macros)
|
||||
self.addProfile(self.CSS_COLOR_LEVEL_3, properties['css3color'], css3colormacros)
|
||||
self.addProfile(self.CSS_BOX_LEVEL_3, properties['css3box'])
|
||||
|
||||
self.__update_knownnames()
|
||||
|
||||
def _expand_macros(self, dictionary, macros):
|
||||
"""Expand macros in token dictionary"""
|
||||
def macro_value(m):
|
||||
return '(?:%s)' % macros[m.groupdict()['macro']]
|
||||
for key, value in dictionary.items():
|
||||
if not hasattr(value, '__call__'):
|
||||
while re.search(r'{[a-z][a-z0-9-]*}', value):
|
||||
value = re.sub(r'{(?P<macro>[a-z][a-z0-9-]*)}',
|
||||
macro_value, value)
|
||||
dictionary[key] = value
|
||||
return dictionary
|
||||
|
||||
def _compile_regexes(self, dictionary):
|
||||
"""Compile all regular expressions into callable objects"""
|
||||
for key, value in dictionary.items():
|
||||
if not hasattr(value, '__call__'):
|
||||
value = re.compile('^(?:%s)$' % value, re.I).match
|
||||
dictionary[key] = value
|
||||
|
||||
return dictionary
|
||||
|
||||
def __update_knownnames(self):
|
||||
self._knownnames = []
|
||||
for properties in self._profiles.values():
|
||||
self._knownnames.extend(properties.keys())
|
||||
|
||||
profiles = property(lambda self: sorted(self._profiles.keys()),
|
||||
doc=u'Names of all profiles.')
|
||||
|
||||
knownnames = property(lambda self: self._knownnames,
|
||||
doc="All known property names of all profiles.")
|
||||
|
||||
def addProfile(self, profile, properties, macros=None):
|
||||
"""Add a new profile with name `profile` (e.g. 'CSS level 2')
|
||||
and the given `properties`.
|
||||
|
||||
:param profile:
|
||||
the new `profile`'s name
|
||||
:param properties:
|
||||
a dictionary of ``{ property-name: propery-value }`` items where
|
||||
property-value is a regex which may use macros defined in given
|
||||
``macros`` or the standard macros Profiles.tokens and
|
||||
Profiles.generalvalues.
|
||||
|
||||
``propery-value`` may also be a function which takes a single
|
||||
argument which is the value to validate and which should return
|
||||
True or False.
|
||||
Any exceptions which may be raised during this custom validation
|
||||
are reported or raised as all other cssutils exceptions depending
|
||||
on cssutils.log.raiseExceptions which e.g during parsing normally
|
||||
is False so the exceptions would be logged only.
|
||||
:param macros:
|
||||
may be used in the given properties definitions. There are some
|
||||
predefined basic macros which may always be used in
|
||||
:attr:`Profiles.basicmacros` and :attr:`Profiles.generalmacros`.
|
||||
"""
|
||||
if not macros:
|
||||
macros = {}
|
||||
m = self.basicmacros
|
||||
m.update(self.generalmacros)
|
||||
m.update(macros)
|
||||
properties = self._expand_macros(properties, m)
|
||||
self._profilenames.append(profile)
|
||||
self._profiles[profile] = self._compile_regexes(properties)
|
||||
|
||||
self.__update_knownnames()
|
||||
|
||||
def removeProfile(self, profile=None, all=False):
|
||||
"""Remove `profile` or remove `all` profiles.
|
||||
|
||||
:param profile:
|
||||
profile name to remove
|
||||
:param all:
|
||||
if ``True`` removes all profiles to start with a clean state
|
||||
:exceptions:
|
||||
- :exc:`cssutils.profiles.NoSuchProfileException`:
|
||||
If given `profile` cannot be found.
|
||||
"""
|
||||
if all:
|
||||
self._profiles.clear()
|
||||
else:
|
||||
try:
|
||||
del self._profiles[profile]
|
||||
except KeyError:
|
||||
raise NoSuchProfileException(u'No profile %r.' % profile)
|
||||
|
||||
self.__update_knownnames()
|
||||
|
||||
def propertiesByProfile(self, profiles=None):
|
||||
"""Generator: Yield property names, if no `profiles` is given all
|
||||
profile's properties are used.
|
||||
|
||||
:param profiles:
|
||||
a single profile name or a list of names.
|
||||
"""
|
||||
if not profiles:
|
||||
profiles = self.profiles
|
||||
elif isinstance(profiles, basestring):
|
||||
profiles = (profiles, )
|
||||
try:
|
||||
for profile in sorted(profiles):
|
||||
for name in sorted(self._profiles[profile].keys()):
|
||||
yield name
|
||||
except KeyError, e:
|
||||
raise NoSuchProfileException(e)
|
||||
|
||||
def validate(self, name, value):
|
||||
"""Check if `value` is valid for given property `name` using **any**
|
||||
profile.
|
||||
|
||||
:param name:
|
||||
a property name
|
||||
:param value:
|
||||
a CSS value (string)
|
||||
:returns:
|
||||
if the `value` is valid for the given property `name` in any
|
||||
profile
|
||||
"""
|
||||
for profile in self.profiles:
|
||||
if name in self._profiles[profile]:
|
||||
try:
|
||||
# custom validation errors are caught
|
||||
r = bool(self._profiles[profile][name](value))
|
||||
except Exception, e:
|
||||
self._log.error(e, error=Exception)
|
||||
return False
|
||||
if r:
|
||||
return r
|
||||
return False
|
||||
|
||||
def validateWithProfile(self, name, value, profiles=None):
|
||||
"""Check if `value` is valid for given property `name` returning
|
||||
``(valid, profile)``.
|
||||
|
||||
:param name:
|
||||
a property name
|
||||
:param value:
|
||||
a CSS value (string)
|
||||
:returns:
|
||||
``valid, profiles`` where ``valid`` is if the `value` is valid for
|
||||
the given property `name` in any profile of given `profiles`
|
||||
and ``profiles`` the profile names for which the value is valid
|
||||
(or ``[]`` if not valid at all)
|
||||
|
||||
Example: You might expect a valid Profiles.CSS_LEVEL_2 value but
|
||||
e.g. ``validateWithProfile('color', 'rgba(1,1,1,1)')`` returns
|
||||
(True, Profiles.CSS_COLOR_LEVEL_3)
|
||||
"""
|
||||
if name not in self.knownnames:
|
||||
return False, []
|
||||
else:
|
||||
if not profiles:
|
||||
profiles = self._profilenames
|
||||
elif isinstance(profiles, basestring):
|
||||
profiles = (profiles, )
|
||||
|
||||
for profilename in profiles:
|
||||
# check given profiles
|
||||
if name in self._profiles[profilename]:
|
||||
validate = self._profiles[profilename][name]
|
||||
try:
|
||||
if validate(value):
|
||||
return True, [profilename]
|
||||
except Exception, e:
|
||||
self._log.error(e, error=Exception)
|
||||
|
||||
for profilename in (p for p in self._profilenames if p not in profiles):
|
||||
# check remaining profiles as well
|
||||
if name in self._profiles[profilename]:
|
||||
validate = self._profiles[profilename][name]
|
||||
try:
|
||||
if validate(value):
|
||||
return True, [profilename]
|
||||
except Exception, e:
|
||||
self._log.error(e, error=Exception)
|
||||
|
||||
names = []
|
||||
for profilename, properties in self._profiles.items():
|
||||
# return profile to which name belongs
|
||||
if name in properties.keys():
|
||||
names.append(profilename)
|
||||
names.sort()
|
||||
return False, names
|
||||
|
||||
# used by
|
||||
profiles = Profiles()
|
||||
|
||||
# set for validation to e.g.``Profiles.CSS_LEVEL_2``
|
||||
defaultprofile = None
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
"""cssutils serializer"""
|
||||
__all__ = ['CSSSerializer', 'Preferences']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: serialize.py 1606 2009-01-03 20:32:17Z cthedot $'
|
||||
__version__ = '$Id: serialize.py 1741 2009-05-09 18:20:20Z cthedot $'
|
||||
|
||||
import codecs
|
||||
import cssutils
|
||||
@ -58,6 +58,9 @@ class Preferences(object):
|
||||
keepEmptyRules = False
|
||||
defines if empty rules like e.g. ``a {}`` are kept in the resulting
|
||||
serialized sheet
|
||||
keepUnkownAtRules = True
|
||||
defines if unknown @rules like e.g. ``@three-dee {}`` are kept in the
|
||||
serialized sheet
|
||||
keepUsedNamespaceRulesOnly = False
|
||||
if True only namespace rules which are actually used are kept
|
||||
|
||||
@ -82,12 +85,10 @@ class Preferences(object):
|
||||
spacer = u' '
|
||||
general spacer, used e.g. by CSSUnknownRule
|
||||
|
||||
validOnly = False **DO NOT CHANGE YET**
|
||||
if True only valid (currently Properties) are kept
|
||||
validOnly = False
|
||||
if True only valid (Properties) are output
|
||||
|
||||
A Property is valid if it is a known Property with a valid value.
|
||||
Currently CSS 2.1 values as defined in cssproperties.py would be
|
||||
valid.
|
||||
"""
|
||||
def __init__(self, **initials):
|
||||
"""Always use named instead of positional parameters."""
|
||||
@ -118,6 +119,7 @@ class Preferences(object):
|
||||
self.keepAllProperties = True
|
||||
self.keepComments = True
|
||||
self.keepEmptyRules = False
|
||||
self.keepUnkownAtRules = True
|
||||
self.keepUsedNamespaceRulesOnly = False
|
||||
self.lineNumbers = False
|
||||
self.lineSeparator = u'\n'
|
||||
@ -139,6 +141,7 @@ class Preferences(object):
|
||||
self.indent = u''
|
||||
self.keepComments = False
|
||||
self.keepEmptyRules = False
|
||||
self.keepUnkownAtRules = False
|
||||
self.keepUsedNamespaceRulesOnly = True
|
||||
self.lineNumbers = False
|
||||
self.lineSeparator = u''
|
||||
@ -563,7 +566,7 @@ class CSSSerializer(object):
|
||||
anything until ";" or "{...}"
|
||||
+ CSSComments
|
||||
"""
|
||||
if rule.wellformed:
|
||||
if rule.wellformed and self.prefs.keepUnkownAtRules:
|
||||
out = Out(self)
|
||||
out.append(rule.atkeyword)
|
||||
|
||||
@ -741,10 +744,11 @@ class CSSSerializer(object):
|
||||
out.append(separator)
|
||||
elif isinstance(val, cssutils.css.Property):
|
||||
# PropertySimilarNameList
|
||||
out.append(val.cssText)
|
||||
if not (self.prefs.omitLastSemicolon and i==len(seq)-1):
|
||||
out.append(u';')
|
||||
out.append(separator)
|
||||
if val.cssText:
|
||||
out.append(val.cssText)
|
||||
if not (self.prefs.omitLastSemicolon and i==len(seq)-1):
|
||||
out.append(u';')
|
||||
out.append(separator)
|
||||
elif isinstance(val, cssutils.css.CSSUnknownRule):
|
||||
# @rule
|
||||
out.append(val.cssText)
|
||||
|
@ -5,7 +5,7 @@ A cssutils implementation, not defined in official DOM.
|
||||
"""
|
||||
__all__ = ['MediaQuery']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: mediaquery.py 1638 2009-01-13 20:39:33Z cthedot $'
|
||||
__version__ = '$Id: mediaquery.py 1738 2009-05-02 13:03:28Z cthedot $'
|
||||
|
||||
import cssutils
|
||||
import re
|
||||
@ -21,8 +21,8 @@ class MediaQuery(cssutils.util.Base):
|
||||
media_query: [[only | not]? <media_type> [ and <expression> ]*]
|
||||
| <expression> [ and <expression> ]*
|
||||
expression: ( <media_feature> [: <value>]? )
|
||||
media_type: all | aural | braille | handheld | print |
|
||||
projection | screen | tty | tv | embossed
|
||||
media_type: all | braille | handheld | print |
|
||||
projection | speech | screen | tty | tv | embossed
|
||||
media_feature: width | min-width | max-width
|
||||
| height | min-height | max-height
|
||||
| device-width | min-device-width | max-device-width
|
||||
@ -35,8 +35,8 @@ class MediaQuery(cssutils.util.Base):
|
||||
| scan | grid
|
||||
|
||||
"""
|
||||
MEDIA_TYPES = [u'all', u'aural', u'braille', u'embossed', u'handheld',
|
||||
u'print', u'projection', u'screen', u'tty', u'tv']
|
||||
MEDIA_TYPES = [u'all', u'braille', u'embossed', u'handheld',
|
||||
u'print', u'projection', u'screen', u'speech', u'tty', u'tv']
|
||||
|
||||
# From the HTML spec (see MediaQuery):
|
||||
# "[...] character that isn't a US ASCII letter [a-zA-Z] (Unicode
|
||||
|
@ -2,7 +2,7 @@
|
||||
"""
|
||||
__all__ = []
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id: util.py 1654 2009-02-03 20:16:20Z cthedot $'
|
||||
__version__ = '$Id: util.py 1743 2009-05-09 20:33:15Z cthedot $'
|
||||
|
||||
from helper import normalize
|
||||
from itertools import ifilter
|
||||
@ -307,7 +307,6 @@ class Base(_BaseClass):
|
||||
bracket == parant == 0) and typ in endtypes:
|
||||
# mediaqueryendonly with STRING
|
||||
break
|
||||
|
||||
if separateEnd:
|
||||
# TODO: use this method as generator, then this makes sense
|
||||
if resulttokens:
|
||||
|
Loading…
x
Reference in New Issue
Block a user