################# Getting started ################# Go from an empty environment to escaping and unescaping your first HTML. Install turbohtml from PyPI: .. code-block:: console $ pip install turbohtml Open a Python prompt and escape some text for safe inclusion in an HTML page: .. testcode:: import turbohtml print(turbohtml.escape("5 > 3 & 2 < 4")) .. testoutput:: 5 > 3 & 2 < 4 By default ``escape`` escapes quotation marks too, which you want inside an attribute value: .. testcode:: print(turbohtml.escape('name="O\'Brien"')) .. testoutput:: name="O'Brien" Reverse the process: turn HTML character references back into text: .. testcode:: print(turbohtml.unescape("Tom & Jerry, café")) .. testoutput:: Tom & Jerry, café Stay with the string helpers below, or continue to :doc:`tokenizing` to break whole documents into tokens. ******************** Linkify plain text ******************** One more string-in, string-out helper rounds out the getting-started toolkit: :func:`turbohtml.clean.linkify` finds the URLs in a run of text and wraps each one in an anchor, leaving the surrounding characters untouched. It is the quickest way to turn a plain message into clickable HTML: .. testcode:: from turbohtml.clean import linkify print(linkify("Visit https://example.com today")) .. testoutput:: Visit https://example.com today Every generated link carries ``rel="nofollow"`` by default, so untrusted text stays safe to publish. ************************ Normalize Unicode text ************************ One more string helper cleans up Unicode itself. The same character can be typed as one code point or as a base letter plus a combining mark, so ``"café"`` need not equal ``"café"`` even though they look identical. :func:`turbohtml.detect.normalize` folds text to a Unicode normalization form, so the two compare equal: .. testcode:: from turbohtml.detect import normalize composed = "café" # e-acute as one code point decomposed = "café" # plain e followed by a combining acute accent print(composed == decomposed) print(normalize("NFC", composed) == normalize("NFC", decomposed)) .. testoutput:: False True Reach for ``NFC`` before you compare or store text; the :doc:`/how-to/encoding` guide covers the other three forms. ************************* Sanitize untrusted HTML ************************* When the input is already HTML rather than plain text, clean it against an allowlist with :func:`turbohtml.clean.sanitize`. A :class:`~turbohtml.clean.Policy` says what to keep; here it allows a ``

`` with a ``style`` attribute and, through ``allowed_styles``, keeps a ``color`` only when it is a hex value. A non-overridable baseline still drops dangerous CSS, so the ``url(javascript:...)`` goes even though the property name is allowed: .. testcode:: 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('

Hi

', policy)) .. testoutput::

Hi

************************** Resolve a computed style ************************** When you need the style a browser would apply rather than the raw rules, run the CSS cascade with :func:`turbohtml.cssom.computed_style`. It reads the document's ``" "

Hello world

" "" ) intro = doc.select_one("#intro") print(computed_style(intro)["color"], computed_style(intro)["font-weight"]) print(computed_style(doc.select_one("em"))["color"]) .. testoutput:: teal bold teal The value is the *computed* value, not the *used* value: turbohtml runs no layout, so lengths and percentages come back as written -- see :doc:`/explanation/cssom`. ********************* Transform with XSLT ********************* turbohtml can also reshape one document into another with an XSLT 1.0 stylesheet, the job lxml's ``etree.XSLT`` does. A stylesheet is XML, so parse it with :func:`turbohtml.parse_xml`, wrap it in :class:`turbohtml.transform.Transform`, and call it on a source document: .. testcode:: from turbohtml import parse_xml from turbohtml.transform import Transform style = parse_xml( '' '' '
    ' '
' '
  • ' "
    " ) print(Transform(style)(parse_xml("onetwo"))) .. testoutput:: The transform reuses the XPath engine for every match pattern and select expression; see :doc:`/how-to/xslt` for parameters and output methods, and :doc:`/explanation/xslt` for how it works. *************************** Validate against a schema *************************** When the input is XML with a contract, check it against an XSD or RELAX NG schema with :class:`turbohtml.validate.XMLSchema` or :class:`~turbohtml.validate.RelaxNG`. Compile the schema once, then validate a document parsed with :func:`turbohtml.parse_xml`; the result carries a ``valid`` flag and one :class:`~turbohtml.validate.ValidationError` per violation, each with the ``/root/child`` path that located it: .. testcode:: from turbohtml import parse_xml from turbohtml.validate import XMLSchema schema = XMLSchema( '' '' '' '' "" ) result = schema.validate(parse_xml("A-10")) print(result.valid) print(result.errors[0].path, result.errors[0].type) .. testoutput:: False /order/qty datatype With the string helpers in hand, continue to :doc:`tokenizing` to break whole documents into tokens.