# coding: utf-8 """ tinycss.page3 ------------------ Support for CSS 3 Paged Media syntax: http://dev.w3.org/csswg/css3-page/ Adds support for named page selectors and margin rules. :copyright: (c) 2012 by Simon Sapin. :license: BSD, see LICENSE for more details. """ from __future__ import division, unicode_literals from .css21 import CSS21Parser, ParseError class MarginRule(object): """A parsed at-rule for margin box. .. attribute:: at_keyword One of the 16 following strings: * ``@top-left-corner`` * ``@top-left`` * ``@top-center`` * ``@top-right`` * ``@top-right-corner`` * ``@bottom-left-corner`` * ``@bottom-left`` * ``@bottom-center`` * ``@bottom-right`` * ``@bottom-right-corner`` * ``@left-top`` * ``@left-middle`` * ``@left-bottom`` * ``@right-top`` * ``@right-middle`` * ``@right-bottom`` .. attribute:: declarations A list of :class:`~.css21.Declaration` objects. .. attribute:: line Source line where this was read. .. attribute:: column Source column where this was read. """ def __init__(self, at_keyword, declarations, line, column): self.at_keyword = at_keyword self.declarations = declarations self.line = line self.column = column class CSSPage3Parser(CSS21Parser): """Extend :class:`~.css21.CSS21Parser` for `CSS 3 Paged Media`_ syntax. .. _CSS 3 Paged Media: http://dev.w3.org/csswg/css3-page/ Compared to CSS 2.1, the ``at_rules`` and ``selector`` attributes of :class:`~.css21.PageRule` objects are modified: * ``at_rules`` is not always empty, it is a list of :class:`MarginRule` objects. * ``selector``, instead of a single string, is a tuple of the page name and the pseudo class. Each of these may be a ``None`` or a string. +--------------------------+------------------------+ | CSS | Parsed selectors | +==========================+========================+ | .. code-block:: css | .. code-block:: python | | | | | @page {} | (None, None) | | @page :first {} | (None, 'first') | | @page chapter {} | ('chapter', None) | | @page table:right {} | ('table', 'right') | +--------------------------+------------------------+ """ PAGE_MARGIN_AT_KEYWORDS = [ '@top-left-corner', '@top-left', '@top-center', '@top-right', '@top-right-corner', '@bottom-left-corner', '@bottom-left', '@bottom-center', '@bottom-right', '@bottom-right-corner', '@left-top', '@left-middle', '@left-bottom', '@right-top', '@right-middle', '@right-bottom', ] def parse_at_rule(self, rule, previous_rules, errors, context): if rule.at_keyword in self.PAGE_MARGIN_AT_KEYWORDS: if context != '@page': raise ParseError( rule, '{0} rule not allowed in {1}'.format( rule.at_keyword, context)) if rule.head: raise ParseError( rule.head[0], 'unexpected {0} token in {1} rule header'.format( rule.head[0].type, rule.at_keyword)) declarations, body_errors = self.parse_declaration_list(rule.body) errors.extend(body_errors) return MarginRule( rule.at_keyword, declarations, rule.line, rule.column) return super(CSSPage3Parser, self).parse_at_rule( rule, previous_rules, errors, context) def parse_page_selector(self, head): """Parse an @page selector. :param head: The ``head`` attribute of an unparsed :class:`AtRule`. :returns: A page selector. For CSS 2.1, this is 'first', 'left', 'right' or None. 'blank' is added by GCPM. :raises: :class`~parsing.ParseError` on invalid selectors """ if not head: return (None, None), (0, 0, 0) if head[0].type == 'IDENT': name = head.pop(0).value while head and head[0].type == 'S': head.pop(0) if not head: return (name, None), (1, 0, 0) name_specificity = (1,) else: name = None name_specificity = (0,) if (len(head) == 2 and head[0].type == ':' and head[1].type == 'IDENT'): pseudo_class = head[1].value specificity = { 'first': (1, 0), 'blank': (1, 0), 'left': (0, 1), 'right': (0, 1), }.get(pseudo_class) if specificity: return (name, pseudo_class), (name_specificity + specificity) raise ParseError(head[0], 'invalid @page selector')