gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / cssutils / profiles.py @ 475
History | View | Annotate | Download (37.5 KB)
1 |
"""CSS profiles.
|
---|---|
2 |
|
3 |
Profiles is based on code by Kevin D. Smith, orginally used as cssvalues,
|
4 |
thanks!
|
5 |
"""
|
6 |
__all__ = ['Profiles']
|
7 |
__docformat__ = 'restructuredtext'
|
8 |
__version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $'
|
9 |
|
10 |
import re |
11 |
import types |
12 |
|
13 |
class NoSuchProfileException(Exception): |
14 |
"""Raised if no profile with given name is found"""
|
15 |
pass
|
16 |
|
17 |
|
18 |
# dummies, replaced in Profiles.addProfile
|
19 |
_fontRegexReplacements = { |
20 |
'__FONT_FAMILY_SINGLE': lambda f: False, |
21 |
'__FONT_WITH_1_FAMILY': lambda f: False |
22 |
} |
23 |
|
24 |
def _fontFamilyValidator(families): |
25 |
"""Check if ``font-family`` value is valid, regex is too slow.
|
26 |
|
27 |
Splits on ``,`` and checks each family separately.
|
28 |
Somehow naive as font-family name could contain a "," but this is unlikely.
|
29 |
Still should be a TODO.
|
30 |
"""
|
31 |
match = _fontRegexReplacements['__FONT_FAMILY_SINGLE']
|
32 |
|
33 |
for f in families.split(u','): |
34 |
if not match(f.strip()): |
35 |
return False |
36 |
return True |
37 |
|
38 |
def _fontValidator(font): |
39 |
"""Check if font value is valid, regex is too slow.
|
40 |
|
41 |
Checks everything before ``,`` on basic font value. Everything after should
|
42 |
be a valid font-family value.
|
43 |
"""
|
44 |
if u',' in font: |
45 |
# split off until 1st family
|
46 |
font1, families2 = font.split(u',', 1) |
47 |
else:
|
48 |
font1, families2 = font, None
|
49 |
|
50 |
if not _fontRegexReplacements['__FONT_WITH_1_FAMILY'](font1.strip()): |
51 |
return False |
52 |
|
53 |
if families2 and not _fontFamilyValidator(families2): |
54 |
return False |
55 |
|
56 |
return True |
57 |
|
58 |
|
59 |
class Profiles(object): |
60 |
"""
|
61 |
All profiles used for validation. ``cssutils.profile`` is a
|
62 |
preset object of this class and used by all properties for validation.
|
63 |
|
64 |
Predefined profiles are (use
|
65 |
:meth:`~cssutils.profiles.Profiles.propertiesByProfile` to
|
66 |
get a list of defined properties):
|
67 |
|
68 |
:attr:`~cssutils.profiles.Profiles.CSS_LEVEL_2`
|
69 |
Properties defined by CSS2.1
|
70 |
:attr:`~cssutils.profiles.Profiles.CSS3_BASIC_USER_INTERFACE`
|
71 |
Currently resize and outline properties only
|
72 |
:attr:`~cssutils.profiles.Profiles.CSS3_BOX`
|
73 |
Currently overflow related properties only
|
74 |
:attr:`~cssutils.profiles.Profiles.CSS3_COLOR`
|
75 |
CSS 3 color properties
|
76 |
:attr:`~cssutils.profiles.Profiles.CSS3_PAGED_MEDIA`
|
77 |
As defined at http://www.w3.org/TR/css3-page/ (at 090307)
|
78 |
|
79 |
Predefined macros are:
|
80 |
|
81 |
:attr:`~cssutils.profiles.Profiles._TOKEN_MACROS`
|
82 |
Macros containing the token values as defined to CSS2
|
83 |
:attr:`~cssutils.profiles.Profiles._MACROS`
|
84 |
Additional general macros.
|
85 |
|
86 |
If you want to redefine any of these macros do this in your custom
|
87 |
macros.
|
88 |
"""
|
89 |
CSS_LEVEL_2 = u'CSS Level 2.1'
|
90 |
CSS3_BACKGROUNDS_AND_BORDERS = u'CSS Backgrounds and Borders Module Level 3'
|
91 |
CSS3_BASIC_USER_INTERFACE = u'CSS3 Basic User Interface Module'
|
92 |
CSS3_BOX = CSS_BOX_LEVEL_3 = u'CSS Box Module Level 3'
|
93 |
CSS3_COLOR = CSS_COLOR_LEVEL_3 = u'CSS Color Module Level 3'
|
94 |
CSS3_FONTS = u'CSS Fonts Module Level 3'
|
95 |
CSS3_FONT_FACE = u'CSS Fonts Module Level 3 @font-face properties'
|
96 |
CSS3_PAGED_MEDIA = u'CSS3 Paged Media Module'
|
97 |
CSS3_TEXT = u'CSS Text Level 3'
|
98 |
|
99 |
_TOKEN_MACROS = { |
100 |
'ident': r'[-]?{nmstart}{nmchar}*', |
101 |
'name': r'{nmchar}+', |
102 |
'nmstart': r'[_a-z]|{nonascii}|{escape}', |
103 |
'nonascii': r'[^\0-\177]', |
104 |
'unicode': r'\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?', |
105 |
'escape': r'{unicode}|\\[ -~\200-\777]', |
106 |
# 'escape': r'{unicode}|\\[ -~\200-\4177777]',
|
107 |
'int': r'[-]?\d+', |
108 |
'nmchar': r'[\w-]|{nonascii}|{escape}', |
109 |
'num': r'[-]?\d+|[-]?\d*\.\d+', |
110 |
'positivenum': r'\d+|\d*\.\d+', |
111 |
'number': r'{num}', |
112 |
'string': r'{string1}|{string2}', |
113 |
'string1': r'"(\\\"|[^\"])*"', |
114 |
'uri': r'url\({w}({string}|(\\\)|[^\)])+){w}\)', |
115 |
'string2': r"'(\\\'|[^\'])*'", |
116 |
'nl': r'\n|\r\n|\r|\f', |
117 |
'w': r'\s*', |
118 |
} |
119 |
_MACROS = { |
120 |
'hexcolor': r'#[0-9a-f]{3}|#[0-9a-f]{6}', |
121 |
'rgbcolor': r'rgb\({w}{int}{w}\,{w}{int}{w}\,{w}{int}{w}\)|rgb\({w}{num}%{w}\,{w}{num}%{w}\,{w}{num}%{w}\)', |
122 |
'namedcolor': r'(transparent|orange|maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray)', |
123 |
'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)', |
124 |
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{uicolor}', |
125 |
#'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}\)',
|
126 |
'integer': r'{int}', |
127 |
'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)', |
128 |
'positivelength': r'0|{positivenum}(em|ex|px|in|cm|mm|pt|pc)', |
129 |
'angle': r'0|{num}(deg|grad|rad)', |
130 |
'time': r'0|{num}m?s', |
131 |
'frequency': r'0|{num}k?Hz', |
132 |
'percentage': r'{num}%', |
133 |
'shadow': '(inset)?{w}{length}{w}{length}{w}{length}?{w}{length}?{w}{color}?' |
134 |
} |
135 |
|
136 |
def __init__(self, log=None): |
137 |
"""A few profiles are predefined."""
|
138 |
self._log = log
|
139 |
|
140 |
# macro cache
|
141 |
self._usedMacros = Profiles._TOKEN_MACROS.copy()
|
142 |
self._usedMacros.update(Profiles._MACROS.copy())
|
143 |
|
144 |
# to keep order, REFACTOR!
|
145 |
self._profileNames = []
|
146 |
# for reset if macro changes
|
147 |
self._rawProfiles = {}
|
148 |
# already compiled profiles: {profile: {property: checkfunc, ...}, ...}
|
149 |
self._profilesProperties = {}
|
150 |
|
151 |
self._defaultProfiles = None |
152 |
|
153 |
self.addProfiles([(self.CSS_LEVEL_2, |
154 |
properties[self.CSS_LEVEL_2],
|
155 |
macros[self.CSS_LEVEL_2]
|
156 |
), |
157 |
(self.CSS3_BACKGROUNDS_AND_BORDERS,
|
158 |
properties[self.CSS3_BACKGROUNDS_AND_BORDERS],
|
159 |
macros[self.CSS3_BACKGROUNDS_AND_BORDERS]
|
160 |
), |
161 |
(self.CSS3_BASIC_USER_INTERFACE,
|
162 |
properties[self.CSS3_BASIC_USER_INTERFACE],
|
163 |
macros[self.CSS3_BASIC_USER_INTERFACE]
|
164 |
), |
165 |
(self.CSS3_BOX,
|
166 |
properties[self.CSS3_BOX],
|
167 |
macros[self.CSS3_BOX]
|
168 |
), |
169 |
(self.CSS3_COLOR,
|
170 |
properties[self.CSS3_COLOR],
|
171 |
macros[self.CSS3_COLOR]
|
172 |
), |
173 |
(self.CSS3_FONTS,
|
174 |
properties[self.CSS3_FONTS],
|
175 |
macros[self.CSS3_FONTS]
|
176 |
), |
177 |
# new object for font-face only?
|
178 |
(self.CSS3_FONT_FACE,
|
179 |
properties[self.CSS3_FONT_FACE],
|
180 |
macros[self.CSS3_FONTS]
|
181 |
), |
182 |
(self.CSS3_PAGED_MEDIA,
|
183 |
properties[self.CSS3_PAGED_MEDIA],
|
184 |
macros[self.CSS3_PAGED_MEDIA]
|
185 |
), |
186 |
(self.CSS3_TEXT,
|
187 |
properties[self.CSS3_TEXT],
|
188 |
macros[self.CSS3_TEXT]
|
189 |
) |
190 |
]) |
191 |
|
192 |
self.__update_knownNames()
|
193 |
|
194 |
def _expand_macros(self, dictionary, macros): |
195 |
"""Expand macros in token dictionary"""
|
196 |
def macro_value(m): |
197 |
return '(?:%s)' % macros[m.groupdict()['macro']] |
198 |
|
199 |
for key, value in dictionary.items(): |
200 |
if not hasattr(value, '__call__'): |
201 |
while re.search(r'{[a-z][a-z0-9-]*}', value): |
202 |
value = re.sub(r'{(?P<macro>[a-z][a-z0-9-]*)}',
|
203 |
macro_value, value) |
204 |
dictionary[key] = value |
205 |
|
206 |
return dictionary
|
207 |
|
208 |
def _compile_regexes(self, dictionary): |
209 |
"""Compile all regular expressions into callable objects"""
|
210 |
for key, value in dictionary.items(): |
211 |
# might be a function (font-family) as regex is too slow
|
212 |
if not hasattr(value, '__call__') and not isinstance(value, |
213 |
types.FunctionType): |
214 |
value = re.compile('^(?:%s)$' % value, re.I).match
|
215 |
dictionary[key] = value |
216 |
|
217 |
return dictionary
|
218 |
|
219 |
def __update_knownNames(self): |
220 |
self._knownNames = []
|
221 |
for properties in self._profilesProperties.values(): |
222 |
self._knownNames.extend(properties.keys())
|
223 |
|
224 |
def _getDefaultProfiles(self): |
225 |
"If not explicitly set same as Profiles.profiles but in reverse order."
|
226 |
if not self._defaultProfiles: |
227 |
return self.profiles |
228 |
else:
|
229 |
return self._defaultProfiles |
230 |
|
231 |
def _setDefaultProfiles(self, profiles): |
232 |
"profiles may be a single or a list of profile names"
|
233 |
if isinstance(profiles, basestring): |
234 |
self._defaultProfiles = (profiles,)
|
235 |
else:
|
236 |
self._defaultProfiles = profiles
|
237 |
|
238 |
defaultProfiles = property(_getDefaultProfiles,
|
239 |
_setDefaultProfiles, |
240 |
doc=u"Names of profiles to use for validation."
|
241 |
u"To use e.g. the CSS2 profile set "
|
242 |
u"``cssutils.profile.defaultProfiles = "
|
243 |
u"cssutils.profile.CSS_LEVEL_2``")
|
244 |
|
245 |
profiles = property(lambda self: self._profileNames, |
246 |
doc=u'Names of all profiles in order as defined.')
|
247 |
|
248 |
knownNames = property(lambda self: self._knownNames, |
249 |
doc="All known property names of all profiles.")
|
250 |
|
251 |
def _resetProperties(self, newMacros=None): |
252 |
"reset all props from raw values as changes in macros happened"
|
253 |
# base
|
254 |
macros = Profiles._TOKEN_MACROS.copy() |
255 |
macros.update(Profiles._MACROS.copy()) |
256 |
|
257 |
# former
|
258 |
for profile in self._profileNames: |
259 |
macros.update(self._rawProfiles[profile]['macros']) |
260 |
|
261 |
# new
|
262 |
if newMacros:
|
263 |
macros.update(newMacros) |
264 |
|
265 |
# reset properties
|
266 |
self._profilesProperties.clear()
|
267 |
for profile in self._profileNames: |
268 |
properties = self._expand_macros(
|
269 |
# keep raw
|
270 |
self._rawProfiles[profile]['properties'].copy(), |
271 |
macros) |
272 |
self._profilesProperties[profile] = self._compile_regexes(properties) |
273 |
|
274 |
# save
|
275 |
self._usedMacros = macros
|
276 |
|
277 |
|
278 |
def addProfiles(self, profiles): |
279 |
"""Add a list of profiles at once. Useful as if profiles define custom
|
280 |
macros these are used in one go. Using `addProfile` instead my be
|
281 |
**very** slow instead.
|
282 |
"""
|
283 |
# add macros
|
284 |
for profile, properties, macros in profiles: |
285 |
if macros:
|
286 |
self._usedMacros.update(macros)
|
287 |
self._rawProfiles[profile] = {'macros': macros.copy()} |
288 |
|
289 |
# only add new properties
|
290 |
for profile, properties, macros in profiles: |
291 |
self.addProfile(profile, properties.copy(), None) |
292 |
|
293 |
|
294 |
def addProfile(self, profile, properties, macros=None): |
295 |
"""Add a new profile with name `profile` (e.g. 'CSS level 2')
|
296 |
and the given `properties`.
|
297 |
|
298 |
:param profile:
|
299 |
the new `profile`'s name
|
300 |
:param properties:
|
301 |
a dictionary of ``{ property-name: propery-value }`` items where
|
302 |
property-value is a regex which may use macros defined in given
|
303 |
``macros`` or the standard macros Profiles.tokens and
|
304 |
Profiles.generalvalues.
|
305 |
|
306 |
``propery-value`` may also be a function which takes a single
|
307 |
argument which is the value to validate and which should return
|
308 |
True or False.
|
309 |
Any exceptions which may be raised during this custom validation
|
310 |
are reported or raised as all other cssutils exceptions depending
|
311 |
on cssutils.log.raiseExceptions which e.g during parsing normally
|
312 |
is False so the exceptions would be logged only.
|
313 |
:param macros:
|
314 |
may be used in the given properties definitions. There are some
|
315 |
predefined basic macros which may always be used in
|
316 |
:attr:`Profiles._TOKEN_MACROS` and :attr:`Profiles._MACROS`.
|
317 |
"""
|
318 |
if macros:
|
319 |
# check if known macros would change and if yes reset properties
|
320 |
if len(set(macros.keys()).intersection(self._usedMacros.keys())): |
321 |
self._resetProperties(newMacros=macros)
|
322 |
|
323 |
else:
|
324 |
# no replacement, simply continue
|
325 |
self._usedMacros.update(macros)
|
326 |
|
327 |
else:
|
328 |
# might have been set by addProfiles before
|
329 |
try:
|
330 |
macros = self._rawProfiles[profile]['macros'] |
331 |
except KeyError, e: |
332 |
macros = {} |
333 |
|
334 |
# save name and raw props/macros if macros change to completely reset
|
335 |
self._profileNames.append(profile)
|
336 |
self._rawProfiles[profile] = {'properties': properties.copy(), |
337 |
'macros': macros.copy()}
|
338 |
# prepare and save properties
|
339 |
properties = self._expand_macros(properties, self._usedMacros) |
340 |
self._profilesProperties[profile] = self._compile_regexes(properties) |
341 |
|
342 |
self.__update_knownNames()
|
343 |
|
344 |
# hack for font and font-family which are too slow with regexes
|
345 |
if '__FONT_WITH_1_FAMILY' in properties: |
346 |
_fontRegexReplacements['__FONT_WITH_1_FAMILY'] = properties['__FONT_WITH_1_FAMILY'] |
347 |
if '__FONT_FAMILY_SINGLE' in properties: |
348 |
_fontRegexReplacements['__FONT_FAMILY_SINGLE'] = properties['__FONT_FAMILY_SINGLE'] |
349 |
|
350 |
|
351 |
def removeProfile(self, profile=None, all=False): |
352 |
"""Remove `profile` or remove `all` profiles.
|
353 |
|
354 |
If the removed profile used custom macros all remaining profiles
|
355 |
are reset to reflect the macro changes. This may be quite an expensive
|
356 |
operation!
|
357 |
|
358 |
:param profile:
|
359 |
profile name to remove
|
360 |
:param all:
|
361 |
if ``True`` removes all profiles to start with a clean state
|
362 |
:exceptions:
|
363 |
- :exc:`cssutils.profiles.NoSuchProfileException`:
|
364 |
If given `profile` cannot be found.
|
365 |
"""
|
366 |
if all: |
367 |
self._profilesProperties.clear()
|
368 |
self._rawProfiles.clear()
|
369 |
del self._profileNames[:] |
370 |
else:
|
371 |
reset = False
|
372 |
|
373 |
try:
|
374 |
if (self._rawProfiles[profile]['macros']): |
375 |
reset = True
|
376 |
|
377 |
del self._profilesProperties[profile] |
378 |
del self._rawProfiles[profile] |
379 |
del self._profileNames[self._profileNames.index(profile)] |
380 |
except KeyError: |
381 |
raise NoSuchProfileException(u'No profile %r.' % profile) |
382 |
|
383 |
else:
|
384 |
if reset:
|
385 |
# reset properties as macros were removed
|
386 |
self._resetProperties()
|
387 |
|
388 |
self.__update_knownNames()
|
389 |
|
390 |
def propertiesByProfile(self, profiles=None): |
391 |
"""Generator: Yield property names, if no `profiles` is given all
|
392 |
profile's properties are used.
|
393 |
|
394 |
:param profiles:
|
395 |
a single profile name or a list of names.
|
396 |
"""
|
397 |
if not profiles: |
398 |
profiles = self.profiles
|
399 |
elif isinstance(profiles, basestring): |
400 |
profiles = (profiles, ) |
401 |
try:
|
402 |
for profile in sorted(profiles): |
403 |
for name in sorted(self._profilesProperties[profile].keys()): |
404 |
yield name
|
405 |
except KeyError, e: |
406 |
raise NoSuchProfileException(e)
|
407 |
|
408 |
def validate(self, name, value): |
409 |
"""Check if `value` is valid for given property `name` using **any**
|
410 |
profile.
|
411 |
|
412 |
:param name:
|
413 |
a property name
|
414 |
:param value:
|
415 |
a CSS value (string)
|
416 |
:returns:
|
417 |
if the `value` is valid for the given property `name` in any
|
418 |
profile
|
419 |
"""
|
420 |
for profile in self.profiles: |
421 |
if name in self._profilesProperties[profile]: |
422 |
try:
|
423 |
# custom validation errors are caught
|
424 |
r = bool(self._profilesProperties[profile][name](value)) |
425 |
except Exception, e: |
426 |
# TODO: more specific exception?
|
427 |
# Validate should not be fatal though!
|
428 |
self._log.error(e, error=Exception) |
429 |
r = False
|
430 |
if r:
|
431 |
return r
|
432 |
return False |
433 |
|
434 |
def validateWithProfile(self, name, value, profiles=None): |
435 |
"""Check if `value` is valid for given property `name` returning
|
436 |
``(valid, profile)``.
|
437 |
|
438 |
:param name:
|
439 |
a property name
|
440 |
:param value:
|
441 |
a CSS value (string)
|
442 |
:param profiles:
|
443 |
internal parameter used by Property.validate only
|
444 |
:returns:
|
445 |
``valid, matching, profiles`` where ``valid`` is if the `value`
|
446 |
is valid for the given property `name` in any profile,
|
447 |
``matching==True`` if it is valid in the given `profiles`
|
448 |
and ``profiles`` the profile names for which the value is valid
|
449 |
(or ``[]`` if not valid at all)
|
450 |
|
451 |
Example::
|
452 |
|
453 |
>>> cssutils.profile.defaultProfiles = cssutils.profile.CSS_LEVEL_2
|
454 |
>>> print cssutils.profile.validateWithProfile('color', 'rgba(1,1,1,1)')
|
455 |
(True, False, Profiles.CSS3_COLOR)
|
456 |
"""
|
457 |
if name not in self.knownNames: |
458 |
return False, False, [] |
459 |
else:
|
460 |
if not profiles: |
461 |
profiles = self.defaultProfiles
|
462 |
elif isinstance(profiles, basestring): |
463 |
profiles = (profiles, ) |
464 |
for profilename in reversed(profiles): |
465 |
# check given profiles
|
466 |
if name in self._profilesProperties[profilename]: |
467 |
validate = self._profilesProperties[profilename][name]
|
468 |
try:
|
469 |
if validate(value):
|
470 |
return True, True, [profilename] |
471 |
except Exception, e: |
472 |
self._log.error(e, error=Exception) |
473 |
|
474 |
for profilename in (p for p in self._profileNames |
475 |
if p not in profiles): |
476 |
# check remaining profiles as well
|
477 |
if name in self._profilesProperties[profilename]: |
478 |
validate = self._profilesProperties[profilename][name]
|
479 |
try:
|
480 |
if validate(value):
|
481 |
return True, False, [profilename] |
482 |
except Exception, e: |
483 |
self._log.error(e, error=Exception) |
484 |
|
485 |
names = [] |
486 |
for profilename, properties in self._profilesProperties.items(): |
487 |
# return profile to which name belongs
|
488 |
if name in properties.keys(): |
489 |
names.append(profilename) |
490 |
names.sort() |
491 |
return False, False, names |
492 |
|
493 |
|
494 |
properties = {} |
495 |
macros = {} |
496 |
|
497 |
|
498 |
"""
|
499 |
Define some regular expression fragments that will be used as
|
500 |
macros within the CSS property value regular expressions.
|
501 |
"""
|
502 |
macros[Profiles.CSS_LEVEL_2] = { |
503 |
'background-color': r'{color}|transparent|inherit', |
504 |
'background-image': r'{uri}|none|inherit', |
505 |
#'background-position': r'({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?)|inherit',
|
506 |
'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', |
507 |
'background-repeat': r'repeat|repeat-x|repeat-y|no-repeat|inherit', |
508 |
'background-attachment': r'scroll|fixed|inherit', |
509 |
'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)', |
510 |
'counter': r'counter\({w}{ident}{w}(?:,{w}{list-style-type}{w})?\)', |
511 |
'identifier': r'{ident}', |
512 |
'family-name': r'{string}|{ident}({w}{ident})*', |
513 |
'generic-family': r'serif|sans-serif|cursive|fantasy|monospace', |
514 |
'absolute-size': r'(x?x-)?(small|large)|medium', |
515 |
'relative-size': r'smaller|larger', |
516 |
|
517 |
#[[ <family-name> | <generic-family> ] [, <family-name>| <generic-family>]* ] | inherit
|
518 |
#'font-family': r'(({family-name}|{generic-family})({w},{w}({family-name}|{generic-family}))*)|inherit',
|
519 |
# EXTREMELY SLOW REGEX
|
520 |
#'font-family': r'({family-name}({w},{w}{family-name})*)|inherit',
|
521 |
|
522 |
'font-size': r'{absolute-size}|{relative-size}|{positivelength}|{percentage}|inherit', |
523 |
'font-style': r'normal|italic|oblique|inherit', |
524 |
'font-variant': r'normal|small-caps|inherit', |
525 |
'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit', |
526 |
'line-height': r'normal|{number}|{length}|{percentage}|inherit', |
527 |
'list-style-image': r'{uri}|none|inherit', |
528 |
'list-style-position': r'inside|outside|inherit', |
529 |
'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', |
530 |
'margin-width': r'{length}|{percentage}|auto', |
531 |
'padding-width': r'{length}|{percentage}', |
532 |
'specific-voice': r'{ident}', |
533 |
'generic-voice': r'male|female|child', |
534 |
'content': r'{string}|{uri}|{counter}|attr\({w}{ident}{w}\)|open-quote|close-quote|no-open-quote|no-close-quote', |
535 |
'background-attrs': r'{background-color}|{background-image}|{background-repeat}|{background-attachment}|{background-position}', |
536 |
'list-attrs': r'{list-style-type}|{list-style-position}|{list-style-image}', |
537 |
'font-attrs': r'{font-style}|{font-variant}|{font-weight}', |
538 |
'text-attrs': r'underline|overline|line-through|blink', |
539 |
'overflow': r'visible|hidden|scroll|auto|inherit', |
540 |
} |
541 |
|
542 |
"""
|
543 |
Define the regular expressions for validation all CSS values
|
544 |
"""
|
545 |
properties[Profiles.CSS_LEVEL_2] = { |
546 |
'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', |
547 |
'background-attachment': r'{background-attachment}', |
548 |
'background-color': r'{background-color}', |
549 |
'background-image': r'{background-image}', |
550 |
'background-position': r'{background-position}', |
551 |
'background-repeat': r'{background-repeat}', |
552 |
# Each piece should only be allowed one time
|
553 |
'background': r'{background-attrs}(\s+{background-attrs})*|inherit', |
554 |
'border-collapse': r'collapse|separate|inherit', |
555 |
'border-spacing': r'{length}(\s+{length})?|inherit', |
556 |
'bottom': r'{length}|{percentage}|auto|inherit', |
557 |
'caption-side': r'top|bottom|inherit', |
558 |
'clear': r'none|left|right|both|inherit', |
559 |
'clip': r'{shape}|auto|inherit', |
560 |
'color': r'{color}|inherit', |
561 |
'content': r'none|normal|{content}(\s+{content})*|inherit', |
562 |
'counter-increment': r'({ident}(\s+{integer})?)(\s+({ident}(\s+{integer})?))*|none|inherit', |
563 |
'counter-reset': r'({ident}(\s+{integer})?)(\s+({ident}(\s+{integer})?))*|none|inherit', |
564 |
'cue-after': r'{uri}|none|inherit', |
565 |
'cue-before': r'{uri}|none|inherit', |
566 |
'cue': r'({uri}|none|inherit){1,2}|inherit', |
567 |
#'cursor': r'((({uri}{w},{w})*)?(auto|crosshair|default|pointer|move|(e|ne|nw|n|se|sw|s|w)-resize|text|wait|help|progress))|inherit',
|
568 |
'direction': r'ltr|rtl|inherit', |
569 |
'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', |
570 |
'elevation': r'{angle}|below|level|above|higher|lower|inherit', |
571 |
'empty-cells': r'show|hide|inherit', |
572 |
'float': r'left|right|none|inherit', |
573 |
|
574 |
# regex too slow:
|
575 |
# 'font-family': r'{font-family}',
|
576 |
'font-family': _fontFamilyValidator,
|
577 |
'__FONT_FAMILY_SINGLE': r'{family-name}', |
578 |
|
579 |
'font-size': r'{font-size}', |
580 |
'font-style': r'{font-style}', |
581 |
'font-variant': r'{font-variant}', |
582 |
'font-weight': r'{font-weight}', |
583 |
|
584 |
# regex too slow and wrong too:
|
585 |
# 'font': r'({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{font-family}|caption|icon|menu|message-box|small-caption|status-bar|inherit',
|
586 |
'font': _fontValidator,
|
587 |
'__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', |
588 |
|
589 |
'height': r'{length}|{percentage}|auto|inherit', |
590 |
'left': r'{length}|{percentage}|auto|inherit', |
591 |
'letter-spacing': r'normal|{length}|inherit', |
592 |
'line-height': r'{line-height}', |
593 |
'list-style-image': r'{list-style-image}', |
594 |
'list-style-position': r'{list-style-position}', |
595 |
'list-style-type': r'{list-style-type}', |
596 |
'list-style': r'{list-attrs}(\s+{list-attrs})*|inherit', |
597 |
'margin-right': r'{margin-width}|inherit', |
598 |
'margin-left': r'{margin-width}|inherit', |
599 |
'margin-top': r'{margin-width}|inherit', |
600 |
'margin-bottom': r'{margin-width}|inherit', |
601 |
'margin': r'{margin-width}(\s+{margin-width}){0,3}|inherit', |
602 |
'max-height': r'{length}|{percentage}|none|inherit', |
603 |
'max-width': r'{length}|{percentage}|none|inherit', |
604 |
'min-height': r'{length}|{percentage}|none|inherit', |
605 |
'min-width': r'{length}|{percentage}|none|inherit', |
606 |
'orphans': r'{integer}|inherit', |
607 |
'overflow': r'{overflow}', |
608 |
'padding-top': r'{padding-width}|inherit', |
609 |
'padding-right': r'{padding-width}|inherit', |
610 |
'padding-bottom': r'{padding-width}|inherit', |
611 |
'padding-left': r'{padding-width}|inherit', |
612 |
'padding': r'{padding-width}(\s+{padding-width}){0,3}|inherit', |
613 |
'page-break-after': r'auto|always|avoid|left|right|inherit', |
614 |
'page-break-before': r'auto|always|avoid|left|right|inherit', |
615 |
'page-break-inside': r'avoid|auto|inherit', |
616 |
'pause-after': r'{time}|{percentage}|inherit', |
617 |
'pause-before': r'{time}|{percentage}|inherit', |
618 |
'pause': r'({time}|{percentage}){1,2}|inherit', |
619 |
'pitch-range': r'{number}|inherit', |
620 |
'pitch': r'{frequency}|x-low|low|medium|high|x-high|inherit', |
621 |
'play-during': r'{uri}(\s+(mix|repeat))*|auto|none|inherit', |
622 |
'position': r'static|relative|absolute|fixed|inherit', |
623 |
'quotes': r'({string}\s+{string})(\s+{string}\s+{string})*|none|inherit', |
624 |
'richness': r'{number}|inherit', |
625 |
'right': r'{length}|{percentage}|auto|inherit', |
626 |
'speak-header': r'once|always|inherit', |
627 |
'speak-numeral': r'digits|continuous|inherit', |
628 |
'speak-punctuation': r'code|none|inherit', |
629 |
'speak': r'normal|none|spell-out|inherit', |
630 |
'speech-rate': r'{number}|x-slow|slow|medium|fast|x-fast|faster|slower|inherit', |
631 |
'stress': r'{number}|inherit', |
632 |
'table-layout': r'auto|fixed|inherit', |
633 |
'text-align': r'left|right|center|justify|inherit', |
634 |
'text-decoration': r'none|{text-attrs}(\s+{text-attrs})*|inherit', |
635 |
'text-indent': r'{length}|{percentage}|inherit', |
636 |
'text-transform': r'capitalize|uppercase|lowercase|none|inherit', |
637 |
'top': r'{length}|{percentage}|auto|inherit', |
638 |
'unicode-bidi': r'normal|embed|bidi-override|inherit', |
639 |
'vertical-align': r'baseline|sub|super|top|text-top|middle|bottom|text-bottom|{percentage}|{length}|inherit', |
640 |
'visibility': r'visible|hidden|collapse|inherit', |
641 |
'voice-family': r'({specific-voice}|{generic-voice}{w},{w})*({specific-voice}|{generic-voice})|inherit', |
642 |
'volume': r'{number}|{percentage}|silent|x-soft|soft|medium|loud|x-loud|inherit', |
643 |
'white-space': r'normal|pre|nowrap|pre-wrap|pre-line|inherit', |
644 |
'widows': r'{integer}|inherit', |
645 |
'width': r'{length}|{percentage}|auto|inherit', |
646 |
'word-spacing': r'normal|{length}|inherit', |
647 |
'z-index': r'auto|{integer}|inherit', |
648 |
} |
649 |
|
650 |
|
651 |
macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS] = { |
652 |
'border-style': 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset', |
653 |
'border-width': '{length}|thin|medium|thick', |
654 |
'b1': r'{border-width}?({w}{border-style})?({w}{color})?', |
655 |
'b2': r'{border-width}?({w}{color})?({w}{border-style})?', |
656 |
'b3': r'{border-style}?({w}{border-width})?({w}{color})?', |
657 |
'b4': r'{border-style}?({w}{color})?({w}{border-width})?', |
658 |
'b5': r'{color}?({w}{border-style})?({w}{border-width})?', |
659 |
'b6': r'{color}?({w}{border-width})?({w}{border-style})?', |
660 |
'border-attrs': r'{b1}|{b2}|{b3}|{b4}|{b5}|{b6}', |
661 |
'border-radius-part': '({length}|{percentage})(\s+({length}|{percentage}))?' |
662 |
} |
663 |
properties[Profiles.CSS3_BACKGROUNDS_AND_BORDERS] = { |
664 |
'border-color': r'({color}|transparent)(\s+({color}|transparent)){0,3}|inherit', |
665 |
'border-style': r'{border-style}(\s+{border-style}){0,3}|inherit', |
666 |
'border-top': r'{border-attrs}|inherit', |
667 |
'border-right': r'{border-attrs}|inherit', |
668 |
'border-bottom': r'{border-attrs}|inherit', |
669 |
'border-left': r'{border-attrs}|inherit', |
670 |
'border-top-color': r'{color}|transparent|inherit', |
671 |
'border-right-color': r'{color}|transparent|inherit', |
672 |
'border-bottom-color': r'{color}|transparent|inherit', |
673 |
'border-left-color': r'{color}|transparent|inherit', |
674 |
'border-top-style': r'{border-style}|inherit', |
675 |
'border-right-style': r'{border-style}|inherit', |
676 |
'border-bottom-style': r'{border-style}|inherit', |
677 |
'border-left-style': r'{border-style}|inherit', |
678 |
'border-top-width': r'{border-width}|inherit', |
679 |
'border-right-width': r'{border-width}|inherit', |
680 |
'border-bottom-width': r'{border-width}|inherit', |
681 |
'border-left-width': r'{border-width}|inherit', |
682 |
'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit', |
683 |
'border': r'{border-attrs}|inherit', |
684 |
'border-top-right-radius': '{border-radius-part}', |
685 |
'border-bottom-right-radius': '{border-radius-part}', |
686 |
'border-bottom-left-radius': '{border-radius-part}', |
687 |
'border-top-left-radius': '{border-radius-part}', |
688 |
'border-radius': '({length}{w}|{percentage}{w}){1,4}(/{w}({length}{w}|{percentage}{w}){1,4})?', |
689 |
'box-shadow': 'none|{shadow}({w},{w}{shadow})*', |
690 |
} |
691 |
|
692 |
# CSS3 Basic User Interface Module
|
693 |
macros[Profiles.CSS3_BASIC_USER_INTERFACE] = { |
694 |
'border-style': macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS]['border-style'], |
695 |
'border-width': macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS]['border-width'], |
696 |
'outline-1': r'{outline-color}(\s+{outline-style})?(\s+{outline-width})?', |
697 |
'outline-2': r'{outline-color}(\s+{outline-width})?(\s+{outline-style})?', |
698 |
'outline-3': r'{outline-style}(\s+{outline-color})?(\s+{outline-width})?', |
699 |
'outline-4': r'{outline-style}(\s+{outline-width})?(\s+{outline-color})?', |
700 |
'outline-5': r'{outline-width}(\s+{outline-color})?(\s+{outline-style})?', |
701 |
'outline-6': r'{outline-width}(\s+{outline-style})?(\s+{outline-color})?', |
702 |
'outline-color': r'{color}|invert|inherit', |
703 |
'outline-style': r'auto|{border-style}|inherit', |
704 |
'outline-width': r'{border-width}|inherit', |
705 |
} |
706 |
properties[Profiles.CSS3_BASIC_USER_INTERFACE] = { |
707 |
'box-sizing': r'content-box|border-box', |
708 |
'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', |
709 |
'nav-index': r'auto|{number}|inherit', |
710 |
'outline-color': r'{outline-color}', |
711 |
'outline-style': r'{outline-style}', |
712 |
'outline-width': r'{outline-width}', |
713 |
'outline-offset': r'{length}|inherit', |
714 |
#'outline': r'{outline-attrs}(\s+{outline-attrs})*|inherit',
|
715 |
'outline': r'{outline-1}|{outline-2}|{outline-3}|{outline-4}|{outline-5}|{outline-6}|inherit', |
716 |
'resize': 'none|both|horizontal|vertical|inherit', |
717 |
} |
718 |
|
719 |
# CSS Box Module Level 3
|
720 |
macros[Profiles.CSS3_BOX] = { |
721 |
'overflow': macros[Profiles.CSS_LEVEL_2]['overflow'] |
722 |
} |
723 |
properties[Profiles.CSS3_BOX] = { |
724 |
'overflow': '{overflow}{w}{overflow}?|inherit', |
725 |
'overflow-x': '{overflow}|inherit', |
726 |
'overflow-y': '{overflow}|inherit' |
727 |
} |
728 |
|
729 |
# CSS Color Module Level 3
|
730 |
macros[Profiles.CSS3_COLOR] = { |
731 |
# orange and transparent in CSS 2.1
|
732 |
'namedcolor': r'(currentcolor|transparent|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)', |
733 |
# orange?
|
734 |
'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}\)', |
735 |
'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}\)', |
736 |
'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', |
737 |
'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)', |
738 |
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{rgbacolor}|{hslcolor}|{x11color}|inherit', |
739 |
} |
740 |
properties[Profiles.CSS3_COLOR] = { |
741 |
'opacity': r'{num}|inherit', |
742 |
} |
743 |
|
744 |
# CSS Fonts Module Level 3 http://www.w3.org/TR/css3-fonts/
|
745 |
macros[Profiles.CSS3_FONTS] = { |
746 |
#'family-name': r'{string}|{ident}',
|
747 |
'family-name': r'{string}|{ident}({w}{ident})*', |
748 |
'font-face-name': 'local\({w}{family-name}{w}\)', |
749 |
'font-stretch-names': r'(ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded)', |
750 |
'unicode-range': r'[uU]\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?' |
751 |
} |
752 |
properties[Profiles.CSS3_FONTS] = { |
753 |
'font-size-adjust': r'{number}|none|inherit', |
754 |
'font-stretch': r'normal|wider|narrower|{font-stretch-names}|inherit' |
755 |
} |
756 |
properties[Profiles.CSS3_FONT_FACE] = { |
757 |
'font-family': '{family-name}', |
758 |
'font-stretch': r'{font-stretch-names}', |
759 |
'font-style': r'normal|italic|oblique', |
760 |
'font-weight': r'normal|bold|[1-9]00', |
761 |
'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}))*', |
762 |
'unicode-range': '{unicode-range}({w},{w}{unicode-range})*' |
763 |
} |
764 |
|
765 |
# CSS3 Paged Media
|
766 |
macros[Profiles.CSS3_PAGED_MEDIA] = { |
767 |
'page-size': 'a5|a4|a3|b5|b4|letter|legal|ledger', |
768 |
'page-orientation': 'portrait|landscape', |
769 |
'page-1': '{page-size}(?:{w}{page-orientation})?', |
770 |
'page-2': '{page-orientation}(?:{w}{page-size})?', |
771 |
'page-size-orientation': '{page-1}|{page-2}', |
772 |
'pagebreak': 'auto|always|avoid|left|right' |
773 |
} |
774 |
properties[Profiles.CSS3_PAGED_MEDIA] = { |
775 |
'fit': 'fill|hidden|meet|slice', |
776 |
'fit-position': r'auto|(({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?))', |
777 |
'image-orientation': 'auto|{angle}', |
778 |
'orphans': r'{integer}|inherit', |
779 |
'page': 'auto|{ident}', |
780 |
'page-break-before': '{pagebreak}|inherit', |
781 |
'page-break-after': '{pagebreak}|inherit', |
782 |
'page-break-inside': 'auto|avoid|inherit', |
783 |
'size': '({length}{w}){1,2}|auto|{page-size-orientation}', |
784 |
'widows': r'{integer}|inherit' |
785 |
} |
786 |
|
787 |
macros[Profiles.CSS3_TEXT] = { |
788 |
} |
789 |
properties[Profiles.CSS3_TEXT] = { |
790 |
'text-shadow': 'none|{shadow}({w},{w}{shadow})*', |
791 |
} |