Title
Body text that is fairly long here.
################################################## Truncate HTML to a length, keeping tags balanced ################################################## Cutting an HTML string at a character count with a slice breaks it: you can land in the middle of a tag or leave an element open. Truncating the *tree* instead keeps the markup well-formed. Parse the HTML, walk it counting visible text, and drop everything past the budget; because you edit nodes rather than a string, every tag you keep stays balanced. The walk trims the text node that crosses the limit and removes whole subtrees after it with :meth:`~turbohtml.Node.decompose`: .. testcode:: import turbohtml from turbohtml import Element, Text def truncate(html, limit): body = turbohtml.parse(html).find("body") _trim(body, limit) return "".join(child.serialize() for child in body) def _trim(node, budget): for child in list(node): if budget <= 0: child.decompose() elif isinstance(child, Text): if len(child.data) > budget: child.data = child.data[:budget].rstrip() + "…" budget = 0 else: budget -= len(child.data) elif isinstance(child, Element): budget = _trim(child, budget) return budget print(truncate("
Hello world, this is long.
Second para.
Hello world…
Body text that is fairly long here.
Body…