Statistics
| Revision:

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
    }