###################### From lxml-html-clean ###################### .. package-meta:: lxml_html_clean fedora-python/lxml_html_clean `lxml-html-clean `_ is the ``Cleaner`` that used to live in ``lxml.html.clean``, split into its own package when lxml dropped the module. It cleans HTML by **blocklist**: you toggle off categories of dangerous content (``scripts``, ``javascript``, ``style``, ``comments``, ``embedded``, ``frames``, ``forms``, ``meta``, ``annoying_tags``, ...) and everything the toggles do not name survives. A tag the library has never heard of passes through untouched. ``Cleaner`` runs in Python over an lxml element tree parsed by libxml2, so it inherits lxml's runtime dependency and its non-WHATWG HTML parser. It is the standard choice for projects already built on lxml that need to scrub user HTML in place. ``turbohtml.clean`` covers the same ground from the other direction: :func:`~turbohtml.clean.sanitize` filters against an **allowlist** driven by a :class:`~turbohtml.clean.Policy`, parses with the WHATWG algorithm browsers use, and runs the filtering walk in C. ****************************** turbohtml vs lxml-html-clean ****************************** .. list-table:: :header-rows: 1 :widths: 16 42 42 - - Dimension - turbohtml - lxml-html-clean - - Scope - Allowlist HTML sanitizer, plus linkify and HTML/CSS minify in one module - Blocklist ``Cleaner`` extracted from ``lxml.html.clean``; sanitization plus ``autolink_html`` - - Feature breadth - Policy presets (``strict``/``basic``/``relaxed``), per-tag attribute allowlists, attribute-prefix/value allowlists, embedded-media ``host_whitelist``, ``style`` scrubbing, link ``rel`` injection, attribute-filter hook - ~20 category toggles, ``host_whitelist`` for embedded media, ``safe_attrs``, ``add_nofollow``, in-place tree mutation - - Performance - C filtering walk; sanitizes an order of magnitude faster (see below) - Python traversal over a libxml2-parsed lxml tree - - Typing - Fully type annotated - No type annotations shipped - - Dependencies - Self-contained C extension, no runtime dependencies - Requires lxml (libxml2 / libxslt) - - Maintenance - Actively developed - Community-maintained successor to the removed ``lxml.html.clean`` Feature overlap =============== The shared surface ports directly: - Strip scripting: ``Cleaner(scripts=True, javascript=True)`` becomes the non-overridable safety baseline that removes script elements, event-handler attributes, and ``javascript:`` URLs on every policy. - Restrict the kept tag set: ``allow_tags=`` maps to :class:`~turbohtml.clean.Policy` ``tags``. - Drop an element with its whole subtree: ``kill_tags=`` maps to ``Policy.remove_with_content``. - Drop comments: ``comments=True`` maps to ``Policy.strip_comments`` (on by default). - Constrain attributes: ``safe_attrs_only`` / ``safe_attrs=`` map to the per-tag ``Policy.attributes`` allowlist. - Force ``rel`` tokens on links: ``add_nofollow=True`` maps to ``Policy.add_link_rel`` (e.g. ``frozenset({"nofollow"})``). - Remove forms, frames, and embedded media: leave those tags out of ``Policy.tags`` instead of toggling ``forms``, ``frames``, ``embedded``. - Restrict embedded media to named hosts: ``host_whitelist=`` maps to :class:`Policy.media_hosts `, which drops an ``audio``/``video``/``source``/``track`` ``src`` whose URL host is not on the allowlist. What turbohtml adds =================== - Allowlist model: nothing survives unless the policy names it, so markup the author never anticipated cannot slip through a missing toggle. - WHATWG parsing: the input is parsed by the same algorithm browsers use, so the cleaned tree matches what a browser would build, not what libxml2's HTML parser produces. - ``style`` attribute scrubbing against ``Policy.css_properties``, dropping any declaration whose property is not allowed. - ``Policy.attribute_filter``, a per-attribute callback that gets the last word on every surviving value, and ``Policy.add_link_rel`` to inject ``rel`` tokens. - ``Policy.on_disallowed_tag`` (:class:`~turbohtml.clean.OnDisallowed`) to choose escape, strip, or remove for a tag outside the allowlist. - Companion :func:`~turbohtml.clean.linkify`, :func:`~turbohtml.clean.minify`, and value-safe :func:`~turbohtml.clean.minify_css` in the same module. What lxml-html-clean has that turbohtml does not ================================================ - In-place cleaning of an existing lxml element or subtree: ``Cleaner`` mutates the tree you already hold. :func:`~turbohtml.clean.sanitize` takes an HTML string and returns an HTML string, so there is no in-tree equivalent. - ``annoying_tags``: a named toggle for presentational cruft (``blink``, ``marquee``). turbohtml has no dedicated flag; exclude those tags from ``Policy.tags`` (the allowlist already drops them by default). Performance =========== ``turbohtml.clean`` runs the filtering walk in C rather than over an lxml tree, sanitizing about 13x faster than the blocklist cleaner. lxml-html-clean also ships ``autolink_html``, benchmarked here as the linkify op: turbohtml runs 2.1x faster on the comment case and 1.1x on markup, and about half the speed on plain prose (0.5x). .. bench-table:: :file: bench/lxml-html-clean.json **************** How to migrate **************** Swap the import and invert the model: instead of switching dangerous categories off, declare the small set you keep. .. code-block:: python # lxml-html-clean: enumerate what to strip, keep the rest from lxml_html_clean import Cleaner Cleaner(scripts=True, javascript=True, comments=True, style=True, forms=True).clean_html(text) .. list-table:: :header-rows: 1 :widths: 50 50 - - `lxml-html-clean `__ - turbohtml - - ``Cleaner(...).clean_html(text)`` - :func:`turbohtml.clean.sanitize` with a :class:`~turbohtml.clean.Policy` - - ``allow_tags=`` - ``Policy.tags`` - - ``safe_attrs_only=``, ``safe_attrs=`` - ``Policy.attributes`` - - ``kill_tags=`` (drop element with content) - ``Policy.remove_with_content`` - - ``comments=True`` - ``Policy.strip_comments`` - - ``add_nofollow=True`` - ``Policy.add_link_rel`` - - ``host_whitelist=`` - ``Policy.media_hosts`` .. testcode:: from turbohtml.clean import sanitize, Policy print( sanitize( "

Hi l

", Policy(tags=frozenset({"p", "a"}), attributes={"a": frozenset({"href"})}), ) ) .. testoutput::

Hi<script>x()</script> l

The ``javascript:`` URL is gone because ``http``/``https``/``mailto`` are the only schemes the policy admits, and the ``