Skip to content

Commit 32d7677

Browse files
committed
Add placeholder replacement functionality for interpolated attribute values
1 parent 3b30c07 commit 32d7677

File tree

2 files changed

+93
-7
lines changed

2 files changed

+93
-7
lines changed

tdom/processor.py

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import random
2+
import re
23
import string
34
import sys
45
import typing as t
@@ -53,6 +54,7 @@ def format_interpolation(interpolation: Interpolation) -> object:
5354

5455
_PLACEHOLDER_PREFIX = f"t🐍-{''.join(random.choices(string.ascii_lowercase, k=4))}-"
5556
_PP_LEN = len(_PLACEHOLDER_PREFIX)
57+
_PLACEHOLDER_PATTERN = re.compile(re.escape(_PLACEHOLDER_PREFIX) + r"(\d+)")
5658

5759

5860
def _placeholder(i: int) -> str:
@@ -65,6 +67,47 @@ def _placholder_index(s: str) -> int:
6567
return int(s[_PP_LEN:])
6668

6769

70+
def _replace_placeholders_in_string(
71+
value: str, interpolations: tuple[Interpolation, ...]
72+
) -> object:
73+
"""Replace any placeholders embedded within a string attribute value."""
74+
segments: list[tuple[str, object]] = []
75+
has_static_content = False
76+
last_index = 0
77+
78+
for match in _PLACEHOLDER_PATTERN.finditer(value):
79+
if match.start() > last_index:
80+
static_segment = value[last_index : match.start()]
81+
segments.append(("static", static_segment))
82+
if static_segment:
83+
has_static_content = True
84+
85+
index = int(match.group(1))
86+
interpolation = interpolations[index]
87+
formatted = format_interpolation(interpolation)
88+
segments.append(("dynamic", formatted))
89+
last_index = match.end()
90+
91+
if last_index < len(value):
92+
static_segment = value[last_index:]
93+
segments.append(("static", static_segment))
94+
if static_segment:
95+
has_static_content = True
96+
97+
if not segments:
98+
return value
99+
100+
dynamic_segments = [segment for segment in segments if segment[0] == "dynamic"]
101+
102+
if not has_static_content and len(dynamic_segments) == 1 and len(segments) == 1:
103+
return dynamic_segments[0][1]
104+
105+
return "".join(
106+
segment[1] if segment[0] == "static" else str(segment[1])
107+
for segment in segments
108+
)
109+
110+
68111
def _instrument(
69112
strings: tuple[str, ...], callable_infos: tuple[CallableInfo | None, ...]
70113
) -> t.Iterable[str]:
@@ -256,13 +299,22 @@ def _substitute_interpolated_attrs(
256299
"""
257300
new_attrs: dict[str, object | None] = {}
258301
for key, value in attrs.items():
259-
if value and value.startswith(_PLACEHOLDER_PREFIX):
260-
# Interpolated attribute value
261-
index = _placholder_index(value)
262-
interpolation = interpolations[index]
263-
interpolated_value = format_interpolation(interpolation)
264-
new_attrs[key] = interpolated_value
265-
elif key.startswith(_PLACEHOLDER_PREFIX):
302+
if isinstance(value, str):
303+
matches = tuple(_PLACEHOLDER_PATTERN.finditer(value))
304+
if matches:
305+
if len(matches) == 1:
306+
match = matches[0]
307+
if match.start() == 0 and match.end() == len(value):
308+
index = int(match.group(1))
309+
interpolation = interpolations[index]
310+
interpolated_value = format_interpolation(interpolation)
311+
new_attrs[key] = interpolated_value
312+
continue
313+
314+
new_attrs[key] = _replace_placeholders_in_string(value, interpolations)
315+
continue
316+
317+
if key.startswith(_PLACEHOLDER_PREFIX):
266318
# Spread attributes
267319
index = _placholder_index(key)
268320
interpolation = interpolations[index]

tdom/processor_test.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,40 @@ def test_interpolated_attribute_spread_with_class_attribute():
471471
assert str(node) == '<button id="button1" class="btn btn-primary">Click me</button>'
472472

473473

474+
def test_interpolated_attribute_value_embedded_placeholder():
475+
slug = "item42"
476+
node = html(t"<div data-id='prefix-{slug}'></div>")
477+
assert node == Element(
478+
"div",
479+
attrs={"data-id": "prefix-item42"},
480+
children=[],
481+
)
482+
assert str(node) == "<div data-id=\"prefix-item42\"></div>"
483+
484+
485+
def test_interpolated_attribute_value_with_static_prefix_and_suffix():
486+
counter = 3
487+
node = html(t"<div data-id=\"item-{counter}-suffix\"></div>")
488+
assert node == Element(
489+
"div",
490+
attrs={"data-id": "item-3-suffix"},
491+
children=[],
492+
)
493+
assert str(node) == '<div data-id="item-3-suffix"></div>'
494+
495+
496+
def test_interpolated_attribute_value_multiple_placeholders():
497+
start = 1
498+
end = 5
499+
node = html(t"<div data-range=\"{start}-{end}\"></div>")
500+
assert node == Element(
501+
"div",
502+
attrs={"data-range": "1-5"},
503+
children=[],
504+
)
505+
assert str(node) == '<div data-range="1-5"></div>'
506+
507+
474508
def test_interpolated_data_attributes():
475509
data = {"user-id": 123, "role": "admin", "wild": True}
476510
node = html(t"<div data={data}>User Info</div>")

0 commit comments

Comments
 (0)