{"record":{"id":"fcdae89cb87f6b5e","repo":"666ghj/MiroFish","slug":"generated-svg-is-not-valid-xml","errorCode":null,"errorMessage":"generated SVG is not valid XML","messagePattern":"generated SVG is not valid XML","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":1087,"sourceCode":"\ndef _validate_svg(payload: bytes) -> None:\n    try:\n        decoded_payload = payload.decode(\"utf-8\", errors=\"strict\")\n    except UnicodeDecodeError as exc:\n        raise StarHistoryError(\"generated SVG must be strict UTF-8\") from exc\n    if decoded_payload.startswith(\"\\ufeff\") or \"\\x00\" in decoded_payload:\n        raise StarHistoryError(\"generated SVG must be canonical UTF-8\")\n    upper_payload = decoded_payload.upper()\n    if (\n        \"<!DOCTYPE\" in upper_payload\n        or \"<!ENTITY\" in upper_payload\n        or \"<?\" in decoded_payload\n    ):\n        raise StarHistoryError(\"generated SVG contains forbidden XML directives\")\n    try:\n        root = ET.fromstring(decoded_payload)\n    except ET.ParseError as exc:\n        raise StarHistoryError(\"generated SVG is not valid XML\") from exc\n    svg_namespace = \"{http://www.w3.org/2000/svg}\"\n    if root.tag != f\"{svg_namespace}svg\":\n        raise StarHistoryError(\"generated SVG root is invalid\")\n    allowed_attributes: dict[str, set[str]] = {\n        \"svg\": {\n            \"viewBox\",\n            \"width\",\n            \"height\",\n            \"preserveAspectRatio\",\n            \"role\",\n            \"aria-labelledby\",\n        },\n        \"title\": {\"id\"},\n        \"desc\": {\"id\"},\n        \"rect\": {\n            \"x\",\n            \"y\",\n            \"width\",","sourceCodeStart":1069,"sourceCodeEnd":1105,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L1069-L1105","documentation":"Raised by `_validate_svg` (scripts/star_history.py:1087) when `xml.etree.ElementTree.fromstring` rejects the payload — the bytes decoded fine and contain no forbidden directives, but the document is not well-formed XML (unclosed tags, mismatched tags, stray `&`, malformed attributes).","triggerScenarios":"Any ET.ParseError while parsing the decoded SVG: unescaped `&` in text (e.g. 'AT&T'), truncated output from a bad write, hand-string-concatenated fragments, or fork code that builds tags via f-strings without escaping.","commonSituations":"Forks that interpolate repository names or usernames containing `<`, `>`, `&`, or quotes directly into SVG text; partial writes from crashed processes; templates split across concatenation points that lose a closing tag.","solutions":["If you forked the text generation, escape all interpolated values: `xml.sax.saxutils.escape(value)` and `quoteattr(value)` for attributes.","Test round-trip in CI: `render_svg(...)` then `ET.fromstring(payload)` immediately — catches escaping regressions at build time.","If validating an external file, re-run the renderer instead of repairing the broken file; canonical output always parses.","Locate the defect with `ET.fromstring` traceback line/column numbers, which point at the exact malformed construct."],"exampleFix":"# before: raw interpolation breaks XML on '&' or '<'\ntitle_text = f\"{owner}/{name} Star History\"\n\n# after: escape dynamic text\nfrom xml.sax.saxutils import escape\ntitle_text = escape(f\"{owner}/{name} Star History\")","handlingStrategy":"try-catch","validationCode":"import xml.etree.ElementTree as ET\n\ndef is_well_formed_xml(payload: bytes) -> bool:\n    try:\n        ET.fromstring(payload)\n        return True\n    except ET.ParseError:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    _validate_svg(payload)\nexcept StarHistoryError as exc:\n    if \"not valid XML\" in str(exc):\n        raise ValueError(f\"SVG is malformed: {exc.__cause__}\") from exc\n    raise","preventionTips":["Escape all dynamic text with xml.sax.saxutils.escape / quoteattr in forked templates.","Add a CI step that parses render_svg output immediately after generation.","Never assemble SVG by f-string concatenation of untrusted values."],"tags":["svg","xml","escaping","well-formedness","self-check"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}