Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 684308e

Browse files
committed
Add feature
1 parent fb204c1 commit 684308e

File tree

3 files changed

+40
-4
lines changed

3 files changed

+40
-4
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,14 @@ page = html(t"<div>My widget: {SafeWidget()}</div>")
228228
# <div>My widget: <button>Custom Widget</button></div>
229229
```
230230

231-
TODO: support explicitly marking content as `unsafe` with a format specifier, too.
231+
You can also explicitly mark a string as "unsafe" using the `:unsafe` format specifier. This forces the string to be escaped, even if it would normally be treated as safe:
232+
233+
```python
234+
from html_tstring import html, Markup
235+
trusted_html = Markup("<strong>This is safe HTML</strong>")
236+
page = html(t"<div>{trusted_html:unsafe}</div>")
237+
# <div>&lt;strong&gt;This is safe HTML&lt;/strong&gt;</div>
238+
```
232239

233240
### Template Composition
234241

html_tstring/processor.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
from .parser import parse_html
1313
from .utils import format_interpolation as base_format_interpolation
1414

15+
16+
@t.runtime_checkable
17+
class HasHTMLDunder(t.Protocol):
18+
def __html__(self) -> str: ...
19+
20+
1521
# --------------------------------------------------------------------------
1622
# Value formatting
1723
# --------------------------------------------------------------------------
@@ -292,6 +298,8 @@ def _node_from_value(value: object) -> Node:
292298
case Iterable():
293299
children = [_node_from_value(v) for v in value]
294300
return Fragment(children=children)
301+
case HasHTMLDunder():
302+
return Text(Markup(value.__html__()))
295303
case _:
296304
return Text(str(value))
297305

@@ -319,6 +327,8 @@ def _invoke_component(
319327
return html(result)
320328
case str():
321329
return Text(result)
330+
case HasHTMLDunder():
331+
return Text(Markup(result.__html__()))
322332
case _:
323333
raise TypeError(
324334
f"Component callable must return a Node, Template, or str; "

html_tstring/processor_test.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,15 @@ def test_conversions():
134134
# --------------------------------------------------------------------------
135135

136136

137-
def test_raw_html_injection_with_helper():
137+
def test_raw_html_injection_with_markupsafe():
138138
raw_content = Markup("<strong>I am bold</strong>")
139139
node = html(t"<div>{raw_content}</div>")
140140
assert node == Element("div", children=[Text(text=raw_content)])
141141
assert str(node) == "<div><strong>I am bold</strong></div>"
142142

143143

144144
def test_raw_html_injection_with_dunder_html_protocol():
145-
class SafeContent(str):
145+
class SafeContent:
146146
def __init__(self, text):
147147
self._text = text
148148

@@ -153,7 +153,12 @@ def __html__(self):
153153
content = SafeContent("emphasized")
154154
node = html(t"<p>Here is some {content}.</p>")
155155
assert node == Element(
156-
"p", children=[Text("Here is some "), Text(content), Text(".")]
156+
"p",
157+
children=[
158+
Text("Here is some "),
159+
Text(Markup("<em>emphasized</em>")),
160+
Text("."),
161+
],
157162
)
158163
assert str(node) == "<p>Here is some <em>emphasized</em>.</p>"
159164

@@ -172,6 +177,20 @@ def test_raw_html_injection_with_format_spec():
172177
assert str(node) == "<p>This is <u>underlined</u> text.</p>"
173178

174179

180+
def test_raw_html_injection_with_markupsafe_unsafe_format_spec():
181+
supposedly_safe = Markup("<i>italic</i>")
182+
node = html(t"<p>This is {supposedly_safe:unsafe} text.</p>")
183+
assert node == Element(
184+
"p",
185+
children=[
186+
Text("This is "),
187+
Text(supposedly_safe),
188+
Text(" text."),
189+
],
190+
)
191+
assert str(node) == "<p>This is &lt;i&gt;italic&lt;/i&gt; text.</p>"
192+
193+
175194
# --------------------------------------------------------------------------
176195
# Conditional rendering and control flow
177196
# --------------------------------------------------------------------------

0 commit comments

Comments
 (0)