import codecs import operator import os import re import warnings from collections import OrderedDict from html import escape, unescape from urllib.parse import urljoin, urlparse, unquote import cssutils import requests from lxml import etree from lxml.cssselect import CSSSelector from premailer.cache import function_cache from premailer.merge_style import csstext_to_pairs, merge_styles __all__ = ["PremailerError", "Premailer", "transform"] class PremailerError(Exception): pass class ExternalNotFoundError(ValueError): pass class ExternalFileLoadingError(Exception): 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 = _create_cssselector("head")(root) if not head: head = etree.Element("head") body = _create_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) @function_cache() def _create_cssselector(selector): return CSSSelector(selector) 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"""(?PYes!