################# Troubleshooting ################# The things that trip up a first turbohtml session, each as a symptom and a fix. turbohtml is not a drop-in for BeautifulSoup or lxml, so a habit from one of those is the usual cause. ***************************** A node indexes its children ***************************** turbohtml models text as real child nodes, the WHATWG DOM shape, where lxml uses ``text``/``tail`` and BeautifulSoup uses ``.string``. So ``node[i]`` indexes the children, and a text run is one of them. For the text, call :attr:`~turbohtml.Node.text`: .. testcode:: import turbohtml para = turbohtml.parse("
Hello world
").find("p") print(len(para), type(para[0]).__name__) print(para.text) .. testoutput:: 2 Text Hello world ************************** Attributes live in attrs ************************** An element is not a mapping of its attributes. Reach an attribute through :attr:`~turbohtml.Element.attrs` (or :meth:`~turbohtml.Element.attr` for one with a default), never ``node["href"]``: .. testcode:: link = turbohtml.parse('y').find("a") print(link.attrs["href"]) print(link.attr("title")) .. testoutput:: /x None ********************** find can return None ********************** :meth:`~turbohtml.Node.find` and :meth:`~turbohtml.Node.select_one` return ``None`` when nothing matches, so reaching an attribute straight off the result raises ``AttributeError`` on a page that happens to lack the element. Guard it: .. testcode:: doc = turbohtml.parse("x
") heading = doc.select_one("h1") print(heading.text if heading is not None else "no heading") .. testoutput:: no heading **************************** An invalid selector raises **************************** A malformed CSS selector raises :class:`~turbohtml.SelectorSyntaxError`, and a malformed XPath raises :class:`ValueError`. Catch the specific type when the selector comes from user input: .. testcode:: from turbohtml import SelectorSyntaxError try: doc.select("p!!!") except SelectorSyntaxError: print("bad selector") .. testoutput:: bad selector ****************************** Encoding detection is opt-in ****************************** :func:`turbohtml.parse` treats a ``str`` as already decoded and never sniffs it. When the bytes came off a socket or a file with an unknown encoding, pass the ``bytes`` so the parser runs the WHATWG sniff, or call :func:`turbohtml.detect.detect` to learn the encoding first. Decoding a stream yourself with the wrong codec before handing turbohtml a ``str`` is the usual cause of mojibake. See :doc:`encoding`. ************************************* Streaming rewrite cannot look ahead ************************************* :func:`turbohtml.rewrite.rewrite` runs in one forward pass and never buffers the document, so a selector that needs content it has not reached -- a sibling combinator, ``:has()``, ``:nth-child()`` -- raises :class:`~turbohtml.SelectorSyntaxError` rather than silently matching nothing. When you need one of those, parse a tree with :func:`turbohtml.parse` and edit it instead. See :doc:`/explanation/streaming`. ***************************************** parse, parse_fragment, parse_xml differ ***************************************** The three entry points build different trees. :func:`turbohtml.parse` builds a full document, inserting the implied ````, ````, and ````. :func:`turbohtml.parse_fragment` parses in a fragment context, so no wrapper elements appear. :func:`turbohtml.parse_xml` applies XML rules, where tag case is significant and every element must close. Reaching for ``find("body")`` on a fragment, or feeding XML to the HTML parser, gives a tree you did not expect. ************************* Sanitize needs a policy ************************* :func:`turbohtml.clean.sanitize` keeps only what its :class:`~turbohtml.clean.Policy` allows on top of a non-overridable safety baseline. An empty or too-narrow policy strips more than you meant; ``Policy.relaxed()`` is the starting point for user content. ``transform_tags`` renames a tag rather than dropping it. See :doc:`sanitizing`. ********************************** Foreign content changes matching ********************************** Inside SVG and MathML, tag and attribute names are case-sensitive and namespaced (``