Clean¶
Clean untrusted or raw HTML: sanitize it against an allowlist and rewrite bare URLs into links. Sanitizing is a
successor to bleach.clean – build a Policy (or take a preset), then sanitize; a non-overridable baseline
removes scripting elements, event-handler attributes, and javascript: URLs regardless of the policy. Linkifying is a
successor to bleach.linkify – it finds URLs and email addresses and wraps them
in <a> links, HTML-aware so it never links inside an existing <a>, a raw-text element, or a caller’s
skip_tags.
- turbohtml.clean.sanitize(html, options=None)[source]¶
Sanitize an HTML fragment against a policy.
- Parameters:
- Return type:
- Returns:
the sanitized, safe HTML.
- Raises:
TypeError – if a set-typed policy field (
tags,url_schemes,remove_with_content,css_properties,attribute_prefixes,media_hosts) holds a value that is not a set or frozenset, orattribute_prefixescontains a non-string.ValueError – if
attribute_prefixescontains an empty string, which would match every attribute.
sanitize_report() sanitizes and also returns what the policy dropped, one Removed record per removed
element or stripped attribute, the way DOMPurify populates DOMPurify.removed.
- turbohtml.clean.sanitize_report(html, options=None)[source]¶
Sanitize a fragment and report what the policy dropped, like DOMPurify’s
DOMPurify.removed.- Parameters:
- Return type:
- Returns:
the sanitized HTML paired with one
Removedrecord per dropped element or attribute.- Raises:
TypeError – like
sanitize(), on a mistyped set-valued policy field.ValueError – like
sanitize(), on an emptyattribute_prefixesentry.
- class turbohtml.clean.Removed(tag, attribute=None)[source]¶
One item a policy dropped, the shape
Sanitizer.sanitize_report()returns.
- class turbohtml.clean.Sanitizer(options=None)[source]¶
A reusable sanitizer; build it once from a
Policyand callsanitize()from any thread.- sanitize(html)[source]¶
Sanitize an HTML fragment.
- Parameters:
html (
str) – the untrusted HTML fragment.- Return type:
- Returns:
the sanitized, safe HTML.
- Raises:
TypeError – if a set-typed policy field (
tags,url_schemes,remove_with_content,css_properties,attribute_prefixes,media_hosts) holds a value that is not a set or frozenset, orattribute_prefixescontains a non-string.ValueError – if
attribute_prefixescontains an empty string, which would match every attribute.
- sanitize_report(html)[source]¶
Sanitize a fragment and report what the policy dropped, the way DOMPurify populates
DOMPurify.removed.Every disallowed element (removed, stripped, or escaped) and every stripped attribute becomes one
Removedrecord, in the order the walk reached it, so a caller can log or tune a policy against evidence instead of guessing.- Parameters:
html (
str) – the untrusted HTML fragment.- Return type:
- Returns:
the sanitized HTML paired with the list of dropped items.
- Raises:
TypeError – like
sanitize(), on a mistyped set-valued policy field.ValueError – like
sanitize(), on an emptyattribute_prefixesentry.
- class turbohtml.clean.Policy(tags=frozenset({'a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'strong', 'ul'}), attributes=<factory>, url_schemes=frozenset({'http', 'https', 'mailto'}), allow_relative_urls=True, on_disallowed_tag=OnDisallowed.ESCAPE, strip_comments=True, add_link_rel=frozenset({}), attribute_filter=None, set_attributes=<factory>, remove_with_content=frozenset({}), css_properties=frozenset({'azimuth', 'background-color', 'border-bottom-color', 'border-collapse', 'border-color', 'border-left-color', 'border-right-color', 'border-top-color', 'clear', 'color', 'cursor', 'direction', 'display', 'elevation', 'fill', 'fill-opacity', 'fill-rule', 'float', 'font', 'font-family', 'font-size', 'font-style', 'font-variant', 'font-weight', 'height', 'letter-spacing', 'line-height', 'overflow', 'pause', 'pause-after', 'pause-before', 'pitch', 'pitch-range', 'richness', 'speak', 'speak-header', 'speak-numeral', 'speak-punctuation', 'speech-rate', 'stress', 'stroke', 'stroke-linecap', 'stroke-linejoin', 'stroke-opacity', 'stroke-width', 'text-align', 'text-decoration', 'text-indent', 'unicode-bidi', 'vertical-align', 'voice-family', 'volume', 'white-space', 'width'}), attribute_prefixes=frozenset({}), attribute_values=<factory>, media_hosts=frozenset({}), strip_template_markers=False, allowed_styles=<factory>, transform_tags=<factory>, isolate_named_props=False, custom_element_check=None, custom_attribute_check=None, allow_customized_builtins=False, allow_html=True, allow_svg=True, allow_mathml=True, xml=False)[source]¶
An immutable, thread-safe description of what sanitizing keeps.
Build one and reuse it across threads. Whatever a policy allows, a non-configurable baseline still removes the unsafe element set, event-handler attributes, and dangerous URL schemes, so every policy is safe by construction.
- Parameters:
tags (
frozenset[str]) – the allowed element set; any tag outside it is handled peron_disallowed_tag.attributes (
Mapping[str,frozenset[str]]) – allowed attribute names keyed by tag ("*"as the key matches every tag, and"*"inside a set allows every name).url_schemes (
frozenset[str]) – the allowlist for URL-bearing attributes such ashrefandsrc.allow_relative_urls (
bool) – keep relative (scheme-less) URLs, which carry no scheme to check.on_disallowed_tag (
OnDisallowed) – how to treat a tag not intags(OnDisallowed: escape, strip, or remove).strip_comments (
bool) – drop HTML comments from the output.add_link_rel (
frozenset[str]) –reltokens forced onto every kept<a href>(e.g.noopener).attribute_filter (
Callable[[str,str,str],str|None] |None) – an optional last word over every surviving attribute, returning a replacement value orNoneto drop it.set_attributes (
Mapping[str,Mapping[str,str]]) – attribute values forced onto every kept instance of a tag (added if absent, overwritten if present); unlikeattribute_filter, this can add attributes that were not there.remove_with_content (
frozenset[str]) – disallowed tags whose whole subtree is dropped (e.g.script/style) rather than escaped or stripped, so their text never leaks into the output.css_properties (
frozenset[str]) – the CSS property allowlist. A keptstyleattribute and, whenstyleis intags, the<style>element’s stylesheet body are both scrubbed against it: any declaration whose property name is not in the set (or whose value smugglesexpression()or aurl()with a disallowed scheme) is dropped, while selectors and block nesting are kept, so dangerous CSS cannot ride in on a keptstyle.attribute_prefixes (
frozenset[str]) – allow any attribute whose name starts with one of these prefixes (e.g."data-"for everydata-*), on top of the exact-name and"*"matches inattributes.attribute_values (
Mapping[str,Mapping[str,frozenset[str]]]) – restrict a kept attribute to literal values, keyed{tag: {attribute: allowed_values}}; a surviving attribute whose value is outside its set is dropped. This narrows an attributeattributesalready admits and cannot admit a new one.transform_tags (
Mapping[str,Transform|str]) – rename rules applied before the allowlist, keyed by source tag (sanitize-html’stransformTags). A value that is a plain string renames the element to it; aTransformrenames it and adds attributes. The rename runs first, then the renamed element is re-checked from scratch, so a transform decides an element’s name but never its safety: the allowlist still governs the target tag (mapping toscriptstill drops it), and any added attributes are scrubbed like the element’s own. Only HTML elements are transformed, matched by tag name. Empty (the default) renames nothing.allowed_styles (
Mapping[str,Mapping[str,Sequence[str|Pattern[str]]]]) – a per-property value allowlist for thestyleattribute, keyed{tag: {property: [pattern, ...]}}with"*"as a tag matching every element (sanitize-html’sallowedStyles). A declaration survives only when its property is listed for the element’s tag or"*"(their pattern lists union) and its value matches one of the patterns (an unanchoredre.search()). Patterns are strings or precompiledre.Pattern. This narrows on top ofcss_propertiesand the non-configurable dangerous-value baseline: a property this admits is still dropped unlesscss_propertieslists it too, andexpression()or aurl()with a disallowed scheme is dropped even if a pattern would match it. Empty (the default) leavescss_propertiesas the onlystylefilter.media_hosts (
frozenset[str]) – allowed hosts for an embedded-mediasrc(audio,video,source,track); asrcwhose URL host is not one of these lowercase entries is dropped. Empty means no host restriction.strip_template_markers (
bool) – collapse template-engine expressions ({{ }},${ },<% %>) in kept text and attribute values to a single space, so sanitized output cannot re-inject when a template engine (Angular, Vue, Mustache, EJS, ERB) later renders it. DOMPurify’sSAFE_FOR_TEMPLATES; off by default.isolate_named_props (
bool) – prefix every keptidandnamevalue withuser-content-so it cannot shadow a built-indocumentor form property through named access (DOM clobbering:<input name="attributes">makesform.attributesresolve to the input,<img name="body">hidesdocument.body). The prefix is left alone when already present, so re-sanitizing is a fixpoint. DOMPurify’sSANITIZE_NAMED_PROPS; off by default.custom_element_check (
Callable[[str],bool] |None) – a predicate that keeps an unlisted custom element (a hyphenated HTML tag such asmy-widgetorx-card) without naming it intags, DOMPurify’sCUSTOM_ELEMENT_HANDLING.tagNameCheck. Given the element’s lowercased tag name, return true to keep it (passre.compile(r"x-.*").searchto drive it from a regex). Only basic custom-element names reach it (the reservedannotation-xml/font-facefamily never does), and the safety baseline still escapes unsafe tags and scrubson*/URL/style attributes on whatever it keeps.None(the default) escapes every unlisted tag as before.custom_attribute_check (
Callable[[str,str],bool] |None) – a predicate that keeps an unlisted attribute on a kept custom element, DOMPurify’sCUSTOM_ELEMENT_HANDLING.attributeNameCheck. Given(tag, attribute_name)return true to keep it; theon*, URL-scheme, andstylescrubbing still apply, so this widens the name allowlist, never the safety baseline.None(the default) keeps only the attributesattributesalready admits.allow_customized_builtins (
bool) – keep anisattribute whose value passescustom_element_check, so a customized built-in element (<button is="my-button">) survives, DOMPurify’sallowCustomizedBuiltInElements. Off by default, and inert withoutcustom_element_check.allow_html (
bool) – keep HTML-namespace elements; turning it off drops the whole HTML namespace, DOMPurify’sUSE_PROFILES.html. Independent of the tag allowlist, so it composes withallow_svg/allow_mathmlto select which content languages a policy admits.allow_svg (
bool) – keep SVG-namespace elements, DOMPurify’sUSE_PROFILES.svg. Off drops every SVG element even when its tag is intags.allow_mathml (
bool) – keep MathML-namespace elements, DOMPurify’sUSE_PROFILES.mathMl. Off drops every MathML element even when its tag is intags.xml (
bool) – emit well-formed XML/XHTML instead of HTML, DOMPurify’sRETURN_DOMserved through the XML serializer. Every kept empty element self-closes (<br/>), foreign SVG and MathML subtrees carry their namespace declarations, text and attribute values follow the XML escaping rules, and a kept comment or a stray control character is neutralized so the result reparses throughturbohtml.parse_xml(). Use it to clean an XHTML dialect (Reportlab’s RML, ePub content) whose consumer rejects HTML’s bare<br>. Off (the default) keeps the HTML serialization.
- classmethod strict()[source]¶
Allow no markup at all: every tag is escaped to text and every attribute dropped.
- Return type:
- Returns:
the strict policy.
Policy.css_properties allowlists style property names; Policy.allowed_styles narrows further by value, the
way sanitize-html’s allowedStyles does. Key it {tag: {property: [pattern, ...]}} ("*" matches every tag); a
style declaration survives only when its property is listed for the element’s tag or "*" and its value matches
one of the patterns via an unanchored re.search(). It runs on top of css_properties and the dangerous-value
baseline – the property must still be in css_properties, and expression() or a disallowed-scheme url() is
dropped even when a pattern would admit it:
from turbohtml.clean import sanitize, Policy
policy = Policy(
tags=frozenset({"p"}),
attributes={"p": frozenset({"style"})},
css_properties=frozenset({"color"}),
allowed_styles={"*": {"color": [r"^#[0-9a-f]{3,6}$"]}},
)
print(sanitize('<p style="color: #0a0; color: red">ok</p>', policy))
<p style="color: #0a0">ok</p>
Policy.transform_tags renames elements during the same walk, sanitize-html’s transformTags. Key it by source
tag: map to a bare string to rename, or to a Transform to rename and add attributes. The rename runs before
the allowlist, so the renamed element is re-checked from scratch – a transform decides an element’s name but never its
safety. Mapping a tag to script still drops it, and an added attribute is scrubbed like the element’s own, so it
must be allowlisted to survive:
from turbohtml.clean import sanitize, Policy, Transform
policy = Policy(
tags=frozenset({"strong", "div"}),
attributes={"div": frozenset({"class"})},
transform_tags={"b": "strong", "center": Transform("div", {"class": "center"})},
)
print(sanitize("<b>bold</b> and <center>middle</center>", policy))
<strong>bold</strong> and <div class="center">middle</div>
Policy.isolate_named_props prefixes every kept id and name value with user-content-, DOMPurify’s
SANITIZE_NAMED_PROPS. It stops DOM clobbering – an id or name whose value matches a built-in document
or form property shadows that property through named access – by moving the value out of the property namespace. An
already-prefixed value is left alone, so re-sanitizing is a fixpoint. The isolation is applied after
attribute_filter, so the value a filter returns is the one that gets namespaced:
from turbohtml.clean import sanitize, Policy
policy = Policy(
tags=frozenset({"input"}),
attributes={"input": frozenset({"name"})},
isolate_named_props=True,
)
print(sanitize('<input name="attributes">', policy))
<input name="user-content-attributes">
Policy.custom_element_check keeps an unlisted hyphenated custom element when its matcher admits the tag name,
DOMPurify’s CUSTOM_ELEMENT_HANDLING.tagNameCheck; custom_attribute_check does the same for that element’s
attributes (attributeNameCheck), and allow_customized_builtins keeps an is attribute naming a custom
element. The safety baseline still runs on whatever the matcher keeps, so an on* handler is dropped even here:
from turbohtml.clean import sanitize, Policy
policy = Policy(
tags=frozenset({"p"}),
custom_element_check=lambda tag: tag.startswith("x-"),
custom_attribute_check=lambda _tag, name: name.startswith("data-"),
)
print(sanitize('<p><x-card data-id="7" onclick="x">c</x-card></p>', policy))
<p><x-card data-id="7">c</x-card></p>
Policy.allow_html, Policy.allow_svg, and Policy.allow_mathml gate each namespace independently, DOMPurify’s
USE_PROFILES. All default on; turning one off drops that whole namespace even when its tags are allowlisted, so a
policy can keep SVG but not MathML:
policy = Policy(tags=frozenset({"svg", "circle", "math", "mi"}), allow_svg=False)
print(sanitize("<svg><circle></circle></svg><math><mi>x</mi></math>", policy))
<svg><circle></circle></svg><math><mi>x</mi></math>
Policy.xml serializes the cleaned tree as well-formed XML/XHTML rather than HTML. The walk is unchanged, so the
policy is exactly as safe; the difference is output syntax – an empty element self-closes, values follow the XML
escaping rules, a foreign root declares its namespace, and any comment, control character, or attribute name XML cannot
hold is neutralized – so the result reparses through turbohtml.parse_xml(). Use it to feed an XHTML dialect that
rejects HTML’s bare <br>:
policy = Policy(tags=frozenset({"p", "br"}), xml=True)
print(sanitize("<p>one<br>two</p>", policy))
<p>one<br/>two</p>
- class turbohtml.clean.Transform(tag, attributes=<factory>)[source]¶
A rename rule for
Policy.transform_tags, sanitize-html’ssimpleTransform.Mapping a source tag to a bare string renames it; mapping it to a
Transformrenames it and adds attributes. The added attributes are not trusted: they join the element’s own attributes and go through the same allowlist and URL/style scrubbing, so a transform cannot add an attribute the policy would otherwise strip.
- class turbohtml.clean.OnDisallowed(*values)[source]¶
What to do with an element the policy does not allow.
ESCAPErenders the tag as visible text (<x>becomes<x>), the safe bleach default that keeps the content readable;STRIPdrops the tag but keeps its children;REMOVEdrops the tag and its whole subtree.
The sanitizer ships bleach’s default allowlists as module constants, so a Policy can extend a known baseline
instead of enumerating a safe set from scratch.
- turbohtml.clean.DEFAULT_TAGS¶
Build an immutable unordered collection of unique elements.
The tags the default policy keeps:
a,abbr,acronym,b,blockquote,code,em,i,li,ol,strong,ul.
- turbohtml.clean.DEFAULT_ATTRIBUTES¶
Read-only proxy of a mapping.
The attributes the default policy keeps, keyed by tag:
hrefandtitleona, andtitleonabbrandacronym.
- turbohtml.clean.DEFAULT_SCHEMES¶
Build an immutable unordered collection of unique elements.
The URL schemes the default policy allows in an
hreforsrc:http,https,mailto.
- turbohtml.clean.DEFAULT_CSS_PROPERTIES¶
Build an immutable unordered collection of unique elements.
The CSS properties the default policy keeps when scrubbing a
styleattribute: the CSS 2.1 safe set plus the SVG paint properties.
Linkifying¶
A Linkify configuration object carries the knobs: a callback receives each generated LinkCandidate and
returns it to keep the link or None to leave the text bare, process_existing runs the callbacks over <a>
tags already in the input (a callback reads LinkCandidate.existing to tell the two apart), extra_tlds extends
bare-domain detection beyond the built-in IANA table, and schemes sets which explicit-scheme URLs autolink
(defaulting to the built-in http/https/ftp set, so a typo scheme or a javascript:// payload stays plain
text).
- turbohtml.clean.linkify(text, options=None)[source]¶
Find URLs and email addresses in HTML and wrap them in
<a>links, leaving existing markup untouched.
- class turbohtml.clean.Linkify(callbacks=(<function nofollow>, ), skip_tags=None, parse_email=False, process_existing=False, extra_tlds=None, schemes=None)[source]¶
An immutable, thread-safe description of how
linkify()andLinkerfind and rewrite links.Build one and reuse it across threads.
- Parameters:
callbacks (
Iterable[Callable[[LinkCandidate],LinkCandidate|None]]) – callables run on each detected link to adjust or veto it (defaults toDEFAULT_CALLBACKS).skip_tags (
Iterable[str] |None) – tags whose text is left untouched, such aspreandcode.parse_email (
bool) – also autolink bare email addresses asmailto:links.process_existing (
bool) – run the callbacks over<a>tags already present, not only freshly detected links.extra_tlds (
Iterable[str] |None) – top-level domains that make a bare domain a link, on top of the built-in IANA table.schemes (
Iterable[str] |None) – the exact set ofscheme://URL schemes that autolink;Nonekeeps the built-inhttp/https/ftpdefault, so a typo scheme or ajavascript://payload stays plain text. A bare domain is always treated ashttpand is governed by the TLD table, notschemes.
- class turbohtml.clean.Linker(options=None)[source]¶
A reusable linkifier; build it once from a
Linkifyconfiguration and calllinkify()per document.
- class turbohtml.clean.LinkCandidate(url, text, attrs=None, *, existing=False)[source]¶
A link handed to each callback to mutate or veto.
A callback returns the link to keep it, or
Noneto drop the anchor: a detected link stays plain text, an existing one is unwrapped to its contents.
- turbohtml.clean.nofollow(link)[source]¶
Add
rel="nofollow"to a web link so search engines skip it, leavingmailto:and other links alone.- Parameters:
link (
LinkCandidate) – the link to adjust.- Return type:
- Returns:
the link, with
nofollowadded when it is a web link.
- turbohtml.clean.target_blank(link)[source]¶
Open a web link in a new tab, stripping a stale
targetfrom a non-web link so it cannot leak through.- Parameters:
link (
LinkCandidate) – the link to adjust.- Return type:
- Returns:
the link, with
targetset on a web link or cleared on a non-web link.
- turbohtml.clean.Callback¶
str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to ‘utf-8’. errors defaults to ‘strict’.
The type of a linkify callback: a callable that takes one
LinkCandidateand returns it to keep the link orNoneto leave the text bare.nofollow()andtarget_blank()are built-in examples.
- turbohtml.clean.DEFAULT_CALLBACKS¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.
The callbacks
linkify()applies when the caller passes none:(nofollow,), so bare-URL links getrel="nofollow"unless you opt out.
To only locate links in plain text rather than rewrite HTML, use LinkDetector. It returns a LinkSpan
for each match and accepts custom tlds and scheme-less schemes.
- class turbohtml.clean.LinkDetector(*, emails=True, bare_domains=True, tlds=(), schemes=())[source]¶
Find the links in plain text, configured once and reused per call.
Unlike
Linker, which rewrites HTML, a detector only locates links and hands backLinkSpanobjects, leaving the text untouched.- Parameters:
emails (
bool) – detect bare email addresses.bare_domains (
bool) – detect bare domains (example.com) with no explicit scheme.tlds (
Iterable[str]) – custom top-level domains accepted for bare-domain matching, on top of the IANA table.schemes (
Iterable[str]) – extra schemes to detect, both as scheme-less opaque URLs (tel:,bitcoin:) and asscheme://authority URLs, on top of the built-inhttp/https/ftpset; an unregistered scheme such asjavascript://or a typo likehppt://is not detected.
- class turbohtml.clean.LinkSpan(start, end, text, url, is_email)[source]¶
One URL or email address found in a run of plain text.
- Parameters:
start (
int) – the half-open start offset of the match in the scanned text.end (
int) – the half-open end offset of the match in the scanned text.text (
str) – the matched substring exactly as it appeared.url (
str) – the normalizedhref(mailto:for an email,http://for a bare domain, the text itself for ascheme://or registered scheme-less URL).is_email (
bool) – whether the match is an email address.
Minifying¶
minify() shrinks an HTML document in one call – it parses the input and serializes it through the round-trip-safe
Minify layout, so the output reparses to the same tree and minifying is idempotent
(minify(minify(x)) == minify(x)). It replaces minify-html and htmlmin. The four transforms (fold
insignificant whitespace, omit optional tags, unquote attributes, strip comments) default on; pass a
Minify to turn any off.
- turbohtml.clean.minify(html, options=None)[source]¶
Minify an HTML document.
Parse
htmlinto a tree and serialize it with the minifying layout, folding insignificant whitespace, omitting the start/end tags the WHATWG rules make optional, dropping redundant attribute quotes, and stripping comments. Each transform is round-trip safe, so the result reparses to the same tree and minifying is idempotent.
The HTML minify layout emits <style> bodies verbatim; to also minify embedded CSS, run minify_css() (below)
over a <style> body yourself, which is what minify-html’s minify_css did inline. Passing a JSMinify
as Minify(minify_js=...) rewrites inline <script> content, covering minify-html’s minify_js. The doctype
is always normalized to <!doctype html> (minify-html’s minify_doctype is implicit), and HTML has no
processing instructions to drop (remove_processing_instructions is moot under the WHATWG parser, which reads them as
bogus comments).
JS minification¶
minify_js() minifies a JavaScript string on its own, the jsmin/rjsmin successor. It always folds
whitespace, comments, and number literals; a frozen JSMinify toggles the optional mangle (rename local
bindings) and fold (constant-fold and eliminate dead code) passes. The same JSMinify passed as
Minify(minify_js=...) rewrites inline <script> content during HTML minification.
- turbohtml.clean.minify_js(source, options=None, on_error='raise')[source]¶
Minify a JavaScript source string, returning the shortest equivalent program.
Every transform preserves semantics, so the result evaluates identically to
source.optionsdefaults to the full pipeline (fold and mangle).on_errorchooses what happens when the parser cannot handle the script. The default"raise"fails loudly;"passthrough"returnssourceunchanged instead, the never-fail contractjsminandrjsminguarantee – the same verbatim fallback the inline-<script>path already applies so one bad script cannot break serialization.- Parameters:
- Return type:
- Returns:
the minified JavaScript, or
sourceverbatim when it is unparsable andon_error="passthrough".- Raises:
ValueError – if
on_erroris not"raise"or"passthrough", or – whenon_error="raise"– if the parser cannot handle the script (a lexical or syntax error, or input nested past the depth limit), with the message naming the construct, its byte offset, and the offending token.
- class turbohtml.clean.JSMinify(mangle=True, fold=True)[source]¶
Which optional JavaScript-minifier passes to run.
Whitespace and numeric-literal minification is unconditional, as is comment removal – except
/*! ... */bang comments and any comment carrying an@licenseor@preserveannotation, which are kept byte-exact as a leading banner so a license header survives (the same rule the CSS minifier applies).manglerenames local bindings to short names (the bulk of the size win) andfoldruns constant folding and dead-code elimination; turning either off keeps that aspect of the source readable (e.g.mangle=Falsefor debuggable output).
CSS minification¶
Minify CSS the value-safe way: every transform produces output that parses to the same cascade. minify_css() takes
a whole stylesheet; minify_css_inline() takes a bare declaration list, the value of an HTML style attribute.
Both are value-safe at any baseline; the optional CSSMinify baseline year only bounds how new the output
syntax may be.
- turbohtml.clean.minify_css_inline(css, options=None)[source]¶
Minify an inline declaration list, the value of an HTML
styleattribute.Use this rather than
minify_css()when the source is bare declarations (color:red; margin:0) with no surrounding selector or braces.
- class turbohtml.clean.CSSMinify(baseline=None)[source]¶
Options for
minify_css()andminify_css_inline().baselineis the Baseline year the output may target: the minifier applies a transform whose output syntax reached Baseline in yearYonly whenbaseline >= Y.None(the default) emits only long-interoperable syntax;2021additionally merges the shorthands that reached Baseline that year (inset, the flexgap, the two-valueoverflow). Every year is value-safe – the year bounds only how new the output syntax may be, never the cascade it parses to.
Transformations¶
Every transformation below preserves the computed value: the output parses to the same cascade as the input on any conformant browser. Each links to the specification that establishes the equivalence.
Numbers and dimensions¶
Drop a leading
+, redundant leading and trailing zeros, and switch toe-notation when it is shorter (Syntax 3 §4.3.3, Values 4 §6.1).a{width:+0.50px}→a{width:.5px},a{margin:100000px}→a{margin:1e5px}.Lower-case a known unit and drop the unit on a zero
<length>(0px→0); angle, time, frequency and other dimensions keep their unit, since a bare0is a<length>only (Values 4 §5.2).a{margin:0PX}→a{margin:0}, whilea{transform:rotate(0deg)}is unchanged.Fold a
calc()of constant, like-united operands with exact rational arithmetic; a non-combinable or non-terminating result is kept verbatim, and+/-without surrounding whitespace is left untouched (Values 4 §10).a{width:calc(1px + 2px)}→a{width:3px}.
Colors¶
Swap a named color and its hex for whichever is shorter, and shorten
#rrggbb→#rgband#rrggbbaa→#rgba(Color 4 §5.2, §6.1).a{color:#ffffff}→a{color:#fff},a{color:#000080}→a{color:navy}.Fold an opaque
rgb()/hsl()to hex only when every channel lands on an exact 8-bit value; a fractional channel is kept functional, since rounding it changes the color (Color 4 §15).a{color:rgb(255,0,0)}→a{color:red}.Collapse
transparentandrgba(0,0,0,0)to#0000, drop an alpha of1, and use the shorterrgb()/hsl()alias ofrgba()/hsla()(Color 4 §4).a{color:transparent}→a{color:#0000},a{color:rgba(1,2,3,.5)}→a{color:rgb(1,2,3,.5)}.
Shorthands¶
Collapse a 1–4 value box shorthand when mirrored edges are equal, and merge the four physical longhands back into the shorthand (Box 3 §6–7, Cascade 5 §2.2).
a{margin:1px 1px 1px 1px}→a{margin:1px}.Collapse the
background,background-position,background-repeat(Backgrounds 3 §3),flex(Flexbox 1 §7.1.1) andfont(Fonts 4 §2.7) shorthands to their shortest equivalent form.a{font:bold 12px x}→a{font:700 12px x}.Merge
flex-direction+flex-wrapintoflex-flow, and each Box Alignment axis pair into itsplace-shorthand –align-content+justify-contentintoplace-content,align-items+justify-itemsintoplace-items, andalign-self+justify-selfintoplace-self(Box Alignment 3).a{align-items:center;justify-items:center}→a{place-items:center}.Merge the four
border-*-radiuscorners intoborder-radius(Backgrounds 3 §6) andoutline-width+outline-style+outline-colorintooutline(UI 4 §3.1); each resets only its own longhands, and the corner merge stands down for an elliptical corner or a logical sibling.a{outline-width:1px; outline-style:solid;outline-color:red}→a{outline:1px solid red}.
Structure and selectors¶
Collapse insignificant whitespace and strip comments, keeping a
/*! … */bang comment (Syntax 3 §3.3)./*c*/a{color:red}→a{color:red}, while/*! keep */a{color:red}→/*!keep*/a{color:red}.Drop a declaration an identically-keyed later one overrides, remove an empty rule or an empty
@media/@supports/@container(an empty@layeror@keyframesis kept, since either is observable), merge two rules with the same selector or an identical body – even across intervening rules, as long as each sets none of the moved properties so the cascade cannot change – and fuse consecutive@mediablocks that share a prelude (Cascade 5 §6.4.4).a{}b{c:d}→b{c:d},@media print{}is dropped, anda{color:red}b{margin:0}a{font-size:2px}→a{color:red;font-size:2px}b{margin:0}.Lower-case type selectors, trim combinator whitespace, drop a redundant universal
*before a subclass, write the four legacy pseudo-elements with one colon (::before→:before), and unquote an attribute value that is a valid identifier (Selectors 4 §5–6, Pseudo-Elements 4 §8); a custom-property name keeps its case (Variables 1 §2).Keep a custom-property value byte-exact past edge trimming: §2 defines the value as the literal token stream,
var()splices it verbatim, andgetComputedStyle().getPropertyValue()reads it back as written, so collapsing its internal whitespace – which the other PyPI minifiers do for a few hundred bytes on custom-property-heavy CSS – is observable and never applied.A{X:1}→a{x:1},[a="Foo"]{x:1}→[a=Foo]{x:1}.Rewrite a
@keyframesfromselector to0%(Animations 1), and drop the space beforeand/orafter a)in a media query (Media Queries 4).@keyframes k{from{opacity:0}}→@keyframes k{0%{opacity:0}}.
Baseline¶
Every transform above is value-safe and, by default, applies to every stylesheet – its output syntax has been
interoperable for years. A few emit a shorthand whose interop is more recent, so they are gated on the
CSSMinify baseline year and stay off unless you target that year or later. Each row lists the year a
transform’s output syntax reached Baseline; a transform tagged year Y runs when
baseline >= Y.
Baseline year |
Transforms enabled |
|---|---|
any ( |
Every transform in the sections above: numbers, colors, box/flex/ |
|
Merge |
turbohtml.migration.bleach¶
A drop-in for bleach.clean for projects migrating off bleach. It translates bleach’s arguments onto a
Policy; the safety baseline still applies, so an attributes callable cannot re-admit an
event handler or a javascript: URL.
- turbohtml.migration.bleach.clean(text, tags=None, attributes=None, protocols=None, strip=False, strip_comments=True, css_sanitizer=None)[source]¶
Sanitize HTML against a bleach-style allowlist, the way
bleach.cleandid.- Parameters:
text (
str) – the untrusted HTML.tags (
Iterable[str] |None) – the allowed tag names; None uses bleach’s default set.attributes (
object) – the allowed attributes as a flat list, a per-tag dict, or a (tag, name, value) predicate; None uses bleach’s default.protocols (
Iterable[str] |None) – the allowed URL schemes; None uses bleach’s default.strip (
bool) – drop a disallowed tag and keep its children, rather than escaping it to text.strip_comments (
bool) – drop HTML comments from the output.css_sanitizer (
object) – accepted for signature compatibility; passing one raisesNotImplementedError.
- Return type:
- Returns:
the sanitized, safe HTML.