diff --git a/colosseum/constants.py b/colosseum/constants.py index 431c9b6ae..227f2d275 100644 --- a/colosseum/constants.py +++ b/colosseum/constants.py @@ -1,6 +1,6 @@ from .validators import (ValidationError, is_border_spacing, is_color, is_integer, is_length, is_number, is_percentage, - is_quote, is_rect) + is_quote, is_rect, is_uri) class Choices: @@ -305,8 +305,60 @@ def value(self, context): # 12.5 Lists ###################################################################### # list_style_type +DISC = 'disc' +CIRCLE = 'circle' +SQUARE = 'square' +DECIMAL = 'decimal' +DECIMAL_LEADING_ZERO = 'decimal-leading-zero' +LOWER_ROMAN = 'lower-roman' +UPPER_ROMAN = 'upper-roman' +LOWER_GREEK = 'lower-greek' +LOWER_LATIN = 'lower-latin' +UPPER_LATIN = 'upper-latin' +ARMENIAN = 'armenian' +GEORGIAN = 'georgian' +LOWER_ALPHA = 'lower-alpha' +UPPER_ALPHA = 'upper-alpha' + + +LIST_TYPE_CHOICES = Choices( + DISC, + CIRCLE, + SQUARE, + DECIMAL, + DECIMAL_LEADING_ZERO, + LOWER_ROMAN, + UPPER_ROMAN, + LOWER_GREEK, + LOWER_LATIN, + UPPER_LATIN, + ARMENIAN, + GEORGIAN, + LOWER_ALPHA, + UPPER_ALPHA, + None, + explicit_defaulting_constants=[INHERIT], +) + # list_style_image +LIST_IMAGE_CHOICES = Choices( + None, + validators=[is_uri], + explicit_defaulting_constants=[INHERIT], +) + + # list_style_position +INSIDE = 'inside' +OUTSIDE = 'outside' + +LIST_POSITION_CHOICES = Choices( + INSIDE, + OUTSIDE, + explicit_defaulting_constants=[INHERIT], +) + + # list_style # 13. Paged media #################################################### diff --git a/colosseum/declaration.py b/colosseum/declaration.py index 4b2515373..1d14eb939 100644 --- a/colosseum/declaration.py +++ b/colosseum/declaration.py @@ -21,10 +21,12 @@ TEXT_TRANSFORM_CHOICES, TOP, TRANSPARENT, UNICODE_BIDI_CHOICES, VISIBILITY_CHOICES, VISIBLE, WHITE_SPACE_CHOICES, WIDOWS_CHOICES, WORD_SPACING_CHOICES, Z_INDEX_CHOICES, OtherProperty, - TextAlignInitialValue, default, + TextAlignInitialValue, default, LIST_TYPE_CHOICES, LIST_IMAGE_CHOICES, + LIST_POSITION_CHOICES, DISC, OUTSIDE ) + from .exceptions import ValidationError -from .wrappers import Border, BorderBottom, BorderLeft, BorderRight, BorderTop, Outline +from .wrappers import Border, BorderBottom, BorderLeft, BorderRight, BorderTop, Outline, ListStyle _CSS_PROPERTIES = set() @@ -329,10 +331,10 @@ def __init__(self, **style): # counter-increment # 12.5 Lists - # list_style_type - # list_style_image - # list_style_position - # list_style + list_style_type = validated_property('list_style_type', choices=LIST_TYPE_CHOICES, initial=DISC) + list_style_image = validated_property('list_style_type', choices=LIST_IMAGE_CHOICES, initial=None) + list_style_position = validated_property('list_style_position', choices=LIST_POSITION_CHOICES, initial=OUTSIDE) + list_style = validated_shorthand_property('list_style', parser=parser.list_style, wrapper=ListStyle) # 13. Paged media #################################################### # 13.3.1 Page break properties diff --git a/colosseum/parser.py b/colosseum/parser.py index a2b734a6c..12b2d22bf 100644 --- a/colosseum/parser.py +++ b/colosseum/parser.py @@ -369,3 +369,64 @@ def border_bottom(value): def border_top(value): """Parse border string into a dictionary of outline properties.""" return border(value, direction='top') + + +############################################################################## +# List shorthands +############################################################################## +def _parse_list_style_property_part(value, list_style_dict): + """Parse list style shorthand property part for known properties.""" + from .constants import ( # noqa + LIST_TYPE_CHOICES, LIST_IMAGE_CHOICES, LIST_POSITION_CHOICES + ) + + for property_name, choices in {'list_style_type': LIST_TYPE_CHOICES, + 'list_style_image': LIST_IMAGE_CHOICES, + 'list_style_position': LIST_POSITION_CHOICES}.items(): + try: + value = choices.validate(value) + except (ValueError, ValidationError): + continue + + if property_name in list_style_dict: + raise ValueError('Invalid duplicated property!') + + list_style_dict[property_name] = value + return list_style_dict + + raise ValueError('List style value "{value}" not valid!'.format(value=value)) + + +def list_style(value): + """ + Parse list style string into a dictionary of list style properties. + + The font CSS property is a shorthand for list-style-type, list-style-image, and list-style-position. + + Reference: + - https://www.w3.org/TR/2011/REC-CSS2-20110607/generate.html#lists + """ + if value: + if isinstance(value, str): + values = [val.strip() for val in value.split()] + elif isinstance(value, Sequence): + values = value + else: + raise ValueError('Unknown list style %s ' % value) + else: + raise ValueError('Unknown list style %s ' % value) + + # We iteratively split by the first left hand space found and try to validate if that part + # is a valid or or (which can come in any order) + + # We use this dictionary to store parsed values and check that values properties are not + # duplicated + list_style_dict = {} + for idx, part in enumerate(values): + if idx > 2: + # Outline can have a maximum of 3 parts + raise ValueError('List style property shorthand contains too many parts!') + + list_style_dict = _parse_list_style_property_part(part, list_style_dict) + + return list_style_dict diff --git a/colosseum/validators.py b/colosseum/validators.py index 4ed0aebc2..96c0800c5 100644 --- a/colosseum/validators.py +++ b/colosseum/validators.py @@ -147,3 +147,10 @@ def is_quote(value): is_quote.description = '[ ]+' + + +def is_uri(value): + return value + + +is_uri.description = 'TODO' diff --git a/colosseum/wrappers.py b/colosseum/wrappers.py index c1688cb09..65b949323 100644 --- a/colosseum/wrappers.py +++ b/colosseum/wrappers.py @@ -151,3 +151,7 @@ class BorderLeft(Shorthand): class Border(Shorthand): VALID_KEYS = ['border_width', 'border_style', 'border_color'] + + +class ListStyle(Shorthand): + VALID_KEYS = ['list_style_type', 'list_style_image', 'list_style_position']