##################### 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 ``