Framework for testing cascade.py

This commit is contained in:
Kovid Goyal 2016-04-12 08:10:45 +05:30
parent c2ef186d9f
commit 176b6b248d
5 changed files with 94 additions and 14 deletions

View File

@ -29,40 +29,55 @@ def html_css_stylesheet(container):
return _html_css_stylesheet return _html_css_stylesheet
def media_allowed(media): def media_allowed(media):
if not media or not media.mediaText:
return True
return media_ok(media.mediaText) return media_ok(media.mediaText)
def iterrules(container, rules, sheet_name, media_rule_ok=media_allowed, rule_index_counter=None, rule_type=None): def iterrules(container, sheet_name, rules=None, media_rule_ok=media_allowed, rule_index_counter=None, rule_type=None, importing=None):
''' Iterate over all style rules in the specified sheet. Import and Media rules are ''' Iterate over all style rules in the specified sheet. Import and Media rules are
automatically resolved. Yields (rule, sheet_name, rule_number). automatically resolved. Yields (rule, sheet_name, rule_number).
:param rules: List of CSSRules or a CSSStyleSheet instance :param rules: List of CSSRules or a CSSStyleSheet instance or None in which case it is read from container using sheet_name
:param sheet_name: The name of the sheet in the container (in case of inline style sheets, the name of the html file) :param sheet_name: The name of the sheet in the container (in case of inline style sheets, the name of the html file)
:param media_rule_ok: A function to test if a @media rule is allowed :param media_rule_ok: A function to test if a @media rule is allowed
:param rule_index_counter: A counter object, rule numbers will be calculated by incrementing the counter. :param rule_index_counter: A counter object, rule numbers will be calculated by incrementing the counter.
:param rule_type: Only yield rules of this type (by default all rules are yielded :param rule_type: Only yield rules of this type, where type is a string type name, see cssutils.css.CSSRule for the names (by default all rules are yielded)
:return: (CSSRule object, the name of the sheet from which it comes, rule index - a monotonically increasing number) :return: (CSSRule object, the name of the sheet from which it comes, rule index - a monotonically increasing number)
''' '''
rule_index_counter = rule_index_counter or count() rule_index_counter = rule_index_counter or count()
riter = partial(iterrules, container, rule_index_counter=rule_index_counter, media_rule_ok=media_rule_ok, rule_type=rule_type) if importing is None:
importing = set()
importing.add(sheet_name)
riter = partial(iterrules, container, rule_index_counter=rule_index_counter, media_rule_ok=media_rule_ok, rule_type=rule_type, importing=importing)
if rules is None:
rules = container.parsed(sheet_name)
if rule_type is not None:
rule_type = getattr(CSSRule, rule_type)
for rule in rules: for rule in rules:
if rule.type == CSSRule.IMPORT_RULE: if rule.type == CSSRule.IMPORT_RULE:
name = container.href_to_name(rule.href, sheet_name) if media_rule_ok(rule.media):
if container.has_name(name): name = container.href_to_name(rule.href, sheet_name)
csheet = container.parsed(name) if container.has_name(name):
if isinstance(csheet, CSSStyleSheet): if name in importing:
for cr in riter(csheet, name): container.log.error('Recursive import of {} from {}, ignoring'.format(name, sheet_name))
yield cr else:
csheet = container.parsed(name)
if isinstance(csheet, CSSStyleSheet):
for cr in riter(name, rules=csheet):
yield cr
elif rule.type == CSSRule.MEDIA_RULE: elif rule.type == CSSRule.MEDIA_RULE:
if media_rule_ok(rule.media): if media_rule_ok(rule.media):
for cr in riter(rule.cssRules, sheet_name): for cr in riter(sheet_name, rules=rule.cssRules):
yield cr yield cr
elif rule_type is None or rule.type == rule_type: elif rule_type is None or rule.type == rule_type:
num = next(rule_index_counter) num = next(rule_index_counter)
yield rule, sheet_name, num yield rule, sheet_name, num
importing.discard(sheet_name)
StyleDeclaration = namedtuple('StyleDeclaration', 'index declaration pseudo_element') StyleDeclaration = namedtuple('StyleDeclaration', 'index declaration pseudo_element')
Specificity = namedtuple('Specificity', 'is_style num_id num_class num_elem rule_index') Specificity = namedtuple('Specificity', 'is_style num_id num_class num_elem rule_index')
@ -123,7 +138,7 @@ def resolve_styles(container, name):
pseudo_pat = re.compile(ur':{1,2}(%s)' % ('|'.join(INAPPROPRIATE_PSEUDO_CLASSES)), re.I) pseudo_pat = re.compile(ur':{1,2}(%s)' % ('|'.join(INAPPROPRIATE_PSEUDO_CLASSES)), re.I)
def process_sheet(sheet, sheet_name): def process_sheet(sheet, sheet_name):
for rule, sheet_name, rule_index in iterrules(container, sheet, sheet_name, 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=CSSRule.STYLE_RULE):
for selector in rule.selectorList: for selector in rule.selectorList:
text = selector.selectorText text = selector.selectorText
try: try:

View File

@ -186,7 +186,6 @@ class ContainerBase(object): # {{{
css_preprocessor=(None if self.tweak_mode else self.css_preprocessor)) css_preprocessor=(None if self.tweak_mode else self.css_preprocessor))
# }}} # }}}
class Container(ContainerBase): # {{{ class Container(ContainerBase): # {{{
''' '''

View File

@ -0,0 +1,63 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
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.container import ContainerBase, href_to_name
from calibre.ebooks.oeb.polish.tests.base import BaseTest
from calibre.utils.logging import Log, Stream
class VirtualContainer(ContainerBase):
tweak_mode = True
def __init__(self, files):
s = Stream()
self.log_stream = s.stream
log = Log()
log.outputs = [s]
ContainerBase.__init__(self, log=log)
self.mime_map = {k:self.guess_type(k) for k in files}
self.files = files
def has_name(self, name):
return name in self.mime_map
def href_to_name(self, href, base=None):
return href_to_name(href, ('C:\\root' if iswindows else '/root'), base)
def parsed(self, name):
if name not in self.parsed_cache:
mt = self.mime_map[name]
if mt in OEB_STYLES:
self.parsed_cache[name] = self.parse_css(self.files[name], name)
elif mt in OEB_DOCS:
self.parsed_cache[name] = self.parse_xhtml(self.files[name], name)
else:
self.parsed_cache[name] = self.files[name]
return self.parsed_cache[name]
class CascadeTest(BaseTest):
def test_iterrules(self):
def get_rules(files, name='x/one.css', l=1, rule_type=None):
c = VirtualContainer(files)
rules = tuple(iterrules(c, name, rule_type=rule_type))
self.assertEqual(len(rules), l)
return rules, c
get_rules({'x/one.css':'@import "../two.css";', 'two.css':'body { color: red; }'})
get_rules({'x/one.css':'@import "../two.css" screen;', 'two.css':'body { color: red; }'})
get_rules({'x/one.css':'@import "../two.css" xyz;', 'two.css':'body { color: red; }'}, l=0)
get_rules({'x/one.css':'@import "../two.css";', 'two.css':'body { color: red; }'}, l=0, rule_type='FONT_FACE_RULE')
get_rules({'x/one.css':'@import "../two.css";', 'two.css':'body { color: red; }'}, rule_type='STYLE_RULE')
get_rules({'x/one.css':'@media screen { body { color: red; } }'})
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'))

View File

@ -50,7 +50,6 @@ FONT_SIZE_NAMES = {
'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'
} }
ALL_MEDIA_TYPES = frozenset('all aural braille handheld print projection screen tty tv embossed amzn-mobi amzn-kf8'.split())
ALLOWED_MEDIA_TYPES = frozenset({'screen', 'all', 'aural', 'amzn-kf8'}) ALLOWED_MEDIA_TYPES = frozenset({'screen', 'all', 'aural', 'amzn-kf8'})
IGNORED_MEDIA_FEATURES = frozenset('width min-width max-width height min-height max-height device-width min-device-width max-device-width device-height min-device-height max-device-height aspect-ratio min-aspect-ratio max-aspect-ratio device-aspect-ratio min-device-aspect-ratio max-device-aspect-ratio color min-color max-color color-index min-color-index max-color-index monochrome min-monochrome max-monochrome -webkit-min-device-pixel-ratio resolution min-resolution max-resolution scan grid'.split()) # noqa IGNORED_MEDIA_FEATURES = frozenset('width min-width max-width height min-height max-height device-width min-device-width max-device-width device-height min-device-height max-device-height aspect-ratio min-aspect-ratio max-aspect-ratio device-aspect-ratio min-device-aspect-ratio max-device-aspect-ratio color min-color max-color color-index min-color-index max-color-index monochrome min-monochrome max-monochrome -webkit-min-device-pixel-ratio resolution min-resolution max-resolution scan grid'.split()) # noqa

View File

@ -27,6 +27,10 @@ class Stream(object):
def flush(self): def flush(self):
self.stream.flush() self.stream.flush()
def prints(self, level, *args, **kwargs):
self._prints(*args, **kwargs)
class ANSIStream(Stream): class ANSIStream(Stream):
def __init__(self, stream=sys.stdout): def __init__(self, stream=sys.stdout):