| using System; |
| using System.Collections.Generic; |
| using System.Globalization; |
| using System.Linq; |
| using System.Text; |
| using System.Text.RegularExpressions; |
|
|
| namespace UnityEngine.InputSystem.Utilities |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| internal struct JsonParser |
| { |
| public JsonParser(string json) |
| : this() |
| { |
| if (json == null) |
| throw new ArgumentNullException(nameof(json)); |
|
|
| m_Text = json; |
| m_Length = json.Length; |
| } |
|
|
| public void Reset() |
| { |
| m_Position = 0; |
| m_MatchAnyElementInArray = false; |
| m_DryRun = false; |
| } |
|
|
| public override string ToString() |
| { |
| if (m_Text != null) |
| return $"{m_Position}: {m_Text.Substring(m_Position)}"; |
| return base.ToString(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public bool NavigateToProperty(string path) |
| { |
| if (string.IsNullOrEmpty(path)) |
| throw new ArgumentNullException(nameof(path)); |
|
|
| var pathLength = path.Length; |
| var pathPosition = 0; |
|
|
| m_DryRun = true; |
| if (!ParseToken('{')) |
| return false; |
|
|
| while (m_Position < m_Length && pathPosition < pathLength) |
| { |
| |
| SkipWhitespace(); |
| if (m_Position == m_Length) |
| return false; |
| if (m_Text[m_Position] != '"') |
| return false; |
| ++m_Position; |
|
|
| |
| var pathStartPosition = pathPosition; |
| while (pathPosition < pathLength) |
| { |
| var ch = path[pathPosition]; |
| if (ch == '/' || ch == '[') |
| break; |
|
|
| if (m_Text[m_Position] != ch) |
| break; |
|
|
| ++m_Position; |
| ++pathPosition; |
| } |
|
|
| |
| if (m_Position < m_Length && m_Text[m_Position] == '"' && (pathPosition >= pathLength || path[pathPosition] == '/' || path[pathPosition] == '[')) |
| { |
| |
| ++m_Position; |
| if (!SkipToValue()) |
| return false; |
|
|
| |
| if (pathPosition >= pathLength) |
| return true; |
| if (path[pathPosition] == '/') |
| { |
| ++pathPosition; |
| if (!ParseToken('{')) |
| return false; |
| } |
| else if (path[pathPosition] == '[') |
| { |
| ++pathPosition; |
| if (pathPosition == pathLength) |
| throw new ArgumentException("Malformed JSON property path: " + path, nameof(path)); |
| if (path[pathPosition] == ']') |
| { |
| m_MatchAnyElementInArray = true; |
| ++pathPosition; |
| if (pathPosition == pathLength) |
| return true; |
| } |
| else |
| throw new NotImplementedException("Navigating to specific array element"); |
| } |
| } |
| else |
| { |
| |
| |
|
|
| pathPosition = pathStartPosition; |
| while (m_Position < m_Length && m_Text[m_Position] != '"') |
| ++m_Position; |
| if (m_Position == m_Length || m_Text[m_Position] != '"') |
| return false; |
| ++m_Position; |
| if (!SkipToValue() || !ParseValue()) |
| return false; |
| SkipWhitespace(); |
| if (m_Position == m_Length || m_Text[m_Position] == '}' || m_Text[m_Position] != ',') |
| return false; |
| ++m_Position; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| public bool CurrentPropertyHasValueEqualTo(JsonValue expectedValue) |
| { |
| |
| var savedPosition = m_Position; |
| m_DryRun = false; |
| if (!ParseValue(out var propertyValue)) |
| { |
| m_Position = savedPosition; |
| return false; |
| } |
| m_Position = savedPosition; |
|
|
| |
| var isMatch = false; |
| if (propertyValue.type == JsonValueType.Array && m_MatchAnyElementInArray) |
| { |
| var array = propertyValue.arrayValue; |
| for (var i = 0; !isMatch && i < array.Count; ++i) |
| isMatch = array[i] == expectedValue; |
| } |
| else |
| { |
| isMatch = propertyValue == expectedValue; |
| } |
|
|
| return isMatch; |
| } |
|
|
| public bool ParseToken(char token) |
| { |
| SkipWhitespace(); |
| if (m_Position == m_Length) |
| return false; |
|
|
| if (m_Text[m_Position] != token) |
| return false; |
|
|
| ++m_Position; |
| SkipWhitespace(); |
|
|
| return m_Position < m_Length; |
| } |
|
|
| public bool ParseValue() |
| { |
| return ParseValue(out var result); |
| } |
|
|
| public bool ParseValue(out JsonValue result) |
| { |
| result = default; |
|
|
| SkipWhitespace(); |
| if (m_Position == m_Length) |
| return false; |
|
|
| var ch = m_Text[m_Position]; |
| switch (ch) |
| { |
| case '"': |
| if (ParseStringValue(out result)) |
| return true; |
| break; |
| case '[': |
| if (ParseArrayValue(out result)) |
| return true; |
| break; |
| case '{': |
| if (ParseObjectValue(out result)) |
| return true; |
| break; |
| case 't': |
| case 'f': |
| if (ParseBooleanValue(out result)) |
| return true; |
| break; |
| case 'n': |
| if (ParseNullValue(out result)) |
| return true; |
| break; |
| default: |
| if (ParseNumber(out result)) |
| return true; |
| break; |
| } |
|
|
| return false; |
| } |
|
|
| public bool ParseStringValue(out JsonValue result) |
| { |
| result = default; |
|
|
| SkipWhitespace(); |
| if (m_Position == m_Length || m_Text[m_Position] != '"') |
| return false; |
| ++m_Position; |
|
|
| var startIndex = m_Position; |
| var hasEscapes = false; |
|
|
| while (m_Position < m_Length) |
| { |
| var ch = m_Text[m_Position]; |
| if (ch == '\\') |
| { |
| ++m_Position; |
| if (m_Position == m_Length) |
| break; |
| hasEscapes = true; |
| } |
| else if (ch == '"') |
| { |
| ++m_Position; |
| result = new JsonString |
| { |
| text = new Substring(m_Text, startIndex, m_Position - startIndex - 1), |
| hasEscapes = hasEscapes |
| }; |
| return true; |
| } |
| ++m_Position; |
| } |
|
|
| return false; |
| } |
|
|
| public bool ParseArrayValue(out JsonValue result) |
| { |
| result = default; |
|
|
| SkipWhitespace(); |
| if (m_Position == m_Length || m_Text[m_Position] != '[') |
| return false; |
| ++m_Position; |
|
|
| if (m_Position == m_Length) |
| return false; |
| if (m_Text[m_Position] == ']') |
| { |
| |
| result = new JsonValue { type = JsonValueType.Array }; |
| ++m_Position; |
| return true; |
| } |
|
|
| List<JsonValue> values = null; |
| if (!m_DryRun) |
| values = new List<JsonValue>(); |
|
|
| while (m_Position < m_Length) |
| { |
| if (!ParseValue(out var value)) |
| return false; |
| if (!m_DryRun) |
| values.Add(value); |
| SkipWhitespace(); |
| if (m_Position == m_Length) |
| return false; |
| var ch = m_Text[m_Position]; |
| if (ch == ']') |
| { |
| ++m_Position; |
| if (!m_DryRun) |
| result = values; |
| return true; |
| } |
| if (ch == ',') |
| ++m_Position; |
| } |
|
|
| return false; |
| } |
|
|
| public bool ParseObjectValue(out JsonValue result) |
| { |
| result = default; |
|
|
| if (!ParseToken('{')) |
| return false; |
| if (m_Position < m_Length && m_Text[m_Position] == '}') |
| { |
| result = new JsonValue { type = JsonValueType.Object }; |
| ++m_Position; |
| return true; |
| } |
|
|
| while (m_Position < m_Length) |
| { |
| if (!ParseStringValue(out var propertyName)) |
| return false; |
|
|
| if (!SkipToValue()) |
| return false; |
|
|
| if (!ParseValue(out var propertyValue)) |
| return false; |
|
|
| if (!m_DryRun) |
| throw new NotImplementedException(); |
|
|
| SkipWhitespace(); |
| if (m_Position < m_Length && m_Text[m_Position] == '}') |
| { |
| if (!m_DryRun) |
| throw new NotImplementedException(); |
| ++m_Position; |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| public bool ParseNumber(out JsonValue result) |
| { |
| result = default; |
|
|
| SkipWhitespace(); |
| if (m_Position == m_Length) |
| return false; |
|
|
| var negative = false; |
| var haveFractionalPart = false; |
| var integralPart = 0L; |
| var fractionalPart = 0.0; |
| var fractionalDivisor = 10.0; |
| var exponent = 0; |
|
|
| |
| if (m_Text[m_Position] == '-') |
| { |
| negative = true; |
| ++m_Position; |
| } |
|
|
| if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position])) |
| return false; |
|
|
| |
| while (m_Position < m_Length) |
| { |
| var ch = m_Text[m_Position]; |
| if (ch == '.') |
| break; |
| if (ch < '0' || ch > '9') |
| break; |
| integralPart = integralPart * 10 + ch - '0'; |
| ++m_Position; |
| } |
|
|
| |
| if (m_Position < m_Length && m_Text[m_Position] == '.') |
| { |
| haveFractionalPart = true; |
| ++m_Position; |
| if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position])) |
| return false; |
| while (m_Position < m_Length) |
| { |
| var ch = m_Text[m_Position]; |
| if (ch < '0' || ch > '9') |
| break; |
| fractionalPart = (ch - '0') / fractionalDivisor + fractionalPart; |
| fractionalDivisor *= 10; |
| ++m_Position; |
| } |
| } |
|
|
| if (m_Position < m_Length && (m_Text[m_Position] == 'e' || m_Text[m_Position] == 'E')) |
| { |
| ++m_Position; |
| var isNegative = false; |
| if (m_Position < m_Length && m_Text[m_Position] == '-') |
| { |
| isNegative = true; |
| ++m_Position; |
| } |
| else if (m_Position < m_Length && m_Text[m_Position] == '+') |
| { |
| ++m_Position; |
| } |
|
|
| var multiplier = 1; |
| while (m_Position < m_Length && char.IsDigit(m_Text[m_Position])) |
| { |
| var digit = m_Text[m_Position] - '0'; |
| exponent *= multiplier; |
| exponent += digit; |
| multiplier *= 10; |
| ++m_Position; |
| } |
|
|
| if (isNegative) |
| exponent *= -1; |
| } |
|
|
| if (!m_DryRun) |
| { |
| if (!haveFractionalPart && exponent == 0) |
| { |
| if (negative) |
| result = -integralPart; |
| else |
| result = integralPart; |
| } |
| else |
| { |
| float value; |
| if (negative) |
| value = (float)-(integralPart + fractionalPart); |
| else |
| value = (float)(integralPart + fractionalPart); |
| if (exponent != 0) |
| value *= Mathf.Pow(10, exponent); |
| result = value; |
| } |
| } |
|
|
| return true; |
| } |
|
|
| public bool ParseBooleanValue(out JsonValue result) |
| { |
| SkipWhitespace(); |
| if (SkipString("true")) |
| { |
| result = true; |
| return true; |
| } |
|
|
| if (SkipString("false")) |
| { |
| result = false; |
| return true; |
| } |
|
|
| result = default; |
| return false; |
| } |
|
|
| public bool ParseNullValue(out JsonValue result) |
| { |
| result = default; |
| return SkipString("null"); |
| } |
|
|
| public bool SkipToValue() |
| { |
| SkipWhitespace(); |
| if (m_Position == m_Length || m_Text[m_Position] != ':') |
| return false; |
| ++m_Position; |
| SkipWhitespace(); |
| return true; |
| } |
|
|
| private bool SkipString(string text) |
| { |
| SkipWhitespace(); |
| var length = text.Length; |
| if (m_Position + length >= m_Length) |
| return false; |
| for (var i = 0; i < length; ++i) |
| { |
| if (m_Text[m_Position + i] != text[i]) |
| return false; |
| } |
|
|
| m_Position += length; |
| return true; |
| } |
|
|
| private void SkipWhitespace() |
| { |
| while (m_Position < m_Length && char.IsWhiteSpace(m_Text[m_Position])) |
| ++m_Position; |
| } |
|
|
| public bool isAtEnd => m_Position >= m_Length; |
|
|
| private readonly string m_Text; |
| private readonly int m_Length; |
| private int m_Position; |
| private bool m_MatchAnyElementInArray; |
| private bool m_DryRun; |
|
|
| public enum JsonValueType |
| { |
| None, |
| Bool, |
| Real, |
| Integer, |
| String, |
| Array, |
| Object, |
| Any, |
| } |
|
|
| public struct JsonString : IEquatable<JsonString> |
| { |
| public Substring text; |
| public bool hasEscapes; |
|
|
| public override string ToString() |
| { |
| if (!hasEscapes) |
| return text.ToString(); |
|
|
| var builder = new StringBuilder(); |
| var length = text.length; |
| for (var i = 0; i < length; ++i) |
| { |
| var ch = text[i]; |
| if (ch == '\\') |
| { |
| ++i; |
| if (i == length) |
| break; |
| ch = text[i]; |
| } |
| builder.Append(ch); |
| } |
| return builder.ToString(); |
| } |
|
|
| public bool Equals(JsonString other) |
| { |
| if (hasEscapes == other.hasEscapes) |
| return Substring.Compare(text, other.text, StringComparison.InvariantCultureIgnoreCase) == 0; |
|
|
| var thisLength = text.length; |
| var otherLength = other.text.length; |
|
|
| int thisIndex = 0, otherIndex = 0; |
| for (; thisIndex < thisLength && otherIndex < otherLength; ++thisIndex, ++otherIndex) |
| { |
| var thisChar = text[thisIndex]; |
| var otherChar = other.text[otherIndex]; |
|
|
| if (thisChar == '\\') |
| { |
| ++thisIndex; |
| if (thisIndex == thisLength) |
| return false; |
| thisChar = text[thisIndex]; |
| } |
|
|
| if (otherChar == '\\') |
| { |
| ++otherIndex; |
| if (otherIndex == otherLength) |
| return false; |
| otherChar = other.text[otherIndex]; |
| } |
|
|
| if (char.ToUpperInvariant(thisChar) != char.ToUpperInvariant(otherChar)) |
| return false; |
| } |
|
|
| return thisIndex == thisLength && otherIndex == otherLength; |
| } |
|
|
| public override bool Equals(object obj) |
| { |
| return obj is JsonString other && Equals(other); |
| } |
|
|
| public override int GetHashCode() |
| { |
| unchecked |
| { |
| return (text.GetHashCode() * 397) ^ hasEscapes.GetHashCode(); |
| } |
| } |
|
|
| public static bool operator==(JsonString left, JsonString right) |
| { |
| return left.Equals(right); |
| } |
|
|
| public static bool operator!=(JsonString left, JsonString right) |
| { |
| return !left.Equals(right); |
| } |
|
|
| public static implicit operator JsonString(string str) |
| { |
| return new JsonString { text = str }; |
| } |
| } |
|
|
| public struct JsonValue : IEquatable<JsonValue> |
| { |
| public JsonValueType type; |
| public bool boolValue; |
| public double realValue; |
| public long integerValue; |
| public JsonString stringValue; |
| public List<JsonValue> arrayValue; |
| public Dictionary<string, JsonValue> objectValue; |
| public object anyValue; |
|
|
| public bool ToBoolean() |
| { |
| switch (type) |
| { |
| case JsonValueType.Bool: return boolValue; |
| case JsonValueType.Integer: return integerValue != 0; |
| case JsonValueType.Real: return NumberHelpers.Approximately(0, realValue); |
| case JsonValueType.String: return Convert.ToBoolean(ToString()); |
| } |
| return default; |
| } |
|
|
| public long ToInteger() |
| { |
| switch (type) |
| { |
| case JsonValueType.Bool: return boolValue ? 1 : 0; |
| case JsonValueType.Integer: return integerValue; |
| case JsonValueType.Real: return (long)realValue; |
| case JsonValueType.String: return Convert.ToInt64(ToString()); |
| } |
| return default; |
| } |
|
|
| public double ToDouble() |
| { |
| switch (type) |
| { |
| case JsonValueType.Bool: return boolValue ? 1 : 0; |
| case JsonValueType.Integer: return integerValue; |
| case JsonValueType.Real: return realValue; |
| case JsonValueType.String: return Convert.ToSingle(ToString()); |
| } |
| return default; |
| } |
|
|
| public override string ToString() |
| { |
| switch (type) |
| { |
| case JsonValueType.None: return "null"; |
| case JsonValueType.Bool: return boolValue.ToString(); |
| case JsonValueType.Integer: return integerValue.ToString(CultureInfo.InvariantCulture); |
| case JsonValueType.Real: return realValue.ToString(CultureInfo.InvariantCulture); |
| case JsonValueType.String: return stringValue.ToString(); |
| case JsonValueType.Array: |
| if (arrayValue == null) |
| return "[]"; |
| return $"[{string.Join(",", arrayValue.Select(x => x.ToString()))}]"; |
| case JsonValueType.Object: |
| if (objectValue == null) |
| return "{}"; |
| var elements = objectValue.Select(pair => $"\"{pair.Key}\" : \"{pair.Value}\""); |
| return $"{{{string.Join(",", elements)}}}"; |
| case JsonValueType.Any: return anyValue.ToString(); |
| } |
| return base.ToString(); |
| } |
|
|
| public static implicit operator JsonValue(bool val) |
| { |
| return new JsonValue |
| { |
| type = JsonValueType.Bool, |
| boolValue = val |
| }; |
| } |
|
|
| public static implicit operator JsonValue(long val) |
| { |
| return new JsonValue |
| { |
| type = JsonValueType.Integer, |
| integerValue = val |
| }; |
| } |
|
|
| public static implicit operator JsonValue(double val) |
| { |
| return new JsonValue |
| { |
| type = JsonValueType.Real, |
| realValue = val |
| }; |
| } |
|
|
| public static implicit operator JsonValue(string str) |
| { |
| return new JsonValue |
| { |
| type = JsonValueType.String, |
| stringValue = new JsonString { text = str } |
| }; |
| } |
|
|
| public static implicit operator JsonValue(JsonString str) |
| { |
| return new JsonValue |
| { |
| type = JsonValueType.String, |
| stringValue = str |
| }; |
| } |
|
|
| public static implicit operator JsonValue(List<JsonValue> array) |
| { |
| return new JsonValue |
| { |
| type = JsonValueType.Array, |
| arrayValue = array |
| }; |
| } |
|
|
| public static implicit operator JsonValue(Dictionary<string, JsonValue> obj) |
| { |
| return new JsonValue |
| { |
| type = JsonValueType.Object, |
| objectValue = obj |
| }; |
| } |
|
|
| public static implicit operator JsonValue(Enum val) |
| { |
| return new JsonValue |
| { |
| type = JsonValueType.Any, |
| anyValue = val |
| }; |
| } |
|
|
| public bool Equals(JsonValue other) |
| { |
| |
| if (type == other.type) |
| { |
| switch (type) |
| { |
| case JsonValueType.None: return true; |
| case JsonValueType.Bool: return boolValue == other.boolValue; |
| case JsonValueType.Integer: return integerValue == other.integerValue; |
| case JsonValueType.Real: return NumberHelpers.Approximately(realValue, other.realValue); |
| case JsonValueType.String: return stringValue == other.stringValue; |
| case JsonValueType.Object: throw new NotImplementedException(); |
| case JsonValueType.Array: throw new NotImplementedException(); |
| case JsonValueType.Any: return anyValue.Equals(other.anyValue); |
| } |
| return false; |
| } |
|
|
| |
| if (anyValue != null) |
| return Equals(anyValue, other); |
| if (other.anyValue != null) |
| return Equals(other.anyValue, this); |
|
|
| return false; |
| } |
|
|
| private static bool Equals(object obj, JsonValue value) |
| { |
| if (obj == null) |
| return false; |
|
|
| if (obj is Regex regex) |
| return regex.IsMatch(value.ToString()); |
| if (obj is string str) |
| { |
| switch (value.type) |
| { |
| case JsonValueType.String: return value.stringValue == str; |
| case JsonValueType.Integer: return long.TryParse(str, out var si) && si == value.integerValue; |
| case JsonValueType.Real: |
| return double.TryParse(str, out var sf) && NumberHelpers.Approximately(sf, value.realValue); |
| case JsonValueType.Bool: |
| if (value.boolValue) |
| return str == "True" || str == "true" || str == "1"; |
| return str == "False" || str == "false" || str == "0"; |
| } |
| } |
| if (obj is float f) |
| { |
| if (value.type == JsonValueType.Real) |
| return NumberHelpers.Approximately(f, value.realValue); |
| if (value.type == JsonValueType.String) |
| return float.TryParse(value.ToString(), out var otherF) && Mathf.Approximately(f, otherF); |
| } |
| if (obj is double d) |
| { |
| if (value.type == JsonValueType.Real) |
| return NumberHelpers.Approximately(d, value.realValue); |
| if (value.type == JsonValueType.String) |
| return double.TryParse(value.ToString(), out var otherD) && |
| NumberHelpers.Approximately(d, otherD); |
| } |
| if (obj is int i) |
| { |
| if (value.type == JsonValueType.Integer) |
| return i == value.integerValue; |
| if (value.type == JsonValueType.String) |
| return int.TryParse(value.ToString(), out var otherI) && i == otherI; |
| } |
| if (obj is long l) |
| { |
| if (value.type == JsonValueType.Integer) |
| return l == value.integerValue; |
| if (value.type == JsonValueType.String) |
| return long.TryParse(value.ToString(), out var otherL) && l == otherL; |
| } |
| if (obj is bool b) |
| { |
| if (value.type == JsonValueType.Bool) |
| return b == value.boolValue; |
| if (value.type == JsonValueType.String) |
| { |
| if (b) |
| return value.stringValue == "true" || value.stringValue == "True" || |
| value.stringValue == "1"; |
| return value.stringValue == "false" || value.stringValue == "False" || |
| value.stringValue == "0"; |
| } |
| } |
| |
| |
| if (obj is Enum) |
| { |
| if (value.type == JsonValueType.Integer) |
| return Convert.ToInt64(obj) == value.integerValue; |
| if (value.type == JsonValueType.String) |
| return value.stringValue == Enum.GetName(obj.GetType(), obj); |
| } |
|
|
| return false; |
| } |
|
|
| public override bool Equals(object obj) |
| { |
| return obj is JsonValue other && Equals(other); |
| } |
|
|
| public override int GetHashCode() |
| { |
| unchecked |
| { |
| var hashCode = (int)type; |
| hashCode = (hashCode * 397) ^ boolValue.GetHashCode(); |
| hashCode = (hashCode * 397) ^ realValue.GetHashCode(); |
| hashCode = (hashCode * 397) ^ integerValue.GetHashCode(); |
| hashCode = (hashCode * 397) ^ stringValue.GetHashCode(); |
| hashCode = (hashCode * 397) ^ (arrayValue != null ? arrayValue.GetHashCode() : 0); |
| hashCode = (hashCode * 397) ^ (objectValue != null ? objectValue.GetHashCode() : 0); |
| hashCode = (hashCode * 397) ^ (anyValue != null ? anyValue.GetHashCode() : 0); |
| return hashCode; |
| } |
| } |
|
|
| public static bool operator==(JsonValue left, JsonValue right) |
| { |
| return left.Equals(right); |
| } |
|
|
| public static bool operator!=(JsonValue left, JsonValue right) |
| { |
| return !left.Equals(right); |
| } |
| } |
| } |
| } |
|
|