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
def media_allowed(media):
if not media or not media.mediaText:
return True
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
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 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_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)
'''
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:
if rule.type == CSSRule.IMPORT_RULE:
if media_rule_ok(rule.media):
name = container.href_to_name(rule.href, sheet_name)
if container.has_name(name):
if name in importing:
container.log.error('Recursive import of {} from {}, ignoring'.format(name, sheet_name))
else:
csheet = container.parsed(name)
if isinstance(csheet, CSSStyleSheet):
for cr in riter(csheet, name):
for cr in riter(name, rules=csheet):
yield cr
elif rule.type == CSSRule.MEDIA_RULE:
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
elif rule_type is None or rule.type == rule_type:
num = next(rule_index_counter)
yield rule, sheet_name, num
importing.discard(sheet_name)
StyleDeclaration = namedtuple('StyleDeclaration', 'index declaration pseudo_element')
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)
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:
text = selector.selectorText
try:

View File

@ -186,7 +186,6 @@ class ContainerBase(object): # {{{
css_preprocessor=(None if self.tweak_mode else self.css_preprocessor))
# }}}
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'
}
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'})
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):
self.stream.flush()
def prints(self, level, *args, **kwargs):
self._prints(*args, **kwargs)
class ANSIStream(Stream):
def __init__(self, stream=sys.stdout):