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"""(?Pmargin(-(top|bottom|left|right))?|float) : (?P.*?) (?P$|;)""", re.IGNORECASE | re.VERBOSE, ) _importants = re.compile(r"\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=None, base_url=None, disable_link_rewrites=False, preserve_internal_links=False, preserve_inline_attachments=True, preserve_handlebar_syntax=False, 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, allow_network=True, allow_insecure_ssl=False, allow_loading_external_files=False, session=None, ): self.html = html self.base_url = base_url # If base_url is specified, it is used for loading external stylesheets # via relative URLs. # # Also, if base_url is specified, premailer will transform all URLs by # joining them with the base_url. Setting preserve_internal_links to # True will disable this behavior for links to named anchors. Setting # preserve_inline_attachments to True will disable this behavior for # any links with cid: scheme. Setting disable_link_rewrites to True # will disable this behavior altogether. self.disable_link_rewrites = disable_link_rewrites self.preserve_internal_links = preserve_internal_links self.preserve_inline_attachments = preserve_inline_attachments self.preserve_handlebar_syntax = preserve_handlebar_syntax self.exclude_pseudoclasses = exclude_pseudoclasses # whether to delete the

Hi!

Yes!

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