using System.Text.Json; namespace Inspectron.Epson.Templates.Interpreter; public class DataContext { private readonly JsonElement _root; private readonly DataContext? _parent; private readonly Dictionary _localVariables = new(); private readonly Dictionary _loopMetadata = new(); public DataContext(JsonElement root) { _root = root; _parent = null; } private DataContext(JsonElement root, DataContext parent) { _root = root; _parent = parent; } public DataContext CreateChildContext(string variableName, JsonElement value, int index, int count) { var child = new DataContext(_root, this); child._localVariables[variableName] = value; child._loopMetadata["_index"] = index; child._loopMetadata["_number"] = index + 1; child._loopMetadata["_first"] = index == 0; child._loopMetadata["_last"] = index == count - 1; child._loopMetadata["_count"] = count; return child; } public JsonElement? Resolve(string path) { if (string.IsNullOrEmpty(path)) { return _root; } // Check loop metadata first if (_loopMetadata.TryGetValue(path, out var metadata)) { return JsonSerializer.SerializeToElement(metadata); } var segments = path.Split('.'); var firstSegment = segments[0]; // Check local variables (loop variables) if (_localVariables.TryGetValue(firstSegment, out var localValue)) { return NavigatePath(localValue, segments.Skip(1).ToArray()); } // Check parent context for local variables if (_parent != null) { // First check if parent has this as a local variable var parentResult = _parent.ResolveLocalOnly(firstSegment); if (parentResult.HasValue) { return NavigatePath(parentResult.Value, segments.Skip(1).ToArray()); } // Also check parent's loop metadata if (_parent._loopMetadata.TryGetValue(path, out var parentMetadata)) { return JsonSerializer.SerializeToElement(parentMetadata); } } // Navigate from root return NavigatePath(_root, segments); } private JsonElement? ResolveLocalOnly(string name) { if (_localVariables.TryGetValue(name, out var value)) { return value; } return _parent?.ResolveLocalOnly(name); } private JsonElement? NavigatePath(JsonElement element, string[] segments) { var current = element; foreach (var segment in segments) { if (current.ValueKind == JsonValueKind.Null || current.ValueKind == JsonValueKind.Undefined) { return null; } // Handle array access: collection.count or collection.length if (segment.Equals("count", StringComparison.OrdinalIgnoreCase) || segment.Equals("length", StringComparison.OrdinalIgnoreCase)) { if (current.ValueKind == JsonValueKind.Array) { return JsonSerializer.SerializeToElement(current.GetArrayLength()); } } // Handle array index: collection.0, collection.1 if (int.TryParse(segment, out var index)) { if (current.ValueKind == JsonValueKind.Array && index >= 0 && index < current.GetArrayLength()) { current = current[index]; continue; } return null; } // Handle object property if (current.ValueKind == JsonValueKind.Object) { if (current.TryGetProperty(segment, out var property)) { current = property; } else { // Try case-insensitive match var found = false; foreach (var prop in current.EnumerateObject()) { if (prop.Name.Equals(segment, StringComparison.OrdinalIgnoreCase)) { current = prop.Value; found = true; break; } } if (!found) return null; } } else { return null; } } return current; } public IEnumerable ResolveCollection(string path) { var element = Resolve(path); if (element.HasValue && element.Value.ValueKind == JsonValueKind.Array) { foreach (var item in element.Value.EnumerateArray()) { yield return item; } } } public string? GetString(string path) { var element = Resolve(path); if (!element.HasValue) return null; return element.Value.ValueKind switch { JsonValueKind.String => element.Value.GetString(), JsonValueKind.Number => element.Value.GetRawText(), JsonValueKind.True => "true", JsonValueKind.False => "false", JsonValueKind.Null => null, _ => element.Value.GetRawText() }; } public bool IsTruthy(string path) { var element = Resolve(path); if (!element.HasValue) return false; return element.Value.ValueKind switch { JsonValueKind.Null or JsonValueKind.Undefined => false, JsonValueKind.False => false, JsonValueKind.True => true, JsonValueKind.String => !string.IsNullOrEmpty(element.Value.GetString()), JsonValueKind.Number => element.Value.GetDouble() != 0, JsonValueKind.Array => element.Value.GetArrayLength() > 0, JsonValueKind.Object => true, _ => false }; } public object? GetValue(string path) { var element = Resolve(path); if (!element.HasValue) return null; return element.Value.ValueKind switch { JsonValueKind.String => element.Value.GetString(), JsonValueKind.Number => element.Value.TryGetInt64(out var l) ? l : element.Value.GetDouble(), JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.Null => null, _ => element.Value.GetRawText() }; } }