"""Plex text fields as plain text. Plex hands a summary over as the agent wrote it, and some agents (a Hungarian TV listing, for one) write HTML: `

…

` paragraphs and `á`-style entities. The app renders summaries as text, so the tags showed literally on the info page and the hover card, and a card's 300-character cut could land inside one. Every summary and tagline is stored through `plain_text`, and the migration that cleaned the existing rows calls it too, so the two cannot disagree. """ import html import re _BREAK = re.compile(r"", re.IGNORECASE) # A paragraph or block boundary: a blank line between the two. Only a tag name ends the match, so a # bare "<" in prose ("a < b", "<3") is left alone. _BLOCK = re.compile(r"]*>", re.IGNORECASE) _TAG = re.compile(r"]*>") _BLANKS = re.compile(r"[ \t]*\n[ \t]*\n\s*") def plain_text(value: str | None) -> str | None: """`value` with its markup gone: line breaks kept as newlines, paragraphs as a blank line, every other tag dropped and entities decoded. None or an all-blank result stays None.""" if value is None: return None if "<" in value or "&" in value: value = _BREAK.sub("\n", value) value = _BLOCK.sub("\n\n", value) value = _TAG.sub("", value) value = html.unescape(value) value = _BLANKS.sub("\n\n", value) return value.strip() or None