557 lines
19 KiB
Python
557 lines
19 KiB
Python
"""Tests for parsing which does not raise Exceptions normally"""
|
|
|
|
|
|
import xml.dom
|
|
import cssutils
|
|
import urllib.request
|
|
import urllib.error
|
|
import urllib.parse
|
|
from unittest import mock
|
|
import pytest
|
|
|
|
|
|
class TestCSSParser:
|
|
def _make_fetcher(self, encoding, content):
|
|
"make an URL fetcher with specified data"
|
|
|
|
def fetcher(url):
|
|
return encoding, content
|
|
|
|
return fetcher
|
|
|
|
def test_init(self):
|
|
"CSSParser.__init__()"
|
|
assert cssutils.log.raiseExceptions
|
|
|
|
# also the default:
|
|
cssutils.log.raiseExceptions = True
|
|
|
|
# default non raising parser
|
|
p = cssutils.CSSParser()
|
|
s = p.parseString('$')
|
|
assert s.cssText == b''
|
|
|
|
# explicit raiseExceptions=False
|
|
p = cssutils.CSSParser(raiseExceptions=False)
|
|
s = p.parseString('$')
|
|
assert s.cssText == b''
|
|
|
|
# working with sheet does raise though!
|
|
with pytest.raises(xml.dom.DOMException):
|
|
s.__setattr__('cssText', '$')
|
|
|
|
# ----
|
|
|
|
# raiseExceptions=True
|
|
p = cssutils.CSSParser(raiseExceptions=True)
|
|
with pytest.raises(xml.dom.SyntaxErr):
|
|
p.parseString('$')
|
|
|
|
# working with a sheet does raise too
|
|
s = cssutils.css.CSSStyleSheet()
|
|
with pytest.raises(xml.dom.DOMException):
|
|
s.__setattr__('cssText', '$')
|
|
|
|
# RESET cssutils.log.raiseExceptions
|
|
cssutils.log.raiseExceptions = False
|
|
s = cssutils.css.CSSStyleSheet()
|
|
# does not raise!
|
|
s.__setattr__('cssText', '$')
|
|
assert s.cssText == b''
|
|
|
|
def test_parseComments(self):
|
|
"cssutils.CSSParser(parseComments=False)"
|
|
css = '/*1*/ a { color: /*2*/ red; }'
|
|
|
|
p = cssutils.CSSParser(parseComments=False)
|
|
assert p.parseString(css).cssText == b'a {\n color: red\n }'
|
|
p = cssutils.CSSParser(parseComments=True)
|
|
assert p.parseString(css).cssText == b'/*1*/\na {\n color: /*2*/ red\n }'
|
|
|
|
def test_parseUrl(self):
|
|
"CSSParser.parseUrl()"
|
|
# parseUrl(self, href, encoding=None, media=None, title=None):
|
|
parser = cssutils.CSSParser()
|
|
m = mock.Mock()
|
|
with mock.patch('cssutils.util._defaultFetcher', m):
|
|
m.return_value = (None, '')
|
|
sheet = parser.parseUrl(
|
|
'http://example.com', media='tv,print', title='test'
|
|
)
|
|
|
|
assert sheet.href == 'http://example.com'
|
|
assert sheet.encoding == 'utf-8'
|
|
assert sheet.media.mediaText == 'tv, print'
|
|
assert sheet.title == 'test'
|
|
|
|
# URL and content tests
|
|
tests = {
|
|
# (url, content): isSheet, encoding, cssText
|
|
('', None): (False, None, None),
|
|
('1', None): (False, None, None),
|
|
('mailto:a@bb.cd', None): (False, None, None),
|
|
('http://cthedot.de/test.css', None): (False, None, None),
|
|
('http://cthedot.de/test.css', ''): (True, 'utf-8', ''),
|
|
('http://cthedot.de/test.css', 'a'): (True, 'utf-8', ''),
|
|
('http://cthedot.de/test.css', 'a {color: red}'): (
|
|
True,
|
|
'utf-8',
|
|
'a {\n color: red\n }',
|
|
),
|
|
('http://cthedot.de/test.css', '@charset "ascii";a {color: red}'): (
|
|
True,
|
|
'ascii',
|
|
'@charset "ascii";\na {\n color: red\n }',
|
|
),
|
|
}
|
|
override = 'iso-8859-1'
|
|
overrideprefix = '@charset "iso-8859-1";'
|
|
httpencoding = None
|
|
|
|
for (url, content), (isSheet, expencoding, cssText) in list(tests.items()):
|
|
parser.setFetcher(self._make_fetcher(httpencoding, content))
|
|
sheet1 = parser.parseUrl(url)
|
|
sheet2 = parser.parseUrl(url, encoding=override)
|
|
if isSheet:
|
|
assert sheet1.encoding == expencoding
|
|
assert sheet1.cssText == cssText.encode()
|
|
assert sheet2.encoding == override
|
|
if sheet1.cssText and cssText.startswith('@charset'):
|
|
assert sheet2.cssText == (
|
|
cssText.replace('ascii', override).encode()
|
|
)
|
|
elif sheet1.cssText:
|
|
assert sheet2.cssText == (overrideprefix + '\n' + cssText).encode()
|
|
else:
|
|
assert sheet2.cssText == (overrideprefix + cssText).encode()
|
|
else:
|
|
assert sheet1 is None
|
|
assert sheet2 is None
|
|
|
|
parser.setFetcher(None)
|
|
|
|
with pytest.raises(ValueError):
|
|
parser.parseUrl('../not-valid-in-urllib')
|
|
with pytest.raises(urllib.error.HTTPError):
|
|
parser.parseUrl(
|
|
'http://cthedot.de/not-present.css',
|
|
)
|
|
|
|
def test_parseString(self):
|
|
"CSSParser.parseString()"
|
|
tests = {
|
|
# (byte) string, encoding: encoding, cssText
|
|
('/*a*/', None): ('utf-8', b'/*a*/'),
|
|
('/*a*/', 'ascii'): ('ascii', b'@charset "ascii";\n/*a*/'),
|
|
# org
|
|
# ('/*\xc3\xa4*/', None): (u'utf-8', u'/*\xc3\xa4*/'.encode('utf-8')),
|
|
# ('/*\xc3\xa4*/', 'utf-8'): (u'utf-8',
|
|
# u'@charset "utf-8";\n/*\xc3\xa4*/'.encode('utf-8')),
|
|
# new for 2.x and 3.x
|
|
('/*\xe4*/'.encode(), None): ('utf-8', '/*\xe4*/'.encode()),
|
|
('/*\xe4*/'.encode(), 'utf-8'): (
|
|
'utf-8',
|
|
'@charset "utf-8";\n/*\xe4*/'.encode(),
|
|
),
|
|
('@charset "ascii";/*a*/', None): (
|
|
'ascii',
|
|
b'@charset "ascii";\n/*a*/',
|
|
),
|
|
('@charset "utf-8";/*a*/', None): (
|
|
'utf-8',
|
|
b'@charset "utf-8";\n/*a*/',
|
|
),
|
|
('@charset "iso-8859-1";/*a*/', None): (
|
|
'iso-8859-1',
|
|
b'@charset "iso-8859-1";\n/*a*/',
|
|
),
|
|
# unicode string, no encoding: encoding, cssText
|
|
('/*€*/', None): ('utf-8', '/*€*/'.encode()),
|
|
('@charset "iso-8859-1";/*ä*/', None): (
|
|
'iso-8859-1',
|
|
'@charset "iso-8859-1";\n/*ä*/'.encode('iso-8859-1'),
|
|
),
|
|
('@charset "utf-8";/*€*/', None): (
|
|
'utf-8',
|
|
'@charset "utf-8";\n/*€*/'.encode(),
|
|
),
|
|
('@charset "utf-16";/**/', None): (
|
|
'utf-16',
|
|
'@charset "utf-16";\n/**/'.encode('utf-16'),
|
|
),
|
|
# unicode string, encoding utf-8: encoding, cssText
|
|
('/*€*/', 'utf-8'): ('utf-8', '@charset "utf-8";\n/*€*/'.encode()),
|
|
('@charset "iso-8859-1";/*ä*/', 'utf-8'): (
|
|
'utf-8',
|
|
'@charset "utf-8";\n/*ä*/'.encode(),
|
|
),
|
|
('@charset "utf-8";/*€*/', 'utf-8'): (
|
|
'utf-8',
|
|
'@charset "utf-8";\n/*€*/'.encode(),
|
|
),
|
|
('@charset "utf-16";/**/', 'utf-8'): (
|
|
'utf-8',
|
|
b'@charset "utf-8";\n/**/',
|
|
),
|
|
# probably not what is wanted but does not raise:
|
|
('/*€*/', 'ascii'): (
|
|
'ascii',
|
|
b'@charset "ascii";\n/*\\20AC */',
|
|
),
|
|
('/*€*/', 'iso-8859-1'): (
|
|
'iso-8859-1',
|
|
b'@charset "iso-8859-1";\n/*\\20AC */',
|
|
),
|
|
}
|
|
for test in tests:
|
|
css, encoding = test
|
|
sheet = cssutils.parseString(css, encoding=encoding)
|
|
encoding, cssText = tests[test]
|
|
assert encoding == sheet.encoding
|
|
assert cssText == sheet.cssText
|
|
|
|
tests = [
|
|
# encoded css, overiding encoding
|
|
('/*€*/'.encode('utf-16'), 'utf-8'),
|
|
('/*ä*/'.encode('iso-8859-1'), 'ascii'),
|
|
('/*€*/'.encode(), 'ascii'),
|
|
(b'a', 'utf-16'),
|
|
]
|
|
for test in tests:
|
|
# self.assertEqual(None, cssutils.parseString(css, encoding=encoding))
|
|
with pytest.raises(UnicodeDecodeError):
|
|
cssutils.parseString(test[0], test[1])
|
|
|
|
def test_validate(self):
|
|
"""CSSParser(validate)"""
|
|
style = 'color: red'
|
|
t = 'a { %s }' % style
|
|
|
|
# helper
|
|
s = cssutils.parseString(t)
|
|
assert s.validating
|
|
s = cssutils.parseString(t, validate=False)
|
|
assert s.validating is False
|
|
s = cssutils.parseString(t, validate=True)
|
|
assert s.validating
|
|
|
|
d = cssutils.parseStyle(style)
|
|
assert d.validating
|
|
d = cssutils.parseStyle(style, validate=True)
|
|
assert d.validating
|
|
d = cssutils.parseStyle(style, validate=False)
|
|
assert d.validating is False
|
|
|
|
# parser
|
|
p = cssutils.CSSParser()
|
|
s = p.parseString(t)
|
|
assert s.validating
|
|
s = p.parseString(t, validate=False)
|
|
assert s.validating is False
|
|
s = p.parseString(t, validate=True)
|
|
assert s.validating
|
|
d = p.parseStyle(style)
|
|
assert d.validating
|
|
|
|
p = cssutils.CSSParser(validate=True)
|
|
s = p.parseString(t)
|
|
assert s.validating
|
|
s = p.parseString(t, validate=False)
|
|
assert s.validating is False
|
|
s = p.parseString(t, validate=True)
|
|
assert s.validating
|
|
d = p.parseStyle(style)
|
|
assert d.validating
|
|
|
|
p = cssutils.CSSParser(validate=False)
|
|
s = p.parseString(t)
|
|
assert s.validating is False
|
|
s = p.parseString(t, validate=False)
|
|
assert s.validating is False
|
|
s = p.parseString(t, validate=True)
|
|
assert s.validating
|
|
d = p.parseStyle(style)
|
|
assert d.validating is False
|
|
|
|
# url
|
|
p = cssutils.CSSParser(validate=False)
|
|
p.setFetcher(self._make_fetcher('utf-8', t))
|
|
u = 'url'
|
|
s = p.parseUrl(u)
|
|
assert s.validating is False
|
|
s = p.parseUrl(u, validate=False)
|
|
assert s.validating is False
|
|
s = p.parseUrl(u, validate=True)
|
|
assert s.validating
|
|
|
|
# check if it raises see log test
|
|
|
|
def test_fetcher(self):
|
|
"""CSSParser.fetcher
|
|
|
|
order:
|
|
0. explicity given encoding OVERRIDE (cssutils only)
|
|
|
|
1. An HTTP "charset" parameter in a "Content-Type" field
|
|
(or similar parameters in other protocols)
|
|
2. BOM and/or @charset (see below)
|
|
3. <link charset=""> or other metadata from the linking mechanism (if any)
|
|
4. charset of referring style sheet or document (if any)
|
|
5. Assume UTF-8
|
|
"""
|
|
tests = {
|
|
# css, encoding, (mimetype, encoding, importcss):
|
|
# encoding, importIndex, importEncoding, importText
|
|
# 0/0 override/override => ASCII/ASCII
|
|
(
|
|
'@charset "utf-16"; @import "x";',
|
|
'ASCII',
|
|
('iso-8859-1', '@charset "latin1";/*t*/'),
|
|
): ('ascii', 1, 'ascii', b'@charset "ascii";\n/*t*/'),
|
|
# 1/1 not tested her but same as next
|
|
# 2/1 @charset/HTTP => UTF-16/ISO-8859-1
|
|
(
|
|
'@charset "UTF-16"; @import "x";',
|
|
None,
|
|
('ISO-8859-1', '@charset "latin1";/*t*/'),
|
|
): (
|
|
'utf-16',
|
|
1,
|
|
'iso-8859-1',
|
|
b'@charset "iso-8859-1";\n/*t*/',
|
|
),
|
|
# 2/2 @charset/@charset => UTF-16/ISO-8859-1
|
|
(
|
|
'@charset "UTF-16"; @import "x";',
|
|
None,
|
|
(None, '@charset "ISO-8859-1";/*t*/'),
|
|
): (
|
|
'utf-16',
|
|
1,
|
|
'iso-8859-1',
|
|
b'@charset "iso-8859-1";\n/*t*/',
|
|
),
|
|
# 2/4 @charset/referrer => ASCII/ASCII
|
|
('@charset "ASCII"; @import "x";', None, (None, '/*t*/')): (
|
|
'ascii',
|
|
1,
|
|
'ascii',
|
|
b'@charset "ascii";\n/*t*/',
|
|
),
|
|
# 5/5 default/default or referrer
|
|
('@import "x";', None, (None, '/*t*/')): (
|
|
'utf-8',
|
|
0,
|
|
'utf-8',
|
|
b'/*t*/',
|
|
),
|
|
# 0/0 override/override+unicode
|
|
(
|
|
'@charset "utf-16"; @import "x";',
|
|
'ASCII',
|
|
(None, '@charset "latin1";/*\u0287*/'),
|
|
): ('ascii', 1, 'ascii', b'@charset "ascii";\n/*\\287 */'),
|
|
# 2/1 @charset/HTTP+unicode
|
|
('@charset "ascii"; @import "x";', None, ('iso-8859-1', '/*\u0287*/')): (
|
|
'ascii',
|
|
1,
|
|
'iso-8859-1',
|
|
b'@charset "iso-8859-1";\n/*\\287 */',
|
|
),
|
|
# 2/4 @charset/referrer+unicode
|
|
('@charset "ascii"; @import "x";', None, (None, '/*\u0287*/')): (
|
|
'ascii',
|
|
1,
|
|
'ascii',
|
|
b'@charset "ascii";\n/*\\287 */',
|
|
),
|
|
# 5/1 default/HTTP+unicode
|
|
('@import "x";', None, ('ascii', '/*\u0287*/')): (
|
|
'utf-8',
|
|
0,
|
|
'ascii',
|
|
b'@charset "ascii";\n/*\\287 */',
|
|
),
|
|
# 5/5 default+unicode/default+unicode
|
|
('@import "x";', None, (None, '/*\u0287*/')): (
|
|
'utf-8',
|
|
0,
|
|
'utf-8',
|
|
'/*\u0287*/'.encode(),
|
|
),
|
|
}
|
|
parser = cssutils.CSSParser()
|
|
for test in tests:
|
|
css, encoding, fetchdata = test
|
|
sheetencoding, importIndex, importEncoding, importText = tests[test]
|
|
|
|
# use setFetcher
|
|
parser.setFetcher(self._make_fetcher(*fetchdata))
|
|
# use init
|
|
parser2 = cssutils.CSSParser(fetcher=self._make_fetcher(*fetchdata))
|
|
|
|
sheet = parser.parseString(css, encoding=encoding)
|
|
sheet2 = parser2.parseString(css, encoding=encoding)
|
|
|
|
# sheet
|
|
assert sheet.encoding == sheetencoding
|
|
assert sheet2.encoding == sheetencoding
|
|
# imported sheet
|
|
assert sheet.cssRules[importIndex].styleSheet.encoding == importEncoding
|
|
assert sheet2.cssRules[importIndex].styleSheet.encoding == importEncoding
|
|
assert sheet.cssRules[importIndex].styleSheet.cssText == importText
|
|
assert sheet2.cssRules[importIndex].styleSheet.cssText == importText
|
|
|
|
def test_roundtrip(self):
|
|
"cssutils encodings"
|
|
css1 = r'''@charset "utf-8";
|
|
/* ä */'''
|
|
s = cssutils.parseString(css1)
|
|
css2 = str(s.cssText, 'utf-8')
|
|
assert css1 == css2
|
|
|
|
s = cssutils.parseString(css2)
|
|
s.cssRules[0].encoding = 'ascii'
|
|
css3 = r'''@charset "ascii";
|
|
/* \E4 */'''
|
|
assert css3 == str(s.cssText, 'utf-8')
|
|
|
|
def test_escapes(self):
|
|
"cssutils escapes"
|
|
css = r'\43\x { \43\x: \43\x !import\41nt }'
|
|
sheet = cssutils.parseString(css)
|
|
assert (
|
|
sheet.cssText
|
|
== r'''C\x {
|
|
c\x: C\x !important
|
|
}'''.encode()
|
|
)
|
|
|
|
css = r'\ x{\ x :\ x ;y:1} '
|
|
sheet = cssutils.parseString(css)
|
|
assert (
|
|
sheet.cssText
|
|
== br'''\ x {
|
|
\ x: \ x;
|
|
y: 1
|
|
}'''
|
|
)
|
|
|
|
def test_invalidstring(self):
|
|
"cssutils.parseString(INVALID_STRING)"
|
|
validfromhere = '@namespace "x";'
|
|
csss = (
|
|
'''@charset "ascii
|
|
;'''
|
|
+ validfromhere,
|
|
'''@charset 'ascii
|
|
;'''
|
|
+ validfromhere,
|
|
'''@namespace "y
|
|
;'''
|
|
+ validfromhere,
|
|
'''@import "y
|
|
;'''
|
|
+ validfromhere,
|
|
'''@import url('a
|
|
);'''
|
|
+ validfromhere,
|
|
'''@unknown "y
|
|
;'''
|
|
+ validfromhere,
|
|
)
|
|
for css in csss:
|
|
s = cssutils.parseString(css)
|
|
assert validfromhere.encode() == s.cssText
|
|
|
|
csss = (
|
|
'''a { font-family: "Courier
|
|
; }''',
|
|
r'''a { content: "\"; }
|
|
''',
|
|
r'''a { content: "\\\"; }
|
|
''',
|
|
)
|
|
for css in csss:
|
|
assert b'' == cssutils.parseString(css).cssText
|
|
|
|
def test_invalid(self):
|
|
"cssutils.parseString(INVALID_CSS)"
|
|
tests = {
|
|
'a {color: blue}} a{color: red} a{color: green}': '''a {
|
|
color: blue
|
|
}
|
|
a {
|
|
color: green
|
|
}''',
|
|
'p @here {color: red} p {color: green}': 'p {\n color: green\n }',
|
|
}
|
|
|
|
for css in tests:
|
|
exp = tests[css]
|
|
if exp is None:
|
|
exp = css
|
|
s = cssutils.parseString(css)
|
|
assert exp.encode() == s.cssText
|
|
|
|
def test_nesting(self):
|
|
"cssutils.parseString nesting"
|
|
# examples from csslist 27.11.2007
|
|
tests = {
|
|
'@1; div{color:green}': 'div {\n color: green\n }',
|
|
'@1 []; div{color:green}': 'div {\n color: green\n }',
|
|
'@1 [{}]; div { color:green; }': 'div {\n color: green\n }',
|
|
'@media all { @ } div{color:green}': 'div {\n color: green\n }',
|
|
# should this be u''?
|
|
'@1 { [ } div{color:green}': '',
|
|
# red was eaten:
|
|
'@1 { [ } ] div{color:red}div{color:green}': 'div {\n color: green\n }',
|
|
}
|
|
for css, exp in list(tests.items()):
|
|
assert exp.encode() == cssutils.parseString(css).cssText
|
|
|
|
def test_specialcases(self):
|
|
"cssutils.parseString(special_case)"
|
|
tests = {
|
|
'''
|
|
a[title="a not s\
|
|
o very long title"] {/*...*/}''': '''a[title="a not so very long title"] {
|
|
/*...*/
|
|
}'''
|
|
}
|
|
for css in tests:
|
|
exp = tests[css]
|
|
if exp is None:
|
|
exp = css
|
|
s = cssutils.parseString(css)
|
|
assert exp.encode() == s.cssText
|
|
|
|
def test_iehack(self):
|
|
"IEhack: $property (not since 0.9.5b3)"
|
|
# $color is not color!
|
|
css = 'a { color: green; $color: red; }'
|
|
s = cssutils.parseString(css)
|
|
|
|
p1 = s.cssRules[0].style.getProperty('color')
|
|
assert 'color' == p1.name
|
|
assert 'color' == p1.literalname
|
|
assert '' == s.cssRules[0].style.getPropertyValue('$color')
|
|
|
|
p2 = s.cssRules[0].style.getProperty('$color')
|
|
assert p2 is None
|
|
|
|
assert 'green' == s.cssRules[0].style.getPropertyValue('color')
|
|
assert 'green' == s.cssRules[0].style.color
|
|
|
|
def test_attributes(self):
|
|
"cssutils.parseString(href, media)"
|
|
s = cssutils.parseString(
|
|
"a{}", href="file:foo.css", media="screen, projection, tv"
|
|
)
|
|
assert s.href == "file:foo.css"
|
|
assert s.media.mediaText == "screen, projection, tv"
|
|
|
|
s = cssutils.parseString(
|
|
"a{}", href="file:foo.css", media=["screen", "projection", "tv"]
|
|
)
|
|
assert s.media.mediaText == "screen, projection, tv"
|