Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/formatted_text/html.py: 22%
74 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1from __future__ import annotations
3import xml.dom.minidom as minidom
4from string import Formatter
5from typing import Any
7from .base import FormattedText, StyleAndTextTuples
9__all__ = ["HTML"]
12class HTML:
13 """
14 HTML formatted text.
15 Take something HTML-like, for use as a formatted string.
17 ::
19 # Turn something into red.
20 HTML('<style fg="ansired" bg="#00ff44">...</style>')
22 # Italic, bold, underline and strike.
23 HTML('<i>...</i>')
24 HTML('<b>...</b>')
25 HTML('<u>...</u>')
26 HTML('<s>...</s>')
28 All HTML elements become available as a "class" in the style sheet.
29 E.g. ``<username>...</username>`` can be styled, by setting a style for
30 ``username``.
31 """
33 def __init__(self, value: str) -> None:
34 self.value = value
35 document = minidom.parseString(f"<html-root>{value}</html-root>")
37 result: StyleAndTextTuples = []
38 name_stack: list[str] = []
39 fg_stack: list[str] = []
40 bg_stack: list[str] = []
42 def get_current_style() -> str:
43 "Build style string for current node."
44 parts = []
45 if name_stack:
46 parts.append("class:" + ",".join(name_stack))
48 if fg_stack:
49 parts.append("fg:" + fg_stack[-1])
50 if bg_stack:
51 parts.append("bg:" + bg_stack[-1])
52 return " ".join(parts)
54 def process_node(node: Any) -> None:
55 "Process node recursively."
56 for child in node.childNodes:
57 if child.nodeType == child.TEXT_NODE:
58 result.append((get_current_style(), child.data))
59 else:
60 add_to_name_stack = child.nodeName not in (
61 "#document",
62 "html-root",
63 "style",
64 )
65 fg = bg = ""
67 for k, v in child.attributes.items():
68 if k == "fg":
69 fg = v
70 if k == "bg":
71 bg = v
72 if k == "color":
73 fg = v # Alias for 'fg'.
75 # Check for spaces in attributes. This would result in
76 # invalid style strings otherwise.
77 if " " in fg:
78 raise ValueError('"fg" attribute contains a space.')
79 if " " in bg:
80 raise ValueError('"bg" attribute contains a space.')
82 if add_to_name_stack:
83 name_stack.append(child.nodeName)
84 if fg:
85 fg_stack.append(fg)
86 if bg:
87 bg_stack.append(bg)
89 process_node(child)
91 if add_to_name_stack:
92 name_stack.pop()
93 if fg:
94 fg_stack.pop()
95 if bg:
96 bg_stack.pop()
98 process_node(document)
100 self.formatted_text = FormattedText(result)
102 def __repr__(self) -> str:
103 return f"HTML({self.value!r})"
105 def __pt_formatted_text__(self) -> StyleAndTextTuples:
106 return self.formatted_text
108 def format(self, *args: object, **kwargs: object) -> HTML:
109 """
110 Like `str.format`, but make sure that the arguments are properly
111 escaped.
112 """
113 return HTML(FORMATTER.vformat(self.value, args, kwargs))
115 def __mod__(self, value: object) -> HTML:
116 """
117 HTML('<b>%s</b>') % value
118 """
119 if not isinstance(value, tuple):
120 value = (value,)
122 value = tuple(html_escape(i) for i in value)
123 return HTML(self.value % value)
126class HTMLFormatter(Formatter):
127 def format_field(self, value: object, format_spec: str) -> str:
128 return html_escape(format(value, format_spec))
131def html_escape(text: object) -> str:
132 # The string interpolation functions also take integers and other types.
133 # Convert to string first.
134 if not isinstance(text, str):
135 text = f"{text}"
137 return (
138 text.replace("&", "&")
139 .replace("<", "<")
140 .replace(">", ">")
141 .replace('"', """)
142 )
145FORMATTER = HTMLFormatter()