2011-08-16 04:47:01 +04:00
|
|
|
|
# coding: utf8
|
|
|
|
|
"""
|
2012-03-22 02:19:27 +04:00
|
|
|
|
weasyprint.css.validation
|
|
|
|
|
-------------------------
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
Expand shorthands and validate property values.
|
|
|
|
|
See http://www.w3.org/TR/CSS21/propidx.html and various CSS3 modules.
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2014-01-10 18:27:02 +04:00
|
|
|
|
:copyright: Copyright 2011-2014 Simon Sapin and contributors, see AUTHORS.
|
2012-03-22 02:19:27 +04:00
|
|
|
|
:license: BSD, see LICENSE for details.
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
"""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-02-17 21:49:58 +04:00
|
|
|
|
from __future__ import division, unicode_literals
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
import functools
|
2013-04-04 17:30:50 +04:00
|
|
|
|
import math
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-03-24 16:39:31 +04:00
|
|
|
|
from tinycss.color3 import parse_color
|
2012-04-05 18:31:32 +04:00
|
|
|
|
from tinycss.parsing import split_on_comma, remove_whitespace
|
2012-03-24 16:39:31 +04:00
|
|
|
|
|
2012-02-22 20:12:40 +04:00
|
|
|
|
from ..logger import LOGGER
|
2011-12-07 20:09:59 +04:00
|
|
|
|
from ..formatting_structure import counters
|
2012-05-30 22:21:36 +04:00
|
|
|
|
from ..compat import urljoin, unquote
|
2012-10-04 21:12:34 +04:00
|
|
|
|
from ..urls import url_is_absolute, iri_to_uri
|
2013-04-11 12:39:23 +04:00
|
|
|
|
from ..images import LinearGradient, RadialGradient
|
2012-04-04 17:05:25 +04:00
|
|
|
|
from .properties import (INITIAL_VALUES, KNOWN_PROPERTIES, NOT_PRINT_MEDIA,
|
2012-04-05 13:21:26 +04:00
|
|
|
|
Dimension)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
from . import computed_values
|
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
# TODO: unit-test these validators
|
|
|
|
|
|
2012-04-03 16:59:06 +04:00
|
|
|
|
|
|
|
|
|
# get the sets of keys
|
2013-04-04 04:38:39 +04:00
|
|
|
|
LENGTH_UNITS = set(computed_values.LENGTHS_TO_PIXELS) | set(['ex', 'em', 'ch'])
|
2013-04-04 17:30:50 +04:00
|
|
|
|
|
2012-04-03 16:59:06 +04:00
|
|
|
|
|
2011-12-02 21:02:58 +04:00
|
|
|
|
# keyword -> (open, insert)
|
|
|
|
|
CONTENT_QUOTE_KEYWORDS = {
|
|
|
|
|
'open-quote': (True, True),
|
|
|
|
|
'close-quote': (False, True),
|
|
|
|
|
'no-open-quote': (True, False),
|
|
|
|
|
'no-close-quote': (False, False),
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-25 21:36:14 +04:00
|
|
|
|
ZERO_PERCENT = Dimension(0, '%')
|
2013-03-21 17:01:12 +04:00
|
|
|
|
FIFTY_PERCENT = Dimension(50, '%')
|
2013-03-25 21:36:14 +04:00
|
|
|
|
HUNDRED_PERCENT = Dimension(100, '%')
|
2011-12-16 17:25:05 +04:00
|
|
|
|
BACKGROUND_POSITION_PERCENTAGES = {
|
2013-03-25 21:36:14 +04:00
|
|
|
|
'top': ZERO_PERCENT,
|
|
|
|
|
'left': ZERO_PERCENT,
|
2013-03-21 17:01:12 +04:00
|
|
|
|
'center': FIFTY_PERCENT,
|
2013-03-25 21:36:14 +04:00
|
|
|
|
'bottom': HUNDRED_PERCENT,
|
|
|
|
|
'right': HUNDRED_PERCENT,
|
2011-12-16 17:25:05 +04:00
|
|
|
|
}
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
# yes/no validators for non-shorthand properties
|
|
|
|
|
# Maps property names to functions taking a property name and a value list,
|
2011-10-08 16:41:12 +04:00
|
|
|
|
# returning a value or None for invalid.
|
|
|
|
|
# Also transform values: keyword and URLs are returned as strings.
|
|
|
|
|
# For properties that take a single value, that value is returned by itself
|
|
|
|
|
# instead of a list.
|
2011-08-16 04:47:01 +04:00
|
|
|
|
VALIDATORS = {}
|
|
|
|
|
|
|
|
|
|
EXPANDERS = {}
|
|
|
|
|
|
2011-12-13 15:51:30 +04:00
|
|
|
|
PREFIXED = set()
|
2012-07-04 13:27:20 +04:00
|
|
|
|
UNPREFIXED = set()
|
2012-04-04 17:05:25 +04:00
|
|
|
|
PREFIX = '-weasy-'
|
2011-12-13 15:51:30 +04:00
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
class InvalidValues(ValueError):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Invalid or unsupported values for a known CSS property."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-23 19:35:10 +04:00
|
|
|
|
# Validators
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
def validator(property_name=None, prefixed=False, unprefixed=False,
|
|
|
|
|
wants_base_url=False):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Decorator adding a function to the ``VALIDATORS``.
|
|
|
|
|
|
|
|
|
|
The name of the property covered by the decorated function is set to
|
|
|
|
|
``property_name`` if given, or is inferred from the function name
|
|
|
|
|
(replacing underscores by hyphens).
|
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
:param prefixed:
|
|
|
|
|
Vendor-specific (non-standard) and experimental properties are
|
|
|
|
|
prefixed: stylesheets need to use eg. ``-weasy-bookmark-level: 2``
|
|
|
|
|
instead of ``bookmark-level: 2``.
|
|
|
|
|
See http://wiki.csswg.org/spec/vendor-prefixes
|
|
|
|
|
:param unprefixed:
|
|
|
|
|
Mark properties that used to be prefixed. When used with the prefix,
|
|
|
|
|
they will be ignored be give a specific warning.
|
|
|
|
|
:param wants_base_url:
|
|
|
|
|
The function takes the stylesheet’s base URL as an additional
|
|
|
|
|
parameter.
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
"""
|
2012-07-04 13:27:20 +04:00
|
|
|
|
assert not (prefixed and unprefixed)
|
2013-02-25 17:53:30 +04:00
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
def decorator(function):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Add ``function`` to the ``VALIDATORS``."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
if property_name is None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
name = function.__name__.replace('_', '-')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
name = property_name
|
|
|
|
|
assert name in KNOWN_PROPERTIES, name
|
2011-08-16 04:47:01 +04:00
|
|
|
|
assert name not in VALIDATORS, name
|
|
|
|
|
|
2012-03-24 16:39:31 +04:00
|
|
|
|
function.wants_base_url = wants_base_url
|
2011-08-16 04:47:01 +04:00
|
|
|
|
VALIDATORS[name] = function
|
2011-12-13 15:51:30 +04:00
|
|
|
|
if prefixed:
|
|
|
|
|
PREFIXED.add(name)
|
2012-07-04 13:27:20 +04:00
|
|
|
|
if unprefixed:
|
|
|
|
|
UNPREFIXED.add(name)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return function
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
2012-04-03 16:59:06 +04:00
|
|
|
|
def get_keyword(token):
|
|
|
|
|
"""If ``value`` is a keyword, return its name.
|
|
|
|
|
|
|
|
|
|
Otherwise return ``None``.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
if token.type == 'IDENT':
|
|
|
|
|
return token.value.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_single_keyword(tokens):
|
|
|
|
|
"""If ``values`` is a 1-element list of keywords, return its name.
|
|
|
|
|
|
|
|
|
|
Otherwise return ``None``.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
if len(tokens) == 1:
|
|
|
|
|
token = tokens[0]
|
|
|
|
|
if token.type == 'IDENT':
|
|
|
|
|
return token.value.lower()
|
|
|
|
|
|
|
|
|
|
|
2011-08-24 14:23:14 +04:00
|
|
|
|
def single_keyword(function):
|
|
|
|
|
"""Decorator for validators that only accept a single keyword."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@functools.wraps(function)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def keyword_validator(tokens):
|
|
|
|
|
"""Wrap a validator to call get_single_keyword on tokens."""
|
|
|
|
|
keyword = get_single_keyword(tokens)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if function(keyword):
|
|
|
|
|
return keyword
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return keyword_validator
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def single_token(function):
|
|
|
|
|
"""Decorator for validators that only accept a single token."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@functools.wraps(function)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def single_token_validator(tokens, *args):
|
|
|
|
|
"""Validate a property whose token is single."""
|
|
|
|
|
if len(tokens) == 1:
|
|
|
|
|
return function(tokens[0], *args)
|
|
|
|
|
single_token_validator.__func__ = function
|
|
|
|
|
return single_token_validator
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2013-03-19 21:29:58 +04:00
|
|
|
|
def comma_separated_list(function):
|
|
|
|
|
"""Decorator for validators that accept a comma separated list."""
|
|
|
|
|
@functools.wraps(function)
|
|
|
|
|
def wrapper(tokens, *args):
|
|
|
|
|
results = []
|
|
|
|
|
for part in split_on_comma(tokens):
|
|
|
|
|
result = function(remove_whitespace(part), *args)
|
|
|
|
|
if result is None:
|
|
|
|
|
return None
|
|
|
|
|
results.append(result)
|
|
|
|
|
return results
|
|
|
|
|
wrapper.single_value = function
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
2012-04-03 16:59:06 +04:00
|
|
|
|
def get_length(token, negative=True, percentage=False):
|
2015-04-30 11:58:51 +03:00
|
|
|
|
if (token.unit in LENGTH_UNITS or
|
|
|
|
|
(percentage and token.unit == '%') or
|
|
|
|
|
(token.type in ('INTEGER', 'NUMBER') and token.value == 0)):
|
|
|
|
|
if negative or token.value >= 0:
|
|
|
|
|
return Dimension(token.value, token.unit)
|
2011-08-23 19:35:10 +04:00
|
|
|
|
|
|
|
|
|
|
2013-04-04 17:30:50 +04:00
|
|
|
|
# http://dev.w3.org/csswg/css3-values/#angles
|
|
|
|
|
# 1<unit> is this many radians.
|
|
|
|
|
ANGLE_TO_RADIANS = {
|
|
|
|
|
'rad': 1,
|
|
|
|
|
'turn': 2 * math.pi,
|
|
|
|
|
'deg': math.pi / 180,
|
|
|
|
|
'grad': math.pi / 200,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-04-03 16:59:06 +04:00
|
|
|
|
def get_angle(token):
|
2013-07-26 19:26:30 +04:00
|
|
|
|
"""Return the value in radians of an <angle> token, or None."""
|
2013-04-04 17:30:50 +04:00
|
|
|
|
factor = ANGLE_TO_RADIANS.get(token.unit)
|
|
|
|
|
if factor is not None:
|
|
|
|
|
return token.value * factor
|
2012-02-08 18:44:03 +04:00
|
|
|
|
|
|
|
|
|
|
2013-07-26 19:26:30 +04:00
|
|
|
|
# http://dev.w3.org/csswg/css-values/#resolution
|
|
|
|
|
RESOLUTION_TO_DPPX = {
|
|
|
|
|
'dppx': 1,
|
|
|
|
|
'dpi': 1 / computed_values.LENGTHS_TO_PIXELS['in'],
|
|
|
|
|
'dpcm': 1 / computed_values.LENGTHS_TO_PIXELS['cm'],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_resolution(token):
|
|
|
|
|
"""Return the value in dppx of a <resolution> token, or None."""
|
|
|
|
|
factor = RESOLUTION_TO_DPPX.get(token.unit)
|
|
|
|
|
if factor is not None:
|
|
|
|
|
return token.value * factor
|
|
|
|
|
|
|
|
|
|
|
2012-05-30 22:21:36 +04:00
|
|
|
|
def safe_urljoin(base_url, url):
|
|
|
|
|
if url_is_absolute(url):
|
2013-03-18 15:09:57 +04:00
|
|
|
|
return iri_to_uri(url)
|
2012-05-30 22:21:36 +04:00
|
|
|
|
elif base_url:
|
2013-03-18 15:09:57 +04:00
|
|
|
|
return iri_to_uri(urljoin(base_url, url))
|
2012-05-30 22:21:36 +04:00
|
|
|
|
else:
|
|
|
|
|
raise InvalidValues(
|
|
|
|
|
'Relative URI reference without a base URI: %r' % url)
|
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2013-03-19 21:29:58 +04:00
|
|
|
|
@comma_separated_list
|
2011-08-24 14:23:14 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def background_attachment(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``background-attachment`` property validation."""
|
2013-03-20 16:34:13 +04:00
|
|
|
|
return keyword in ('scroll', 'fixed', 'local')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator('background-color')
|
|
|
|
|
@validator('border-top-color')
|
|
|
|
|
@validator('border-right-color')
|
|
|
|
|
@validator('border-bottom-color')
|
|
|
|
|
@validator('border-left-color')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def other_colors(token):
|
|
|
|
|
return parse_color(token)
|
2012-04-02 16:45:44 +04:00
|
|
|
|
|
|
|
|
|
|
2012-08-03 18:21:47 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_token
|
|
|
|
|
def outline_color(token):
|
|
|
|
|
if get_keyword(token) == 'invert':
|
|
|
|
|
return 'currentColor'
|
|
|
|
|
else:
|
|
|
|
|
return parse_color(token)
|
|
|
|
|
|
|
|
|
|
|
2012-07-11 16:11:26 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def border_collapse(keyword):
|
|
|
|
|
return keyword in ('separate', 'collapse')
|
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator('color')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def color(token):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``*-color`` and ``color`` properties validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
result = parse_color(token)
|
|
|
|
|
if result == 'currentColor':
|
2011-12-15 18:00:28 +04:00
|
|
|
|
return 'inherit'
|
2012-03-24 16:39:31 +04:00
|
|
|
|
else:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
return result
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2012-03-24 16:39:31 +04:00
|
|
|
|
@validator('background-image', wants_base_url=True)
|
2013-03-19 21:29:58 +04:00
|
|
|
|
@comma_separated_list
|
2013-04-04 21:48:23 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def background_image(token, base_url):
|
|
|
|
|
if token.type != 'FUNCTION':
|
|
|
|
|
return image_url([token], base_url)
|
2013-04-11 12:39:23 +04:00
|
|
|
|
arguments = split_on_comma(t for t in token.content if t.type != 'S')
|
2013-04-04 21:48:23 +04:00
|
|
|
|
name = token.function_name.lower()
|
|
|
|
|
if name in ('linear-gradient', 'repeating-linear-gradient'):
|
|
|
|
|
direction, color_stops = parse_linear_gradient_parameters(arguments)
|
|
|
|
|
if color_stops:
|
2013-04-11 12:39:23 +04:00
|
|
|
|
return 'linear-gradient', LinearGradient(
|
2013-04-04 21:48:23 +04:00
|
|
|
|
[parse_color_stop(stop) for stop in color_stops],
|
|
|
|
|
direction, 'repeating' in name)
|
2013-04-11 12:39:23 +04:00
|
|
|
|
elif name in ('radial-gradient', 'repeating-radial-gradient'):
|
|
|
|
|
result = parse_radial_gradient_parameters(arguments)
|
|
|
|
|
if result is not None:
|
|
|
|
|
shape, size, position, color_stops = result
|
|
|
|
|
else:
|
|
|
|
|
shape = 'ellipse'
|
|
|
|
|
size = 'keyword', 'farthest-corner'
|
|
|
|
|
position = 'left', FIFTY_PERCENT, 'top', FIFTY_PERCENT
|
|
|
|
|
color_stops = arguments
|
|
|
|
|
if color_stops:
|
|
|
|
|
return 'radial-gradient', RadialGradient(
|
|
|
|
|
[parse_color_stop(stop) for stop in color_stops],
|
|
|
|
|
shape, size, position, 'repeating' in name)
|
2013-04-04 21:48:23 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DIRECTION_KEYWORDS = {
|
|
|
|
|
# ('angle', radians) 0 upwards, then clockwise
|
|
|
|
|
('to', 'top'): ('angle', 0),
|
|
|
|
|
('to', 'right'): ('angle', math.pi / 2),
|
|
|
|
|
('to', 'bottom'): ('angle', math.pi),
|
|
|
|
|
('to', 'left'): ('angle', math.pi * 3 / 2),
|
|
|
|
|
# ('corner', keyword)
|
|
|
|
|
('to', 'top', 'left'): ('corner', 'top_left'),
|
|
|
|
|
('to', 'left', 'top'): ('corner', 'top_left'),
|
|
|
|
|
('to', 'top', 'right'): ('corner', 'top_right'),
|
|
|
|
|
('to', 'right', 'top'): ('corner', 'top_right'),
|
|
|
|
|
('to', 'bottom', 'left'): ('corner', 'bottom_left'),
|
|
|
|
|
('to', 'left', 'bottom'): ('corner', 'bottom_left'),
|
|
|
|
|
('to', 'bottom', 'right'): ('corner', 'bottom_right'),
|
|
|
|
|
('to', 'right', 'bottom'): ('corner', 'bottom_right'),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_linear_gradient_parameters(arguments):
|
|
|
|
|
first_arg = arguments[0]
|
|
|
|
|
if len(first_arg) == 1:
|
|
|
|
|
angle = get_angle(first_arg[0])
|
|
|
|
|
if angle is not None:
|
|
|
|
|
return ('angle', angle), arguments[1:]
|
|
|
|
|
else:
|
|
|
|
|
result = DIRECTION_KEYWORDS.get(tuple(map(get_keyword, first_arg)))
|
|
|
|
|
if result is not None:
|
|
|
|
|
return result, arguments[1:]
|
2013-04-11 18:06:28 +04:00
|
|
|
|
return ('angle', math.pi), arguments # Default direction is 'to bottom'
|
2013-04-04 21:48:23 +04:00
|
|
|
|
|
|
|
|
|
|
2013-04-11 12:39:23 +04:00
|
|
|
|
def parse_radial_gradient_parameters(arguments):
|
|
|
|
|
shape = None
|
|
|
|
|
position = None
|
|
|
|
|
size = None
|
|
|
|
|
size_shape = None
|
|
|
|
|
stack = arguments[0][::-1]
|
|
|
|
|
while stack:
|
|
|
|
|
token = stack.pop()
|
|
|
|
|
keyword = get_keyword(token)
|
|
|
|
|
if keyword == 'at':
|
|
|
|
|
position = background_position.single_value(stack[::-1])
|
2013-04-11 18:06:28 +04:00
|
|
|
|
if position is None:
|
|
|
|
|
return
|
2013-04-11 12:39:23 +04:00
|
|
|
|
break
|
|
|
|
|
elif keyword in ('circle', 'ellipse') and shape is None:
|
|
|
|
|
shape = keyword
|
|
|
|
|
elif keyword in ('closest-corner', 'farthest-corner',
|
|
|
|
|
'closest-side', 'farthest-side') and size is None:
|
|
|
|
|
size = 'keyword', keyword
|
|
|
|
|
else:
|
|
|
|
|
if stack and size is None:
|
|
|
|
|
length_1 = get_length(token, percentage=True)
|
|
|
|
|
length_2 = get_length(stack[-1], percentage=True)
|
|
|
|
|
if None not in (length_1, length_2):
|
|
|
|
|
size = 'explicit', (length_1, length_2)
|
|
|
|
|
size_shape = 'ellipse'
|
|
|
|
|
stack.pop()
|
|
|
|
|
if size is None:
|
|
|
|
|
length_1 = get_length(token)
|
|
|
|
|
if length_1 is not None:
|
|
|
|
|
size = 'explicit', (length_1, length_1)
|
|
|
|
|
size_shape = 'circle'
|
|
|
|
|
if size is None:
|
|
|
|
|
return
|
|
|
|
|
if (shape, size_shape) in (('circle', 'ellipse'), ('circle', 'ellipse')):
|
|
|
|
|
return
|
|
|
|
|
return (
|
|
|
|
|
shape or size_shape or 'ellipse',
|
2013-04-11 18:06:28 +04:00
|
|
|
|
size or ('keyword', 'farthest-corner'),
|
2013-04-11 12:39:23 +04:00
|
|
|
|
position or ('left', FIFTY_PERCENT, 'top', FIFTY_PERCENT),
|
|
|
|
|
arguments[1:])
|
|
|
|
|
|
|
|
|
|
|
2013-04-04 21:48:23 +04:00
|
|
|
|
def parse_color_stop(tokens):
|
|
|
|
|
if len(tokens) == 1:
|
|
|
|
|
color = parse_color(tokens[0])
|
|
|
|
|
if color is not None:
|
|
|
|
|
return color, None
|
|
|
|
|
elif len(tokens) == 2:
|
|
|
|
|
color = parse_color(tokens[0])
|
|
|
|
|
position = get_length(tokens[1], negative=True, percentage=True)
|
|
|
|
|
if color is not None and position is not None:
|
|
|
|
|
return color, position
|
|
|
|
|
raise InvalidValues
|
|
|
|
|
|
|
|
|
|
|
2012-03-24 16:39:31 +04:00
|
|
|
|
@validator('list-style-image', wants_base_url=True)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
2013-04-04 21:48:23 +04:00
|
|
|
|
def image_url(token, base_url):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``*-image`` properties validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if get_keyword(token) == 'none':
|
2013-04-04 17:33:15 +04:00
|
|
|
|
return 'none', None
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if token.type == 'URI':
|
2013-04-04 17:33:15 +04:00
|
|
|
|
return 'url', safe_urljoin(base_url, token.value)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2013-03-25 21:36:14 +04:00
|
|
|
|
class CenterKeywordFakeToken(object):
|
|
|
|
|
type = 'IDENT'
|
|
|
|
|
value = 'center'
|
|
|
|
|
unit = None
|
|
|
|
|
|
|
|
|
|
|
2013-03-21 17:01:12 +04:00
|
|
|
|
@validator(unprefixed=True)
|
|
|
|
|
def transform_origin(tokens):
|
|
|
|
|
# TODO: parse (and ignore) a third value for Z.
|
2013-03-25 21:36:14 +04:00
|
|
|
|
return simple_2d_position(tokens)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2013-03-21 17:01:12 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@comma_separated_list
|
|
|
|
|
def background_position(tokens):
|
|
|
|
|
"""``background-position`` property validation.
|
|
|
|
|
|
2013-03-25 21:36:14 +04:00
|
|
|
|
See http://dev.w3.org/csswg/css3-background/#the-background-position
|
2013-03-21 17:01:12 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2013-03-25 21:36:14 +04:00
|
|
|
|
result = simple_2d_position(tokens)
|
|
|
|
|
if result is not None:
|
|
|
|
|
pos_x, pos_y = result
|
|
|
|
|
return 'left', pos_x, 'top', pos_y
|
|
|
|
|
|
|
|
|
|
if len(tokens) == 4:
|
|
|
|
|
keyword_1 = get_keyword(tokens[0])
|
|
|
|
|
keyword_2 = get_keyword(tokens[2])
|
|
|
|
|
length_1 = get_length(tokens[1], percentage=True)
|
|
|
|
|
length_2 = get_length(tokens[3], percentage=True)
|
|
|
|
|
if length_1 and length_2:
|
|
|
|
|
if (keyword_1 in ('left', 'right') and
|
2013-04-11 12:39:23 +04:00
|
|
|
|
keyword_2 in ('top', 'bottom')):
|
2013-03-25 21:36:14 +04:00
|
|
|
|
return keyword_1, length_1, keyword_2, length_2
|
|
|
|
|
if (keyword_2 in ('left', 'right') and
|
2013-04-11 12:39:23 +04:00
|
|
|
|
keyword_1 in ('top', 'bottom')):
|
2013-03-25 21:36:14 +04:00
|
|
|
|
return keyword_2, length_2, keyword_1, length_1
|
|
|
|
|
|
|
|
|
|
if len(tokens) == 3:
|
|
|
|
|
length = get_length(tokens[2], percentage=True)
|
|
|
|
|
if length is not None:
|
|
|
|
|
keyword = get_keyword(tokens[1])
|
|
|
|
|
other_keyword = get_keyword(tokens[0])
|
2013-03-21 17:01:12 +04:00
|
|
|
|
else:
|
2013-03-25 21:36:14 +04:00
|
|
|
|
length = get_length(tokens[1], percentage=True)
|
|
|
|
|
other_keyword = get_keyword(tokens[2])
|
|
|
|
|
keyword = get_keyword(tokens[0])
|
|
|
|
|
|
|
|
|
|
if length is not None:
|
|
|
|
|
if other_keyword == 'center':
|
|
|
|
|
if keyword in ('top', 'bottom'):
|
|
|
|
|
return 'left', FIFTY_PERCENT, keyword, length
|
|
|
|
|
if keyword in ('left', 'right'):
|
|
|
|
|
return keyword, length, 'top', FIFTY_PERCENT
|
|
|
|
|
elif (keyword in ('left', 'right') and
|
|
|
|
|
other_keyword in ('top', 'bottom')):
|
|
|
|
|
return keyword, length, other_keyword, ZERO_PERCENT
|
|
|
|
|
elif (keyword in ('top', 'bottom') and
|
|
|
|
|
other_keyword in ('left', 'right')):
|
|
|
|
|
return other_keyword, ZERO_PERCENT, keyword, length
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def simple_2d_position(tokens):
|
|
|
|
|
"""Common syntax of background-position and transform-origin."""
|
|
|
|
|
if len(tokens) == 1:
|
|
|
|
|
tokens = [tokens[0], CenterKeywordFakeToken]
|
|
|
|
|
elif len(tokens) != 2:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
token_1, token_2 = tokens
|
|
|
|
|
length_1 = get_length(token_1, percentage=True)
|
|
|
|
|
length_2 = get_length(token_2, percentage=True)
|
|
|
|
|
if length_1 and length_2:
|
|
|
|
|
return length_1, length_2
|
|
|
|
|
keyword_1, keyword_2 = map(get_keyword, tokens)
|
|
|
|
|
if length_1 and keyword_2 in ('top', 'center', 'bottom'):
|
|
|
|
|
return length_1, BACKGROUND_POSITION_PERCENTAGES[keyword_2]
|
|
|
|
|
elif length_2 and keyword_1 in ('left', 'center', 'right'):
|
|
|
|
|
return BACKGROUND_POSITION_PERCENTAGES[keyword_1], length_2
|
|
|
|
|
elif (keyword_1 in ('left', 'center', 'right') and
|
|
|
|
|
keyword_2 in ('top', 'center', 'bottom')):
|
|
|
|
|
return (BACKGROUND_POSITION_PERCENTAGES[keyword_1],
|
|
|
|
|
BACKGROUND_POSITION_PERCENTAGES[keyword_2])
|
|
|
|
|
elif (keyword_1 in ('top', 'center', 'bottom') and
|
|
|
|
|
keyword_2 in ('left', 'center', 'right')):
|
|
|
|
|
# Swap tokens. They need to be in (horizontal, vertical) order.
|
|
|
|
|
return (BACKGROUND_POSITION_PERCENTAGES[keyword_2],
|
|
|
|
|
BACKGROUND_POSITION_PERCENTAGES[keyword_1])
|
2013-03-21 17:01:12 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2013-03-19 21:29:58 +04:00
|
|
|
|
@comma_separated_list
|
2013-03-26 15:04:00 +04:00
|
|
|
|
def background_repeat(tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``background-repeat`` property validation."""
|
2013-03-26 15:04:00 +04:00
|
|
|
|
keywords = tuple(map(get_keyword, tokens))
|
|
|
|
|
if keywords == ('repeat-x',):
|
|
|
|
|
return ('repeat', 'no-repeat')
|
|
|
|
|
if keywords == ('repeat-y',):
|
|
|
|
|
return ('no-repeat', 'repeat')
|
2013-03-31 02:32:14 +04:00
|
|
|
|
if keywords in (('no-repeat',), ('repeat',), ('space',), ('round',)):
|
2013-03-26 15:04:00 +04:00
|
|
|
|
return keywords * 2
|
|
|
|
|
if len(keywords) == 2 and all(
|
2013-03-31 02:32:14 +04:00
|
|
|
|
k in ('no-repeat', 'repeat', 'space', 'round')
|
2013-03-26 15:04:00 +04:00
|
|
|
|
for k in keywords):
|
|
|
|
|
return keywords
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2012-01-31 14:45:24 +04:00
|
|
|
|
@validator()
|
2013-03-19 21:29:58 +04:00
|
|
|
|
@comma_separated_list
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def background_size(tokens):
|
2012-01-31 14:45:24 +04:00
|
|
|
|
"""Validation for ``background-size``."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if len(tokens) == 1:
|
|
|
|
|
token = tokens[0]
|
|
|
|
|
keyword = get_keyword(token)
|
2012-01-31 14:45:24 +04:00
|
|
|
|
if keyword in ('contain', 'cover'):
|
|
|
|
|
return keyword
|
|
|
|
|
if keyword == 'auto':
|
2012-04-05 13:21:26 +04:00
|
|
|
|
return ('auto', 'auto')
|
2013-03-21 15:53:53 +04:00
|
|
|
|
length = get_length(token, negative=False, percentage=True)
|
2012-04-03 16:59:06 +04:00
|
|
|
|
if length:
|
2012-04-05 13:21:26 +04:00
|
|
|
|
return (length, 'auto')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
elif len(tokens) == 2:
|
2012-04-03 16:59:06 +04:00
|
|
|
|
values = []
|
2012-04-03 14:45:29 +04:00
|
|
|
|
for token in tokens:
|
2013-03-25 22:03:40 +04:00
|
|
|
|
length = get_length(token, negative=False, percentage=True)
|
|
|
|
|
if length:
|
|
|
|
|
values.append(length)
|
|
|
|
|
elif get_keyword(token) == 'auto':
|
2012-04-05 13:21:26 +04:00
|
|
|
|
values.append('auto')
|
2012-04-03 21:02:14 +04:00
|
|
|
|
if len(values) == 2:
|
2012-04-03 16:59:06 +04:00
|
|
|
|
return tuple(values)
|
2012-01-31 14:45:24 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-04 17:05:25 +04:00
|
|
|
|
@validator('background-clip')
|
|
|
|
|
@validator('background-origin')
|
2013-03-19 21:29:58 +04:00
|
|
|
|
@comma_separated_list
|
2012-01-30 20:23:25 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def box(keyword):
|
|
|
|
|
"""Validation for the ``<box>`` type used in ``background-clip``
|
|
|
|
|
and ``background-origin``."""
|
|
|
|
|
return keyword in ('border-box', 'padding-box', 'content-box')
|
|
|
|
|
|
|
|
|
|
|
2011-11-22 16:06:50 +04:00
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def border_spacing(tokens):
|
2011-11-22 16:06:50 +04:00
|
|
|
|
"""Validator for the `border-spacing` property."""
|
2012-04-03 16:59:06 +04:00
|
|
|
|
lengths = [get_length(token, negative=False) for token in tokens]
|
|
|
|
|
if all(lengths):
|
|
|
|
|
if len(lengths) == 1:
|
|
|
|
|
return (lengths[0], lengths[0])
|
|
|
|
|
elif len(lengths) == 2:
|
|
|
|
|
return tuple(lengths)
|
2011-11-22 16:06:50 +04:00
|
|
|
|
|
|
|
|
|
|
2013-06-05 20:56:57 +04:00
|
|
|
|
@validator('border-top-right-radius')
|
|
|
|
|
@validator('border-bottom-right-radius')
|
|
|
|
|
@validator('border-bottom-left-radius')
|
|
|
|
|
@validator('border-top-left-radius')
|
|
|
|
|
def border_corner_radius(tokens):
|
|
|
|
|
"""Validator for the `border-*-radius` properties."""
|
|
|
|
|
lengths = [
|
|
|
|
|
get_length(token, negative=False, percentage=True) for token in tokens]
|
|
|
|
|
if all(lengths):
|
|
|
|
|
if len(lengths) == 1:
|
|
|
|
|
return (lengths[0], lengths[0])
|
|
|
|
|
elif len(lengths) == 2:
|
|
|
|
|
return tuple(lengths)
|
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator('border-top-style')
|
|
|
|
|
@validator('border-right-style')
|
|
|
|
|
@validator('border-left-style')
|
|
|
|
|
@validator('border-bottom-style')
|
2011-08-24 14:23:14 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def border_style(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``border-*-style`` properties validation."""
|
2011-12-13 19:40:27 +04:00
|
|
|
|
return keyword in ('none', 'hidden', 'dotted', 'dashed', 'double',
|
|
|
|
|
'inset', 'outset', 'groove', 'ridge', 'solid')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2013-04-10 21:18:46 +04:00
|
|
|
|
@validator('outline-style')
|
|
|
|
|
@single_keyword
|
|
|
|
|
def outline_style(keyword):
|
|
|
|
|
"""``outline-style`` properties validation."""
|
|
|
|
|
return keyword in ('none', 'dotted', 'dashed', 'double', 'inset',
|
|
|
|
|
'outset', 'groove', 'ridge', 'solid')
|
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator('border-top-width')
|
|
|
|
|
@validator('border-right-width')
|
|
|
|
|
@validator('border-left-width')
|
|
|
|
|
@validator('border-bottom-width')
|
2012-08-03 18:21:47 +04:00
|
|
|
|
@validator('outline-width')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def border_width(token):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``border-*-width`` properties validation."""
|
2012-04-03 16:59:06 +04:00
|
|
|
|
length = get_length(token, negative=False)
|
|
|
|
|
if length:
|
|
|
|
|
return length
|
2012-04-03 14:45:29 +04:00
|
|
|
|
keyword = get_keyword(token)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if keyword in ('thin', 'medium', 'thick'):
|
|
|
|
|
return keyword
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-11-21 17:25:43 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def box_sizing(keyword):
|
2011-12-26 15:47:26 +04:00
|
|
|
|
"""Validation for the ``box-sizing`` property from css3-ui"""
|
2012-04-06 17:56:36 +04:00
|
|
|
|
return keyword in ('padding-box', 'border-box', 'content-box')
|
2011-11-21 17:25:43 +04:00
|
|
|
|
|
|
|
|
|
|
2011-11-15 21:22:00 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def caption_side(keyword):
|
|
|
|
|
"""``caption-side`` properties validation."""
|
|
|
|
|
return keyword in ('top', 'bottom')
|
|
|
|
|
|
|
|
|
|
|
2012-05-29 18:01:59 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def clear(keyword):
|
|
|
|
|
"""``clear`` property validation."""
|
|
|
|
|
return keyword in ('left', 'right', 'both', 'none')
|
|
|
|
|
|
|
|
|
|
|
2012-02-07 21:06:59 +04:00
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def clip(token):
|
2012-02-07 21:06:59 +04:00
|
|
|
|
"""Validation for the ``clip`` property."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
function = parse_function(token)
|
2012-02-07 21:06:59 +04:00
|
|
|
|
if function:
|
|
|
|
|
name, args = function
|
|
|
|
|
if name == 'rect' and len(args) == 4:
|
2012-04-03 21:02:14 +04:00
|
|
|
|
values = []
|
2012-02-07 21:06:59 +04:00
|
|
|
|
for arg in args:
|
|
|
|
|
if get_keyword(arg) == 'auto':
|
2012-04-05 13:21:26 +04:00
|
|
|
|
values.append('auto')
|
2012-02-07 21:06:59 +04:00
|
|
|
|
else:
|
2012-04-03 16:59:06 +04:00
|
|
|
|
length = get_length(arg)
|
|
|
|
|
if length:
|
2012-04-03 21:02:14 +04:00
|
|
|
|
values.append(length)
|
|
|
|
|
if len(values) == 4:
|
|
|
|
|
return values
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if get_keyword(token) == 'auto':
|
2012-02-07 21:06:59 +04:00
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
2012-03-24 16:39:31 +04:00
|
|
|
|
@validator(wants_base_url=True)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def content(tokens, base_url):
|
2011-12-07 20:09:59 +04:00
|
|
|
|
"""``content`` property validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
keyword = get_single_keyword(tokens)
|
2011-12-02 19:51:41 +04:00
|
|
|
|
if keyword in ('normal', 'none'):
|
|
|
|
|
return keyword
|
2012-04-03 14:45:29 +04:00
|
|
|
|
parsed_tokens = [validate_content_token(base_url, v) for v in tokens]
|
|
|
|
|
if None not in parsed_tokens:
|
|
|
|
|
return parsed_tokens
|
2011-12-02 19:51:41 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def validate_content_token(base_url, token):
|
2015-04-30 22:54:19 +03:00
|
|
|
|
"""Validation for a single token for the ``content`` property.
|
2011-12-02 19:51:41 +04:00
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
Return (type, content) or False for invalid tokens.
|
2011-12-02 19:51:41 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
quote_type = CONTENT_QUOTE_KEYWORDS.get(get_keyword(token))
|
2011-12-02 21:02:58 +04:00
|
|
|
|
if quote_type is not None:
|
|
|
|
|
return ('QUOTE', quote_type)
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
type_ = token.type
|
2011-12-02 19:51:41 +04:00
|
|
|
|
if type_ == 'STRING':
|
2012-04-03 14:45:29 +04:00
|
|
|
|
return ('STRING', token.value)
|
2011-12-02 21:19:34 +04:00
|
|
|
|
if type_ == 'URI':
|
2012-05-30 22:21:36 +04:00
|
|
|
|
return ('URI', safe_urljoin(base_url, token.value))
|
2012-04-03 14:45:29 +04:00
|
|
|
|
function = parse_function(token)
|
2012-02-07 21:06:59 +04:00
|
|
|
|
if function:
|
|
|
|
|
name, args = function
|
|
|
|
|
prototype = (name, [a.type for a in args])
|
2013-04-08 12:02:37 +04:00
|
|
|
|
args = [getattr(a, 'value', a) for a in args]
|
2012-02-07 21:06:59 +04:00
|
|
|
|
if prototype == ('attr', ['IDENT']):
|
|
|
|
|
return (name, args[0])
|
|
|
|
|
elif prototype in (('counter', ['IDENT']),
|
|
|
|
|
('counters', ['IDENT', 'STRING'])):
|
|
|
|
|
args.append('decimal')
|
|
|
|
|
return (name, args)
|
|
|
|
|
elif prototype in (('counter', ['IDENT', 'IDENT']),
|
|
|
|
|
('counters', ['IDENT', 'STRING', 'IDENT'])):
|
|
|
|
|
style = args[-1]
|
|
|
|
|
if style in ('none', 'decimal') or style in counters.STYLES:
|
|
|
|
|
return (name, args)
|
2015-03-21 07:50:22 +03:00
|
|
|
|
elif prototype in (('string', ['IDENT']),
|
|
|
|
|
('string', ['IDENT', 'IDENT'])):
|
2015-03-21 07:08:12 +03:00
|
|
|
|
if len(args) > 1:
|
|
|
|
|
args[1] = args[1].lower()
|
|
|
|
|
if args[1] not in ('first', 'start', 'last', 'first-except'):
|
|
|
|
|
raise InvalidValues()
|
2015-03-09 06:02:51 +03:00
|
|
|
|
return (name, args)
|
2012-02-07 21:06:59 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def parse_function(function_token):
|
|
|
|
|
"""Return ``(name, args)`` if the given token is a function
|
2012-02-07 21:06:59 +04:00
|
|
|
|
with comma-separated arguments, or None.
|
|
|
|
|
.
|
|
|
|
|
"""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if function_token.type == 'FUNCTION':
|
|
|
|
|
content = [t for t in function_token.content if t.type != 'S']
|
2015-04-30 22:54:19 +03:00
|
|
|
|
if not content or len(content) % 2:
|
2012-03-24 16:39:31 +04:00
|
|
|
|
for token in content[1::2]:
|
|
|
|
|
if token.type != 'DELIM' or token.value != ',':
|
|
|
|
|
break
|
|
|
|
|
else:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
return function_token.function_name.lower(), content[::2]
|
2011-12-07 20:09:59 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def counter_increment(tokens):
|
2011-12-07 20:09:59 +04:00
|
|
|
|
"""``counter-increment`` property validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
return counter(tokens, default_integer=1)
|
2011-12-07 20:09:59 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def counter_reset(tokens):
|
2011-12-07 20:09:59 +04:00
|
|
|
|
"""``counter-reset`` property validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
return counter(tokens, default_integer=0)
|
2011-12-07 20:09:59 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def counter(tokens, default_integer):
|
2011-12-07 20:09:59 +04:00
|
|
|
|
"""``counter-increment`` and ``counter-reset`` properties validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if get_single_keyword(tokens) == 'none':
|
2011-12-07 20:09:59 +04:00
|
|
|
|
return []
|
2012-04-03 14:45:29 +04:00
|
|
|
|
tokens = iter(tokens)
|
|
|
|
|
token = next(tokens, None)
|
2012-04-03 21:02:14 +04:00
|
|
|
|
assert token, 'got an empty token list'
|
2011-12-07 20:09:59 +04:00
|
|
|
|
results = []
|
2012-04-03 14:45:29 +04:00
|
|
|
|
while token is not None:
|
2013-05-10 14:13:21 +04:00
|
|
|
|
if token.type != 'IDENT':
|
2011-12-07 20:09:59 +04:00
|
|
|
|
return # expected a keyword here
|
2013-05-10 14:13:21 +04:00
|
|
|
|
counter_name = token.value
|
2011-12-07 20:09:59 +04:00
|
|
|
|
if counter_name in ('none', 'initial', 'inherit'):
|
2013-02-25 17:53:30 +04:00
|
|
|
|
raise InvalidValues('Invalid counter name: ' + counter_name)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
token = next(tokens, None)
|
|
|
|
|
if token is not None and token.type == 'INTEGER':
|
|
|
|
|
# Found an integer. Use it and get the next token
|
|
|
|
|
integer = token.value
|
|
|
|
|
token = next(tokens, None)
|
2011-12-07 20:09:59 +04:00
|
|
|
|
else:
|
|
|
|
|
# Not an integer. Might be the next counter name.
|
2012-04-03 14:45:29 +04:00
|
|
|
|
# Keep `token` for the next loop iteration.
|
2011-12-07 20:09:59 +04:00
|
|
|
|
integer = default_integer
|
|
|
|
|
results.append((counter_name, integer))
|
|
|
|
|
return results
|
2011-12-02 19:51:41 +04:00
|
|
|
|
|
|
|
|
|
|
2012-05-09 15:15:13 +04:00
|
|
|
|
@validator('top')
|
|
|
|
|
@validator('right')
|
|
|
|
|
@validator('left')
|
|
|
|
|
@validator('bottom')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator('margin-top')
|
|
|
|
|
@validator('margin-right')
|
|
|
|
|
@validator('margin-bottom')
|
|
|
|
|
@validator('margin-left')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
2012-04-06 17:37:43 +04:00
|
|
|
|
def lenght_precentage_or_auto(token):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``margin-*`` properties validation."""
|
2012-04-06 17:37:43 +04:00
|
|
|
|
length = get_length(token, percentage=True)
|
2012-04-03 16:59:06 +04:00
|
|
|
|
if length:
|
|
|
|
|
return length
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if get_keyword(token) == 'auto':
|
2012-04-05 13:21:26 +04:00
|
|
|
|
return 'auto'
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-16 10:28:44 +04:00
|
|
|
|
@validator('height')
|
|
|
|
|
@validator('width')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def width_height(token):
|
2011-12-26 15:47:26 +04:00
|
|
|
|
"""Validation for the ``width`` and ``height`` properties."""
|
2012-04-06 17:37:43 +04:00
|
|
|
|
length = get_length(token, negative=False, percentage=True)
|
|
|
|
|
if length:
|
|
|
|
|
return length
|
|
|
|
|
if get_keyword(token) == 'auto':
|
|
|
|
|
return 'auto'
|
2011-08-16 10:28:44 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2011-08-24 14:23:14 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def direction(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``direction`` property validation."""
|
2011-08-24 14:23:14 +04:00
|
|
|
|
return keyword in ('ltr', 'rtl')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2011-10-08 16:41:12 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def display(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``display`` property validation."""
|
2011-11-14 17:29:40 +04:00
|
|
|
|
return keyword in (
|
2012-03-23 02:26:09 +04:00
|
|
|
|
'inline', 'block', 'inline-block', 'list-item', 'none',
|
2011-11-15 21:19:36 +04:00
|
|
|
|
'table', 'inline-table', 'table-caption',
|
2011-11-14 17:29:40 +04:00
|
|
|
|
'table-row-group', 'table-header-group', 'table-footer-group',
|
|
|
|
|
'table-row', 'table-column-group', 'table-column', 'table-cell')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2012-06-29 19:13:10 +04:00
|
|
|
|
@validator('float')
|
2012-05-29 18:01:59 +04:00
|
|
|
|
@single_keyword
|
2012-06-29 19:13:10 +04:00
|
|
|
|
def float_(keyword): # XXX do not hide the "float" builtin
|
2012-05-29 18:01:59 +04:00
|
|
|
|
"""``float`` property validation."""
|
|
|
|
|
return keyword in ('left', 'right', 'none')
|
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2013-03-19 21:29:58 +04:00
|
|
|
|
@comma_separated_list
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def font_family(tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``font-family`` property validation."""
|
2013-03-19 21:29:58 +04:00
|
|
|
|
if len(tokens) == 1 and tokens[0].type == 'STRING':
|
|
|
|
|
return tokens[0].value
|
|
|
|
|
elif tokens and all(token.type == 'IDENT' for token in tokens):
|
|
|
|
|
return ' '.join(token.value for token in tokens)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def font_size(token):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``font-size`` property validation."""
|
2013-04-02 20:32:15 +04:00
|
|
|
|
length = get_length(token, negative=False, percentage=True)
|
2012-04-03 16:59:06 +04:00
|
|
|
|
if length:
|
|
|
|
|
return length
|
2012-04-03 14:45:29 +04:00
|
|
|
|
font_size_keyword = get_keyword(token)
|
2011-08-23 19:35:10 +04:00
|
|
|
|
if font_size_keyword in ('smaller', 'larger'):
|
2011-08-18 17:44:45 +04:00
|
|
|
|
raise InvalidValues('value not supported yet')
|
2013-04-11 14:08:53 +04:00
|
|
|
|
if font_size_keyword in computed_values.FONT_SIZE_KEYWORDS:
|
|
|
|
|
# or keyword in ('smaller', 'larger')
|
2011-10-08 16:41:12 +04:00
|
|
|
|
return font_size_keyword
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2011-08-24 14:23:14 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def font_style(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``font-style`` property validation."""
|
2011-08-24 14:23:14 +04:00
|
|
|
|
return keyword in ('normal', 'italic', 'oblique')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2012-08-17 19:37:33 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def font_stretch(keyword):
|
|
|
|
|
"""Validation for the ``font-stretch`` property."""
|
|
|
|
|
return keyword in (
|
|
|
|
|
'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed',
|
|
|
|
|
'normal',
|
|
|
|
|
'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded')
|
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2011-08-24 14:23:14 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def font_variant(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``font-variant`` property validation."""
|
2011-08-24 14:23:14 +04:00
|
|
|
|
return keyword in ('normal', 'small-caps')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def font_weight(token):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``font-weight`` property validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
keyword = get_keyword(token)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if keyword in ('normal', 'bold', 'bolder', 'lighter'):
|
|
|
|
|
return keyword
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if token.type == 'INTEGER':
|
|
|
|
|
if token.value in [100, 200, 300, 400, 500, 600, 700, 800, 900]:
|
|
|
|
|
return token.value
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2013-07-26 19:26:30 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_token
|
|
|
|
|
def image_resolution(token):
|
|
|
|
|
# TODO: support 'snap' and 'from-image'
|
|
|
|
|
return get_resolution(token)
|
|
|
|
|
|
|
|
|
|
|
2012-04-04 17:05:25 +04:00
|
|
|
|
@validator('letter-spacing')
|
|
|
|
|
@validator('word-spacing')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def spacing(token):
|
2012-02-01 20:13:08 +04:00
|
|
|
|
"""Validation for ``letter-spacing`` and ``word-spacing``."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if get_keyword(token) == 'normal':
|
2012-01-26 14:33:49 +04:00
|
|
|
|
return 'normal'
|
2012-04-03 16:59:06 +04:00
|
|
|
|
length = get_length(token)
|
|
|
|
|
if length:
|
|
|
|
|
return length
|
2012-01-26 14:33:49 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def line_height(token):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``line-height`` property validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if get_keyword(token) == 'normal':
|
2011-10-08 16:41:12 +04:00
|
|
|
|
return 'normal'
|
2015-04-29 00:55:27 +03:00
|
|
|
|
if token.type in ('NUMBER', 'INTEGER', 'PERCENTAGE') and token.value >= 0:
|
2012-04-03 16:59:06 +04:00
|
|
|
|
return Dimension(token.value, token.unit)
|
2015-04-29 00:55:27 +03:00
|
|
|
|
elif token.type == 'DIMENSION' and token.value >= 0:
|
2015-04-29 09:35:24 +03:00
|
|
|
|
return get_length(token)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2011-08-24 14:23:14 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def list_style_position(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``list-style-position`` property validation."""
|
2011-08-24 14:23:14 +04:00
|
|
|
|
return keyword in ('inside', 'outside')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2011-10-08 16:41:12 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def list_style_type(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``list-style-type`` property validation."""
|
2011-12-07 20:09:59 +04:00
|
|
|
|
return keyword in ('none', 'decimal') or keyword in counters.STYLES
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator('padding-top')
|
|
|
|
|
@validator('padding-right')
|
|
|
|
|
@validator('padding-bottom')
|
|
|
|
|
@validator('padding-left')
|
2012-04-06 18:19:16 +04:00
|
|
|
|
@validator('min-width')
|
2012-04-06 18:42:06 +04:00
|
|
|
|
@validator('min-height')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def length_or_precentage(token):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``padding-*`` properties validation."""
|
2012-04-06 17:37:43 +04:00
|
|
|
|
length = get_length(token, negative=False, percentage=True)
|
2012-04-03 16:59:06 +04:00
|
|
|
|
if length:
|
|
|
|
|
return length
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-06 18:19:16 +04:00
|
|
|
|
@validator('max-width')
|
2012-04-06 18:42:06 +04:00
|
|
|
|
@validator('max-height')
|
2012-04-06 18:19:16 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def max_width_height(token):
|
|
|
|
|
"""Validation for max-width and max-height"""
|
|
|
|
|
length = get_length(token, negative=False, percentage=True)
|
|
|
|
|
if length:
|
|
|
|
|
return length
|
|
|
|
|
if get_keyword(token) == 'none':
|
|
|
|
|
return Dimension(float('inf'), 'px')
|
|
|
|
|
|
|
|
|
|
|
2012-02-07 21:26:23 +04:00
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def opacity(token):
|
2012-02-07 21:26:23 +04:00
|
|
|
|
"""Validation for the ``opacity`` property."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if token.type in ('NUMBER', 'INTEGER'):
|
|
|
|
|
return min(1, max(0, token.value))
|
2012-02-07 21:26:23 +04:00
|
|
|
|
|
|
|
|
|
|
2012-05-11 18:07:14 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_token
|
|
|
|
|
def z_index(token):
|
|
|
|
|
"""Validation for the ``z-index`` property."""
|
|
|
|
|
if get_keyword(token) == 'auto':
|
|
|
|
|
return 'auto'
|
|
|
|
|
if token.type == 'INTEGER':
|
|
|
|
|
return token.value
|
|
|
|
|
|
|
|
|
|
|
2012-03-14 22:33:24 +04:00
|
|
|
|
@validator('orphans')
|
|
|
|
|
@validator('widows')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def orphans_widows(token):
|
2012-03-14 22:33:24 +04:00
|
|
|
|
"""Validation for the ``orphans`` or ``widows`` properties."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if token.type == 'INTEGER':
|
|
|
|
|
value = token.value
|
2012-05-15 21:29:54 +04:00
|
|
|
|
if value >= 1:
|
2012-03-14 22:33:24 +04:00
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
2012-02-07 19:59:22 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def overflow(keyword):
|
|
|
|
|
"""Validation for the ``overflow`` property."""
|
|
|
|
|
return keyword in ('auto', 'visible', 'hidden', 'scroll')
|
|
|
|
|
|
|
|
|
|
|
2012-01-20 14:55:06 +04:00
|
|
|
|
@validator('page-break-before')
|
|
|
|
|
@validator('page-break-after')
|
|
|
|
|
@single_keyword
|
|
|
|
|
def page_break(keyword):
|
|
|
|
|
"""Validation for the ``page-break-before`` and ``page-break-after``
|
|
|
|
|
properties.
|
|
|
|
|
|
|
|
|
|
"""
|
2012-06-20 14:53:22 +04:00
|
|
|
|
return keyword in ('auto', 'always', 'left', 'right', 'avoid')
|
2012-01-20 14:55:06 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def page_break_inside(keyword):
|
|
|
|
|
"""Validation for the ``page-break-inside`` property."""
|
2012-03-16 19:45:31 +04:00
|
|
|
|
return keyword in ('auto', 'avoid')
|
2012-01-20 14:55:06 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2011-10-08 16:41:12 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def position(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``position`` property validation."""
|
2012-05-23 13:30:52 +04:00
|
|
|
|
return keyword in ('static', 'relative', 'absolute', 'fixed')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-12-02 21:02:58 +04:00
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def quotes(tokens):
|
2011-12-02 21:02:58 +04:00
|
|
|
|
"""``quotes`` property validation."""
|
2015-04-30 11:58:51 +03:00
|
|
|
|
if (tokens and len(tokens) % 2 == 0 and
|
|
|
|
|
all(v.type == 'STRING' for v in tokens)):
|
2013-04-11 14:08:53 +04:00
|
|
|
|
strings = [token.value for token in tokens]
|
2011-12-02 21:02:58 +04:00
|
|
|
|
# Separate open and close quotes.
|
|
|
|
|
# eg. ['«', '»', '“', '”'] -> (['«', '“'], ['»', '”'])
|
|
|
|
|
return strings[::2], strings[1::2]
|
|
|
|
|
|
|
|
|
|
|
2012-03-23 22:31:54 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def table_layout(keyword):
|
|
|
|
|
"""Validation for the ``table-layout`` property"""
|
|
|
|
|
if keyword in ('fixed', 'auto'):
|
|
|
|
|
return keyword
|
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2011-10-08 16:41:12 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def text_align(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``text-align`` property validation."""
|
2012-01-26 14:33:49 +04:00
|
|
|
|
return keyword in ('left', 'right', 'center', 'justify')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def text_decoration(tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``text-decoration`` property validation."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
keywords = [get_keyword(v) for v in tokens]
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if keywords == ['none']:
|
|
|
|
|
return 'none'
|
|
|
|
|
if all(keyword in ('underline', 'overline', 'line-through', 'blink')
|
|
|
|
|
for keyword in keywords):
|
2012-04-03 21:02:14 +04:00
|
|
|
|
unique = set(keywords)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if len(unique) == len(keywords):
|
|
|
|
|
# No duplicate
|
2012-04-03 21:02:14 +04:00
|
|
|
|
# blink is accepted but ignored
|
|
|
|
|
# "Conforming user agents may simply not blink the text."
|
|
|
|
|
return frozenset(unique - set(['blink']))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-10-21 14:22:19 +04:00
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def text_indent(token):
|
2011-10-21 14:22:19 +04:00
|
|
|
|
"""``text-indent`` property validation."""
|
2012-04-03 16:59:06 +04:00
|
|
|
|
length = get_length(token, percentage=True)
|
|
|
|
|
if length:
|
|
|
|
|
return length
|
2011-10-21 14:22:19 +04:00
|
|
|
|
|
|
|
|
|
|
2011-10-21 13:36:01 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def text_transform(keyword):
|
|
|
|
|
"""``text-align`` property validation."""
|
|
|
|
|
return keyword in ('none', 'uppercase', 'lowercase', 'capitalize')
|
|
|
|
|
|
|
|
|
|
|
2011-10-19 20:18:24 +04:00
|
|
|
|
@validator()
|
2012-04-03 14:45:29 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def vertical_align(token):
|
2011-12-26 15:47:26 +04:00
|
|
|
|
"""Validation for the ``vertical-align`` property"""
|
2012-04-03 16:59:06 +04:00
|
|
|
|
length = get_length(token, percentage=True)
|
|
|
|
|
if length:
|
|
|
|
|
return length
|
2012-04-03 14:45:29 +04:00
|
|
|
|
keyword = get_keyword(token)
|
2011-11-24 14:00:33 +04:00
|
|
|
|
if keyword in ('baseline', 'middle', 'sub', 'super',
|
2011-11-24 15:40:52 +04:00
|
|
|
|
'text-top', 'text-bottom', 'top', 'bottom'):
|
2011-10-19 20:18:24 +04:00
|
|
|
|
return keyword
|
|
|
|
|
|
|
|
|
|
|
2011-10-21 13:15:06 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def visibility(keyword):
|
|
|
|
|
"""``white-space`` property validation."""
|
|
|
|
|
return keyword in ('visible', 'hidden', 'collapse')
|
|
|
|
|
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@validator()
|
2011-08-24 14:23:14 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def white_space(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``white-space`` property validation."""
|
2011-08-24 14:23:14 +04:00
|
|
|
|
return keyword in ('normal', 'pre', 'nowrap', 'pre-wrap', 'pre-line')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2014-04-27 15:29:55 +04:00
|
|
|
|
|
2013-09-30 22:16:03 +04:00
|
|
|
|
@validator()
|
|
|
|
|
@single_keyword
|
|
|
|
|
def overflow_wrap(keyword):
|
|
|
|
|
"""``overflow-wrap`` property validation."""
|
|
|
|
|
return keyword in ('normal', 'break-word')
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
@validator(unprefixed=True)
|
2012-01-31 14:45:24 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def image_rendering(keyword):
|
|
|
|
|
"""Validation for ``image-rendering``."""
|
2012-04-03 16:59:06 +04:00
|
|
|
|
return keyword in ('auto', 'optimizespeed', 'optimizequality')
|
2012-01-31 14:45:24 +04:00
|
|
|
|
|
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
@validator(unprefixed=True)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def size(tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``size`` property validation.
|
|
|
|
|
|
|
|
|
|
See http://www.w3.org/TR/css3-page/#page-size-prop
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
"""
|
2012-04-03 16:59:06 +04:00
|
|
|
|
lengths = [get_length(token, negative=False) for token in tokens]
|
|
|
|
|
if all(lengths):
|
|
|
|
|
if len(lengths) == 1:
|
|
|
|
|
return (lengths[0], lengths[0])
|
|
|
|
|
elif len(lengths) == 2:
|
|
|
|
|
return tuple(lengths)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
keywords = [get_keyword(v) for v in tokens]
|
2011-12-16 18:06:59 +04:00
|
|
|
|
if len(keywords) == 1:
|
|
|
|
|
keyword = keywords[0]
|
2012-04-03 16:59:06 +04:00
|
|
|
|
if keyword in computed_values.PAGE_SIZES:
|
2011-12-16 18:06:59 +04:00
|
|
|
|
return computed_values.PAGE_SIZES[keyword]
|
2012-04-03 16:59:06 +04:00
|
|
|
|
elif keyword in ('auto', 'portrait'):
|
|
|
|
|
return computed_values.INITIAL_PAGE_SIZE
|
|
|
|
|
elif keyword == 'landscape':
|
|
|
|
|
return computed_values.INITIAL_PAGE_SIZE[::-1]
|
2011-12-16 18:06:59 +04:00
|
|
|
|
|
|
|
|
|
if len(keywords) == 2:
|
|
|
|
|
if keywords[0] in ('portrait', 'landscape'):
|
2011-12-26 15:47:26 +04:00
|
|
|
|
orientation, page_size = keywords
|
2011-12-16 18:06:59 +04:00
|
|
|
|
elif keywords[1] in ('portrait', 'landscape'):
|
2011-12-26 15:47:26 +04:00
|
|
|
|
page_size, orientation = keywords
|
2011-12-16 18:06:59 +04:00
|
|
|
|
else:
|
2011-12-26 15:47:26 +04:00
|
|
|
|
page_size = None
|
|
|
|
|
if page_size in computed_values.PAGE_SIZES:
|
|
|
|
|
width_height = computed_values.PAGE_SIZES[page_size]
|
2011-12-16 18:06:59 +04:00
|
|
|
|
if orientation == 'portrait':
|
2011-12-26 15:47:26 +04:00
|
|
|
|
return width_height
|
2011-12-16 18:06:59 +04:00
|
|
|
|
else:
|
2011-12-26 15:47:26 +04:00
|
|
|
|
height, width = width_height
|
2011-12-16 18:06:59 +04:00
|
|
|
|
return width, height
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
@validator(prefixed=True) # Non-standard
|
2012-05-14 20:31:51 +04:00
|
|
|
|
@single_token
|
2012-05-15 21:29:54 +04:00
|
|
|
|
def anchor(token):
|
|
|
|
|
"""Validation for ``anchor``."""
|
2012-05-14 20:31:51 +04:00
|
|
|
|
if get_keyword(token) == 'none':
|
|
|
|
|
return 'none'
|
|
|
|
|
function = parse_function(token)
|
|
|
|
|
if function:
|
|
|
|
|
name, args = function
|
|
|
|
|
prototype = (name, [a.type for a in args])
|
2013-04-08 12:02:37 +04:00
|
|
|
|
args = [getattr(a, 'value', a) for a in args]
|
2012-05-14 20:31:51 +04:00
|
|
|
|
if prototype == ('attr', ['IDENT']):
|
|
|
|
|
return (name, args[0])
|
|
|
|
|
|
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
@validator(prefixed=True, wants_base_url=True) # Non-standard
|
2012-05-14 21:40:38 +04:00
|
|
|
|
@single_token
|
2012-05-15 21:29:54 +04:00
|
|
|
|
def link(token, base_url):
|
|
|
|
|
"""Validation for ``link``."""
|
2012-05-14 21:40:38 +04:00
|
|
|
|
if get_keyword(token) == 'none':
|
|
|
|
|
return 'none'
|
2012-05-15 21:29:54 +04:00
|
|
|
|
elif token.type == 'URI':
|
2012-05-30 22:21:36 +04:00
|
|
|
|
if token.value.startswith('#'):
|
|
|
|
|
return 'internal', unquote(token.value[1:])
|
|
|
|
|
else:
|
2013-03-18 15:09:57 +04:00
|
|
|
|
return 'external', safe_urljoin(base_url, token.value)
|
2012-05-14 21:40:38 +04:00
|
|
|
|
function = parse_function(token)
|
|
|
|
|
if function:
|
|
|
|
|
name, args = function
|
|
|
|
|
prototype = (name, [a.type for a in args])
|
2013-04-08 12:02:37 +04:00
|
|
|
|
args = [getattr(a, 'value', a) for a in args]
|
2012-05-14 21:40:38 +04:00
|
|
|
|
if prototype == ('attr', ['IDENT']):
|
|
|
|
|
return (name, args[0])
|
2012-05-15 21:29:54 +04:00
|
|
|
|
|
|
|
|
|
|
2012-12-09 01:50:08 +04:00
|
|
|
|
@validator(prefixed=True) # Non-standard
|
|
|
|
|
@single_token
|
|
|
|
|
def hyphens(token):
|
|
|
|
|
"""Validation for ``hyphens``."""
|
|
|
|
|
keyword = get_keyword(token)
|
|
|
|
|
if keyword in ('none', 'manual', 'auto'):
|
2012-12-09 03:41:12 +04:00
|
|
|
|
return keyword
|
2012-12-09 01:50:08 +04:00
|
|
|
|
|
|
|
|
|
|
2013-03-02 05:45:48 +04:00
|
|
|
|
@validator(prefixed=True) # Non-standard
|
|
|
|
|
@single_token
|
|
|
|
|
def hyphenate_character(token):
|
|
|
|
|
"""Validation for ``hyphenate-character``."""
|
|
|
|
|
keyword = get_keyword(token)
|
|
|
|
|
if keyword == 'auto':
|
|
|
|
|
return '‐'
|
|
|
|
|
elif token.type == 'STRING':
|
2012-12-09 01:50:08 +04:00
|
|
|
|
return token.value
|
|
|
|
|
|
|
|
|
|
|
2013-03-02 06:42:36 +04:00
|
|
|
|
@validator(prefixed=True) # Non-standard
|
|
|
|
|
@single_token
|
|
|
|
|
def hyphenate_limit_zone(token):
|
|
|
|
|
"""Validation for ``hyphenate-limit-zone``."""
|
|
|
|
|
return get_length(token, negative=False, percentage=True)
|
|
|
|
|
|
|
|
|
|
|
2013-03-02 07:41:32 +04:00
|
|
|
|
@validator(prefixed=True) # Non-standard
|
|
|
|
|
def hyphenate_limit_chars(tokens):
|
|
|
|
|
"""Validation for ``hyphenate-limit-chars``."""
|
|
|
|
|
if len(tokens) == 1:
|
|
|
|
|
token, = tokens
|
|
|
|
|
keyword = get_keyword(token)
|
|
|
|
|
if keyword == 'auto':
|
|
|
|
|
return (5, 2, 2)
|
|
|
|
|
elif token.type == 'INTEGER':
|
|
|
|
|
return (token.value, 2, 2)
|
|
|
|
|
elif len(tokens) == 2:
|
|
|
|
|
total, left = tokens
|
|
|
|
|
total_keyword = get_keyword(total)
|
|
|
|
|
left_keyword = get_keyword(left)
|
|
|
|
|
if total.type == 'INTEGER':
|
|
|
|
|
if left.type == 'INTEGER':
|
|
|
|
|
return (total.value, left.value, left.value)
|
|
|
|
|
elif left_keyword == 'auto':
|
|
|
|
|
return (total.value, 2, 2)
|
|
|
|
|
elif total_keyword == 'auto':
|
|
|
|
|
if left.type == 'INTEGER':
|
|
|
|
|
return (5, left.value, left.value)
|
|
|
|
|
elif left_keyword == 'auto':
|
|
|
|
|
return (5, 2, 2)
|
|
|
|
|
elif len(tokens) == 3:
|
|
|
|
|
total, left, right = tokens
|
2013-04-11 14:08:53 +04:00
|
|
|
|
if (
|
|
|
|
|
(get_keyword(total) == 'auto' or total.type == 'INTEGER') and
|
2013-03-02 07:41:32 +04:00
|
|
|
|
(get_keyword(left) == 'auto' or left.type == 'INTEGER') and
|
2013-04-11 14:08:53 +04:00
|
|
|
|
(get_keyword(right) == 'auto' or right.type == 'INTEGER')
|
|
|
|
|
):
|
2013-03-02 07:41:32 +04:00
|
|
|
|
total = total.value if total.type == 'INTEGER' else 5
|
|
|
|
|
left = left.value if left.type == 'INTEGER' else 2
|
|
|
|
|
right = right.value if right.type == 'INTEGER' else 2
|
|
|
|
|
return (total, left, right)
|
|
|
|
|
|
|
|
|
|
|
2012-12-09 03:41:12 +04:00
|
|
|
|
@validator(prefixed=True) # Non-standard
|
|
|
|
|
@single_token
|
|
|
|
|
def lang(token):
|
|
|
|
|
"""Validation for ``lang``."""
|
|
|
|
|
if get_keyword(token) == 'none':
|
|
|
|
|
return 'none'
|
|
|
|
|
function = parse_function(token)
|
|
|
|
|
if function:
|
|
|
|
|
name, args = function
|
|
|
|
|
prototype = (name, [a.type for a in args])
|
2013-04-08 12:02:37 +04:00
|
|
|
|
args = [getattr(a, 'value', a) for a in args]
|
2012-12-09 03:41:12 +04:00
|
|
|
|
if prototype == ('attr', ['IDENT']):
|
|
|
|
|
return (name, args[0])
|
|
|
|
|
elif token.type == 'STRING':
|
|
|
|
|
return ('string', token.value)
|
|
|
|
|
|
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
@validator(prefixed=True) # CSS3 GCPM, experimental
|
2015-04-30 22:54:19 +03:00
|
|
|
|
def bookmark_label(tokens):
|
2012-05-15 21:29:54 +04:00
|
|
|
|
"""Validation for ``bookmark-label``."""
|
2015-04-30 22:54:19 +03:00
|
|
|
|
parsed_tokens = [validate_content_list_token(v) for v in tokens]
|
|
|
|
|
if None not in parsed_tokens:
|
|
|
|
|
return parsed_tokens
|
2012-05-15 21:29:54 +04:00
|
|
|
|
|
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
@validator(prefixed=True) # CSS3 GCPM, experimental
|
2012-05-15 21:29:54 +04:00
|
|
|
|
@single_token
|
|
|
|
|
def bookmark_level(token):
|
|
|
|
|
"""Validation for ``bookmark-level``."""
|
|
|
|
|
if token.type == 'INTEGER':
|
|
|
|
|
value = token.value
|
|
|
|
|
if value >= 1:
|
|
|
|
|
return value
|
|
|
|
|
elif get_keyword(token) == 'none':
|
|
|
|
|
return 'none'
|
2012-05-14 21:40:38 +04:00
|
|
|
|
|
|
|
|
|
|
2015-03-09 06:02:51 +03:00
|
|
|
|
@validator(prefixed=True) # CSS3 GCPM, experimental
|
2015-04-30 22:54:19 +03:00
|
|
|
|
@comma_separated_list
|
2015-03-09 06:02:51 +03:00
|
|
|
|
def string_set(tokens):
|
|
|
|
|
"""Validation for ``string-set``."""
|
2015-04-30 22:54:19 +03:00
|
|
|
|
if len(tokens) >= 2:
|
2015-03-21 07:08:12 +03:00
|
|
|
|
var_name = get_keyword(tokens[0])
|
2015-04-30 22:54:19 +03:00
|
|
|
|
parsed_tokens = [validate_content_list_token(v) for v in tokens[1:]]
|
|
|
|
|
if None not in parsed_tokens:
|
|
|
|
|
return (var_name, parsed_tokens)
|
2015-04-30 11:58:51 +03:00
|
|
|
|
elif tokens and tokens[0].value == 'none':
|
2015-03-21 07:08:12 +03:00
|
|
|
|
return 'none'
|
2015-04-30 22:54:19 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_content_list_token(token):
|
|
|
|
|
"""Validation for a single token of <content-list> used in GCPM.
|
|
|
|
|
|
|
|
|
|
Return (type, content) or False for invalid tokens.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
type_ = token.type
|
|
|
|
|
if type_ == 'STRING':
|
|
|
|
|
return ('STRING', token.value)
|
|
|
|
|
function = parse_function(token)
|
|
|
|
|
if function:
|
|
|
|
|
name, args = function
|
|
|
|
|
prototype = (name, [a.type for a in args])
|
|
|
|
|
args = [getattr(a, 'value', a) for a in args]
|
|
|
|
|
if prototype == ('attr', ['IDENT']):
|
|
|
|
|
return (name, args[0])
|
|
|
|
|
elif prototype in (('content', []), ('content', ['IDENT'])):
|
|
|
|
|
if not args:
|
|
|
|
|
return (name, 'text')
|
|
|
|
|
elif args[0] in ('text', 'after', 'before'):
|
|
|
|
|
# TODO: first-letter should be allowed here too
|
|
|
|
|
return (name, args[0])
|
|
|
|
|
elif prototype in (('counter', ['IDENT']),
|
|
|
|
|
('counters', ['IDENT', 'STRING'])):
|
|
|
|
|
args.append('decimal')
|
|
|
|
|
return (name, args)
|
|
|
|
|
elif prototype in (('counter', ['IDENT', 'IDENT']),
|
|
|
|
|
('counters', ['IDENT', 'STRING', 'IDENT'])):
|
|
|
|
|
style = args[-1]
|
|
|
|
|
if style in ('none', 'decimal') or style in counters.STYLES:
|
|
|
|
|
return (name, args)
|
2015-03-09 06:02:51 +03:00
|
|
|
|
|
|
|
|
|
|
2012-07-04 13:27:20 +04:00
|
|
|
|
@validator(unprefixed=True)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def transform(tokens):
|
|
|
|
|
if get_single_keyword(tokens) == 'none':
|
2012-04-03 16:59:06 +04:00
|
|
|
|
return []
|
2012-02-08 18:44:03 +04:00
|
|
|
|
else:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
return [transform_function(v) for v in tokens]
|
2012-02-08 18:44:03 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def transform_function(token):
|
|
|
|
|
function = parse_function(token)
|
2012-02-08 18:44:03 +04:00
|
|
|
|
if not function:
|
|
|
|
|
raise InvalidValues
|
2012-03-25 01:10:38 +04:00
|
|
|
|
name, args = function
|
2012-02-08 18:44:03 +04:00
|
|
|
|
|
|
|
|
|
if len(args) == 1:
|
2012-04-03 16:59:06 +04:00
|
|
|
|
angle = get_angle(args[0])
|
|
|
|
|
length = get_length(args[0], percentage=True)
|
|
|
|
|
if name in ('rotate', 'skewx', 'skewy') and angle:
|
|
|
|
|
return name, angle
|
|
|
|
|
elif name in ('translatex', 'translate') and length:
|
|
|
|
|
return 'translate', (length, computed_values.ZERO_PIXELS)
|
|
|
|
|
elif name == 'translatey' and length:
|
|
|
|
|
return 'translate', (computed_values.ZERO_PIXELS, length)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
elif name == 'scalex' and args[0].type in ('NUMBER', 'INTEGER'):
|
2012-02-08 18:44:03 +04:00
|
|
|
|
return 'scale', (args[0].value, 1)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
elif name == 'scaley' and args[0].type in ('NUMBER', 'INTEGER'):
|
2012-02-08 18:44:03 +04:00
|
|
|
|
return 'scale', (1, args[0].value)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
elif name == 'scale' and args[0].type in ('NUMBER', 'INTEGER'):
|
2012-02-08 23:56:23 +04:00
|
|
|
|
return 'scale', (args[0].value,) * 2
|
2012-02-08 18:44:03 +04:00
|
|
|
|
elif len(args) == 2:
|
2012-03-24 16:39:31 +04:00
|
|
|
|
if name == 'scale' and all(a.type in ('NUMBER', 'INTEGER')
|
|
|
|
|
for a in args):
|
2012-02-08 18:44:03 +04:00
|
|
|
|
return name, [arg.value for arg in args]
|
2012-04-03 21:02:14 +04:00
|
|
|
|
lengths = tuple(get_length(token, percentage=True) for token in args)
|
2012-04-03 16:59:06 +04:00
|
|
|
|
if name == 'translate' and all(lengths):
|
|
|
|
|
return name, lengths
|
2012-02-08 18:44:03 +04:00
|
|
|
|
elif len(args) == 6 and name == 'matrix' and all(
|
2012-03-24 16:39:31 +04:00
|
|
|
|
a.type in ('NUMBER', 'INTEGER') for a in args):
|
2012-02-08 18:44:03 +04:00
|
|
|
|
return name, [arg.value for arg in args]
|
|
|
|
|
raise InvalidValues
|
|
|
|
|
|
|
|
|
|
|
2011-08-23 19:35:10 +04:00
|
|
|
|
# Expanders
|
|
|
|
|
|
2011-10-08 17:46:41 +04:00
|
|
|
|
# Let's be consistent, always use ``name`` as an argument even
|
|
|
|
|
# when it is useless.
|
2011-08-23 19:35:10 +04:00
|
|
|
|
# pylint: disable=W0613
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
def expander(property_name):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Decorator adding a function to the ``EXPANDERS``."""
|
|
|
|
|
def expander_decorator(function):
|
|
|
|
|
"""Add ``function`` to the ``EXPANDERS``."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
assert property_name not in EXPANDERS, property_name
|
|
|
|
|
EXPANDERS[property_name] = function
|
|
|
|
|
return function
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return expander_decorator
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expander('border-color')
|
|
|
|
|
@expander('border-style')
|
|
|
|
|
@expander('border-width')
|
|
|
|
|
@expander('margin')
|
|
|
|
|
@expander('padding')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def expand_four_sides(base_url, name, tokens):
|
|
|
|
|
"""Expand properties setting a token for the four sides of a box."""
|
|
|
|
|
# Make sure we have 4 tokens
|
|
|
|
|
if len(tokens) == 1:
|
|
|
|
|
tokens *= 4
|
|
|
|
|
elif len(tokens) == 2:
|
|
|
|
|
tokens *= 2 # (bottom, left) defaults to (top, right)
|
|
|
|
|
elif len(tokens) == 3:
|
|
|
|
|
tokens.append(tokens[1]) # left defaults to right
|
|
|
|
|
elif len(tokens) != 4:
|
2011-08-23 19:35:10 +04:00
|
|
|
|
raise InvalidValues(
|
2012-04-03 14:45:29 +04:00
|
|
|
|
'Expected 1 to 4 token components got %i' % len(tokens))
|
2012-04-04 17:05:25 +04:00
|
|
|
|
for suffix, token in zip(('-top', '-right', '-bottom', '-left'), tokens):
|
|
|
|
|
i = name.rfind('-')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
if i == -1:
|
|
|
|
|
new_name = name + suffix
|
|
|
|
|
else:
|
|
|
|
|
# eg. border-color becomes border-*-color, not border-color-*
|
|
|
|
|
new_name = name[:i] + suffix + name[i:]
|
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
|
# validate_non_shorthand returns [(name, value)], we want
|
|
|
|
|
# to yield (name, value)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
result, = validate_non_shorthand(
|
2012-04-03 14:45:29 +04:00
|
|
|
|
base_url, new_name, [token], required=True)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
yield result
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2013-06-05 20:56:57 +04:00
|
|
|
|
@expander('border-radius')
|
|
|
|
|
def border_radius(base_url, name, tokens):
|
|
|
|
|
"""Validator for the `border-radius` property."""
|
|
|
|
|
current = horizontal = []
|
|
|
|
|
vertical = []
|
|
|
|
|
for token in tokens:
|
|
|
|
|
if token.type == 'DELIM' and token.value == '/':
|
|
|
|
|
if current is horizontal:
|
|
|
|
|
if token == tokens[-1]:
|
|
|
|
|
raise InvalidValues('Expected value after "/" separator')
|
|
|
|
|
else:
|
|
|
|
|
current = vertical
|
|
|
|
|
else:
|
|
|
|
|
raise InvalidValues('Expected only one "/" separator')
|
|
|
|
|
else:
|
2013-06-13 18:55:40 +04:00
|
|
|
|
current.append(token)
|
2013-06-05 20:56:57 +04:00
|
|
|
|
|
|
|
|
|
if not vertical:
|
|
|
|
|
vertical = horizontal[:]
|
|
|
|
|
|
|
|
|
|
for values in horizontal, vertical:
|
|
|
|
|
# Make sure we have 4 tokens
|
|
|
|
|
if len(values) == 1:
|
|
|
|
|
values *= 4
|
|
|
|
|
elif len(values) == 2:
|
|
|
|
|
values *= 2 # (br, bl) defaults to (tl, tr)
|
|
|
|
|
elif len(values) == 3:
|
|
|
|
|
values.append(values[1]) # bl defaults to tr
|
|
|
|
|
elif len(values) != 4:
|
|
|
|
|
raise InvalidValues(
|
|
|
|
|
'Expected 1 to 4 token components got %i' % len(values))
|
|
|
|
|
corners = ('top-left', 'top-right', 'bottom-right', 'bottom-left')
|
|
|
|
|
for corner, tokens in zip(corners, zip(horizontal, vertical)):
|
|
|
|
|
new_name = 'border-%s-radius' % corner
|
|
|
|
|
# validate_non_shorthand returns [(name, value)], we want
|
|
|
|
|
# to yield (name, value)
|
|
|
|
|
result, = validate_non_shorthand(
|
|
|
|
|
base_url, new_name, tokens, required=True)
|
|
|
|
|
yield result
|
|
|
|
|
|
|
|
|
|
|
2012-03-24 16:39:31 +04:00
|
|
|
|
def generic_expander(*expanded_names, **kwargs):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Decorator helping expanders to handle ``inherit`` and ``initial``.
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
Wrap an expander so that it does not have to handle the 'inherit' and
|
|
|
|
|
'initial' cases, and can just yield name suffixes. Missing suffixes
|
|
|
|
|
get the initial value.
|
2011-08-23 19:35:10 +04:00
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
"""
|
2012-03-24 16:39:31 +04:00
|
|
|
|
wants_base_url = kwargs.pop('wants_base_url', False)
|
|
|
|
|
assert not kwargs
|
2013-04-11 14:08:53 +04:00
|
|
|
|
|
2011-08-23 19:35:10 +04:00
|
|
|
|
def generic_expander_decorator(wrapped):
|
|
|
|
|
"""Decorate the ``wrapped`` expander."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@functools.wraps(wrapped)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def generic_expander_wrapper(base_url, name, tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Wrap the expander."""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
keyword = get_single_keyword(tokens)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if keyword in ('inherit', 'initial'):
|
|
|
|
|
results = dict.fromkeys(expanded_names, keyword)
|
|
|
|
|
skip_validation = True
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
2011-10-08 16:41:12 +04:00
|
|
|
|
skip_validation = False
|
2011-08-16 04:47:01 +04:00
|
|
|
|
results = {}
|
2012-03-24 16:39:31 +04:00
|
|
|
|
if wants_base_url:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
result = wrapped(name, tokens, base_url)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
else:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
result = wrapped(name, tokens)
|
|
|
|
|
for new_name, new_token in result:
|
2011-08-16 11:08:59 +04:00
|
|
|
|
assert new_name in expanded_names, new_name
|
2012-03-21 16:57:33 +04:00
|
|
|
|
if new_name in results:
|
|
|
|
|
raise InvalidValues(
|
|
|
|
|
'got multiple %s values in a %s shorthand'
|
2012-04-04 17:05:25 +04:00
|
|
|
|
% (new_name.strip('-'), name))
|
2012-04-03 14:45:29 +04:00
|
|
|
|
results[new_name] = new_token
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
for new_name in expanded_names:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
if new_name.startswith('-'):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
# new_name is a suffix
|
|
|
|
|
actual_new_name = name + new_name
|
|
|
|
|
else:
|
|
|
|
|
actual_new_name = new_name
|
|
|
|
|
|
|
|
|
|
if new_name in results:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
value = results[new_name]
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if not skip_validation:
|
|
|
|
|
# validate_non_shorthand returns [(name, value)]
|
2012-04-03 14:45:29 +04:00
|
|
|
|
(actual_new_name, value), = validate_non_shorthand(
|
|
|
|
|
base_url, actual_new_name, value, required=True)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
2013-03-20 16:06:42 +04:00
|
|
|
|
value = 'initial'
|
2011-10-08 16:41:12 +04:00
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
yield actual_new_name, value
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return generic_expander_wrapper
|
|
|
|
|
return generic_expander_decorator
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expander('list-style')
|
2012-03-24 16:39:31 +04:00
|
|
|
|
@generic_expander('-type', '-position', '-image', wants_base_url=True)
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def expand_list_style(name, tokens, base_url):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Expand the ``list-style`` shorthand property.
|
|
|
|
|
|
|
|
|
|
See http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2011-08-16 11:08:59 +04:00
|
|
|
|
type_specified = image_specified = False
|
|
|
|
|
none_count = 0
|
2012-04-03 14:45:29 +04:00
|
|
|
|
for token in tokens:
|
|
|
|
|
if get_keyword(token) == 'none':
|
2011-08-16 11:08:59 +04:00
|
|
|
|
# Can be either -style or -image, see at the end which is not
|
|
|
|
|
# otherwise specified.
|
|
|
|
|
none_count += 1
|
2012-04-03 14:45:29 +04:00
|
|
|
|
none_token = token
|
2011-08-16 11:08:59 +04:00
|
|
|
|
continue
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if list_style_type([token]) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-type'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
type_specified = True
|
2012-04-03 14:45:29 +04:00
|
|
|
|
elif list_style_position([token]) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-position'
|
2013-04-04 21:48:23 +04:00
|
|
|
|
elif image_url([token], base_url) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-image'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
image_specified = True
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
2011-08-16 11:08:59 +04:00
|
|
|
|
raise InvalidValues
|
2012-04-03 14:45:29 +04:00
|
|
|
|
yield suffix, [token]
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2011-08-16 11:08:59 +04:00
|
|
|
|
if not type_specified and none_count:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
yield '-type', [none_token]
|
2011-08-16 11:08:59 +04:00
|
|
|
|
none_count -= 1
|
|
|
|
|
|
|
|
|
|
if not image_specified and none_count:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
yield '-image', [none_token]
|
2011-08-16 11:08:59 +04:00
|
|
|
|
none_count -= 1
|
|
|
|
|
|
|
|
|
|
if none_count:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
# Too many none tokens.
|
2011-08-16 11:08:59 +04:00
|
|
|
|
raise InvalidValues
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
@expander('border')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def expand_border(base_url, name, tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Expand the ``border`` shorthand property.
|
|
|
|
|
|
|
|
|
|
See http://www.w3.org/TR/CSS21/box.html#propdef-border
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2012-04-04 17:05:25 +04:00
|
|
|
|
for suffix in ('-top', '-right', '-bottom', '-left'):
|
2012-04-03 14:45:29 +04:00
|
|
|
|
for new_prop in expand_border_side(base_url, name + suffix, tokens):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
yield new_prop
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expander('border-top')
|
|
|
|
|
@expander('border-right')
|
|
|
|
|
@expander('border-bottom')
|
|
|
|
|
@expander('border-left')
|
2012-08-03 18:21:47 +04:00
|
|
|
|
@expander('outline')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@generic_expander('-width', '-color', '-style')
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def expand_border_side(name, tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Expand the ``border-*`` shorthand properties.
|
|
|
|
|
|
|
|
|
|
See http://www.w3.org/TR/CSS21/box.html#propdef-border-top
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
for token in tokens:
|
|
|
|
|
if parse_color(token) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-color'
|
2012-04-03 14:45:29 +04:00
|
|
|
|
elif border_width([token]) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-width'
|
2012-04-03 14:45:29 +04:00
|
|
|
|
elif border_style([token]) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-style'
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
2011-08-16 11:08:59 +04:00
|
|
|
|
raise InvalidValues
|
2012-04-03 14:45:29 +04:00
|
|
|
|
yield suffix, [token]
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expander('background')
|
2013-03-20 16:06:42 +04:00
|
|
|
|
def expand_background(base_url, name, tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Expand the ``background`` shorthand property.
|
|
|
|
|
|
2013-03-19 21:29:58 +04:00
|
|
|
|
See http://dev.w3.org/csswg/css3-background/#the-background
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2013-03-20 16:06:42 +04:00
|
|
|
|
properties = [
|
|
|
|
|
'background_color', 'background_image', 'background_repeat',
|
|
|
|
|
'background_attachment', 'background_position', 'background_size',
|
|
|
|
|
'background_clip', 'background_origin']
|
|
|
|
|
keyword = get_single_keyword(tokens)
|
|
|
|
|
if keyword in ('initial', 'inherit'):
|
|
|
|
|
for name in properties:
|
|
|
|
|
yield name, keyword
|
|
|
|
|
return
|
|
|
|
|
|
2013-03-20 18:28:04 +04:00
|
|
|
|
def parse_layer(tokens, final_layer=False):
|
2013-03-20 16:06:42 +04:00
|
|
|
|
results = {}
|
2013-04-11 14:08:53 +04:00
|
|
|
|
|
2013-03-20 16:06:42 +04:00
|
|
|
|
def add(name, value):
|
|
|
|
|
if value is None:
|
|
|
|
|
return False
|
|
|
|
|
name = 'background_' + name
|
|
|
|
|
if name in results:
|
|
|
|
|
raise InvalidValues
|
|
|
|
|
results[name] = value
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# Make `tokens` a stack
|
2013-03-20 18:28:04 +04:00
|
|
|
|
tokens = tokens[::-1]
|
2013-03-20 16:06:42 +04:00
|
|
|
|
while tokens:
|
2013-04-11 14:08:53 +04:00
|
|
|
|
if add('repeat',
|
|
|
|
|
background_repeat.single_value(tokens[-2:][::-1])):
|
2013-03-26 15:04:00 +04:00
|
|
|
|
del tokens[-2:]
|
|
|
|
|
continue
|
2013-03-25 21:36:14 +04:00
|
|
|
|
token = tokens[-1:]
|
2015-04-30 11:58:51 +03:00
|
|
|
|
if final_layer and add('color', other_colors(token)):
|
|
|
|
|
tokens.pop()
|
|
|
|
|
continue
|
|
|
|
|
if add('image', background_image.single_value(token, base_url)):
|
|
|
|
|
tokens.pop()
|
|
|
|
|
continue
|
|
|
|
|
if add('repeat', background_repeat.single_value(token)):
|
|
|
|
|
tokens.pop()
|
|
|
|
|
continue
|
|
|
|
|
if add('attachment', background_attachment.single_value(token)):
|
2013-03-25 21:36:14 +04:00
|
|
|
|
tokens.pop()
|
|
|
|
|
continue
|
|
|
|
|
for n in (4, 3, 2, 1)[-len(tokens):]:
|
|
|
|
|
n_tokens = tokens[-n:][::-1]
|
|
|
|
|
position = background_position.single_value(n_tokens)
|
|
|
|
|
if position is not None:
|
|
|
|
|
assert add('position', position)
|
|
|
|
|
del tokens[-n:]
|
2015-04-30 11:58:51 +03:00
|
|
|
|
if (tokens and tokens[-1].type == 'DELIM' and
|
|
|
|
|
tokens[-1].value == '/'):
|
2013-03-25 22:03:40 +04:00
|
|
|
|
for n in (3, 2)[-len(tokens):]:
|
|
|
|
|
# n includes the '/' delimiter.
|
|
|
|
|
n_tokens = tokens[-n:-1][::-1]
|
|
|
|
|
size = background_size.single_value(n_tokens)
|
|
|
|
|
if size is not None:
|
|
|
|
|
assert add('size', size)
|
|
|
|
|
del tokens[-n:]
|
2013-03-25 21:36:14 +04:00
|
|
|
|
break
|
|
|
|
|
if position is not None:
|
2013-03-20 16:06:42 +04:00
|
|
|
|
continue
|
2013-03-25 21:36:14 +04:00
|
|
|
|
if add('origin', box.single_value(token)):
|
|
|
|
|
tokens.pop()
|
|
|
|
|
next_token = tokens[-1:]
|
|
|
|
|
if add('clip', box.single_value(next_token)):
|
2013-03-20 18:28:04 +04:00
|
|
|
|
tokens.pop()
|
2013-03-25 21:36:14 +04:00
|
|
|
|
else:
|
|
|
|
|
# The same keyword sets both:
|
|
|
|
|
assert add('clip', box.single_value(token))
|
2013-03-20 16:06:42 +04:00
|
|
|
|
continue
|
2011-08-16 11:08:59 +04:00
|
|
|
|
raise InvalidValues
|
2013-03-20 16:06:42 +04:00
|
|
|
|
|
|
|
|
|
color = results.pop(
|
|
|
|
|
'background_color', INITIAL_VALUES['background_color'])
|
|
|
|
|
for name in properties:
|
|
|
|
|
if name not in results:
|
|
|
|
|
results[name] = INITIAL_VALUES[name][0]
|
|
|
|
|
return color, results
|
|
|
|
|
|
|
|
|
|
layers = reversed(split_on_comma(tokens))
|
2013-03-20 18:28:04 +04:00
|
|
|
|
color, last_layer = parse_layer(next(layers), final_layer=True)
|
2013-03-20 16:06:42 +04:00
|
|
|
|
results = dict((k, [v]) for k, v in last_layer.items())
|
|
|
|
|
for tokens in layers:
|
|
|
|
|
_, layer = parse_layer(tokens)
|
|
|
|
|
for name, value in layer.items():
|
|
|
|
|
results[name].append(value)
|
|
|
|
|
for name, values in results.items():
|
|
|
|
|
yield name, values[::-1] # "Un-reverse"
|
|
|
|
|
yield 'background-color', color
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expander('font')
|
2013-09-10 00:14:30 +04:00
|
|
|
|
@generic_expander('-style', '-variant', '-weight', '-stretch', '-size',
|
2011-08-16 04:47:01 +04:00
|
|
|
|
'line-height', '-family') # line-height is not a suffix
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def expand_font(name, tokens):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Expand the ``font`` shorthand property.
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
|
|
|
|
|
"""
|
2012-04-03 14:45:29 +04:00
|
|
|
|
expand_font_keyword = get_single_keyword(tokens)
|
2011-08-23 19:35:10 +04:00
|
|
|
|
if expand_font_keyword in ('caption', 'icon', 'menu', 'message-box',
|
|
|
|
|
'small-caption', 'status-bar'):
|
2012-04-03 21:02:14 +04:00
|
|
|
|
raise InvalidValues('System fonts are not supported')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
# Make `tokens` a stack
|
|
|
|
|
tokens = list(reversed(tokens))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
# Values for font-style font-variant and font-weight can come in any
|
|
|
|
|
# order and are all optional.
|
2012-04-03 14:45:29 +04:00
|
|
|
|
while tokens:
|
|
|
|
|
token = tokens.pop()
|
|
|
|
|
if get_keyword(token) == 'normal':
|
2011-08-16 11:08:59 +04:00
|
|
|
|
# Just ignore 'normal' keywords. Unspecified properties will get
|
2012-04-03 14:45:29 +04:00
|
|
|
|
# their initial token, which is 'normal' for all three here.
|
2012-04-03 21:02:14 +04:00
|
|
|
|
# TODO: fail if there is too many 'normal' values?
|
2011-08-16 11:08:59 +04:00
|
|
|
|
continue
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if font_style([token]) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-style'
|
2012-04-03 14:45:29 +04:00
|
|
|
|
elif font_variant([token]) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-variant'
|
2012-04-03 14:45:29 +04:00
|
|
|
|
elif font_weight([token]) is not None:
|
2012-04-04 17:05:25 +04:00
|
|
|
|
suffix = '-weight'
|
2013-09-10 00:14:30 +04:00
|
|
|
|
elif font_stretch([token]) is not None:
|
|
|
|
|
suffix = '-stretch'
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
2011-08-16 11:08:59 +04:00
|
|
|
|
# We’re done with these three, continue with font-size
|
2011-08-16 04:47:01 +04:00
|
|
|
|
break
|
2012-04-03 14:45:29 +04:00
|
|
|
|
yield suffix, [token]
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
# Then font-size is mandatory
|
2012-04-03 14:45:29 +04:00
|
|
|
|
# Latest `token` from the loop.
|
|
|
|
|
if font_size([token]) is None:
|
2011-08-26 12:02:55 +04:00
|
|
|
|
raise InvalidValues
|
2012-04-04 17:05:25 +04:00
|
|
|
|
yield '-size', [token]
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
# Then line-height is optional, but font-family is not so the list
|
|
|
|
|
# must not be empty yet
|
2012-04-03 14:45:29 +04:00
|
|
|
|
if not tokens:
|
2011-10-11 14:09:37 +04:00
|
|
|
|
raise InvalidValues
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
token = tokens.pop()
|
|
|
|
|
if token.type == 'DELIM' and token.value == '/':
|
|
|
|
|
token = tokens.pop()
|
|
|
|
|
if line_height([token]) is None:
|
2012-03-24 16:39:31 +04:00
|
|
|
|
raise InvalidValues
|
2012-04-04 17:05:25 +04:00
|
|
|
|
yield 'line-height', [token]
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
|
|
|
|
# We pop()ed a font-family, add it back
|
2012-04-03 14:45:29 +04:00
|
|
|
|
tokens.append(token)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
# Reverse the stack to get normal list
|
2012-04-03 14:45:29 +04:00
|
|
|
|
tokens.reverse()
|
|
|
|
|
if font_family(tokens) is None:
|
2011-08-26 12:02:55 +04:00
|
|
|
|
raise InvalidValues
|
2012-04-04 17:05:25 +04:00
|
|
|
|
yield '-family', tokens
|
2014-04-27 15:29:55 +04:00
|
|
|
|
|
2013-10-01 19:52:05 +04:00
|
|
|
|
|
|
|
|
|
@expander('word-wrap')
|
|
|
|
|
def expand_word_wrap(base_url, name, tokens):
|
|
|
|
|
"""Expand the ``word-wrap`` legacy property.
|
|
|
|
|
|
|
|
|
|
See http://http://www.w3.org/TR/css3-text/#overflow-wrap
|
|
|
|
|
|
|
|
|
|
"""
|
2013-10-01 21:35:08 +04:00
|
|
|
|
keyword = overflow_wrap(tokens)
|
|
|
|
|
if keyword is None:
|
2013-10-01 19:52:05 +04:00
|
|
|
|
raise InvalidValues
|
2013-10-01 21:35:08 +04:00
|
|
|
|
|
|
|
|
|
yield 'overflow-wrap', keyword
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
def validate_non_shorthand(base_url, name, tokens, required=False):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Default validator for non-shorthand properties."""
|
2012-04-04 17:05:25 +04:00
|
|
|
|
if not required and name not in KNOWN_PROPERTIES:
|
|
|
|
|
hyphens_name = name.replace('_', '-')
|
|
|
|
|
if hyphens_name in KNOWN_PROPERTIES:
|
|
|
|
|
raise InvalidValues('did you mean %s?' % hyphens_name)
|
|
|
|
|
else:
|
|
|
|
|
raise InvalidValues('unknown property')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
if not required and name not in VALIDATORS:
|
|
|
|
|
raise InvalidValues('property not supported yet')
|
|
|
|
|
|
2012-04-03 14:45:29 +04:00
|
|
|
|
keyword = get_single_keyword(tokens)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if keyword in ('initial', 'inherit'):
|
|
|
|
|
value = keyword
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
2012-03-24 16:39:31 +04:00
|
|
|
|
function = VALIDATORS[name]
|
|
|
|
|
if function.wants_base_url:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
value = function(tokens, base_url)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
else:
|
2012-04-03 14:45:29 +04:00
|
|
|
|
value = function(tokens)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if value is None:
|
|
|
|
|
raise InvalidValues
|
|
|
|
|
return [(name, value)]
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2012-04-05 18:31:32 +04:00
|
|
|
|
def preprocess_declarations(base_url, declarations):
|
2012-04-04 17:05:25 +04:00
|
|
|
|
"""
|
|
|
|
|
Expand shorthand properties and filter unsupported properties and values.
|
2011-08-23 19:35:10 +04:00
|
|
|
|
|
2012-04-04 17:05:25 +04:00
|
|
|
|
Log a warning for every ignored declaration.
|
2011-08-23 19:35:10 +04:00
|
|
|
|
|
2012-04-05 18:31:32 +04:00
|
|
|
|
Return a iterable of ``(name, value, priority)`` tuples.
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2012-04-05 18:31:32 +04:00
|
|
|
|
def validation_error(level, reason):
|
2013-04-11 14:08:53 +04:00
|
|
|
|
getattr(LOGGER, level)(
|
|
|
|
|
'Ignored `%s: %s` at %i:%i, %s.',
|
2012-04-05 18:31:32 +04:00
|
|
|
|
declaration.name, declaration.value.as_css(),
|
|
|
|
|
declaration.line, declaration.column, reason)
|
|
|
|
|
|
|
|
|
|
for declaration in declarations:
|
|
|
|
|
name = declaration.name
|
|
|
|
|
|
|
|
|
|
if name in PREFIXED and not name.startswith(PREFIX):
|
2013-04-11 14:08:53 +04:00
|
|
|
|
validation_error(
|
2013-08-19 16:40:51 +04:00
|
|
|
|
'warning',
|
2015-04-30 11:58:51 +03:00
|
|
|
|
'the property is experimental or non-standard, use %s' %
|
|
|
|
|
PREFIX + name)
|
2012-04-05 18:31:32 +04:00
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if name in NOT_PRINT_MEDIA:
|
2013-04-11 14:08:53 +04:00
|
|
|
|
validation_error(
|
|
|
|
|
'info', 'the property does not apply for the print media')
|
2012-04-05 18:31:32 +04:00
|
|
|
|
continue
|
|
|
|
|
|
2011-12-13 15:51:30 +04:00
|
|
|
|
if name.startswith(PREFIX):
|
|
|
|
|
unprefixed_name = name[len(PREFIX):]
|
2012-07-04 13:27:20 +04:00
|
|
|
|
if unprefixed_name in UNPREFIXED:
|
2013-04-11 14:08:53 +04:00
|
|
|
|
validation_error(
|
2013-08-19 16:40:51 +04:00
|
|
|
|
'warning',
|
2012-07-04 13:27:20 +04:00
|
|
|
|
'the property was unprefixed, use ' + unprefixed_name)
|
|
|
|
|
continue
|
2011-12-13 15:51:30 +04:00
|
|
|
|
if unprefixed_name in PREFIXED:
|
|
|
|
|
name = unprefixed_name
|
2012-04-05 18:31:32 +04:00
|
|
|
|
|
2011-08-23 19:35:10 +04:00
|
|
|
|
expander_ = EXPANDERS.get(name, validate_non_shorthand)
|
2012-04-05 18:31:32 +04:00
|
|
|
|
tokens = remove_whitespace(declaration.value)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
try:
|
2012-04-05 18:31:32 +04:00
|
|
|
|
# Use list() to consume generators now and catch any error.
|
|
|
|
|
result = list(expander_(base_url, name, tokens))
|
2012-02-17 21:49:58 +04:00
|
|
|
|
except InvalidValues as exc:
|
2013-04-11 14:08:53 +04:00
|
|
|
|
validation_error(
|
2013-08-19 16:40:51 +04:00
|
|
|
|
'warning',
|
2012-04-05 18:31:32 +04:00
|
|
|
|
exc.args[0] if exc.args and exc.args[0] else 'invalid value')
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
priority = declaration.priority
|
|
|
|
|
for long_name, value in result:
|
|
|
|
|
yield long_name.replace('-', '_'), value, priority
|