def test_external_styles_with_base_url(self, urllib2): """Test loading styles that are genuinely external if you use the base_url""" html = """<html> <head> <link href="style.css" rel="stylesheet" type="text/css"> </head> <body> <h1>Hello</h1> </body> </html>""" expect_html = """<html> <head> </head> <body> <h1 style="color:brown">Hello</h1> </body> </html>""" def mocked_urlopen(url): if url == "http://www.peterbe.com/style.css": return MockResponse("h1 { color: brown }") raise NotImplementedError(url) urllib2.urlopen = mocked_urlopen p = Premailer(html, base_url="http://www.peterbe.com/") result_html = p.transform() compare_html(expect_html, result_html)
def test_favour_rule_with_class_over_generic(): html = """<html> <head> <style> div.example { color: green; } div { color: black; } </style> </head> <body> <div class="example"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="color:green"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_basic_xml(): """Test the simplest case with xml""" if not etree: # can't test it return html = """<html> <head> <title>Title</title> <style type="text/css"> img { border: none; } </style> </head> <body> <img src="test.png" alt="test"> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <img src="test.png" alt="test" style="border:none"/> </body> </html>""" p = Premailer(html, method="xml") result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_inline_wins_over_external(self): html = """<html> <head> <style type="text/css"> div { text-align: left; } /* This tests that a second loop for the same style still doesn't * overwrite it. */ div { text-align: left; } </style> </head> <body> <div style="text-align:right">Some text</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">Some text</div> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_precedence_comparison(): p = Premailer('html') # won't need the html rules, leftover = p._parse_style_rules(""" #identified { color:blue; } h1, h2 { color:red; } ul li { list-style: 2px; } li.example { color:green; } strong { text-decoration:none } div li.example p.sample { color:black; } """, 0) # 'rules' is a list, turn it into a dict for # easier assertion testing rules_specificity = {} for specificity, k, v in rules: rules_specificity[k] = specificity # Last in file wins assert rules_specificity['h1'] < rules_specificity['h2'] # More elements wins assert rules_specificity['strong'] < rules_specificity['ul li'] # IDs trump everything assert (rules_specificity['div li.example p.sample'] < rules_specificity['#identified']) # Classes trump multiple elements assert (rules_specificity['ul li'] < rules_specificity['li.example'])
def test_basic_html_with_pseudo_selector(): """test the simplest case""" if not etree: # can't test it return html = """ <html> <style type="text/css"> h1 { border:1px solid black } p { color:red;} p::first-letter { float:left; } </style> <h1 style="font-weight:bolder">Peter</h1> <p>Hej</p> </html> """ expect_html = """<html> <head> <style type="text/css">p::first-letter {float:left}</style> </head> <body> <h1 style="border:1px solid black; font-weight:bolder">Peter</h1> <p style="color:red">Hej</p> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_strip_important(): """Get rid of !important. Makes no sense inline.""" html = """<html> <head> <style type="text/css"> p { height:100% !important; width:100% !important; } </style> </head> <body> <p>Paragraph</p> </body> </html> """ expect_html = """<html> <head> </head> <body> <p style="height:100%; width:100%" width="100%" height="100%">Paragraph</p> </body> </html>""" p = Premailer(html, strip_important=True) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_mailto_url(): """if you use URL with mailto: protocol, they should stay as mailto: when baseurl is used """ if not etree: # can't test it return html = """<html> <head> <title>Title</title> </head> <body> <a href="mailto:[email protected]">[email protected]</a> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <a href="mailto:[email protected]">[email protected]</a> </body> </html>""" p = Premailer(html, base_url='http://kungfupeople.com') result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() assert expect_html == result_html
def test_last_child_exclude_pseudo(): html = """<html> <head> <style type="text/css"> div { text-align: right; } div:last-child { text-align: left; } </style> </head> <body> <div>First child</div> <div>Last child</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First child</div> <div style="text-align:left" align="left">Last child</div> </body> </html>""" p = Premailer(html, exclude_pseudoclasses=True) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_inline_wins_over_external(): html = """<html> <head> <style type="text/css"> div { text-align: left; } /* This tests that a second loop for the same style still doesn't * overwrite it. */ div { text-align: left; } </style> </head> <body> <div style="text-align:right">Some text</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">Some text</div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_mediaquery(): html = """<html> <head> <style type="text/css"> div { text-align: right; } @media print{ div { text-align: center; } } </style> </head> <body> <div>First div</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First div</div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_child_selector(): html = """<html> <head> <style type="text/css"> body > div { text-align: right; } </style> </head> <body> <div>First div</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First div</div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_prefer_inline_to_class(): html = """<html> <head> <style> .example { color: black; } </style> </head> <body> <div class="example" style="color:red"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="color:red"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_strip_important(self): """Get rid of !important. Makes no sense inline.""" html = """<html> <head> <style type="text/css"> p { height:100% !important; width:100% !important; } </style> </head> <body> <p>Paragraph</p> </body> </html> """ expect_html = """<html> <head> </head> <body> <p style="height:100%; width:100%" width="100%" height="100%">Paragraph</p> </body> </html>""" p = Premailer(html, strip_important=True) result_html = p.transform() compare_html(expect_html, result_html)
def test_basic_html_with_pseudo_selector(self): """test the simplest case""" html = """ <html> <style type="text/css"> h1 { border:1px solid black } p { color:red;} p::first-letter { float:left; } </style> <h1 style="font-weight:bolder">Peter</h1> <p>Hej</p> </html> """ expect_html = """<html> <head> <style type="text/css">p::first-letter {float:left}</style> </head> <body> <h1 style="border:1px solid black; font-weight:bolder">Peter</h1> <p style="color:red">Hej</p> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_disabled_validator(self): """test disabled_validator""" html = """<html> <head> <title>Title</title> <style type="text/css"> h1, h2 { fo:bar; } strong { color:baz; text-decoration:none; } </style> </head> <body> <h1>Hi!</h1> <p><strong>Yes!</strong></p> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <h1 style="fo:bar">Hi!</h1> <p><strong style="color:baz; text-decoration:none">Yes!</strong></p> </body> </html>""" p = Premailer(html, disable_validation=True) result_html = p.transform() compare_html(expect_html, result_html)
def test_css_with_pseudoclasses_included(self): "Pick up the pseudoclasses too and include them" html = """<html> <head> <style type="text/css"> a.special:link { text-decoration:none; } a { color:red; } a:hover { text-decoration:none; } a,a:hover, a:visited { border:1px solid green; } p::first-letter {float: left; font-size: 300%} </style> </head> <body> <a href="#" class="special">Special!</a> <a href="#">Page</a> <p>Paragraph</p> </body> </html>""" p = Premailer(html, exclude_pseudoclasses=False) result_html = p.transform() # because we're dealing with random dicts here # we can't predict what order the style attribute # will be written in so we'll look for things manually. ok_('<p style="::first-letter{font-size:300%; float:left}">' "Paragraph</p>" in result_html) ok_('style="{color:red; border:1px solid green}' in result_html) ok_(" :visited{border:1px solid green}" in result_html) ok_(" :hover{border:1px solid green; text-decoration:none}" in result_html)
def test_fontface_selectors_with_no_selectortext(self): """ @font-face selectors are weird. This is a fix for https://github.com/peterbe/premailer/issues/71 """ html = """<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> @font-face { font-family: 'Garamond'; src: local('Garamond'), local('Garamond-Regular'), url('Garamond.ttf') format('truetype'); /* Safari, Android, iOS */ font-weight: normal; font-style: normal; } </style> </head> <body></body> </html>""" p = Premailer(html, disable_validation=True) p.transform() # it should just work
def test_child_selector(self): html = """<html> <head> <style type="text/css"> body > div { text-align: right; } </style> </head> <body> <div>First div</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First div</div> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_empty_style_tag(): """empty style tag""" if not etree: # can't test it return html = """<html> <head> <title></title> <style type="text/css"></style> </head> <body> </body> </html>""" expect_html = """<html> <head> <title></title> </head> <body> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_prefer_inline_to_class(self): html = """<html> <head> <style> .example { color: black; } </style> </head> <body> <div class="example" style="color:red"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="color:red"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_doctype(self): html = ( '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' """<html> <head> </head> <body> </body> </html>""" ) expect_html = ( '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' """<html> <head> </head> <body> </body> </html>""" ) p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_favour_rule_with_class_over_generic(self): html = """<html> <head> <style> div.example { color: green; } div { color: black; } </style> </head> <body> <div class="example"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="color:green"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_shortcut_function(self): # you don't have to use this approach: # from premailer import Premailer # p = Premailer(html, base_url=base_url) # print p.transform() # You can do it this way: # from premailer import transform # print transform(html, base_url=base_url) html = """<html> <head> <style type="text/css">h1{color:#123}</style> </head> <body> <h1>Hi!</h1> </body> </html>""" expect_html = """<html> <head></head> <body> <h1 style="color:#123">Hi!</h1> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_xml_cdata(): """Test that CDATA is set correctly on remaining styles""" if not etree: # can't test it return html = """<html> <head> <title>Title</title> <style type="text/css"> span:hover > a { background: red; } </style> </head> <body> <span><a>Test</a></span> </body> </html>""" expect_html = """<html> <head> <title>Title</title> <style type="text/css">/*<![CDATA[*/span:hover > a {background:red}/*]]>*/</style> </head> <body> <span><a>Test</a></span> </body> </html>""" p = Premailer(html, method="xml") result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_favour_rule_with_id_over_others(): html = """<html> <head> <style> #identified { color: green; } div.example { color: black; } </style> </head> <body> <div class="example" id="identified"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div id="identified" style="color:green"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_mailto_url(self): """if you use URL with mailto: protocol, they should stay as mailto: when baseurl is used """ html = """<html> <head> <title>Title</title> </head> <body> <a href="mailto:[email protected]">[email protected]</a> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <a href="mailto:[email protected]">[email protected]</a> </body> </html>""" p = Premailer(html, base_url="http://kungfupeople.com") result_html = p.transform() compare_html(expect_html, result_html)
def test_mixed_pseudo_selectors(self): """mixing pseudo selectors with straight forward selectors""" html = """<html> <head> <title>Title</title> <style type="text/css"> p { color: yellow } a { color: blue } a:hover { color: pink } </style> </head> <body> <p> <a href="#">Page</a> </p> </body> </html>""" expect_html = """<html> <head> <title>Title</title> <style type="text/css">a:hover {color:pink}</style> </head> <body> <p style="color:yellow"><a href="#" style="color:blue">Page</a></p> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_last_child_exclude_pseudo(self): html = """<html> <head> <style type="text/css"> div { text-align: right; } div:last-child { text-align: left; } </style> </head> <body> <div>First child</div> <div>Last child</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First child</div> <div style="text-align:left" align="left">Last child</div> </body> </html>""" p = Premailer(html, exclude_pseudoclasses=True) result_html = p.transform() compare_html(expect_html, result_html)
def test_precedence_comparison(self): p = Premailer("html") # won't need the html rules, leftover = p._parse_style_rules( """ #identified { color:blue; } h1, h2 { color:red; } ul li { list-style: none; } li.example { color:green; } strong { text-decoration:none } div li.example p.sample { color:black; } """, 0, ) # 'rules' is a list, turn it into a dict for # easier assertion testing rules_specificity = {} for specificity, k, v in rules: rules_specificity[k] = specificity # Last in file wins ok_(rules_specificity["h1"] < rules_specificity["h2"]) # More elements wins ok_(rules_specificity["strong"] < rules_specificity["ul li"]) # IDs trump everything ok_(rules_specificity["div li.example p.sample"] < rules_specificity["#identified"]) # Classes trump multiple elements ok_(rules_specificity["ul li"] < rules_specificity["li.example"])
def test_css_with_pseudoclasses_excluded(): "Skip things like `a:hover{}` and keep them in the style block" if not etree: # can't test it return html = '''<html> <head> <style type="text/css"> a { color:red; } a:hover { text-decoration:none; } a,a:hover, a:visited { border:1px solid green; } p::first-letter {float: left; font-size: 300%} </style> </head> <body> <a href="#">Page</a> <p>Paragraph</p> </body> </html>''' expect_html = '''<html> <head> <style type="text/css">a:hover {text-decoration:none} a:hover {border:1px solid green} a:visited {border:1px solid green}p::first-letter {float:left;font-size:300%} </style> </head> <body> <a href="#" style="color:red; border:1px solid green">Page</a> <p>Paragraph</p> </body> </html>''' p = Premailer(html, exclude_pseudoclasses=True) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() expect_html = re.sub('}\s+', '}', expect_html) result_html = result_html.replace('}\n', '}') eq_(expect_html, result_html)
def test_css_with_html_attributes(self): """Some CSS styles can be applied as normal HTML attribute like 'background-color' can be turned into 'bgcolor' """ html = """<html> <head> <style type="text/css"> td { background-color:red; } p { text-align:center; } table { width:200px; } </style> </head> <body> <p>Text</p> <table> <tr> <td>Cell 1</td> <td>Cell 2</td> </tr> </table> </body> </html>""" expect_html = """<html> <head> </head> <body> <p style="text-align:center" align="center">Text</p> <table style="width:200px" width="200"> <tr> <td style="background-color:red" bgcolor="red">Cell 1</td> <td style="background-color:red" bgcolor="red">Cell 2</td> </tr> </table> </body> </html>""" p = Premailer(html, exclude_pseudoclasses=True) result_html = p.transform() expect_html = re.sub('}\s+', '}', expect_html) result_html = result_html.replace('}\n', '}') compare_html(expect_html, result_html)
def test_keyframe_selectors(self): """ keyframes shouldn't be a problem. """ html = """<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> @keyframes fadein { from { opacity: 0; } to { opacity: 1; } } /* Firefox */ @-moz-keyframes fadein { from { opacity: 0; } to { opacity: 1; } } /* Safari and Chrome */ @-webkit-keyframes fadein { from { opacity: 0; } to { opacity: 1; } } /* Internet Explorer */ @-ms-keyframes fadein { from { opacity: 0; } to { opacity: 1; } } /* Opera */ @-o-keyframes fadein { from { opacity: 0; } to { opacity: 1; } } </style> </head> <body></body> </html>""" with self.assertRaises(CSSSyntaxError): p = Premailer(html, disable_validation=True) p.transform() # it should just work
def test_base_url_with_path(self): """if you leave some URLS as /foo and set base_url to 'http://www.google.com' the URLS become 'http://www.google.com/foo' """ html = '''<html> <head> <title>Title</title> </head> <body> <img src="/images/foo.jpg"> <img src="/images/bar.gif"> <img src="http://www.googe.com/photos/foo.jpg"> <a href="/home">Home</a> <a href="http://www.peterbe.com">External</a> <a href="http://www.peterbe.com/base/">External 2</a> <a href="subpage">Subpage</a> <a href="#internal_link">Internal Link</a> </body> </html> ''' expect_html = '''<html> <head> <title>Title</title> </head> <body> <img src="http://kungfupeople.com/base/images/foo.jpg"> <img src="http://kungfupeople.com/base/images/bar.gif"> <img src="http://www.googe.com/photos/foo.jpg"> <a href="http://kungfupeople.com/base/home">Home</a> <a href="http://www.peterbe.com">External</a> <a href="http://www.peterbe.com/base/">External 2</a> <a href="http://kungfupeople.com/base/subpage">Subpage</a> <a href="#internal_link">Internal Link</a> </body> </html>''' p = Premailer(html, base_url='http://kungfupeople.com/base', preserve_internal_links=True) result_html = p.transform() compare_html(expect_html, result_html)
def test_style_block_with_external_urls(): """ From http://github.com/peterbe/premailer/issues/#issue/2 If you have body { background:url(http://example.com/bg.png); } the ':' inside '://' is causing a problem """ if not etree: # can't test it return html = """<html> <head> <title>Title</title> <style type="text/css"> body { color:#123; background: url(http://example.com/bg.png); font-family: Omerta; } </style> </head> <body> <h1>Hi!</h1> </body> </html>""" expect_html = '''<html> <head> <title>Title</title> </head> <body style="color:#123; font-family:Omerta; background:url(http://example.com/bg.png)"> <h1>Hi!</h1> </body> </html>''' p = Premailer(html) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() assert expect_html == result_html
def test_multiple_style_elements(): """Asserts that rules from multiple style elements are inlined correctly.""" if not etree: # can't test it return html = """<html> <head> <title>Title</title> <style type="text/css"> h1, h2 { color:red; } strong { text-decoration:none } </style> <style type="text/css"> h1, h2 { color:green; } p { font-size:120% } </style> </head> <body> <h1>Hi!</h1> <p><strong>Yes!</strong></p> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <h1 style="color:green">Hi!</h1> <p style="font-size:120%"><strong style="text-decoration:none">Yes!</strong></p> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def prepare_letter(html, base_url): html = Premailer(html=html, base_url=base_url, strip_important=False, keep_style_tags=True, capitalize_float_margin=True).transform() if "<!doctype" not in html: html = f"<!doctype html>{html}" return html
def test_apple_newsletter_example(self): # stupidity test import os html_file = os.path.join(os.path.dirname(__file__), 'test-apple-newsletter.html') html = open(html_file).read() p = Premailer(html, exclude_pseudoclasses=False, keep_style_tags=True, strip_important=False) result_html = p.transform() ok_('<html>' in result_html) ok_('<style media="only screen and (max-device-width: 480px)" ' 'type="text/css">\n' '* {line-height: normal !important; -webkit-text-size-adjust: 125%}\n' '</style>' in result_html)
def render(self, context): rendered_contents = self.nodelist.render(context) kwargs = PREMAILER_OPTIONS.copy() for expression in self.filter_expressions: kwargs.update(base_url=expression.resolve(context, True)) transformed = Premailer(rendered_contents, **kwargs).transform() return transformed
def css_inline_html(html, **kwargs): """ Contentfilter to inline CSS into HTML """ p = Premailer(html=html, preserve_internal_links=True, exclude_pseudoclasses=True, keep_style_tags=False, include_star_selectors=True, remove_classes=False, strip_important=False, method='html', base_path=None, disable_basic_attributes=[], disable_validation=False, disable_leftover_css=True, align_floating_images=True, remove_unset_properties=True, capitalize_float_margin=True) return p.transform(pretty_print=True)
def test_apple_newsletter_example(self): # stupidity test html = self.read_html_file('test-apple-newsletter') p = Premailer(html, exclude_pseudoclasses=False, keep_style_tags=True) result_html = p.transform() self.assertIn('<html>', result_html) self.assertIn( """<style media="only screen and (max-device-width: \ 480px)" type="text/css">*{line-height:normal !important;\ -webkit-text-size-adjust:125%}</style>""", result_html) self.assertIsNotNone(result_html.find('Add this to your calendar')) self.assertIn( '''style="{font-family:Lucida Grande,Arial,Helvetica,\ Geneva,Verdana,sans-serif;font-size:11px;color:#5b7ab3} :active{color:#5b7ab3;\ text-decoration:none} :visited{color:#5b7ab3;text-decoration:none} :hover\ {color:#5b7ab3;text-decoration:underline} :link{color:#5b7ab3;text-decoration:\ none}">Add this to your calendar''', result_html)
def inline_style_in_html(html): ''' Convert email.css and html to inline-styled html ''' from premailer import Premailer from frappe.utils.jinja_globals import bundled_asset # get email css files from hooks css_files = frappe.get_hooks('email_css') css_files = [bundled_asset(path) for path in css_files] css_files = [path.lstrip('/') for path in css_files] css_files = [ css_file for css_file in css_files if os.path.exists(os.path.abspath(css_file)) ] p = Premailer(html=html, external_styles=css_files, strip_important=False) return p.transform()
def test_mediaquery(self): html = """<html> <head> <style type="text/css"> div { text-align: right; } @media print{ div { text-align: center; color: white; } div { font-size: 999px; } } </style> </head> <body> <div>First div</div> </body> </html>""" expect_html = """<html> <head> <style type="text/css">@media print { div { text-align: center !important; color: white !important } div { font-size: 999px !important } }</style> </head> <body> <div style="text-align:right" align="right">First div</div> </body> </html>""" p = Premailer(html, strip_important=False) result_html = p.transform() compare_html(expect_html, result_html)
def store_order(self, method, userid, contact_info): """Store various data following payment processing. This code runs in a separate transaction (with up to 10 retries) to make sure that ConflictErrors do not cause the Zope publisher to retry the request which performs payment processing. IMPORTANT: Make sure that code which runs before this is called does not try to make persistent changes, because they'll be lost. """ order = self.cart.data order['bill_to'] = contact_info order['notes'] = self.request.form.get('notes') # Call `after_purchase` hook for each product coupons_used = set() for index, item in enumerate(self.cart.items): ob = resolve_uid(item.uid) if ob is not None: purchase_handler = IPurchaseHandler(ob) purchase_handler.after_purchase(item._item) # keep track of coupons used if item.is_discounted: coupons_used.add(item.coupon) # store count of coupon usage for coupon_uid in coupons_used: storage.increment_shop_data( [userid, 'coupons', coupon_uid], 1) # Store historic record of order self.order_id = self.cart.store_order(userid) # Keep cart as old_cart (for the thank you page) before clearing it self.old_cart = self.cart.clone() self.cart_is_editable = False # Queue receipt email (email is actually sent at transaction commit) if self.receipt_email: subject = get_setting('receipt_subject') unstyled_msg = self.receipt_email() msg = Premailer(unstyled_msg, css_text=EMAIL_CSS).transform() # only send email if the email field was entered in the SIM billing # address section and if the email address is a valid email format if 'x_email' in self.request.form: try: is_email(self.request.form['x_email']) except Exception as e: self.mail_not_sent = ( 'Receipt email was not sent as the ' 'email address entered on the payment ' 'form was not valid.') if not self.mail_not_sent: mto = self.request['x_email'] send_mail(subject, msg, mto=mto)
def create_welcome_email(user, request): # fish out all the relevant information about the user and # then create an unsent WelcomeEmail subject = u"Welcome to %s" % settings.PROJECT_NAME try: person = user.get_profile() except KungfuPerson.DoesNotExist: return None alu = AutoLoginKey.get_or_create(user) profile_url = reverse('person.view', args=(user.username, )) upload_photo_url = reverse('upload_profile_photo', args=(user.username, )) change_password_url = reverse("edit_password", args=(user.username, )) edit_style_url = reverse("edit_style", args=(user.username, )) edit_club_url = reverse("edit_club", args=(user.username, )) edit_profile_url = reverse("edit_profile", args=(user.username, )) data = locals() domain = RequestSite(request).domain base_url = 'http://%s' % domain # for every variable that ends with _url make it an absolute url # and add the _alu variable def aluify_url(url): if '?' in url: return url + '&alu=%s' % alu.uuid else: return url + '?alu=%s' % alu.uuid keys = list(data.keys()) for key in keys: if key.endswith('_url'): url = data[key] if url.startswith('/'): url = base_url + url data[key] = url data[key + '_alu'] = aluify_url(url) # now the interesting thing starts. We need to find out what they haven't # done with their profile and pester them about that. response = render(request, 'welcome-email.html', data) html = response.content html = Premailer( html, base_url=base_url, keep_style_tags=False, ).transform() return WelcomeEmail.objects.create( user=user, subject=subject, body=html, )
def test_external_styles_on_http(urllib2): """Test loading styles that are genuinely external""" html = """<html> <head> <link href="https://www.com/style1.css" rel="stylesheet" type="text/css"> <link href="//www.com/style2.css" rel="stylesheet" type="text/css"> <link href="//www.com/style3.css" rel="stylesheet" type="text/css"> </head> <body> <h1>Hello</h1> <h2>World</h2> <h3>World</h3> </body> </html>""" expect_html = """<html> <head> </head> <body> <h1 style="color:brown">Hello</h1> <h2 style="color:pink">World</h2> <h3 style="color:red">World</h3> </body> </html>""" def mocked_urlopen(url): if 'style1.css' in url: return MockResponse("h1 { color: brown }") if 'style2.css' in url: return MockResponse("h2 { color: pink }") if 'style3.css' in url: return MockResponse("h3 { color: red }", gzip=True) urllib2.urlopen = mocked_urlopen p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_comments_in_media_queries(self): """CSS comments inside a media query block should not be a problem""" html = """<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> @media screen { /* comment */ } </style> </head> <body></body> </html>""" p = Premailer(html, disable_validation=True) result_html = p.transform() ok_('/* comment */' in result_html)
def password_reset(): """Request a password reset for a user.""" form = RecoverPasswordForm() # TODO: Refactor this logic so the if block is not nested if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user: user.activation_key = str(uuid4()) db.session.add(user) db.session.commit() # Send reset password email. css = get_resource_as_string('static/css/email.css') change_password_url = '%s/#accounts/password/reset/confirm/%s/%s/' % (current_app.config['DOMAIN'], quote(user.email), quote(user.activation_key)) html = render_template('user/emails/reset_password.html', css=css, username=user.username, email_recipient=user.email, change_password_url=change_password_url) current_app.logger.debug('change_password_url=[%s]' % change_password_url) p = Premailer(html) result_html = p.transform() message = Message(subject='Recover your password', html=result_html, recipients=[user.email]) try: mail.send(message) except SMTPDataError as e: current_app.logger.debug('Returning fail = [%s].' % e) response = jsonify(status='fail', data={'email': "Couldn't send email to %s." % form.email.data}) response.status_code = 200 return response current_app.logger.debug('Returning success.') response = jsonify(status='success') response.status_code = 200 return response else: current_app.logger.debug('Returning fail = [Sorry, no user found for that email address.].') response = jsonify(status='fail', data={'email': 'Sorry, no user found for that email address.'}) response.status_code = 200 return response else: current_app.logger.debug('Returning fail = [%s].' % form.errors) response = jsonify(status='fail', data=form.errors) response.status_code = 200 return response
def test_external_links(self): """Test loading stylesheets via link tags""" html = """<html> <head> <title>Title</title> <style type="text/css"> h1 { color:red; } h3 { color:yellow; } </style> <link href="premailer/test-external-links.css" rel="stylesheet" type="text/css"> <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml"> <style type="text/css"> h1 { color:orange; } </style> </head> <body> <h1>Hello</h1> <h2>World</h2> <h3>Test</h3> <a href="#">Link</a> </body> </html>""" expect_html = """<html> <head> <title>Title</title> <style type="text/css">a:hover {color:purple !important}</style> <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml"> </head> <body> <h1 style="color:orange">Hello</h1> <h2 style="color:green">World</h2> <h3 style="color:yellow">Test</h3> <a href="#" style="color:pink">Link</a> </body> </html>""" p = Premailer(html, strip_important=False) result_html = p.transform() compare_html(expect_html, result_html)
def test_CSS_unknown_rule(self): html = u"""<html> <head> <title>Test</title> <style> @page { size:8.5in 11in; margin: 2cm } @keyframes fadein { from { opacity: 0; } to { opacity: 1; } } /* Firefox */ @-moz-keyframes fadein { from { opacity: 0; } to { opacity: 1; } } /* Safari and Chrome */ @-webkit-keyframes fadein { from { opacity: 0; } to { opacity: 1; } } /* Internet Explorer */ @-ms-keyframes fadein { from { opacity: 0; } to { opacity: 1; } } /* Opera */ @-o-keyframes fadein { from { opacity: 0; } to { opacity: 1; } } p { font-size:2px; width: 400px; } h1, h2 { color: red; } strong { text-decoration:none } </style> </head> <body> <h1>Hi!</h1> <p><strong>Yes!</strong></p> <p class="footer" style="color:red">Feetnuts</p> </body> </html>""" with self.assertRaisesRegexp( CSSSyntaxError, 'WARNING CSSStylesheet: ' 'Unknown @rule found'): Premailer(html).transform()
def customhtml(self): template_url = 'emails/base_email.html' html_template = get_template(template_url) context_dict = {'user': self.user, 'body': self.body} email_data = Context(context_dict) htmlemail = Premailer(html_template.render(email_data), base_url=settings.EMAIL_BASE_URL, remove_classes=False, strip_important=False).transform() self._customhtml = htmlemail return self._customhtml
def test_external_styles_and_links(self): """Test loading stylesheets via both the 'external_styles' argument and link tags""" html = """<html> <head> <link href="test-external-links.css" rel="stylesheet" type="text/css"> <style type="text/css"> h1 { color: red; } </style> </head> <body> <h1>Hello</h1> <h2>Hello</h2> <a href="">Hello</a> </body> </html>""" expect_html = """<html> <head> <style type="text/css">a:hover {color:purple !important}</style> <style type="text/css">h2::after {content:"" !important;display:block !important} @media all and (max-width: 320px) { h1 { font-size: 12px !important } }</style> </head> <body> <h1 style="color:brown">Hello</h1> <h2 style="color:green">Hello</h2> <a href="" style="color:pink">Hello</a> </body> </html>""" p = Premailer(html, strip_important=False, external_styles='test-external-styles.css', base_path='premailer/') result_html = p.transform() compare_html(expect_html, result_html)
def test_multiple_style_elements(self): """Asserts that rules from multiple style elements are inlined correctly.""" html = """<html> <head> <title>Title</title> <style type="text/css"> h1, h2 { color:red; } strong { text-decoration:none } </style> <style type="text/css"> h1, h2 { color:green; } p { font-size:120% } </style> </head> <body> <h1>Hi!</h1> <p><strong>Yes!</strong></p> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <h1 style="color:green">Hi!</h1> <p style="font-size:120%"> <strong style="text-decoration:none">Yes!</strong></p> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_mixed_pseudo_selectors(): """mixing pseudo selectors with straight forward selectors""" if not etree: # can't test it return html = """<html> <head> <title>Title</title> <style type="text/css"> p { color: yellow } a { color: blue } a:hover { color: pink } </style> </head> <body> <p> <a href="#">Page</a> </p> </body> </html>""" expect_html = """<html> <head> <title>Title</title> <style type="text/css">a:hover {color:pink}</style> </head> <body> <p style="color:yellow"><a href="#" style="color:blue">Page</a></p> </body> </html>""" p = Premailer(html) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_basic_html(): """test the simplest case""" if not etree: # can't test it return html = """<html> <head> <title>Title</title> <style type="text/css"> h1, h2 { color:red; } strong { text-decoration:none } </style> </head> <body> <h1>Hi!</h1> <p><strong>Yes!</strong></p> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <h1 style="color:red">Hi!</h1> <p><strong style="text-decoration:none">Yes!</strong></p> </body> </html>""" p = Premailer(html) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_external_styles_on_https(self, urllib2): """Test loading styles that are genuinely external""" html = """<html> <head> <link href="https://www.com/style1.css" rel="stylesheet" type="text/css"> <link href="//www.com/style2.css" rel="stylesheet" type="text/css"> <link href="/style3.css" rel="stylesheet" type="text/css"> </head> <body> <h1>Hello</h1> <h2>World</h2> <h3>World</h3> </body> </html>""" expect_html = """<html> <head> </head> <body> <h1 style="color:brown">Hello</h1> <h2 style="color:pink">World</h2> <h3 style="color:red">World</h3> </body> </html>""" def mocked_urlopen(url): ok_(url.startswith('https://')) if 'style1.css' in url: return MockResponse("h1 { color: brown }") if 'style2.css' in url: return MockResponse("h2 { color: pink }") if 'style3.css' in url: return MockResponse("h3 { color: red }", gzip=True) urllib2.urlopen = mocked_urlopen p = Premailer(html, base_url='https://www.peterbe.com') result_html = p.transform() compare_html(expect_html, result_html)
def sendUpsellEmail(data): name = Utils.getUserName(data['user']) with webapp.app_context(): consumer_mail = render_template( 'mailers/extend_order.html', name=name, book_name=data['book_name'], order_id=data['order_id'], items=data['items'], curated_items=data['curated_items'], quote=data['quote'], quote_author=data['quote_author']) pre = Premailer(consumer_mail, remove_classes=False, strip_important=False) consumer_mail = pre.transform() email = Message('Enjoying the book?', recipients=[data['user'].email]) email.html = consumer_mail mail.send(email) return True
def assert_transformed_html_equal(self, input_html, expected_html, strip_whitespace_after_brace=False, use_shortcut_function=False, **kwargs): if use_shortcut_function: result_html = transform(input_html, **kwargs) else: premailer = Premailer(input_html, **kwargs) result_html = premailer.transform() expected_html = WHITESPACE_BETWEEN_TAGS.sub('><', expected_html).strip() result_html = WHITESPACE_BETWEEN_TAGS.sub('><', result_html).strip() if strip_whitespace_after_brace: expected_html = WHITESPACE_AFTER_BRACE.sub('}', expected_html) result_html = WHITESPACE_AFTER_BRACE.sub('}', result_html) self.assertEqual(expected_html, result_html)
def send_newsletter(token, email): for subreddit in user_subreddits(token): subreddit = subreddit.display_name with io.StringIO() as body: print("Sending {} weekly for {}...".format(subreddit, email)) weekly_page(subreddit, body, css=REDDIT_CSS) email_body = Premailer(body.getvalue(), base_url='https://www.reddit.com', disable_leftover_css=True).transform() send_email(subject='Reddit weekly r/{}'.format(subreddit), to=email, message=email_body)
def inline_style_in_html(html): ''' Convert email.css and html to inline-styled html ''' from premailer import Premailer apps = frappe.get_installed_apps() # add frappe email css file css_files = ['assets/css/email.css'] if 'frappe' in apps: apps.remove('frappe') for app in apps: path = 'assets/{0}/css/email.css'.format(app) css_files.append(path) css_files = [css_file for css_file in css_files if os.path.exists(os.path.abspath(css_file))] p = Premailer(html=html, external_styles=css_files, strip_important=False) return p.transform()