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:]), errors) 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 read_at_rule(self, at_keyword_token, tokens): """Read an at-rule from a token stream. :param at_keyword_token: The ATKEYWORD token that starts this at-rule You may have read it already to distinguish the rule from a ruleset. :param tokens: An iterator of subsequent tokens. Will be consumed just enough for one at-rule. :return: An unparsed :class:`AtRule`. :raises: :class:`~.parsing.ParseError` if the head is invalid for the core grammar. The body is **not** validated. See :class:`AtRule`. """ # CSS syntax is case-insensitive at_keyword = at_keyword_token.value.lower() head = [] # For the ParseError in case `tokens` is empty: token = at_keyword_token for token in tokens: if token.type in '{;': break # Ignore white space just after the at-keyword. else: head.append(token) # On unexpected end of stylesheet, pretend that a ';' was there head = strip_whitespace(head) for head_token in head: validate_any(head_token, 'at-rule head') body = token.content if token.type == '{' else None return AtRule(at_keyword, head, body, at_keyword_token.line, at_keyword_token.column)
def _selector_as_string(self, selector): """ Returns a selector as a CSS string :param selector: A list of tinycss Tokens :type selector: list :returns: The CSS string for the selector :rtype: str """ return ','.join(''.join(token.as_css() for token in strip_whitespace(token_list)) for token_list in split_on_comma(selector))
def _selector_as_string(self, selector): """ Returns a selector as a CSS string :param selector: A list of tinycss Tokens :type selector: list :returns: The CSS string for the selector :rtype: str """ return ",".join( "".join(token.as_css() for token in strip_whitespace(token_list)) for token_list in split_on_comma(selector) )
def parse_declaration(self, tokens): """Parse a single declaration. :param tokens: an iterable of at least one token. Should stop at (before) the end of the declaration, as marked by a ``;`` or ``}``. Empty declarations (ie. consecutive ``;`` with only white space in-between) should be skipped earlier and not passed to this method. :returns: a :class:`Declaration` :raises: :class:`~.parsing.ParseError` if the tokens do not match the 'declaration' production of the core grammar. """ tokens = iter(tokens) name_token = next(tokens) # assume there is at least one if name_token.type == 'IDENT': # CSS syntax is case-insensitive property_name = name_token.value.lower() else: raise ParseError( name_token, 'expected a property name, got {0}'.format(name_token.type)) token = name_token # In case ``tokens`` is now empty for token in tokens: if token.type == ':': break elif token.type != 'S': raise ParseError(token, "expected ':', got {0}".format(token.type)) else: raise ParseError(token, "expected ':'") value = strip_whitespace(list(tokens)) if not value: raise ParseError(token, 'expected a property value') validate_value(value) value, priority = self.parse_value_priority(value) return Declaration(property_name, value, priority, name_token.line, name_token.column)
def parse_declaration(self, tokens): """Parse a single declaration. :param tokens: an iterable of at least one token. Should stop at (before) the end of the declaration, as marked by a ``;`` or ``}``. Empty declarations (ie. consecutive ``;`` with only white space in-between) should be skipped earlier and not passed to this method. :returns: a :class:`Declaration` :raises: :class:`~.parsing.ParseError` if the tokens do not match the 'declaration' production of the core grammar. """ tokens = iter(tokens) name_token = next(tokens) # assume there is at least one if name_token.type == 'IDENT': # CSS syntax is case-insensitive property_name = name_token.value.lower() else: raise ParseError(name_token, 'expected a property name, got {0}'.format(name_token.type)) token = name_token # In case ``tokens`` is now empty for token in tokens: if token.type == ':': break elif token.type != 'S': raise ParseError( token, "expected ':', got {0}".format(token.type)) else: raise ParseError(token, "expected ':'") value = strip_whitespace(list(tokens)) if not value: raise ParseError(token, 'expected a property value') validate_value(value) value, priority = self.parse_value_priority(value) return Declaration( property_name, value, priority, name_token.line, name_token.column)
def parse_ruleset(self, first_token, tokens): """Parse a ruleset: a selector followed by declaration block. :param first_token: The first token of the ruleset (probably of the selector). You may have read it already to distinguish the rule from an at-rule. :param tokens: an iterator of subsequent tokens. Will be consumed just enough for one ruleset. :return: a tuple of a :class:`RuleSet` and an error list. The errors are recovered :class:`~.parsing.ParseError` in declarations. (Parsing continues from the next declaration on such errors.) :raises: :class:`~.parsing.ParseError` if the selector is invalid for the core grammar. Note a that a selector can be valid for the core grammar but not for CSS 2.1 or another level. """ selector = [] for token in chain([first_token], tokens): if token.type == '{': # Parse/validate once we’ve read the whole rule selector = strip_whitespace(selector) if not selector: raise ParseError(first_token, 'empty selector') for selector_token in selector: validate_any(selector_token, 'selector') declarations, errors = self.parse_declaration_list( token.content) ruleset = RuleSet(selector, declarations, first_token.line, first_token.column) return ruleset, errors else: selector.append(token) raise ParseError(token, 'no declaration block found for ruleset')
def parse_declaration_list(self, tokens): """Parse a ``;`` separated declaration list. You may want to use :meth:`parse_declarations_and_at_rules` (or some other method that uses :func:`parse_declaration` directly) instead if you have not just declarations in the same context. :param tokens: an iterable of tokens. Should stop at (before) the end of the block, as marked by ``}``. :return: a tuple of the list of valid :class:`Declaration` and a list of :class:`~.parsing.ParseError` """ # split at ';' parts = [] this_part = [] for token in tokens: if token.type == ';': parts.append(this_part) this_part = [] else: this_part.append(token) parts.append(this_part) declarations = [] errors = [] for tokens in parts: tokens = strip_whitespace(tokens) if tokens: try: declarations.append(self.parse_declaration(tokens)) except ParseError as exc: errors.append(exc) # Skip the entire declaration return declarations, errors