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