From f69d233b7d8cf0d1aa252ef561b093999850be63 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Tue, 13 Sep 2016 10:17:20 +1200 Subject: [PATCH 01/18] Removed existing path parsing logic. Added new logic for start, object, and search operators. Added unit tests to support path parsing. Removed reference to Manatee.StateMachine! Related issues: #10, #35 --- Manatee.Json.Tests/Manatee.Json.Tests.csproj | 6 +- Manatee.Json.Tests/Path/ParsingTest.cs | 152 +++++++ Manatee.Json.Tests/packages.config | 4 - Manatee.Json.sln.DotSettings | 3 +- Manatee.Json/Manatee.Json.csproj | 7 +- Manatee.Json/Path/Expressions/Expression.cs | 442 +-------------------- Manatee.Json/Path/JsonPath.cs | 411 +------------------ Manatee.Json/Path/JsonPathSyntaxException.cs | 2 +- Manatee.Json/Path/Operators/NameOperator.cs | 4 +- Manatee.Json/Path/Parsing/IJsonPathParser.cs | 9 + Manatee.Json/Path/Parsing/JsonPathParser.cs | 48 +++ Manatee.Json/Path/Parsing/ObjectParser.cs | 34 ++ Manatee.Json/Path/Parsing/PathParsingExtensions.cs | 103 +++++ Manatee.Json/Path/Parsing/SearchParser.cs | 34 ++ Manatee.Json/Path/Parsing/StartParser.cs | 22 + Manatee.Json/packages.config | 4 - 16 files changed, 418 insertions(+), 867 deletions(-) create mode 100644 Manatee.Json.Tests/Path/ParsingTest.cs delete mode 100644 Manatee.Json.Tests/packages.config create mode 100644 Manatee.Json/Path/Parsing/IJsonPathParser.cs create mode 100644 Manatee.Json/Path/Parsing/JsonPathParser.cs create mode 100644 Manatee.Json/Path/Parsing/ObjectParser.cs create mode 100644 Manatee.Json/Path/Parsing/PathParsingExtensions.cs create mode 100644 Manatee.Json/Path/Parsing/SearchParser.cs create mode 100644 Manatee.Json/Path/Parsing/StartParser.cs delete mode 100644 Manatee.Json/packages.config diff --git a/Manatee.Json.Tests/Manatee.Json.Tests.csproj b/Manatee.Json.Tests/Manatee.Json.Tests.csproj index e19db5f..faab337 100644 --- a/Manatee.Json.Tests/Manatee.Json.Tests.csproj +++ b/Manatee.Json.Tests/Manatee.Json.Tests.csproj @@ -139,10 +139,6 @@ Manatee.Json.snk - - ..\packages\Manatee.StateMachine.1.1.2\lib\net40\Manatee.StateMachine.dll - True - False @@ -174,6 +170,7 @@ + @@ -223,7 +220,6 @@ - Always diff --git a/Manatee.Json.Tests/Path/ParsingTest.cs b/Manatee.Json.Tests/Path/ParsingTest.cs new file mode 100644 index 0000000..65ebf05 --- /dev/null +++ b/Manatee.Json.Tests/Path/ParsingTest.cs @@ -0,0 +1,152 @@ +using Manatee.Json.Path; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Manatee.Json.Tests.Path +{ + [TestClass] + public class ParsingTest + { + [TestMethod] + public void SingleNamedObject() + { + var text = "$.name"; + var expected = JsonPathWith.Name("name"); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SingleQuotedNamedObject() + { + var text = "$.'quoted name'"; + var expected = JsonPathWith.Name("quoted name"); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void DoubleQuotedNamedObject() + { + var text = "$.\"quoted name\""; + var expected = JsonPathWith.Name("quoted name"); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SingleWildcardObject() + { + var text = "$.*"; + var expected = JsonPathWith.Wildcard(); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void NamedObjectWithWildcardObject() + { + var text = "$.name.*"; + var expected = JsonPathWith.Name("name").Wildcard(); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void WildcardObjectWithNamedObject() + { + var text = "$.*.name"; + var expected = JsonPathWith.Wildcard().Name("name"); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + [TestMethod] + public void SingleNamedSearch() + { + var text = "$..name"; + var expected = JsonPathWith.Search("name"); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SingleQuotedNamedSearch() + { + var text = "$..'quoted name'"; + var expected = JsonPathWith.Search("quoted name"); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void DoubleQuotedNamedSearch() + { + var text = "$..\"quoted name\""; + var expected = JsonPathWith.Search("quoted name"); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SingleWildcardSearch() + { + var text = "$..*"; + var expected = JsonPathWith.Search(); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void NamedObjectWithWildcardSearch() + { + var text = "$.name..*"; + var expected = JsonPathWith.Name("name").Search(); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void WildcardObjectWithNamedSearch() + { + var text = "$.*..name"; + var expected = JsonPathWith.Wildcard().Search("name"); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + } +} diff --git a/Manatee.Json.Tests/packages.config b/Manatee.Json.Tests/packages.config deleted file mode 100644 index 5916821..0000000 --- a/Manatee.Json.Tests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Manatee.Json.sln.DotSettings b/Manatee.Json.sln.DotSettings index 627c71a..f683f41 100644 --- a/Manatee.Json.sln.DotSettings +++ b/Manatee.Json.sln.DotSettings @@ -17,7 +17,8 @@ TOGETHER_SAME_LINE NEXT_LINE_SHIFTED_2 200 - <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Private methods, properties, and events"><ElementKinds><Kind Name="METHOD" /><Kind Name="EVENT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index 3992d46..d4546fd 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -311,6 +311,12 @@ + + + + + + @@ -535,7 +541,6 @@ - diff --git a/Manatee.Json/Path/Expressions/Expression.cs b/Manatee.Json/Path/Expressions/Expression.cs index 2c3d0ff..ac225af 100644 --- a/Manatee.Json/Path/Expressions/Expression.cs +++ b/Manatee.Json/Path/Expressions/Expression.cs @@ -22,84 +22,14 @@ ***************************************************************************************/ using System; using System.Collections.Generic; -using System.Linq; -using Manatee.Json.Internal; -using Manatee.Json.Path.Operators; -using Manatee.StateMachine; -using Manatee.StateMachine.Exceptions; namespace Manatee.Json.Path.Expressions { internal class Expression { - private enum State - { - Start, - Value, - NumberOrPath, - Operator, - Comparison, - ValueOrOperator, - BooleanOrComparison, - End, - } - - private static readonly StateMachine _stateMachine = new StateMachine(); - private List> _nodeList; - private readonly InputStream _stream = new InputStream(); - private bool _done; - private string _source; - private int _index; - private JsonPathExpressionInput? _previousInput; - private int _startIndex; - internal ExpressionTreeNode Root { get; set; } - static Expression() - { - _stateMachine[State.Start, JsonPathExpressionInput.OpenParenth] = GotStart; - _stateMachine[State.Value, JsonPathExpressionInput.OpenParenth] = GotGroup; - _stateMachine[State.Value, JsonPathExpressionInput.Number] = GotNumber; - _stateMachine[State.Value, JsonPathExpressionInput.Minus] = GotNumber; - _stateMachine[State.Value, JsonPathExpressionInput.Root] = GotRoot; - _stateMachine[State.Value, JsonPathExpressionInput.Current] = GotCurrent; - _stateMachine[State.Value, JsonPathExpressionInput.Bang] = GotNot; - _stateMachine[State.Value, JsonPathExpressionInput.Quote] = GotString; - _stateMachine[State.Value, JsonPathExpressionInput.Letter] = GotNamedConstant; - _stateMachine[State.NumberOrPath, JsonPathExpressionInput.OpenParenth] = GotGroup; - _stateMachine[State.NumberOrPath, JsonPathExpressionInput.Number] = GotNumber; - _stateMachine[State.NumberOrPath, JsonPathExpressionInput.Root] = GotRoot; - _stateMachine[State.NumberOrPath, JsonPathExpressionInput.Current] = GotCurrent; - _stateMachine[State.Operator, JsonPathExpressionInput.Plus] = GotPlus; - _stateMachine[State.Operator, JsonPathExpressionInput.Minus] = GotMinus; - _stateMachine[State.Operator, JsonPathExpressionInput.Star] = GotMultiply; - _stateMachine[State.Operator, JsonPathExpressionInput.Slash] = GotDivide; - _stateMachine[State.Operator, JsonPathExpressionInput.Caret] = GotExponent; - _stateMachine[State.Operator, JsonPathExpressionInput.LessThan] = GotLessThan; - _stateMachine[State.Operator, JsonPathExpressionInput.Equal] = GotEqual; - _stateMachine[State.Operator, JsonPathExpressionInput.And] = GotAnd; - _stateMachine[State.Operator, JsonPathExpressionInput.Or] = GotOr; - _stateMachine[State.Operator, JsonPathExpressionInput.GreaterThan] = GotGreaterThan; - _stateMachine[State.Operator, JsonPathExpressionInput.Bang] = GotNot; - _stateMachine[State.Operator, JsonPathExpressionInput.CloseParenth] = CompleteExpression; - _stateMachine[State.Comparison, JsonPathExpressionInput.Equal] = GotEqual; - _stateMachine[State.Comparison, JsonPathExpressionInput.And] = GotAnd; - _stateMachine[State.Comparison, JsonPathExpressionInput.Or] = GotOr; - _stateMachine[State.ValueOrOperator, JsonPathExpressionInput.OpenParenth] = GotGroup; - _stateMachine[State.ValueOrOperator, JsonPathExpressionInput.Number] = GotNumber; - _stateMachine[State.ValueOrOperator, JsonPathExpressionInput.Root] = GotRoot; - _stateMachine[State.ValueOrOperator, JsonPathExpressionInput.Current] = GotCurrent; - _stateMachine[State.ValueOrOperator, JsonPathExpressionInput.Equal] = FinalizeComparison; - _stateMachine[State.ValueOrOperator, JsonPathExpressionInput.Letter] = GotNamedConstant; - _stateMachine[State.BooleanOrComparison, JsonPathExpressionInput.OpenParenth] = GotGroup; - _stateMachine[State.BooleanOrComparison, JsonPathExpressionInput.Root] = GotRoot; - _stateMachine[State.BooleanOrComparison, JsonPathExpressionInput.Current] = GotCurrent; - _stateMachine[State.BooleanOrComparison, JsonPathExpressionInput.Equal] = GotEqual; - _stateMachine[State.BooleanOrComparison, JsonPathExpressionInput.Letter] = GotNamedConstant; - _stateMachine.UpdateFunction = GetNextInput; - } - public T Evaluate(TIn json, JsonValue root) { var result = Root.Evaluate(json, root); @@ -131,377 +61,7 @@ public override string ToString() internal int Parse(string source, int i) { - _source = source; - Parse(i); - return _index; - } - - private void Parse(int i) - { - _stream.Clear(); - _startIndex = i + 1; - _index = i; - _done = false; - try - { - _stateMachine.Run(this, State.Start, _stream); - if (!_done) - throw new JsonPathSyntaxException(GetErrorLocation(), "Found incomplete expression."); - } - catch (InputNotValidForStateException e) - { - switch (e.State) - { - case State.Start: - throw new JsonPathSyntaxException(GetErrorLocation(), "Expression must start with '('."); - case State.Value: - throw new JsonPathSyntaxException(GetErrorLocation(), "Expected an expression, a value, or a path which evaluates to a value."); - case State.NumberOrPath: - throw new JsonPathSyntaxException(GetErrorLocation(), "Expected an expression, a value, or a path which evaluates to a value."); - case State.Operator: - throw new JsonPathSyntaxException(GetErrorLocation(), "Expected an operator."); - case State.Comparison: - throw new JsonPathSyntaxException(GetErrorLocation(), "Invalid comparison operator."); - case State.ValueOrOperator: - if (e.Input == JsonPathExpressionInput.CloseParenth) - throw new JsonPathSyntaxException(GetErrorLocation(), "Found incomplete expression."); - throw new JsonPathSyntaxException(GetErrorLocation(), "Invalid comparison operator."); - case State.BooleanOrComparison: - throw new JsonPathSyntaxException(GetErrorLocation(), "Expected '=', a boolean value, or an expression which evaluates to a boolean."); - default: - throw new ArgumentOutOfRangeException(); - } - } - catch (StateNotValidException) - { - throw new JsonPathSyntaxException(GetErrorLocation(), "An unrecoverable error occurred while parsing a JSON path expression. Please report to littlecrabsolutions@yahoo.com."); - } - catch (ActionNotDefinedForStateAndInputException) - { - throw new JsonPathSyntaxException(GetErrorLocation(), "An unrecoverable error occurred while parsing a JSON path expression. Please report to littlecrabsolutions@yahoo.com."); - } - } - private string GetErrorLocation() - { - var length = _index - _startIndex - 1; - return length < 1 ? string.Empty : _source.Substring(_startIndex, length); - } - private static void GetNextInput(object owner) - { - var obj = owner as Expression; - if (obj == null) return; - if (obj._done || (obj._index == obj._source.Length)) return; - var c = default(char); - try - { - c = obj._source[obj._index++]; - var next = CharacterConverter.ExpressionItem(c); - obj._stream.Add(next); - } - catch (KeyNotFoundException) - { - throw new JsonPathSyntaxException(obj.GetErrorLocation(), "Unrecognized character '{0}' in input string.", c); - } - } - private static State GotStart(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._nodeList = new List>(); - return State.Value; - } - private static State GotGroup(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - CheckComparison(exp); - var group = new Expression(); - exp._index = group.Parse(exp._source, exp._index - 1); - var last = exp._nodeList.LastOrDefault() as IndexOfExpression; - if (last != null) - last.ParameterExpression = group.Root as ExpressionTreeNode; - else - exp._nodeList.Add(new GroupExpression {Group = group.Root}); - return State.Operator; - } - private static State CompleteExpression(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp.Root = BuildTree(exp._nodeList); - exp._nodeList = null; - exp._done = true; - return State.End; - } - private static State GotNumber(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - CheckComparison(exp); - var numString = new string(exp._source.Skip(exp._index - 1).TakeWhile(char.IsNumber).ToArray()); - double num; - if (!double.TryParse(numString, out num)) - throw new JsonPathSyntaxException("Attempt to parse '{0}' as a number failed.", numString); - exp._index += numString.Length - 1; - exp._nodeList.Add(new ValueExpression {Value = num}); - return State.Operator; - } - private static State GotRoot(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - CheckComparison(exp); - var path = new JsonPath(); - exp._index = path.Parse(exp._source, exp._index - 1) - 1; - var name = path.Operators.Last() as NameOperator; - var indexOf = path.Operators.Last() as IndexOfOperator; - if (name != null && name.Name == "length") - { - path.Operators.Remove(name); - exp._nodeList.Add(new LengthExpression {Path = path}); - } - else if (indexOf != null) - { - path.Operators.Remove(indexOf); - exp._nodeList.Add(new IndexOfExpression { Path = path, IsLocal = true, ParameterExpression = indexOf.Parameter.Root }); - } - else - exp._nodeList.Add(new PathExpression {Path = path}); - return State.Operator; - } - private static State GotCurrent(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - CheckComparison(exp); - var path = new JsonPath(); - exp._index = path.Parse(exp._source, exp._index - 1) - 1; - var name = path.Operators.Last() as NameOperator; - var indexOf = path.Operators.Last() as IndexOfOperator; - if (name != null && name.Name == "length") - { - path.Operators.Remove(name); - exp._nodeList.Add(new LengthExpression {Path = path, IsLocal = true}); - } - else if (indexOf != null) - { - path.Operators.Remove(indexOf); - exp._nodeList.Add(new IndexOfExpression {Path = path, IsLocal = true, ParameterExpression = indexOf.Parameter.Root}); - } - else - exp._nodeList.Add(new PathExpression {Path = path, IsLocal = true}); - return State.Operator; - } - private static State GotString(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - CheckComparison(exp); - var value = new string(exp._source.Skip(exp._index).TakeWhile(c => c != '"').ToArray()); - exp._index += value.Length+1; - exp._nodeList.Add(new ValueExpression {Value = value}); - return State.Operator; - } - private static State GotPlus(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._nodeList.Add(new AddExpression()); - return State.Value; - } - private static State GotMinus(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._nodeList.Add(new SubtractExpression()); - return State.Value; - } - private static State GotMultiply(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._nodeList.Add(new MultiplyExpression()); - return State.Value; - } - private static State GotDivide(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._nodeList.Add(new DivideExpression()); - return State.Value; - } - private static State GotExponent(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._nodeList.Add(new ExponentExpression()); - return State.Value; - } - private static State GotLessThan(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._previousInput = JsonPathExpressionInput.LessThan; - return State.ValueOrOperator; - } - private static State GotEqual(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - if (exp._previousInput.HasValue) - { - switch (exp._previousInput) - { - case JsonPathExpressionInput.Equal: - exp._nodeList.Add(new IsEqualExpression()); - break; - case JsonPathExpressionInput.Bang: - exp._nodeList.Add(new IsNotEqualExpression()); - break; - default: - throw new JsonPathSyntaxException(exp.GetErrorLocation(), "Operator '={0}' not recognized.", exp._source[exp._index - 1]); - } - exp._previousInput = null; - return State.Value; - } - exp._previousInput = JsonPathExpressionInput.Equal; - return State.Comparison; - } - private static State GotAnd(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - if (exp._previousInput.HasValue) - { - if (exp._previousInput == JsonPathExpressionInput.And) - exp._nodeList.Add(new AndExpression()); - else - throw new JsonPathSyntaxException(exp.GetErrorLocation(), "Operator '&{0}' not recognized.", exp._source[exp._index - 1]); - exp._previousInput = null; - return State.Value; - } - exp._previousInput = JsonPathExpressionInput.And; - return State.Comparison; - } - private static State GotOr(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - if (exp._previousInput.HasValue) - { - if (exp._previousInput == JsonPathExpressionInput.Or) - exp._nodeList.Add(new OrExpression()); - else - throw new JsonPathSyntaxException(exp.GetErrorLocation(), "Operator '|{0}' not recognized.", exp._source[exp._index - 1]); - exp._previousInput = null; - return State.Value; - } - exp._previousInput = JsonPathExpressionInput.Or; - return State.Comparison; - } - private static State GotGreaterThan(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._previousInput = JsonPathExpressionInput.GreaterThan; - return State.ValueOrOperator; - } - private static State GotNot(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - exp._previousInput = JsonPathExpressionInput.Bang; - return State.BooleanOrComparison; - } - private static State GotNamedConstant(object owner, JsonPathExpressionInput input) - { - ExpressionTreeNode bang = null; - var exp = owner as Expression; - if (exp._previousInput.HasValue) - { - bang = new NotExpression(); - exp._previousInput = null; - } - object value; - var substring = exp._source.Substring(exp._index - 1).ToLower(); - if (substring.StartsWith("true")) - { - if (bang != null) - exp._nodeList.Add(bang); - value = true; - exp._index += 3; - } - else if (substring.StartsWith("false")) - { - if (bang != null) - exp._nodeList.Add(bang); - value = false; - exp._index += 4; - } - else if (substring.StartsWith("null")) - { - if (bang != null) - throw new JsonPathSyntaxException(exp.GetErrorLocation(), "Cannot apply '!' operator to 'null'."); - value = JsonValue.Null; - exp._index += 3; - } - else - { - var constant = new string(substring.TakeWhile(char.IsLetterOrDigit).ToArray()); - throw new JsonPathSyntaxException(exp.GetErrorLocation(), "Constant value '{0}' not recognized", constant); - } - exp._nodeList.Add(new ValueExpression {Value = value}); - return State.Operator; - } - private static State FinalizeComparison(object owner, JsonPathExpressionInput input) - { - var exp = owner as Expression; - switch (input) - { - case JsonPathExpressionInput.LessThan: - exp._nodeList.Add(new IsLessThanExpression()); - break; - case JsonPathExpressionInput.Equal: - switch (exp._previousInput) - { - case JsonPathExpressionInput.LessThan: - exp._nodeList.Add(new IsLessThanEqualExpression()); - break; - case JsonPathExpressionInput.GreaterThan: - exp._nodeList.Add(new IsGreaterThanEqualExpression()); - break; - case JsonPathExpressionInput.Bang: - exp._nodeList.Add(new IsNotEqualExpression()); - break; - default: - throw new JsonPathSyntaxException(exp.GetErrorLocation(), "Operator '{0}=' not recognized.", exp._source[exp._index - 2]); - } - break; - case JsonPathExpressionInput.GreaterThan: - exp._nodeList.Add(new IsGreaterThanExpression()); - break; - case JsonPathExpressionInput.Bang: - exp._nodeList.Add(new NotExpression()); - break; - default: - throw new ArgumentOutOfRangeException(); - } - exp._previousInput = null; - return State.NumberOrPath; - } - private static void CheckComparison(Expression exp) - { - if (exp._previousInput.HasValue) - { - var previous = exp._previousInput.Value; - exp._previousInput = null; - FinalizeComparison(exp, previous); - } - } - private static ExpressionTreeNode BuildTree(IList> nodes) - { - if (!nodes.Any()) return null; - var minPriority = nodes.Min(n => n.Priority); - var root = nodes.Last(n => n.Priority == minPriority); - var branch = root as ExpressionTreeBranch; - if (branch != null) - { - var split = nodes.IndexOf(root); - var left = nodes.Take(split).ToList(); - var right = nodes.Skip(split + 1).ToList(); - branch.Left = BuildTree(left); - branch.Right = BuildTree(right); - } - var not = root as NotExpression; - if (not != null) - { - var split = nodes.IndexOf(root); - var right = nodes.Skip(split + 1).FirstOrDefault(); - not.Root = right; - } - return root; + throw new NotImplementedException(); } } } diff --git a/Manatee.Json/Path/JsonPath.cs b/Manatee.Json/Path/JsonPath.cs index f1e2c47..b6b2b97 100644 --- a/Manatee.Json/Path/JsonPath.cs +++ b/Manatee.Json/Path/JsonPath.cs @@ -24,14 +24,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using Manatee.Json.Internal; -using Manatee.Json.Path.ArrayParameters; -using Manatee.Json.Path.Expressions; -using Manatee.Json.Path.Operators; -using Manatee.Json.Path.SearchParameters; -using Manatee.StateMachine; -using Manatee.StateMachine.Exceptions; +using Manatee.Json.Path.Parsing; namespace Manatee.Json.Path { @@ -40,79 +34,8 @@ namespace Manatee.Json.Path /// public class JsonPath { - private enum State - { - Start, - ObjectOrArray, - ArrayContent, - IndexOrSlice, - Colon, - SliceValue, - Comma, - IndexValue, - CloseArray, - ObjectContent, - Search, - End - } - - private static readonly StateMachine _stateMachine = new StateMachine(); - - private bool _gotObject; - private string _source; - private int _index; - private List _indices; - private Dictionary _slice; - private int _colonCount; - private bool _done; - private readonly InputStream _stream = new InputStream(); - private bool _isSearch; - private bool _allowLocalRoot; - internal List Operators { get; } = new List(); - static JsonPath() - { - _stateMachine[State.Start, JsonPathInput.Dollar] = GotRoot; - _stateMachine[State.Start, JsonPathInput.Current] = GotCurrent; - _stateMachine[State.ObjectOrArray, JsonPathInput.OpenBracket] = GotArray; - _stateMachine[State.ObjectOrArray, JsonPathInput.Period] = GotObject; - _stateMachine[State.ObjectOrArray, JsonPathInput.OpenParenth] = GotFunction; - _stateMachine[State.ObjectOrArray, JsonPathInput.Number] = CompletePath; - _stateMachine[State.ObjectOrArray, JsonPathInput.End] = CompletePath; - _stateMachine[State.ArrayContent, JsonPathInput.Star] = GotArrayWildCard; - _stateMachine[State.ArrayContent, JsonPathInput.OpenParenth] = ParseIndexExpression; - _stateMachine[State.ArrayContent, JsonPathInput.Question] = ParseFilterExpression; - _stateMachine[State.ArrayContent, JsonPathInput.Number] = GotIndexOrSlice; - _stateMachine[State.ArrayContent, JsonPathInput.Colon] = GotSlice; - _stateMachine[State.IndexOrSlice, JsonPathInput.Colon] = GotSlice; - _stateMachine[State.IndexOrSlice, JsonPathInput.Comma] = GotIndex; - _stateMachine[State.IndexOrSlice, JsonPathInput.CloseBracket] = FinalizeIndex; - _stateMachine[State.Colon, JsonPathInput.CloseBracket] = FinalizeSlice; - _stateMachine[State.Colon, JsonPathInput.Number] = GotSliceValue; - _stateMachine[State.Colon, JsonPathInput.Colon] = GotSlice; - _stateMachine[State.SliceValue, JsonPathInput.CloseBracket] = FinalizeSlice; - _stateMachine[State.SliceValue, JsonPathInput.Colon] = GotSlice; - _stateMachine[State.Comma, JsonPathInput.Number] = GotIndexValue; - _stateMachine[State.IndexValue, JsonPathInput.CloseBracket] = FinalizeIndex; - _stateMachine[State.IndexValue, JsonPathInput.Comma] = GotIndex; - _stateMachine[State.CloseArray, JsonPathInput.CloseBracket] = FinalizeArray; - _stateMachine[State.ObjectContent, JsonPathInput.Period] = GotSearch; - _stateMachine[State.ObjectContent, JsonPathInput.Star] = GotObjectWildCard; - _stateMachine[State.ObjectContent, JsonPathInput.Letter] = GotName; - _stateMachine[State.Search, JsonPathInput.OpenBracket] = GotSearchArray; - _stateMachine[State.Search, JsonPathInput.Star] = GotSearchWildCard; - _stateMachine[State.Search, JsonPathInput.Letter] = GotSearchName; - _stateMachine.UpdateFunction = GetNextInput; - } - /// - /// Finalizes memory management responsibilities. - /// - ~JsonPath() - { - _stateMachine.UnregisterOwner(this); - } - /// /// Parses a containing a JSON path. /// @@ -123,13 +46,7 @@ static JsonPath() /// Thrown if contains invalid JSON path syntax. public static JsonPath Parse(string source) { - if (source == null) - throw new ArgumentNullException(nameof(source)); - if (source.IsNullOrWhiteSpace()) - throw new ArgumentException("Source string contains no data."); - var path = new JsonPath {_source = Regex.Replace(source, @"\s+", string.Empty)}; - path.Parse(0); - return path; + return JsonPathParser.Parse(source); } /// /// Evaluates a JSON value using the path. @@ -158,329 +75,5 @@ internal string GetRawString() { return Operators.Select(o => o.ToString()).Join(string.Empty); } - internal int Parse(string source, int i) - { - _source = source; - _allowLocalRoot = true; - Parse(i); - return _index; - } - - private void Parse(int i) - { - _stream.Clear(); - _index = i; - _done = false; - try - { - _stateMachine.Run(this, State.Start, _stream); - if (!_done) - throw new JsonPathSyntaxException(this, "Found incomplete JSON path."); - } - catch (InputNotValidForStateException e) - { - if (e.Input == JsonPathInput.Star && _allowLocalRoot) - { - _done = true; - return; - } - switch (e.State) - { - case State.Start: - throw new JsonPathSyntaxException(this, "Paths must start with a '$', or optionally a '@' if inside an expression."); - case State.ObjectOrArray: - throw new JsonPathSyntaxException(this, "Expected '.', '..', or '['."); - case State.ArrayContent: - throw new JsonPathSyntaxException(this, "Expected '?', '(', ':', or an integer."); - case State.IndexOrSlice: - throw new JsonPathSyntaxException(this, "Expected ',', ':', or ']'."); - case State.Colon: - if (_colonCount == 2) - throw new JsonPathSyntaxException(this, "Expected ']' or an integer."); - throw new JsonPathSyntaxException(this, "Expected ']', ':', or an integer."); - case State.SliceValue: - throw new JsonPathSyntaxException(this, "Expected ':' or ']'."); - case State.Comma: - throw new JsonPathSyntaxException(this, "Expected an integer."); - case State.IndexValue: - throw new JsonPathSyntaxException(this, "Expected ',' or ']'."); - case State.CloseArray: - throw new JsonPathSyntaxException(this, "Expected ']'."); - case State.ObjectContent: - throw new JsonPathSyntaxException(this, "Expected a key name. Note: quoted keys and keys which start with numbers are not currently supported."); - case State.Search: - throw new JsonPathSyntaxException(this, "Expected a key name or '['. Note: quoted keys and keys which start with numbers are not currently supported."); - default: - throw new ArgumentOutOfRangeException(); - } - } - catch (StateNotValidException) - { - throw new JsonPathSyntaxException(this, "An unrecoverable error occurred while parsing a JSON path. Please report to littlecrabsolutions@yahoo.com."); - } - catch (ActionNotDefinedForStateAndInputException) - { - throw new JsonPathSyntaxException(this, "An unrecoverable error occurred while parsing a JSON path. Please report to littlecrabsolutions@yahoo.com."); - } - } - private static void GetNextInput(object owner) - { - var obj = owner as JsonPath; - if (obj == null) return; - if (obj._done) return; - if (obj._index == obj._source.Length) - { - obj._stream.Add(JsonPathInput.End); - return; - } - var c = default(char); - try - { - c = obj._source[obj._index++]; - var next = CharacterConverter.PathItem(c); - obj._stream.Add(next); - } - catch (KeyNotFoundException) - { - throw new JsonPathSyntaxException(obj, "Unrecognized character '{0}' in input string.", c); - } - } - private static State GotRoot(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - return State.ObjectOrArray; - } - private static State GotCurrent(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - if (!path._allowLocalRoot) - throw new JsonPathSyntaxException(path, "Local paths (starting with '@') are only allowed within the context of an expression."); - path._gotObject = false; - return State.ObjectOrArray; - } - private static State GotArray(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - path._colonCount = 0; - return State.ArrayContent; - } - private static State GotObject(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - if (path._gotObject) - { - path._gotObject = false; - return State.Search; - } - path._gotObject = true; - return State.ObjectContent; - } - private static State GotFunction(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - var exp = new Expression(); - path._index = exp.Parse(path._source, path._index - 1); - var last = path.Operators.Last() as NameOperator; - if (last != null && last.Name != "indexOf") - throw new NotSupportedException("Currently, 'indexOf()' is the only supported function."); - path.Operators.Remove(last); - path.Operators.Add(new IndexOfOperator(exp)); - return State.ObjectOrArray; - } - private static State CompletePath(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._done = true; - return State.End; - } - private static State GotArrayWildCard(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - if (path._isSearch) - { - path._isSearch = false; - path.Operators.Add(new SearchOperator(new ArraySearchParameter(WildCardQuery.Instance))); - } - else - path.Operators.Add(new ArrayOperator(WildCardQuery.Instance)); - return State.CloseArray; - } - private static State ParseIndexExpression(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - var exp = new Expression(); - path._index = exp.Parse(path._source, path._index - 1); - path.Operators.Add(new ArrayOperator(new IndexExpressionQuery(exp))); - return State.CloseArray; - } - private static State ParseFilterExpression(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - var exp = new Expression(); - path._index = exp.Parse(path._source, path._index); - path.Operators.Add(new ArrayOperator(new FilterExpressionQuery(exp))); - return State.CloseArray; - } - private static State GotIndexOrSlice(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - var value = GetNumber(path); - path._indices = new List {value}; - return State.IndexOrSlice; - } - private static State GotSliceValue(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - var value = GetNumber(path); - path._slice[path._colonCount] = value; - return State.SliceValue; - } - private static State FinalizeSlice(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - if (path._colonCount > 2) - throw new JsonPathSyntaxException(path, "Array slice format incorrect. Parameters are 'start:end:increment'."); - int? start = path._slice.ContainsKey(0) ? path._slice[0] : (int?)null; - int? end = path._slice.ContainsKey(1) ? path._slice[1] : (int?)null; - int? step = path._slice.ContainsKey(2) ? path._slice[2] : (int?)null; - if (path._isSearch) - { - path._isSearch = false; - path.Operators.Add(new SearchOperator(new ArraySearchParameter(new SliceQuery(start, end, step)))); - } - else - path.Operators.Add(new ArrayOperator(new SliceQuery(start, end, step))); - return State.ObjectOrArray; - } - private static State GotSlice(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - if (path._slice == null) - path._slice = new Dictionary(); - if (path._indices != null && path._indices.Any()) - { - path._slice.Add(path._colonCount, path._indices.First()); - path._indices = null; - } - path._colonCount++; - return State.Colon; - } - private static State GotIndexValue(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - var value = GetNumber(path); - path._indices.Add(value); - return State.IndexValue; - } - private static State FinalizeIndex(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - if (path._isSearch) - { - path._isSearch = false; - path.Operators.Add(new SearchOperator(new ArraySearchParameter(new IndexQuery(path._indices.ToArray())))); - } - else - path.Operators.Add(new ArrayOperator(new IndexQuery(path._indices.ToArray()))); - return State.ObjectOrArray; - } - private static State GotIndex(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - return State.Comma; - } - private static State FinalizeArray(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - return State.ObjectOrArray; - } - private static State GotSearch(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - return State.Search; - } - private static State GotObjectWildCard(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - path.Operators.Add(WildCardOperator.Instance); - return State.ObjectOrArray; - } - private static State GotName(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - var name = GetName(path); - path.Operators.Add(new NameOperator(name)); - return State.ObjectOrArray; - } - private static State GotSearchArray(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - path._isSearch = true; - return State.ArrayContent; - } - private static State GotSearchWildCard(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - path.Operators.Add(new SearchOperator(WildCardSearchParameter.Instance)); - return State.ObjectOrArray; - } - private static State GotSearchName(object owner, JsonPathInput input) - { - var path = owner as JsonPath; - path._gotObject = false; - var name = GetName(path); - path.Operators.Add(new SearchOperator(new NameSearchParameter(name))); - return State.ObjectOrArray; - } - private static string GetName(JsonPath path) - { - var chars = new List(); - path._index--; - while (path._index < path._source.Length && (char.IsLetterOrDigit(path._source[path._index]) || path._source[path._index] == '_')) - { - chars.Add(path._source[path._index]); - path._index++; - } - var name = new string(chars.ToArray()); - return name; - } - private static int GetNumber(JsonPath path) - { - var chars = new List(); - path._index--; - var negate = path._source[path._index] == '-'; - int sign = 1; - if (negate) - { - sign = -1; - path._index++; - } - while (path._index < path._source.Length && char.IsNumber(path._source[path._index])) - { - chars.Add(path._source[path._index]); - path._index++; - } - var name = new string(chars.ToArray()); - return int.Parse(name)*sign; - } } } \ No newline at end of file diff --git a/Manatee.Json/Path/JsonPathSyntaxException.cs b/Manatee.Json/Path/JsonPathSyntaxException.cs index f824fee..18e889f 100644 --- a/Manatee.Json/Path/JsonPathSyntaxException.cs +++ b/Manatee.Json/Path/JsonPathSyntaxException.cs @@ -47,7 +47,7 @@ public class JsonPathSyntaxException : Exception /// The error message that explains the reason for the exception, or an empty string(""). /// /// 1 - public override string Message => string.Format(_isExpression ? "{0} Expression up to error: {1}" : "{0} Path up to error: {1}", base.Message, Path); + public override string Message => string.Format(_isExpression ? "{0} Expression up to error: '{1}'" : "{0} Path up to error: '{1}'", base.Message, Path); [StringFormatMethod("format")] internal JsonPathSyntaxException(JsonPath path, string format, params object[] parameters) diff --git a/Manatee.Json/Path/Operators/NameOperator.cs b/Manatee.Json/Path/Operators/NameOperator.cs index 4d3278e..0afb069 100644 --- a/Manatee.Json/Path/Operators/NameOperator.cs +++ b/Manatee.Json/Path/Operators/NameOperator.cs @@ -43,7 +43,9 @@ public JsonArray Evaluate(JsonArray json, JsonValue root) } public override string ToString() { - return $".{Name}"; + return Name.Any(c => !char.IsLetterOrDigit(c)) + ? $".'{Name}'" + : $".{Name}"; } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Parsing/IJsonPathParser.cs b/Manatee.Json/Path/Parsing/IJsonPathParser.cs new file mode 100644 index 0000000..ef8d555 --- /dev/null +++ b/Manatee.Json/Path/Parsing/IJsonPathParser.cs @@ -0,0 +1,9 @@ +namespace Manatee.Json.Path.Parsing +{ + internal interface IJsonPathParser + { + bool Handles(string input); + + string TryParse(string source, ref int index, ref JsonPath path); + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Parsing/JsonPathParser.cs b/Manatee.Json/Path/Parsing/JsonPathParser.cs new file mode 100644 index 0000000..d50b677 --- /dev/null +++ b/Manatee.Json/Path/Parsing/JsonPathParser.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Manatee.Json.Internal; + +namespace Manatee.Json.Path.Parsing +{ + internal static class JsonPathParser + { + private static readonly List Parsers; + + static JsonPathParser() + { + Parsers = typeof(JsonPathParser).Assembly.GetTypes() + .Where(t => typeof(IJsonPathParser).IsAssignableFrom(t) && t.IsClass) + .Select(Activator.CreateInstance) + .Cast() + .ToList(); + } + + public static JsonPath Parse(string source) + { + var index = 0; + JsonPath path; + var errorMessage = Parse(source, ref index, out path); + if (errorMessage != null) + throw new JsonPathSyntaxException(path, errorMessage); + return path; + } + public static string Parse(string source, ref int index, out JsonPath path) + { + var length = source.Length; + path = null; + while(index < length) + { + char c; + var errorMessage = source.SkipWhiteSpace(ref index, length, out c); + if (errorMessage != null) return errorMessage; + var i = index; + var parser = Parsers.FirstOrDefault(p => p.Handles(source.Substring(i))); + if (parser == null) return "Unrecognized JSON Path element."; + errorMessage = parser.TryParse(source, ref index, ref path); + if (errorMessage != null) return errorMessage; + } + return null; + } + } +} diff --git a/Manatee.Json/Path/Parsing/ObjectParser.cs b/Manatee.Json/Path/Parsing/ObjectParser.cs new file mode 100644 index 0000000..0579a14 --- /dev/null +++ b/Manatee.Json/Path/Parsing/ObjectParser.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; + +namespace Manatee.Json.Path.Parsing +{ + internal class ObjectParser : IJsonPathParser + { + public bool Handles(string input) + { + return input.Length > 1 && input[0] == '.' && input[1] != '.'; + } + public string TryParse(string source, ref int index, ref JsonPath path) + { + if (path == null) + return "Start token not found."; + + index++; + + if (source[index] == '*') + { + path = path.Wildcard(); + index++; + return null; + } + + string key; + var error = source.GetKey(ref index, out key); + if (error != null) return error; + + path = path.Name(key); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs new file mode 100644 index 0000000..6ee3be3 --- /dev/null +++ b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs @@ -0,0 +1,103 @@ +using System; +using Manatee.Json.Internal; + +namespace Manatee.Json.Path.Parsing +{ + internal static class PathParsingExtensions + { + private static readonly int[] FibSequence = { 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657 }; + + public static string GetKey(this string source, ref int index, out string key) + { + return source[index].In('"', '\'') + ? GetQuotedKey(source, ref index, out key) + : GetBasicKey(source, ref index, out key); + } + + private static string GetBasicKey(string source, ref int index, out string key) + { + var bufferSize = 0; + var bufferLength = FibSequence[bufferSize]; + var buffer = new char[bufferLength]; + var bufferIndex = 0; + var sourceLength = source.Length; + var complete = false; + var c = (char)0; + while (index < sourceLength) + { + if (bufferIndex == bufferLength) + { + var currentLength = bufferLength; + bufferSize++; + bufferLength = FibSequence[bufferSize]; + var newBuffer = new char[bufferLength]; + Buffer.BlockCopy(buffer, 0, newBuffer, 0, currentLength * 2); + buffer = newBuffer; + } + c = source[index]; + index++; + if (!char.IsLetterOrDigit(c)) + { + complete = true; + index--; + break; + } + buffer[bufferIndex] = c; + bufferIndex++; + } + if (!complete && index + 1 < sourceLength) + { + key = null; + return $"The character '{c}' is not supported for unquoted names."; + } + var result = new string(buffer, 0, bufferIndex); + key = result; + return null; + } + + private static string GetQuotedKey(string source, ref int index, out string key) + { + var bufferSize = 0; + var bufferLength = FibSequence[bufferSize]; + var buffer = new char[bufferLength]; + var bufferIndex = 0; + var sourceLength = source.Length; + var foundEscape = false; + var complete = false; + var quoteChar = source[index]; + index++; + while (index < sourceLength) + { + if (bufferIndex == bufferLength) + { + var currentLength = bufferLength; + bufferSize++; + bufferLength = FibSequence[bufferSize]; + var newBuffer = new char[bufferLength]; + Buffer.BlockCopy(buffer, 0, newBuffer, 0, currentLength * 2); + buffer = newBuffer; + } + var c = source[index]; + index++; + if (c == quoteChar && !foundEscape) + { + complete = true; + break; + } + foundEscape = c == '\\'; + buffer[bufferIndex] = c; + bufferIndex++; + } + if (!complete) + { + key = null; + return "Could not find end of string value."; + } + var result = new string(buffer, 0, bufferIndex); + string escaped; + var errorMessage = result.EvaluateEscapeSequences(out escaped); + key = escaped; + return errorMessage; + } + } +} diff --git a/Manatee.Json/Path/Parsing/SearchParser.cs b/Manatee.Json/Path/Parsing/SearchParser.cs new file mode 100644 index 0000000..2c9d057 --- /dev/null +++ b/Manatee.Json/Path/Parsing/SearchParser.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; + +namespace Manatee.Json.Path.Parsing +{ + internal class SearchParser : IJsonPathParser + { + public bool Handles(string input) + { + return input.StartsWith(".."); + } + public string TryParse(string source, ref int index, ref JsonPath path) + { + if (path == null) + return "Start token not found."; + + index += 2; + + if (source[index] == '*') + { + path = path.Search(); + index++; + return null; + } + + string key; + var error = source.GetKey(ref index, out key); + if (error != null) return error; + + path = path.Search(key); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Parsing/StartParser.cs b/Manatee.Json/Path/Parsing/StartParser.cs new file mode 100644 index 0000000..0240600 --- /dev/null +++ b/Manatee.Json/Path/Parsing/StartParser.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace Manatee.Json.Path.Parsing +{ + internal class StartParser : IJsonPathParser + { + public bool Handles(string input) + { + return input[0] == '$' || input[0] == '@'; + } + public string TryParse(string source, ref int index, ref JsonPath path) + { + if (path != null) + return "Start token not valid in the middle of path."; + + path = new JsonPath(); + index++; + return null; + } + } +} diff --git a/Manatee.Json/packages.config b/Manatee.Json/packages.config deleted file mode 100644 index 66d7e29..0000000 --- a/Manatee.Json/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From 2a2173f7fc75961a4ec9d953f8398b8ac708ed6e Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Tue, 13 Sep 2016 12:47:45 +1200 Subject: [PATCH 02/18] Added indexed array parsing. --- Manatee.Json.Tests/Path/ParsingTest.cs | 103 +++++++++++++++++++++ Manatee.Json/Manatee.Json.csproj | 3 +- Manatee.Json/Path/ArrayParameters/IndexQuery.cs | 53 ----------- Manatee.Json/Path/ArrayParameters/Slice.cs | 66 +++++++++++++ Manatee.Json/Path/ArrayParameters/SliceQuery.cs | 42 ++------- .../Translation/PathExpressionTranslator.cs | 2 +- Manatee.Json/Path/JsonPathWith.cs | 28 +++--- Manatee.Json/Path/Parsing/ArrayParser.cs | 90 ++++++++++++++++++ Manatee.Json/Path/Parsing/JsonPathParser.cs | 8 +- Manatee.Json/Path/Parsing/ObjectParser.cs | 5 +- 10 files changed, 292 insertions(+), 108 deletions(-) delete mode 100644 Manatee.Json/Path/ArrayParameters/IndexQuery.cs create mode 100644 Manatee.Json/Path/ArrayParameters/Slice.cs create mode 100644 Manatee.Json/Path/Parsing/ArrayParser.cs diff --git a/Manatee.Json.Tests/Path/ParsingTest.cs b/Manatee.Json.Tests/Path/ParsingTest.cs index 65ebf05..c2d7876 100644 --- a/Manatee.Json.Tests/Path/ParsingTest.cs +++ b/Manatee.Json.Tests/Path/ParsingTest.cs @@ -1,4 +1,5 @@ using Manatee.Json.Path; +using Manatee.Json.Path.ArrayParameters; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Manatee.Json.Tests.Path @@ -148,5 +149,107 @@ public void WildcardObjectWithNamedSearch() Assert.AreEqual(expected.ToString(), actual.ToString()); //Assert.AreEqual(expected, actual); } + + [TestMethod] + public void SingleIndexedArray() + { + var text = "$[1]"; + var expected = JsonPathWith.Array(1); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SingleSlicedArray() + { + var text = "$[1:5]"; + var expected = JsonPathWith.Array(new Slice(1, 5)); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SteppedSlicedArray() + { + var text = "$[1:5:2]"; + var expected = JsonPathWith.Array(new Slice(1, 5, 2)); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void IndexedSlicedArray() + { + var text = "$[1,5:7]"; + var expected = JsonPathWith.Array(1, new Slice(5, 7)); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SlicedIndexedArray() + { + var text = "$[1:5,7]"; + var expected = JsonPathWith.Array(new Slice(1, 5), 7); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void MultiSlicedArray() + { + var text = "$[1:5,7:11:2]"; + var expected = JsonPathWith.Array(new Slice(1, 5), new Slice(7, 11, 2)); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void MultiIndexedArray() + { + var text = "$[1,3]"; + var expected = JsonPathWith.Array(1, 3); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } + + [TestMethod] + [ExpectedException(typeof(JsonPathSyntaxException))] + public void EmptyIndexedArray() + { + var text = "$[]"; + + JsonPath.Parse(text); + } + + [TestMethod] + [ExpectedException(typeof(JsonPathSyntaxException))] + public void EmptyObject() + { + var text = "$."; + + JsonPath.Parse(text); + } } } diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index d4546fd..1d3edcc 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -233,7 +233,7 @@ - + @@ -311,6 +311,7 @@ + diff --git a/Manatee.Json/Path/ArrayParameters/IndexQuery.cs b/Manatee.Json/Path/ArrayParameters/IndexQuery.cs deleted file mode 100644 index 1e85170..0000000 --- a/Manatee.Json/Path/ArrayParameters/IndexQuery.cs +++ /dev/null @@ -1,53 +0,0 @@ -/*************************************************************************************** - - Copyright 2016 Greg Dennis - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - File Name: IndexQuery.cs - Namespace: Manatee.Json.Path.ArrayParameters - Class Name: IndexQuery - Purpose: Provides indexed array queries. - -***************************************************************************************/ -using System.Collections.Generic; -using System.Linq; -using Manatee.Json.Internal; - -namespace Manatee.Json.Path.ArrayParameters -{ - internal class IndexQuery : IJsonPathArrayQuery - { - private readonly IEnumerable _indices; - - public IndexQuery(params int[] indices) - { - _indices = indices; - } - - public IEnumerable Find(JsonArray json, JsonValue root) - { - var index = 0; - while (index < json.Count) - { - if (_indices.Contains(index)) - yield return json[index]; - index++; - } - } - public override string ToString() - { - return _indices.Join(","); - } - } -} \ No newline at end of file diff --git a/Manatee.Json/Path/ArrayParameters/Slice.cs b/Manatee.Json/Path/ArrayParameters/Slice.cs new file mode 100644 index 0000000..abca3a5 --- /dev/null +++ b/Manatee.Json/Path/ArrayParameters/Slice.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Manatee.Json.Path.ArrayParameters +{ + public class Slice + { + private int? _index; + private int? _start; + private int? _end; + private int? _step; + + public Slice(int index) + { + _index = index; + } + public Slice(int? start, int? end, int? step = null) + { + _start = start; + _end = end; + _step = step; + } + + public IEnumerable Find(JsonArray json, JsonValue root) + { + if (_index.HasValue) + { + return json.Count > _index.Value + ? Enumerable.Empty() + : new[] {json[_index.Value]}; + } + + var start = ResolveIndex(_start ?? 0, json.Count); + var end = ResolveIndex(_end ?? json.Count, json.Count); + var step = Math.Max(_step ?? 1, 1); + + var index = start; + var list = new List(); + while (index < end) + { + list.Add(json[index]); + index += step; + } + return list; + } + public override string ToString() + { + return _index.HasValue + ? _index.ToString() + : _step.HasValue + ? $"{(_start.HasValue ? _start.ToString() : string.Empty)}:{(_end.HasValue ? _end.ToString() : string.Empty)}:{_step}" + : $"{(_start.HasValue ? _start.ToString() : string.Empty)}:{(_end.HasValue ? _end.ToString() : string.Empty)}"; + } + + public static implicit operator Slice(int i) + { + return new Slice(i); + } + + private static int ResolveIndex(int index, int count) + { + return index < 0 ? count + index : index; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/ArrayParameters/SliceQuery.cs b/Manatee.Json/Path/ArrayParameters/SliceQuery.cs index 8c35186..75acbf2 100644 --- a/Manatee.Json/Path/ArrayParameters/SliceQuery.cs +++ b/Manatee.Json/Path/ArrayParameters/SliceQuery.cs @@ -20,53 +20,29 @@ Purpose: Provides array-slice-syntax queries for arrays. ***************************************************************************************/ -using System; +using Manatee.Json.Internal; using System.Collections.Generic; +using System.Linq; namespace Manatee.Json.Path.ArrayParameters { internal class SliceQuery : IJsonPathArrayQuery { - private int? _start; - private int? _end; - private int? _step; + private readonly IEnumerable _slices; - public SliceQuery(int? start, int? end, int? step = null) + public SliceQuery(params Slice[] slices) + : this((IEnumerable) slices) {} + public SliceQuery(IEnumerable slices) { - _start = start; - _end = end; - _step = step; + _slices = slices.ToList(); } - public IEnumerable Find(JsonArray json, JsonValue root) { - var start = ResolveIndex(_start ?? 0, json.Count); - var end = ResolveIndex(_end ?? json.Count, json.Count); - var step = Math.Max(_step ?? 1, 1); - - var index = start; - while (index < end) - { - yield return json[index]; - index += step; - } + return _slices.SelectMany(s => s.Find(json, root)).Distinct(); } public override string ToString() { - return _step.HasValue - ? string.Format("{0}:{1}:{2}", - _start.HasValue ? _start.ToString() : string.Empty, - _end.HasValue ? _end.ToString() : string.Empty, - _step) - : string.Format("{0}:{1}", - _start.HasValue ? _start.ToString() : string.Empty, - _end.HasValue ? _end.ToString() : string.Empty); - - } - - private static int ResolveIndex(int index, int count) - { - return index < 0 ? count + index : index; + return _slices.Join(","); } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Translation/PathExpressionTranslator.cs b/Manatee.Json/Path/Expressions/Translation/PathExpressionTranslator.cs index 031b1f8..4b979cb 100644 --- a/Manatee.Json/Path/Expressions/Translation/PathExpressionTranslator.cs +++ b/Manatee.Json/Path/Expressions/Translation/PathExpressionTranslator.cs @@ -51,7 +51,7 @@ protected static JsonPath BuildPath(MethodCallExpression method, out bool isLoca case "ArrayIndex": if (parameter == null || parameter.Type != typeof (int)) throw new NotSupportedException("Only literal string arguments are supported within JsonPath expressions."); - path.Operators.Insert(0, new ArrayOperator(new IndexQuery((int) parameter.Value))); + path.Operators.Insert(0, new ArrayOperator(new SliceQuery(new Slice((int) parameter.Value)))); break; } isLocal = currentMethod.Arguments.Count != 1; diff --git a/Manatee.Json/Path/JsonPathWith.cs b/Manatee.Json/Path/JsonPathWith.cs index e8bf218..50b861b 100644 --- a/Manatee.Json/Path/JsonPathWith.cs +++ b/Manatee.Json/Path/JsonPathWith.cs @@ -21,6 +21,7 @@ ***************************************************************************************/ using System; +using System.Linq; using System.Linq.Expressions; using Manatee.Json.Path.ArrayParameters; using Manatee.Json.Path.Expressions.Translation; @@ -116,12 +117,12 @@ public static JsonPath SearchArray() /// /// Appends a by specifying a series of array indicies. /// - /// The indices of the s to include. + /// The indices and slices of the s to include. /// The new . - public static JsonPath SearchArray(params int[] indices) + public static JsonPath SearchArray(params Slice[] slices) { var path = new JsonPath(); - path.Operators.Add(new SearchOperator(new ArraySearchParameter(new IndexQuery(indices)))); + path.Operators.Add(new SearchOperator(new ArraySearchParameter(new SliceQuery(slices)))); return path; } /// @@ -134,10 +135,11 @@ public static JsonPath SearchArray(params int[] indices) /// The format for the array slice is [start:end:step]. All parameters are individually optional, /// however either the start or end must be defines. Negative values for start and end indicate that the /// iterator should begin counting from the end of the array. + [Obsolete("Use the SearchArray(params Slice[]) overload instead.")] public static JsonPath SearchArraySlice(int? start, int? end, int? step = null) { var path = new JsonPath(); - path.Operators.Add(new SearchOperator(new ArraySearchParameter(new SliceQuery(start, end, step)))); + path.Operators.Add(new SearchOperator(new ArraySearchParameter(new SliceQuery(new Slice(start, end, step))))); return path; } /// @@ -175,12 +177,12 @@ public static JsonPath Array() /// /// Appends a by specifying a series of array indicies. /// - /// The indices of the s to include. + /// The indices and slices of the s to include. /// The new . - public static JsonPath Array(params int[] indices) + public static JsonPath Array(params Slice[] slices) { var path = new JsonPath(); - path.Operators.Add(new ArrayOperator(new IndexQuery(indices))); + path.Operators.Add(new ArrayOperator(new SliceQuery(slices))); return path; } /// @@ -193,10 +195,11 @@ public static JsonPath Array(params int[] indices) /// The format for the array slice is [start:end:step]. All parameters are individually optional, /// however either the start or end must be defines. Negative values for start and end indicate that the /// iterator should begin counting from the end of the array. + [Obsolete("Use the Array(params Slice[]) overload instead.")] public static JsonPath ArraySlice(int? start, int? end, int? step = null) { var path = new JsonPath(); - path.Operators.Add(new ArrayOperator(new SliceQuery(start, end, step))); + path.Operators.Add(new ArrayOperator(new SliceQuery(new Slice(start, end, step)))); return path; } /// @@ -321,13 +324,13 @@ public static JsonPath Array(this JsonPath path) /// Appends a by specifying a series of array indicies. /// /// The to extend. - /// The indices of the s to include. + /// The indices of the s to include. /// The new . - public static JsonPath Array(this JsonPath path, params int[] indices) + public static JsonPath Array(this JsonPath path, params Slice[] slices) { var newPath = new JsonPath(); newPath.Operators.AddRange(path.Operators); - newPath.Operators.Add(new ArrayOperator(new IndexQuery(indices))); + newPath.Operators.Add(new ArrayOperator(new SliceQuery(slices))); return newPath; } /// @@ -341,11 +344,12 @@ public static JsonPath Array(this JsonPath path, params int[] indices) /// The format for the array slice is [start:end:step]. All parameters are individually optional, /// however either the start or end must be defines. Negative values for start and end indicate that the /// iterator should begin counting from the end of the array. + [Obsolete("Use the Array(this JsonPath, params Slice[]) overload instead.")] public static JsonPath ArraySlice(this JsonPath path, int? start, int? end, int? step = null) { var newPath = new JsonPath(); newPath.Operators.AddRange(path.Operators); - newPath.Operators.Add(new ArrayOperator(new SliceQuery(start, end, step))); + newPath.Operators.Add(new ArrayOperator(new SliceQuery(new Slice(start, end, step)))); return newPath; } /// diff --git a/Manatee.Json/Path/Parsing/ArrayParser.cs b/Manatee.Json/Path/Parsing/ArrayParser.cs new file mode 100644 index 0000000..7a10b4e --- /dev/null +++ b/Manatee.Json/Path/Parsing/ArrayParser.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Manatee.Json.Internal; +using Manatee.Json.Path.ArrayParameters; + +namespace Manatee.Json.Path.Parsing +{ + internal class IndexedArrayParser : IJsonPathParser + { + public bool Handles(string input) + { + return input.Length > 1 && input[0] == '[' && char.IsDigit(input[1]); + //return input.Length > 1 && input[0] == '[' && (char.IsDigit(input[1]) || input[1].In('"', '\'')); + } + public string TryParse(string source, ref int index, ref JsonPath path) + { + if (path == null) + return "Start token not found."; + + string error; + Slice lastSlice; + var slices = new List(); + do + { + index++; + error = GetSlice(source, ref index, out lastSlice); + if (lastSlice != null) + slices.Add(lastSlice); + } while (error == null && lastSlice != null); + if (error != null) return error; + + index++; + if (!slices.Any()) + return "Index required inside '[]'"; + path = path.Array(slices.ToArray()); + return null; + } + + private static string GetSlice(string source, ref int index, out Slice slice) + { + slice = null; + if (source[index - 1] == ']') return null; + + int n1, n2, n3; + + var error = GetNumber(source, ref index, out n1); + if (error != null) return error; + if (source[index].In(',', ']')) + { + slice = new Slice(n1); + return null; + } + if (source[index] != ':') + return "Expected ':', ',', or ']'."; + + index++; + error = GetNumber(source, ref index, out n2); + if (error != null) return error; + if (source[index].In(',', ']')) + { + slice = new Slice(n1, n2); + return null; + } + if (source[index] != ':') + return "Expected ':', ',', or ']'."; + + index++; + error = GetNumber(source, ref index, out n3); + if (error != null) return error; + if (source[index].In(',', ']')) + { + slice = new Slice(n1, n2, n3); + return null; + } + return "Expected ',' or ']'."; + } + + private static string GetNumber(string source, ref int index, out int number) + { + var text = new string(source.Substring(index).TakeWhile(char.IsDigit).ToArray()); + if (!int.TryParse(text, out number)) + return "Expected number."; + + index += text.Length; + return null; + } + } +} diff --git a/Manatee.Json/Path/Parsing/JsonPathParser.cs b/Manatee.Json/Path/Parsing/JsonPathParser.cs index d50b677..6da0387 100644 --- a/Manatee.Json/Path/Parsing/JsonPathParser.cs +++ b/Manatee.Json/Path/Parsing/JsonPathParser.cs @@ -12,10 +12,10 @@ internal static class JsonPathParser static JsonPathParser() { Parsers = typeof(JsonPathParser).Assembly.GetTypes() - .Where(t => typeof(IJsonPathParser).IsAssignableFrom(t) && t.IsClass) - .Select(Activator.CreateInstance) - .Cast() - .ToList(); + .Where(t => typeof(IJsonPathParser).IsAssignableFrom(t) && t.IsClass) + .Select(Activator.CreateInstance) + .Cast() + .ToList(); } public static JsonPath Parse(string source) diff --git a/Manatee.Json/Path/Parsing/ObjectParser.cs b/Manatee.Json/Path/Parsing/ObjectParser.cs index 0579a14..6225f8c 100644 --- a/Manatee.Json/Path/Parsing/ObjectParser.cs +++ b/Manatee.Json/Path/Parsing/ObjectParser.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; - -namespace Manatee.Json.Path.Parsing +namespace Manatee.Json.Path.Parsing { internal class ObjectParser : IJsonPathParser { From 7fa6697db1afe40e35cf632409f85e2a2d0e27cb Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Tue, 13 Sep 2016 13:23:16 +1200 Subject: [PATCH 03/18] Added wildcard array parser. Updated namespace for Slice. Fixed indexed array parser to accept negatives and partial slices. --- Manatee.Json.Tests/Path/ParsingTest.cs | 12 +++++++++ Manatee.Json/Manatee.Json.csproj | 5 ++-- Manatee.Json/Path/JsonPathWith.cs | 28 +++++---------------- .../{ArrayParser.cs => IndexedArrayParser.cs} | 29 ++++++++++++++-------- Manatee.Json/Path/Parsing/WildcardArrayParser.cs | 16 ++++++++++++ Manatee.Json/Path/{ArrayParameters => }/Slice.cs | 4 +-- 6 files changed, 57 insertions(+), 37 deletions(-) rename Manatee.Json/Path/Parsing/{ArrayParser.cs => IndexedArrayParser.cs} (73%) create mode 100644 Manatee.Json/Path/Parsing/WildcardArrayParser.cs rename Manatee.Json/Path/{ArrayParameters => }/Slice.cs (90%) diff --git a/Manatee.Json.Tests/Path/ParsingTest.cs b/Manatee.Json.Tests/Path/ParsingTest.cs index c2d7876..8b17645 100644 --- a/Manatee.Json.Tests/Path/ParsingTest.cs +++ b/Manatee.Json.Tests/Path/ParsingTest.cs @@ -251,5 +251,17 @@ public void EmptyObject() JsonPath.Parse(text); } + + [TestMethod] + public void WildcardArray() + { + var text = "$[*]"; + var expected = JsonPathWith.Array(); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + //Assert.AreEqual(expected, actual); + } } } diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index 1d3edcc..d77d8cf 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -233,7 +233,7 @@ - + @@ -311,13 +311,14 @@ - + + diff --git a/Manatee.Json/Path/JsonPathWith.cs b/Manatee.Json/Path/JsonPathWith.cs index 50b861b..fe0c11e 100644 --- a/Manatee.Json/Path/JsonPathWith.cs +++ b/Manatee.Json/Path/JsonPathWith.cs @@ -105,16 +105,6 @@ public static JsonPath SearchLength() return path; } /// - /// Appends a by including all array values. - /// - /// The new . - public static JsonPath SearchArray() - { - var path = new JsonPath(); - path.Operators.Add(new SearchOperator(new ArraySearchParameter(WildCardQuery.Instance))); - return path; - } - /// /// Appends a by specifying a series of array indicies. /// /// The indices and slices of the s to include. @@ -122,7 +112,9 @@ public static JsonPath SearchArray() public static JsonPath SearchArray(params Slice[] slices) { var path = new JsonPath(); - path.Operators.Add(new SearchOperator(new ArraySearchParameter(new SliceQuery(slices)))); + path.Operators.Add(new SearchOperator(slices.Any() + ? new ArraySearchParameter(new SliceQuery(slices)) + : new ArraySearchParameter(WildCardQuery.Instance))); return path; } /// @@ -165,16 +157,6 @@ public static JsonPath SearchArray(Expression> express return path; } /// - /// Appends a by including all array values. - /// - /// The new . - public static JsonPath Array() - { - var path = new JsonPath(); - path.Operators.Add(new ArrayOperator(WildCardQuery.Instance)); - return path; - } - /// /// Appends a by specifying a series of array indicies. /// /// The indices and slices of the s to include. @@ -182,7 +164,9 @@ public static JsonPath Array() public static JsonPath Array(params Slice[] slices) { var path = new JsonPath(); - path.Operators.Add(new ArrayOperator(new SliceQuery(slices))); + path.Operators.Add(!slices.Any() + ? new ArrayOperator(WildCardQuery.Instance) + : new ArrayOperator(new SliceQuery(slices))); return path; } /// diff --git a/Manatee.Json/Path/Parsing/ArrayParser.cs b/Manatee.Json/Path/Parsing/IndexedArrayParser.cs similarity index 73% rename from Manatee.Json/Path/Parsing/ArrayParser.cs rename to Manatee.Json/Path/Parsing/IndexedArrayParser.cs index 7a10b4e..d935bfe 100644 --- a/Manatee.Json/Path/Parsing/ArrayParser.cs +++ b/Manatee.Json/Path/Parsing/IndexedArrayParser.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; using Manatee.Json.Internal; -using Manatee.Json.Path.ArrayParameters; namespace Manatee.Json.Path.Parsing { @@ -11,7 +8,7 @@ internal class IndexedArrayParser : IJsonPathParser { public bool Handles(string input) { - return input.Length > 1 && input[0] == '[' && char.IsDigit(input[1]); + return input.Length > 1 && input[0] == '[' && (char.IsDigit(input[1]) || input[1].In('-', ':')); //return input.Length > 1 && input[0] == '[' && (char.IsDigit(input[1]) || input[1].In('"', '\'')); } public string TryParse(string source, ref int index, ref JsonPath path) @@ -43,13 +40,13 @@ private static string GetSlice(string source, ref int index, out Slice slice) slice = null; if (source[index - 1] == ']') return null; - int n1, n2, n3; + int? n1, n2, n3; var error = GetNumber(source, ref index, out n1); if (error != null) return error; - if (source[index].In(',', ']')) + if (n1.HasValue && source[index].In(',', ']')) { - slice = new Slice(n1); + slice = new Slice(n1.Value); return null; } if (source[index] != ':') @@ -77,13 +74,23 @@ private static string GetSlice(string source, ref int index, out Slice slice) return "Expected ',' or ']'."; } - private static string GetNumber(string source, ref int index, out int number) + private static string GetNumber(string source, ref int index, out int? number) { - var text = new string(source.Substring(index).TakeWhile(char.IsDigit).ToArray()); - if (!int.TryParse(text, out number)) + int value; + var text = new string(source.Substring(index).TakeWhile(c => char.IsDigit(c) || c == '-').ToArray()); + if (text.Length == 0 && source[index].In(':',',',']')) + { + number = null; + return null; + } + if (!int.TryParse(text, out value)) + { + number = null; return "Expected number."; + } index += text.Length; + number = value; return null; } } diff --git a/Manatee.Json/Path/Parsing/WildcardArrayParser.cs b/Manatee.Json/Path/Parsing/WildcardArrayParser.cs new file mode 100644 index 0000000..1fcaf3c --- /dev/null +++ b/Manatee.Json/Path/Parsing/WildcardArrayParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Parsing +{ + internal class WildcardArrayParser : IJsonPathParser + { + public bool Handles(string input) + { + return input.StartsWith("[*]"); + } + public string TryParse(string source, ref int index, ref JsonPath path) + { + path = path.Array(); + index += 3; + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/ArrayParameters/Slice.cs b/Manatee.Json/Path/Slice.cs similarity index 90% rename from Manatee.Json/Path/ArrayParameters/Slice.cs rename to Manatee.Json/Path/Slice.cs index abca3a5..27ba41e 100644 --- a/Manatee.Json/Path/ArrayParameters/Slice.cs +++ b/Manatee.Json/Path/Slice.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace Manatee.Json.Path.ArrayParameters +namespace Manatee.Json.Path { public class Slice { @@ -26,7 +26,7 @@ public IEnumerable Find(JsonArray json, JsonValue root) { if (_index.HasValue) { - return json.Count > _index.Value + return json.Count < _index.Value ? Enumerable.Empty() : new[] {json[_index.Value]}; } From 9181c950f16908ccd9fa06dbb9f9c46d9fdf5816 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 14 Sep 2016 09:45:54 +1200 Subject: [PATCH 04/18] Added searching indexed array parser. Added Equals() to all the things! Added XML comments. Added expression array parsers. Still need expression parsing. Added missing search array extension methods. --- Manatee.Json.Tests/Path/ParsingTest.cs | 71 ++++++------- Manatee.Json/Manatee.Json.csproj | 5 + .../Path/ArrayParameters/FilterExpressionQuery.cs | 17 ++- .../Path/ArrayParameters/IndexExpressionQuery.cs | 19 +++- Manatee.Json/Path/ArrayParameters/SliceQuery.cs | 17 ++- Manatee.Json/Path/ArrayParameters/WildCardQuery.cs | 17 ++- .../Parsing/IJsonPathExpressionParser.cs | 8 ++ .../Parsing/JsonPathExpressionParser.cs | 39 +++++++ Manatee.Json/Path/JsonPath.cs | 26 ++++- Manatee.Json/Path/JsonPathWith.cs | 35 +++++++ Manatee.Json/Path/Operators/ArrayOperator.cs | 17 ++- Manatee.Json/Path/Operators/IndexOfOperator.cs | 17 ++- Manatee.Json/Path/Operators/LengthOperator.cs | 18 +++- Manatee.Json/Path/Operators/NameOperator.cs | 17 ++- Manatee.Json/Path/Operators/SearchOperator.cs | 18 +++- Manatee.Json/Path/Operators/WildCardOperator.cs | 24 ++++- .../Path/Parsing/ExpressionFilterParser.cs | 48 +++++++++ Manatee.Json/Path/Parsing/ExpressionIndexParser.cs | 48 +++++++++ Manatee.Json/Path/Parsing/IJsonPathParser.cs | 24 ++++- Manatee.Json/Path/Parsing/IndexedArrayParser.cs | 99 +++++------------- Manatee.Json/Path/Parsing/JsonPathParser.cs | 24 ++++- Manatee.Json/Path/Parsing/ObjectParser.cs | 24 ++++- Manatee.Json/Path/Parsing/PathParsingExtensions.cs | 116 ++++++++++++++++++++- .../Path/Parsing/SearchIndexedArrayParser.cs | 49 +++++++++ Manatee.Json/Path/Parsing/SearchParser.cs | 25 ++++- Manatee.Json/Path/Parsing/StartParser.cs | 23 +++- Manatee.Json/Path/Parsing/WildcardArrayParser.cs | 24 ++++- .../Path/SearchParameters/ArraySearchParameter.cs | 17 ++- .../Path/SearchParameters/LengthSearchParameter.cs | 16 ++- .../Path/SearchParameters/NameSearchParameter.cs | 17 ++- .../SearchParameters/WildCardSearchParameter.cs | 16 ++- Manatee.Json/Path/Slice.cs | 110 ++++++++++++++++--- .../Schema/JsonSchemaPropertyDefinition.cs | 31 ++---- 33 files changed, 895 insertions(+), 181 deletions(-) create mode 100644 Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs create mode 100644 Manatee.Json/Path/Parsing/ExpressionFilterParser.cs create mode 100644 Manatee.Json/Path/Parsing/ExpressionIndexParser.cs create mode 100644 Manatee.Json/Path/Parsing/SearchIndexedArrayParser.cs diff --git a/Manatee.Json.Tests/Path/ParsingTest.cs b/Manatee.Json.Tests/Path/ParsingTest.cs index 8b17645..255e8fe 100644 --- a/Manatee.Json.Tests/Path/ParsingTest.cs +++ b/Manatee.Json.Tests/Path/ParsingTest.cs @@ -15,8 +15,7 @@ public void SingleNamedObject() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -27,8 +26,7 @@ public void SingleQuotedNamedObject() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -39,8 +37,7 @@ public void DoubleQuotedNamedObject() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -51,8 +48,7 @@ public void SingleWildcardObject() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -63,8 +59,7 @@ public void NamedObjectWithWildcardObject() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -75,8 +70,7 @@ public void WildcardObjectWithNamedObject() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] public void SingleNamedSearch() @@ -86,8 +80,7 @@ public void SingleNamedSearch() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -98,8 +91,7 @@ public void SingleQuotedNamedSearch() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -110,8 +102,7 @@ public void DoubleQuotedNamedSearch() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -122,8 +113,7 @@ public void SingleWildcardSearch() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -134,8 +124,7 @@ public void NamedObjectWithWildcardSearch() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -146,8 +135,7 @@ public void WildcardObjectWithNamedSearch() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -158,8 +146,7 @@ public void SingleIndexedArray() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -170,8 +157,7 @@ public void SingleSlicedArray() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -182,8 +168,7 @@ public void SteppedSlicedArray() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -194,8 +179,7 @@ public void IndexedSlicedArray() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -206,8 +190,7 @@ public void SlicedIndexedArray() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -218,8 +201,7 @@ public void MultiSlicedArray() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -230,8 +212,7 @@ public void MultiIndexedArray() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); } [TestMethod] @@ -260,8 +241,18 @@ public void WildcardArray() var actual = JsonPath.Parse(text); - Assert.AreEqual(expected.ToString(), actual.ToString()); - //Assert.AreEqual(expected, actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SearchIndexedArray() + { + var text = "$..[1]"; + var expected = JsonPathWith.SearchArray(1); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); } } } diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index d77d8cf..52790b0 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -233,6 +233,11 @@ + + + + + diff --git a/Manatee.Json/Path/ArrayParameters/FilterExpressionQuery.cs b/Manatee.Json/Path/ArrayParameters/FilterExpressionQuery.cs index 37da1ca..554b04f 100644 --- a/Manatee.Json/Path/ArrayParameters/FilterExpressionQuery.cs +++ b/Manatee.Json/Path/ArrayParameters/FilterExpressionQuery.cs @@ -21,13 +21,14 @@ ***************************************************************************************/ +using System; using System.Collections.Generic; using System.Linq; using Manatee.Json.Path.Expressions; namespace Manatee.Json.Path.ArrayParameters { - internal class FilterExpressionQuery : IJsonPathArrayQuery + internal class FilterExpressionQuery : IJsonPathArrayQuery, IEquatable { private readonly Expression _expression; @@ -44,5 +45,19 @@ public override string ToString() { return $"?({_expression})"; } + public bool Equals(FilterExpressionQuery other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(_expression, other._expression); + } + public override bool Equals(object obj) + { + return Equals(obj as FilterExpressionQuery); + } + public override int GetHashCode() + { + return _expression?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/ArrayParameters/IndexExpressionQuery.cs b/Manatee.Json/Path/ArrayParameters/IndexExpressionQuery.cs index 592abaf..9ac1316 100644 --- a/Manatee.Json/Path/ArrayParameters/IndexExpressionQuery.cs +++ b/Manatee.Json/Path/ArrayParameters/IndexExpressionQuery.cs @@ -20,13 +20,14 @@ Purpose: Provides expression-based indexing for array queries. ***************************************************************************************/ +using System; using System.Collections.Generic; using System.Linq; using Manatee.Json.Path.Expressions; namespace Manatee.Json.Path.ArrayParameters { - internal class IndexExpressionQuery : IJsonPathArrayQuery + internal class IndexExpressionQuery : IJsonPathArrayQuery, IEquatable { private readonly Expression _expression; @@ -42,7 +43,21 @@ public IEnumerable Find(JsonArray json, JsonValue root) } public override string ToString() { - return string.Format("({0})", _expression); + return $"({_expression})"; + } + public bool Equals(IndexExpressionQuery other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(_expression, other._expression); + } + public override bool Equals(object obj) + { + return Equals(obj as IndexExpressionQuery); + } + public override int GetHashCode() + { + return _expression?.GetHashCode() ?? 0; } } } \ No newline at end of file diff --git a/Manatee.Json/Path/ArrayParameters/SliceQuery.cs b/Manatee.Json/Path/ArrayParameters/SliceQuery.cs index 75acbf2..01ed9a4 100644 --- a/Manatee.Json/Path/ArrayParameters/SliceQuery.cs +++ b/Manatee.Json/Path/ArrayParameters/SliceQuery.cs @@ -20,13 +20,14 @@ Purpose: Provides array-slice-syntax queries for arrays. ***************************************************************************************/ +using System; using Manatee.Json.Internal; using System.Collections.Generic; using System.Linq; namespace Manatee.Json.Path.ArrayParameters { - internal class SliceQuery : IJsonPathArrayQuery + internal class SliceQuery : IJsonPathArrayQuery, IEquatable { private readonly IEnumerable _slices; @@ -44,5 +45,19 @@ public override string ToString() { return _slices.Join(","); } + public bool Equals(SliceQuery other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return _slices.ContentsEqual(other._slices); + } + public override bool Equals(object obj) + { + return Equals(obj as SliceQuery); + } + public override int GetHashCode() + { + return _slices?.GetCollectionHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/ArrayParameters/WildCardQuery.cs b/Manatee.Json/Path/ArrayParameters/WildCardQuery.cs index da3220a..fc0fe3d 100644 --- a/Manatee.Json/Path/ArrayParameters/WildCardQuery.cs +++ b/Manatee.Json/Path/ArrayParameters/WildCardQuery.cs @@ -20,11 +20,12 @@ Purpose: Provides a wildcard for array queries. ***************************************************************************************/ +using System; using System.Collections.Generic; namespace Manatee.Json.Path.ArrayParameters { - internal class WildCardQuery : IJsonPathArrayQuery + internal class WildCardQuery : IJsonPathArrayQuery, IEquatable { public static WildCardQuery Instance { get; } @@ -32,6 +33,7 @@ static WildCardQuery() { Instance = new WildCardQuery(); } + private WildCardQuery() {} public IEnumerable Find(JsonArray json, JsonValue root) { @@ -41,5 +43,18 @@ public override string ToString() { return "*"; } + public bool Equals(WildCardQuery other) + { + return !ReferenceEquals(null, other); + } + public override bool Equals(object obj) + { + return Equals(obj as WildCardQuery); + } + public override int GetHashCode() + { + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + return base.GetHashCode(); + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs new file mode 100644 index 0000000..3f30640 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs @@ -0,0 +1,8 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal interface IJsonPathExpressionParser + { + bool Handles(char input); + string TryParse(string source, ref int index, ref Expression path); + } +} diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs new file mode 100644 index 0000000..8e15747 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Manatee.Json.Internal; + +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal static class JsonPathExpressionParser + { + private static readonly List Parsers; + + static JsonPathExpressionParser() + { + Parsers = typeof(JsonPathExpressionParser).Assembly.GetTypes() + .Where(t => typeof(IJsonPathExpressionParser).IsAssignableFrom(t) && t.IsClass) + .Select(Activator.CreateInstance) + .Cast() + .ToList(); + } + + public static string Parse(string source, ref int index, out Expression expr) + { + var length = source.Length; + expr = null; + while (index < length) + { + char c; + var errorMessage = source.SkipWhiteSpace(ref index, length, out c); + if (errorMessage != null) return errorMessage; + var i = index; + var parser = Parsers.FirstOrDefault(p => p.Handles(source[i])); + if (parser == null) return "Unrecognized JSON Path Expression element."; + errorMessage = parser.TryParse(source, ref index, ref expr); + if (errorMessage != null) return errorMessage; + } + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/JsonPath.cs b/Manatee.Json/Path/JsonPath.cs index b6b2b97..6f9fac8 100644 --- a/Manatee.Json/Path/JsonPath.cs +++ b/Manatee.Json/Path/JsonPath.cs @@ -32,7 +32,7 @@ namespace Manatee.Json.Path /// /// Provides primary functionality for JSON Path objects. /// - public class JsonPath + public class JsonPath : IEquatable { internal List Operators { get; } = new List(); @@ -70,6 +70,30 @@ public override string ToString() { return $"${GetRawString()}"; } + /// Indicates whether the current object is equal to another object of the same type. + /// true if the current object is equal to the parameter; otherwise, false. + /// An object to compare with this object. + public bool Equals(JsonPath other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Operators.SequenceEqual(other.Operators); + } + /// Determines whether the specified is equal to the current . + /// true if the specified is equal to the current ; otherwise, false. + /// The object to compare with the current object. + /// 2 + public override bool Equals(object obj) + { + return Equals(obj as JsonPath); + } + /// Serves as a hash function for a particular type. + /// A hash code for the current . + /// 2 + public override int GetHashCode() + { + return Operators?.GetCollectionHashCode() ?? 0; + } internal string GetRawString() { diff --git a/Manatee.Json/Path/JsonPathWith.cs b/Manatee.Json/Path/JsonPathWith.cs index fe0c11e..968b107 100644 --- a/Manatee.Json/Path/JsonPathWith.cs +++ b/Manatee.Json/Path/JsonPathWith.cs @@ -293,6 +293,41 @@ public static JsonPath SearchLength(this JsonPath path) return newPath; } /// + /// Appends a by specifying a series of array indicies. + /// + /// The to extend. + /// The indices and slices of the s to include. + /// The new . + public static JsonPath SearchArray(this JsonPath path, params Slice[] slices) + { + path.Operators.Add(new SearchOperator(slices.Any() + ? new ArraySearchParameter(new SliceQuery(slices)) + : new ArraySearchParameter(WildCardQuery.Instance))); + return path; + } + /// + /// Appends a by specifying an expression which evaluates to the index to include. + /// + /// The to extend. + /// The expression. + /// The new . + public static JsonPath SearchArray(this JsonPath path, Expression> expression) + { + path.Operators.Add(new SearchOperator(new ArraySearchParameter(new IndexExpressionQuery(ExpressionTranslator.Translate(expression))))); + return path; + } + /// + /// Appends a by specifying a predicate expression which filters the values. + /// + /// The to extend. + /// The predicate expression + /// The new . + public static JsonPath SearchArray(this JsonPath path, Expression> expression) + { + path.Operators.Add(new SearchOperator(new ArraySearchParameter(new FilterExpressionQuery(ExpressionTranslator.Translate(expression))))); + return path; + } + /// /// Appends a by including all array values. /// /// The to extend. diff --git a/Manatee.Json/Path/Operators/ArrayOperator.cs b/Manatee.Json/Path/Operators/ArrayOperator.cs index 5551afc..be8a149 100644 --- a/Manatee.Json/Path/Operators/ArrayOperator.cs +++ b/Manatee.Json/Path/Operators/ArrayOperator.cs @@ -20,12 +20,13 @@ Purpose: Indicates that the current value should be an array. ***************************************************************************************/ +using System; using System.Linq; using Manatee.Json.Internal; namespace Manatee.Json.Path.Operators { - internal class ArrayOperator : IJsonPathOperator + internal class ArrayOperator : IJsonPathOperator, IEquatable { private readonly IJsonPathArrayQuery _query; @@ -48,5 +49,19 @@ public override string ToString() { return $"[{_query}]"; } + public bool Equals(ArrayOperator other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(_query, other._query); + } + public override bool Equals(object obj) + { + return Equals(obj as ArrayOperator); + } + public override int GetHashCode() + { + return _query?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Operators/IndexOfOperator.cs b/Manatee.Json/Path/Operators/IndexOfOperator.cs index d2a3021..c3c46c9 100644 --- a/Manatee.Json/Path/Operators/IndexOfOperator.cs +++ b/Manatee.Json/Path/Operators/IndexOfOperator.cs @@ -21,12 +21,13 @@ returns the number of items. ***************************************************************************************/ +using System; using System.Linq; using Manatee.Json.Path.Expressions; namespace Manatee.Json.Path.Operators { - internal class IndexOfOperator : IJsonPathOperator + internal class IndexOfOperator : IJsonPathOperator, IEquatable { public Expression Parameter { get; } @@ -46,5 +47,19 @@ public override string ToString() { return $".indexOf({Parameter})"; } + public bool Equals(IndexOfOperator other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Parameter, other.Parameter); + } + public override bool Equals(object obj) + { + return Equals(obj as IndexOfOperator); + } + public override int GetHashCode() + { + return Parameter?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Operators/LengthOperator.cs b/Manatee.Json/Path/Operators/LengthOperator.cs index 2bbd470..dc3d3ae 100644 --- a/Manatee.Json/Path/Operators/LengthOperator.cs +++ b/Manatee.Json/Path/Operators/LengthOperator.cs @@ -21,11 +21,12 @@ returns the number of items. ***************************************************************************************/ +using System; using System.Linq; namespace Manatee.Json.Path.Operators { - internal class LengthOperator : IJsonPathOperator + internal class LengthOperator : IJsonPathOperator, IEquatable { public static LengthOperator Instance { get; } = new LengthOperator(); @@ -37,7 +38,20 @@ public JsonArray Evaluate(JsonArray json, JsonValue root) } public override string ToString() { - return string.Format(".length"); + return ".length"; + } + public bool Equals(LengthOperator other) + { + return !ReferenceEquals(null, other); + } + public override bool Equals(object obj) + { + return Equals(obj as LengthOperator); + } + public override int GetHashCode() + { + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + return base.GetHashCode(); } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Operators/NameOperator.cs b/Manatee.Json/Path/Operators/NameOperator.cs index 0afb069..e3e800a 100644 --- a/Manatee.Json/Path/Operators/NameOperator.cs +++ b/Manatee.Json/Path/Operators/NameOperator.cs @@ -21,12 +21,13 @@ a named property should be retrieved. ***************************************************************************************/ +using System; using System.Linq; using Manatee.Json.Internal; namespace Manatee.Json.Path.Operators { - internal class NameOperator : IJsonPathOperator + internal class NameOperator : IJsonPathOperator, IEquatable { public string Name { get; } @@ -47,5 +48,19 @@ public override string ToString() ? $".'{Name}'" : $".{Name}"; } + public bool Equals(NameOperator other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Name, other.Name); + } + public override bool Equals(object obj) + { + return Equals(obj as NameOperator); + } + public override int GetHashCode() + { + return Name?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Operators/SearchOperator.cs b/Manatee.Json/Path/Operators/SearchOperator.cs index 1073348..78d92b7 100644 --- a/Manatee.Json/Path/Operators/SearchOperator.cs +++ b/Manatee.Json/Path/Operators/SearchOperator.cs @@ -20,9 +20,11 @@ Purpose: Indicates that the path should search for a named property. ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Operators { - internal class SearchOperator : IJsonPathOperator + internal class SearchOperator : IJsonPathOperator, IEquatable { private readonly IJsonPathSearchParameter _parameter; @@ -39,5 +41,19 @@ public override string ToString() { return $"..{_parameter}"; } + public bool Equals(SearchOperator other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(_parameter, other._parameter); + } + public override bool Equals(object obj) + { + return Equals(obj as SearchOperator); + } + public override int GetHashCode() + { + return _parameter?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Operators/WildCardOperator.cs b/Manatee.Json/Path/Operators/WildCardOperator.cs index 49d1868..2650e50 100644 --- a/Manatee.Json/Path/Operators/WildCardOperator.cs +++ b/Manatee.Json/Path/Operators/WildCardOperator.cs @@ -21,14 +21,21 @@ current level. ***************************************************************************************/ +using System; using System.Linq; using Manatee.Json.Internal; namespace Manatee.Json.Path.Operators { - internal class WildCardOperator : IJsonPathOperator + internal class WildCardOperator : IJsonPathOperator, IEquatable { - public static WildCardOperator Instance { get; } = new WildCardOperator(); + public static WildCardOperator Instance { get; } + + static WildCardOperator() + { + Instance = new WildCardOperator(); + } + private WildCardOperator() {} public JsonArray Evaluate(JsonArray json, JsonValue root) { @@ -49,5 +56,18 @@ public override string ToString() { return ".*"; } + public bool Equals(WildCardOperator other) + { + return !ReferenceEquals(null, other); + } + public override bool Equals(object obj) + { + return Equals(obj as WildCardOperator); + } + public override int GetHashCode() + { + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + return base.GetHashCode(); + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Parsing/ExpressionFilterParser.cs b/Manatee.Json/Path/Parsing/ExpressionFilterParser.cs new file mode 100644 index 0000000..65e37c5 --- /dev/null +++ b/Manatee.Json/Path/Parsing/ExpressionFilterParser.cs @@ -0,0 +1,48 @@ +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ExpressionFilterParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: ExpressionFilterParser + Purpose: Parses JSON Path array components with filter expresssions. + +***************************************************************************************/ +using Manatee.Json.Path.ArrayParameters; +using Manatee.Json.Path.Expressions; +using Manatee.Json.Path.Expressions.Parsing; +using Manatee.Json.Path.Operators; + +namespace Manatee.Json.Path.Parsing +{ + internal class ExpressionFilterParser : IJsonPathParser + { + public bool Handles(string input) + { + return input.StartsWith("[("); + } + public string TryParse(string source, ref int index, ref JsonPath path) + { + Expression expression; + var error = JsonPathExpressionParser.Parse(source, ref index, out expression); + + if (error != null) + return error; + + path.Operators.Add(new ArrayOperator(new FilterExpressionQuery(expression))); + return null; + } + } +} diff --git a/Manatee.Json/Path/Parsing/ExpressionIndexParser.cs b/Manatee.Json/Path/Parsing/ExpressionIndexParser.cs new file mode 100644 index 0000000..621d43a --- /dev/null +++ b/Manatee.Json/Path/Parsing/ExpressionIndexParser.cs @@ -0,0 +1,48 @@ +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ExpressionIndexParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: ExpressionIndexParser + Purpose: Parses JSON Path array components with index expresssions. + +***************************************************************************************/ +using Manatee.Json.Path.ArrayParameters; +using Manatee.Json.Path.Expressions; +using Manatee.Json.Path.Expressions.Parsing; +using Manatee.Json.Path.Operators; + +namespace Manatee.Json.Path.Parsing +{ + internal class ExpressionIndexParser : IJsonPathParser + { + public bool Handles(string input) + { + return input.StartsWith("[?("); + } + public string TryParse(string source, ref int index, ref JsonPath path) + { + Expression expression; + var error = JsonPathExpressionParser.Parse(source, ref index, out expression); + + if (error != null) + return error; + + path.Operators.Add(new ArrayOperator(new IndexExpressionQuery(expression))); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Parsing/IJsonPathParser.cs b/Manatee.Json/Path/Parsing/IJsonPathParser.cs index ef8d555..d34e7e0 100644 --- a/Manatee.Json/Path/Parsing/IJsonPathParser.cs +++ b/Manatee.Json/Path/Parsing/IJsonPathParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IJsonPathParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: IJsonPathParser + Purpose: Defines methods used to parse JSON Path components. + +***************************************************************************************/ +namespace Manatee.Json.Path.Parsing { internal interface IJsonPathParser { diff --git a/Manatee.Json/Path/Parsing/IndexedArrayParser.cs b/Manatee.Json/Path/Parsing/IndexedArrayParser.cs index d935bfe..17045bc 100644 --- a/Manatee.Json/Path/Parsing/IndexedArrayParser.cs +++ b/Manatee.Json/Path/Parsing/IndexedArrayParser.cs @@ -1,4 +1,26 @@ -using System.Collections.Generic; +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IndexedArrayParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: IndexedArrayParser + Purpose: Parses JSON Path array components with discrete indices. + +***************************************************************************************/ +using System.Collections.Generic; using System.Linq; using Manatee.Json.Internal; @@ -9,89 +31,18 @@ internal class IndexedArrayParser : IJsonPathParser public bool Handles(string input) { return input.Length > 1 && input[0] == '[' && (char.IsDigit(input[1]) || input[1].In('-', ':')); - //return input.Length > 1 && input[0] == '[' && (char.IsDigit(input[1]) || input[1].In('"', '\'')); } public string TryParse(string source, ref int index, ref JsonPath path) { if (path == null) return "Start token not found."; - string error; - Slice lastSlice; - var slices = new List(); - do - { - index++; - error = GetSlice(source, ref index, out lastSlice); - if (lastSlice != null) - slices.Add(lastSlice); - } while (error == null && lastSlice != null); + IList slices; + var error = source.GetSlices(ref index, out slices); if (error != null) return error; - index++; - if (!slices.Any()) - return "Index required inside '[]'"; path = path.Array(slices.ToArray()); return null; } - - private static string GetSlice(string source, ref int index, out Slice slice) - { - slice = null; - if (source[index - 1] == ']') return null; - - int? n1, n2, n3; - - var error = GetNumber(source, ref index, out n1); - if (error != null) return error; - if (n1.HasValue && source[index].In(',', ']')) - { - slice = new Slice(n1.Value); - return null; - } - if (source[index] != ':') - return "Expected ':', ',', or ']'."; - - index++; - error = GetNumber(source, ref index, out n2); - if (error != null) return error; - if (source[index].In(',', ']')) - { - slice = new Slice(n1, n2); - return null; - } - if (source[index] != ':') - return "Expected ':', ',', or ']'."; - - index++; - error = GetNumber(source, ref index, out n3); - if (error != null) return error; - if (source[index].In(',', ']')) - { - slice = new Slice(n1, n2, n3); - return null; - } - return "Expected ',' or ']'."; - } - - private static string GetNumber(string source, ref int index, out int? number) - { - int value; - var text = new string(source.Substring(index).TakeWhile(c => char.IsDigit(c) || c == '-').ToArray()); - if (text.Length == 0 && source[index].In(':',',',']')) - { - number = null; - return null; - } - if (!int.TryParse(text, out value)) - { - number = null; - return "Expected number."; - } - - index += text.Length; - number = value; - return null; - } } } diff --git a/Manatee.Json/Path/Parsing/JsonPathParser.cs b/Manatee.Json/Path/Parsing/JsonPathParser.cs index 6da0387..25b184f 100644 --- a/Manatee.Json/Path/Parsing/JsonPathParser.cs +++ b/Manatee.Json/Path/Parsing/JsonPathParser.cs @@ -1,4 +1,26 @@ -using System; +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: JsonPathParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: JsonPathParser + Purpose: Parses JSON Path. + +***************************************************************************************/ +using System; using System.Collections.Generic; using System.Linq; using Manatee.Json.Internal; diff --git a/Manatee.Json/Path/Parsing/ObjectParser.cs b/Manatee.Json/Path/Parsing/ObjectParser.cs index 6225f8c..3bdb3ea 100644 --- a/Manatee.Json/Path/Parsing/ObjectParser.cs +++ b/Manatee.Json/Path/Parsing/ObjectParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ObjectParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: ObjectParser + Purpose: Parses JSON Path object components. + +***************************************************************************************/ +namespace Manatee.Json.Path.Parsing { internal class ObjectParser : IJsonPathParser { diff --git a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs index 6ee3be3..e4d02cf 100644 --- a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs +++ b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs @@ -1,17 +1,43 @@ -using System; +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: PathParsingExtensions.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: PathParsingExtensions + Purpose: Extension methods used while parsing JSON Path. + +***************************************************************************************/ +using System; +using System.Collections.Generic; +using System.Linq; using Manatee.Json.Internal; namespace Manatee.Json.Path.Parsing { internal static class PathParsingExtensions { + #region GetKey + private static readonly int[] FibSequence = { 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657 }; public static string GetKey(this string source, ref int index, out string key) { return source[index].In('"', '\'') - ? GetQuotedKey(source, ref index, out key) - : GetBasicKey(source, ref index, out key); + ? GetQuotedKey(source, ref index, out key) + : GetBasicKey(source, ref index, out key); } private static string GetBasicKey(string source, ref int index, out string key) @@ -99,5 +125,89 @@ private static string GetQuotedKey(string source, ref int index, out string key) key = escaped; return errorMessage; } + + #endregion + + #region GetSlice + + public static string GetSlices(this string source, ref int index, out IList slices) + { + string error; + Slice lastSlice; + slices = new List(); + do + { + index++; + error = source.GetSlice(ref index, out lastSlice); + if (lastSlice != null) + slices.Add(lastSlice); + } while (error == null && lastSlice != null); + if (error != null) return error; + + index++; + if (!slices.Any()) + return "Index required inside '[]'"; + return null; + } + + private static string GetSlice(this string source, ref int index, out Slice slice) + { + slice = null; + if (source[index - 1] == ']') return null; + + int? n1, n2, n3; + + var error = GetNumber(source, ref index, out n1); + if (error != null) return error; + if (n1.HasValue && source[index].In(',', ']')) + { + slice = new Slice(n1.Value); + return null; + } + if (source[index] != ':') + return "Expected ':', ',', or ']'."; + + index++; + error = GetNumber(source, ref index, out n2); + if (error != null) return error; + if (source[index].In(',', ']')) + { + slice = new Slice(n1, n2); + return null; + } + if (source[index] != ':') + return "Expected ':', ',', or ']'."; + + index++; + error = GetNumber(source, ref index, out n3); + if (error != null) return error; + if (source[index].In(',', ']')) + { + slice = new Slice(n1, n2, n3); + return null; + } + return "Expected ',' or ']'."; + } + private static string GetNumber(string source, ref int index, out int? number) + { + int value; + var text = new string(source.Substring(index).TakeWhile(c => char.IsDigit(c) || c == '-').ToArray()); + if (text.Length == 0 && source[index].In(':', ',', ']')) + { + number = null; + return null; + } + if (!int.TryParse(text, out value)) + { + number = null; + return "Expected number."; + } + + index += text.Length; + number = value; + return null; + } + + #endregion } } diff --git a/Manatee.Json/Path/Parsing/SearchIndexedArrayParser.cs b/Manatee.Json/Path/Parsing/SearchIndexedArrayParser.cs new file mode 100644 index 0000000..0fc1891 --- /dev/null +++ b/Manatee.Json/Path/Parsing/SearchIndexedArrayParser.cs @@ -0,0 +1,49 @@ +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IndexedArrayParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: IndexedArrayParser + Purpose: Parses JSON Path array components with discrete indices. + +***************************************************************************************/ +using System.Collections.Generic; +using System.Linq; +using Manatee.Json.Internal; + +namespace Manatee.Json.Path.Parsing +{ + internal class SearchIndexedArrayParser : IJsonPathParser + { + public bool Handles(string input) + { + return input.Length > 4 && input.StartsWith("..[") && (char.IsDigit(input[3]) || input[3].In('-', ':')); + } + public string TryParse(string source, ref int index, ref JsonPath path) + { + if (path == null) + return "Start token not found."; + + index += 2; + IList slices; + string error = source.GetSlices(ref index, out slices); + if (error != null) return error; + + path = path.SearchArray(slices.ToArray()); + return null; + } + } +} diff --git a/Manatee.Json/Path/Parsing/SearchParser.cs b/Manatee.Json/Path/Parsing/SearchParser.cs index 2c9d057..8e1d409 100644 --- a/Manatee.Json/Path/Parsing/SearchParser.cs +++ b/Manatee.Json/Path/Parsing/SearchParser.cs @@ -1,13 +1,32 @@ -using System; -using System.IO; +/*************************************************************************************** + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: SearchParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: SearchParser + Purpose: Parses JSON Path named and wildcard search components. + +***************************************************************************************/ namespace Manatee.Json.Path.Parsing { internal class SearchParser : IJsonPathParser { public bool Handles(string input) { - return input.StartsWith(".."); + return input.StartsWith("..") && !input.StartsWith("..["); } public string TryParse(string source, ref int index, ref JsonPath path) { diff --git a/Manatee.Json/Path/Parsing/StartParser.cs b/Manatee.Json/Path/Parsing/StartParser.cs index 0240600..831b471 100644 --- a/Manatee.Json/Path/Parsing/StartParser.cs +++ b/Manatee.Json/Path/Parsing/StartParser.cs @@ -1,6 +1,25 @@ -using System; -using System.IO; +/*************************************************************************************** + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: StartParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: StartParser + Purpose: Parses JSON Path start symbols. + +***************************************************************************************/ namespace Manatee.Json.Path.Parsing { internal class StartParser : IJsonPathParser diff --git a/Manatee.Json/Path/Parsing/WildcardArrayParser.cs b/Manatee.Json/Path/Parsing/WildcardArrayParser.cs index 1fcaf3c..b124fc9 100644 --- a/Manatee.Json/Path/Parsing/WildcardArrayParser.cs +++ b/Manatee.Json/Path/Parsing/WildcardArrayParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: WildcardArrayParser.cs + Namespace: Manatee.Json.Path.Parsing + Class Name: WildcardArrayParser + Purpose: Parses JSON Path array components with wildcard indexers. + +***************************************************************************************/ +namespace Manatee.Json.Path.Parsing { internal class WildcardArrayParser : IJsonPathParser { diff --git a/Manatee.Json/Path/SearchParameters/ArraySearchParameter.cs b/Manatee.Json/Path/SearchParameters/ArraySearchParameter.cs index de6fd91..5be3fa4 100644 --- a/Manatee.Json/Path/SearchParameters/ArraySearchParameter.cs +++ b/Manatee.Json/Path/SearchParameters/ArraySearchParameter.cs @@ -20,12 +20,13 @@ Purpose: Indicates that a search should look for an array parameter. ***************************************************************************************/ +using System; using System.Collections.Generic; using System.Linq; namespace Manatee.Json.Path.SearchParameters { - internal class ArraySearchParameter : IJsonPathSearchParameter + internal class ArraySearchParameter : IJsonPathSearchParameter, IEquatable { private readonly IJsonPathArrayQuery _query; @@ -56,5 +57,19 @@ public override string ToString() { return $"[{_query}]"; } + public bool Equals(ArraySearchParameter other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(_query, other._query); + } + public override bool Equals(object obj) + { + return Equals(obj as ArraySearchParameter); + } + public override int GetHashCode() + { + return _query?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/SearchParameters/LengthSearchParameter.cs b/Manatee.Json/Path/SearchParameters/LengthSearchParameter.cs index 15f2160..55cc106 100644 --- a/Manatee.Json/Path/SearchParameters/LengthSearchParameter.cs +++ b/Manatee.Json/Path/SearchParameters/LengthSearchParameter.cs @@ -21,12 +21,13 @@ object and array values. ***************************************************************************************/ +using System; using System.Collections.Generic; using System.Linq; namespace Manatee.Json.Path.SearchParameters { - internal class LengthSearchParameter : IJsonPathSearchParameter + internal class LengthSearchParameter : IJsonPathSearchParameter, IEquatable { public static LengthSearchParameter Instance { get; } = new LengthSearchParameter(); @@ -51,5 +52,18 @@ public override string ToString() { return "length"; } + public bool Equals(LengthSearchParameter other) + { + return !ReferenceEquals(null, other); + } + public override bool Equals(object obj) + { + return Equals(obj as LengthSearchParameter); + } + public override int GetHashCode() + { + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + return base.GetHashCode(); + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/SearchParameters/NameSearchParameter.cs b/Manatee.Json/Path/SearchParameters/NameSearchParameter.cs index 5681615..31f9042 100644 --- a/Manatee.Json/Path/SearchParameters/NameSearchParameter.cs +++ b/Manatee.Json/Path/SearchParameters/NameSearchParameter.cs @@ -20,12 +20,13 @@ Purpose: Indicates that a search should look for a named parameter. ***************************************************************************************/ +using System; using System.Collections.Generic; using System.Linq; namespace Manatee.Json.Path.SearchParameters { - internal class NameSearchParameter : IJsonPathSearchParameter + internal class NameSearchParameter : IJsonPathSearchParameter, IEquatable { private readonly string _name; @@ -57,5 +58,19 @@ public override string ToString() { return _name; } + public bool Equals(NameSearchParameter other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(_name, other._name); + } + public override bool Equals(object obj) + { + return Equals(obj as NameSearchParameter); + } + public override int GetHashCode() + { + return _name?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/SearchParameters/WildCardSearchParameter.cs b/Manatee.Json/Path/SearchParameters/WildCardSearchParameter.cs index ec7c016..d80383e 100644 --- a/Manatee.Json/Path/SearchParameters/WildCardSearchParameter.cs +++ b/Manatee.Json/Path/SearchParameters/WildCardSearchParameter.cs @@ -20,12 +20,13 @@ Purpose: Indicates that a search should return all values. ***************************************************************************************/ +using System; using System.Collections.Generic; using System.Linq; namespace Manatee.Json.Path.SearchParameters { - internal class WildCardSearchParameter : IJsonPathSearchParameter + internal class WildCardSearchParameter : IJsonPathSearchParameter, IEquatable { public static WildCardSearchParameter Instance { get; } = new WildCardSearchParameter(); @@ -50,5 +51,18 @@ public override string ToString() { return "*"; } + public bool Equals(WildCardSearchParameter other) + { + return !ReferenceEquals(null, other); + } + public override bool Equals(object obj) + { + return Equals(obj as WildCardSearchParameter); + } + public override int GetHashCode() + { + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + return base.GetHashCode(); + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Slice.cs b/Manatee.Json/Path/Slice.cs index 27ba41e..aa81f66 100644 --- a/Manatee.Json/Path/Slice.cs +++ b/Manatee.Json/Path/Slice.cs @@ -1,20 +1,55 @@ +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: Slice.cs + Namespace: Manatee.Json.Path + Class Name: Slice + Purpose: Defines an index in a JSON Path array. + +***************************************************************************************/ using System; using System.Collections.Generic; using System.Linq; namespace Manatee.Json.Path { - public class Slice + /// + /// Defines an index in a JSON Path array. + /// + public class Slice : IEquatable { private int? _index; private int? _start; private int? _end; private int? _step; + /// + /// Creates a new instance of the class. + /// + /// A single index. public Slice(int index) { _index = index; } + /// + /// Creates a new instance of the class. + /// + /// The start index of the slice. + /// The end index of the slice. + /// Optional. The increment between each selected index between and . public Slice(int? start, int? end, int? step = null) { _start = start; @@ -22,13 +57,67 @@ public Slice(int? start, int? end, int? step = null) _step = step; } - public IEnumerable Find(JsonArray json, JsonValue root) + /// Returns a string that represents the current object. + /// A string that represents the current object. + /// 2 + public override string ToString() + { + return _index.HasValue + ? _index.ToString() + : _step.HasValue + ? $"{(_start.HasValue ? _start.ToString() : string.Empty)}:{(_end.HasValue ? _end.ToString() : string.Empty)}:{_step}" + : $"{(_start.HasValue ? _start.ToString() : string.Empty)}:{(_end.HasValue ? _end.ToString() : string.Empty)}"; + } + /// Indicates whether the current object is equal to another object of the same type. + /// true if the current object is equal to the parameter; otherwise, false. + /// An object to compare with this object. + public bool Equals(Slice other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return _index == other._index && + _start == other._start && + _end == other._end && + _step == other._step; + } + /// Determines whether the specified is equal to the current . + /// true if the specified is equal to the current ; otherwise, false. + /// The object to compare with the current object. + /// 2 + public override bool Equals(object obj) + { + return Equals(obj as Slice); + } + /// Serves as a hash function for a particular type. + /// A hash code for the current . + /// 2 + public override int GetHashCode() + { + unchecked + { + var hashCode = _index.GetHashCode(); + hashCode = (hashCode*397) ^ _start.GetHashCode(); + hashCode = (hashCode*397) ^ _end.GetHashCode(); + hashCode = (hashCode*397) ^ _step.GetHashCode(); + return hashCode; + } + } + /// + /// Implicit conversion from integer to a single-indexed . + /// + /// The index. + public static implicit operator Slice(int i) + { + return new Slice(i); + } + + internal IEnumerable Find(JsonArray json, JsonValue root) { if (_index.HasValue) { return json.Count < _index.Value - ? Enumerable.Empty() - : new[] {json[_index.Value]}; + ? Enumerable.Empty() + : new[] { json[_index.Value] }; } var start = ResolveIndex(_start ?? 0, json.Count); @@ -44,19 +133,6 @@ public IEnumerable Find(JsonArray json, JsonValue root) } return list; } - public override string ToString() - { - return _index.HasValue - ? _index.ToString() - : _step.HasValue - ? $"{(_start.HasValue ? _start.ToString() : string.Empty)}:{(_end.HasValue ? _end.ToString() : string.Empty)}:{_step}" - : $"{(_start.HasValue ? _start.ToString() : string.Empty)}:{(_end.HasValue ? _end.ToString() : string.Empty)}"; - } - - public static implicit operator Slice(int i) - { - return new Slice(i); - } private static int ResolveIndex(int index, int count) { diff --git a/Manatee.Json/Schema/JsonSchemaPropertyDefinition.cs b/Manatee.Json/Schema/JsonSchemaPropertyDefinition.cs index 4b43b57..699f650 100644 --- a/Manatee.Json/Schema/JsonSchemaPropertyDefinition.cs +++ b/Manatee.Json/Schema/JsonSchemaPropertyDefinition.cs @@ -83,17 +83,15 @@ public JsonValue ToJson(JsonSerializer serializer) return new JsonObject {{Name, Type.ToJson(serializer)}}; } + /// Returns a string that represents the current object. + /// A string that represents the current object. + /// 2 public override string ToString() { return ToJson(null).ToString(); } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// + /// Indicates whether the current object is equal to another object of the same type. + /// true if the current object is equal to the parameter; otherwise, false. /// An object to compare with this object. public bool Equals(JsonSchemaPropertyDefinition other) { @@ -101,13 +99,10 @@ public bool Equals(JsonSchemaPropertyDefinition other) Equals(Type, other.Type) && IsRequired == other.IsRequired; } - /// - /// Determines whether the specified is equal to the current . - /// - /// - /// true if the specified is equal to the current ; otherwise, false. - /// - /// The object to compare with the current object. 2 + /// Determines whether the specified is equal to the current . + /// true if the specified is equal to the current ; otherwise, false. + /// The object to compare with the current object. + /// 2 public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; @@ -115,12 +110,8 @@ public override bool Equals(object obj) if (obj.GetType() != GetType()) return false; return Equals((JsonSchemaPropertyDefinition) obj); } - /// - /// Serves as a hash function for a particular type. - /// - /// - /// A hash code for the current . - /// + /// Serves as a hash function for a particular type. + /// A hash code for the current . /// 2 public override int GetHashCode() { From 723915685b424486ea3041542ec39bdb3fe82bb5 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 14 Sep 2016 10:27:13 +1200 Subject: [PATCH 05/18] Added simple expression parsers. --- Manatee.Json.Tests/Path/GoessnerExamplesTest.cs | 4 +-- Manatee.Json/Manatee.Json.csproj | 13 +++++++++ Manatee.Json/Path/Expressions/Expression.cs | 18 ++++++------ .../Expressions/Parsing/AddExpressionParser.cs | 16 +++++++++++ .../Expressions/Parsing/AndExpressionParser.cs | 16 +++++++++++ .../Expressions/Parsing/DivideExpressionParser.cs | 16 +++++++++++ .../Parsing/ExponentExpressionParser.cs | 16 +++++++++++ .../Parsing/IJsonPathExpressionParser.cs | 4 +-- .../Expressions/Parsing/IsEqualExpressionParser.cs | 16 +++++++++++ .../Parsing/IsGreaterThanEqualExpressionParser.cs | 16 +++++++++++ .../Parsing/IsGreaterThanExpressionParser.cs | 16 +++++++++++ .../Parsing/IsLessThanEqualExpressionParser.cs | 16 +++++++++++ .../Parsing/IsLessThanExpressionParser.cs | 16 +++++++++++ .../Parsing/IsNotEqualExpressionParser.cs | 16 +++++++++++ .../Parsing/JsonPathExpressionParser.cs | 33 ++++++++++++++++++++-- .../Expressions/Parsing/ModuloExpressionParser.cs | 16 +++++++++++ .../Parsing/MultiplyExpressionParser.cs | 16 +++++++++++ .../Parsing/SubtractExpressionParser.cs | 16 +++++++++++ .../Translation/ExpressionTranslator.cs | 22 +++++++-------- 19 files changed, 275 insertions(+), 27 deletions(-) create mode 100644 Manatee.Json/Path/Expressions/Parsing/AddExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/AndExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/DivideExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/ExponentExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/IsEqualExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/IsGreaterThanEqualExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/IsGreaterThanExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/IsLessThanEqualExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/IsLessThanExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/IsNotEqualExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/ModuloExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/MultiplyExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs diff --git a/Manatee.Json.Tests/Path/GoessnerExamplesTest.cs b/Manatee.Json.Tests/Path/GoessnerExamplesTest.cs index 2fe2f0c..ccb75ee 100644 --- a/Manatee.Json.Tests/Path/GoessnerExamplesTest.cs +++ b/Manatee.Json.Tests/Path/GoessnerExamplesTest.cs @@ -332,7 +332,7 @@ public void GoessnerExample6BParsed() [TestMethod] public void GoessnerExample6BConstructed() { - var path = JsonPathWith.Search("book").ArraySlice(-1, null); + var path = JsonPathWith.Search("book").Array(new Slice(-1, null)); var expected = new JsonArray { new JsonObject @@ -426,7 +426,7 @@ public void GoessnerExample7BParsed() [TestMethod] public void GoessnerExample7BConstructed() { - var path = JsonPathWith.Search("book").ArraySlice(null, 2); + var path = JsonPathWith.Search("book").Array(new Slice(null, 2)); var expected = new JsonArray { new JsonObject diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index 52790b0..f6c42c2 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -233,7 +233,20 @@ + + + + + + + + + + + + + diff --git a/Manatee.Json/Path/Expressions/Expression.cs b/Manatee.Json/Path/Expressions/Expression.cs index ac225af..e3b98f2 100644 --- a/Manatee.Json/Path/Expressions/Expression.cs +++ b/Manatee.Json/Path/Expressions/Expression.cs @@ -21,18 +21,21 @@ ***************************************************************************************/ using System; -using System.Collections.Generic; namespace Manatee.Json.Path.Expressions { internal class Expression { - private List> _nodeList; - internal ExpressionTreeNode Root { get; set; } + private readonly ExpressionTreeNode _root; + + public Expression(ExpressionTreeNode root) + { + _root = root; + } public T Evaluate(TIn json, JsonValue root) { - var result = Root.Evaluate(json, root); + var result = _root.Evaluate(json, root); if (typeof (T) == typeof (bool) && result == null) return (T) (object) false; if (typeof (T) == typeof (bool) && result != null && !(result is bool)) @@ -56,12 +59,7 @@ public T Evaluate(TIn json, JsonValue root) } public override string ToString() { - return Root.ToString(); - } - - internal int Parse(string source, int i) - { - throw new NotImplementedException(); + return _root.ToString(); } } } diff --git a/Manatee.Json/Path/Expressions/Parsing/AddExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/AddExpressionParser.cs new file mode 100644 index 0000000..eabece4 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/AddExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class AddExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("+"); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new AddExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/AndExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/AndExpressionParser.cs new file mode 100644 index 0000000..a1a961b --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/AndExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class AndExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("&&"); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index += 2; + node = new AndExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/DivideExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/DivideExpressionParser.cs new file mode 100644 index 0000000..0a2b5af --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/DivideExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class DivideExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("/"); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new DivideExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/ExponentExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ExponentExpressionParser.cs new file mode 100644 index 0000000..68e9f52 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/ExponentExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class ExponentExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("^"); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new ExponentExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs index 3f30640..136c7b8 100644 --- a/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs @@ -2,7 +2,7 @@ { internal interface IJsonPathExpressionParser { - bool Handles(char input); - string TryParse(string source, ref int index, ref Expression path); + bool Handles(string input); + string TryParse(string source, ref int index, out ExpressionTreeNode node); } } diff --git a/Manatee.Json/Path/Expressions/Parsing/IsEqualExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsEqualExpressionParser.cs new file mode 100644 index 0000000..ca0c16a --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/IsEqualExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class IsEqualExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("=="); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index += 2; + node = new IsEqualExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanEqualExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanEqualExpressionParser.cs new file mode 100644 index 0000000..556d4ed --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanEqualExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class IsGreaterThanEqualExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith(">="); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index += 2; + node = new IsGreaterThanEqualExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanExpressionParser.cs new file mode 100644 index 0000000..49653f9 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class IsGreaterThanExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith(">") && !input.StartsWith(">="); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new IsGreaterThanExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/IsLessThanEqualExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsLessThanEqualExpressionParser.cs new file mode 100644 index 0000000..7740fa5 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/IsLessThanEqualExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class IsLessThanEqualExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("<="); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index += 2; + node = new IsLessThanEqualExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/IsLessThanExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsLessThanExpressionParser.cs new file mode 100644 index 0000000..ade45d9 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/IsLessThanExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class IsLessThanExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("<") && !input.StartsWith("<="); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new IsLessThanExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/IsNotEqualExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsNotEqualExpressionParser.cs new file mode 100644 index 0000000..fe7aae5 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/IsNotEqualExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class IsNotEqualExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("!="); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index += 2; + node = new IsNotEqualExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs index 8e15747..0dfa7dc 100644 --- a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -20,6 +20,7 @@ static JsonPathExpressionParser() public static string Parse(string source, ref int index, out Expression expr) { + var nodes = new List>(); var length = source.Length; expr = null; while (index < length) @@ -28,12 +29,40 @@ static JsonPathExpressionParser() var errorMessage = source.SkipWhiteSpace(ref index, length, out c); if (errorMessage != null) return errorMessage; var i = index; - var parser = Parsers.FirstOrDefault(p => p.Handles(source[i])); + var parser = Parsers.FirstOrDefault(p => p.Handles(source.Substring(i))); if (parser == null) return "Unrecognized JSON Path Expression element."; - errorMessage = parser.TryParse(source, ref index, ref expr); + ExpressionTreeNode node; + errorMessage = parser.TryParse(source, ref index, out node); if (errorMessage != null) return errorMessage; + nodes.Add(node); } + + expr = new Expression(BuildTree(nodes)); return null; } + + private static ExpressionTreeNode BuildTree(IList> nodes) + { + if (!nodes.Any()) return null; + var minPriority = nodes.Min(n => n.Priority); + var root = nodes.Last(n => n.Priority == minPriority); + var branch = root as ExpressionTreeBranch; + if (branch != null) + { + var split = nodes.IndexOf(root); + var left = nodes.Take(split).ToList(); + var right = nodes.Skip(split + 1).ToList(); + branch.Left = BuildTree(left); + branch.Right = BuildTree(right); + } + var not = root as NotExpression; + if (not != null) + { + var split = nodes.IndexOf(root); + var right = nodes.Skip(split + 1).FirstOrDefault(); + not.Root = right; + } + return root; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/ModuloExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ModuloExpressionParser.cs new file mode 100644 index 0000000..c745ac7 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/ModuloExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class ModuloExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("%"); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new ModuloExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/MultiplyExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/MultiplyExpressionParser.cs new file mode 100644 index 0000000..b7a0066 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/MultiplyExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class MultiplyExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("*"); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new MultiplyExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs new file mode 100644 index 0000000..7b76961 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class SubtractExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("-"); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new SubtractExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs b/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs index bfe0ced..664c48e 100644 --- a/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs +++ b/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs @@ -32,14 +32,14 @@ namespace Manatee.Json.Path.Expressions.Translation { internal static class ExpressionTranslator { - private static readonly Dictionary> _translators = + private static readonly Dictionary> Translators = new Dictionary> { - {typeof (ConstantExpression), GetValueTranslator}, - {typeof (BinaryExpression), e => GetNodeTypeBasedTranslator(e.NodeType)}, - {typeof (UnaryExpression), e => GetNodeTypeBasedTranslator(e.NodeType)}, - {typeof (MethodCallExpression), GetMethodCallTranslator}, - {typeof (MemberExpression), GetMemberTranslator}, + {typeof(ConstantExpression), GetValueTranslator}, + {typeof(BinaryExpression), e => GetNodeTypeBasedTranslator(e.NodeType)}, + {typeof(UnaryExpression), e => GetNodeTypeBasedTranslator(e.NodeType)}, + {typeof(MethodCallExpression), GetMethodCallTranslator}, + {typeof(MemberExpression), GetMemberTranslator}, }; private static IExpressionTranslator GetValueTranslator(Expression e) @@ -50,7 +50,7 @@ private static IExpressionTranslator GetValueTranslator(Expression e) if (type == typeof(string)) return new StringValueExpressionTranslator(); if (type.In(typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort), typeof(int), - typeof (uint), typeof (long), typeof (ulong), typeof (float), typeof (double), typeof (decimal))) + typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal))) return new NumberValueExpressionTranslator(); var constant = (ConstantExpression) e; if (constant.Value == null) @@ -199,10 +199,10 @@ private static IExpressionTranslator GetMemberTranslator(Expression exp) public static ExpressionTreeNode TranslateNode(Expression source) { var type = source.GetType(); - var expressionKey = _translators.Keys.FirstOrDefault(t => t.IsAssignableFrom(type)); + var expressionKey = Translators.Keys.FirstOrDefault(t => t.IsAssignableFrom(type)); if (expressionKey != null) { - var translator = _translators[expressionKey](source); + var translator = Translators[expressionKey](source); if (translator != null) return translator.Translate(source); } @@ -210,11 +210,11 @@ public static ExpressionTreeNode TranslateNode(Expression source) } public static Expression Translate(Expression> source) { - return new Expression {Root = TranslateNode(source.Body)}; + return new Expression(TranslateNode(source.Body)); } public static Expression Translate(Expression> source) { - return new Expression {Root = TranslateNode(source.Body)}; + return new Expression(TranslateNode(source.Body)); } } } \ No newline at end of file From 0c291b0cf44b79505f9e4956f0e65642b18bfd3d Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 14 Sep 2016 15:03:50 +1200 Subject: [PATCH 06/18] Added equality on expressions. Added a few expression parsers. --- Manatee.Json.Tests/Manatee.Json.Tests.csproj | 2 + Manatee.Json.Tests/Path/FilterExpressionTest.cs | 25 +++++++++++ .../Path/IndexExpressionParseTest.cs | 26 ++++++++++++ Manatee.Json/Manatee.Json.csproj | 3 ++ Manatee.Json/Path/Expressions/AddExpression.cs | 26 +++++++++++- Manatee.Json/Path/Expressions/AndExpression.cs | 23 ++++++++++- .../Path/Expressions/ArrayIndexExpression.cs | 24 ++++++++++- .../Path/Expressions/ConversionExpression.cs | 16 +++++++- Manatee.Json/Path/Expressions/DivideExpression.cs | 23 ++++++++++- .../Path/Expressions/ExponentExpression.cs | 21 +++++++++- Manatee.Json/Path/Expressions/Expression.cs | 16 +++++++- .../Path/Expressions/ExpressionTreeBranch.cs | 13 ++++++ Manatee.Json/Path/Expressions/FieldExpression.cs | 16 +++++++- Manatee.Json/Path/Expressions/GroupExpression.cs | 18 +++++++- .../Path/Expressions/HasPropertyExpression.cs | 16 +++++++- Manatee.Json/Path/Expressions/IndexOfExpression.cs | 22 +++++++++- Manatee.Json/Path/Expressions/IsEqualExpression.cs | 21 +++++++++- .../Expressions/IsGreaterThanEqualExpression.cs | 23 ++++++++++- .../Path/Expressions/IsGreaterThanExpression.cs | 24 ++++++++++- .../Path/Expressions/IsLessThanEqualExpression.cs | 23 ++++++++++- .../Path/Expressions/IsLessThanExpression.cs | 23 ++++++++++- .../Path/Expressions/IsNotEqualExpression.cs | 21 +++++++++- Manatee.Json/Path/Expressions/LengthExpression.cs | 21 +++++++++- Manatee.Json/Path/Expressions/ModuloExpression.cs | 23 ++++++++++- .../Path/Expressions/MultiplyExpression.cs | 23 ++++++++++- Manatee.Json/Path/Expressions/NameExpresssion.cs | 22 +++++++++- Manatee.Json/Path/Expressions/NegateExpression.cs | 16 +++++++- Manatee.Json/Path/Expressions/NotExpression.cs | 18 +++++++- Manatee.Json/Path/Expressions/OrExpression.cs | 23 ++++++++++- .../Parsing/ConstantNumberExpressionParser.cs | 25 +++++++++++ .../Parsing/ConstantStringExpressionParser.cs | 48 ++++++++++++++++++++++ .../Parsing/JsonPathExpressionParser.cs | 9 ++-- .../Expressions/Parsing/PathExpressionParser.cs | 44 ++++++++++++++++++++ .../Parsing/SubtractExpressionParser.cs | 1 + Manatee.Json/Path/Expressions/PathExpression.cs | 16 +++++++- .../Path/Expressions/SubtractExpression.cs | 23 ++++++++++- Manatee.Json/Path/Expressions/ValueExpression.cs | 18 +++++++- .../Path/Parsing/ExpressionFilterParser.cs | 4 +- Manatee.Json/Path/Parsing/ExpressionIndexParser.cs | 4 +- Manatee.Json/Path/Parsing/PathParsingExtensions.cs | 34 +++++++++++++-- 40 files changed, 760 insertions(+), 37 deletions(-) create mode 100644 Manatee.Json.Tests/Path/FilterExpressionTest.cs create mode 100644 Manatee.Json.Tests/Path/IndexExpressionParseTest.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/ConstantNumberExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs diff --git a/Manatee.Json.Tests/Manatee.Json.Tests.csproj b/Manatee.Json.Tests/Manatee.Json.Tests.csproj index faab337..1e0602d 100644 --- a/Manatee.Json.Tests/Manatee.Json.Tests.csproj +++ b/Manatee.Json.Tests/Manatee.Json.Tests.csproj @@ -170,6 +170,8 @@ + + diff --git a/Manatee.Json.Tests/Path/FilterExpressionTest.cs b/Manatee.Json.Tests/Path/FilterExpressionTest.cs new file mode 100644 index 0000000..7d6a1cf --- /dev/null +++ b/Manatee.Json.Tests/Path/FilterExpressionTest.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Manatee.Json.Path; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Manatee.Json.Tests.Path +{ + [TestClass] + public class FilterExpressionTest + { + [TestMethod] + public void KeyEqualsValue() + { + var text = "$[?(@.test == 5)]"; + var expected = JsonPathWith.Array(jv => jv.Name("test").Name("second") == 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + } +} diff --git a/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs b/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs new file mode 100644 index 0000000..b109556 --- /dev/null +++ b/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Manatee.Json.Path; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Manatee.Json.Tests.Path +{ + [TestClass] + public class IndexExpressionParseTest + { + + [TestMethod] + public void Length() + { + var text = "$[(@.length)]"; + var expected = JsonPathWith.Array(jv => jv.Length()); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + } +} diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index f6c42c2..2970d44 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -235,6 +235,8 @@ + + @@ -246,6 +248,7 @@ + diff --git a/Manatee.Json/Path/Expressions/AddExpression.cs b/Manatee.Json/Path/Expressions/AddExpression.cs index 1fcf53c..bf44d59 100644 --- a/Manatee.Json/Path/Expressions/AddExpression.cs +++ b/Manatee.Json/Path/Expressions/AddExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class AddExpression : ExpressionTreeBranch + internal class AddExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 2; @@ -44,5 +46,27 @@ public override string ToString() { return $"{Left}+{Right}"; } + public bool Equals(AddExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((AddExpression) obj); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/AndExpression.cs b/Manatee.Json/Path/Expressions/AndExpression.cs index fde7413..4c55340 100644 --- a/Manatee.Json/Path/Expressions/AndExpression.cs +++ b/Manatee.Json/Path/Expressions/AndExpression.cs @@ -20,9 +20,11 @@ Purpose: Expresses the intent to perform a boolean AND. ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class AndExpression : ExpressionTreeBranch + internal class AndExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 0; @@ -34,5 +36,24 @@ public override string ToString() { return $"{Left} && {Right}"; } + public bool Equals(AndExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as AndExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs b/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs index 7822287..d2a440c 100644 --- a/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs +++ b/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs @@ -26,7 +26,7 @@ namespace Manatee.Json.Path.Expressions { - internal class ArrayIndexExpression : PathExpression + internal class ArrayIndexExpression : PathExpression, IEquatable> { public override int Priority => 6; public int Index { get; set; } @@ -56,6 +56,28 @@ public override string ToString() var path = Path == null ? string.Empty : Path.GetRawString(); return string.Format(IsLocal ? "@{0}[{1}]" : "${0}[{1}]", path, GetIndex()); } + public bool Equals(ArrayIndexExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && + Index == other.Index && + Equals(IndexExpression, other.IndexExpression); + } + public override bool Equals(object obj) + { + return Equals(obj as ArrayIndexExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode*397) ^ Index; + hashCode = (hashCode*397) ^ (IndexExpression?.GetHashCode() ?? 0); + return hashCode; + } + } private int GetIndex() { diff --git a/Manatee.Json/Path/Expressions/ConversionExpression.cs b/Manatee.Json/Path/Expressions/ConversionExpression.cs index 63e2e4e..9dfe129 100644 --- a/Manatee.Json/Path/Expressions/ConversionExpression.cs +++ b/Manatee.Json/Path/Expressions/ConversionExpression.cs @@ -25,7 +25,7 @@ namespace Manatee.Json.Path.Expressions { - internal class ConversionExpression : ExpressionTreeNode + internal class ConversionExpression : ExpressionTreeNode, IEquatable> { public override int Priority => 6; public ExpressionTreeNode Root { get; set; } @@ -41,6 +41,20 @@ public override string ToString() { return Root.ToString(); } + public bool Equals(ConversionExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Root, other.Root) && TargetType == other.TargetType; + } + public override bool Equals(object obj) + { + return Equals(obj as ConversionExpression); + } + public override int GetHashCode() + { + unchecked { return ((Root?.GetHashCode() ?? 0)*397) ^ (TargetType != null ? TargetType.GetHashCode() : 0); } + } private object CastValue(object value) { diff --git a/Manatee.Json/Path/Expressions/DivideExpression.cs b/Manatee.Json/Path/Expressions/DivideExpression.cs index 7c90c10..b143e6c 100644 --- a/Manatee.Json/Path/Expressions/DivideExpression.cs +++ b/Manatee.Json/Path/Expressions/DivideExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class DivideExpression : ExpressionTreeBranch + internal class DivideExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 3; @@ -40,5 +42,24 @@ public override string ToString() var right = Right.Priority <= Priority ? $"({Right})" : Right.ToString(); return $"{left}/{right}"; } + public bool Equals(DivideExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as DivideExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/ExponentExpression.cs b/Manatee.Json/Path/Expressions/ExponentExpression.cs index 9c3c5d5..e852c4d 100644 --- a/Manatee.Json/Path/Expressions/ExponentExpression.cs +++ b/Manatee.Json/Path/Expressions/ExponentExpression.cs @@ -24,7 +24,7 @@ namespace Manatee.Json.Path.Expressions { - internal class ExponentExpression : ExpressionTreeBranch + internal class ExponentExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 4; @@ -40,5 +40,24 @@ public override string ToString() var right = Right.Priority <= Priority ? $"({Right})" : Right.ToString(); return $"{left}^{right}"; } + public bool Equals(ExponentExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as ExponentExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Expression.cs b/Manatee.Json/Path/Expressions/Expression.cs index e3b98f2..89520bb 100644 --- a/Manatee.Json/Path/Expressions/Expression.cs +++ b/Manatee.Json/Path/Expressions/Expression.cs @@ -24,7 +24,7 @@ namespace Manatee.Json.Path.Expressions { - internal class Expression + internal class Expression : IEquatable> { private readonly ExpressionTreeNode _root; @@ -61,5 +61,19 @@ public override string ToString() { return _root.ToString(); } + public bool Equals(Expression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(_root, other._root); + } + public override bool Equals(object obj) + { + return Equals(obj as Expression); + } + public override int GetHashCode() + { + return _root?.GetHashCode() ?? 0; + } } } diff --git a/Manatee.Json/Path/Expressions/ExpressionTreeBranch.cs b/Manatee.Json/Path/Expressions/ExpressionTreeBranch.cs index 696b9fe..08179d3 100644 --- a/Manatee.Json/Path/Expressions/ExpressionTreeBranch.cs +++ b/Manatee.Json/Path/Expressions/ExpressionTreeBranch.cs @@ -26,5 +26,18 @@ internal abstract class ExpressionTreeBranch : ExpressionTreeNode { public ExpressionTreeNode Left { get; set; } public ExpressionTreeNode Right { get; set; } + + protected bool Equals(ExpressionTreeBranch other) + { + return Equals(Left, other.Left) && Equals(Right, other.Right); + } + public override bool Equals(object obj) + { + return Equals(obj as ExpressionTreeBranch); + } + public override int GetHashCode() + { + unchecked { return ((Left?.GetHashCode() ?? 0)*397) ^ (Right?.GetHashCode() ?? 0); } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/FieldExpression.cs b/Manatee.Json/Path/Expressions/FieldExpression.cs index 27ddcde..edd46ff 100644 --- a/Manatee.Json/Path/Expressions/FieldExpression.cs +++ b/Manatee.Json/Path/Expressions/FieldExpression.cs @@ -26,7 +26,7 @@ namespace Manatee.Json.Path.Expressions { - internal class FieldExpression : ExpressionTreeNode + internal class FieldExpression : ExpressionTreeNode, IEquatable> { public override int Priority => 6; public FieldInfo Field { get; set; } @@ -45,5 +45,19 @@ public override string ToString() ? $"\"{value}\"" : value?.ToString() ?? "null"; } + public bool Equals(FieldExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Field, other.Field) && Equals(Source, other.Source); + } + public override bool Equals(object obj) + { + return Equals(obj as FieldExpression); + } + public override int GetHashCode() + { + unchecked { return ((Field != null ? Field.GetHashCode() : 0)*397) ^ (Source?.GetHashCode() ?? 0); } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/GroupExpression.cs b/Manatee.Json/Path/Expressions/GroupExpression.cs index ec2b86d..cd79e54 100644 --- a/Manatee.Json/Path/Expressions/GroupExpression.cs +++ b/Manatee.Json/Path/Expressions/GroupExpression.cs @@ -22,9 +22,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class GroupExpression : ExpressionTreeNode + internal class GroupExpression : ExpressionTreeNode, IEquatable> { public override int Priority => 6; public ExpressionTreeNode Group { get; set; } @@ -37,5 +39,19 @@ public override string ToString() { return $"({Group})"; } + public bool Equals(GroupExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Group, other.Group); + } + public override bool Equals(object obj) + { + return Equals(obj as GroupExpression); + } + public override int GetHashCode() + { + return Group?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/HasPropertyExpression.cs b/Manatee.Json/Path/Expressions/HasPropertyExpression.cs index 0b79936..5c35d68 100644 --- a/Manatee.Json/Path/Expressions/HasPropertyExpression.cs +++ b/Manatee.Json/Path/Expressions/HasPropertyExpression.cs @@ -24,7 +24,7 @@ namespace Manatee.Json.Path.Expressions { - internal class HasPropertyExpression : ExpressionTreeNode + internal class HasPropertyExpression : ExpressionTreeNode, IEquatable> { public override int Priority => 6; public string Name { get; set; } @@ -43,5 +43,19 @@ public override string ToString() { return $"@.{Name}"; } + public bool Equals(HasPropertyExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Name, other.Name); + } + public override bool Equals(object obj) + { + return Equals(obj as HasPropertyExpression); + } + public override int GetHashCode() + { + return Name?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/IndexOfExpression.cs b/Manatee.Json/Path/Expressions/IndexOfExpression.cs index 3224b0a..30b4592 100644 --- a/Manatee.Json/Path/Expressions/IndexOfExpression.cs +++ b/Manatee.Json/Path/Expressions/IndexOfExpression.cs @@ -25,7 +25,7 @@ namespace Manatee.Json.Path.Expressions { - internal class IndexOfExpression : PathExpression + internal class IndexOfExpression : PathExpression, IEquatable> { public override int Priority => 6; public JsonValue Parameter { get; set; } @@ -51,6 +51,26 @@ public override string ToString() var parameter = ParameterExpression?.ToString() ?? Parameter.ToString(); return string.Format(IsLocal ? "@{0}.indexOf({1})" : "${0}.indexOf({1})", path, parameter); } + public bool Equals(IndexOfExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Equals(Parameter, other.Parameter) && Equals(ParameterExpression, other.ParameterExpression); + } + public override bool Equals(object obj) + { + return Equals(obj as IndexOfExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode*397) ^ (Parameter != null ? Parameter.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ParameterExpression?.GetHashCode() ?? 0); + return hashCode; + } + } private JsonValue GetParameter() { diff --git a/Manatee.Json/Path/Expressions/IsEqualExpression.cs b/Manatee.Json/Path/Expressions/IsEqualExpression.cs index 6a90e1d..1b1fac6 100644 --- a/Manatee.Json/Path/Expressions/IsEqualExpression.cs +++ b/Manatee.Json/Path/Expressions/IsEqualExpression.cs @@ -25,7 +25,7 @@ namespace Manatee.Json.Path.Expressions { - internal class IsEqualExpression : ExpressionTreeBranch + internal class IsEqualExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 1; @@ -41,5 +41,24 @@ public override string ToString() { return $"{Left} == {Right}"; } + public bool Equals(IsEqualExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as IsEqualExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/IsGreaterThanEqualExpression.cs b/Manatee.Json/Path/Expressions/IsGreaterThanEqualExpression.cs index fa55ed7..b519713 100644 --- a/Manatee.Json/Path/Expressions/IsGreaterThanEqualExpression.cs +++ b/Manatee.Json/Path/Expressions/IsGreaterThanEqualExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class IsGreaterThanEqualExpression : ExpressionTreeBranch + internal class IsGreaterThanEqualExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 1; @@ -37,5 +39,24 @@ public override string ToString() { return $"{Left} >= {Right}"; } + public bool Equals(IsGreaterThanEqualExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as IsGreaterThanEqualExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/IsGreaterThanExpression.cs b/Manatee.Json/Path/Expressions/IsGreaterThanExpression.cs index 7f6b819..d712d8e 100644 --- a/Manatee.Json/Path/Expressions/IsGreaterThanExpression.cs +++ b/Manatee.Json/Path/Expressions/IsGreaterThanExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class IsGreaterThanExpression : ExpressionTreeBranch + internal class IsGreaterThanExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 1; @@ -37,5 +39,25 @@ public override string ToString() { return $"{Left} > {Right}"; } + public bool Equals(IsGreaterThanExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + if (obj.GetType() != this.GetType()) return false; + return Equals((IsGreaterThanExpression) obj); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/IsLessThanEqualExpression.cs b/Manatee.Json/Path/Expressions/IsLessThanEqualExpression.cs index 5819946..0e32b59 100644 --- a/Manatee.Json/Path/Expressions/IsLessThanEqualExpression.cs +++ b/Manatee.Json/Path/Expressions/IsLessThanEqualExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class IsLessThanEqualExpression : ExpressionTreeBranch + internal class IsLessThanEqualExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 1; @@ -37,5 +39,24 @@ public override string ToString() { return $"{Left} <= {Right}"; } + public bool Equals(IsLessThanEqualExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as IsLessThanEqualExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/IsLessThanExpression.cs b/Manatee.Json/Path/Expressions/IsLessThanExpression.cs index df9b8e2..33bb137 100644 --- a/Manatee.Json/Path/Expressions/IsLessThanExpression.cs +++ b/Manatee.Json/Path/Expressions/IsLessThanExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class IsLessThanExpression : ExpressionTreeBranch + internal class IsLessThanExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 1; @@ -37,5 +39,24 @@ public override string ToString() { return $"{Left} < {Right}"; } + public bool Equals(IsLessThanExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as IsLessThanExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/IsNotEqualExpression.cs b/Manatee.Json/Path/Expressions/IsNotEqualExpression.cs index cc0090f..0bf3e5f 100644 --- a/Manatee.Json/Path/Expressions/IsNotEqualExpression.cs +++ b/Manatee.Json/Path/Expressions/IsNotEqualExpression.cs @@ -25,7 +25,7 @@ namespace Manatee.Json.Path.Expressions { - internal class IsNotEqualExpression : ExpressionTreeBranch + internal class IsNotEqualExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 1; @@ -41,5 +41,24 @@ public override string ToString() { return $"{Left} != {Right}"; } + public bool Equals(IsNotEqualExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as IsNotEqualExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/LengthExpression.cs b/Manatee.Json/Path/Expressions/LengthExpression.cs index e42d716..2265a7a 100644 --- a/Manatee.Json/Path/Expressions/LengthExpression.cs +++ b/Manatee.Json/Path/Expressions/LengthExpression.cs @@ -25,7 +25,7 @@ namespace Manatee.Json.Path.Expressions { - internal class LengthExpression : PathExpression + internal class LengthExpression : PathExpression, IEquatable> { public override int Priority => 6; @@ -66,5 +66,24 @@ public override string ToString() var path = Path == null ? string.Empty : Path.GetRawString(); return (IsLocal ? "@" : "$") + $"{path}.length"; } + public bool Equals(LengthExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as LengthExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/ModuloExpression.cs b/Manatee.Json/Path/Expressions/ModuloExpression.cs index 6e6d6a7..60518fc 100644 --- a/Manatee.Json/Path/Expressions/ModuloExpression.cs +++ b/Manatee.Json/Path/Expressions/ModuloExpression.cs @@ -20,9 +20,11 @@ Purpose: Expresses the intent to calculate the modulo of two numbers. ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class ModuloExpression : ExpressionTreeBranch + internal class ModuloExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 2; @@ -39,5 +41,24 @@ public override string ToString() var right = Right.Priority <= Priority ? $"({Right})" : Right.ToString(); return $"{left}%{right}"; } + public bool Equals(ModuloExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as ModuloExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/MultiplyExpression.cs b/Manatee.Json/Path/Expressions/MultiplyExpression.cs index 5296dd7..f988bad 100644 --- a/Manatee.Json/Path/Expressions/MultiplyExpression.cs +++ b/Manatee.Json/Path/Expressions/MultiplyExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class MultiplyExpression : ExpressionTreeBranch + internal class MultiplyExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 3; @@ -40,5 +42,24 @@ public override string ToString() var right = Right.Priority <= Priority ? $"({Right})" : Right.ToString(); return $"{left}*{right}"; } + public bool Equals(MultiplyExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as MultiplyExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/NameExpresssion.cs b/Manatee.Json/Path/Expressions/NameExpresssion.cs index 2494677..ab8e9dc 100644 --- a/Manatee.Json/Path/Expressions/NameExpresssion.cs +++ b/Manatee.Json/Path/Expressions/NameExpresssion.cs @@ -26,7 +26,7 @@ namespace Manatee.Json.Path.Expressions { - internal class NameExpression : PathExpression + internal class NameExpression : PathExpression, IEquatable> { public override int Priority => 6; public string Name { get; set; } @@ -59,5 +59,25 @@ private string GetName() return (string)value; return Name; } + public bool Equals(NameExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && string.Equals(Name, other.Name) && Equals(NameExp, other.NameExp); + } + public override bool Equals(object obj) + { + return Equals(obj as NameExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode*397) ^ (Name?.GetHashCode() ?? 0); + hashCode = (hashCode*397) ^ (NameExp?.GetHashCode() ?? 0); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/NegateExpression.cs b/Manatee.Json/Path/Expressions/NegateExpression.cs index ab21e68..5254864 100644 --- a/Manatee.Json/Path/Expressions/NegateExpression.cs +++ b/Manatee.Json/Path/Expressions/NegateExpression.cs @@ -24,7 +24,7 @@ namespace Manatee.Json.Path.Expressions { - internal class NegateExpression : ExpressionTreeNode + internal class NegateExpression : ExpressionTreeNode, IEquatable> { public override int Priority => 6; public ExpressionTreeNode Root { get; set; } @@ -39,5 +39,19 @@ public override string ToString() ? $"-({Root})" : $"-{Root}"; } + public bool Equals(NegateExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Root, other.Root); + } + public override bool Equals(object obj) + { + return Equals(obj as NegateExpression); + } + public override int GetHashCode() + { + return Root?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/NotExpression.cs b/Manatee.Json/Path/Expressions/NotExpression.cs index 6a79a21..f194c95 100644 --- a/Manatee.Json/Path/Expressions/NotExpression.cs +++ b/Manatee.Json/Path/Expressions/NotExpression.cs @@ -20,9 +20,11 @@ Purpose: Expresses the intent to invert a boolean. ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class NotExpression : ExpressionTreeNode + internal class NotExpression : ExpressionTreeNode, IEquatable> { public override int Priority => 5; public ExpressionTreeNode Root { get; set; } @@ -41,5 +43,19 @@ public override string ToString() ? $"!({Root})" : $"!{Root}"; } + public bool Equals(NotExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Root, other.Root); + } + public override bool Equals(object obj) + { + return Equals(obj as NotExpression); + } + public override int GetHashCode() + { + return Root?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/OrExpression.cs b/Manatee.Json/Path/Expressions/OrExpression.cs index 8676e0e..f72540d 100644 --- a/Manatee.Json/Path/Expressions/OrExpression.cs +++ b/Manatee.Json/Path/Expressions/OrExpression.cs @@ -20,9 +20,11 @@ Purpose: Expresses the intent to perform a boolean OR. ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class OrExpression : ExpressionTreeBranch + internal class OrExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 0; @@ -34,5 +36,24 @@ public override string ToString() { return $"{Left} || {Right}"; } + public bool Equals(OrExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as OrExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/ConstantNumberExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ConstantNumberExpressionParser.cs new file mode 100644 index 0000000..860f4a4 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/ConstantNumberExpressionParser.cs @@ -0,0 +1,25 @@ +using Manatee.Json.Path.Parsing; + +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class ConstantNumberExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return char.IsDigit(input[0]); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + double? number; + var error = source.GetNumber(ref index, out number); + if (error != null) + { + node = null; + return error; + } + + node = new ValueExpression {Value = number}; + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs new file mode 100644 index 0000000..b295f38 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Manatee.Json.Internal; + +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class ConstantStringExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input[0].In('"', '\''); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + throw new NotImplementedException(); + } + } + + internal class LengthExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith(".length") && input.Length > 7 && !char.IsLetterOrDigit(input[7]); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index += 7; + node = new LengthExpression(); + return null; + } + } + + internal class ExpressionEndParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input[0] == ')'; + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = null; + return null; + } + } +} diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs index 0dfa7dc..7742cd9 100644 --- a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -22,8 +22,9 @@ static JsonPathExpressionParser() { var nodes = new List>(); var length = source.Length; + ExpressionTreeNode node; expr = null; - while (index < length) + do { char c; var errorMessage = source.SkipWhiteSpace(ref index, length, out c); @@ -31,11 +32,11 @@ static JsonPathExpressionParser() var i = index; var parser = Parsers.FirstOrDefault(p => p.Handles(source.Substring(i))); if (parser == null) return "Unrecognized JSON Path Expression element."; - ExpressionTreeNode node; errorMessage = parser.TryParse(source, ref index, out node); if (errorMessage != null) return errorMessage; - nodes.Add(node); - } + if (node != null) + nodes.Add(node); + } while (index < length && node != null); expr = new Expression(BuildTree(nodes)); return null; diff --git a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs new file mode 100644 index 0000000..0e1fd08 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs @@ -0,0 +1,44 @@ +using System.Linq; +using Manatee.Json.Internal; +using Manatee.Json.Path.Operators; +using Manatee.Json.Path.Parsing; + +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class PathExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input[0].In('@', '$'); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + var isLocal = source[index] == '@'; + JsonPath path; + var error = JsonPathParser.Parse(source, ref index, out path); + // Swallow this error from the path parser and assume the path just ended. + // If it's really a syntax error, the expression parser should catch it. + if (error != null && error != "Unrecognized JSON Path element.") + { + node = null; + return error; + } + + var name = path.Operators.Last() as NameOperator; + var indexOf = path.Operators.Last() as IndexOfOperator; + if (name != null && name.Name == "length") + { + path.Operators.Remove(name); + index -= 7; // back up to allow the LengthExpressionParser handle it. + } + else if (indexOf != null) + { + path.Operators.Remove(indexOf); + index -= 8; // back up to allow the IndexOfExpressionParser handle it. + } + + node = new PathExpression {Path = path, IsLocal = isLocal}; + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs index 7b76961..72ce448 100644 --- a/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs @@ -4,6 +4,7 @@ internal class SubtractExpressionParser : IJsonPathExpressionParser { public bool Handles(string input) { + // TODO: Determine how to identify negations separately from subtractions. return input.StartsWith("-"); } public string TryParse(string source, ref int index, out ExpressionTreeNode node) diff --git a/Manatee.Json/Path/Expressions/PathExpression.cs b/Manatee.Json/Path/Expressions/PathExpression.cs index def753a..b8dc221 100644 --- a/Manatee.Json/Path/Expressions/PathExpression.cs +++ b/Manatee.Json/Path/Expressions/PathExpression.cs @@ -25,7 +25,7 @@ namespace Manatee.Json.Path.Expressions { - internal class PathExpression : ExpressionTreeNode + internal class PathExpression : ExpressionTreeNode, IEquatable> { public override int Priority => 6; public JsonPath Path { get; set; } @@ -45,5 +45,19 @@ public override string ToString() { return (IsLocal ? "@" : "$") + Path.GetRawString(); } + public bool Equals(PathExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Path, other.Path) && IsLocal == other.IsLocal; + } + public override bool Equals(object obj) + { + return Equals(obj as PathExpression); + } + public override int GetHashCode() + { + unchecked { return ((Path?.GetHashCode() ?? 0)*397) ^ IsLocal.GetHashCode(); } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/SubtractExpression.cs b/Manatee.Json/Path/Expressions/SubtractExpression.cs index 6ad2d33..9045367 100644 --- a/Manatee.Json/Path/Expressions/SubtractExpression.cs +++ b/Manatee.Json/Path/Expressions/SubtractExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class SubtractExpression : ExpressionTreeBranch + internal class SubtractExpression : ExpressionTreeBranch, IEquatable> { public override int Priority => 2; @@ -40,5 +42,24 @@ public override string ToString() return $"{Left}-({Right})"; return $"{Left}-{Right}"; } + public bool Equals(SubtractExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other); + } + public override bool Equals(object obj) + { + return Equals(obj as SubtractExpression); + } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/ValueExpression.cs b/Manatee.Json/Path/Expressions/ValueExpression.cs index 06d622d..d4f2ab5 100644 --- a/Manatee.Json/Path/Expressions/ValueExpression.cs +++ b/Manatee.Json/Path/Expressions/ValueExpression.cs @@ -21,9 +21,11 @@ ***************************************************************************************/ +using System; + namespace Manatee.Json.Path.Expressions { - internal class ValueExpression : ExpressionTreeNode + internal class ValueExpression : ExpressionTreeNode, IEquatable> { public override int Priority => 6; public object Value { get; set; } @@ -38,5 +40,19 @@ public override string ToString() ? $"\"{Value}\"" : Value.ToString(); } + public bool Equals(ValueExpression other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Value, other.Value); + } + public override bool Equals(object obj) + { + return Equals(obj as ValueExpression); + } + public override int GetHashCode() + { + return Value?.GetHashCode() ?? 0; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Parsing/ExpressionFilterParser.cs b/Manatee.Json/Path/Parsing/ExpressionFilterParser.cs index 65e37c5..a1db3fa 100644 --- a/Manatee.Json/Path/Parsing/ExpressionFilterParser.cs +++ b/Manatee.Json/Path/Parsing/ExpressionFilterParser.cs @@ -31,16 +31,18 @@ internal class ExpressionFilterParser : IJsonPathParser { public bool Handles(string input) { - return input.StartsWith("[("); + return input.StartsWith("[?("); } public string TryParse(string source, ref int index, ref JsonPath path) { + index += 3; Expression expression; var error = JsonPathExpressionParser.Parse(source, ref index, out expression); if (error != null) return error; + index++; // consume closing ']' path.Operators.Add(new ArrayOperator(new FilterExpressionQuery(expression))); return null; } diff --git a/Manatee.Json/Path/Parsing/ExpressionIndexParser.cs b/Manatee.Json/Path/Parsing/ExpressionIndexParser.cs index 621d43a..4706d0a 100644 --- a/Manatee.Json/Path/Parsing/ExpressionIndexParser.cs +++ b/Manatee.Json/Path/Parsing/ExpressionIndexParser.cs @@ -31,16 +31,18 @@ internal class ExpressionIndexParser : IJsonPathParser { public bool Handles(string input) { - return input.StartsWith("[?("); + return input.StartsWith("[("); } public string TryParse(string source, ref int index, ref JsonPath path) { + index += 2; Expression expression; var error = JsonPathExpressionParser.Parse(source, ref index, out expression); if (error != null) return error; + index++; // consume closing ']' path.Operators.Add(new ArrayOperator(new IndexExpressionQuery(expression))); return null; } diff --git a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs index e4d02cf..c60ed2e 100644 --- a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs +++ b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs @@ -157,7 +157,7 @@ private static string GetSlice(this string source, ref int index, out Slice slic int? n1, n2, n3; - var error = GetNumber(source, ref index, out n1); + var error = GetInt(source, ref index, out n1); if (error != null) return error; if (n1.HasValue && source[index].In(',', ']')) { @@ -168,7 +168,7 @@ private static string GetSlice(this string source, ref int index, out Slice slic return "Expected ':', ',', or ']'."; index++; - error = GetNumber(source, ref index, out n2); + error = GetInt(source, ref index, out n2); if (error != null) return error; if (source[index].In(',', ']')) { @@ -179,7 +179,7 @@ private static string GetSlice(this string source, ref int index, out Slice slic return "Expected ':', ',', or ']'."; index++; - error = GetNumber(source, ref index, out n3); + error = GetInt(source, ref index, out n3); if (error != null) return error; if (source[index].In(',', ']')) { @@ -188,7 +188,7 @@ private static string GetSlice(this string source, ref int index, out Slice slic } return "Expected ',' or ']'."; } - private static string GetNumber(string source, ref int index, out int? number) + public static string GetInt(string source, ref int index, out int? number) { int value; var text = new string(source.Substring(index).TakeWhile(c => char.IsDigit(c) || c == '-').ToArray()); @@ -209,5 +209,31 @@ private static string GetNumber(string source, ref int index, out int? number) } #endregion + + #region GetNumber + + private const string NumberChars = "0123456789e.-"; + + public static string GetNumber(this string source, ref int index, out double? number) + { + double value; + var text = new string(source.Substring(index).TakeWhile(c => NumberChars.Contains(c)).ToArray()); + if (text.Length == 0 && source[index].In(':', ',', ']')) + { + number = null; + return null; + } + if (!double.TryParse(text, out value)) + { + number = null; + return "Expected number."; + } + + index += text.Length; + number = value; + return null; + } + + #endregion } } From 8731c084f592450a33e16e1991c02feb8b74d446 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 14 Sep 2016 15:20:28 +1200 Subject: [PATCH 07/18] I've seen the light. --- Manatee.Json.Tests/Path/FilterExpressionTest.cs | 26 ++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Manatee.Json.Tests/Path/FilterExpressionTest.cs b/Manatee.Json.Tests/Path/FilterExpressionTest.cs index 7d6a1cf..9322eb1 100644 --- a/Manatee.Json.Tests/Path/FilterExpressionTest.cs +++ b/Manatee.Json.Tests/Path/FilterExpressionTest.cs @@ -12,13 +12,37 @@ namespace Manatee.Json.Tests.Path public class FilterExpressionTest { [TestMethod] + [Ignore] // interestingly, it never worked like this... public void KeyEqualsValue() { var text = "$[?(@.test == 5)]"; - var expected = JsonPathWith.Array(jv => jv.Name("test").Name("second") == 5); + var expected = JsonPathWith.Array(jv => jv.Name("test") == 5); var actual = JsonPath.Parse(text); + // this failes because the parsed version uses a raw PathExpression + // whereas the constructed version uses a NameExpression (which contains a path) + // TODO: Remove PathExpression derivatives. + + Assert.AreEqual(expected, actual); + } + + [TestMethod] // this is the same test as above, just that we're validating the + // path evaluation rather than comparing the paths. + public void KeyEqualsValue_Evaluation() + { + var text = "$[?(@.test == 5)]"; + var json = new JsonArray + { + new JsonObject {{"test", 5}, {"yep", 6}}, + new JsonObject {{"test", 6}, {"nope", 6}}, + }; + var path = JsonPathWith.Array(jv => jv.Name("test") == 5); + var expected = path.Evaluate(json); + var parsed = JsonPath.Parse(text); + + var actual = parsed.Evaluate(json); + Assert.AreEqual(expected, actual); } } From e605953c1aface05bc7580b7542cd5f3001953cc Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Thu, 15 Sep 2016 16:30:41 +1200 Subject: [PATCH 08/18] Added array and hasproperty expression parsing. Updated HasProperty to path expression. --- Manatee.Json.Tests/Path/FilterExpressionTest.cs | 108 ++++++++++++++++----- .../Path/IndexExpressionParseTest.cs | 13 ++- Manatee.Json/Path/ArrayParameters/SliceQuery.cs | 12 +-- .../Path/Expressions/HasPropertyExpression.cs | 11 ++- .../Parsing/JsonPathExpressionParser.cs | 23 ++++- .../Expressions/Parsing/PathExpressionParser.cs | 49 +++++++++- .../Translation/ArrayIndexExpressionTranslator.cs | 18 ++-- .../Translation/HasPropertyExpressionTranslator.cs | 12 ++- Manatee.Json/Path/Operators/ArrayOperator.cs | 14 +-- Manatee.Json/Path/Slice.cs | 19 ++-- 10 files changed, 211 insertions(+), 68 deletions(-) diff --git a/Manatee.Json.Tests/Path/FilterExpressionTest.cs b/Manatee.Json.Tests/Path/FilterExpressionTest.cs index 9322eb1..dfae605 100644 --- a/Manatee.Json.Tests/Path/FilterExpressionTest.cs +++ b/Manatee.Json.Tests/Path/FilterExpressionTest.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Manatee.Json.Path; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -12,36 +8,102 @@ namespace Manatee.Json.Tests.Path public class FilterExpressionTest { [TestMethod] - [Ignore] // interestingly, it never worked like this... - public void KeyEqualsValue() + public void PropertyEqualsValue() { var text = "$[?(@.test == 5)]"; var expected = JsonPathWith.Array(jv => jv.Name("test") == 5); var actual = JsonPath.Parse(text); - // this failes because the parsed version uses a raw PathExpression - // whereas the constructed version uses a NameExpression (which contains a path) - // TODO: Remove PathExpression derivatives. + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void ArrayIndexEqualsValue() + { + var text = "$[?(@[1] == 5)]"; + var expected = JsonPathWith.Array(jv => jv.ArrayIndex(1) == 5); + + var actual = JsonPath.Parse(text); Assert.AreEqual(expected, actual); } - [TestMethod] // this is the same test as above, just that we're validating the - // path evaluation rather than comparing the paths. - public void KeyEqualsValue_Evaluation() + [TestMethod] + [ExpectedException(typeof(JsonPathSyntaxException))] + public void ArrayMultiIndex() + { + var text = "$[?(@[1,3] == 5)]"; + + try + { + var actual = JsonPath.Parse(text); + } + catch (JsonPathSyntaxException e) + { + Assert.IsTrue(e.Message.StartsWith("JSON Path expression indexers only support single constant values.")); + throw; + } + } + + [TestMethod] + [ExpectedException(typeof(JsonPathSyntaxException))] + public void ArraySliceEqualsValue() { - var text = "$[?(@.test == 5)]"; - var json = new JsonArray - { - new JsonObject {{"test", 5}, {"yep", 6}}, - new JsonObject {{"test", 6}, {"nope", 6}}, - }; - var path = JsonPathWith.Array(jv => jv.Name("test") == 5); - var expected = path.Evaluate(json); - var parsed = JsonPath.Parse(text); - - var actual = parsed.Evaluate(json); + var text = "$[?(@[1:3] == 5)]"; + + try + { + var actual = JsonPath.Parse(text); + } + catch (JsonPathSyntaxException e) + { + Assert.IsTrue(e.Message.StartsWith("JSON Path expression indexers only support single constant values.")); + throw; + } + } + + [TestMethod] + [ExpectedException(typeof(JsonPathSyntaxException))] + public void ArrayIndexExpressionEqualsValue() + { + var text = "$[?(@[(@.length-1))]"; + + try + { + var actual = JsonPath.Parse(text); + } + catch (JsonPathSyntaxException e) + { + Assert.IsTrue(e.Message.StartsWith("JSON Path expression indexers only support single constant values.")); + throw; + } + } + + [TestMethod] + [ExpectedException(typeof(JsonPathSyntaxException))] + public void ArrayFilterExpressionEqualsValue() + { + var text = "$[?(@[?(@.name == 5))]"; + + try + { + var actual = JsonPath.Parse(text); + } + catch (JsonPathSyntaxException e) + { + Assert.IsTrue(e.Message.StartsWith("JSON Path expression indexers only support single constant values.")); + throw; + } + } + + [TestMethod] + public void HasProperty() + { + var text = "$[?(@.test)]"; + var expected = JsonPathWith.Array(jv => jv.HasProperty("test")); + + var actual = JsonPath.Parse(text); Assert.AreEqual(expected, actual); } diff --git a/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs b/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs index b109556..6acd21f 100644 --- a/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs +++ b/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs @@ -11,7 +11,6 @@ namespace Manatee.Json.Tests.Path [TestClass] public class IndexExpressionParseTest { - [TestMethod] public void Length() { @@ -22,5 +21,17 @@ public void Length() Assert.AreEqual(expected, actual); } + // TODO: Add .Name to JsonPathArray extension set. Will need to make HasPropertyExpression derive from PathExpression. + // Paserser can handle it, but need the extension method to test against. + //[TestMethod] + //public void ExtendedLength() + //{ + // var text = "$[(@.name.length)]"; + // var expected = JsonPathWith.Array(jv => jv.Name("name").Length()); + + // var actual = JsonPath.Parse(text); + + // Assert.AreEqual(expected, actual); + //} } } diff --git a/Manatee.Json/Path/ArrayParameters/SliceQuery.cs b/Manatee.Json/Path/ArrayParameters/SliceQuery.cs index 01ed9a4..b536732 100644 --- a/Manatee.Json/Path/ArrayParameters/SliceQuery.cs +++ b/Manatee.Json/Path/ArrayParameters/SliceQuery.cs @@ -29,27 +29,27 @@ namespace Manatee.Json.Path.ArrayParameters { internal class SliceQuery : IJsonPathArrayQuery, IEquatable { - private readonly IEnumerable _slices; + internal IEnumerable Slices { get; } public SliceQuery(params Slice[] slices) : this((IEnumerable) slices) {} public SliceQuery(IEnumerable slices) { - _slices = slices.ToList(); + Slices = slices.ToList(); } public IEnumerable Find(JsonArray json, JsonValue root) { - return _slices.SelectMany(s => s.Find(json, root)).Distinct(); + return Slices.SelectMany(s => s.Find(json, root)).Distinct(); } public override string ToString() { - return _slices.Join(","); + return Slices.Join(","); } public bool Equals(SliceQuery other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return _slices.ContentsEqual(other._slices); + return Slices.ContentsEqual(other.Slices); } public override bool Equals(object obj) { @@ -57,7 +57,7 @@ public override bool Equals(object obj) } public override int GetHashCode() { - return _slices?.GetCollectionHashCode() ?? 0; + return Slices?.GetCollectionHashCode() ?? 0; } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/HasPropertyExpression.cs b/Manatee.Json/Path/Expressions/HasPropertyExpression.cs index 5c35d68..5512460 100644 --- a/Manatee.Json/Path/Expressions/HasPropertyExpression.cs +++ b/Manatee.Json/Path/Expressions/HasPropertyExpression.cs @@ -24,7 +24,7 @@ namespace Manatee.Json.Path.Expressions { - internal class HasPropertyExpression : ExpressionTreeNode, IEquatable> + internal class HasPropertyExpression : PathExpression, IEquatable> { public override int Priority => 6; public string Name { get; set; } @@ -47,7 +47,7 @@ public bool Equals(HasPropertyExpression other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return string.Equals(Name, other.Name); + return base.Equals(other) && string.Equals(Name, other.Name); } public override bool Equals(object obj) { @@ -55,7 +55,12 @@ public override bool Equals(object obj) } public override int GetHashCode() { - return Name?.GetHashCode() ?? 0; + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (Name?.GetHashCode() ?? 0); + return hashCode; + } } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs index 7742cd9..4cde706 100644 --- a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -38,7 +38,9 @@ static JsonPathExpressionParser() nodes.Add(node); } while (index < length && node != null); - expr = new Expression(BuildTree(nodes)); + expr = new Expression(nodes.Count == 1 + ? CheckNode(nodes[0], null) + : BuildTree(nodes)); return null; } @@ -53,8 +55,8 @@ private static ExpressionTreeNode BuildTree(IList> n var split = nodes.IndexOf(root); var left = nodes.Take(split).ToList(); var right = nodes.Skip(split + 1).ToList(); - branch.Left = BuildTree(left); - branch.Right = BuildTree(right); + branch.Left = CheckNode(BuildTree(left), branch); + branch.Right = CheckNode(BuildTree(right), branch); } var not = root as NotExpression; if (not != null) @@ -65,5 +67,20 @@ private static ExpressionTreeNode BuildTree(IList> n } return root; } + + private static ExpressionTreeNode CheckNode(ExpressionTreeNode node, ExpressionTreeBranch root) + { + var named = node as NameExpression; + if (named != null && (root == null || root.Priority == 0)) + { + return new HasPropertyExpression + { + Path = named.Path, + IsLocal = named.IsLocal, + Name = named.Name + }; + } + return node; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs index 0e1fd08..6c5f9e1 100644 --- a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs @@ -1,5 +1,7 @@ -using System.Linq; +using System; +using System.Linq; using Manatee.Json.Internal; +using Manatee.Json.Path.ArrayParameters; using Manatee.Json.Path.Operators; using Manatee.Json.Path.Parsing; @@ -26,18 +28,57 @@ public string TryParse(string source, ref int index, out ExpressionTreeNode + { + Path = path, + IsLocal = isLocal, + Name = name.Name + }; } else if (indexOf != null) { path.Operators.Remove(indexOf); + node = new IndexOfExpression + { + Path = path, + IsLocal = isLocal + }; index -= 8; // back up to allow the IndexOfExpressionParser handle it. } + else if (length != null) + { + path.Operators.Remove(length); + node = new LengthExpression + { + Path = path, + IsLocal = isLocal + }; + } + else if (array != null) + { + path.Operators.Remove(array); + var query = array.Query as SliceQuery; + var constant = query?.Slices.FirstOrDefault()?.Index; + if (query == null || query.Slices.Count() != 1 || !constant.HasValue) + { + node = null; + return "JSON Path expression indexers only support single constant values."; + } + node = new ArrayIndexExpression + { + Path = path, + IsLocal = isLocal, + Index = constant.Value + }; + } + else + throw new NotImplementedException(); - node = new PathExpression {Path = path, IsLocal = isLocal}; return null; } } diff --git a/Manatee.Json/Path/Expressions/Translation/ArrayIndexExpressionTranslator.cs b/Manatee.Json/Path/Expressions/Translation/ArrayIndexExpressionTranslator.cs index 0975f45..de91ad7 100644 --- a/Manatee.Json/Path/Expressions/Translation/ArrayIndexExpressionTranslator.cs +++ b/Manatee.Json/Path/Expressions/Translation/ArrayIndexExpressionTranslator.cs @@ -36,21 +36,21 @@ public override ExpressionTreeNode Translate(Expression body) if (method == null) throw new InvalidOperationException(); var parameter = method.Arguments.Last() as ConstantExpression; - if (parameter == null || parameter.Type != typeof (int)) + if (parameter == null || parameter.Type != typeof(int)) { return new ArrayIndexExpression + { + Path = BuildPath(method, out isLocal), + IsLocal = isLocal, + IndexExpression = ExpressionTranslator.TranslateNode(method.Arguments.Last()) + }; + } + return new ArrayIndexExpression { Path = BuildPath(method, out isLocal), IsLocal = isLocal, - IndexExpression = ExpressionTranslator.TranslateNode(method.Arguments.Last()) + Index = (int) parameter.Value, }; - } - return new ArrayIndexExpression - { - Path = BuildPath(method, out isLocal), - IsLocal = isLocal, - Index = (int)parameter.Value, - }; } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Translation/HasPropertyExpressionTranslator.cs b/Manatee.Json/Path/Expressions/Translation/HasPropertyExpressionTranslator.cs index c24f5a7..8dec90a 100644 --- a/Manatee.Json/Path/Expressions/Translation/HasPropertyExpressionTranslator.cs +++ b/Manatee.Json/Path/Expressions/Translation/HasPropertyExpressionTranslator.cs @@ -27,17 +27,23 @@ namespace Manatee.Json.Path.Expressions.Translation { - internal class HasPropertyExpressionTranslator : IExpressionTranslator + internal class HasPropertyExpressionTranslator : PathExpressionTranslator { - public ExpressionTreeNode Translate(Expression body) + public override ExpressionTreeNode Translate(Expression body) { + bool isLocal; var method = body as MethodCallExpression; if (method == null) throw new InvalidOperationException(); var parameter = method.Arguments.Last() as ConstantExpression; if (parameter == null || parameter.Type != typeof(string)) throw new NotSupportedException("Only constant string arguments are supported in HasProperty()"); - return new HasPropertyExpression {Name = parameter.Value.ToString()}; + return new HasPropertyExpression + { + Path = BuildPath(method, out isLocal), + IsLocal = isLocal, + Name = parameter.Value.ToString() + }; } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Operators/ArrayOperator.cs b/Manatee.Json/Path/Operators/ArrayOperator.cs index be8a149..bd6c30d 100644 --- a/Manatee.Json/Path/Operators/ArrayOperator.cs +++ b/Manatee.Json/Path/Operators/ArrayOperator.cs @@ -28,32 +28,32 @@ namespace Manatee.Json.Path.Operators { internal class ArrayOperator : IJsonPathOperator, IEquatable { - private readonly IJsonPathArrayQuery _query; + public IJsonPathArrayQuery Query { get; } public ArrayOperator(IJsonPathArrayQuery query) { - _query = query; + Query = query; } public JsonArray Evaluate(JsonArray json, JsonValue root) { return new JsonArray(json.SelectMany(v => v.Type == JsonValueType.Array - ? _query.Find(v.Array, root) + ? Query.Find(v.Array, root) : v.Type == JsonValueType.Object - ? _query.Find(v.Object.Values.ToJson(), root) + ? Query.Find(v.Object.Values.ToJson(), root) : Enumerable.Empty()) .NotNull()); } public override string ToString() { - return $"[{_query}]"; + return $"[{Query}]"; } public bool Equals(ArrayOperator other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Equals(_query, other._query); + return Equals(Query, other.Query); } public override bool Equals(object obj) { @@ -61,7 +61,7 @@ public override bool Equals(object obj) } public override int GetHashCode() { - return _query?.GetHashCode() ?? 0; + return Query?.GetHashCode() ?? 0; } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Slice.cs b/Manatee.Json/Path/Slice.cs index aa81f66..9d773f1 100644 --- a/Manatee.Json/Path/Slice.cs +++ b/Manatee.Json/Path/Slice.cs @@ -31,18 +31,19 @@ namespace Manatee.Json.Path /// public class Slice : IEquatable { - private int? _index; private int? _start; private int? _end; private int? _step; + internal int? Index { get; } + /// /// Creates a new instance of the class. /// /// A single index. public Slice(int index) { - _index = index; + Index = index; } /// /// Creates a new instance of the class. @@ -62,8 +63,8 @@ public Slice(int? start, int? end, int? step = null) /// 2 public override string ToString() { - return _index.HasValue - ? _index.ToString() + return Index.HasValue + ? Index.ToString() : _step.HasValue ? $"{(_start.HasValue ? _start.ToString() : string.Empty)}:{(_end.HasValue ? _end.ToString() : string.Empty)}:{_step}" : $"{(_start.HasValue ? _start.ToString() : string.Empty)}:{(_end.HasValue ? _end.ToString() : string.Empty)}"; @@ -75,7 +76,7 @@ public bool Equals(Slice other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return _index == other._index && + return Index == other.Index && _start == other._start && _end == other._end && _step == other._step; @@ -95,7 +96,7 @@ public override int GetHashCode() { unchecked { - var hashCode = _index.GetHashCode(); + var hashCode = Index.GetHashCode(); hashCode = (hashCode*397) ^ _start.GetHashCode(); hashCode = (hashCode*397) ^ _end.GetHashCode(); hashCode = (hashCode*397) ^ _step.GetHashCode(); @@ -113,11 +114,11 @@ public override int GetHashCode() internal IEnumerable Find(JsonArray json, JsonValue root) { - if (_index.HasValue) + if (Index.HasValue) { - return json.Count < _index.Value + return json.Count < Index.Value ? Enumerable.Empty() - : new[] { json[_index.Value] }; + : new[] { json[Index.Value] }; } var start = ResolveIndex(_start ?? 0, json.Count); From 8a134055ca4c4078236e722027fb255ab6c55b0b Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Fri, 16 Sep 2016 09:34:02 +1200 Subject: [PATCH 09/18] Added Name() to indexed expression options. Updated ExclusiveOr expression type to function as Exponent operator. Overrode item addition methods in JsonArray to replace null with JsonValue.Null. --- Manatee.Json.Tests/Manatee.Json.Tests.csproj | 1 + Manatee.Json.Tests/Path/EvaluationTest.cs | 42 ++++++++++ .../Path/IndexExpressionParseTest.cs | 92 +++++++++++++++++++--- Manatee.Json/JsonArray.cs | 23 +++++- Manatee.Json/JsonObject.cs | 3 +- .../Translation/ExpressionTranslator.cs | 2 +- .../Translation/PathExpressionTranslator.cs | 6 +- Manatee.Json/Path/PathExpressionExtensions.cs | 10 +++ 8 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 Manatee.Json.Tests/Path/EvaluationTest.cs diff --git a/Manatee.Json.Tests/Manatee.Json.Tests.csproj b/Manatee.Json.Tests/Manatee.Json.Tests.csproj index 1e0602d..30516b0 100644 --- a/Manatee.Json.Tests/Manatee.Json.Tests.csproj +++ b/Manatee.Json.Tests/Manatee.Json.Tests.csproj @@ -170,6 +170,7 @@ + diff --git a/Manatee.Json.Tests/Path/EvaluationTest.cs b/Manatee.Json.Tests/Path/EvaluationTest.cs new file mode 100644 index 0000000..c51ae0d --- /dev/null +++ b/Manatee.Json.Tests/Path/EvaluationTest.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Manatee.Json.Path; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Manatee.Json.Tests.Path +{ + [TestClass] + public class EvaluationTest + { + [TestMethod] + public void ArrayLengthExpression_LastItem() + { + var json = new JsonArray {1, 2, 3}; + var path = JsonPathWith.Array(jv => jv.Length() - 1); + var expected = new JsonArray {3}; + + var actual = path.Evaluate(json); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void ObjectNameLengthExpression_LastItem() + { + var json = new JsonObject + { + {"name", new JsonArray {1, 2, 3}}, + {"name2", new JsonArray {1, 2, 3}}, + {"test", new JsonArray {1, 2}}, + }; + var path = JsonPathWith.Array(jv => JsonPathRoot.Name("test").Length()); + var expected = new JsonArray {new JsonArray {1, 2}}; + + var actual = path.Evaluate(json); + + Assert.AreEqual(expected, actual); + } + } +} diff --git a/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs b/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs index 6acd21f..1dd181e 100644 --- a/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs +++ b/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs @@ -21,17 +21,85 @@ public void Length() Assert.AreEqual(expected, actual); } - // TODO: Add .Name to JsonPathArray extension set. Will need to make HasPropertyExpression derive from PathExpression. - // Paserser can handle it, but need the extension method to test against. - //[TestMethod] - //public void ExtendedLength() - //{ - // var text = "$[(@.name.length)]"; - // var expected = JsonPathWith.Array(jv => jv.Name("name").Length()); - - // var actual = JsonPath.Parse(text); - - // Assert.AreEqual(expected, actual); - //} + [TestMethod] + public void Length_Root() + { + var text = "$[($.length)]"; + var expected = JsonPathWith.Array(jv => JsonPathRoot.Length()); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void ExtendedLength() + { + var text = "$[(@.name.length)]"; + var expected = JsonPathWith.Array(jv => jv.Name("name").Length()); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void Addition() + { + var text = "$[(@.length+1)]"; + var expected = JsonPathWith.Array(jv => jv.Length() + 1); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void Subtraction() + { + var text = "$[(@.length-1)]"; + var expected = JsonPathWith.Array(jv => jv.Length() - 1); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void Multiplication() + { + var text = "$[(@.length*1)]"; + var expected = JsonPathWith.Array(jv => jv.Length()*1); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void Division() + { + var text = "$[(@.length/1)]"; + var expected = JsonPathWith.Array(jv => jv.Length()/1); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void Modulus() + { + var text = "$[(@.length%1)]"; + var expected = JsonPathWith.Array(jv => jv.Length()%1); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void Exponent() + { + var text = "$[(@.length^1)]"; + var expected = JsonPathWith.Array(jv => jv.Length() ^ 1); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } } } diff --git a/Manatee.Json/JsonArray.cs b/Manatee.Json/JsonArray.cs index f3fab9c..bf7334c 100644 --- a/Manatee.Json/JsonArray.cs +++ b/Manatee.Json/JsonArray.cs @@ -21,8 +21,8 @@ ***************************************************************************************/ +using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Manatee.Json.Internal; @@ -72,7 +72,26 @@ public string GetIndentedString(int indentLevel = 0) s += $"{tab1}{this[i].GetIndentedString(indentLevel + 1)}\n{tab0}]"; return s; } - + /// + /// Adds an object to the end of the . + /// + /// The object to be added to the end of the . + /// If the value is null, it will be replaced by . + public new void Add(JsonValue item) + { + base.Add(item ?? JsonValue.Null); + } + /// + /// Adds the elements of the specified collection to the end of the . + /// + /// The collection whose elements should be added to the end of the + /// . The collection itself cannot be null, but it can contain elements + /// that are null. These elements will be replaced by + /// is null. + public new void AddRange(IEnumerable collection) + { + base.AddRange(collection.Select(v => v ?? JsonValue.Null)); + } /// /// Creates a string representation of the JSON data. /// diff --git a/Manatee.Json/JsonObject.cs b/Manatee.Json/JsonObject.cs index be43c75..3a7b22a 100644 --- a/Manatee.Json/JsonObject.cs +++ b/Manatee.Json/JsonObject.cs @@ -88,7 +88,8 @@ public string GetIndentedString(int indentLevel = 0) /// Adds the specified key and value to the dictionary. /// /// The key of the element to add. - /// The value of the element to add. The value can be null for reference types. + /// The value of the element to add. If the value is null, + /// it will be replaced by . public new void Add(string key, JsonValue value) { base.Add(key, value ?? JsonValue.Null); diff --git a/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs b/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs index 664c48e..925e871 100644 --- a/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs +++ b/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs @@ -71,6 +71,7 @@ private static IExpressionTranslator GetNodeTypeBasedTranslator(ExpressionType t case ExpressionType.Multiply: case ExpressionType.MultiplyChecked: return new MultiplyExpressionTranslator(); + case ExpressionType.ExclusiveOr: case ExpressionType.Power: return new ExponentExpressionTranslator(); case ExpressionType.Subtract: @@ -109,7 +110,6 @@ private static IExpressionTranslator GetNodeTypeBasedTranslator(ExpressionType t case ExpressionType.Coalesce: case ExpressionType.Conditional: case ExpressionType.Constant: - case ExpressionType.ExclusiveOr: case ExpressionType.Invoke: case ExpressionType.Lambda: case ExpressionType.LeftShift: diff --git a/Manatee.Json/Path/Expressions/Translation/PathExpressionTranslator.cs b/Manatee.Json/Path/Expressions/Translation/PathExpressionTranslator.cs index 4b979cb..d7fa17d 100644 --- a/Manatee.Json/Path/Expressions/Translation/PathExpressionTranslator.cs +++ b/Manatee.Json/Path/Expressions/Translation/PathExpressionTranslator.cs @@ -36,8 +36,8 @@ internal abstract class PathExpressionTranslator : IExpressionTranslator protected static JsonPath BuildPath(MethodCallExpression method, out bool isLocal) { var path = new JsonPath(); - var currentMethod = method.Arguments.First() as MethodCallExpression; - isLocal = method.Method.Name== "Length" ? method.Arguments.Count != 0 : method.Arguments.Count != 1; + var currentMethod = method.Arguments.FirstOrDefault() as MethodCallExpression; + isLocal = method.Method.Name == "Length" ? method.Arguments.Count != 0 : method.Arguments.Count != 1; while (currentMethod != null) { var parameter = currentMethod.Arguments.Last() as ConstantExpression; @@ -55,7 +55,7 @@ protected static JsonPath BuildPath(MethodCallExpression method, out bool isLoca break; } isLocal = currentMethod.Arguments.Count != 1; - currentMethod = currentMethod.Arguments.First() as MethodCallExpression; + currentMethod = currentMethod.Arguments.FirstOrDefault() as MethodCallExpression; } return path; } diff --git a/Manatee.Json/Path/PathExpressionExtensions.cs b/Manatee.Json/Path/PathExpressionExtensions.cs index a3d56b2..2f8d722 100644 --- a/Manatee.Json/Path/PathExpressionExtensions.cs +++ b/Manatee.Json/Path/PathExpressionExtensions.cs @@ -44,6 +44,16 @@ public static int Length(this JsonPathArray json) /// Specifies the length of a . /// /// The array. + /// The name of the property. + /// The length of the array. + public static JsonPathArray Name(this JsonPathArray json, string name) + { + throw new InvalidOperationException("This operation is reserved for JsonPath."); + } + /// + /// Specifies the length of a . + /// + /// The array. /// The length of the array. public static int Length(this JsonPathValue json) { From 1ec2e2c440a518b8e25a7ca707d41dcf80e3657e Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Fri, 16 Sep 2016 10:19:05 +1200 Subject: [PATCH 10/18] Added parsers for not and or expressions. --- Manatee.Json.Tests/Path/FilterExpressionTest.cs | 102 ++++++++++++++++++++- Manatee.Json/Manatee.Json.csproj | 2 + .../Parsing/JsonPathExpressionParser.cs | 2 +- .../Expressions/Parsing/NotExpressionParser.cs | 16 ++++ .../Path/Expressions/Parsing/OrExpressionParser.cs | 16 ++++ 5 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 Manatee.Json/Path/Expressions/Parsing/NotExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/OrExpressionParser.cs diff --git a/Manatee.Json.Tests/Path/FilterExpressionTest.cs b/Manatee.Json.Tests/Path/FilterExpressionTest.cs index dfae605..408bfd2 100644 --- a/Manatee.Json.Tests/Path/FilterExpressionTest.cs +++ b/Manatee.Json.Tests/Path/FilterExpressionTest.cs @@ -1,5 +1,4 @@ -using System; -using Manatee.Json.Path; +using Manatee.Json.Path; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Manatee.Json.Tests.Path @@ -19,6 +18,61 @@ public void PropertyEqualsValue() } [TestMethod] + public void PropertyNotEqualToValue() + { + var text = "$[?(@.test != 5)]"; + var expected = JsonPathWith.Array(jv => jv.Name("test") != 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void PropertyGreaterThanValue() + { + var text = "$[?(@.test > 5)]"; + var expected = JsonPathWith.Array(jv => jv.Name("test") > 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void PropertyGreaterThanEqualToValue() + { + var text = "$[?(@.test >= 5)]"; + var expected = JsonPathWith.Array(jv => jv.Name("test") >= 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void PropertyLessThanValue() + { + var text = "$[?(@.test < 5)]"; + var expected = JsonPathWith.Array(jv => jv.Name("test") < 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void PropertyLessThanEqualToValue() + { + var text = "$[?(@.test <= 5)]"; + var expected = JsonPathWith.Array(jv => jv.Name("test") <= 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] public void ArrayIndexEqualsValue() { var text = "$[?(@[1] == 5)]"; @@ -107,5 +161,49 @@ public void HasProperty() Assert.AreEqual(expected, actual); } + + [TestMethod] + public void DoesNotHaveProperty() + { + var text = "$[?(!@.test)]"; + var expected = JsonPathWith.Array(jv => !jv.HasProperty("test")); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void GroupedNot() + { + var text = "$[?(!(@.test && @.name == 5))]"; + var expected = JsonPathWith.Array(jv => !(jv.HasProperty("test") && jv.Name("name") == 5)); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void And() + { + var text = "$[?(@.test && @.name == 5)]"; + var expected = JsonPathWith.Array(jv => jv.HasProperty("test") && jv.Name("name") == 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void Or() + { + var text = "$[?(@.test || @.name == 5)]"; + var expected = JsonPathWith.Array(jv => jv.HasProperty("test") || jv.Name("name") == 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } } } diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index 2970d44..19f18d3 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -248,6 +248,8 @@ + + diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs index 4cde706..cb54f55 100644 --- a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -63,7 +63,7 @@ private static ExpressionTreeNode BuildTree(IList> n { var split = nodes.IndexOf(root); var right = nodes.Skip(split + 1).FirstOrDefault(); - not.Root = right; + not.Root = CheckNode(right, null); } return root; } diff --git a/Manatee.Json/Path/Expressions/Parsing/NotExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/NotExpressionParser.cs new file mode 100644 index 0000000..e5ca09b --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/NotExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class NotExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("!") && !input.StartsWith("!="); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = new NotExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/OrExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/OrExpressionParser.cs new file mode 100644 index 0000000..09b1581 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/OrExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class OrExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith("||"); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index += 2; + node = new OrExpression(); + return null; + } + } +} \ No newline at end of file From 4ac7ab6d72e017e5a43ea2b11e4a3171479dedb0 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Fri, 16 Sep 2016 10:54:30 +1200 Subject: [PATCH 11/18] Added grouped expression parsing. --- Manatee.Json/Manatee.Json.csproj | 4 +- Manatee.Json/Path/Expressions/AddExpression.cs | 2 +- Manatee.Json/Path/Expressions/AndExpression.cs | 2 +- .../Path/Expressions/ArrayIndexExpression.cs | 3 +- .../Path/Expressions/ConversionExpression.cs | 3 +- Manatee.Json/Path/Expressions/DivideExpression.cs | 2 +- .../Path/Expressions/ExponentExpression.cs | 2 +- .../Path/Expressions/ExpressionTreeNode.cs | 11 ++++- Manatee.Json/Path/Expressions/FieldExpression.cs | 3 +- Manatee.Json/Path/Expressions/GroupExpression.cs | 57 ---------------------- .../Path/Expressions/HasPropertyExpression.cs | 3 +- Manatee.Json/Path/Expressions/IndexOfExpression.cs | 3 +- Manatee.Json/Path/Expressions/IsEqualExpression.cs | 2 +- .../Expressions/IsGreaterThanEqualExpression.cs | 2 +- .../Path/Expressions/IsGreaterThanExpression.cs | 2 +- .../Path/Expressions/IsLessThanEqualExpression.cs | 2 +- .../Path/Expressions/IsLessThanExpression.cs | 2 +- .../Path/Expressions/IsNotEqualExpression.cs | 2 +- Manatee.Json/Path/Expressions/LengthExpression.cs | 2 +- Manatee.Json/Path/Expressions/ModuloExpression.cs | 2 +- .../Path/Expressions/MultiplyExpression.cs | 2 +- Manatee.Json/Path/Expressions/NameExpresssion.cs | 3 +- Manatee.Json/Path/Expressions/NegateExpression.cs | 3 +- Manatee.Json/Path/Expressions/NotExpression.cs | 3 +- Manatee.Json/Path/Expressions/OrExpression.cs | 2 +- .../Parsing/ConstantStringExpressionParser.cs | 31 ------------ .../Expressions/Parsing/ExpressionEndParser.cs | 16 ++++++ .../Expressions/Parsing/GroupExpressionParser.cs | 17 +++++++ .../Parsing/JsonPathExpressionParser.cs | 21 ++++++-- .../Expressions/Parsing/LengthExpressionParser.cs | 16 ++++++ Manatee.Json/Path/Expressions/PathExpression.cs | 3 +- .../Path/Expressions/SubtractExpression.cs | 2 +- Manatee.Json/Path/Expressions/ValueExpression.cs | 3 +- 33 files changed, 114 insertions(+), 119 deletions(-) delete mode 100644 Manatee.Json/Path/Expressions/GroupExpression.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/ExpressionEndParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/GroupExpressionParser.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/LengthExpressionParser.cs diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index 19f18d3..b6378ab 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -239,6 +239,8 @@ + + @@ -246,6 +248,7 @@ + @@ -269,7 +272,6 @@ - diff --git a/Manatee.Json/Path/Expressions/AddExpression.cs b/Manatee.Json/Path/Expressions/AddExpression.cs index bf44d59..87bdf29 100644 --- a/Manatee.Json/Path/Expressions/AddExpression.cs +++ b/Manatee.Json/Path/Expressions/AddExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class AddExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 2; + protected override int BasePriority => 2; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/AndExpression.cs b/Manatee.Json/Path/Expressions/AndExpression.cs index 4c55340..ebf61e2 100644 --- a/Manatee.Json/Path/Expressions/AndExpression.cs +++ b/Manatee.Json/Path/Expressions/AndExpression.cs @@ -26,7 +26,7 @@ namespace Manatee.Json.Path.Expressions { internal class AndExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 0; + protected override int BasePriority => 0; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs b/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs index d2a440c..b00a1a2 100644 --- a/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs +++ b/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs @@ -28,10 +28,11 @@ namespace Manatee.Json.Path.Expressions { internal class ArrayIndexExpression : PathExpression, IEquatable> { - public override int Priority => 6; public int Index { get; set; } public ExpressionTreeNode IndexExpression { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { var value = IsLocal ? json as JsonValue : root; diff --git a/Manatee.Json/Path/Expressions/ConversionExpression.cs b/Manatee.Json/Path/Expressions/ConversionExpression.cs index 9dfe129..49ea350 100644 --- a/Manatee.Json/Path/Expressions/ConversionExpression.cs +++ b/Manatee.Json/Path/Expressions/ConversionExpression.cs @@ -27,10 +27,11 @@ namespace Manatee.Json.Path.Expressions { internal class ConversionExpression : ExpressionTreeNode, IEquatable> { - public override int Priority => 6; public ExpressionTreeNode Root { get; set; } public Type TargetType { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { var value = Root.Evaluate(json, root); diff --git a/Manatee.Json/Path/Expressions/DivideExpression.cs b/Manatee.Json/Path/Expressions/DivideExpression.cs index b143e6c..e875e2c 100644 --- a/Manatee.Json/Path/Expressions/DivideExpression.cs +++ b/Manatee.Json/Path/Expressions/DivideExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class DivideExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 3; + protected override int BasePriority => 3; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/ExponentExpression.cs b/Manatee.Json/Path/Expressions/ExponentExpression.cs index e852c4d..defc193 100644 --- a/Manatee.Json/Path/Expressions/ExponentExpression.cs +++ b/Manatee.Json/Path/Expressions/ExponentExpression.cs @@ -26,7 +26,7 @@ namespace Manatee.Json.Path.Expressions { internal class ExponentExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 4; + protected override int BasePriority => 4; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/ExpressionTreeNode.cs b/Manatee.Json/Path/Expressions/ExpressionTreeNode.cs index dbe2d84..a4f16d0 100644 --- a/Manatee.Json/Path/Expressions/ExpressionTreeNode.cs +++ b/Manatee.Json/Path/Expressions/ExpressionTreeNode.cs @@ -24,8 +24,17 @@ namespace Manatee.Json.Path.Expressions { internal abstract class ExpressionTreeNode { - public abstract int Priority { get; } + private int _priorityBump; + + public int Priority => BasePriority + _priorityBump; + + protected abstract int BasePriority { get; } public abstract object Evaluate(T json, JsonValue root); + + public void BumpPriority() + { + _priorityBump += 10; + } } } \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/FieldExpression.cs b/Manatee.Json/Path/Expressions/FieldExpression.cs index edd46ff..4ccb974 100644 --- a/Manatee.Json/Path/Expressions/FieldExpression.cs +++ b/Manatee.Json/Path/Expressions/FieldExpression.cs @@ -28,10 +28,11 @@ namespace Manatee.Json.Path.Expressions { internal class FieldExpression : ExpressionTreeNode, IEquatable> { - public override int Priority => 6; public FieldInfo Field { get; set; } public object Source { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { if (Field.FieldType == typeof(string)) diff --git a/Manatee.Json/Path/Expressions/GroupExpression.cs b/Manatee.Json/Path/Expressions/GroupExpression.cs deleted file mode 100644 index cd79e54..0000000 --- a/Manatee.Json/Path/Expressions/GroupExpression.cs +++ /dev/null @@ -1,57 +0,0 @@ -/*************************************************************************************** - - Copyright 2016 Greg Dennis - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - File Name: GroupExpression.cs - Namespace: Manatee.Json.Path.Expressions - Class Name: GroupExpression - Purpose: Expresses the intent to group an expression as a component - in a larger expression. - -***************************************************************************************/ - -using System; - -namespace Manatee.Json.Path.Expressions -{ - internal class GroupExpression : ExpressionTreeNode, IEquatable> - { - public override int Priority => 6; - public ExpressionTreeNode Group { get; set; } - - public override object Evaluate(T json, JsonValue root) - { - return Group.Evaluate(json, root); - } - public override string ToString() - { - return $"({Group})"; - } - public bool Equals(GroupExpression other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(Group, other.Group); - } - public override bool Equals(object obj) - { - return Equals(obj as GroupExpression); - } - public override int GetHashCode() - { - return Group?.GetHashCode() ?? 0; - } - } -} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/HasPropertyExpression.cs b/Manatee.Json/Path/Expressions/HasPropertyExpression.cs index 5512460..79d7bec 100644 --- a/Manatee.Json/Path/Expressions/HasPropertyExpression.cs +++ b/Manatee.Json/Path/Expressions/HasPropertyExpression.cs @@ -26,9 +26,10 @@ namespace Manatee.Json.Path.Expressions { internal class HasPropertyExpression : PathExpression, IEquatable> { - public override int Priority => 6; public string Name { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { var value = json as JsonValue; diff --git a/Manatee.Json/Path/Expressions/IndexOfExpression.cs b/Manatee.Json/Path/Expressions/IndexOfExpression.cs index 30b4592..6c5d169 100644 --- a/Manatee.Json/Path/Expressions/IndexOfExpression.cs +++ b/Manatee.Json/Path/Expressions/IndexOfExpression.cs @@ -27,10 +27,11 @@ namespace Manatee.Json.Path.Expressions { internal class IndexOfExpression : PathExpression, IEquatable> { - public override int Priority => 6; public JsonValue Parameter { get; set; } public ExpressionTreeNode ParameterExpression { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { var value = IsLocal ? json as JsonValue : root; diff --git a/Manatee.Json/Path/Expressions/IsEqualExpression.cs b/Manatee.Json/Path/Expressions/IsEqualExpression.cs index 1b1fac6..3221031 100644 --- a/Manatee.Json/Path/Expressions/IsEqualExpression.cs +++ b/Manatee.Json/Path/Expressions/IsEqualExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class IsEqualExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 1; + protected override int BasePriority => 1; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/IsGreaterThanEqualExpression.cs b/Manatee.Json/Path/Expressions/IsGreaterThanEqualExpression.cs index b519713..8f1e0e4 100644 --- a/Manatee.Json/Path/Expressions/IsGreaterThanEqualExpression.cs +++ b/Manatee.Json/Path/Expressions/IsGreaterThanEqualExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class IsGreaterThanEqualExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 1; + protected override int BasePriority => 1; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/IsGreaterThanExpression.cs b/Manatee.Json/Path/Expressions/IsGreaterThanExpression.cs index d712d8e..93d9ba2 100644 --- a/Manatee.Json/Path/Expressions/IsGreaterThanExpression.cs +++ b/Manatee.Json/Path/Expressions/IsGreaterThanExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class IsGreaterThanExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 1; + protected override int BasePriority => 1; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/IsLessThanEqualExpression.cs b/Manatee.Json/Path/Expressions/IsLessThanEqualExpression.cs index 0e32b59..cdfa826 100644 --- a/Manatee.Json/Path/Expressions/IsLessThanEqualExpression.cs +++ b/Manatee.Json/Path/Expressions/IsLessThanEqualExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class IsLessThanEqualExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 1; + protected override int BasePriority => 1; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/IsLessThanExpression.cs b/Manatee.Json/Path/Expressions/IsLessThanExpression.cs index 33bb137..a1952d0 100644 --- a/Manatee.Json/Path/Expressions/IsLessThanExpression.cs +++ b/Manatee.Json/Path/Expressions/IsLessThanExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class IsLessThanExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 1; + protected override int BasePriority => 1; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/IsNotEqualExpression.cs b/Manatee.Json/Path/Expressions/IsNotEqualExpression.cs index 0bf3e5f..d7c36cc 100644 --- a/Manatee.Json/Path/Expressions/IsNotEqualExpression.cs +++ b/Manatee.Json/Path/Expressions/IsNotEqualExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class IsNotEqualExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 1; + protected override int BasePriority => 1; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/LengthExpression.cs b/Manatee.Json/Path/Expressions/LengthExpression.cs index 2265a7a..c50bf9b 100644 --- a/Manatee.Json/Path/Expressions/LengthExpression.cs +++ b/Manatee.Json/Path/Expressions/LengthExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class LengthExpression : PathExpression, IEquatable> { - public override int Priority => 6; + protected override int BasePriority => 6; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/ModuloExpression.cs b/Manatee.Json/Path/Expressions/ModuloExpression.cs index 60518fc..93f846d 100644 --- a/Manatee.Json/Path/Expressions/ModuloExpression.cs +++ b/Manatee.Json/Path/Expressions/ModuloExpression.cs @@ -26,7 +26,7 @@ namespace Manatee.Json.Path.Expressions { internal class ModuloExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 2; + protected override int BasePriority => 2; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/MultiplyExpression.cs b/Manatee.Json/Path/Expressions/MultiplyExpression.cs index f988bad..3892ce0 100644 --- a/Manatee.Json/Path/Expressions/MultiplyExpression.cs +++ b/Manatee.Json/Path/Expressions/MultiplyExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class MultiplyExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 3; + protected override int BasePriority => 3; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/NameExpresssion.cs b/Manatee.Json/Path/Expressions/NameExpresssion.cs index ab8e9dc..ff37b04 100644 --- a/Manatee.Json/Path/Expressions/NameExpresssion.cs +++ b/Manatee.Json/Path/Expressions/NameExpresssion.cs @@ -28,10 +28,11 @@ namespace Manatee.Json.Path.Expressions { internal class NameExpression : PathExpression, IEquatable> { - public override int Priority => 6; public string Name { get; set; } public ExpressionTreeNode NameExp { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { var value = IsLocal ? json as JsonValue : root; diff --git a/Manatee.Json/Path/Expressions/NegateExpression.cs b/Manatee.Json/Path/Expressions/NegateExpression.cs index 5254864..77deaec 100644 --- a/Manatee.Json/Path/Expressions/NegateExpression.cs +++ b/Manatee.Json/Path/Expressions/NegateExpression.cs @@ -26,9 +26,10 @@ namespace Manatee.Json.Path.Expressions { internal class NegateExpression : ExpressionTreeNode, IEquatable> { - public override int Priority => 6; public ExpressionTreeNode Root { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { return -Convert.ToDouble(Root.Evaluate(json, root)); diff --git a/Manatee.Json/Path/Expressions/NotExpression.cs b/Manatee.Json/Path/Expressions/NotExpression.cs index f194c95..3c5d550 100644 --- a/Manatee.Json/Path/Expressions/NotExpression.cs +++ b/Manatee.Json/Path/Expressions/NotExpression.cs @@ -26,9 +26,10 @@ namespace Manatee.Json.Path.Expressions { internal class NotExpression : ExpressionTreeNode, IEquatable> { - public override int Priority => 5; public ExpressionTreeNode Root { get; set; } + protected override int BasePriority => 5; + public override object Evaluate(T json, JsonValue root) { var result = Root.Evaluate(json, root); diff --git a/Manatee.Json/Path/Expressions/OrExpression.cs b/Manatee.Json/Path/Expressions/OrExpression.cs index f72540d..10f1e12 100644 --- a/Manatee.Json/Path/Expressions/OrExpression.cs +++ b/Manatee.Json/Path/Expressions/OrExpression.cs @@ -26,7 +26,7 @@ namespace Manatee.Json.Path.Expressions { internal class OrExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 0; + protected override int BasePriority => 0; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs index b295f38..c347b15 100644 --- a/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Manatee.Json.Internal; namespace Manatee.Json.Path.Expressions.Parsing @@ -17,32 +14,4 @@ public string TryParse(string source, ref int index, out ExpressionTreeNode 7 && !char.IsLetterOrDigit(input[7]); - } - public string TryParse(string source, ref int index, out ExpressionTreeNode node) - { - index += 7; - node = new LengthExpression(); - return null; - } - } - - internal class ExpressionEndParser : IJsonPathExpressionParser - { - public bool Handles(string input) - { - return input[0] == ')'; - } - public string TryParse(string source, ref int index, out ExpressionTreeNode node) - { - index++; - node = null; - return null; - } - } } diff --git a/Manatee.Json/Path/Expressions/Parsing/ExpressionEndParser.cs b/Manatee.Json/Path/Expressions/Parsing/ExpressionEndParser.cs new file mode 100644 index 0000000..b2f69f3 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/ExpressionEndParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class ExpressionEndParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input[0] == ')'; + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + node = null; + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/GroupExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/GroupExpressionParser.cs new file mode 100644 index 0000000..10ab145 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/GroupExpressionParser.cs @@ -0,0 +1,17 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class GroupExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input[0] == '('; + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index++; + var error = JsonPathExpressionParser.Parse(source, ref index, out node); + node?.BumpPriority(); + return error; + } + } +} diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs index cb54f55..b33678f 100644 --- a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -20,10 +20,23 @@ static JsonPathExpressionParser() public static string Parse(string source, ref int index, out Expression expr) { + ExpressionTreeNode root; + var error = Parse(source, ref index, out root); + if (error != null) + { + expr = null; + return error; + } + + expr = new Expression(root); + return null; + } + public static string Parse(string source, ref int index, out ExpressionTreeNode root) + { var nodes = new List>(); var length = source.Length; ExpressionTreeNode node; - expr = null; + root = null; do { char c; @@ -38,9 +51,9 @@ static JsonPathExpressionParser() nodes.Add(node); } while (index < length && node != null); - expr = new Expression(nodes.Count == 1 - ? CheckNode(nodes[0], null) - : BuildTree(nodes)); + root = nodes.Count == 1 + ? CheckNode(nodes[0], null) + : BuildTree(nodes); return null; } diff --git a/Manatee.Json/Path/Expressions/Parsing/LengthExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/LengthExpressionParser.cs new file mode 100644 index 0000000..2d64a88 --- /dev/null +++ b/Manatee.Json/Path/Expressions/Parsing/LengthExpressionParser.cs @@ -0,0 +1,16 @@ +namespace Manatee.Json.Path.Expressions.Parsing +{ + internal class LengthExpressionParser : IJsonPathExpressionParser + { + public bool Handles(string input) + { + return input.StartsWith(".length") && input.Length > 7 && !char.IsLetterOrDigit(input[7]); + } + public string TryParse(string source, ref int index, out ExpressionTreeNode node) + { + index += 7; + node = new LengthExpression(); + return null; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/PathExpression.cs b/Manatee.Json/Path/Expressions/PathExpression.cs index b8dc221..d14378a 100644 --- a/Manatee.Json/Path/Expressions/PathExpression.cs +++ b/Manatee.Json/Path/Expressions/PathExpression.cs @@ -27,10 +27,11 @@ namespace Manatee.Json.Path.Expressions { internal class PathExpression : ExpressionTreeNode, IEquatable> { - public override int Priority => 6; public JsonPath Path { get; set; } public bool IsLocal { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { var value = IsLocal ? json as JsonValue : root; diff --git a/Manatee.Json/Path/Expressions/SubtractExpression.cs b/Manatee.Json/Path/Expressions/SubtractExpression.cs index 9045367..b2940c4 100644 --- a/Manatee.Json/Path/Expressions/SubtractExpression.cs +++ b/Manatee.Json/Path/Expressions/SubtractExpression.cs @@ -27,7 +27,7 @@ namespace Manatee.Json.Path.Expressions { internal class SubtractExpression : ExpressionTreeBranch, IEquatable> { - public override int Priority => 2; + protected override int BasePriority => 2; public override object Evaluate(T json, JsonValue root) { diff --git a/Manatee.Json/Path/Expressions/ValueExpression.cs b/Manatee.Json/Path/Expressions/ValueExpression.cs index d4f2ab5..1f3eaa3 100644 --- a/Manatee.Json/Path/Expressions/ValueExpression.cs +++ b/Manatee.Json/Path/Expressions/ValueExpression.cs @@ -27,9 +27,10 @@ namespace Manatee.Json.Path.Expressions { internal class ValueExpression : ExpressionTreeNode, IEquatable> { - public override int Priority => 6; public object Value { get; set; } + protected override int BasePriority => 6; + public override object Evaluate(T json, JsonValue root) { return Value; From aedfd42367a74446631957e39fd9017c89d047c6 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Fri, 16 Sep 2016 14:04:13 +1200 Subject: [PATCH 12/18] Removed the ExclusiveOr expression type mapping to exponentiation as it doesn't prioritize right with C# and is confusing. Added more tests. BUG FIX: #13 - Issues with tests running concurrently. --- Manatee.Json.Tests/Manatee.Json.Tests.csproj | 1 + Manatee.Json.Tests/Path/EvaluationTest.cs | 7 +-- Manatee.Json.Tests/Path/FilterExpressionTest.cs | 15 ----- .../Path/IndexExpressionParseTest.cs | 31 ++++++++-- Manatee.Json.Tests/Path/ToStringTests.cs | 71 ++++++++++++++++++++++ .../Serialization/JsonDeserializerTest.cs | 2 + Manatee.Json.sln.DotSettings | 3 +- Manatee.Json.sln.DotSettings.user | 3 + .../Parsing/JsonPathExpressionParser.cs | 6 +- .../Path/Expressions/SubtractExpression.cs | 2 +- .../Translation/ExpressionTranslator.cs | 2 +- Manatee.Json/Path/JsonPathArray.cs | 2 +- Manatee.Json/Path/JsonPathValue.cs | 37 +---------- Manatee.Json/Path/Parsing/PathParsingExtensions.cs | 2 +- 14 files changed, 115 insertions(+), 69 deletions(-) create mode 100644 Manatee.Json.Tests/Path/ToStringTests.cs diff --git a/Manatee.Json.Tests/Manatee.Json.Tests.csproj b/Manatee.Json.Tests/Manatee.Json.Tests.csproj index 30516b0..2068830 100644 --- a/Manatee.Json.Tests/Manatee.Json.Tests.csproj +++ b/Manatee.Json.Tests/Manatee.Json.Tests.csproj @@ -174,6 +174,7 @@ + diff --git a/Manatee.Json.Tests/Path/EvaluationTest.cs b/Manatee.Json.Tests/Path/EvaluationTest.cs index c51ae0d..586481a 100644 --- a/Manatee.Json.Tests/Path/EvaluationTest.cs +++ b/Manatee.Json.Tests/Path/EvaluationTest.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Manatee.Json.Path; +using Manatee.Json.Path; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Manatee.Json.Tests.Path diff --git a/Manatee.Json.Tests/Path/FilterExpressionTest.cs b/Manatee.Json.Tests/Path/FilterExpressionTest.cs index 408bfd2..1416eee 100644 --- a/Manatee.Json.Tests/Path/FilterExpressionTest.cs +++ b/Manatee.Json.Tests/Path/FilterExpressionTest.cs @@ -16,7 +16,6 @@ public void PropertyEqualsValue() Assert.AreEqual(expected, actual); } - [TestMethod] public void PropertyNotEqualToValue() { @@ -27,7 +26,6 @@ public void PropertyNotEqualToValue() Assert.AreEqual(expected, actual); } - [TestMethod] public void PropertyGreaterThanValue() { @@ -38,7 +36,6 @@ public void PropertyGreaterThanValue() Assert.AreEqual(expected, actual); } - [TestMethod] public void PropertyGreaterThanEqualToValue() { @@ -49,7 +46,6 @@ public void PropertyGreaterThanEqualToValue() Assert.AreEqual(expected, actual); } - [TestMethod] public void PropertyLessThanValue() { @@ -60,7 +56,6 @@ public void PropertyLessThanValue() Assert.AreEqual(expected, actual); } - [TestMethod] public void PropertyLessThanEqualToValue() { @@ -71,7 +66,6 @@ public void PropertyLessThanEqualToValue() Assert.AreEqual(expected, actual); } - [TestMethod] public void ArrayIndexEqualsValue() { @@ -82,7 +76,6 @@ public void ArrayIndexEqualsValue() Assert.AreEqual(expected, actual); } - [TestMethod] [ExpectedException(typeof(JsonPathSyntaxException))] public void ArrayMultiIndex() @@ -99,7 +92,6 @@ public void ArrayMultiIndex() throw; } } - [TestMethod] [ExpectedException(typeof(JsonPathSyntaxException))] public void ArraySliceEqualsValue() @@ -116,7 +108,6 @@ public void ArraySliceEqualsValue() throw; } } - [TestMethod] [ExpectedException(typeof(JsonPathSyntaxException))] public void ArrayIndexExpressionEqualsValue() @@ -133,7 +124,6 @@ public void ArrayIndexExpressionEqualsValue() throw; } } - [TestMethod] [ExpectedException(typeof(JsonPathSyntaxException))] public void ArrayFilterExpressionEqualsValue() @@ -150,7 +140,6 @@ public void ArrayFilterExpressionEqualsValue() throw; } } - [TestMethod] public void HasProperty() { @@ -161,7 +150,6 @@ public void HasProperty() Assert.AreEqual(expected, actual); } - [TestMethod] public void DoesNotHaveProperty() { @@ -172,7 +160,6 @@ public void DoesNotHaveProperty() Assert.AreEqual(expected, actual); } - [TestMethod] public void GroupedNot() { @@ -183,7 +170,6 @@ public void GroupedNot() Assert.AreEqual(expected, actual); } - [TestMethod] public void And() { @@ -194,7 +180,6 @@ public void And() Assert.AreEqual(expected, actual); } - [TestMethod] public void Or() { diff --git a/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs b/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs index 1dd181e..03aff96 100644 --- a/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs +++ b/Manatee.Json.Tests/Path/IndexExpressionParseTest.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Manatee.Json.Path; +using Manatee.Json.Path; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Manatee.Json.Tests.Path @@ -92,6 +87,10 @@ public void Modulus() Assert.AreEqual(expected, actual); } [TestMethod] + [Ignore] + // The C# ^ operator doesn't have the required exponentiation priority, so + // constructing expressions with this operator results in a strange structure. + // Also not really sure JS supports it as an exponentiation operator, either. public void Exponent() { var text = "$[(@.length^1)]"; @@ -101,5 +100,25 @@ public void Exponent() Assert.AreEqual(expected, actual); } + [TestMethod] + public void Add3() + { + var text = "$[(3+@.length+3)]"; + var expected = JsonPathWith.Array(jv => 3 + jv.Length() + 3); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void WhyOnGodsGreenEarthWouldAnyoneDoThis() + { + var text = "$[(4+@.length*($.name.length-2)+5)]"; + var expected = JsonPathWith.Array(jv => 4 + jv.Length()*(JsonPathRoot.Name("name").Length() - 2) + 5); + + var actual = JsonPath.Parse(text); + + Assert.AreEqual(expected, actual); + } } } diff --git a/Manatee.Json.Tests/Path/ToStringTests.cs b/Manatee.Json.Tests/Path/ToStringTests.cs new file mode 100644 index 0000000..d07dd43 --- /dev/null +++ b/Manatee.Json.Tests/Path/ToStringTests.cs @@ -0,0 +1,71 @@ +using Manatee.Json.Path; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Manatee.Json.Tests.Path +{ + [TestClass] + public class ToStringTests + { + private void Run(string expected, JsonPath path) + { + var actual = path.ToString(); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void JustRoot() + { + Run("$", new JsonPath()); + } + [TestMethod] + public void ObjectKey() + { + Run("$.name", JsonPathWith.Name("name")); + } + [TestMethod] + public void ArrayIndex_SingleConstant() + { + Run("$[0]", JsonPathWith.Array(0)); + } + [TestMethod] + public void ArrayIndex_SingleSlice() + { + Run("$[0:6]", JsonPathWith.Array(new Slice(0, 6))); + } + [TestMethod] + public void ArrayIndex_SingleSliceStartEndStep() + { + Run("$[0:6:2]", JsonPathWith.Array(new Slice(0, 6, 2))); + } + [TestMethod] + public void ArrayIndex_SingleSliceStart() + { + Run("$[0:]", JsonPathWith.Array(new Slice(0, null))); + } + [TestMethod] + public void ArrayIndex_SingleSliceEnd() + { + Run("$[:6]", JsonPathWith.Array(new Slice(null, 6))); + } + [TestMethod] + public void ArrayIndex_SingleSliceStartStep() + { + Run("$[0::2]", JsonPathWith.Array(new Slice(0, null, 2))); + } + [TestMethod] + public void ArrayIndex_SingleEndStep() + { + Run("$[:6:2]", JsonPathWith.Array(new Slice(null, 6, 2))); + } + [TestMethod] + public void ArrayIndex_SingleSliceStep() + { + Run("$[::2]", JsonPathWith.Array(new Slice(null, null, 2))); + } + [TestMethod] + public void ArrayIndex_ConstantSlice() + { + Run("$[1,4:6]", JsonPathWith.Array(1, new Slice(4, 6))); + } + } +} diff --git a/Manatee.Json.Tests/Serialization/JsonDeserializerTest.cs b/Manatee.Json.Tests/Serialization/JsonDeserializerTest.cs index f08d92d..e60233f 100644 --- a/Manatee.Json.Tests/Serialization/JsonDeserializerTest.cs +++ b/Manatee.Json.Tests/Serialization/JsonDeserializerTest.cs @@ -286,6 +286,7 @@ public void InterfaceWithoutMap_Successful() { {"RequiredProp", "test"} }; + JsonSerializationAbstractionMap.RemoveMap(); IInterface expected = new ImplementationClass {RequiredProp = "test"}; var actual = serializer.Deserialize(json); @@ -562,6 +563,7 @@ public void UnimplementedInterface_ReturnsRunTimeImplementation() { var serializer = new JsonSerializer(); var json = new JsonObject {{"RequiredProp", "test"}}; + JsonSerializationAbstractionMap.RemoveMap(); IInterface expected = new ImplementationClass {RequiredProp = "test"}; var actual = serializer.Deserialize(json); diff --git a/Manatee.Json.sln.DotSettings b/Manatee.Json.sln.DotSettings index f683f41..f70fddb 100644 --- a/Manatee.Json.sln.DotSettings +++ b/Manatee.Json.sln.DotSettings @@ -23,4 +23,5 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True True - True \ No newline at end of file + True + <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Manatee.Json" ModuleVersionMask="*" ClassMask="Manatee.Json.Path.JsonPathRoot" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file diff --git a/Manatee.Json.sln.DotSettings.user b/Manatee.Json.sln.DotSettings.user index 0e7f399..2fc9a05 100644 --- a/Manatee.Json.sln.DotSettings.user +++ b/Manatee.Json.sln.DotSettings.user @@ -1,5 +1,8 @@  SOLUTION + True + 2016.2 + C:\Users\Gregd\AppData\Local\Temp\ssm.Pohepiq.tmp diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs index b33678f..d1c4d59 100644 --- a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -57,15 +57,15 @@ public static string Parse(string source, ref int index, out ExpressionTree return null; } - private static ExpressionTreeNode BuildTree(IList> nodes) + private static ExpressionTreeNode BuildTree(List> nodes) { if (!nodes.Any()) return null; var minPriority = nodes.Min(n => n.Priority); var root = nodes.Last(n => n.Priority == minPriority); var branch = root as ExpressionTreeBranch; - if (branch != null) + if (branch != null && branch.Right == null && branch.Left == null) { - var split = nodes.IndexOf(root); + var split = nodes.LastIndexOf(root); var left = nodes.Take(split).ToList(); var right = nodes.Skip(split + 1).ToList(); branch.Left = CheckNode(BuildTree(left), branch); diff --git a/Manatee.Json/Path/Expressions/SubtractExpression.cs b/Manatee.Json/Path/Expressions/SubtractExpression.cs index b2940c4..7da05e7 100644 --- a/Manatee.Json/Path/Expressions/SubtractExpression.cs +++ b/Manatee.Json/Path/Expressions/SubtractExpression.cs @@ -38,7 +38,7 @@ public override object Evaluate(T json, JsonValue root) } public override string ToString() { - if (Right.Priority == Priority) + if (Right?.Priority == Priority) return $"{Left}-({Right})"; return $"{Left}-{Right}"; } diff --git a/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs b/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs index 925e871..664c48e 100644 --- a/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs +++ b/Manatee.Json/Path/Expressions/Translation/ExpressionTranslator.cs @@ -71,7 +71,6 @@ private static IExpressionTranslator GetNodeTypeBasedTranslator(ExpressionType t case ExpressionType.Multiply: case ExpressionType.MultiplyChecked: return new MultiplyExpressionTranslator(); - case ExpressionType.ExclusiveOr: case ExpressionType.Power: return new ExponentExpressionTranslator(); case ExpressionType.Subtract: @@ -110,6 +109,7 @@ private static IExpressionTranslator GetNodeTypeBasedTranslator(ExpressionType t case ExpressionType.Coalesce: case ExpressionType.Conditional: case ExpressionType.Constant: + case ExpressionType.ExclusiveOr: case ExpressionType.Invoke: case ExpressionType.Lambda: case ExpressionType.LeftShift: diff --git a/Manatee.Json/Path/JsonPathArray.cs b/Manatee.Json/Path/JsonPathArray.cs index 8826d6f..26f1563 100644 --- a/Manatee.Json/Path/JsonPathArray.cs +++ b/Manatee.Json/Path/JsonPathArray.cs @@ -26,7 +26,7 @@ namespace Manatee.Json.Path /// /// Serves as a stand-in for JsonArray in Path Expressions. /// - public class JsonPathArray + public sealed class JsonPathArray { } diff --git a/Manatee.Json/Path/JsonPathValue.cs b/Manatee.Json/Path/JsonPathValue.cs index 955825d..dfaa54e 100644 --- a/Manatee.Json/Path/JsonPathValue.cs +++ b/Manatee.Json/Path/JsonPathValue.cs @@ -28,41 +28,10 @@ namespace Manatee.Json.Path /// /// Serves as a stand-in for JsonValue in Path Expressions. /// - public class JsonPathValue +#pragma warning disable 660,661 + public sealed class JsonPathValue +#pragma warning restore 660,661 { - /// - /// Determines whether the specified is equal to the current . - /// - /// - /// true if the specified is equal to the current ; otherwise, false. - /// - /// The to compare with the current . 2 - protected bool Equals(JsonPathValue other) - { - throw new InvalidOperationException("This operation is reserved for JsonPath."); - } - /// - /// Determines whether the specified is equal to the current . - /// - /// - /// true if the specified is equal to the current ; otherwise, false. - /// - /// The to compare with the current . 2 - public override bool Equals(object obj) - { - throw new InvalidOperationException("This operation is reserved for JsonPath."); - } - /// - /// Serves as a hash function for a particular type. - /// - /// - /// A hash code for the current . - /// - /// 2 - public override int GetHashCode() - { - throw new InvalidOperationException("This operation is reserved for JsonPath."); - } /// /// /// diff --git a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs index c60ed2e..fa1aa46 100644 --- a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs +++ b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs @@ -62,7 +62,7 @@ private static string GetBasicKey(string source, ref int index, out string key) } c = source[index]; index++; - if (!char.IsLetterOrDigit(c)) + if (!(char.IsLetterOrDigit(c) || c == '_')) { complete = true; index--; From 5bc8bb8f8d9381fa8a78b83f257733ef68848b6b Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 21 Sep 2016 11:05:45 +1200 Subject: [PATCH 13/18] Added a lot more individual tests. Submoduled schema test suite directory. Added submodule for path test suite. --- .gitmodules | 6 + JSON-Schema-Test-Suite/README.md | 1 + JSON-Schema-Test-Suite/tests/draft4/ref.json | 20 ++ Json-Path-Test-Suite | 1 + Json-Schema-Test-Suite/.gitignore | 1 + Manatee.Json.Tests/Manatee.Json.Tests.csproj | 5 +- Manatee.Json.Tests/Path/FilterExpressionTest.cs | 48 ++++- Manatee.Json.Tests/Path/ParsingTest.cs | 37 +++- .../Path/TestSuite/JsonPathTestSuite.cs | 120 +++++++++++ Manatee.Json.Tests/Path/TestSuite/PathTest.cs | 22 ++ Manatee.Json.Tests/Path/TestSuite/PathTestSet.cs | 24 +++ Manatee.Json.Tests/Path/ToStringTest.cs | 223 +++++++++++++++++++++ Manatee.Json.Tests/Path/ToStringTests.cs | 71 ------- Manatee.Json.sln.DotSettings.user | 2 +- Manatee.Json/Manatee.Json.csproj | 1 + Manatee.Json/Path/Expressions/FieldExpression.cs | 5 +- .../Parsing/ConstantBooleanExpressionParser.cs | 28 +++ .../Parsing/ConstantStringExpressionParser.cs | 14 +- .../Parsing/JsonPathExpressionParser.cs | 4 +- .../Expressions/Parsing/PathExpressionParser.cs | 2 +- Manatee.Json/Path/Expressions/ValueExpression.cs | 4 +- Manatee.Json/Path/Parsing/JsonPathParser.cs | 4 +- Manatee.Json/Path/Parsing/PathParsingExtensions.cs | 2 +- Manatee.Json/Path/Slice.cs | 2 +- 24 files changed, 550 insertions(+), 97 deletions(-) create mode 100644 .gitmodules create mode 160000 Json-Path-Test-Suite create mode 100644 Json-Schema-Test-Suite/.gitignore create mode 100644 Manatee.Json.Tests/Path/TestSuite/JsonPathTestSuite.cs create mode 100644 Manatee.Json.Tests/Path/TestSuite/PathTest.cs create mode 100644 Manatee.Json.Tests/Path/TestSuite/PathTestSet.cs create mode 100644 Manatee.Json.Tests/Path/ToStringTest.cs delete mode 100644 Manatee.Json.Tests/Path/ToStringTests.cs create mode 100644 Manatee.Json/Path/Expressions/Parsing/ConstantBooleanExpressionParser.cs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2fb6147 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "Json-Path-Test-Suite"] + path = Json-Path-Test-Suite + url = https://github.com/gregsdennis/JSON-Path-Test-Suite.git +[submodule "Json-Schema-Test-Suite"] + path = Json-Schema-Test-Suite + url = https://github.com/json-schema-org/JSON-Schema-Test-Suite.git diff --git a/JSON-Schema-Test-Suite/README.md b/JSON-Schema-Test-Suite/README.md index 2958ee6..5adf569 100644 --- a/JSON-Schema-Test-Suite/README.md +++ b/JSON-Schema-Test-Suite/README.md @@ -118,6 +118,7 @@ for more information. ### .NET ### * [Newtonsoft.Json.Schema](https://github.com/JamesNK/Newtonsoft.Json.Schema) +* [Manatee.Json](https://github.com/gregsdennis/Manatee.Json) ### PHP ### diff --git a/JSON-Schema-Test-Suite/tests/draft4/ref.json b/JSON-Schema-Test-Suite/tests/draft4/ref.json index 7e80552..fe5aa90 100644 --- a/JSON-Schema-Test-Suite/tests/draft4/ref.json +++ b/JSON-Schema-Test-Suite/tests/draft4/ref.json @@ -155,5 +155,25 @@ "valid": false } ] + }, + { + "description": "property named $ref that is not a reference", + "schema": { + "properties": { + "$ref": {"type": "string"} + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] } ] diff --git a/Json-Path-Test-Suite b/Json-Path-Test-Suite new file mode 160000 index 0000000..a8c55f6 --- /dev/null +++ b/Json-Path-Test-Suite @@ -0,0 +1 @@ +Subproject commit a8c55f66e5fe15de99f2d1f7614e145b821aa9b3 diff --git a/Json-Schema-Test-Suite/.gitignore b/Json-Schema-Test-Suite/.gitignore new file mode 100644 index 0000000..1333ed7 --- /dev/null +++ b/Json-Schema-Test-Suite/.gitignore @@ -0,0 +1 @@ +TODO diff --git a/Manatee.Json.Tests/Manatee.Json.Tests.csproj b/Manatee.Json.Tests/Manatee.Json.Tests.csproj index 2068830..74ad343 100644 --- a/Manatee.Json.Tests/Manatee.Json.Tests.csproj +++ b/Manatee.Json.Tests/Manatee.Json.Tests.csproj @@ -174,7 +174,10 @@ - + + + + diff --git a/Manatee.Json.Tests/Path/FilterExpressionTest.cs b/Manatee.Json.Tests/Path/FilterExpressionTest.cs index 1416eee..4265aff 100644 --- a/Manatee.Json.Tests/Path/FilterExpressionTest.cs +++ b/Manatee.Json.Tests/Path/FilterExpressionTest.cs @@ -6,16 +6,17 @@ namespace Manatee.Json.Tests.Path [TestClass] public class FilterExpressionTest { - [TestMethod] - public void PropertyEqualsValue() + private void Run(JsonPath expected, string text) { - var text = "$[?(@.test == 5)]"; - var expected = JsonPathWith.Array(jv => jv.Name("test") == 5); - var actual = JsonPath.Parse(text); - Assert.AreEqual(expected, actual); } + + [TestMethod] + public void PropertyEqualsValue() + { + Run(JsonPathWith.Array(jv => jv.Name("test") == 5), "$[?(@.test == 5)]"); + } [TestMethod] public void PropertyNotEqualToValue() { @@ -190,5 +191,40 @@ public void Or() Assert.AreEqual(expected, actual); } + [TestMethod] + public void IndexOfNumber() + { + Run(JsonPathWith.Array(jv => jv.IndexOf(5) == 4), "$[?(@.indexOf(5) == 4)]"); + } + [TestMethod] + public void IndexOfBoolean() + { + Run(JsonPathWith.Array(jv => jv.IndexOf(false) == 4), "$[?(@.indexOf(false) == 4)]"); + } + [TestMethod] + public void IndexOfString() + { + Run(JsonPathWith.Array(jv => jv.IndexOf("string") == 4), "$[?(@.indexOf(\"string\") == 4)]"); + } + [TestMethod] + [Ignore] + // This won't work because there's not a way to get a FieldExpression when parsing, + // and there's not a way to construct this with a ValueExpression as a JsonArray. + // TODO: This should still create a valid path. + public void IndexOfArray() + { + var arr = new JsonArray {1, 2, 3}; + Run(JsonPathWith.Array(jv => jv.IndexOf(arr) == 4), "$[?(@.indexOf([1,2,3]) == 4)]"); + } + [TestMethod] + [Ignore] + // This won't work because there's not a way to get a FieldExpression when parsing, + // and there's not a way to construct this with a ValueExpression as a JsonObject. + // TODO: This should still create a valid path. + public void IndexOfObject() + { + var obj = new JsonObject {{"key", "value"}}; + Run(JsonPathWith.Array(jv => jv.IndexOf(obj) == 4), "$[?(@.indexOf({\"key\":\"value\"}) == 4)]"); + } } } diff --git a/Manatee.Json.Tests/Path/ParsingTest.cs b/Manatee.Json.Tests/Path/ParsingTest.cs index 255e8fe..06ab062 100644 --- a/Manatee.Json.Tests/Path/ParsingTest.cs +++ b/Manatee.Json.Tests/Path/ParsingTest.cs @@ -1,5 +1,4 @@ using Manatee.Json.Path; -using Manatee.Json.Path.ArrayParameters; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Manatee.Json.Tests.Path @@ -7,18 +6,20 @@ namespace Manatee.Json.Tests.Path [TestClass] public class ParsingTest { - [TestMethod] - public void SingleNamedObject() + private void Run(JsonPath expected, string text) { - var text = "$.name"; - var expected = JsonPathWith.Name("name"); - var actual = JsonPath.Parse(text); Assert.AreEqual(expected, actual); } [TestMethod] + public void SingleNamedObject() + { + Run(JsonPathWith.Name("name"), "$.name"); + } + + [TestMethod] public void SingleQuotedNamedObject() { var text = "$.'quoted name'"; @@ -254,5 +255,29 @@ public void SearchIndexedArray() Assert.AreEqual(expected, actual); } + + [TestMethod] + public void ChainedNameIndexedArray() + { + Run(JsonPathWith.Name("name").Array(4), "$.name[4]"); + } + + [TestMethod] + public void ChainedIndexedArrayName() + { + Run(JsonPathWith.Array(4).Name("name"), "$[4].name"); + } + + [TestMethod] + public void ChainedNameName() + { + Run(JsonPathWith.Name("name").Name("test"), "$.name.test"); + } + + [TestMethod] + public void ChainedIndexedArrayIndexedArray() + { + Run(JsonPathWith.Array(2).Array(4), "$[2][4]"); + } } } diff --git a/Manatee.Json.Tests/Path/TestSuite/JsonPathTestSuite.cs b/Manatee.Json.Tests/Path/TestSuite/JsonPathTestSuite.cs new file mode 100644 index 0000000..5b24ee1 --- /dev/null +++ b/Manatee.Json.Tests/Path/TestSuite/JsonPathTestSuite.cs @@ -0,0 +1,120 @@ +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: JsonPathTestSuite.cs + Namespace: Manatee.Json.Tests.Schema.TestSuite + Class Name: JsonPathTestSuite + Purpose: Runs the series of schema tests defined at + https://github.com/json-schema-org/JSON-Schema-Test-Suite. + +***************************************************************************************/ +using System; +using System.Collections.Generic; +using System.IO; +using Manatee.Json.Serialization; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Manatee.Json.Tests.Path.TestSuite +{ + [TestClass] + public class JsonPathTestSuite + { + private const string TestFolder = @"http://github.com/gregsdennis/Json-Path-Test-Suite/trunk/tests/"; + private static readonly JsonSerializer Serializer; + private int _failures; + private int _passes; +#pragma warning disable 649 + private string _fileNameForDebugging; + private string _testPathForDebugging = ""; +#pragma warning restore 649 + + static JsonPathTestSuite() + { + Serializer = new JsonSerializer(); + JsonSerializationAbstractionMap.MapGeneric(typeof(IEnumerable<>), typeof(List<>)); + } + + [TestMethod] + public void RunSuite() + { + // uncomment and paste the filename of a test suite to debug it. + //_fileNameForDebugging = ""; + // uncomment and paste the description of a test to debug it. + //_testNameForDebugging = "ref within ref valid"; + + try + { + var fileNames = Directory.GetFiles(TestFolder); + + foreach (var fileName in fileNames) + { + _RunFile(fileName); + } + + Assert.AreEqual(0, _failures); + } + finally + { + Console.WriteLine(); + Console.WriteLine($"Passes: {_passes}"); + Console.WriteLine($"Failures: {_failures}"); + } + } + + private void _RunFile(string fileName) + { + if (fileName == _fileNameForDebugging) + { + System.Diagnostics.Debugger.Break(); + } + + var contents = File.ReadAllText(fileName); + var json = JsonValue.Parse(contents); + + var testSet = Serializer.Deserialize(json); + + Console.WriteLine($"{testSet.Title} ({fileName})"); + _RunTestSet(testSet); + } + + private void _RunTestSet(PathTestSet testSet) + { + foreach (var test in testSet.Tests) + { + var pathString = test.Path.ToString(); + if (pathString == _testPathForDebugging) + { + System.Diagnostics.Debugger.Break(); + } + + var results = test.Path.Evaluate(testSet.Data); + + if (results == test.Result) + { + // It's difficult to see the failures when everything shows. + // The Stack Trace Explorer needs to show color output. :/ + //Console.WriteLine($" {test.Description} - Passed"); + _passes++; + } + else + { + Console.WriteLine($" {pathString} - Failed"); + _failures++; + } + } + } + } +} diff --git a/Manatee.Json.Tests/Path/TestSuite/PathTest.cs b/Manatee.Json.Tests/Path/TestSuite/PathTest.cs new file mode 100644 index 0000000..2914b9f --- /dev/null +++ b/Manatee.Json.Tests/Path/TestSuite/PathTest.cs @@ -0,0 +1,22 @@ +using System; +using Manatee.Json.Path; +using Manatee.Json.Serialization; + +namespace Manatee.Json.Tests.Path.TestSuite +{ + internal class PathTest : IJsonSerializable + { + public JsonPath Path { get; private set; } + public JsonValue Result { get; private set; } + + public void FromJson(JsonValue json, JsonSerializer serializer) + { + Path = JsonPath.Parse(json.Object["path"].String); + Result = json.Object["result"]; + } + public JsonValue ToJson(JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Manatee.Json.Tests/Path/TestSuite/PathTestSet.cs b/Manatee.Json.Tests/Path/TestSuite/PathTestSet.cs new file mode 100644 index 0000000..3ce6623 --- /dev/null +++ b/Manatee.Json.Tests/Path/TestSuite/PathTestSet.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using Manatee.Json.Serialization; + +namespace Manatee.Json.Tests.Path.TestSuite +{ + internal class PathTestSet : IJsonSerializable + { + public string Title { get; private set; } + public JsonValue Data { get; private set; } + public IEnumerable Tests { get; private set; } + + public void FromJson(JsonValue json, JsonSerializer serializer) + { + Title = json.Object["title"].String; + Data = json.Object["data"]; + Tests = serializer.Deserialize>(json.Object["tests"]); + } + public JsonValue ToJson(JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/Manatee.Json.Tests/Path/ToStringTest.cs b/Manatee.Json.Tests/Path/ToStringTest.cs new file mode 100644 index 0000000..742e8b5 --- /dev/null +++ b/Manatee.Json.Tests/Path/ToStringTest.cs @@ -0,0 +1,223 @@ +using Manatee.Json.Path; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Manatee.Json.Tests.Path +{ + [TestClass] + public class ToStringTest + { + private void Run(string expected, JsonPath path) + { + var actual = path.ToString(); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void JustRoot() + { + Run("$", new JsonPath()); + } + + #region Dot Operator + + [TestMethod] + public void ObjectKey() + { + Run("$.name", JsonPathWith.Name("name")); + } + [TestMethod] + public void ObjectChain() + { + Run("$.name.test", JsonPathWith.Name("name").Name("test")); + } + [TestMethod] + public void Length() + { + Run("$.length", JsonPathWith.Length()); + } + + #endregion + + #region Indexed Arrays + + [TestMethod] + public void ArrayIndex_SingleConstant() + { + Run("$[0]", JsonPathWith.Array(0)); + } + [TestMethod] + public void ArrayIndex_SingleSlice() + { + Run("$[0:6]", JsonPathWith.Array(new Slice(0, 6))); + } + [TestMethod] + public void ArrayIndex_SingleSliceStartEndStep() + { + Run("$[0:6:2]", JsonPathWith.Array(new Slice(0, 6, 2))); + } + [TestMethod] + public void ArrayIndex_SingleSliceStart() + { + Run("$[0:]", JsonPathWith.Array(new Slice(0, null))); + } + [TestMethod] + public void ArrayIndex_SingleSliceEnd() + { + Run("$[:6]", JsonPathWith.Array(new Slice(null, 6))); + } + [TestMethod] + public void ArrayIndex_SingleSliceStartStep() + { + Run("$[0::2]", JsonPathWith.Array(new Slice(0, null, 2))); + } + [TestMethod] + public void ArrayIndex_SingleEndStep() + { + Run("$[:6:2]", JsonPathWith.Array(new Slice(null, 6, 2))); + } + [TestMethod] + public void ArrayIndex_SingleSliceStep() + { + Run("$[::2]", JsonPathWith.Array(new Slice(null, null, 2))); + } + [TestMethod] + public void ArrayIndex_ConstantSlice() + { + Run("$[1,4:6]", JsonPathWith.Array(1, new Slice(4, 6))); + } + + #endregion + + #region Index Expression Arrays + + [TestMethod] + public void ArrayIndexExpression_LocalLength() + { + Run("$[(@.length)]", JsonPathWith.Array(jv => jv.Length())); + } + [TestMethod] + public void ArrayIndexExpression_RootLength() + { + Run("$[($.length)]", JsonPathWith.Array(jv => JsonPathRoot.Length())); + } + [TestMethod] + public void ArrayIndexExpression_NameLength() + { + Run("$[(@.name.length)]", JsonPathWith.Array(jv => jv.Name("name").Length())); + } + [TestMethod] + public void ArrayIndexExpression_Addition() + { + Run("$[(@.length+1)]", JsonPathWith.Array(jv => jv.Length() + 1)); + } + [TestMethod] + public void ArrayIndexExpression_Subtraction() + { + Run("$[(@.length-1)]", JsonPathWith.Array(jv => jv.Length() - 1)); + } + [TestMethod] + public void ArrayIndexExpression_Multiplication() + { + Run("$[(@.length*1)]", JsonPathWith.Array(jv => jv.Length() * 1)); + } + [TestMethod] + public void ArrayIndexExpression_Division() + { + Run("$[(@.length/1)]", JsonPathWith.Array(jv => jv.Length() / 1)); + } + [TestMethod] + public void ArrayIndexExpression_Modulus() + { + Run("$[(@.length%1)]", JsonPathWith.Array(jv => jv.Length() % 1)); + } + + #endregion + + #region Filter Expression Arrays + + [TestMethod] + public void ArrayFilterExpression_LocalNameExists() + { + Run("$[?(@.test)]", JsonPathWith.Array(jv => jv.HasProperty("test"))); + } + [TestMethod] + public void ArrayFilterExpression_LocalLengthEqualsInt() + { + Run("$[?(@.length == 1)]", JsonPathWith.Array(jv => jv.Length() == 1)); + } + [TestMethod] + public void ArrayFilterExpression_LocalNameEqualsString() + { + Run("$[?(@.test == \"string\")]", JsonPathWith.Array(jv => jv.Name("test") == "string")); + } + [TestMethod] + public void ArrayFilterExpression_LocalNameChainEqualsBoolean() + { + Run("$[?(@.name.test == false)]", JsonPathWith.Array(jv => jv.Name("name").Name("test") == false)); + } + [TestMethod] + public void ArrayFilterExpression_RootLengthNotEqualsValue() + { + Run("$[?($.length != 1)]", JsonPathWith.Array(jv => JsonPathRoot.Length() != 1)); + } + [TestMethod] + public void ArrayFilterExpression_NameLengthLessThanValue() + { + Run("$[?(@.name.length < 1)]", JsonPathWith.Array(jv => jv.Name("name").Length() < 1)); + } + [TestMethod] + public void ArrayFilterExpression_AdditionLessThanEqualValue() + { + Run("$[?(@.length <= 1)]", JsonPathWith.Array(jv => jv.Length() <= 1)); + } + [TestMethod] + public void ArrayFilterExpression_SubtractionGreaterThanValue() + { + Run("$[?(@.length-1 > 2)]", JsonPathWith.Array(jv => jv.Length() - 1 > 2)); + } + [TestMethod] + public void ArrayFilterExpression_MultiplicationGreaterThanEqualValue() + { + Run("$[?(@.length*1 >= 2)]", JsonPathWith.Array(jv => jv.Length() * 1 >= 2)); + } + [TestMethod] + public void ArrayFilterExpression_DivisionEqualValue() + { + Run("$[?(@.length/1 == 2)]", JsonPathWith.Array(jv => jv.Length() / 1 == 2)); + } + [TestMethod] + public void ArrayFilterExpression_ModulusEqualValue() + { + Run("$[?(@.length%1 == 2)]", JsonPathWith.Array(jv => jv.Length() % 1 == 2)); + } + [TestMethod] + public void ArrayIndexExpression_IndexOfNumber() + { + Run("$[?(@.indexOf(5) == 4)]", JsonPathWith.Array(jv => jv.IndexOf(5) == 4)); + } + [TestMethod] + public void ArrayIndexExpression_IndexOfBoolean() + { + Run("$[?(@.indexOf(false) == 4)]", JsonPathWith.Array(jv => jv.IndexOf(false) == 4)); + } + [TestMethod] + public void ArrayIndexExpression_IndexOfString() + { + Run("$[?(@.indexOf(\"string\") == 4)]", JsonPathWith.Array(jv => jv.IndexOf("string") == 4)); + } + [TestMethod] + public void ArrayIndexExpression_IndexOfArray() + { + var arr = new JsonArray {1, 2, 3}; + Run("$[?(@.indexOf([1,2,3]) == 4)]", JsonPathWith.Array(jv => jv.IndexOf(arr) == 4)); + } + [TestMethod] + public void ArrayIndexExpression_IndexOfObject() + { + var obj = new JsonObject {{"key", "value"}}; + Run("$[?(@.indexOf({\"key\":\"value\"}) == 4)]", JsonPathWith.Array(jv => jv.IndexOf(obj) == 4)); + } + + #endregion + } +} diff --git a/Manatee.Json.Tests/Path/ToStringTests.cs b/Manatee.Json.Tests/Path/ToStringTests.cs deleted file mode 100644 index d07dd43..0000000 --- a/Manatee.Json.Tests/Path/ToStringTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Manatee.Json.Path; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Manatee.Json.Tests.Path -{ - [TestClass] - public class ToStringTests - { - private void Run(string expected, JsonPath path) - { - var actual = path.ToString(); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void JustRoot() - { - Run("$", new JsonPath()); - } - [TestMethod] - public void ObjectKey() - { - Run("$.name", JsonPathWith.Name("name")); - } - [TestMethod] - public void ArrayIndex_SingleConstant() - { - Run("$[0]", JsonPathWith.Array(0)); - } - [TestMethod] - public void ArrayIndex_SingleSlice() - { - Run("$[0:6]", JsonPathWith.Array(new Slice(0, 6))); - } - [TestMethod] - public void ArrayIndex_SingleSliceStartEndStep() - { - Run("$[0:6:2]", JsonPathWith.Array(new Slice(0, 6, 2))); - } - [TestMethod] - public void ArrayIndex_SingleSliceStart() - { - Run("$[0:]", JsonPathWith.Array(new Slice(0, null))); - } - [TestMethod] - public void ArrayIndex_SingleSliceEnd() - { - Run("$[:6]", JsonPathWith.Array(new Slice(null, 6))); - } - [TestMethod] - public void ArrayIndex_SingleSliceStartStep() - { - Run("$[0::2]", JsonPathWith.Array(new Slice(0, null, 2))); - } - [TestMethod] - public void ArrayIndex_SingleEndStep() - { - Run("$[:6:2]", JsonPathWith.Array(new Slice(null, 6, 2))); - } - [TestMethod] - public void ArrayIndex_SingleSliceStep() - { - Run("$[::2]", JsonPathWith.Array(new Slice(null, null, 2))); - } - [TestMethod] - public void ArrayIndex_ConstantSlice() - { - Run("$[1,4:6]", JsonPathWith.Array(1, new Slice(4, 6))); - } - } -} diff --git a/Manatee.Json.sln.DotSettings.user b/Manatee.Json.sln.DotSettings.user index 2fc9a05..fe6dd80 100644 --- a/Manatee.Json.sln.DotSettings.user +++ b/Manatee.Json.sln.DotSettings.user @@ -2,7 +2,7 @@ SOLUTION True 2016.2 - C:\Users\Gregd\AppData\Local\Temp\ssm.Pohepiq.tmp + C:\Users\Gregd\AppData\Local\Temp\ssm.Goxeliz.tmp diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index b6378ab..b1bcace 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -235,6 +235,7 @@ + diff --git a/Manatee.Json/Path/Expressions/FieldExpression.cs b/Manatee.Json/Path/Expressions/FieldExpression.cs index 4ccb974..a03197c 100644 --- a/Manatee.Json/Path/Expressions/FieldExpression.cs +++ b/Manatee.Json/Path/Expressions/FieldExpression.cs @@ -35,7 +35,10 @@ internal class FieldExpression : ExpressionTreeNode, IEquatable(string source, ref int index, out ExpressionTreeNode node) + { + var substring = source.Substring(index); + if (substring.StartsWith("true")) + { + index += 4; + node = new ValueExpression {Value = true}; + return null; + } + if (substring.StartsWith("false")) + { + index += 5; + node = new ValueExpression {Value = false}; + return null; + } + node = null; + return "Boolean value not recognized."; + } + } +} \ No newline at end of file diff --git a/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs index c347b15..926e6b4 100644 --- a/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs @@ -1,5 +1,5 @@ -using System; -using Manatee.Json.Internal; +using Manatee.Json.Internal; +using Manatee.Json.Path.Parsing; namespace Manatee.Json.Path.Expressions.Parsing { @@ -11,7 +11,15 @@ public bool Handles(string input) } public string TryParse(string source, ref int index, out ExpressionTreeNode node) { - throw new NotImplementedException(); + string value; + var error = source.GetKey(ref index, out value); + if (error != null) + { + node = null; + return error; + } + node = new ValueExpression {Value = value}; + return null; } } } diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs index d1c4d59..c2aae32 100644 --- a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -42,8 +42,8 @@ public static string Parse(string source, ref int index, out ExpressionTree char c; var errorMessage = source.SkipWhiteSpace(ref index, length, out c); if (errorMessage != null) return errorMessage; - var i = index; - var parser = Parsers.FirstOrDefault(p => p.Handles(source.Substring(i))); + var substring = source.Substring(index); + var parser = Parsers.FirstOrDefault(p => p.Handles(substring)); if (parser == null) return "Unrecognized JSON Path Expression element."; errorMessage = parser.TryParse(source, ref index, out node); if (errorMessage != null) return errorMessage; diff --git a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs index 6c5f9e1..f55da07 100644 --- a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs @@ -48,7 +48,6 @@ public string TryParse(string source, ref int index, out ExpressionTreeNode(string source, ref int index, out ExpressionTreeNode other) { diff --git a/Manatee.Json/Path/Parsing/JsonPathParser.cs b/Manatee.Json/Path/Parsing/JsonPathParser.cs index 25b184f..e16af45 100644 --- a/Manatee.Json/Path/Parsing/JsonPathParser.cs +++ b/Manatee.Json/Path/Parsing/JsonPathParser.cs @@ -58,8 +58,8 @@ public static string Parse(string source, ref int index, out JsonPath path) char c; var errorMessage = source.SkipWhiteSpace(ref index, length, out c); if (errorMessage != null) return errorMessage; - var i = index; - var parser = Parsers.FirstOrDefault(p => p.Handles(source.Substring(i))); + var substring = source.Substring(index); + var parser = Parsers.FirstOrDefault(p => p.Handles(substring)); if (parser == null) return "Unrecognized JSON Path element."; errorMessage = parser.TryParse(source, ref index, ref path); if (errorMessage != null) return errorMessage; diff --git a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs index fa1aa46..0604cd4 100644 --- a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs +++ b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs @@ -144,7 +144,7 @@ public static string GetSlices(this string source, ref int index, out IList Find(JsonArray json, JsonValue root) { if (Index.HasValue) { - return json.Count < Index.Value + return Index.Value < 0 || json.Count < Index.Value ? Enumerable.Empty() : new[] { json[Index.Value] }; } From 8907fb52def7ed2bc7a1fba3a67ea316fed9dc98 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 21 Sep 2016 11:06:13 +1200 Subject: [PATCH 14/18] Remaining changes from previous commit. --- Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs | 1 + Manatee.Json/Path/Parsing/PathParsingExtensions.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs index f55da07..cc5581e 100644 --- a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs @@ -43,6 +43,7 @@ public string TryParse(string source, ref int index, out ExpressionTreeNode { Path = path, diff --git a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs index 0604cd4..33c0e86 100644 --- a/Manatee.Json/Path/Parsing/PathParsingExtensions.cs +++ b/Manatee.Json/Path/Parsing/PathParsingExtensions.cs @@ -144,7 +144,6 @@ public static string GetSlices(this string source, ref int index, out IList Date: Wed, 21 Sep 2016 15:55:19 +1200 Subject: [PATCH 15/18] Passing test suite. Need negative parser tests. --- Json-Path-Test-Suite | 2 +- Manatee.Json.Tests/Path/FilterExpressionTest.cs | 109 +++++++-------------- .../Path/TestSuite/JsonPathTestSuite.cs | 16 +-- Manatee.Json.Tests/Path/TestSuite/PathTest.cs | 8 +- Manatee.Json/Internal/GeneralExtensions.cs | 11 +++ Manatee.Json/Parsing/ArrayParser.cs | 2 +- Manatee.Json/Parsing/BoolParser.cs | 2 +- Manatee.Json/Parsing/IJsonParser.cs | 2 +- Manatee.Json/Parsing/JsonParser.cs | 4 +- Manatee.Json/Parsing/NullParser.cs | 2 +- Manatee.Json/Parsing/NumberParser.cs | 6 +- Manatee.Json/Parsing/ObjectParser.cs | 2 +- Manatee.Json/Parsing/StringParser.cs | 2 +- .../Path/Expressions/ArrayIndexExpression.cs | 3 +- Manatee.Json/Path/Expressions/Expression.cs | 14 ++- Manatee.Json/Path/Expressions/IndexOfExpression.cs | 5 +- Manatee.Json/Path/Expressions/NameExpresssion.cs | 3 +- .../Expressions/Parsing/PathExpressionParser.cs | 52 +++++++--- Manatee.Json/Path/Expressions/ValueComparer.cs | 26 +++-- Manatee.Json/Path/JsonPathSyntaxException.cs | 4 +- 20 files changed, 145 insertions(+), 130 deletions(-) diff --git a/Json-Path-Test-Suite b/Json-Path-Test-Suite index a8c55f6..1cc08f0 160000 --- a/Json-Path-Test-Suite +++ b/Json-Path-Test-Suite @@ -1 +1 @@ -Subproject commit a8c55f66e5fe15de99f2d1f7614e145b821aa9b3 +Subproject commit 1cc08f08f835b38635a488b4275d3148d68d552d diff --git a/Manatee.Json.Tests/Path/FilterExpressionTest.cs b/Manatee.Json.Tests/Path/FilterExpressionTest.cs index 4265aff..650f467 100644 --- a/Manatee.Json.Tests/Path/FilterExpressionTest.cs +++ b/Manatee.Json.Tests/Path/FilterExpressionTest.cs @@ -20,62 +20,32 @@ public void PropertyEqualsValue() [TestMethod] public void PropertyNotEqualToValue() { - var text = "$[?(@.test != 5)]"; - var expected = JsonPathWith.Array(jv => jv.Name("test") != 5); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.Name("test") != 5), "$[?(@.test != 5)]"); } [TestMethod] public void PropertyGreaterThanValue() { - var text = "$[?(@.test > 5)]"; - var expected = JsonPathWith.Array(jv => jv.Name("test") > 5); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.Name("test") > 5), "$[?(@.test > 5)]"); } [TestMethod] public void PropertyGreaterThanEqualToValue() { - var text = "$[?(@.test >= 5)]"; - var expected = JsonPathWith.Array(jv => jv.Name("test") >= 5); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.Name("test") >= 5), "$[?(@.test >= 5)]"); } [TestMethod] public void PropertyLessThanValue() { - var text = "$[?(@.test < 5)]"; - var expected = JsonPathWith.Array(jv => jv.Name("test") < 5); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.Name("test") < 5), "$[?(@.test < 5)]"); } [TestMethod] public void PropertyLessThanEqualToValue() { - var text = "$[?(@.test <= 5)]"; - var expected = JsonPathWith.Array(jv => jv.Name("test") <= 5); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.Name("test") <= 5), "$[?(@.test <= 5)]"); } [TestMethod] public void ArrayIndexEqualsValue() { - var text = "$[?(@[1] == 5)]"; - var expected = JsonPathWith.Array(jv => jv.ArrayIndex(1) == 5); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.ArrayIndex(1) == 5), "$[?(@[1] == 5)]"); } [TestMethod] [ExpectedException(typeof(JsonPathSyntaxException))] @@ -144,73 +114,67 @@ public void ArrayFilterExpressionEqualsValue() [TestMethod] public void HasProperty() { - var text = "$[?(@.test)]"; - var expected = JsonPathWith.Array(jv => jv.HasProperty("test")); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.HasProperty("test")), "$[?(@.test)]"); } [TestMethod] public void DoesNotHaveProperty() { - var text = "$[?(!@.test)]"; - var expected = JsonPathWith.Array(jv => !jv.HasProperty("test")); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => !jv.HasProperty("test")), "$[?(!@.test)]"); } [TestMethod] public void GroupedNot() { - var text = "$[?(!(@.test && @.name == 5))]"; - var expected = JsonPathWith.Array(jv => !(jv.HasProperty("test") && jv.Name("name") == 5)); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => !(jv.HasProperty("test") && jv.Name("name") == 5)), "$[?(!(@.test && @.name == 5))]"); } [TestMethod] public void And() { - var text = "$[?(@.test && @.name == 5)]"; - var expected = JsonPathWith.Array(jv => jv.HasProperty("test") && jv.Name("name") == 5); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.HasProperty("test") && jv.Name("name") == 5), "$[?(@.test && @.name == 5)]"); } [TestMethod] public void Or() { - var text = "$[?(@.test || @.name == 5)]"; - var expected = JsonPathWith.Array(jv => jv.HasProperty("test") || jv.Name("name") == 5); - - var actual = JsonPath.Parse(text); - - Assert.AreEqual(expected, actual); + Run(JsonPathWith.Array(jv => jv.HasProperty("test") || jv.Name("name") == 5),"$[?(@.test || @.name == 5)]"); } [TestMethod] + [Ignore] + // This won't work the same. Parsing generates an IndexOfExpression with a distinct parameter, + // but when constructing the path, the parameter goes through several castings generating a + // parameter expression. The parsed path would be different in structure but should still + // represent the same thing. We have to + // TODO: Test by evaluation public void IndexOfNumber() { Run(JsonPathWith.Array(jv => jv.IndexOf(5) == 4), "$[?(@.indexOf(5) == 4)]"); } [TestMethod] + [Ignore] + // This won't work the same. Parsing generates an IndexOfExpression with a distinct parameter, + // but when constructing the path, the parameter goes through several castings generating a + // parameter expression. The parsed path would be different in structure but should still + // represent the same thing. + // TODO: Test by evaluation public void IndexOfBoolean() { Run(JsonPathWith.Array(jv => jv.IndexOf(false) == 4), "$[?(@.indexOf(false) == 4)]"); } [TestMethod] + [Ignore] + // This won't work the same. Parsing generates an IndexOfExpression with a distinct parameter, + // but when constructing the path, the parameter goes through several castings generating a + // parameter expression. The parsed path would be different in structure but should still + // represent the same thing. + // TODO: Test by evaluation public void IndexOfString() { Run(JsonPathWith.Array(jv => jv.IndexOf("string") == 4), "$[?(@.indexOf(\"string\") == 4)]"); } [TestMethod] [Ignore] - // This won't work because there's not a way to get a FieldExpression when parsing, - // and there's not a way to construct this with a ValueExpression as a JsonArray. - // TODO: This should still create a valid path. + // This won't work the same. Parsing generates a ValueExpression, but the only way to + // construct the path is to pass the field, which generates a FieldExpression. The parsed + // path would be different in structure but should still represent the same thing. + // TODO: Test by evaluation public void IndexOfArray() { var arr = new JsonArray {1, 2, 3}; @@ -218,9 +182,10 @@ public void IndexOfArray() } [TestMethod] [Ignore] - // This won't work because there's not a way to get a FieldExpression when parsing, - // and there's not a way to construct this with a ValueExpression as a JsonObject. - // TODO: This should still create a valid path. + // This won't work the same. Parsing generates a ValueExpression, but the only way to + // construct the path is to pass the field, which generates a FieldExpression. The parsed + // path would be different in structure but should still represent the same thing. + // TODO: Test by evaluation public void IndexOfObject() { var obj = new JsonObject {{"key", "value"}}; diff --git a/Manatee.Json.Tests/Path/TestSuite/JsonPathTestSuite.cs b/Manatee.Json.Tests/Path/TestSuite/JsonPathTestSuite.cs index 5b24ee1..9837781 100644 --- a/Manatee.Json.Tests/Path/TestSuite/JsonPathTestSuite.cs +++ b/Manatee.Json.Tests/Path/TestSuite/JsonPathTestSuite.cs @@ -32,14 +32,14 @@ namespace Manatee.Json.Tests.Path.TestSuite [TestClass] public class JsonPathTestSuite { - private const string TestFolder = @"http://github.com/gregsdennis/Json-Path-Test-Suite/trunk/tests/"; + private const string TestFolder = @"..\..\..\Json-Path-Test-Suite\tests\"; +#pragma warning disable 649 + private const string FileNameForDebugging = ""; + private const string TestPathForDebugging = ""; +#pragma warning restore 649 private static readonly JsonSerializer Serializer; private int _failures; private int _passes; -#pragma warning disable 649 - private string _fileNameForDebugging; - private string _testPathForDebugging = ""; -#pragma warning restore 649 static JsonPathTestSuite() { @@ -76,7 +76,7 @@ public void RunSuite() private void _RunFile(string fileName) { - if (fileName == _fileNameForDebugging) + if (fileName == FileNameForDebugging) { System.Diagnostics.Debugger.Break(); } @@ -95,14 +95,14 @@ private void _RunTestSet(PathTestSet testSet) foreach (var test in testSet.Tests) { var pathString = test.Path.ToString(); - if (pathString == _testPathForDebugging) + if (pathString == TestPathForDebugging) { System.Diagnostics.Debugger.Break(); } var results = test.Path.Evaluate(testSet.Data); - if (results == test.Result) + if (Equals(results, test.Result)) { // It's difficult to see the failures when everything shows. // The Stack Trace Explorer needs to show color output. :/ diff --git a/Manatee.Json.Tests/Path/TestSuite/PathTest.cs b/Manatee.Json.Tests/Path/TestSuite/PathTest.cs index 2914b9f..7c85a33 100644 --- a/Manatee.Json.Tests/Path/TestSuite/PathTest.cs +++ b/Manatee.Json.Tests/Path/TestSuite/PathTest.cs @@ -7,12 +7,16 @@ namespace Manatee.Json.Tests.Path.TestSuite internal class PathTest : IJsonSerializable { public JsonPath Path { get; private set; } - public JsonValue Result { get; private set; } + public JsonArray Result { get; private set; } public void FromJson(JsonValue json, JsonSerializer serializer) { Path = JsonPath.Parse(json.Object["path"].String); - Result = json.Object["result"]; + var result = json.Object["result"]; + if (result.Type == JsonValueType.Boolean && !result.Boolean) + Result = new JsonArray(); + else + Result = result.Array; } public JsonValue ToJson(JsonSerializer serializer) { diff --git a/Manatee.Json/Internal/GeneralExtensions.cs b/Manatee.Json/Internal/GeneralExtensions.cs index 8311b84..588b51b 100644 --- a/Manatee.Json/Internal/GeneralExtensions.cs +++ b/Manatee.Json/Internal/GeneralExtensions.cs @@ -267,5 +267,16 @@ public static bool ContentsEqual(this IEnumerable a, IEnumerable b) var listB = b.ToList(); return listA.Count == listB.Count && listA.All(item => listB.Contains(item)); } + public static JsonValue AsJsonValue(this object value) + { + if (value is JsonValue) return (JsonValue) value; + if (value is JsonArray) return (JsonArray) value; + if (value is JsonObject) return (JsonObject) value; + if (value is string) return (string) value; + if (value is bool) return (bool) value; + if (value is IConvertible) return Convert.ToDouble(value); + + return null; + } } } \ No newline at end of file diff --git a/Manatee.Json/Parsing/ArrayParser.cs b/Manatee.Json/Parsing/ArrayParser.cs index ecda5a2..9798f66 100644 --- a/Manatee.Json/Parsing/ArrayParser.cs +++ b/Manatee.Json/Parsing/ArrayParser.cs @@ -32,7 +32,7 @@ public bool Handles(char c) { return c == '['; } - public string TryParse(string source, ref int index, out JsonValue value) + public string TryParse(string source, ref int index, out JsonValue value, bool allowExtraChars) { var array = new JsonArray(); value = array; diff --git a/Manatee.Json/Parsing/BoolParser.cs b/Manatee.Json/Parsing/BoolParser.cs index df9132a..29cb83b 100644 --- a/Manatee.Json/Parsing/BoolParser.cs +++ b/Manatee.Json/Parsing/BoolParser.cs @@ -33,7 +33,7 @@ public bool Handles(char c) { return c.In('t', 'T', 'f', 'F'); } - public string TryParse(string source, ref int index, out JsonValue value) + public string TryParse(string source, ref int index, out JsonValue value, bool allowExtraChars) { char[] buffer; int count; diff --git a/Manatee.Json/Parsing/IJsonParser.cs b/Manatee.Json/Parsing/IJsonParser.cs index 2bb06bd..838e6b1 100644 --- a/Manatee.Json/Parsing/IJsonParser.cs +++ b/Manatee.Json/Parsing/IJsonParser.cs @@ -29,7 +29,7 @@ internal interface IJsonParser { bool Handles(char c); // returns error message, if any. Null return implies success. - string TryParse(string source, ref int index, out JsonValue value); + string TryParse(string source, ref int index, out JsonValue value, bool allowExtraChars); string TryParse(StreamReader stream, out JsonValue value); } } diff --git a/Manatee.Json/Parsing/JsonParser.cs b/Manatee.Json/Parsing/JsonParser.cs index d2ac7f0..5eeecf4 100644 --- a/Manatee.Json/Parsing/JsonParser.cs +++ b/Manatee.Json/Parsing/JsonParser.cs @@ -58,7 +58,7 @@ public static JsonValue Parse(StreamReader stream) throw new JsonSyntaxException(errorMessage, value); return value; } - public static string Parse(string source, ref int index, out JsonValue value) + public static string Parse(string source, ref int index, out JsonValue value, bool allowExtraChars = false) { var length = source.Length; char c; @@ -74,7 +74,7 @@ public static string Parse(string source, ref int index, out JsonValue value) value = null; return "Cannot determine type."; } - errorMessage = parser.TryParse(source, ref index, out value); + errorMessage = parser.TryParse(source, ref index, out value, allowExtraChars); return errorMessage; } public static string Parse(StreamReader stream, out JsonValue value) diff --git a/Manatee.Json/Parsing/NullParser.cs b/Manatee.Json/Parsing/NullParser.cs index 775f26e..038d02c 100644 --- a/Manatee.Json/Parsing/NullParser.cs +++ b/Manatee.Json/Parsing/NullParser.cs @@ -32,7 +32,7 @@ public bool Handles(char c) { return c.In('n', 'N'); } - public string TryParse(string source, ref int index, out JsonValue value) + public string TryParse(string source, ref int index, out JsonValue value, bool allowExtraChars) { var buffer = new char[4]; for (int i = 0; i < 4 && index + i < source.Length; i++) diff --git a/Manatee.Json/Parsing/NumberParser.cs b/Manatee.Json/Parsing/NumberParser.cs index 0d96081..0a2de7b 100644 --- a/Manatee.Json/Parsing/NumberParser.cs +++ b/Manatee.Json/Parsing/NumberParser.cs @@ -37,7 +37,7 @@ public bool Handles(char c) { return c.In('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-'); } - public string TryParse(string source, ref int index, out JsonValue value) + public string TryParse(string source, ref int index, out JsonValue value, bool allowExtraChars) { var bufferSize = 0; var bufferLength = FibSequence[bufferSize]; @@ -57,7 +57,9 @@ public string TryParse(string source, ref int index, out JsonValue value) } var c = source[index]; if (char.IsWhiteSpace(c) || c.In(',', ']', '}')) break; - if (!NumberChars.Contains(c)) + var isNumber = NumberChars.Contains(c); + if (!isNumber && allowExtraChars) break; + if (!isNumber) { value = null; return "Expected \',\', \']\', or \'}\'."; diff --git a/Manatee.Json/Parsing/ObjectParser.cs b/Manatee.Json/Parsing/ObjectParser.cs index d6c6aaf..e412412 100644 --- a/Manatee.Json/Parsing/ObjectParser.cs +++ b/Manatee.Json/Parsing/ObjectParser.cs @@ -32,7 +32,7 @@ public bool Handles(char c) { return c == '{'; } - public string TryParse(string source, ref int index, out JsonValue value) + public string TryParse(string source, ref int index, out JsonValue value, bool allowExtraChars) { var obj = new JsonObject(); value = obj; diff --git a/Manatee.Json/Parsing/StringParser.cs b/Manatee.Json/Parsing/StringParser.cs index f37e920..16dc195 100644 --- a/Manatee.Json/Parsing/StringParser.cs +++ b/Manatee.Json/Parsing/StringParser.cs @@ -34,7 +34,7 @@ public bool Handles(char c) { return c == '\"'; } - public string TryParse(string source, ref int index, out JsonValue value) + public string TryParse(string source, ref int index, out JsonValue value, bool allowExtraChars) { var bufferSize = 0; var bufferLength = FibSequence[bufferSize]; diff --git a/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs b/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs index b00a1a2..c4a7f16 100644 --- a/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs +++ b/Manatee.Json/Path/Expressions/ArrayIndexExpression.cs @@ -23,6 +23,7 @@ ***************************************************************************************/ using System; using System.Linq; +using Manatee.Json.Internal; namespace Manatee.Json.Path.Expressions { @@ -35,7 +36,7 @@ internal class ArrayIndexExpression : PathExpression, IEquatable root) public T Evaluate(TIn json, JsonValue root) { var result = _root.Evaluate(json, root); - if (typeof (T) == typeof (bool) && result == null) + if (typeof(T) == typeof(bool) && result == null) return (T) (object) false; - if (typeof (T) == typeof (bool) && result != null && !(result is bool)) + if (typeof(T) == typeof(bool) && result != null && !(result is bool)) return (T) (object) true; - if (typeof (T) == typeof (int) && result == null) + if (typeof(T) == typeof(int) && result == null) return (T) (object) -1; - if (typeof (T) == typeof (JsonValue)) + var resultAsJsonValue = result as JsonValue; + if (typeof(T) == typeof(int) && resultAsJsonValue?.Type == JsonValueType.Number) + return (T) Convert.ChangeType(resultAsJsonValue.Number, typeof(T)); + if (typeof(T) == typeof(JsonValue)) { + if (result is JsonValue) return (T) result; if (result is double) return (T) (object) new JsonValue((double) result); if (result is bool) @@ -55,7 +59,7 @@ public T Evaluate(TIn json, JsonValue root) if (result is JsonObject) return (T) (object) new JsonValue((JsonObject) result); } - return (T)Convert.ChangeType(result, typeof(T)); + return (T) Convert.ChangeType(result, typeof(T)); } public override string ToString() { diff --git a/Manatee.Json/Path/Expressions/IndexOfExpression.cs b/Manatee.Json/Path/Expressions/IndexOfExpression.cs index 6c5d169..65f8f29 100644 --- a/Manatee.Json/Path/Expressions/IndexOfExpression.cs +++ b/Manatee.Json/Path/Expressions/IndexOfExpression.cs @@ -22,6 +22,7 @@ ***************************************************************************************/ using System; using System.Linq; +using Manatee.Json.Internal; namespace Manatee.Json.Path.Expressions { @@ -34,7 +35,7 @@ internal class IndexOfExpression : PathExpression, IEquatable other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && Equals(Parameter, other.Parameter) && Equals(ParameterExpression, other.ParameterExpression); + return base.Equals(other) && Equals(ParameterExpression, other.ParameterExpression); } public override bool Equals(object obj) { diff --git a/Manatee.Json/Path/Expressions/NameExpresssion.cs b/Manatee.Json/Path/Expressions/NameExpresssion.cs index ff37b04..83bdb8b 100644 --- a/Manatee.Json/Path/Expressions/NameExpresssion.cs +++ b/Manatee.Json/Path/Expressions/NameExpresssion.cs @@ -23,6 +23,7 @@ ***************************************************************************************/ using System; using System.Linq; +using Manatee.Json.Internal; namespace Manatee.Json.Path.Expressions { @@ -35,7 +36,7 @@ internal class NameExpression : PathExpression, IEquatable(string source, ref int index, out ExpressionTreeNode + if (name.Name == "indexOf") + { + JsonValue parameter; + if (source[index] != '(') { - Path = path, - IsLocal = isLocal, - Name = name.Name - }; - } - else if (indexOf != null) - { - path.Operators.Remove(indexOf); - // TODO: Get indexOf parameter - node = new IndexOfExpression + node = null; + return "Expected '('. 'indexOf' operator requires a parameter."; + } + index++; + error = JsonParser.Parse(source, ref index, out parameter, true); + // Swallow this error from the JSON parser and assume the value just ended. + // If it's really a syntax error, the expression parser should catch it. + if (error != null && error != "Expected \',\', \']\', or \'}\'.") { - Path = path, - IsLocal = isLocal - }; + node = null; + return $"Error parsing parameter for 'indexOf' expression: {error}."; + } + if (source[index] != ')') + { + node = null; + return "Expected ')'."; + } + index++; + node = new IndexOfExpression + { + Path = path, + IsLocal = isLocal, + Parameter = parameter + }; + } + else + node = new NameExpression + { + Path = path, + IsLocal = isLocal, + Name = name.Name + }; } else if (length != null) { @@ -61,7 +82,6 @@ public string TryParse(string source, ref int index, out ExpressionTreeNode 0; + return string.Compare(sleft, sright, StringComparison.Ordinal) > 0; var dleft = TryGetNumber(a); var dright = TryGetNumber(b); if (dleft != null && dright != null) @@ -70,7 +72,7 @@ public static bool LessThanEqual(object a, object b) var sleft = TryGetString(a); var sright = TryGetString(b); if (sleft != null && sright != null) - return string.Compare(sleft, sright, System.StringComparison.Ordinal) <= 0; + return string.Compare(sleft, sright, StringComparison.Ordinal) <= 0; var dleft = TryGetNumber(a); var dright = TryGetNumber(b); if (dleft != null && dright != null) @@ -82,7 +84,7 @@ public static bool GreaterThanEqual(object a, object b) var sleft = TryGetString(a); var sright = TryGetString(b); if (sleft != null && sright != null) - return string.Compare(sleft, sright, System.StringComparison.Ordinal) >= 0; + return string.Compare(sleft, sright, StringComparison.Ordinal) >= 0; var dleft = TryGetNumber(a); var dright = TryGetNumber(b); if (dleft != null && dright != null) @@ -98,7 +100,11 @@ private static string TryGetString(object value) private static double? TryGetNumber(object value) { var jv = value as JsonValue; - return jv != null && jv.Type == JsonValueType.Number ? jv.Number : value as double?; + if (jv != null && jv.Type == JsonValueType.Number) return jv.Number; + if (!(value is bool) && value is IConvertible) + return Convert.ToDouble(value); + // at this point, we have no idea what this is. + return null; } private static bool? TryGetBoolean(object value) { diff --git a/Manatee.Json/Path/JsonPathSyntaxException.cs b/Manatee.Json/Path/JsonPathSyntaxException.cs index 18e889f..515b7b7 100644 --- a/Manatee.Json/Path/JsonPathSyntaxException.cs +++ b/Manatee.Json/Path/JsonPathSyntaxException.cs @@ -50,8 +50,8 @@ public class JsonPathSyntaxException : Exception public override string Message => string.Format(_isExpression ? "{0} Expression up to error: '{1}'" : "{0} Path up to error: '{1}'", base.Message, Path); [StringFormatMethod("format")] - internal JsonPathSyntaxException(JsonPath path, string format, params object[] parameters) - : base(string.Format(format, parameters)) + internal JsonPathSyntaxException(JsonPath path, string message) + : base(message) { Path = path.ToString(); } From fe9aafa2d940c3a1c55977a113c176c990e67cfc Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 21 Sep 2016 16:45:40 +1200 Subject: [PATCH 16/18] Added filter expression parse tests that use evaluation. --- Json-Path-Test-Suite | 2 +- Manatee.Json.Tests/DevTest.cs | 2 +- Manatee.Json.Tests/Path/FilterExpressionTest.cs | 60 +++++++++++++++------- .../Path/Expressions/ConversionExpression.cs | 4 +- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/Json-Path-Test-Suite b/Json-Path-Test-Suite index 1cc08f0..c5afa49 160000 --- a/Json-Path-Test-Suite +++ b/Json-Path-Test-Suite @@ -1 +1 @@ -Subproject commit 1cc08f08f835b38635a488b4275d3148d68d552d +Subproject commit c5afa49488b5a659ab7f5569745d9840ee4a12e1 diff --git a/Manatee.Json.Tests/DevTest.cs b/Manatee.Json.Tests/DevTest.cs index 74781e5..2b81b5f 100644 --- a/Manatee.Json.Tests/DevTest.cs +++ b/Manatee.Json.Tests/DevTest.cs @@ -28,7 +28,7 @@ namespace Manatee.Json.Tests { [TestClass] - [Ignore] + [Ignore] public class DevTest { [TestMethod] diff --git a/Manatee.Json.Tests/Path/FilterExpressionTest.cs b/Manatee.Json.Tests/Path/FilterExpressionTest.cs index 650f467..a9fb9c1 100644 --- a/Manatee.Json.Tests/Path/FilterExpressionTest.cs +++ b/Manatee.Json.Tests/Path/FilterExpressionTest.cs @@ -12,6 +12,31 @@ private void Run(JsonPath expected, string text) Assert.AreEqual(expected, actual); } + private void CompareEval(JsonPath expected, string text) + { + var data = new JsonArray + { + 1, + new JsonObject + { + ["bool"] = false, + ["int"] = 20, + ["string"] = "value", + }, + "hello", + true, + 5, + new JsonArray {1,2,3 } + }; + + var actual = JsonPath.Parse(text); + + var expectedResult = expected.Evaluate(data); + var actualResult = actual.Evaluate(data); + + Assert.AreEqual(expectedResult, actualResult); + } + [TestMethod] public void PropertyEqualsValue() { @@ -137,59 +162,56 @@ public void Or() Run(JsonPathWith.Array(jv => jv.HasProperty("test") || jv.Name("name") == 5),"$[?(@.test || @.name == 5)]"); } [TestMethod] - [Ignore] // This won't work the same. Parsing generates an IndexOfExpression with a distinct parameter, // but when constructing the path, the parameter goes through several castings generating a // parameter expression. The parsed path would be different in structure but should still - // represent the same thing. We have to - // TODO: Test by evaluation + // represent the same thing. We have to test by evaluation. public void IndexOfNumber() { - Run(JsonPathWith.Array(jv => jv.IndexOf(5) == 4), "$[?(@.indexOf(5) == 4)]"); + CompareEval(JsonPathWith.Array(jv => jv.IndexOf(5) == 4), "$[?(@.indexOf(5) == 4)]"); } [TestMethod] - [Ignore] // This won't work the same. Parsing generates an IndexOfExpression with a distinct parameter, // but when constructing the path, the parameter goes through several castings generating a // parameter expression. The parsed path would be different in structure but should still - // represent the same thing. - // TODO: Test by evaluation + // represent the same thing. We have to test by evaluation. public void IndexOfBoolean() { - Run(JsonPathWith.Array(jv => jv.IndexOf(false) == 4), "$[?(@.indexOf(false) == 4)]"); + CompareEval(JsonPathWith.Array(jv => jv.IndexOf(true) == 3), "$[?(@.indexOf(true) == 3)]"); } [TestMethod] - [Ignore] // This won't work the same. Parsing generates an IndexOfExpression with a distinct parameter, // but when constructing the path, the parameter goes through several castings generating a // parameter expression. The parsed path would be different in structure but should still - // represent the same thing. - // TODO: Test by evaluation + // represent the same thing. We have to test by evaluation. public void IndexOfString() { - Run(JsonPathWith.Array(jv => jv.IndexOf("string") == 4), "$[?(@.indexOf(\"string\") == 4)]"); + CompareEval(JsonPathWith.Array(jv => jv.IndexOf("string") == 2), "$[?(@.indexOf(\"hello\") == 2)]"); } [TestMethod] - [Ignore] // This won't work the same. Parsing generates a ValueExpression, but the only way to // construct the path is to pass the field, which generates a FieldExpression. The parsed // path would be different in structure but should still represent the same thing. - // TODO: Test by evaluation + // We have to test by evaluation. public void IndexOfArray() { var arr = new JsonArray {1, 2, 3}; - Run(JsonPathWith.Array(jv => jv.IndexOf(arr) == 4), "$[?(@.indexOf([1,2,3]) == 4)]"); + CompareEval(JsonPathWith.Array(jv => jv.IndexOf(arr) == 6), "$[?(@.indexOf([1,2,3]) == 6)]"); } [TestMethod] - [Ignore] // This won't work the same. Parsing generates a ValueExpression, but the only way to // construct the path is to pass the field, which generates a FieldExpression. The parsed // path would be different in structure but should still represent the same thing. - // TODO: Test by evaluation + // We have to test by evaluation. public void IndexOfObject() { - var obj = new JsonObject {{"key", "value"}}; - Run(JsonPathWith.Array(jv => jv.IndexOf(obj) == 4), "$[?(@.indexOf({\"key\":\"value\"}) == 4)]"); + var obj = new JsonObject + { + ["bool"] = false, + ["int"] = 20, + ["string"] = "value", + }; + CompareEval(JsonPathWith.Array(jv => jv.IndexOf(obj) == 1), "$[?(@.indexOf({\"key\":\"value\"}) == 1)]"); } } } diff --git a/Manatee.Json/Path/Expressions/ConversionExpression.cs b/Manatee.Json/Path/Expressions/ConversionExpression.cs index 49ea350..45c4369 100644 --- a/Manatee.Json/Path/Expressions/ConversionExpression.cs +++ b/Manatee.Json/Path/Expressions/ConversionExpression.cs @@ -64,7 +64,9 @@ private object CastValue(object value) return new JsonValue((bool)value); if (value is string) return new JsonValue((string)value); - return new JsonValue(Convert.ToDouble(value)); + if (value is IConvertible) + return new JsonValue(Convert.ToDouble(value)); + return value; } } } \ No newline at end of file From 1b179f640717700dc093d2bcf6d279614b53afca Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 21 Sep 2016 21:46:20 +1200 Subject: [PATCH 17/18] Fixed obsolete extension methods to properly call newer ones. --- Manatee.Json/Path/JsonPathWith.cs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/Manatee.Json/Path/JsonPathWith.cs b/Manatee.Json/Path/JsonPathWith.cs index 968b107..5dc0a99 100644 --- a/Manatee.Json/Path/JsonPathWith.cs +++ b/Manatee.Json/Path/JsonPathWith.cs @@ -130,9 +130,7 @@ public static JsonPath SearchArray(params Slice[] slices) [Obsolete("Use the SearchArray(params Slice[]) overload instead.")] public static JsonPath SearchArraySlice(int? start, int? end, int? step = null) { - var path = new JsonPath(); - path.Operators.Add(new SearchOperator(new ArraySearchParameter(new SliceQuery(new Slice(start, end, step))))); - return path; + return SearchArray(new Slice(start, end, step)); } /// /// Appends a by specifying an expression which evaluates to the index to include. @@ -182,9 +180,7 @@ public static JsonPath Array(params Slice[] slices) [Obsolete("Use the Array(params Slice[]) overload instead.")] public static JsonPath ArraySlice(int? start, int? end, int? step = null) { - var path = new JsonPath(); - path.Operators.Add(new ArrayOperator(new SliceQuery(new Slice(start, end, step)))); - return path; + return Array(new Slice(start, end, step)); } /// /// Appends a by specifying an expression which evaluates to the index to include. @@ -300,10 +296,12 @@ public static JsonPath SearchLength(this JsonPath path) /// The new . public static JsonPath SearchArray(this JsonPath path, params Slice[] slices) { - path.Operators.Add(new SearchOperator(slices.Any() - ? new ArraySearchParameter(new SliceQuery(slices)) - : new ArraySearchParameter(WildCardQuery.Instance))); - return path; + var newPath = new JsonPath(); + newPath.Operators.AddRange(path.Operators); + newPath.Operators.Add(new SearchOperator(slices.Any() + ? new ArraySearchParameter(new SliceQuery(slices)) + : new ArraySearchParameter(WildCardQuery.Instance))); + return newPath; } /// /// Appends a by specifying an expression which evaluates to the index to include. @@ -313,8 +311,10 @@ public static JsonPath SearchArray(this JsonPath path, params Slice[] slices) /// The new . public static JsonPath SearchArray(this JsonPath path, Expression> expression) { - path.Operators.Add(new SearchOperator(new ArraySearchParameter(new IndexExpressionQuery(ExpressionTranslator.Translate(expression))))); - return path; + var newPath = new JsonPath(); + newPath.Operators.AddRange(path.Operators); + newPath.Operators.Add(new SearchOperator(new ArraySearchParameter(new IndexExpressionQuery(ExpressionTranslator.Translate(expression))))); + return newPath; } /// /// Appends a by specifying a predicate expression which filters the values. @@ -324,8 +324,10 @@ public static JsonPath SearchArray(this JsonPath path, ExpressionThe new . public static JsonPath SearchArray(this JsonPath path, Expression> expression) { - path.Operators.Add(new SearchOperator(new ArraySearchParameter(new FilterExpressionQuery(ExpressionTranslator.Translate(expression))))); - return path; + var newPath = new JsonPath(); + newPath.Operators.AddRange(path.Operators); + newPath.Operators.Add(new SearchOperator(new ArraySearchParameter(new FilterExpressionQuery(ExpressionTranslator.Translate(expression))))); + return newPath; } /// /// Appends a by including all array values. @@ -366,10 +368,7 @@ public static JsonPath Array(this JsonPath path, params Slice[] slices) [Obsolete("Use the Array(this JsonPath, params Slice[]) overload instead.")] public static JsonPath ArraySlice(this JsonPath path, int? start, int? end, int? step = null) { - var newPath = new JsonPath(); - newPath.Operators.AddRange(path.Operators); - newPath.Operators.Add(new ArrayOperator(new SliceQuery(new Slice(start, end, step)))); - return newPath; + return path.Array(new Slice(start, end, step)); } /// /// Appends a by specifying an expression which evaluates to the index to include. From 1b862fdb8b856ef8a3efaf017d364a631684f371 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Wed, 21 Sep 2016 22:02:15 +1200 Subject: [PATCH 18/18] Added apache documentation. Removed remaining references to StateMachine. --- Manatee.Json.sln.DotSettings.user | 2 +- Manatee.Json/Manatee.Json.csproj | 30 ---------------------- .../Expressions/Parsing/AddExpressionParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/AndExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/ConstantBooleanExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/ConstantNumberExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/ConstantStringExpressionParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/DivideExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/ExponentExpressionParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/ExpressionEndParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/GroupExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/IJsonPathExpressionParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/IsEqualExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/IsGreaterThanEqualExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/IsGreaterThanExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/IsLessThanEqualExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/IsLessThanExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/IsNotEqualExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/JsonPathExpressionParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/LengthExpressionParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/ModuloExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/MultiplyExpressionParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/NotExpressionParser.cs | 24 ++++++++++++++++- .../Path/Expressions/Parsing/OrExpressionParser.cs | 24 ++++++++++++++++- .../Expressions/Parsing/PathExpressionParser.cs | 24 ++++++++++++++++- .../Parsing/SubtractExpressionParser.cs | 24 ++++++++++++++++- 26 files changed, 553 insertions(+), 55 deletions(-) diff --git a/Manatee.Json.sln.DotSettings.user b/Manatee.Json.sln.DotSettings.user index fe6dd80..646f45c 100644 --- a/Manatee.Json.sln.DotSettings.user +++ b/Manatee.Json.sln.DotSettings.user @@ -2,7 +2,7 @@ SOLUTION True 2016.2 - C:\Users\Gregd\AppData\Local\Temp\ssm.Goxeliz.tmp + C:\Users\Gregd\AppData\Local\Temp\ssm.Qageqif.tmp diff --git a/Manatee.Json/Manatee.Json.csproj b/Manatee.Json/Manatee.Json.csproj index b1bcace..196a586 100644 --- a/Manatee.Json/Manatee.Json.csproj +++ b/Manatee.Json/Manatee.Json.csproj @@ -167,36 +167,6 @@ prompt MinimumRecommendedRules.ruleset - - - ..\packages\Manatee.StateMachine.1.1.2\lib\net35\Manatee.StateMachine.dll - True - - - - - ..\packages\Manatee.StateMachine.1.1.2\lib\net35-client\Manatee.StateMachine.dll - True - - - - - ..\packages\Manatee.StateMachine.1.1.2\lib\net40\Manatee.StateMachine.dll - True - - - - - ..\packages\Manatee.StateMachine.1.1.2\lib\net40-client\Manatee.StateMachine.dll - True - - - - - ..\packages\Manatee.StateMachine.1.1.2\lib\net45\Manatee.StateMachine.dll - True - - diff --git a/Manatee.Json/Path/Expressions/Parsing/AddExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/AddExpressionParser.cs index eabece4..ac5d5ca 100644 --- a/Manatee.Json/Path/Expressions/Parsing/AddExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/AddExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: AddExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: AddExpressionParser + Purpose: Parses addition expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class AddExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/AndExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/AndExpressionParser.cs index a1a961b..a7569f4 100644 --- a/Manatee.Json/Path/Expressions/Parsing/AndExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/AndExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: AndExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: AndExpressionParser + Purpose: Parses logical-and expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class AndExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/ConstantBooleanExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ConstantBooleanExpressionParser.cs index 1c54e1d..817dca1 100644 --- a/Manatee.Json/Path/Expressions/Parsing/ConstantBooleanExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/ConstantBooleanExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ConstantBooleanExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: ConstantBooleanExpressionParser + Purpose: Parses boolean expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class ConstantBooleanExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/ConstantNumberExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ConstantNumberExpressionParser.cs index 860f4a4..9d86428 100644 --- a/Manatee.Json/Path/Expressions/Parsing/ConstantNumberExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/ConstantNumberExpressionParser.cs @@ -1,4 +1,26 @@ -using Manatee.Json.Path.Parsing; +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ConstantNumberExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: ConstantNumberExpressionParser + Purpose: Parses number expressions within JsonPath. + +***************************************************************************************/ +using Manatee.Json.Path.Parsing; namespace Manatee.Json.Path.Expressions.Parsing { diff --git a/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs index 926e6b4..7b8a2e6 100644 --- a/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/ConstantStringExpressionParser.cs @@ -1,4 +1,26 @@ -using Manatee.Json.Internal; +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ConstantStringExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: ConstantStringExpressionParser + Purpose: Parses string expressions within JsonPath. + +***************************************************************************************/ +using Manatee.Json.Internal; using Manatee.Json.Path.Parsing; namespace Manatee.Json.Path.Expressions.Parsing diff --git a/Manatee.Json/Path/Expressions/Parsing/DivideExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/DivideExpressionParser.cs index 0a2b5af..fbcc224 100644 --- a/Manatee.Json/Path/Expressions/Parsing/DivideExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/DivideExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: DivideExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: DivideExpressionParser + Purpose: Parses division expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class DivideExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/ExponentExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ExponentExpressionParser.cs index 68e9f52..f572226 100644 --- a/Manatee.Json/Path/Expressions/Parsing/ExponentExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/ExponentExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ExponentExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: ExponentExpressionParser + Purpose: Parses exponential expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class ExponentExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/ExpressionEndParser.cs b/Manatee.Json/Path/Expressions/Parsing/ExpressionEndParser.cs index b2f69f3..1639f18 100644 --- a/Manatee.Json/Path/Expressions/Parsing/ExpressionEndParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/ExpressionEndParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ExpressionEndParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: ExpressionEndParser + Purpose: Indicates the end of expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class ExpressionEndParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/GroupExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/GroupExpressionParser.cs index 10ab145..5ba72f4 100644 --- a/Manatee.Json/Path/Expressions/Parsing/GroupExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/GroupExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: GroupExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: GroupExpressionParser + Purpose: Parses parenthetical groups in expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class GroupExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs index 136c7b8..37ef179 100644 --- a/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/IJsonPathExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IJsonPathExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: IJsonPathExpressionParser + Purpose: Defines methods to parse JsonPath expressions. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal interface IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/IsEqualExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsEqualExpressionParser.cs index ca0c16a..d9bb1da 100644 --- a/Manatee.Json/Path/Expressions/Parsing/IsEqualExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/IsEqualExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IsEqualExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: IsEqualExpressionParser + Purpose: Parses equality expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class IsEqualExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanEqualExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanEqualExpressionParser.cs index 556d4ed..b69790a 100644 --- a/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanEqualExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanEqualExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IsGreaterThanEqualExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: IsGreaterThanEqualExpressionParser + Purpose: Parses greater-than-or-equal inequality expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class IsGreaterThanEqualExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanExpressionParser.cs index 49653f9..c1b1e90 100644 --- a/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/IsGreaterThanExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IsGreaterThanExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: IsGreaterThanExpressionParser + Purpose: Parses greater-than inequality expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class IsGreaterThanExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/IsLessThanEqualExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsLessThanEqualExpressionParser.cs index 7740fa5..3e4255f 100644 --- a/Manatee.Json/Path/Expressions/Parsing/IsLessThanEqualExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/IsLessThanEqualExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IsLessThanEqualExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: IsLessThanEqualExpressionParser + Purpose: Parses less-than-or-equal inequality expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class IsLessThanEqualExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/IsLessThanExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsLessThanExpressionParser.cs index ade45d9..df40e96 100644 --- a/Manatee.Json/Path/Expressions/Parsing/IsLessThanExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/IsLessThanExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IsLessThanExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: IsLessThanExpressionParser + Purpose: Parses lees-than inequality expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class IsLessThanExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/IsNotEqualExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/IsNotEqualExpressionParser.cs index fe7aae5..a22caac 100644 --- a/Manatee.Json/Path/Expressions/Parsing/IsNotEqualExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/IsNotEqualExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: IsNotEqualExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: IsNotEqualExpressionParser + Purpose: Parses inequality expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class IsNotEqualExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs index c2aae32..ebb1a6f 100644 --- a/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/JsonPathExpressionParser.cs @@ -1,4 +1,26 @@ -using System; +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: JsonPathExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: JsonPathExpressionParser + Purpose: Parses JsonPath expressions within JsonPath. + +***************************************************************************************/ +using System; using System.Collections.Generic; using System.Linq; using Manatee.Json.Internal; diff --git a/Manatee.Json/Path/Expressions/Parsing/LengthExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/LengthExpressionParser.cs index 2d64a88..7a24b96 100644 --- a/Manatee.Json/Path/Expressions/Parsing/LengthExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/LengthExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: LengthExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: LengthExpressionParser + Purpose: Parses .length expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class LengthExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/ModuloExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/ModuloExpressionParser.cs index c745ac7..60099f1 100644 --- a/Manatee.Json/Path/Expressions/Parsing/ModuloExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/ModuloExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: ModuloExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: ModuloExpressionParser + Purpose: Parses modulo expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class ModuloExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/MultiplyExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/MultiplyExpressionParser.cs index b7a0066..f150384 100644 --- a/Manatee.Json/Path/Expressions/Parsing/MultiplyExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/MultiplyExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: MultiplyExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: MultiplyExpressionParser + Purpose: Parses mulitplication expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class MultiplyExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/NotExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/NotExpressionParser.cs index e5ca09b..3f1fe9f 100644 --- a/Manatee.Json/Path/Expressions/Parsing/NotExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/NotExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: NotExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: NotExpressionParser + Purpose: Parses logical-not expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class NotExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/OrExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/OrExpressionParser.cs index 09b1581..8f1c490 100644 --- a/Manatee.Json/Path/Expressions/Parsing/OrExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/OrExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: OrExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: OrExpressionParser + Purpose: Parses logical-or expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class OrExpressionParser : IJsonPathExpressionParser { diff --git a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs index 1f8e19c..e2f81b6 100644 --- a/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/PathExpressionParser.cs @@ -1,4 +1,26 @@ -using System; +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: PathExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: PathExpressionParser + Purpose: Parses expressions within JsonPath. + +***************************************************************************************/ +using System; using System.Linq; using Manatee.Json.Internal; using Manatee.Json.Parsing; diff --git a/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs b/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs index 72ce448..1830243 100644 --- a/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs +++ b/Manatee.Json/Path/Expressions/Parsing/SubtractExpressionParser.cs @@ -1,4 +1,26 @@ -namespace Manatee.Json.Path.Expressions.Parsing +/*************************************************************************************** + + Copyright 2016 Greg Dennis + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + File Name: SubtractExpressionParser.cs + Namespace: Manatee.Json.Path.Expressions.Parsing + Class Name: SubtractExpressionParser + Purpose: Parses subtraction expressions within JsonPath. + +***************************************************************************************/ +namespace Manatee.Json.Path.Expressions.Parsing { internal class SubtractExpressionParser : IJsonPathExpressionParser {