diff --git a/src/calibre/gui2/tweak_book/editor/smart/css.py b/src/calibre/gui2/tweak_book/editor/smart/css.py index c51c1dfa55..fce627d12b 100644 --- a/src/calibre/gui2/tweak_book/editor/smart/css.py +++ b/src/calibre/gui2/tweak_book/editor/smart/css.py @@ -10,7 +10,7 @@ from calibre.gui2.tweak_book.editor.smart import NullSmarts def find_rule(raw, rule_address): import tinycss - parser = tinycss.make_parser() + parser = tinycss.make_parser('page3', 'fonts3') sheet = parser.parse_stylesheet(raw) rules = sheet.rules ans = None, None diff --git a/src/tinycss/__init__.py b/src/tinycss/__init__.py index 9eca2b1b46..eb981e0e48 100644 --- a/src/tinycss/__init__.py +++ b/src/tinycss/__init__.py @@ -9,17 +9,17 @@ :license: BSD, see LICENSE for more details. """ -import sys - from .version import VERSION __version__ = VERSION from .css21 import CSS21Parser from .page3 import CSSPage3Parser +from .fonts3 import CSSFonts3Parser PARSER_MODULES = { 'page3': CSSPage3Parser, + 'fonts3': CSSFonts3Parser, } diff --git a/src/tinycss/fonts3.py b/src/tinycss/fonts3.py new file mode 100644 index 0000000000..2853de520f --- /dev/null +++ b/src/tinycss/fonts3.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + + +from .css21 import CSS21Parser, ParseError + +class FontFaceRule(object): + + at_keyword = '@font-face' + + def __init__(self, declarations, line, column): + self.declarations = declarations + self.line = line + self.column = column + +class CSSFonts3Parser(CSS21Parser): + + ''' Parse @font-face rules from the CSS 3 fonts module ''' + + ALLOWED_CONTEXTS = {'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: + raise ParseError(rule, + '@font-face rule not allowed in ' + context) + if rule.body is None: + raise ParseError(rule, + 'invalid {0} rule: missing block'.format(rule.at_keyword)) + if rule.head: + raise ParseError(rule, '{0} rule is not allowed to have content before the descriptor declaration'.format(rule.at_keyword)) + declarations, decerrors = self.parse_declaration_list(rule.body) + errors.extend(decerrors) + return FontFaceRule(declarations, rule.line, rule.column) + diff --git a/src/tinycss/tests/__init__.py b/src/tinycss/tests/__init__.py index caae5d68d4..e76516f625 100644 --- a/src/tinycss/tests/__init__.py +++ b/src/tinycss/tests/__init__.py @@ -13,6 +13,17 @@ try: except NameError: unicode = str +def jsonify(tokens): + """Turn tokens into "JSON-compatible" data structures.""" + for token in tokens: + if token.type == 'FUNCTION': + yield (token.type, token.function_name, + list(jsonify(token.content))) + elif token.is_container: + yield token.type, list(jsonify(token.content)) + else: + yield token.type, token.value + class BaseTest(unittest.TestCase): longMessage = True @@ -25,3 +36,8 @@ class BaseTest(unittest.TestCase): for error, expected in zip(errors, expected_errors): self.assertIn(expected, unicode(error)) + def jsonify_declarations(self, rule): + return [(decl.name, list(jsonify(decl.value))) + for decl in rule.declarations] + + diff --git a/src/tinycss/tests/fonts3.py b/src/tinycss/tests/fonts3.py new file mode 100644 index 0000000000..5ed4d9381b --- /dev/null +++ b/src/tinycss/tests/fonts3.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + +from tinycss.fonts3 import CSSFonts3Parser +from tinycss.tests import BaseTest + +class TestFonts3(BaseTest): + + def test_font_face(self): + 'Test parsing of font face rules' + for css, expected_declarations, expected_errors in [ + ('@font-face {}', [], []), + + ('@font-face { font-family: Moose; src: url(font1.ttf) }', + [('font-family', [('IDENT', 'Moose')]), ('src', [('URI', 'font1.ttf')])], []), + ]: + stylesheet = CSSFonts3Parser().parse_stylesheet(css) + self.assert_errors(stylesheet.errors, expected_errors) + self.ae(len(stylesheet.rules), 1) + rule = stylesheet.rules[0] + self.ae(self.jsonify_declarations(rule), expected_declarations) + + stylesheet = CSSFonts3Parser().parse_stylesheet('@font-face;') + self.assert_errors(stylesheet.errors, ['missing block']) + + diff --git a/src/tinycss/tests/page3.py b/src/tinycss/tests/page3.py index 4a6236211d..bfc24d7043 100644 --- a/src/tinycss/tests/page3.py +++ b/src/tinycss/tests/page3.py @@ -8,7 +8,6 @@ __copyright__ = '2014, Kovid Goyal ' from tinycss.page3 import CSSPage3Parser from tinycss.tests import BaseTest -from tinycss.tests.tokenizing import jsonify class TestPage3(BaseTest): @@ -79,14 +78,10 @@ class TestPage3(BaseTest): stylesheet = CSSPage3Parser().parse_stylesheet(css) self.assert_errors(stylesheet.errors, expected_errors) - def declarations(rule): - return [(decl.name, list(jsonify(decl.value))) - for decl in rule.declarations] - self.ae(len(stylesheet.rules), 1) rule = stylesheet.rules[0] self.ae(rule.at_keyword, '@page') - self.ae(declarations(rule), expected_declarations) - rules = [(margin_rule.at_keyword, declarations(margin_rule)) + self.ae(self.jsonify_declarations(rule), expected_declarations) + rules = [(margin_rule.at_keyword, self.jsonify_declarations(margin_rule)) for margin_rule in rule.at_rules] self.ae(rules, expected_rules) diff --git a/src/tinycss/tests/tokenizing.py b/src/tinycss/tests/tokenizing.py index 004122cd06..d6f91f651d 100644 --- a/src/tinycss/tests/tokenizing.py +++ b/src/tinycss/tests/tokenizing.py @@ -6,20 +6,9 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -from tinycss.tests import BaseTest +from tinycss.tests import BaseTest, jsonify from tinycss.tokenizer import python_tokenize_flat, c_tokenize_flat, regroup -def jsonify(tokens): - """Turn tokens into "JSON-compatible" data structures.""" - for token in tokens: - if token.type == 'FUNCTION': - yield (token.type, token.function_name, - list(jsonify(token.content))) - elif token.is_container: - yield token.type, list(jsonify(token.content)) - else: - yield token.type, token.value - if c_tokenize_flat is None: tokenizers = (python_tokenize_flat,) else: