From 0d77ec145139705fd67f4bb37445698b42550360 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 12 Apr 2016 09:21:15 +0530 Subject: [PATCH] Tests for resolving properties --- src/calibre/ebooks/oeb/polish/cascade.py | 23 +++++++----- .../ebooks/oeb/polish/tests/cascade.py | 35 +++++++++++++++++-- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/cascade.py b/src/calibre/ebooks/oeb/polish/cascade.py index c5efea8853..f6a6f07f3b 100644 --- a/src/calibre/ebooks/oeb/polish/cascade.py +++ b/src/calibre/ebooks/oeb/polish/cascade.py @@ -93,6 +93,7 @@ def iterdeclaration(decl): else: for k, v in n(p.name, p.propertyValue).iteritems(): yield Property(k, v, p.literalpriority) + class Values(tuple): ''' A tuple of `cssutils.css.Value ` (and its subclasses) objects. Also has a @@ -105,6 +106,12 @@ class Values(tuple): ans.is_important = priority == 'important' return ans + @property + def cssText(self): + if len(self) == 1: + return self[0].cssText + return tuple(x.cssText for x in self) + def normalize_style_declaration(decl, sheet_name): ans = {} for prop in iterdeclaration(decl): @@ -129,16 +136,16 @@ def resolve_declarations(decls): ans[name] = first_val return ans -def resolve_styles(container, name): +def resolve_styles(container, name, select=None): root = container.parsed(name) - select = Select(root, ignore_inappropriate_pseudo_classes=True) + select = select or Select(root, ignore_inappropriate_pseudo_classes=True) style_map = defaultdict(list) pseudo_style_map = defaultdict(list) rule_index_counter = count() pseudo_pat = re.compile(ur':{1,2}(%s)' % ('|'.join(INAPPROPRIATE_PSEUDO_CLASSES)), re.I) def process_sheet(sheet, sheet_name): - for rule, sheet_name, rule_index in iterrules(container, sheet_name, rules=sheet, rule_index_counter=rule_index_counter, rule_type=CSSRule.STYLE_RULE): + for rule, sheet_name, rule_index in iterrules(container, sheet_name, rules=sheet, rule_index_counter=rule_index_counter, rule_type='STYLE_RULE'): for selector in rule.selectorList: text = selector.selectorText try: @@ -155,7 +162,7 @@ def resolve_styles(container, name): for elem in matches: pseudo_style_map[elem].append(StyleDeclaration(specificity(rule_index, selector), style, m.group(1))) - process_sheet(html_css_stylesheet(), 'user-agent.css') + process_sheet(html_css_stylesheet(container), 'user-agent.css') for elem in root.iterdescendants(XHTML('style'), XHTML('link')): if elem.tag.lower().endswith('style'): @@ -192,7 +199,7 @@ def resolve_styles(container, name): style_map = {elem:resolve_declarations(x) for elem, x in style_map.iteritems()} pseudo_style_map = {elem:resolve_declarations(x) for elem, x in pseudo_style_map.iteritems()} - return style_map, pseudo_style_map + return style_map, pseudo_style_map, select _defvals = None @@ -203,7 +210,7 @@ def defvals(): _defvals = {k:Values(Property(k, u(val)).propertyValue) for k, val in DEFAULTS.iteritems()} return _defvals -def get_resolved_property(elem, name, style_map): +def resolve_property(elem, name, style_map): ''' Given a `style_map` previously generated by :func:`resolve_styles()` and a property `name`, returns the effective value of that property for the specified element. Handles inheritance and CSS cascading rules. Returns @@ -212,11 +219,11 @@ def get_resolved_property(elem, name, style_map): inheritable = name in INHERITED q = elem - while q: + while q is not None: s = style_map.get(q) if s is not None: val = s.get(name) if val is not None: return val - q = elem.getparent() if inheritable else None + q = q.getparent() if inheritable else None return defvals().get(name) diff --git a/src/calibre/ebooks/oeb/polish/tests/cascade.py b/src/calibre/ebooks/oeb/polish/tests/cascade.py index 06b0e1e75f..f616582c47 100644 --- a/src/calibre/ebooks/oeb/polish/tests/cascade.py +++ b/src/calibre/ebooks/oeb/polish/tests/cascade.py @@ -4,12 +4,13 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) __license__ = 'GPL v3' -__copyright__ = '2013, Kovid Goyal ' +__copyright__ = '2016, Kovid Goyal ' + +from functools import partial from calibre.constants import iswindows - from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS -from calibre.ebooks.oeb.polish.cascade import iterrules +from calibre.ebooks.oeb.polish.cascade import iterrules, resolve_property, resolve_styles, DEFAULTS from calibre.ebooks.oeb.polish.container import ContainerBase, href_to_name from calibre.ebooks.oeb.polish.tests.base import BaseTest from calibre.utils.logging import Log, Stream @@ -61,3 +62,31 @@ class CascadeTest(BaseTest): get_rules({'x/one.css':'@media xyz { body { color: red; } }'}, l=0) c = get_rules({'x/one.css':'@import "../two.css";', 'two.css':'@import "x/one.css"; body { color: red; }'})[1] self.assertIn('Recursive import', c.log_stream.getvalue().decode('utf-8')) + + def test_resolve_styles(self): + + def test_property(select, style_map, selector, name, val=None): + elem = next(select(selector)) + ans = resolve_property(elem, name, style_map) + if val is None: + val = type('')(DEFAULTS[name]) + self.assertEqual(val, ans.cssText) + + def get_maps(html, styles=None): + html = '{}'.format(html) + c = VirtualContainer({'index.html':html, 'styles.css':styles or 'body { color: red }'}) + style_map, pseudo_style_map, select = resolve_styles(c, 'index.html') + tp = partial(test_property, select, style_map) + return tp + + t = get_maps('

xxx

') + t('body', 'color', 'red') + t('p', 'color', 'red') + t('b', 'font-weight', 'bold') + t('p', 'margin-top', '11pt') + t('b', 'margin-top') + t('body', 'display', 'block') + t('b', 'display', 'inline') + for e in ('body', 'p', 'b'): + for prop in 'background-color text-indent'.split(): + t(e, prop)