Edit Book: Live CSS: Fix going to style declaration in the editor not working correctly when the stylesheet contains @font-face rules

This commit is contained in:
Kovid Goyal 2014-06-07 14:38:27 +05:30
parent fe0a84985a
commit 397b7cc4e1
7 changed files with 95 additions and 22 deletions

View File

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

View File

@ -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,
}

42
src/tinycss/fonts3.py Normal file
View File

@ -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 <kovid at kovidgoyal.net>'
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)

View File

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

View File

@ -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 <kovid at kovidgoyal.net>'
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'])

View File

@ -8,7 +8,6 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
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)

View File

@ -6,20 +6,9 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
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: