From 27ac936a22b8caa3366cde99e74b5527b4a6dd53 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Tue, 20 Oct 2015 11:40:49 +0200 Subject: [PATCH] Add an option to capitalize margin and float properties The option is disabled by default, that weird idea comes from a weird outlook.com behaviour to handle margin and float properties only if the first letter is uppercase. See https://www.emailonacid.com/blog/article/email-development/outlook.com-does-support-margins --- premailer/__main__.py | 6 +++++ premailer/premailer.py | 29 +++++++++++++++++++++ premailer/tests/test_merge_style.py | 6 ----- premailer/tests/test_premailer.py | 39 +++++++++++++++++++++++++++++ premailer/tests/test_utils.py | 19 ++++++++++++++ 5 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 premailer/tests/test_utils.py diff --git a/premailer/__main__.py b/premailer/__main__.py index abae123..19a7135 100644 --- a/premailer/__main__.py +++ b/premailer/__main__.py @@ -64,6 +64,12 @@ def main(args): action="store_true", dest="remove_classes" ) + parser.add_argument( + "--capitalize-float-margin", default=False, + help="Capitalize float and margin properties for outlook.com compat.", + action="store_true", dest="capitalize_float_margin" + ) + parser.add_argument( "--strip-important", default=False, help="Remove '!important' for all css declarations.", diff --git a/premailer/premailer.py b/premailer/premailer.py index 76736b3..fee2ef7 100644 --- a/premailer/premailer.py +++ b/premailer/premailer.py @@ -85,8 +85,27 @@ def _cache_parse_css_string(css_body, validate=True): """ return cssutils.parseString(css_body, validate=validate) + +def capitalize_float_margin(css_body): + """Capitalize float and margin CSS property names + """ + def _capitalize_property(match): + return '{0}:{1}{2}'.format( + match.group('property').capitalize(), + match.group('value'), + match.group('terminator')) + + return _lowercase_margin_float_rule.sub(_capitalize_property, css_body) + + _element_selector_regex = re.compile(r'(^|\s)\w') _cdata_regex = re.compile(r'\<\!\[CDATA\[(.*?)\]\]\>', re.DOTALL) +_lowercase_margin_float_rule = re.compile( + r'''(?Pmargin(-(top|bottom|left|right))?|float) + : + (?P.*?) + (?P$|;)''', + re.IGNORECASE | re.VERBOSE) _importants = re.compile('\s*!important') # These selectors don't apply to all elements. Rather, they specify # which elements to apply to. @@ -104,6 +123,7 @@ class Premailer(object): keep_style_tags=False, include_star_selectors=False, remove_classes=True, + capitalize_float_margin=False, strip_important=True, external_styles=None, css_text=None, @@ -126,6 +146,7 @@ class Premailer(object): # this will always preserve the original css self.keep_style_tags = keep_style_tags self.remove_classes = remove_classes + self.capitalize_float_margin = capitalize_float_margin # whether to process or ignore selectors like '* { foo:bar; }' self.include_star_selectors = include_star_selectors if isinstance(external_styles, STR_TYPE): @@ -420,6 +441,14 @@ class Premailer(object): parent = item.getparent() del parent.attrib['class'] + # Capitalize Margin properties + # To fix weird outlook bug + # https://www.emailonacid.com/blog/article/email-development/outlook.com-does-support-margins + if self.capitalize_float_margin: + for item in page.xpath('//@style'): + mangled = capitalize_float_margin(item) + item.getparent().attrib['style'] = mangled + # Add align attributes to images if they have a CSS float value of # right or left. Outlook (both on desktop and on the web) are bad at # understanding floats, but they do understand the HTML align attrib. diff --git a/premailer/tests/test_merge_style.py b/premailer/tests/test_merge_style.py index 0d4d11f..411f7c9 100644 --- a/premailer/tests/test_merge_style.py +++ b/premailer/tests/test_merge_style.py @@ -16,9 +16,3 @@ class TestMergeStyle(unittest.TestCase): # Invalid syntax does not raise inline = '{color:pink} :hover{color:purple} :active{color:red}' merge_styles(inline, [], []) - - def test_important_rule(self): - # No exception after #133 - csstext = 'font-size:1px !important' - parsed_csstext = csstext_to_pairs(csstext) - self.assertEqual(('font-size', '1px'), parsed_csstext[0]) diff --git a/premailer/tests/test_premailer.py b/premailer/tests/test_premailer.py index 9adf49b..3f0ed8f 100644 --- a/premailer/tests/test_premailer.py +++ b/premailer/tests/test_premailer.py @@ -828,6 +828,45 @@ ical-align:middle" bgcolor="red" valign="middle">Cell 2 compare_html(expect_html, result_html) + def test_uppercase_margin(self): + """Option to comply with outlook.com + + https://emailonacid.com/blog/article/email-development/outlook.com-does-support-margins + """ + + html = """ + +Title + + + +

a

+

+b +

+ +""" + + expect_html = """ + +Title + + +

a

+

+b +

+ +""" + + p = Premailer(html, capitalize_float_margin=True) + result_html = p.transform() + + compare_html(expect_html, result_html) + def test_strip_important(self): """Get rid of !important. Makes no sense inline.""" html = """ diff --git a/premailer/tests/test_utils.py b/premailer/tests/test_utils.py new file mode 100644 index 0000000..467608d --- /dev/null +++ b/premailer/tests/test_utils.py @@ -0,0 +1,19 @@ +import unittest + +from premailer.premailer import capitalize_float_margin + + +class UtilsTestCase(unittest.TestCase): + def testcapitalize_float_margin(self): + self.assertEqual( + capitalize_float_margin('margin:1em'), + 'Margin:1em') + self.assertEqual( + capitalize_float_margin('margin-left:1em'), + 'Margin-left:1em') + self.assertEqual( + capitalize_float_margin('float:right;'), + 'Float:right;') + self.assertEqual( + capitalize_float_margin('float:right;color:red;margin:0'), + 'Float:right;color:red;Margin:0')