{"record":{"id":"6844c4c80183997f","repo":"3b1b/manim","slug":"invalid-markup-string-markup-str-n-validate","errorCode":null,"errorMessage":"Invalid markup string \\\"{markup_str}\\\"\\n{validate_error}","messagePattern":"Invalid markup string \\\\\"(.+?)\\\\\"\\\\n(.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"manimlib/mobject/svg/text_mobject.py","lineNumber":64,"sourceCode":"        \"RIGHT\": 2\n    }\n\n    def __init__(self, s: str):\n        self.value = _Alignment.VAL_DICT[s.upper()]\n\n\n@lru_cache(maxsize=128)\n@cache_on_disk\ndef markup_to_svg(\n    markup_str: str,\n    justify: bool = False,\n    indent: float = 0,\n    alignment: str = \"CENTER\",\n    line_width: float | None = None,\n) -> str:\n    validate_error = manimpango.MarkupUtils.validate(markup_str)\n    if validate_error:\n        raise ValueError(\n            f\"Invalid markup string \\\"{markup_str}\\\"\\n\" + \\\n            f\"{validate_error}\"\n        )\n\n    # `manimpango` is under construction,\n    # so the following code is intended to suit its interface\n    alignment = _Alignment(alignment)\n    if line_width is None:\n        pango_width = -1\n    else:\n        pango_width = line_width / FRAME_WIDTH * DEFAULT_PIXEL_WIDTH\n\n    # Write the result to a temporary svg file, and return it's contents.\n    temp_file = Path(tempfile.gettempdir(), hash_string(markup_str)).with_suffix(\".svg\")\n    manimpango.MarkupUtils.text2svg(\n        text=markup_str,\n        font=\"\",                     # Already handled\n        slant=\"NORMAL\",              # Already handled","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/3b1b/manim/blob/dee01804d47b9f94402d71472674710dcac125b8/manimlib/mobject/svg/text_mobject.py#L46-L82","documentation":"Raised by markup_to_svg (text_mobject.py:64) when manimpango's MarkupUtils.validate reports the string is not valid Pango markup. Text() uses Pango markup, so raw '&', '<', '>' and unknown/mismatched tags (<tts>, <b> without </b>) all fail validation before rendering.","triggerScenarios":"Text('A & B'); Text('<b>bold'); Text('<tts>hi</tts>') with an unsupported tag; markup built by string concatenation that leaves a dangling '<'. Markup like '<b>bold</b>' and '<i>i</i>' is valid.","commonSituations":"Rendering user-supplied or dynamically composed strings containing XML special characters; switching from Tex/PlainText assumptions to Text() which parses markup; version changes in manimpango that alter the accepted tag set.","solutions":["Escape special characters with html.escape()/xml.sax.saxutils.escape() before passing user data to Text()","Fix the markup tags: every opened tag must be closed and must be a Pango-supported tag (b, i, u, s, sub, sup, span, ...)","For literal angle brackets/ampersands use &lt; &gt; &amp; entities"],"exampleFix":"# before\nText('Tom & Jerry <3')  # invalid markup -> raises\n\n# after\nimport html\nText(html.escape('Tom & Jerry <3'))","handlingStrategy":"validation","validationCode":"from xml.sax.saxutils import escape\nimport re\n\ndef safe_markup(s: str) -> str:\n    # escape bare & < > unless the string already looks like deliberate markup\n    if re.search(r'<(b|i|u|s|sub|sup|span)\\b', s):\n        return s  # author-supplied markup; trust it\n    return escape(s)\n\nText(safe_markup(user_string))","typeGuard":null,"tryCatchPattern":"import manimpango\ntry:\n    Text(markup_str)\nexcept ValueError as e:\n    if 'Invalid markup string' in str(e):\n        from xml.sax.saxutils import escape\n        Text(escape(markup_str))","preventionTips":["html.escape() any user- or data-supplied text before Text()","Close every Pango tag you open; use only supported tags","Use &amp; &lt; &gt; entities for literal special characters"],"tags":["manim","text","pango-markup","escaping","validation"],"backgroundTag":null,"analyzedSha":"dee01804d47b9f94402d71472674710dcac125b8","analyzedAt":"2026-08-14T19:53:44.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}