mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
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:
parent
fe0a84985a
commit
397b7cc4e1
@ -10,7 +10,7 @@ from calibre.gui2.tweak_book.editor.smart import NullSmarts
|
|||||||
|
|
||||||
def find_rule(raw, rule_address):
|
def find_rule(raw, rule_address):
|
||||||
import tinycss
|
import tinycss
|
||||||
parser = tinycss.make_parser()
|
parser = tinycss.make_parser('page3', 'fonts3')
|
||||||
sheet = parser.parse_stylesheet(raw)
|
sheet = parser.parse_stylesheet(raw)
|
||||||
rules = sheet.rules
|
rules = sheet.rules
|
||||||
ans = None, None
|
ans = None, None
|
||||||
|
@ -9,17 +9,17 @@
|
|||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .version import VERSION
|
from .version import VERSION
|
||||||
__version__ = VERSION
|
__version__ = VERSION
|
||||||
|
|
||||||
from .css21 import CSS21Parser
|
from .css21 import CSS21Parser
|
||||||
from .page3 import CSSPage3Parser
|
from .page3 import CSSPage3Parser
|
||||||
|
from .fonts3 import CSSFonts3Parser
|
||||||
|
|
||||||
|
|
||||||
PARSER_MODULES = {
|
PARSER_MODULES = {
|
||||||
'page3': CSSPage3Parser,
|
'page3': CSSPage3Parser,
|
||||||
|
'fonts3': CSSFonts3Parser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
42
src/tinycss/fonts3.py
Normal file
42
src/tinycss/fonts3.py
Normal 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)
|
||||||
|
|
@ -13,6 +13,17 @@ try:
|
|||||||
except NameError:
|
except NameError:
|
||||||
unicode = str
|
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):
|
class BaseTest(unittest.TestCase):
|
||||||
|
|
||||||
longMessage = True
|
longMessage = True
|
||||||
@ -25,3 +36,8 @@ class BaseTest(unittest.TestCase):
|
|||||||
for error, expected in zip(errors, expected_errors):
|
for error, expected in zip(errors, expected_errors):
|
||||||
self.assertIn(expected, unicode(error))
|
self.assertIn(expected, unicode(error))
|
||||||
|
|
||||||
|
def jsonify_declarations(self, rule):
|
||||||
|
return [(decl.name, list(jsonify(decl.value)))
|
||||||
|
for decl in rule.declarations]
|
||||||
|
|
||||||
|
|
||||||
|
31
src/tinycss/tests/fonts3.py
Normal file
31
src/tinycss/tests/fonts3.py
Normal 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'])
|
||||||
|
|
||||||
|
|
@ -8,7 +8,6 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
from tinycss.page3 import CSSPage3Parser
|
from tinycss.page3 import CSSPage3Parser
|
||||||
from tinycss.tests import BaseTest
|
from tinycss.tests import BaseTest
|
||||||
from tinycss.tests.tokenizing import jsonify
|
|
||||||
|
|
||||||
class TestPage3(BaseTest):
|
class TestPage3(BaseTest):
|
||||||
|
|
||||||
@ -79,14 +78,10 @@ class TestPage3(BaseTest):
|
|||||||
stylesheet = CSSPage3Parser().parse_stylesheet(css)
|
stylesheet = CSSPage3Parser().parse_stylesheet(css)
|
||||||
self.assert_errors(stylesheet.errors, expected_errors)
|
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)
|
self.ae(len(stylesheet.rules), 1)
|
||||||
rule = stylesheet.rules[0]
|
rule = stylesheet.rules[0]
|
||||||
self.ae(rule.at_keyword, '@page')
|
self.ae(rule.at_keyword, '@page')
|
||||||
self.ae(declarations(rule), expected_declarations)
|
self.ae(self.jsonify_declarations(rule), expected_declarations)
|
||||||
rules = [(margin_rule.at_keyword, declarations(margin_rule))
|
rules = [(margin_rule.at_keyword, self.jsonify_declarations(margin_rule))
|
||||||
for margin_rule in rule.at_rules]
|
for margin_rule in rule.at_rules]
|
||||||
self.ae(rules, expected_rules)
|
self.ae(rules, expected_rules)
|
||||||
|
@ -6,20 +6,9 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__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
|
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:
|
if c_tokenize_flat is None:
|
||||||
tokenizers = (python_tokenize_flat,)
|
tokenizers = (python_tokenize_flat,)
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user