def strip_xss(html, whitelist=None, replacement="(removed)"):
    """
    This function returns a tuple containing:

        * *html* with all non-whitelisted HTML tags replaced with *replacement*.
        * A `set()` containing the tags that were removed.

    Any tags that contain JavaScript, VBScript, or other known XSS/executable
    functions will also be removed.

    If *whitelist* is not given the following will be used::

        whitelist = set([
            'a', 'abbr', 'aside', 'audio', 'bdi', 'bdo', 'blockquote', 'canvas',
            'caption', 'code', 'col', 'colgroup', 'data', 'dd', 'del',
            'details', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'h1',
            'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li',
            'mark', 'ol', 'p', 'pre', 'q', 'rp', 'rt', 'ruby', 's', 'samp',
            'small', 'source', 'span', 'strong', 'sub', 'summary', 'sup',
            'table', 'td', 'th', 'time', 'tr', 'track', 'u', 'ul', 'var',
            'video', 'wbr'
        ])

    .. note:: To disable the whitelisting simply set `whitelist="off"`.

    Example::

        >>> html = '<span>Hello, exploit: <img src="javascript:alert(\"pwned!\")"></span>'
        >>> html, rejects = strip_xss(html)
        >>> print("'%s', Rejected: '%s'" % (html, " ".join(rejects)))
        '<span>Hello, exploit: (removed)</span>', Rejected: '<img src="javascript:alert("pwned!")">'

    .. note:: The default *replacement* is "(removed)".

    If *replacement* is "entities" bad HTML tags will be encoded into HTML
    entities.  This allows things like <script>'whatever'</script> to be
    displayed without execution (which would be much less annoying to users that
    were merely trying to share a code example).  Here's an example::

        >>> html = '<span>Hello, exploit: <img src="javascript:alert(\"pwned!\")"></span>'
        >>> html, rejects = strip_xss(html, replacement="entities")
        >>> print(html)
        <span>Hello, exploit: &lt;img src="javascript:alert("pwned!")"&gt;</span>
        >>> print("Rejected: '%s'" % ", ".join(rejects))
        Rejected: '<img src="javascript:alert("pwned!")">'

    **NOTE:** This function should work to protect against *all* `the XSS
    examples at OWASP
    <https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet>`_.  Please
    `let us know <https://github.com/LiftoffSoftware/htmltag/issues>`_ if you
    find something we missed.
    """
    re_html_tag = re.compile( # This matches HTML tags (if used correctly)
      "(?i)<\/?\w+((\s+\w+(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>")
    # This will match things like 'onmouseover=' ('on<whatever>=')
    on_events_re = re.compile('.*\s+(on[a-z]+\s*=).*')
    if not whitelist:
        # These are all pretty safe and covers most of what users would want in
        # terms of formatting and sharing media (images, audio, video, etc).
        whitelist = set([
            'a', 'abbr', 'aside', 'audio', 'bdi', 'bdo', 'blockquote', 'canvas',
            'caption', 'code', 'col', 'colgroup', 'data', 'dd', 'del',
            'details', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'h1',
            'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li',
            'mark', 'ol', 'p', 'pre', 'q', 'rp', 'rt', 'ruby', 's', 'samp',
            'small', 'source', 'span', 'strong', 'sub', 'summary', 'sup',
            'table', 'td', 'th', 'time', 'tr', 'track', 'u', 'ul', 'var',
            'video', 'wbr'
        ])
    elif whitelist == "off":
        whitelist = None # Disable it altogether
    bad_tags = set()
    for tag in re_html_tag.finditer(html):
        tag = tag.group()
        tag_lower = tag.lower()
        short_tag = tag_lower.split()[0].lstrip('</').rstrip('>')
        if whitelist and short_tag not in whitelist:
            bad_tags.add(tag)
            continue
        # Make sure the tag can't execute any JavaScript
        if "javascript:" in tag_lower:
            bad_tags.add(tag)
            continue
        # on<whatever> events are not allowed (just another XSS vuln)
        if on_events_re.search(tag_lower):
            bad_tags.add(tag)
            continue
        # Flash sucks
        if "fscommand" in tag_lower:
            bad_tags.add(tag)
            continue
        # I'd be impressed if an attacker tried this one (super obscure)
        if "seeksegmenttime" in tag_lower:
            bad_tags.add(tag)
            continue
        # Yes we'll protect IE users from themselves...
        if "vbscript:" in tag_lower:
            bad_tags.add(tag)
            continue
    if replacement == "entities":
        for bad_tag in bad_tags:
            escaped = cgi.escape(bad_tag).encode('ascii', 'xmlcharrefreplace')
            html = html.replace(bad_tag, escaped.decode('ascii'))
    else:
        for bad_tag in bad_tags:
            html = html.replace(bad_tag, replacement)
    return (html, bad_tags)