diff --git a/src/tinycss/css21.py b/src/tinycss/css21.py index 51e6529226..69738d3914 100644 --- a/src/tinycss/css21.py +++ b/src/tinycss/css21.py @@ -13,11 +13,12 @@ from __future__ import unicode_literals from itertools import chain, islice -from .decoding import decode -from .token_data import TokenList -from .tokenizer import tokenize_grouped -from .parsing import (strip_whitespace, remove_whitespace, split_on_comma, - validate_value, validate_block, validate_any, ParseError) +from tinycss.decoding import decode +from tinycss.token_data import TokenList +from tinycss.tokenizer import tokenize_grouped +from tinycss.parsing import ( + strip_whitespace, remove_whitespace, split_on_comma, validate_value, + validate_any, ParseError) # stylesheet : [ CDO | CDC | S | statement ]*; @@ -293,7 +294,6 @@ class ImportRule(object): ' {0.uri}>'.format(self)) - def _remove_at_charset(tokens): """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: def parse_stylesheet_file(self, css_file, protocol_encoding=None, @@ -507,66 +511,70 @@ class CSS21Parser(object): A parsed at-rule """ - if rule.at_keyword == '@page': - 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) - - 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: + try: + parser = self.at_parsers[rule.at_keyword] + except KeyError: raise ParseError(rule, 'unknown at-rule in {0} context: {1}' .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): """For CSS 2.1, parse a list of media types. diff --git a/src/tinycss/fonts3.py b/src/tinycss/fonts3.py index e736d9116a..e469ee5296 100644 --- a/src/tinycss/fonts3.py +++ b/src/tinycss/fonts3.py @@ -26,13 +26,14 @@ class CSSFonts3Parser(CSS21Parser): ''' 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): - if rule.at_keyword != '@font-face': - return super(CSSFonts3Parser, self).parse_at_rule( - rule, previous_rules, errors, context) - if context not in self.ALLOWED_CONTEXTS: + def __init__(self): + super(CSSFonts3Parser, self).__init__() + self.at_parsers['@font-face'] = self.parse_font_face_rule + + def parse_font_face_rule(self, rule, previous_rules, errors, context): + if context not in self.ALLOWED_CONTEXTS_FOR_FONT_FACE: raise ParseError(rule, '@font-face rule not allowed in ' + context) if rule.body is None: diff --git a/src/tinycss/page3.py b/src/tinycss/page3.py index 292f87fff1..b5b09ffb8f 100644 --- a/src/tinycss/page3.py +++ b/src/tinycss/page3.py @@ -88,7 +88,7 @@ class CSSPage3Parser(CSS21Parser): """ - PAGE_MARGIN_AT_KEYWORDS = { + PAGE_MARGIN_AT_KEYWORDS = ( '@top-left-corner', '@top-left', '@top-center', @@ -105,23 +105,25 @@ class CSSPage3Parser(CSS21Parser): '@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, - '%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) - return super(CSSPage3Parser, self).parse_at_rule( - rule, previous_rules, errors, context) + 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.