Skip to content

Commit ed815d0

Browse files
Natwar589Anushtha-Rathore
authored andcommitted
fix: enhance variable placeholder regex to support nested dot-path notation (Walkover-Web-Solution#1311)
- Update _OBJECT_FULL_RE to support multi-level nested paths (e.g., footerButton.actionData.type) - Update _INLINE_RE to handle complex paths with both dot notation and array indexing - Simplify object path resolution by using _get_path_value for all nested paths - Refactor inline substitution fallback to use simple root key lookup instead of indexed resolver
1 parent 4d82dbb commit ed815d0

1 file changed

Lines changed: 12 additions & 16 deletions

File tree

src/utils/formatter.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ def apply_variables(card_json, variables):
7272

7373
_SIMPLE_FULL_RE = re.compile(r'^\s*\{\{([a-zA-Z_]\w*)\}\}\s*$')
7474
_INDEXED_FULL_RE = re.compile(r'^\s*\{\{([a-zA-Z_]\w*)\[(\d+)\]\.([a-zA-Z_]\w*)\}\}\s*$')
75-
_OBJECT_FULL_RE = re.compile(r'^\s*\{\{([a-zA-Z_]\w*)\.([a-zA-Z_]\w*)\}\}\s*$')
76-
_INLINE_RE = re.compile(r'\{\{([a-zA-Z_]\w*)(?:(?:\[(\d+)\])?\.([a-zA-Z_]\w*))?\}\}')
75+
_OBJECT_FULL_RE = re.compile(r'^\s*\{\{([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)+)\}\}\s*$')
76+
_INLINE_RE = re.compile(r'\{\{([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*(?:\[\d+\](?:\.[a-zA-Z_]\w*)*)?)\}\}')
7777

7878
# Matches a binding expressed as a single placeholder: "{{trips}}" or "{{row.buttons}}"
7979
_BINDING_PLACEHOLDER_RE = re.compile(r'^\s*\{\{([\w][\w.]*)\}\}\s*$')
@@ -150,34 +150,30 @@ def _replace_str(s, context):
150150
if m:
151151
return _resolve_placeholder(m.group(1), m.group(2), m.group(3), context)
152152

153-
# Full object/alias dot-path: "{{row.image}}" or "{{source.name}}"
153+
# Full object/alias dot-path: "{{row.image}}" or "{{footerButton.actionData.type}}"
154154
m = _OBJECT_FULL_RE.match(stripped)
155155
if m:
156-
root, field = m.group(1), m.group(2)
157-
obj = context.get(root)
158-
if isinstance(obj, dict):
159-
result = obj.get(field)
160-
if result is not None:
161-
return result
162-
# General path resolution covers nested paths
163-
resolved = _get_path_value(context, f"{root}.{field}")
156+
full_path = m.group(1)
157+
resolved = _get_path_value(context, full_path)
164158
return resolved if resolved is not None else s
165159

166160
# Full simple: "{{total}}" → single raw value
167161
m = _SIMPLE_FULL_RE.match(stripped)
168162
if m:
169163
return _resolve_placeholder(m.group(1), None, None, context)
170164

171-
# Inline mixed: "From: {{row.source}} — {{total}}"
165+
# Inline mixed: "From: {{row.source}} — {{footerButton.cancelActionData.data.id}}"
172166
def _inline_sub(match):
173167
full_path = match.group(0)[2:-2] # strip {{ and }}
174168
resolved = _get_path_value(context, full_path)
175169
if resolved is not None:
176170
return str(resolved)
177-
# Fallback to original indexed/object/scalar resolver
178-
return str(_resolve_placeholder(
179-
match.group(1), match.group(2), match.group(3), context
180-
))
171+
# Fallback: simple root key lookup
172+
root_key = full_path.split('.')[0].split('[')[0]
173+
v = context.get(root_key)
174+
if v is not None:
175+
return str(v)
176+
return match.group(0) # leave unreplaced
181177

182178
return _INLINE_RE.sub(_inline_sub, s)
183179

0 commit comments

Comments
 (0)