2011-08-16 04:47:01 +04:00
|
|
|
|
# coding: utf8
|
|
|
|
|
|
|
|
|
|
# WeasyPrint converts web documents (HTML, CSS, ...) to PDF.
|
|
|
|
|
# Copyright (C) 2011 Simon Sapin
|
|
|
|
|
#
|
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
Validator for all supported properties.
|
|
|
|
|
|
|
|
|
|
See http://www.w3.org/TR/CSS21/propidx.html for allowed values.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
# TODO: unit-test these validators
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import functools
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
from .values import get_keyword, get_single_keyword, as_css
|
|
|
|
|
from .properties import INITIAL_VALUES, NOT_PRINT_MEDIA
|
|
|
|
|
from . import computed_values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LOGGER = logging.getLogger('WEASYPRINT')
|
|
|
|
|
|
|
|
|
|
# yes/no validators for non-shorthand properties
|
|
|
|
|
# Maps property names to functions taking a property name and a value list,
|
|
|
|
|
# returning True for valid, False for invalid.
|
|
|
|
|
VALIDATORS = {}
|
|
|
|
|
|
|
|
|
|
EXPANDERS = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
def validator(property_name=None):
|
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).
|
|
|
|
|
|
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:
|
|
|
|
|
name = function.__name__.replace('_', '-')
|
|
|
|
|
else:
|
|
|
|
|
name = property_name
|
|
|
|
|
assert name in INITIAL_VALUES, name
|
|
|
|
|
assert name not in VALIDATORS, name
|
|
|
|
|
|
|
|
|
|
VALIDATORS[name] = function
|
|
|
|
|
return function
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
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)
|
2011-08-23 19:35:10 +04:00
|
|
|
|
def keyword_validator(values):
|
2011-08-24 14:23:14 +04:00
|
|
|
|
"""Wrap a validator to call get_single_keyword on values."""
|
|
|
|
|
return function(get_single_keyword(values))
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return keyword_validator
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def single_value(function):
|
2011-08-24 14:23:14 +04:00
|
|
|
|
"""Decorator for validators that only accept a single value."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@functools.wraps(function)
|
2011-08-23 19:35:10 +04:00
|
|
|
|
def single_value_validator(values):
|
|
|
|
|
"""Validate a property whose value is single."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
if len(values) != 1:
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
return function(values[0])
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return single_value_validator
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-16 10:28:44 +04:00
|
|
|
|
def is_dimension(value, negative=True):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Get if ``value`` is a dimension.
|
|
|
|
|
|
|
|
|
|
The ``negative`` argument sets wether negative values are allowed.
|
|
|
|
|
|
2011-08-16 10:28:44 +04:00
|
|
|
|
"""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
type_ = value.type
|
|
|
|
|
# Units may be ommited on zero lenghts.
|
2011-08-16 10:28:44 +04:00
|
|
|
|
return (
|
2011-08-23 19:35:10 +04:00
|
|
|
|
type_ == 'DIMENSION' and (negative or value.value >= 0) and (
|
2011-08-18 17:44:45 +04:00
|
|
|
|
value.dimension in computed_values.LENGTHS_TO_PIXELS or
|
2011-08-23 19:35:10 +04:00
|
|
|
|
value.dimension in ('em', 'ex'))
|
|
|
|
|
) or (type_ == 'NUMBER' and value.value == 0)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-16 10:28:44 +04:00
|
|
|
|
def is_dimension_or_percentage(value, negative=True):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Get if ``value`` is a dimension or a percentage.
|
|
|
|
|
|
|
|
|
|
The ``negative`` argument sets wether negative values are allowed.
|
|
|
|
|
|
2011-08-16 10:28:44 +04:00
|
|
|
|
"""
|
2011-08-18 17:44:45 +04:00
|
|
|
|
return is_dimension(value, negative) or (
|
2011-08-23 19:35:10 +04:00
|
|
|
|
value.type == 'PERCENTAGE' and (negative or value.value >= 0))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
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."""
|
2011-08-24 14:23:14 +04:00
|
|
|
|
return keyword in ('scroll', 'fixed')
|
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')
|
|
|
|
|
@validator('color')
|
|
|
|
|
@single_value
|
|
|
|
|
def color(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``*-color`` and ``color`` properties validation."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return value.type == 'COLOR_VALUE' or get_keyword(value) == 'currentColor'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator('background-image')
|
|
|
|
|
@validator('list-style-image')
|
|
|
|
|
@single_value
|
|
|
|
|
def image(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``*-image`` properties validation."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return get_keyword(value) == 'none' or value.type == 'URI'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
def background_position(values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``background-position`` property validation.
|
|
|
|
|
|
|
|
|
|
See http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
"""
|
|
|
|
|
if len(values) == 1:
|
|
|
|
|
value = values[0]
|
2011-08-16 10:28:44 +04:00
|
|
|
|
return (
|
|
|
|
|
is_dimension_or_percentage(value) or
|
2011-08-23 19:35:10 +04:00
|
|
|
|
get_keyword(value) in ('left', 'right', 'top', 'bottom', 'center'))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
if len(values) == 2:
|
|
|
|
|
value_1, value_2 = values
|
|
|
|
|
if is_dimension_or_percentage(value_1):
|
|
|
|
|
return (
|
|
|
|
|
is_dimension_or_percentage(value_2) or
|
2011-08-23 19:35:10 +04:00
|
|
|
|
get_keyword(value_2) in ('top', 'center', 'bottom'))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
elif is_dimension_or_percentage(value_2):
|
|
|
|
|
return get_keyword(value_1) in ('left', 'center', 'right')
|
|
|
|
|
else:
|
|
|
|
|
keyword_1, keyword_2 = map(get_keyword, values)
|
|
|
|
|
return (
|
|
|
|
|
keyword_1 in ('left', 'center', 'right') and
|
|
|
|
|
keyword_2 in ('top', 'center', 'bottom')
|
|
|
|
|
) or (
|
|
|
|
|
keyword_1 in ('top', 'center', 'bottom') and
|
2011-08-23 19:35:10 +04:00
|
|
|
|
keyword_2 in ('left', 'center', 'right'))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
2011-08-24 14:23:14 +04:00
|
|
|
|
@single_keyword
|
|
|
|
|
def background_repeat(keyword):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``background-repeat`` property validation."""
|
2011-08-24 14:23:14 +04:00
|
|
|
|
return keyword in ('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
|
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-08-24 14:23:14 +04:00
|
|
|
|
return keyword in ('none', 'hidden', 'dotted', 'dashed', 'solid',
|
|
|
|
|
'double', 'groove', 'ridge', 'inset', 'outset')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator('border-top-width')
|
|
|
|
|
@validator('border-right-width')
|
|
|
|
|
@validator('border-left-width')
|
|
|
|
|
@validator('border-bottom-width')
|
|
|
|
|
@single_value
|
|
|
|
|
def border_width(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``border-*-width`` properties validation."""
|
2011-08-16 10:28:44 +04:00
|
|
|
|
return is_dimension(value, negative=False) or get_keyword(value) in (
|
2011-08-16 04:47:01 +04:00
|
|
|
|
'thin', 'medium', 'thick')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#@validator('top')
|
|
|
|
|
#@validator('right')
|
|
|
|
|
#@validator('left')
|
|
|
|
|
#@validator('bottom')
|
|
|
|
|
@validator('margin-top')
|
|
|
|
|
@validator('margin-right')
|
|
|
|
|
@validator('margin-bottom')
|
|
|
|
|
@validator('margin-left')
|
|
|
|
|
@single_value
|
|
|
|
|
def lenght_precentage_or_auto(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``margin-*`` properties validation."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return (
|
|
|
|
|
is_dimension_or_percentage(value) or
|
2011-08-23 19:35:10 +04:00
|
|
|
|
get_keyword(value) == 'auto')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-16 10:28:44 +04:00
|
|
|
|
@validator('height')
|
|
|
|
|
@validator('width')
|
|
|
|
|
@single_value
|
2011-08-16 19:49:33 +04:00
|
|
|
|
def positive_lenght_precentage_or_auto(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``width`` and ``height`` properties validation."""
|
2011-08-16 10:28:44 +04:00
|
|
|
|
return (
|
|
|
|
|
is_dimension_or_percentage(value, negative=False) or
|
2011-08-23 19:35:10 +04:00
|
|
|
|
get_keyword(value) == '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()
|
|
|
|
|
def display(values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``display`` property validation."""
|
|
|
|
|
display_keyword = get_single_keyword(values)
|
|
|
|
|
if display_keyword in (
|
|
|
|
|
'inline-block', 'table', 'inline-table',
|
|
|
|
|
'table-row-group', 'table-header-group', 'table-footer-group',
|
|
|
|
|
'table-row', 'table-column-group', 'table-column', 'table-cell',
|
|
|
|
|
'table-caption'):
|
2011-08-17 16:54:28 +04:00
|
|
|
|
raise InvalidValues('value not supported yet')
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return display_keyword in ('inline', 'block', 'list-item', 'none')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
def font_family(values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``font-family`` property validation."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return all(value.type in ('IDENT', 'STRING') for value in values)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
@single_value
|
|
|
|
|
def font_size(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``font-size`` property validation."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
if is_dimension_or_percentage(value):
|
|
|
|
|
return True
|
2011-08-23 19:35:10 +04:00
|
|
|
|
font_size_keyword = get_keyword(value)
|
|
|
|
|
if font_size_keyword in ('smaller', 'larger'):
|
2011-08-18 17:44:45 +04:00
|
|
|
|
raise InvalidValues('value not supported yet')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return (
|
2011-08-23 19:35:10 +04:00
|
|
|
|
font_size_keyword in computed_values.FONT_SIZE_KEYWORDS #or
|
2011-08-18 17:44:45 +04:00
|
|
|
|
#keyword in ('smaller', 'larger')
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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()
|
|
|
|
|
@single_value
|
|
|
|
|
def font_weight(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``font-weight`` property validation."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return (
|
2011-08-16 10:28:44 +04:00
|
|
|
|
get_keyword(value) in ('normal', 'bold', 'bolder', 'lighter') or (
|
|
|
|
|
value.type == 'NUMBER' and
|
2011-08-23 19:35:10 +04:00
|
|
|
|
value.value in (100, 200, 300, 400, 500, 600, 700, 800, 900)))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-23 19:35:10 +04:00
|
|
|
|
@validator()
|
2011-08-16 04:47:01 +04:00
|
|
|
|
@single_value
|
|
|
|
|
def letter_spacing(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``letter-spacing`` property validation."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return get_keyword(value) == 'normal' or is_dimension(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
@single_value
|
|
|
|
|
def line_height(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``line-height`` property validation."""
|
2011-08-16 10:28:44 +04:00
|
|
|
|
return get_keyword(value) == 'normal' or (
|
|
|
|
|
value.type in ('NUMBER', 'DIMENSION', 'PERCENTAGE') and
|
2011-08-23 19:35:10 +04:00
|
|
|
|
value.value >= 0)
|
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-08-20 17:07:14 +04:00
|
|
|
|
def list_style_type(values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``list-style-type`` property validation."""
|
|
|
|
|
font_size_keyword = get_single_keyword(values)
|
|
|
|
|
if font_size_keyword in ('decimal', 'decimal-leading-zero',
|
2011-08-16 04:47:01 +04:00
|
|
|
|
'lower-roman', 'upper-roman', 'lower-greek', 'lower-latin',
|
|
|
|
|
'upper-latin', 'armenian', 'georgian', 'lower-alpha',
|
2011-08-20 17:07:14 +04:00
|
|
|
|
'upper-alpha'):
|
|
|
|
|
raise InvalidValues('value not supported yet')
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return font_size_keyword in ('disc', 'circle', 'square', 'none')
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator('padding-top')
|
|
|
|
|
@validator('padding-right')
|
|
|
|
|
@validator('padding-bottom')
|
|
|
|
|
@validator('padding-left')
|
|
|
|
|
@single_value
|
2011-08-20 17:07:14 +04:00
|
|
|
|
def length_or_precentage(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``padding-*`` properties validation."""
|
2011-08-16 10:28:44 +04:00
|
|
|
|
return is_dimension_or_percentage(value, negative=False)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
def position(values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``position`` property validation."""
|
|
|
|
|
position_keyword = get_single_keyword(values)
|
|
|
|
|
if position_keyword in ('relative', 'absolute', 'fixed'):
|
2011-08-17 16:54:28 +04:00
|
|
|
|
raise InvalidValues('value not supported yet')
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return position_keyword in ('static',)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
def text_align(values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``text-align`` property validation."""
|
|
|
|
|
text_align_keyword = get_single_keyword(values)
|
|
|
|
|
if text_align_keyword in ('right', 'center', 'justify'):
|
2011-08-17 16:54:28 +04:00
|
|
|
|
raise InvalidValues('value not supported yet')
|
2011-08-23 19:35:10 +04:00
|
|
|
|
return text_align_keyword in ('left',)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
def text_decoration(values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""``text-decoration`` property validation."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return (
|
|
|
|
|
get_single_keyword(values) == 'none' or
|
|
|
|
|
all(
|
2011-08-23 19:35:10 +04:00
|
|
|
|
get_keyword(value) in (
|
|
|
|
|
'underline', 'overline', 'line-through', 'blink')
|
|
|
|
|
for value in values))
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@validator()
|
|
|
|
|
def size(values):
|
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
|
|
|
|
"""
|
|
|
|
|
return (
|
|
|
|
|
len(values) == 1 and (
|
|
|
|
|
is_dimension(values[0]) or
|
|
|
|
|
get_single_keyword(values) in ('auto', 'portrait', 'landscape') or
|
|
|
|
|
get_single_keyword(values) in computed_values.PAGE_SIZES
|
|
|
|
|
)
|
|
|
|
|
) or (
|
|
|
|
|
len(values) == 2 and (
|
|
|
|
|
all(is_dimension(value) for value in values) or (
|
|
|
|
|
get_keyword(values[0]) in ('portrait', 'landscape') and
|
|
|
|
|
get_keyword(values[1]) in computed_values.PAGE_SIZES
|
|
|
|
|
) or (
|
|
|
|
|
get_keyword(values[0]) in computed_values.PAGE_SIZES and
|
|
|
|
|
get_keyword(values[1]) in ('portrait', 'landscape')
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2011-08-23 19:35:10 +04:00
|
|
|
|
# Expanders
|
|
|
|
|
|
|
|
|
|
# Let's be coherent, always use ``name`` as an argument even when it is useless
|
|
|
|
|
# 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')
|
|
|
|
|
def expand_four_sides(name, values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Expand properties setting a value for the four sides of a box."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
# Make sure we have 4 values
|
|
|
|
|
if len(values) == 1:
|
|
|
|
|
values *= 4
|
|
|
|
|
elif len(values) == 2:
|
2011-08-23 19:35:10 +04:00
|
|
|
|
values *= 2 # (bottom, left) defaults to (top, right)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
elif len(values) == 3:
|
2011-08-23 19:35:10 +04:00
|
|
|
|
values.append(values[1]) # left defaults to right
|
2011-08-16 04:47:01 +04:00
|
|
|
|
elif len(values) != 4:
|
2011-08-23 19:35:10 +04:00
|
|
|
|
raise InvalidValues(
|
|
|
|
|
'Expected 1 to 4 value components got %i' % len(values))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
for suffix, value in zip(('-top', '-right', '-bottom', '-left'), values):
|
|
|
|
|
i = name.rfind('-')
|
|
|
|
|
if i == -1:
|
|
|
|
|
new_name = name + suffix
|
|
|
|
|
else:
|
|
|
|
|
# eg. border-color becomes border-*-color, not border-color-*
|
|
|
|
|
new_name = name[:i] + suffix + name[i:]
|
|
|
|
|
|
|
|
|
|
values = [value]
|
|
|
|
|
validate_non_shorthand(new_name, values, required=True)
|
|
|
|
|
yield new_name, values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generic_expander(*expanded_names):
|
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
|
|
|
|
"""
|
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)
|
2011-08-23 19:35:10 +04:00
|
|
|
|
def generic_expander_wrapper(name, values):
|
|
|
|
|
"""Wrap the expander."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
if get_single_keyword(values) in ('inherit', 'initial'):
|
|
|
|
|
results = dict.fromkeys(expanded_names, values)
|
|
|
|
|
else:
|
|
|
|
|
results = {}
|
|
|
|
|
for new_name, new_values in wrapped(name, values):
|
2011-08-16 11:08:59 +04:00
|
|
|
|
assert new_name in expanded_names, new_name
|
|
|
|
|
assert new_name not in results, new_name
|
2011-08-16 04:47:01 +04:00
|
|
|
|
results[new_name] = new_values
|
|
|
|
|
|
|
|
|
|
for new_name in expanded_names:
|
|
|
|
|
if new_name.startswith('-'):
|
|
|
|
|
# new_name is a suffix
|
|
|
|
|
actual_new_name = name + new_name
|
|
|
|
|
else:
|
|
|
|
|
actual_new_name = new_name
|
|
|
|
|
|
|
|
|
|
if new_name in results:
|
|
|
|
|
values = results[new_name]
|
|
|
|
|
else:
|
|
|
|
|
values = INITIAL_VALUES[actual_new_name]
|
|
|
|
|
validate_non_shorthand(actual_new_name, values, required=True)
|
|
|
|
|
yield actual_new_name, values
|
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')
|
|
|
|
|
@generic_expander('-type', '-position', '-image')
|
|
|
|
|
def expand_list_style(name, values):
|
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
|
2011-08-16 04:47:01 +04:00
|
|
|
|
for value in values:
|
2011-08-16 11:08:59 +04:00
|
|
|
|
if get_keyword(value) == 'none':
|
|
|
|
|
# Can be either -style or -image, see at the end which is not
|
|
|
|
|
# otherwise specified.
|
|
|
|
|
none_count += 1
|
|
|
|
|
none_value = value
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if list_style_type([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-type'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
type_specified = True
|
|
|
|
|
elif list_style_position([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-position'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif image([value]):
|
2011-08-16 04:47:01 +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
|
2011-08-16 04:47:01 +04:00
|
|
|
|
yield suffix, [value]
|
|
|
|
|
|
2011-08-16 11:08:59 +04:00
|
|
|
|
if not type_specified and none_count:
|
|
|
|
|
yield '-type', [none_value]
|
|
|
|
|
none_count -= 1
|
|
|
|
|
|
|
|
|
|
if not image_specified and none_count:
|
|
|
|
|
yield '-image', [none_value]
|
|
|
|
|
none_count -= 1
|
|
|
|
|
|
|
|
|
|
if none_count:
|
|
|
|
|
# Too many none values.
|
|
|
|
|
raise InvalidValues
|
|
|
|
|
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
@expander('border')
|
|
|
|
|
def expand_border(name, values):
|
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
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
for suffix in ('-top', '-right', '-bottom', '-left'):
|
|
|
|
|
for new_prop in expand_border_side(name + suffix, values):
|
|
|
|
|
yield new_prop
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expander('border-top')
|
|
|
|
|
@expander('border-right')
|
|
|
|
|
@expander('border-bottom')
|
|
|
|
|
@expander('border-left')
|
|
|
|
|
@generic_expander('-width', '-color', '-style')
|
|
|
|
|
def expand_border_side(name, values):
|
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
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
for value in values:
|
2011-08-16 11:08:59 +04:00
|
|
|
|
if color([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-color'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif border_width([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-width'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif border_style([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-style'
|
|
|
|
|
else:
|
2011-08-16 11:08:59 +04:00
|
|
|
|
raise InvalidValues
|
2011-08-16 04:47:01 +04:00
|
|
|
|
yield suffix, [value]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid_background_positition(value):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Tell whether the value is valid for ``background-position``."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return (
|
|
|
|
|
value.type in ('DIMENSION', 'PERCENTAGE') or
|
|
|
|
|
(value.type == 'NUMBER' and value.value == 0) or
|
2011-08-23 19:35:10 +04:00
|
|
|
|
get_keyword(value) in ('left', 'right', 'top', 'bottom', 'center'))
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expander('background')
|
|
|
|
|
@generic_expander('-color', '-image', '-repeat', '-attachment', '-position')
|
|
|
|
|
def expand_background(name, values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Expand the ``background`` shorthand property.
|
|
|
|
|
|
|
|
|
|
See http://www.w3.org/TR/CSS21/colors.html#propdef-background
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
# Make `values` a stack
|
|
|
|
|
values = list(reversed(values))
|
|
|
|
|
while values:
|
|
|
|
|
value = values.pop()
|
2011-08-16 11:08:59 +04:00
|
|
|
|
if color([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-color'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif image([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-image'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif background_repeat([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-repeat'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif background_attachment([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-attachment'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif background_position([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
if values:
|
|
|
|
|
next_value = values.pop()
|
2011-08-16 11:08:59 +04:00
|
|
|
|
if background_position([value, next_value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
# Two consecutive '-position' values, yield them together
|
|
|
|
|
yield '-position', [value, next_value]
|
|
|
|
|
continue
|
|
|
|
|
else:
|
|
|
|
|
# The next value is not a '-position', put it back
|
|
|
|
|
# for the next loop iteration
|
|
|
|
|
values.append(next_value)
|
|
|
|
|
# A single '-position' value
|
|
|
|
|
suffix = '-position'
|
|
|
|
|
else:
|
2011-08-16 11:08:59 +04:00
|
|
|
|
raise InvalidValues
|
2011-08-16 04:47:01 +04:00
|
|
|
|
yield suffix, [value]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@expander('font')
|
|
|
|
|
@generic_expander('-style', '-variant', '-weight', '-size',
|
|
|
|
|
'line-height', '-family') # line-height is not a suffix
|
|
|
|
|
def expand_font(name, values):
|
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
|
|
|
|
|
"""
|
2011-08-23 19:35:10 +04:00
|
|
|
|
expand_font_keyword = get_single_keyword(values)
|
|
|
|
|
if expand_font_keyword in ('caption', 'icon', 'menu', 'message-box',
|
|
|
|
|
'small-caption', 'status-bar'):
|
2011-08-16 11:08:59 +04:00
|
|
|
|
LOGGER.warn(
|
2011-08-23 19:35:10 +04:00
|
|
|
|
'System fonts are not supported, `font: %s` ignored.',
|
|
|
|
|
expand_font_keyword)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Make `values` a stack
|
|
|
|
|
values = list(reversed(values))
|
|
|
|
|
# Values for font-style font-variant and font-weight can come in any
|
|
|
|
|
# order and are all optional.
|
|
|
|
|
while values:
|
|
|
|
|
value = values.pop()
|
2011-08-16 11:08:59 +04:00
|
|
|
|
if get_keyword(value) == 'normal':
|
|
|
|
|
# Just ignore 'normal' keywords. Unspecified properties will get
|
|
|
|
|
# their initial value, which is 'normal' for all three here.
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if font_style([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-style'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif font_variant([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-variant'
|
2011-08-16 11:08:59 +04:00
|
|
|
|
elif font_weight([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
suffix = '-weight'
|
|
|
|
|
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
|
|
|
|
|
yield suffix, [value]
|
|
|
|
|
|
|
|
|
|
# Then font-size is mandatory
|
2011-08-16 11:08:59 +04:00
|
|
|
|
# Latest `value` from the loop.
|
2011-08-26 12:02:55 +04:00
|
|
|
|
if not font_size([value]):
|
|
|
|
|
raise InvalidValues
|
2011-08-16 04:47:01 +04:00
|
|
|
|
yield '-size', [value]
|
|
|
|
|
|
|
|
|
|
# Then line-height is optional, but font-family is not so the list
|
|
|
|
|
# must not be empty yet
|
|
|
|
|
|
|
|
|
|
value = values.pop()
|
2011-08-16 11:08:59 +04:00
|
|
|
|
if line_height([value]):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
yield 'line-height', [value]
|
|
|
|
|
else:
|
|
|
|
|
# We pop()ed a font-family, add it back
|
|
|
|
|
values.append(value)
|
|
|
|
|
|
|
|
|
|
# Reverse the stack to get normal list
|
|
|
|
|
values.reverse()
|
2011-08-26 12:02:55 +04:00
|
|
|
|
if not font_family(values):
|
|
|
|
|
raise InvalidValues
|
2011-08-16 04:47:01 +04:00
|
|
|
|
yield '-family', values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_non_shorthand(name, values, required=False):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Default validator for non-shorthand properties."""
|
2011-08-16 04:47:01 +04:00
|
|
|
|
if not required and name not in INITIAL_VALUES:
|
|
|
|
|
raise InvalidValues('unknown property')
|
|
|
|
|
|
|
|
|
|
if not required and name not in VALIDATORS:
|
|
|
|
|
raise InvalidValues('property not supported yet')
|
|
|
|
|
|
2011-08-23 19:35:10 +04:00
|
|
|
|
if (get_single_keyword(values) in ('initial', 'inherit') or
|
|
|
|
|
VALIDATORS[name](values)):
|
2011-08-16 04:47:01 +04:00
|
|
|
|
return [(name, values)]
|
|
|
|
|
else:
|
|
|
|
|
raise InvalidValues
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_and_expand(name, values):
|
2011-08-23 19:35:10 +04:00
|
|
|
|
"""Expand and validate shorthand properties.
|
|
|
|
|
|
|
|
|
|
The invalid or unsupported declarations are ignored and logged.
|
|
|
|
|
|
|
|
|
|
Return a iterable of ``(name, values)`` tuples.
|
2011-08-16 04:47:01 +04:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
# Defaults
|
|
|
|
|
level = 'warn'
|
|
|
|
|
reason = 'invalid value'
|
|
|
|
|
|
|
|
|
|
if name in NOT_PRINT_MEDIA:
|
|
|
|
|
level = 'info'
|
|
|
|
|
reason = 'the property does not apply for the print media'
|
|
|
|
|
else:
|
2011-08-23 19:35:10 +04:00
|
|
|
|
expander_ = EXPANDERS.get(name, validate_non_shorthand)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
try:
|
2011-08-23 19:35:10 +04:00
|
|
|
|
results = expander_(name, values)
|
2011-08-16 04:47:01 +04:00
|
|
|
|
# Use list() to consume any generator now,
|
|
|
|
|
# so that InvalidValues is caught.
|
|
|
|
|
return list(results)
|
|
|
|
|
except InvalidValues, exc:
|
|
|
|
|
if exc.args and exc.args[0]:
|
|
|
|
|
reason = exc.args[0]
|
|
|
|
|
getattr(LOGGER, level)('The declaration `%s: %s` was ignored: %s.',
|
|
|
|
|
name, as_css(values), reason)
|
|
|
|
|
return []
|
2011-08-26 15:56:00 +04:00
|
|
|
|
|