handle attributes containing quoted spaces

This commit is contained in:
Will Abbott 2025-01-10 13:13:28 +00:00
parent 072342fae0
commit 12d64ede25
4 changed files with 90 additions and 5 deletions

View file

@ -14,6 +14,4 @@ def index_view(request):
)
urlpatterns = [
path("", index_view),
]
urlpatterns = [path("", index_view)]

View file

@ -7,7 +7,22 @@ class Tag:
r"<(/?)c-([^\s/>]+)((?:\s+[^\s/>\"'=<>`]+(?:\s*=\s*(?:\"[^\"]*\"|'[^']*'|\S+))?)*)\s*(/?)\s*>",
re.DOTALL,
)
attr_pattern = re.compile(r'([^\s/>\"\'=<>`]+)(?:\s*=\s*(?:(["\'])(.*?)\2|(\S+)))?', re.DOTALL)
# attr_pattern = re.compile(r'([^\s/>\"\'=<>`]+)(?:\s*=\s*(?:(["\'])(.*?)\2|(\S+)))?', re.DOTALL)
attr_pattern = re.compile(
r"""([^\s/>\"\'=<>`]+) # Attribute name
(?: # Optional group for value
\s*=\s* # Equals with optional whitespace
(?:
(["\']) # Quote character
((?:(?!\2)|.)*?) # Any character that's not the quote character
\2 # Matching quote
| # OR
(\S+) # Non-quoted value without spaces
)
)?""",
re.VERBOSE | re.DOTALL,
)
def __init__(self, match: re.Match):
self.html = match.group(0)
@ -130,7 +145,7 @@ class CottonCompiler:
if len(matches) > 1:
raise ValueError(
f"Multiple c-vars tags found in component template. Only one c-vars tag is allowed per template."
"Multiple c-vars tags found in component template. Only one c-vars tag is allowed per template."
)
# Process single c-vars tag if present

View file

@ -127,6 +127,57 @@ def cotton_component(parser, token):
attrs = {}
only = False
current_key = None
current_value = []
for bit in bits[1:]:
if bit == "only":
only = True
continue
if "=" in bit:
# If we were building a previous value, store it
if current_key:
attrs[current_key] = " ".join(current_value)
current_value = []
# Start new key-value pair
key, value = bit.split("=", 1)
if value.startswith(("'", '"')):
if value.endswith(("'", '"')) and value[0] == value[-1]:
# Complete quoted value
attrs[key] = value
else:
# Start of quoted value
current_key = key
current_value = [value]
else:
# Simple unquoted value
attrs[key] = value
else:
if current_key:
# Continue building quoted value
current_value.append(bit)
else:
# Boolean attribute
attrs[bit] = True
# Store any final value being built
if current_key:
attrs[current_key] = " ".join(current_value)
nodelist = parser.parse(("endc",))
parser.delete_first_token()
return CottonComponentNode(component_name, nodelist, attrs, only)
def cotton_component_legacy(parser, token):
bits = token.split_contents()[1:]
component_name = bits[0]
attrs = {}
only = False
node_class = CottonComponentNode
for bit in bits[1:]:

View file

@ -511,3 +511,24 @@ class AttributeHandlingTests(CottonTestCase):
response,
"""attrs: ':string="variable" dynamic="Ive been resolved!" :complex-string="{'something': 1}" complex-dynamic="{'something': 1}"'""",
)
def test_attributes_can_contain_valid_json(self):
self.create_template(
"cotton/json_attrs.html",
"""
<button {{ attrs }}>{{ slot }}</button>
""",
)
self.create_template(
"json_attrs_view.html",
"""
<c-json-attrs data-something='{"key=dd": "the value with= spaces"}' />
""",
"view/",
)
with self.settings(ROOT_URLCONF=self.url_conf()):
response = self.client.get("/view/")
self.assertContains(response, """{"key=dd": "the value with= spaces"}""")