mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor the @rule parsers into their own methods for easier sub-classing
This commit is contained in:
parent
8ec267ae38
commit
49e4d09cbc
@ -13,11 +13,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from itertools import chain, islice
|
from itertools import chain, islice
|
||||||
|
|
||||||
from .decoding import decode
|
from tinycss.decoding import decode
|
||||||
from .token_data import TokenList
|
from tinycss.token_data import TokenList
|
||||||
from .tokenizer import tokenize_grouped
|
from tinycss.tokenizer import tokenize_grouped
|
||||||
from .parsing import (strip_whitespace, remove_whitespace, split_on_comma,
|
from tinycss.parsing import (
|
||||||
validate_value, validate_block, validate_any, ParseError)
|
strip_whitespace, remove_whitespace, split_on_comma, validate_value,
|
||||||
|
validate_any, ParseError)
|
||||||
|
|
||||||
|
|
||||||
# stylesheet : [ CDO | CDC | S | statement ]*;
|
# stylesheet : [ CDO | CDC | S | statement ]*;
|
||||||
@ -293,7 +294,6 @@ class ImportRule(object):
|
|||||||
' {0.uri}>'.format(self))
|
' {0.uri}>'.format(self))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _remove_at_charset(tokens):
|
def _remove_at_charset(tokens):
|
||||||
"""Remove any valid @charset at the beggining of a token stream.
|
"""Remove any valid @charset at the beggining of a token stream.
|
||||||
|
|
||||||
@ -328,6 +328,10 @@ class CSS21Parser(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.at_parsers = {
|
||||||
|
'@' + x:getattr(self, 'parse_%s_rule' % x) for x in ('media', 'page', 'import', 'charset')}
|
||||||
|
|
||||||
# User API:
|
# User API:
|
||||||
|
|
||||||
def parse_stylesheet_file(self, css_file, protocol_encoding=None,
|
def parse_stylesheet_file(self, css_file, protocol_encoding=None,
|
||||||
@ -507,66 +511,70 @@ class CSS21Parser(object):
|
|||||||
A parsed at-rule
|
A parsed at-rule
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if rule.at_keyword == '@page':
|
try:
|
||||||
if context != 'stylesheet':
|
parser = self.at_parsers[rule.at_keyword]
|
||||||
raise ParseError(rule, '@page rule not allowed in ' + context)
|
except KeyError:
|
||||||
selector, specificity = self.parse_page_selector(rule.head)
|
|
||||||
if rule.body is None:
|
|
||||||
raise ParseError(rule,
|
|
||||||
'invalid {0} rule: missing block'.format(rule.at_keyword))
|
|
||||||
declarations, at_rules, rule_errors = \
|
|
||||||
self.parse_declarations_and_at_rules(rule.body, '@page')
|
|
||||||
errors.extend(rule_errors)
|
|
||||||
return PageRule(selector, specificity, declarations, at_rules,
|
|
||||||
rule.line, rule.column)
|
|
||||||
|
|
||||||
elif rule.at_keyword == '@media':
|
|
||||||
if context != 'stylesheet':
|
|
||||||
raise ParseError(rule, '@media rule not allowed in ' + context)
|
|
||||||
if not rule.head:
|
|
||||||
raise ParseError(rule, 'expected media types for @media')
|
|
||||||
media = self.parse_media(rule.head)
|
|
||||||
if rule.body is None:
|
|
||||||
raise ParseError(rule,
|
|
||||||
'invalid {0} rule: missing block'.format(rule.at_keyword))
|
|
||||||
rules, rule_errors = self.parse_rules(rule.body, '@media')
|
|
||||||
errors.extend(rule_errors)
|
|
||||||
return MediaRule(media, rules, rule.line, rule.column)
|
|
||||||
|
|
||||||
elif rule.at_keyword == '@import':
|
|
||||||
if context != 'stylesheet':
|
|
||||||
raise ParseError(rule,
|
|
||||||
'@import rule not allowed in ' + context)
|
|
||||||
for previous_rule in previous_rules:
|
|
||||||
if previous_rule.at_keyword not in ('@charset', '@import'):
|
|
||||||
if previous_rule.at_keyword:
|
|
||||||
type_ = 'an {0} rule'.format(previous_rule.at_keyword)
|
|
||||||
else:
|
|
||||||
type_ = 'a ruleset'
|
|
||||||
raise ParseError(previous_rule,
|
|
||||||
'@import rule not allowed after ' + type_)
|
|
||||||
head = rule.head
|
|
||||||
if not head:
|
|
||||||
raise ParseError(rule,
|
|
||||||
'expected URI or STRING for @import rule')
|
|
||||||
if head[0].type not in ('URI', 'STRING'):
|
|
||||||
raise ParseError(rule,
|
|
||||||
'expected URI or STRING for @import rule, got '
|
|
||||||
+ head[0].type)
|
|
||||||
uri = head[0].value
|
|
||||||
media = self.parse_media(strip_whitespace(head[1:]))
|
|
||||||
if rule.body is not None:
|
|
||||||
# The position of the ';' token would be best, but we don’t
|
|
||||||
# have it anymore here.
|
|
||||||
raise ParseError(head[-1], "expected ';', got a block")
|
|
||||||
return ImportRule(uri, media, rule.line, rule.column)
|
|
||||||
|
|
||||||
elif rule.at_keyword == '@charset':
|
|
||||||
raise ParseError(rule, 'mis-placed or malformed @charset rule')
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ParseError(rule, 'unknown at-rule in {0} context: {1}'
|
raise ParseError(rule, 'unknown at-rule in {0} context: {1}'
|
||||||
.format(context, rule.at_keyword))
|
.format(context, rule.at_keyword))
|
||||||
|
else:
|
||||||
|
return parser(rule, previous_rules, errors, context)
|
||||||
|
|
||||||
|
def parse_page_rule(self, rule, previous_rules, errors, context):
|
||||||
|
if context != 'stylesheet':
|
||||||
|
raise ParseError(rule, '@page rule not allowed in ' + context)
|
||||||
|
selector, specificity = self.parse_page_selector(rule.head)
|
||||||
|
if rule.body is None:
|
||||||
|
raise ParseError(rule,
|
||||||
|
'invalid {0} rule: missing block'.format(rule.at_keyword))
|
||||||
|
declarations, at_rules, rule_errors = \
|
||||||
|
self.parse_declarations_and_at_rules(rule.body, '@page')
|
||||||
|
errors.extend(rule_errors)
|
||||||
|
return PageRule(selector, specificity, declarations, at_rules,
|
||||||
|
rule.line, rule.column)
|
||||||
|
|
||||||
|
def parse_media_rule(self, rule, previous_rules, errors, context):
|
||||||
|
if context != 'stylesheet':
|
||||||
|
raise ParseError(rule, '@media rule not allowed in ' + context)
|
||||||
|
if not rule.head:
|
||||||
|
raise ParseError(rule, 'expected media types for @media')
|
||||||
|
media = self.parse_media(rule.head)
|
||||||
|
if rule.body is None:
|
||||||
|
raise ParseError(rule,
|
||||||
|
'invalid {0} rule: missing block'.format(rule.at_keyword))
|
||||||
|
rules, rule_errors = self.parse_rules(rule.body, '@media')
|
||||||
|
errors.extend(rule_errors)
|
||||||
|
return MediaRule(media, rules, rule.line, rule.column)
|
||||||
|
|
||||||
|
def parse_import_rule(self, rule, previous_rules, errors, context):
|
||||||
|
if context != 'stylesheet':
|
||||||
|
raise ParseError(rule,
|
||||||
|
'@import rule not allowed in ' + context)
|
||||||
|
for previous_rule in previous_rules:
|
||||||
|
if previous_rule.at_keyword not in ('@charset', '@import'):
|
||||||
|
if previous_rule.at_keyword:
|
||||||
|
type_ = 'an {0} rule'.format(previous_rule.at_keyword)
|
||||||
|
else:
|
||||||
|
type_ = 'a ruleset'
|
||||||
|
raise ParseError(previous_rule,
|
||||||
|
'@import rule not allowed after ' + type_)
|
||||||
|
head = rule.head
|
||||||
|
if not head:
|
||||||
|
raise ParseError(rule,
|
||||||
|
'expected URI or STRING for @import rule')
|
||||||
|
if head[0].type not in ('URI', 'STRING'):
|
||||||
|
raise ParseError(rule,
|
||||||
|
'expected URI or STRING for @import rule, got '
|
||||||
|
+ head[0].type)
|
||||||
|
uri = head[0].value
|
||||||
|
media = self.parse_media(strip_whitespace(head[1:]))
|
||||||
|
if rule.body is not None:
|
||||||
|
# The position of the ';' token would be best, but we don’t
|
||||||
|
# have it anymore here.
|
||||||
|
raise ParseError(head[-1], "expected ';', got a block")
|
||||||
|
return ImportRule(uri, media, rule.line, rule.column)
|
||||||
|
|
||||||
|
def parse_charset_rule(self, rule, previous_rules, errors, context):
|
||||||
|
raise ParseError(rule, 'mis-placed or malformed @charset rule')
|
||||||
|
|
||||||
def parse_media(self, tokens):
|
def parse_media(self, tokens):
|
||||||
"""For CSS 2.1, parse a list of media types.
|
"""For CSS 2.1, parse a list of media types.
|
||||||
|
@ -26,13 +26,14 @@ class CSSFonts3Parser(CSS21Parser):
|
|||||||
|
|
||||||
''' Parse @font-face rules from the CSS 3 fonts module '''
|
''' Parse @font-face rules from the CSS 3 fonts module '''
|
||||||
|
|
||||||
ALLOWED_CONTEXTS = {'stylesheet', '@media', '@page'}
|
ALLOWED_CONTEXTS_FOR_FONT_FACE = {'stylesheet', '@media', '@page'}
|
||||||
|
|
||||||
def parse_at_rule(self, rule, previous_rules, errors, context):
|
def __init__(self):
|
||||||
if rule.at_keyword != '@font-face':
|
super(CSSFonts3Parser, self).__init__()
|
||||||
return super(CSSFonts3Parser, self).parse_at_rule(
|
self.at_parsers['@font-face'] = self.parse_font_face_rule
|
||||||
rule, previous_rules, errors, context)
|
|
||||||
if context not in self.ALLOWED_CONTEXTS:
|
def parse_font_face_rule(self, rule, previous_rules, errors, context):
|
||||||
|
if context not in self.ALLOWED_CONTEXTS_FOR_FONT_FACE:
|
||||||
raise ParseError(rule,
|
raise ParseError(rule,
|
||||||
'@font-face rule not allowed in ' + context)
|
'@font-face rule not allowed in ' + context)
|
||||||
if rule.body is None:
|
if rule.body is None:
|
||||||
|
@ -88,7 +88,7 @@ class CSSPage3Parser(CSS21Parser):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PAGE_MARGIN_AT_KEYWORDS = {
|
PAGE_MARGIN_AT_KEYWORDS = (
|
||||||
'@top-left-corner',
|
'@top-left-corner',
|
||||||
'@top-left',
|
'@top-left',
|
||||||
'@top-center',
|
'@top-center',
|
||||||
@ -105,23 +105,25 @@ class CSSPage3Parser(CSS21Parser):
|
|||||||
'@right-top',
|
'@right-top',
|
||||||
'@right-middle',
|
'@right-middle',
|
||||||
'@right-bottom',
|
'@right-bottom',
|
||||||
}
|
)
|
||||||
|
|
||||||
def parse_at_rule(self, rule, previous_rules, errors, context):
|
def __init__(self):
|
||||||
if rule.at_keyword in self.PAGE_MARGIN_AT_KEYWORDS:
|
super(CSSPage3Parser, self).__init__()
|
||||||
if context != '@page':
|
for x in self.PAGE_MARGIN_AT_KEYWORDS:
|
||||||
raise ParseError(rule,
|
self.at_parsers[x] = self.parse_page_margin_rule
|
||||||
'%s rule not allowed in %s' % (rule.at_keyword, context))
|
|
||||||
if rule.head:
|
def parse_page_margin_rule(self, rule, previous_rules, errors, context):
|
||||||
raise ParseError(rule.head[0],
|
if context != '@page':
|
||||||
'unexpected %s token in %s rule header'
|
raise ParseError(rule,
|
||||||
% (rule.head[0].type, rule.at_keyword))
|
'%s rule not allowed in %s' % (rule.at_keyword, context))
|
||||||
declarations, body_errors = self.parse_declaration_list(rule.body)
|
if rule.head:
|
||||||
errors.extend(body_errors)
|
raise ParseError(rule.head[0],
|
||||||
return MarginRule(rule.at_keyword, declarations,
|
'unexpected %s token in %s rule header'
|
||||||
rule.line, rule.column)
|
% (rule.head[0].type, rule.at_keyword))
|
||||||
return super(CSSPage3Parser, self).parse_at_rule(
|
declarations, body_errors = self.parse_declaration_list(rule.body)
|
||||||
rule, previous_rules, errors, context)
|
errors.extend(body_errors)
|
||||||
|
return MarginRule(rule.at_keyword, declarations,
|
||||||
|
rule.line, rule.column)
|
||||||
|
|
||||||
def parse_page_selector(self, head):
|
def parse_page_selector(self, head):
|
||||||
"""Parse an @page selector.
|
"""Parse an @page selector.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user