from __future__ import absolute_import, unicode_literals, print_function import codecs import operator import os import re import warnings try: from collections import OrderedDict except ImportError: # pragma: no cover # some old python 2.6 thing then, eh? from ordereddict import OrderedDict import sys if sys.version_info >= (3,): # pragma: no cover # As in, Python 3 from io import StringIO from urllib.parse import urljoin, urlparse STR_TYPE = str else: # Python 2 try: from cStringIO import StringIO except ImportError: # pragma: no cover from StringIO import StringIO StringIO = StringIO # shut up pyflakes from urlparse import urljoin, urlparse STR_TYPE = basestring # NOQA import cssutils import requests from lxml import etree from lxml.cssselect import CSSSelector from premailer.merge_style import merge_styles, csstext_to_pairs from premailer.cache import function_cache __all__ = ['PremailerError', 'Premailer', 'transform'] class PremailerError(Exception): pass class ExternalNotFoundError(ValueError): pass def make_important(bulk): """makes every property in a string !important. """ return ';'.join('%s !important' % p if not p.endswith('!important') else p for p in bulk.split(';')) def get_or_create_head(root): """Ensures that `root` contains a element and returns it. """ head = CSSSelector('head')(root) if not head: head = etree.Element('head') body = CSSSelector('body')(root)[0] body.getparent().insert(0, head) return head else: return head[0] @function_cache() def _cache_parse_css_string(css_body, validate=True): """ This function will cache the result from cssutils It is a big gain when number of rules is big Maximum cache entries are 1000. This is mainly for protecting memory leak in case something gone wild. Be aware that you can turn the cache off in Premailer Args: css_body(str): css rules in string format validate(bool): if cssutils should validate Returns: cssutils.css.cssstylesheet.CSSStyleSheet """ 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') #: The short (3-digit) color codes that cause issues for IBM Notes _short_color_codes = re.compile(r'^#([0-9a-f])([0-9a-f])([0-9a-f])$', re.I) # These selectors don't apply to all elements. Rather, they specify # which elements to apply to. FILTER_PSEUDOSELECTORS = [':last-child', ':first-child', 'nth-child'] class Premailer(object): attribute_name = 'data-premailer' def __init__(self, html, base_url=None, preserve_internal_links=False, preserve_inline_attachments=True, exclude_pseudoclasses=True, keep_style_tags=False, include_star_selectors=False, remove_classes=False, capitalize_float_margin=False, strip_important=True, external_styles=None, css_text=None, method="html", base_path=None, disable_basic_attributes=None, disable_validation=False, cache_css_parsing=True, cssutils_logging_handler=None, cssutils_logging_level=None, disable_leftover_css=False, align_floating_images=True, remove_unset_properties=True): self.html = html self.base_url = base_url self.preserve_internal_links = preserve_internal_links self.preserve_inline_attachments = preserve_inline_attachments self.exclude_pseudoclasses = exclude_pseudoclasses # whether to delete the

Hi!

Yes!

""" p = Premailer(html) print(p.transform())