From 042c2b3024bc1f2bdc3119c7579425c168ddeb5f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 17 Nov 2013 00:22:02 +1300 Subject: [PATCH] -JsonPath++ --- .../Linq/JsonPath/JPathExecuteTests.cs | 105 +++++++ .../Linq/JsonPath/JPathParseTests.cs | 178 ++++++++++- .../Linq/JsonPath/ArraySliceFilter.cs | 5 +- .../Linq/JsonPath/FieldFilter.cs | 7 +- Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs | 295 ++++++++++++++---- .../Linq/JsonPath/QueryFilter.cs | 78 ++++- 6 files changed, 593 insertions(+), 75 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs index b67c3615..0e31faad 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs @@ -355,6 +355,111 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath Assert.AreEqual("Jeff", a2); } + [Test] + public void ExistsQuery() + { + JArray a = new JArray(new JObject(new JProperty("hi", "ho")), new JObject(new JProperty("hi2", "ha"))); + + IList t = a.SelectTokens("[ ?( @.hi ) ]").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(1, t.Count); + Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", "ho")), t[0])); + } + + [Test] + public void EqualsQuery() + { + JArray a = new JArray( + new JObject(new JProperty("hi", "ho")), + new JObject(new JProperty("hi", "ha"))); + + IList t = a.SelectTokens("[ ?( @.['hi'] == 'ha' ) ]").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(1, t.Count); + Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", "ha")), t[0])); + } + + [Test] + public void NotEqualsQuery() + { + JArray a = new JArray( + new JArray(new JObject(new JProperty("hi", "ho"))), + new JArray(new JObject(new JProperty("hi", "ha")))); + + IList t = a.SelectTokens("[ ?( @..hi <> 'ha' ) ]").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(1, t.Count); + Assert.IsTrue(JToken.DeepEquals(new JArray(new JObject(new JProperty("hi", "ho"))), t[0])); + } + + [Test] + public void NoPathQuery() + { + JArray a = new JArray(1, 2, 3); + + IList t = a.SelectTokens("[ ?( @ > 1 ) ]").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(2, t.Count); + Assert.AreEqual(2, (int)t[0]); + Assert.AreEqual(3, (int)t[1]); + } + + [Test] + public void GreaterQuery() + { + JArray a = new JArray( + new JObject(new JProperty("hi", 1)), + new JObject(new JProperty("hi", 2)), + new JObject(new JProperty("hi", 3))); + + IList t = a.SelectTokens("[ ?( @.hi > 1 ) ]").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(2, t.Count); + Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 2)), t[0])); + Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 3)), t[1])); + } + + [Test] + public void GreaterOrEqualQuery() + { + JArray a = new JArray( + new JObject(new JProperty("hi", 1)), + new JObject(new JProperty("hi", 2)), + new JObject(new JProperty("hi", 3))); + + IList t = a.SelectTokens("[ ?( @.hi >= 1 ) ]").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(3, t.Count); + Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 1)), t[0])); + Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 2)), t[1])); + Assert.IsTrue(JToken.DeepEquals(new JObject(new JProperty("hi", 3)), t[2])); + } + + [Test] + public void NestedQuery() + { + JArray a = new JArray( + new JObject( + new JProperty("name", "Bad Boys"), + new JProperty("cast", new JArray( + new JObject(new JProperty("name", "Will Smith"))))), + new JObject( + new JProperty("name", "Independence Day"), + new JProperty("cast", new JArray( + new JObject(new JProperty("name", "Will Smith"))))), + new JObject( + new JProperty("name", "The Rock"), + new JProperty("cast", new JArray( + new JObject(new JProperty("name", "Nick Cage"))))) + ); + + IList t = a.SelectTokens("[?(@.cast[?(@.name=='Will Smith')])].name").ToList(); + Assert.IsNotNull(t); + Assert.AreEqual(2, t.Count); + Assert.AreEqual("Bad Boys", (string)t[0]); + Assert.AreEqual("Independence Day", (string)t[1]); + } + [Test] public void PathWithConstructor() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs index 79b19a55..c20aa08d 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs @@ -38,7 +38,6 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; - #endif namespace Newtonsoft.Json.Tests.Linq.JsonPath @@ -211,13 +210,184 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath } [Test] - public void SinglePropertyAndFilter() + public void SinglePropertyAndExistsQuery() { - JPath path = new JPath("Blah[?(@.name=='hi')]"); + JPath path = new JPath("Blah[ ?( @..name ) ]"); Assert.AreEqual(2, path.Filters.Count); Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); - List expressions = ((QueryFilter)path.Filters[1]).Expression; + List expressions = ((QueryFilter)path.Filters[1]).Expression; Assert.AreEqual(1, expressions.Count); + Assert.AreEqual(QueryOperator.Exists, expressions[0].Operator); + Assert.AreEqual(1, expressions[0].Path.Count); + Assert.AreEqual("name", ((ScanFilter)expressions[0].Path[0]).Name); + } + + [Test] + public void SinglePropertyAndFilterWithWhitespace() + { + JPath path = new JPath("Blah[ ?( @.name=='hi' ) ]"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + List expressions = ((QueryFilter)path.Filters[1]).Expression; + Assert.AreEqual(1, expressions.Count); + Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator); + Assert.AreEqual("hi", (string)expressions[0].Value); + } + + [Test] + public void SinglePropertyAndFilterWithEscapeQuote() + { + JPath path = new JPath(@"Blah[ ?( @.name=='h\'i' ) ]"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + List expressions = ((QueryFilter)path.Filters[1]).Expression; + Assert.AreEqual(1, expressions.Count); + Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator); + Assert.AreEqual("h'i", (string)expressions[0].Value); + } + + [Test] + public void SinglePropertyAndFilterWithDoubleEscape() + { + JPath path = new JPath(@"Blah[ ?( @.name=='h\\i' ) ]"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + List expressions = ((QueryFilter)path.Filters[1]).Expression; + Assert.AreEqual(1, expressions.Count); + Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator); + Assert.AreEqual("h\\i", (string)expressions[0].Value); + } + + [Test] + public void SinglePropertyAndFilterWithUnknownEscape() + { + ExceptionAssert.Throws( + @"Unknown escape chracter: \i", + () => { new JPath(@"Blah[ ?( @.name=='h\i' ) ]"); }); + } + + [Test] + public void SinglePropertyAndFilterWithFalse() + { + JPath path = new JPath("Blah[ ?( @.name==false ) ]"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + List expressions = ((QueryFilter)path.Filters[1]).Expression; + Assert.AreEqual(1, expressions.Count); + Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator); + Assert.AreEqual(false, (bool)expressions[0].Value); + } + + [Test] + public void SinglePropertyAndFilterWithTrue() + { + JPath path = new JPath("Blah[ ?( @.name==true ) ]"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + List expressions = ((QueryFilter)path.Filters[1]).Expression; + Assert.AreEqual(1, expressions.Count); + Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator); + Assert.AreEqual(true, (bool)expressions[0].Value); + } + + [Test] + public void SinglePropertyAndFilterWithNull() + { + JPath path = new JPath("Blah[ ?( @.name==null ) ]"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + List expressions = ((QueryFilter)path.Filters[1]).Expression; + Assert.AreEqual(1, expressions.Count); + Assert.AreEqual(QueryOperator.Equals, expressions[0].Operator); + Assert.AreEqual(null, expressions[0].Value.Value); + } + + [Test] + public void FilterWithScan() + { + JPath path = new JPath("[?(@..name<>null)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual("name", ((ScanFilter)expressions[0].Path[0]).Name); + } + + [Test] + public void FilterWithNotEquals() + { + JPath path = new JPath("[?(@.name<>null)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(QueryOperator.NotEquals, expressions[0].Operator); + } + + [Test] + public void FilterWithNotEquals2() + { + JPath path = new JPath("[?(@.name!=null)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(QueryOperator.NotEquals, expressions[0].Operator); + } + + [Test] + public void FilterWithLessThan() + { + JPath path = new JPath("[?(@.name expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(QueryOperator.LessThan, expressions[0].Operator); + } + + [Test] + public void FilterWithLessThanOrEquals() + { + JPath path = new JPath("[?(@.name<=null)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(QueryOperator.LessThanOrEquals, expressions[0].Operator); + } + + [Test] + public void FilterWithGreaterThan() + { + JPath path = new JPath("[?(@.name>null)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(QueryOperator.GreaterThan, expressions[0].Operator); + } + + [Test] + public void FilterWithGreaterThanOrEquals() + { + JPath path = new JPath("[?(@.name>=null)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(QueryOperator.GreaterThanOrEquals, expressions[0].Operator); + } + + [Test] + public void FilterWithInteger() + { + JPath path = new JPath("[?(@.name>=12)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(12, (int)expressions[0].Value); + } + + [Test] + public void FilterWithNegativeInteger() + { + JPath path = new JPath("[?(@.name>=-12)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(-12, (int)expressions[0].Value); + } + + [Test] + public void FilterWithFloat() + { + JPath path = new JPath("[?(@.name>=12.1)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(12.1d, (double)expressions[0].Value); + } + + [Test] + public void FilterWithFloatExp() + { + JPath path = new JPath("[?(@.name>=5.56789e+0)]"); + List expressions = ((QueryFilter)path.Filters[0]).Expression; + Assert.AreEqual(5.56789e+0, (double)expressions[0].Value); } [Test] diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs index d0213d63..262cbe3d 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs @@ -14,14 +14,11 @@ namespace Newtonsoft.Json.Linq.JsonPath if (Step == 0) throw new JsonException("Step cannot be zero."); + // todo error when no match foreach (JToken t in current) { IList sourceCollection = t as IList ?? new List(t); - // nothing to slice - if (sourceCollection.Count == 0) - yield break; - // set defaults for null arguments int stepCount = Step ?? 1; int startIndex = Start ?? ((stepCount > 0) ? 0 : sourceCollection.Count - 1); diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs index 6e715499..d4ae7dcf 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs @@ -19,10 +19,11 @@ namespace Newtonsoft.Json.Linq.JsonPath { JToken v = o[Name]; - if (v == null && errorWhenNoMatch) - throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, Name)); + if (v != null) + yield return v; - yield return v; + if (errorWhenNoMatch) + throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, Name)); } else { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs index eabcaa5b..8e80e574 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text; using System.Xml.Linq; using Newtonsoft.Json.Utilities; @@ -51,8 +52,6 @@ namespace Newtonsoft.Json.Linq.JsonPath private void ParseMain() { int currentPartStartIndex = _currentIndex; - bool followingIndexer = false; - bool scan = false; if (_expression.Length == 0) return; @@ -68,20 +67,27 @@ namespace Newtonsoft.Json.Linq.JsonPath if (_expression[_currentIndex] != '.') throw new JsonException("Unexpected character while parsing path: " + _expression[_currentIndex]); - _currentIndex++; - - EnsureLength("Unexpected end while parsing path."); - - if (_expression[_currentIndex] == '.') - { - scan = true; - _currentIndex++; - } - currentPartStartIndex = _currentIndex; } - while (_currentIndex < _expression.Length) + if (!ParsePath(Filters, currentPartStartIndex, false)) + { + int lastCharacterIndex = _currentIndex; + + EatWhitespace(); + + if (_currentIndex < _expression.Length) + throw new JsonException("Unexpected character while parsing path: " + _expression[lastCharacterIndex]); + } + } + + private bool ParsePath(List filters, int currentPartStartIndex, bool query) + { + bool scan = false; + bool followingIndexer = false; + + bool ended = false; + while (_currentIndex < _expression.Length && !ended) { char currentChar = _expression[_currentIndex]; @@ -93,22 +99,23 @@ namespace Newtonsoft.Json.Linq.JsonPath { string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex); PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member }; - Filters.Add(filter); + filters.Add(filter); scan = false; } - ParseIndexer(currentChar); + filters.Add(ParseIndexer(currentChar)); _currentIndex++; currentPartStartIndex = _currentIndex; followingIndexer = true; break; case ']': case ')': - throw new JsonException("Unexpected character while parsing path: " + currentChar); + ended = true; + break; case ' ': - EatWhitespace(); + //EatWhitespace(); if (_currentIndex < _expression.Length) - throw new JsonException("Unexpected character while parsing path: " + currentChar); + ended = true; break; case '.': if (_currentIndex > currentPartStartIndex) @@ -117,7 +124,7 @@ namespace Newtonsoft.Json.Linq.JsonPath if (member == "*") member = null; PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member }; - Filters.Add(filter); + filters.Add(filter); scan = false; } if (_currentIndex + 1 < _expression.Length && _expression[_currentIndex + 1] == '.') @@ -130,10 +137,17 @@ namespace Newtonsoft.Json.Linq.JsonPath followingIndexer = false; break; default: - if (followingIndexer) - throw new JsonException("Unexpected character following indexer: " + currentChar); + if (query && (currentChar == '=' || currentChar == '<' || currentChar == '!' || currentChar == '>')) + { + ended = true; + } + else + { + if (followingIndexer) + throw new JsonException("Unexpected character following indexer: " + currentChar); - _currentIndex++; + _currentIndex++; + } break; } } @@ -144,11 +158,13 @@ namespace Newtonsoft.Json.Linq.JsonPath if (member == "*") member = null; PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member }; - Filters.Add(filter); + filters.Add(filter); } + + return (_currentIndex == _expression.Length); } - private void ParseIndexer(char indexerOpenChar) + private PathFilter ParseIndexer(char indexerOpenChar) { _currentIndex++; @@ -160,19 +176,19 @@ namespace Newtonsoft.Json.Linq.JsonPath if (_expression[_currentIndex] == '\'') { - ParseQuotedField(indexerCloseChar); + return ParseQuotedField(indexerCloseChar); } else if (_expression[_currentIndex] == '?') { - ParseQuery(indexerCloseChar); + return ParseQuery(indexerCloseChar); } else { - ParseArrayIndexer(indexerCloseChar); + return ParseArrayIndexer(indexerCloseChar); } } - private void ParseArrayIndexer(char indexerCloseChar) + private PathFilter ParseArrayIndexer(char indexerCloseChar) { int start = _currentIndex; int? end = null; @@ -206,7 +222,7 @@ namespace Newtonsoft.Json.Linq.JsonPath int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture); indexes.Add(index); - Filters.Add(new ArrayMultipleIndexFilter { Indexes = indexes }); + return new ArrayMultipleIndexFilter { Indexes = indexes }; } else if (colonCount > 0) { @@ -221,7 +237,7 @@ namespace Newtonsoft.Json.Linq.JsonPath step = index; } - Filters.Add(new ArraySliceFilter { Start = startIndex, End = endIndex, Step = step }); + return new ArraySliceFilter { Start = startIndex, End = endIndex, Step = step }; } else { @@ -231,10 +247,8 @@ namespace Newtonsoft.Json.Linq.JsonPath string indexer = _expression.Substring(start, length); int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture); - Filters.Add(new ArrayIndexFilter { Index = index }); + return new ArrayIndexFilter { Index = index }; } - - return; } else if (currentCharacter == ',') { int length = (end ?? _currentIndex) - start; @@ -264,8 +278,7 @@ namespace Newtonsoft.Json.Linq.JsonPath if (_expression[_currentIndex] != indexerCloseChar) throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter); - Filters.Add(new ArrayIndexFilter()); - return; + return new ArrayIndexFilter(); } else if (currentCharacter == ':') { @@ -321,7 +334,7 @@ namespace Newtonsoft.Json.Linq.JsonPath } } - private void ParseQuery(char indexerCloseChar) + private PathFilter ParseQuery(char indexerCloseChar) { _currentIndex++; EnsureLength("Path ended with open indexer."); @@ -329,25 +342,27 @@ namespace Newtonsoft.Json.Linq.JsonPath if (_expression[_currentIndex] != '(') throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]); - List expressions = new List(); + _currentIndex++; + + List expressions = new List(); while (true) { - ParseExpression(); - expressions.Add(string.Empty); + expressions.Add(ParseExpression()); if (_expression[_currentIndex] == ')') { _currentIndex++; EnsureLength("Path ended with open indexer."); + EatWhitespace(); + if (_expression[_currentIndex] != indexerCloseChar) throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]); - Filters.Add(new QueryFilter + return new QueryFilter { Expression = expressions - }); - return; + }; } else { @@ -356,25 +371,179 @@ namespace Newtonsoft.Json.Linq.JsonPath } } - private void ParseExpression() + private QueryExpression ParseExpression() { - _currentIndex++; - while (_currentIndex < _expression.Length) - { - char currentCharacter = _expression[_currentIndex]; + EatWhitespace(); - if (currentCharacter == ')') - { - return; - } - else - { - _currentIndex++; - } + if (_expression[_currentIndex] != '@') + throw new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex]); + + _currentIndex++; + + List expressionPath = new List(); + + if (ParsePath(expressionPath, _currentIndex, true)) + throw new JsonException("Path ended with open query."); + + EatWhitespace(); + EnsureLength("Path ended with open query."); + + QueryOperator op; + object value = null; + if (_expression[_currentIndex] == ')') + { + op = QueryOperator.Exists; } + else + { + op = ParseOperator(); + + EatWhitespace(); + EnsureLength("Path ended with open query."); + + value = ParseValue(); + + EatWhitespace(); + EnsureLength("Path ended with open query."); + } + + return new QueryExpression + { + Path = expressionPath, + Operator = op, + Value = (op != QueryOperator.Exists) ? new JValue(value) : null + }; } - private void ParseQuotedField(char indexerCloseChar) + private object ParseValue() + { + char currentChar = _expression[_currentIndex]; + if (currentChar == '\'') + { + StringBuilder sb = new StringBuilder(); + + _currentIndex++; + while (_currentIndex < _expression.Length) + { + currentChar = _expression[_currentIndex]; + if (currentChar == '\\' && _currentIndex + 1 < _expression.Length) + { + _currentIndex++; + + if (_expression[_currentIndex] == '\'') + sb.Append('\''); + else if (_expression[_currentIndex] == '\\') + sb.Append('\\'); + else + throw new JsonException(@"Unknown escape chracter: \" + _expression[_currentIndex]); + + _currentIndex++; + } + else if (currentChar == '\'') + { + _currentIndex++; + return sb.ToString(); + } + else + { + _currentIndex++; + sb.Append(currentChar); + } + } + } + else if (char.IsDigit(currentChar) || currentChar == '-') + { + StringBuilder sb = new StringBuilder(); + sb.Append(currentChar); + + _currentIndex++; + while (_currentIndex < _expression.Length) + { + currentChar = _expression[_currentIndex]; + if (currentChar == ' ' || currentChar == ')') + { + string numberText = sb.ToString(); + + if (numberText.IndexOfAny(new char[] { '.', 'E', 'e' }) != -1) + { + double d; + if (double.TryParse(numberText, out d)) + return d; + else + throw new JsonException("Could not read query value."); + } + else + { + long l; + if (long.TryParse(numberText, out l)) + return l; + else + throw new JsonException("Could not read query value."); + } + } + else + { + sb.Append(currentChar); + _currentIndex++; + } + } + } + else if (currentChar == 't') + { + if (Match("true")) + return true; + } + else if (currentChar == 'f') + { + if (Match("false")) + return false; + } + else if (currentChar == 'n') + { + if (Match("null")) + return null; + } + + throw new JsonException("Could not read query value."); + } + + private bool Match(string s) + { + int currentPosition = _currentIndex; + foreach (char c in s) + { + if (currentPosition < _expression.Length && _expression[currentPosition] == c) + currentPosition++; + else + return false; + } + + _currentIndex = currentPosition; + return true; + } + + private QueryOperator ParseOperator() + { + if (_currentIndex + 1 >= _expression.Length) + throw new JsonException("Path ended with open query."); + + if (Match("==")) + return QueryOperator.Equals; + if (Match("!=") || Match("<>")) + return QueryOperator.NotEquals; + if (Match("<=")) + return QueryOperator.LessThanOrEquals; + if (Match("<")) + return QueryOperator.LessThan; + if (Match(">=")) + return QueryOperator.GreaterThanOrEquals; + if (Match(">")) + return QueryOperator.GreaterThan; + + throw new JsonException("Could not read query operator."); + } + + private FieldFilter ParseQuotedField(char indexerCloseChar) { _currentIndex++; int start = _currentIndex; @@ -398,9 +567,7 @@ namespace Newtonsoft.Json.Linq.JsonPath throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter); string indexer = _expression.Substring(start, length); - Filters.Add(new FieldFilter { Name = indexer }); - - return; + return new FieldFilter { Name = indexer }; } _currentIndex++; @@ -415,11 +582,15 @@ namespace Newtonsoft.Json.Linq.JsonPath throw new JsonException(message); } - internal IEnumerable Evaluate(JToken root, bool errorWhenNoMatch) + internal IEnumerable Evaluate(JToken t, bool errorWhenNoMatch) { - IEnumerable current = new [] { root }; + return Evaluate(Filters, t, errorWhenNoMatch); + } - foreach (PathFilter filter in Filters) + internal static IEnumerable Evaluate(List filters, JToken t, bool errorWhenNoMatch) + { + IEnumerable current = new[] { t }; + foreach (PathFilter filter in filters) { current = filter.ExecuteFilter(current, errorWhenNoMatch); } diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs index 428fae9b..d06b96b1 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs @@ -1,14 +1,88 @@ +using System; using System.Collections.Generic; +using System.Linq; namespace Newtonsoft.Json.Linq.JsonPath { + internal enum QueryOperator + { + None, + Equals, + NotEquals, + Exists, + LessThan, + LessThanOrEquals, + GreaterThan, + GreaterThanOrEquals + } + + internal class QueryExpression + { + public List Path { get; set; } + public QueryOperator Operator { get; set; } + public JValue Value { get; set; } + } + internal class QueryFilter : PathFilter { - public List Expression { get; set; } + public List Expression { get; set; } public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) { - throw new System.NotImplementedException(); + foreach (JToken t in current) + { + foreach (JToken v in t) + { + if (Evaluate(errorWhenNoMatch, v)) + yield return v; + } + } + } + + private bool Evaluate(bool errorWhenNoMatch, JToken value) + { + foreach (QueryExpression expression in Expression) + { + IEnumerable pathResult = JPath.Evaluate(expression.Path, value, errorWhenNoMatch); + + foreach (JToken r in pathResult) + { + JValue v = r as JValue; + switch (expression.Operator) + { + case QueryOperator.Equals: + if (v != null && v.Equals(expression.Value)) + return true; + break; + case QueryOperator.NotEquals: + if (v != null && !v.Equals(expression.Value)) + return true; + break; + case QueryOperator.GreaterThan: + if (v != null && v.CompareTo(expression.Value) > 0) + return true; + break; + case QueryOperator.GreaterThanOrEquals: + if (v != null && v.CompareTo(expression.Value) >= 0) + return true; + break; + case QueryOperator.LessThan: + if (v != null && v.CompareTo(expression.Value) < 0) + return true; + break; + case QueryOperator.LessThanOrEquals: + if (v != null && v.CompareTo(expression.Value) <= 0) + return true; + break; + case QueryOperator.Exists: + return true; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + return false; } } } \ No newline at end of file