mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-11-26 00:05:01 -05:00
162 lines
4.9 KiB
Python
162 lines
4.9 KiB
Python
# coding: utf8
|
|
"""
|
|
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 unicode_literals, division
|
|
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 __init__(self):
|
|
super(CSSPage3Parser, self).__init__()
|
|
for x in self.PAGE_MARGIN_AT_KEYWORDS:
|
|
self.at_parsers[x] = self.parse_page_margin_rule
|
|
|
|
def parse_page_margin_rule(self, rule, previous_rules, errors, context):
|
|
if context != '@page':
|
|
raise ParseError(rule,
|
|
'%s rule not allowed in %s' % (rule.at_keyword, context))
|
|
if rule.head:
|
|
raise ParseError(rule.head[0],
|
|
'unexpected %s token in %s rule header'
|
|
% (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)
|
|
|
|
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')
|