Refactor the @rule parsers into their own methods for easier sub-classing

This commit is contained in:
Kovid Goyal 2014-06-07 21:11:44 +05:30
parent 8ec267ae38
commit 49e4d09cbc
3 changed files with 98 additions and 87 deletions

View File

@ -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 dont
# 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 dont
# 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.

View File

@ -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:

View File

@ -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.