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.
791 lines
37 KiB
791 lines
37 KiB
"""CSS profiles.
|
|
|
|
Profiles is based on code by Kevin D. Smith, orginally used as cssvalues,
|
|
thanks!
|
|
"""
|
|
__all__ = ['Profiles']
|
|
__docformat__ = 'restructuredtext'
|
|
__version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $'
|
|
|
|
import re
|
|
import types
|
|
|
|
class NoSuchProfileException(Exception):
|
|
"""Raised if no profile with given name is found"""
|
|
pass
|
|
|
|
|
|
# dummies, replaced in Profiles.addProfile
|
|
_fontRegexReplacements = {
|
|
'__FONT_FAMILY_SINGLE': lambda f: False,
|
|
'__FONT_WITH_1_FAMILY': lambda f: False
|
|
}
|
|
|
|
def _fontFamilyValidator(families):
|
|
"""Check if ``font-family`` value is valid, regex is too slow.
|
|
|
|
Splits on ``,`` and checks each family separately.
|
|
Somehow naive as font-family name could contain a "," but this is unlikely.
|
|
Still should be a TODO.
|
|
"""
|
|
match = _fontRegexReplacements['__FONT_FAMILY_SINGLE']
|
|
|
|
for f in families.split(u','):
|
|
if not match(f.strip()):
|
|
return False
|
|
return True
|
|
|
|
def _fontValidator(font):
|
|
"""Check if font value is valid, regex is too slow.
|
|
|
|
Checks everything before ``,`` on basic font value. Everything after should
|
|
be a valid font-family value.
|
|
"""
|
|
if u',' in font:
|
|
# split off until 1st family
|
|
font1, families2 = font.split(u',', 1)
|
|
else:
|
|
font1, families2 = font, None
|
|
|
|
if not _fontRegexReplacements['__FONT_WITH_1_FAMILY'](font1.strip()):
|
|
return False
|
|
|
|
if families2 and not _fontFamilyValidator(families2):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
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_BASIC_USER_INTERFACE`
|
|
Currently resize and outline properties only
|
|
:attr:`~cssutils.profiles.Profiles.CSS3_BOX`
|
|
Currently overflow related properties only
|
|
:attr:`~cssutils.profiles.Profiles.CSS3_COLOR`
|
|
CSS 3 color properties
|
|
: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 = u'CSS Level 2.1'
|
|
CSS3_BACKGROUNDS_AND_BORDERS = u'CSS Backgrounds and Borders Module Level 3'
|
|
CSS3_BASIC_USER_INTERFACE = u'CSS3 Basic User Interface Module'
|
|
CSS3_BOX = CSS_BOX_LEVEL_3 = u'CSS Box Module Level 3'
|
|
CSS3_COLOR = CSS_COLOR_LEVEL_3 = u'CSS Color Module Level 3'
|
|
CSS3_FONTS = u'CSS Fonts Module Level 3'
|
|
CSS3_FONT_FACE = u'CSS Fonts Module Level 3 @font-face properties'
|
|
CSS3_PAGED_MEDIA = u'CSS3 Paged Media Module'
|
|
CSS3_TEXT = u'CSS Text Level 3'
|
|
|
|
_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+',
|
|
'positivenum': 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)',
|
|
'positivelength': r'0|{positivenum}(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}%',
|
|
'shadow': '(inset)?{w}{length}{w}{length}{w}{length}?{w}{length}?{w}{color}?'
|
|
}
|
|
|
|
def __init__(self, log=None):
|
|
"""A few profiles are predefined."""
|
|
self._log = log
|
|
|
|
# macro cache
|
|
self._usedMacros = Profiles._TOKEN_MACROS.copy()
|
|
self._usedMacros.update(Profiles._MACROS.copy())
|
|
|
|
# to keep order, REFACTOR!
|
|
self._profileNames = []
|
|
# for reset if macro changes
|
|
self._rawProfiles = {}
|
|
# already compiled profiles: {profile: {property: checkfunc, ...}, ...}
|
|
self._profilesProperties = {}
|
|
|
|
self._defaultProfiles = None
|
|
|
|
self.addProfiles([(self.CSS_LEVEL_2,
|
|
properties[self.CSS_LEVEL_2],
|
|
macros[self.CSS_LEVEL_2]
|
|
),
|
|
(self.CSS3_BACKGROUNDS_AND_BORDERS,
|
|
properties[self.CSS3_BACKGROUNDS_AND_BORDERS],
|
|
macros[self.CSS3_BACKGROUNDS_AND_BORDERS]
|
|
),
|
|
(self.CSS3_BASIC_USER_INTERFACE,
|
|
properties[self.CSS3_BASIC_USER_INTERFACE],
|
|
macros[self.CSS3_BASIC_USER_INTERFACE]
|
|
),
|
|
(self.CSS3_BOX,
|
|
properties[self.CSS3_BOX],
|
|
macros[self.CSS3_BOX]
|
|
),
|
|
(self.CSS3_COLOR,
|
|
properties[self.CSS3_COLOR],
|
|
macros[self.CSS3_COLOR]
|
|
),
|
|
(self.CSS3_FONTS,
|
|
properties[self.CSS3_FONTS],
|
|
macros[self.CSS3_FONTS]
|
|
),
|
|
# new object for font-face only?
|
|
(self.CSS3_FONT_FACE,
|
|
properties[self.CSS3_FONT_FACE],
|
|
macros[self.CSS3_FONTS]
|
|
),
|
|
(self.CSS3_PAGED_MEDIA,
|
|
properties[self.CSS3_PAGED_MEDIA],
|
|
macros[self.CSS3_PAGED_MEDIA]
|
|
),
|
|
(self.CSS3_TEXT,
|
|
properties[self.CSS3_TEXT],
|
|
macros[self.CSS3_TEXT]
|
|
)
|
|
])
|
|
|
|
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():
|
|
# might be a function (font-family) as regex is too slow
|
|
if not hasattr(value, '__call__') and not isinstance(value,
|
|
types.FunctionType):
|
|
value = re.compile('^(?:%s)$' % value, re.I).match
|
|
dictionary[key] = value
|
|
|
|
return dictionary
|
|
|
|
def __update_knownNames(self):
|
|
self._knownNames = []
|
|
for properties in self._profilesProperties.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
|
|
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 _resetProperties(self, newMacros=None):
|
|
"reset all props from raw values as changes in macros happened"
|
|
# base
|
|
macros = Profiles._TOKEN_MACROS.copy()
|
|
macros.update(Profiles._MACROS.copy())
|
|
|
|
# former
|
|
for profile in self._profileNames:
|
|
macros.update(self._rawProfiles[profile]['macros'])
|
|
|
|
# new
|
|
if newMacros:
|
|
macros.update(newMacros)
|
|
|
|
# reset properties
|
|
self._profilesProperties.clear()
|
|
for profile in self._profileNames:
|
|
properties = self._expand_macros(
|
|
# keep raw
|
|
self._rawProfiles[profile]['properties'].copy(),
|
|
macros)
|
|
self._profilesProperties[profile] = self._compile_regexes(properties)
|
|
|
|
# save
|
|
self._usedMacros = macros
|
|
|
|
|
|
def addProfiles(self, profiles):
|
|
"""Add a list of profiles at once. Useful as if profiles define custom
|
|
macros these are used in one go. Using `addProfile` instead my be
|
|
**very** slow instead.
|
|
"""
|
|
# add macros
|
|
for profile, properties, macros in profiles:
|
|
if macros:
|
|
self._usedMacros.update(macros)
|
|
self._rawProfiles[profile] = {'macros': macros.copy()}
|
|
|
|
# only add new properties
|
|
for profile, properties, macros in profiles:
|
|
self.addProfile(profile, properties.copy(), None)
|
|
|
|
|
|
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 macros:
|
|
# check if known macros would change and if yes reset properties
|
|
if len(set(macros.keys()).intersection(self._usedMacros.keys())):
|
|
self._resetProperties(newMacros=macros)
|
|
|
|
else:
|
|
# no replacement, simply continue
|
|
self._usedMacros.update(macros)
|
|
|
|
else:
|
|
# might have been set by addProfiles before
|
|
try:
|
|
macros = self._rawProfiles[profile]['macros']
|
|
except KeyError, e:
|
|
macros = {}
|
|
|
|
# save name and raw props/macros if macros change to completely reset
|
|
self._profileNames.append(profile)
|
|
self._rawProfiles[profile] = {'properties': properties.copy(),
|
|
'macros': macros.copy()}
|
|
# prepare and save properties
|
|
properties = self._expand_macros(properties, self._usedMacros)
|
|
self._profilesProperties[profile] = self._compile_regexes(properties)
|
|
|
|
self.__update_knownNames()
|
|
|
|
# hack for font and font-family which are too slow with regexes
|
|
if '__FONT_WITH_1_FAMILY' in properties:
|
|
_fontRegexReplacements['__FONT_WITH_1_FAMILY'] = properties['__FONT_WITH_1_FAMILY']
|
|
if '__FONT_FAMILY_SINGLE' in properties:
|
|
_fontRegexReplacements['__FONT_FAMILY_SINGLE'] = properties['__FONT_FAMILY_SINGLE']
|
|
|
|
|
|
def removeProfile(self, profile=None, all=False):
|
|
"""Remove `profile` or remove `all` profiles.
|
|
|
|
If the removed profile used custom macros all remaining profiles
|
|
are reset to reflect the macro changes. This may be quite an expensive
|
|
operation!
|
|
|
|
: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._profilesProperties.clear()
|
|
self._rawProfiles.clear()
|
|
del self._profileNames[:]
|
|
else:
|
|
reset = False
|
|
|
|
try:
|
|
if (self._rawProfiles[profile]['macros']):
|
|
reset = True
|
|
|
|
del self._profilesProperties[profile]
|
|
del self._rawProfiles[profile]
|
|
del self._profileNames[self._profileNames.index(profile)]
|
|
except KeyError:
|
|
raise NoSuchProfileException(u'No profile %r.' % profile)
|
|
|
|
else:
|
|
if reset:
|
|
# reset properties as macros were removed
|
|
self._resetProperties()
|
|
|
|
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._profilesProperties[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._profilesProperties[profile]:
|
|
try:
|
|
# custom validation errors are caught
|
|
r = bool(self._profilesProperties[profile][name](value))
|
|
except Exception, e:
|
|
# TODO: more specific exception?
|
|
# Validate should not be fatal though!
|
|
self._log.error(e, error=Exception)
|
|
r = 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 reversed(profiles):
|
|
# check given profiles
|
|
if name in self._profilesProperties[profilename]:
|
|
validate = self._profilesProperties[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._profilesProperties[profilename]:
|
|
validate = self._profilesProperties[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._profilesProperties.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.
|
|
"""
|
|
macros[Profiles.CSS_LEVEL_2] = {
|
|
'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}|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}{ident}{w}(?:,{w}{list-style-type}{w})?\)',
|
|
'identifier': r'{ident}',
|
|
'family-name': r'{string}|{ident}({w}{ident})*',
|
|
'generic-family': r'serif|sans-serif|cursive|fantasy|monospace',
|
|
'absolute-size': r'(x?x-)?(small|large)|medium',
|
|
'relative-size': r'smaller|larger',
|
|
|
|
#[[ <family-name> | <generic-family> ] [, <family-name>| <generic-family>]* ] | inherit
|
|
#'font-family': r'(({family-name}|{generic-family})({w},{w}({family-name}|{generic-family}))*)|inherit',
|
|
# EXTREMELY SLOW REGEX
|
|
#'font-family': r'({family-name}({w},{w}{family-name})*)|inherit',
|
|
|
|
'font-size': r'{absolute-size}|{relative-size}|{positivelength}|{percentage}|inherit',
|
|
'font-style': r'normal|italic|oblique|inherit',
|
|
'font-variant': r'normal|small-caps|inherit',
|
|
'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit',
|
|
'line-height': r'normal|{number}|{length}|{percentage}|inherit',
|
|
'list-style-image': r'{uri}|none|inherit',
|
|
'list-style-position': r'inside|outside|inherit',
|
|
'list-style-type': r'disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-greek|lower-(latin|alpha)|upper-(latin|alpha)|armenian|georgian|none|inherit',
|
|
'margin-width': r'{length}|{percentage}|auto',
|
|
'padding-width': r'{length}|{percentage}',
|
|
'specific-voice': r'{ident}',
|
|
'generic-voice': r'male|female|child',
|
|
'content': r'{string}|{uri}|{counter}|attr\({w}{ident}{w}\)|open-quote|close-quote|no-open-quote|no-close-quote',
|
|
'background-attrs': r'{background-color}|{background-image}|{background-repeat}|{background-attachment}|{background-position}',
|
|
'list-attrs': r'{list-style-type}|{list-style-position}|{list-style-image}',
|
|
'font-attrs': r'{font-style}|{font-variant}|{font-weight}',
|
|
'text-attrs': r'underline|overline|line-through|blink',
|
|
'overflow': r'visible|hidden|scroll|auto|inherit',
|
|
}
|
|
|
|
"""
|
|
Define the regular expressions for validation all CSS values
|
|
"""
|
|
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}',
|
|
'background-image': r'{background-image}',
|
|
'background-position': r'{background-position}',
|
|
'background-repeat': r'{background-repeat}',
|
|
# Each piece should only be allowed one time
|
|
'background': r'{background-attrs}(\s+{background-attrs})*|inherit',
|
|
'border-collapse': r'collapse|separate|inherit',
|
|
'border-spacing': r'{length}(\s+{length})?|inherit',
|
|
'bottom': r'{length}|{percentage}|auto|inherit',
|
|
'caption-side': r'top|bottom|inherit',
|
|
'clear': r'none|left|right|both|inherit',
|
|
'clip': r'{shape}|auto|inherit',
|
|
'color': r'{color}|inherit',
|
|
'content': r'none|normal|{content}(\s+{content})*|inherit',
|
|
'counter-increment': r'({ident}(\s+{integer})?)(\s+({ident}(\s+{integer})?))*|none|inherit',
|
|
'counter-reset': r'({ident}(\s+{integer})?)(\s+({ident}(\s+{integer})?))*|none|inherit',
|
|
'cue-after': r'{uri}|none|inherit',
|
|
'cue-before': r'{uri}|none|inherit',
|
|
'cue': r'({uri}|none|inherit){1,2}|inherit',
|
|
#'cursor': r'((({uri}{w},{w})*)?(auto|crosshair|default|pointer|move|(e|ne|nw|n|se|sw|s|w)-resize|text|wait|help|progress))|inherit',
|
|
'direction': r'ltr|rtl|inherit',
|
|
'display': r'inline|block|list-item|run-in|inline-block|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|none|inherit',
|
|
'elevation': r'{angle}|below|level|above|higher|lower|inherit',
|
|
'empty-cells': r'show|hide|inherit',
|
|
'float': r'left|right|none|inherit',
|
|
|
|
# regex too slow:
|
|
# 'font-family': r'{font-family}',
|
|
'font-family': _fontFamilyValidator,
|
|
'__FONT_FAMILY_SINGLE': r'{family-name}',
|
|
|
|
'font-size': r'{font-size}',
|
|
'font-style': r'{font-style}',
|
|
'font-variant': r'{font-variant}',
|
|
'font-weight': r'{font-weight}',
|
|
|
|
# regex too slow and wrong too:
|
|
# 'font': r'({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{font-family}|caption|icon|menu|message-box|small-caption|status-bar|inherit',
|
|
'font': _fontValidator,
|
|
'__FONT_WITH_1_FAMILY': r'(({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{family-name})|caption|icon|menu|message-box|small-caption|status-bar|inherit',
|
|
|
|
'height': r'{length}|{percentage}|auto|inherit',
|
|
'left': r'{length}|{percentage}|auto|inherit',
|
|
'letter-spacing': r'normal|{length}|inherit',
|
|
'line-height': r'{line-height}',
|
|
'list-style-image': r'{list-style-image}',
|
|
'list-style-position': r'{list-style-position}',
|
|
'list-style-type': r'{list-style-type}',
|
|
'list-style': r'{list-attrs}(\s+{list-attrs})*|inherit',
|
|
'margin-right': r'{margin-width}|inherit',
|
|
'margin-left': r'{margin-width}|inherit',
|
|
'margin-top': r'{margin-width}|inherit',
|
|
'margin-bottom': r'{margin-width}|inherit',
|
|
'margin': r'{margin-width}(\s+{margin-width}){0,3}|inherit',
|
|
'max-height': r'{length}|{percentage}|none|inherit',
|
|
'max-width': r'{length}|{percentage}|none|inherit',
|
|
'min-height': r'{length}|{percentage}|none|inherit',
|
|
'min-width': r'{length}|{percentage}|none|inherit',
|
|
'orphans': r'{integer}|inherit',
|
|
'overflow': r'{overflow}',
|
|
'padding-top': r'{padding-width}|inherit',
|
|
'padding-right': r'{padding-width}|inherit',
|
|
'padding-bottom': r'{padding-width}|inherit',
|
|
'padding-left': r'{padding-width}|inherit',
|
|
'padding': r'{padding-width}(\s+{padding-width}){0,3}|inherit',
|
|
'page-break-after': r'auto|always|avoid|left|right|inherit',
|
|
'page-break-before': r'auto|always|avoid|left|right|inherit',
|
|
'page-break-inside': r'avoid|auto|inherit',
|
|
'pause-after': r'{time}|{percentage}|inherit',
|
|
'pause-before': r'{time}|{percentage}|inherit',
|
|
'pause': r'({time}|{percentage}){1,2}|inherit',
|
|
'pitch-range': r'{number}|inherit',
|
|
'pitch': r'{frequency}|x-low|low|medium|high|x-high|inherit',
|
|
'play-during': r'{uri}(\s+(mix|repeat))*|auto|none|inherit',
|
|
'position': r'static|relative|absolute|fixed|inherit',
|
|
'quotes': r'({string}\s+{string})(\s+{string}\s+{string})*|none|inherit',
|
|
'richness': r'{number}|inherit',
|
|
'right': r'{length}|{percentage}|auto|inherit',
|
|
'speak-header': r'once|always|inherit',
|
|
'speak-numeral': r'digits|continuous|inherit',
|
|
'speak-punctuation': r'code|none|inherit',
|
|
'speak': r'normal|none|spell-out|inherit',
|
|
'speech-rate': r'{number}|x-slow|slow|medium|fast|x-fast|faster|slower|inherit',
|
|
'stress': r'{number}|inherit',
|
|
'table-layout': r'auto|fixed|inherit',
|
|
'text-align': r'left|right|center|justify|inherit',
|
|
'text-decoration': r'none|{text-attrs}(\s+{text-attrs})*|inherit',
|
|
'text-indent': r'{length}|{percentage}|inherit',
|
|
'text-transform': r'capitalize|uppercase|lowercase|none|inherit',
|
|
'top': r'{length}|{percentage}|auto|inherit',
|
|
'unicode-bidi': r'normal|embed|bidi-override|inherit',
|
|
'vertical-align': r'baseline|sub|super|top|text-top|middle|bottom|text-bottom|{percentage}|{length}|inherit',
|
|
'visibility': r'visible|hidden|collapse|inherit',
|
|
'voice-family': r'({specific-voice}|{generic-voice}{w},{w})*({specific-voice}|{generic-voice})|inherit',
|
|
'volume': r'{number}|{percentage}|silent|x-soft|soft|medium|loud|x-loud|inherit',
|
|
'white-space': r'normal|pre|nowrap|pre-wrap|pre-line|inherit',
|
|
'widows': r'{integer}|inherit',
|
|
'width': r'{length}|{percentage}|auto|inherit',
|
|
'word-spacing': r'normal|{length}|inherit',
|
|
'z-index': r'auto|{integer}|inherit',
|
|
}
|
|
|
|
|
|
macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS] = {
|
|
'border-style': 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset',
|
|
'border-width': '{length}|thin|medium|thick',
|
|
'b1': r'{border-width}?({w}{border-style})?({w}{color})?',
|
|
'b2': r'{border-width}?({w}{color})?({w}{border-style})?',
|
|
'b3': r'{border-style}?({w}{border-width})?({w}{color})?',
|
|
'b4': r'{border-style}?({w}{color})?({w}{border-width})?',
|
|
'b5': r'{color}?({w}{border-style})?({w}{border-width})?',
|
|
'b6': r'{color}?({w}{border-width})?({w}{border-style})?',
|
|
'border-attrs': r'{b1}|{b2}|{b3}|{b4}|{b5}|{b6}',
|
|
'border-radius-part': '({length}|{percentage})(\s+({length}|{percentage}))?'
|
|
}
|
|
properties[Profiles.CSS3_BACKGROUNDS_AND_BORDERS] = {
|
|
'border-color': r'({color}|transparent)(\s+({color}|transparent)){0,3}|inherit',
|
|
'border-style': r'{border-style}(\s+{border-style}){0,3}|inherit',
|
|
'border-top': r'{border-attrs}|inherit',
|
|
'border-right': r'{border-attrs}|inherit',
|
|
'border-bottom': r'{border-attrs}|inherit',
|
|
'border-left': r'{border-attrs}|inherit',
|
|
'border-top-color': r'{color}|transparent|inherit',
|
|
'border-right-color': r'{color}|transparent|inherit',
|
|
'border-bottom-color': r'{color}|transparent|inherit',
|
|
'border-left-color': r'{color}|transparent|inherit',
|
|
'border-top-style': r'{border-style}|inherit',
|
|
'border-right-style': r'{border-style}|inherit',
|
|
'border-bottom-style': r'{border-style}|inherit',
|
|
'border-left-style': r'{border-style}|inherit',
|
|
'border-top-width': r'{border-width}|inherit',
|
|
'border-right-width': r'{border-width}|inherit',
|
|
'border-bottom-width': r'{border-width}|inherit',
|
|
'border-left-width': r'{border-width}|inherit',
|
|
'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit',
|
|
'border': r'{border-attrs}|inherit',
|
|
'border-top-right-radius': '{border-radius-part}',
|
|
'border-bottom-right-radius': '{border-radius-part}',
|
|
'border-bottom-left-radius': '{border-radius-part}',
|
|
'border-top-left-radius': '{border-radius-part}',
|
|
'border-radius': '({length}{w}|{percentage}{w}){1,4}(/{w}({length}{w}|{percentage}{w}){1,4})?',
|
|
'box-shadow': 'none|{shadow}({w},{w}{shadow})*',
|
|
}
|
|
|
|
# CSS3 Basic User Interface Module
|
|
macros[Profiles.CSS3_BASIC_USER_INTERFACE] = {
|
|
'border-style': macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS]['border-style'],
|
|
'border-width': macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS]['border-width'],
|
|
'outline-1': r'{outline-color}(\s+{outline-style})?(\s+{outline-width})?',
|
|
'outline-2': r'{outline-color}(\s+{outline-width})?(\s+{outline-style})?',
|
|
'outline-3': r'{outline-style}(\s+{outline-color})?(\s+{outline-width})?',
|
|
'outline-4': r'{outline-style}(\s+{outline-width})?(\s+{outline-color})?',
|
|
'outline-5': r'{outline-width}(\s+{outline-color})?(\s+{outline-style})?',
|
|
'outline-6': r'{outline-width}(\s+{outline-style})?(\s+{outline-color})?',
|
|
'outline-color': r'{color}|invert|inherit',
|
|
'outline-style': r'auto|{border-style}|inherit',
|
|
'outline-width': r'{border-width}|inherit',
|
|
}
|
|
properties[Profiles.CSS3_BASIC_USER_INTERFACE] = {
|
|
'box-sizing': r'content-box|border-box',
|
|
'cursor': r'((({uri}{w}({number}{w}{number}{w})?,{w})*)?(auto|default|none|context-menu|help|pointer|progress|wait|cell|crosshair|text|vertical-text|alias|copy|move|no-drop|not-allowed|(e|n|ne|nw|s|se|sw|w|ew|ns|nesw|nwse|col|row)-resize|all-scroll))|inherit',
|
|
'nav-index': r'auto|{number}|inherit',
|
|
'outline-color': r'{outline-color}',
|
|
'outline-style': r'{outline-style}',
|
|
'outline-width': r'{outline-width}',
|
|
'outline-offset': r'{length}|inherit',
|
|
#'outline': r'{outline-attrs}(\s+{outline-attrs})*|inherit',
|
|
'outline': r'{outline-1}|{outline-2}|{outline-3}|{outline-4}|{outline-5}|{outline-6}|inherit',
|
|
'resize': 'none|both|horizontal|vertical|inherit',
|
|
}
|
|
|
|
# CSS Box Module Level 3
|
|
macros[Profiles.CSS3_BOX] = {
|
|
'overflow': macros[Profiles.CSS_LEVEL_2]['overflow']
|
|
}
|
|
properties[Profiles.CSS3_BOX] = {
|
|
'overflow': '{overflow}{w}{overflow}?|inherit',
|
|
'overflow-x': '{overflow}|inherit',
|
|
'overflow-y': '{overflow}|inherit'
|
|
}
|
|
|
|
# CSS Color Module Level 3
|
|
macros[Profiles.CSS3_COLOR] = {
|
|
# orange and transparent in CSS 2.1
|
|
'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}{num}{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)',
|
|
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{rgbacolor}|{hslcolor}|{x11color}|inherit',
|
|
}
|
|
properties[Profiles.CSS3_COLOR] = {
|
|
'opacity': r'{num}|inherit',
|
|
}
|
|
|
|
# CSS Fonts Module Level 3 http://www.w3.org/TR/css3-fonts/
|
|
macros[Profiles.CSS3_FONTS] = {
|
|
#'family-name': r'{string}|{ident}',
|
|
'family-name': r'{string}|{ident}({w}{ident})*',
|
|
'font-face-name': 'local\({w}{family-name}{w}\)',
|
|
'font-stretch-names': r'(ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded)',
|
|
'unicode-range': r'[uU]\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?'
|
|
}
|
|
properties[Profiles.CSS3_FONTS] = {
|
|
'font-size-adjust': r'{number}|none|inherit',
|
|
'font-stretch': r'normal|wider|narrower|{font-stretch-names}|inherit'
|
|
}
|
|
properties[Profiles.CSS3_FONT_FACE] = {
|
|
'font-family': '{family-name}',
|
|
'font-stretch': r'{font-stretch-names}',
|
|
'font-style': r'normal|italic|oblique',
|
|
'font-weight': r'normal|bold|[1-9]00',
|
|
'src': r'({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name})({w},{w}({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name}))*',
|
|
'unicode-range': '{unicode-range}({w},{w}{unicode-range})*'
|
|
}
|
|
|
|
# CSS3 Paged Media
|
|
macros[Profiles.CSS3_PAGED_MEDIA] = {
|
|
'page-size': 'a5|a4|a3|b5|b4|letter|legal|ledger',
|
|
'page-orientation': 'portrait|landscape',
|
|
'page-1': '{page-size}(?:{w}{page-orientation})?',
|
|
'page-2': '{page-orientation}(?:{w}{page-size})?',
|
|
'page-size-orientation': '{page-1}|{page-2}',
|
|
'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|{page-size-orientation}',
|
|
'widows': r'{integer}|inherit'
|
|
}
|
|
|
|
macros[Profiles.CSS3_TEXT] = {
|
|
}
|
|
properties[Profiles.CSS3_TEXT] = {
|
|
'text-shadow': 'none|{shadow}({w},{w}{shadow})*',
|
|
}
|
|
|