#####################
From html-sanitizer
#####################
.. package-meta:: html-sanitizer matthiask/html-sanitizer
`html-sanitizer `_ is an allowlist HTML sanitizer built on lxml, from the
author of FeinCMS and django-content-editor. You configure it with a ``settings`` dict: ``tags`` (a set of allowed
elements), ``attributes`` (allowed attribute names keyed by tag), ``empty`` and ``separate`` (tags that may stay empty
or must not merge with an adjacent twin), a ``sanitize_href`` scheme check, ``add_nofollow``, ``autolink``, and
``element_preprocessors``/``element_postprocessors`` hooks. Beyond dropping disallowed markup it normalizes the tree:
collapsing whitespace, merging adjacent identical tags, and dropping empty ones. It is the sanitizer behind Django CMS
and rich-text-editor stacks that store user-authored HTML.
turbohtml covers the same allowlist job from its ``turbohtml.clean`` module. The move is a
settings-to-:class:`~turbohtml.clean.Policy` translation rather than a rethink, and turbohtml runs the filtering in C
over its own WHATWG tree builder instead of an lxml parse.
*****************************
turbohtml vs html-sanitizer
*****************************
.. list-table::
:header-rows: 1
:widths: 22 39 39
- - Dimension
- turbohtml
- html-sanitizer
- - Scope
- Full WHATWG parser, serializer, sanitizer, linkifier, minifier, selectors
- Allowlist sanitize with whitespace and typographic normalization, over lxml
- - Feature breadth
- Escape/strip/remove per tag, value-rewriting attribute filter, forced attributes, CSS-property scrubbing, fixed
safety baseline
- Tag/attribute allowlist, tag merging, whitespace and typographic cleanup, autolink, pre/post-processor hooks
- - Performance
- Filtering in C on a native tree
- Pure Python over an lxml parse
- - Typing
- Fully annotated, ``py.typed``
- Untyped
- - Dependencies
- None (self-contained C extension)
- lxml
- - Maintenance
- Active
- Active, single maintainer
Feature overlap
===============
The shared allowlist surface ports one-to-one:
- ``Sanitizer(settings).sanitize(text)`` -> :func:`turbohtml.clean.sanitize` with a :class:`~turbohtml.clean.Policy`, or
a reusable :class:`~turbohtml.clean.Sanitizer`.
- ``settings["tags"]`` (a set) -> ``Policy.tags`` (a ``frozenset``).
- ``settings["attributes"]`` (per-tag dict) -> ``Policy.attributes`` (per-tag ``frozenset``; ``"*"`` as key matches
every tag).
- ``sanitize_href`` scheme check -> ``Policy.url_schemes`` plus ``Policy.allow_relative_urls``.
- ``add_nofollow=True`` -> ``Policy.add_link_rel = frozenset({"nofollow"})``.
What turbohtml adds
===================
- A frozen, thread-safe :class:`~turbohtml.clean.Policy` you build once and reuse across threads.
- An :class:`~turbohtml.clean.OnDisallowed` enum that names three outcomes for a disallowed tag: ``ESCAPE`` (render as
text), ``STRIP`` (unwrap, keep children), and ``REMOVE`` (drop the subtree). html-sanitizer only unwraps.
- ``Policy.remove_with_content`` and a fixed safety baseline that drops ``