From 5ce7d8d3dc92b91c23a155a0f23b1a6dce58e214 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 16 Nov 2013 12:30:47 +1300 Subject: [PATCH] -JsonPath++ --- .../JPathExecuteTests.cs} | 261 +++-------- .../Linq/JsonPath/JPathParseTests.cs | 383 ++++++++++++++++ .../Newtonsoft.Json.Tests.csproj | 3 +- Src/Newtonsoft.Json/Linq/JToken.cs | 1 + .../Linq/JsonPath/ArrayIndexFilter.cs | 40 ++ .../Linq/JsonPath/ArrayMultipleIndexFilter.cs | 23 + .../Linq/JsonPath/ArraySliceFilter.cs | 49 ++ .../Linq/JsonPath/FieldFilter.cs | 45 ++ .../Linq/{ => JsonPath}/JPath.cs | 424 ++++++------------ .../Linq/JsonPath/PathFilter.cs | 49 ++ .../Linq/JsonPath/QueryFilter.cs | 14 + .../Linq/JsonPath/ScanFilter.cs | 55 +++ Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 9 +- 13 files changed, 847 insertions(+), 509 deletions(-) rename Src/Newtonsoft.Json.Tests/Linq/{JPathTests.cs => JsonPath/JPathExecuteTests.cs} (62%) create mode 100644 Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/ArrayIndexFilter.cs create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/ArrayMultipleIndexFilter.cs create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs rename Src/Newtonsoft.Json/Linq/{ => JsonPath}/JPath.cs (52%) create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/PathFilter.cs create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs create mode 100644 Src/Newtonsoft.Json/Linq/JsonPath/ScanFilter.cs diff --git a/Src/Newtonsoft.Json.Tests/Linq/JPathTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs similarity index 62% rename from Src/Newtonsoft.Json.Tests/Linq/JPathTests.cs rename to Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs index 7247081b..b67c3615 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JPathTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json.Linq.JsonPath; #if !NETFX_CORE using NUnit.Framework; #else @@ -40,219 +41,11 @@ using System.Linq; #endif -namespace Newtonsoft.Json.Tests.Linq +namespace Newtonsoft.Json.Tests.Linq.JsonPath { [TestFixture] - public class JPathTests : TestFixtureBase + public class JPathExecuteTests : TestFixtureBase { - [Test] - public void SingleProperty() - { - JPath path = new JPath("Blah"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name); - } - - [Test] - public void SinglePropertyWithRoot() - { - JPath path = new JPath("$.Blah"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name); - } - - [Test] - public void WildcardPropertyWithRoot() - { - JPath path = new JPath("$.*"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual(null, ((FieldFilter)path.Parts[0]).Name); - } - - [Test] - public void WildcardArrayWithRoot() - { - JPath path = new JPath("$.[*]"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual(null, ((ArrayIndexFilter)path.Parts[0]).Index); - } - - [Test] - public void WildcardArray() - { - JPath path = new JPath("[*]"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual(null, ((ArrayIndexFilter)path.Parts[0]).Index); - } - - [Test] - public void WildcardArrayWithProperty() - { - JPath path = new JPath("[*].derp"); - Assert.AreEqual(2, path.Parts.Count); - Assert.AreEqual(null, ((ArrayIndexFilter)path.Parts[0]).Index); - Assert.AreEqual("derp", ((FieldFilter)path.Parts[1]).Name); - } - - [Test] - public void QuotedWildcardPropertyWithRoot() - { - JPath path = new JPath("$.['*']"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual("*", ((FieldFilter)path.Parts[0]).Name); - } - - [Test] - public void SingleScanWithRoot() - { - JPath path = new JPath("$..Blah"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual("Blah", ((ScanFilter)path.Parts[0]).Name); - } - - [Test] - public void WildcardScanWithRoot() - { - JPath path = new JPath("$..*"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual(null, ((ScanFilter)path.Parts[0]).Name); - } - - [Test] - public void TwoProperties() - { - JPath path = new JPath("Blah.Two"); - Assert.AreEqual(2, path.Parts.Count); - Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name); - Assert.AreEqual("Two", ((FieldFilter)path.Parts[1]).Name); - } - - [Test] - public void OnePropertyOneScan() - { - JPath path = new JPath("Blah..Two"); - Assert.AreEqual(2, path.Parts.Count); - Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name); - Assert.AreEqual("Two", ((ScanFilter)path.Parts[1]).Name); - } - - [Test] - public void SinglePropertyAndIndexer() - { - JPath path = new JPath("Blah[0]"); - Assert.AreEqual(2, path.Parts.Count); - Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name); - Assert.AreEqual(0, ((ArrayIndexFilter)path.Parts[1]).Index); - } - - [Test] - public void SinglePropertyAndFilter() - { - JPath path = new JPath("Blah[?(@.name=='hi')]"); - Assert.AreEqual(2, path.Parts.Count); - Assert.AreEqual("Blah", ((FieldFilter)path.Parts[0]).Name); - List expressions = ((QueryFilter)path.Parts[1]).Expression; - Assert.AreEqual(1, expressions.Count); - } - - [Test] - public void MultiplePropertiesAndIndexers() - { - JPath path = new JPath("Blah[0]..Two.Three[1].Four"); - Assert.AreEqual(6, path.Parts.Count); - Assert.AreEqual(FilterType.Field, path.Parts[0].Type); - Assert.AreEqual("Blah", ((FieldFilter) path.Parts[0]).Name); - Assert.AreEqual(FilterType.ArrayIndex, path.Parts[1].Type); - Assert.AreEqual(0, ((ArrayIndexFilter) path.Parts[1]).Index); - Assert.AreEqual(FilterType.Scan, path.Parts[2].Type); - Assert.AreEqual("Two", ((ScanFilter)path.Parts[2]).Name); - Assert.AreEqual("Three", ((FieldFilter)path.Parts[3]).Name); - Assert.AreEqual(1, ((ArrayIndexFilter)path.Parts[4]).Index); - Assert.AreEqual("Four", ((FieldFilter)path.Parts[5]).Name); - } - - [Test] - public void BadCharactersInIndexer() - { - ExceptionAssert.Throws( - @"Unexpected character while parsing path indexer: [", - () => { new JPath("Blah[[0]].Two.Three[1].Four"); }); - } - - [Test] - public void UnclosedIndexer() - { - ExceptionAssert.Throws( - @"Path ended with open indexer.", - () => { new JPath("Blah[0"); }); - } - - //[Test] - //public void AdditionalDots() - //{ - // JPath path = new JPath(".Blah..[0]..Two.Three....[1].Four."); - // Assert.AreEqual(6, path.Parts.Count); - // Assert.AreEqual("Blah", path.Parts[0]); - // Assert.AreEqual(0, path.Parts[1]); - // Assert.AreEqual("Two", path.Parts[2]); - // Assert.AreEqual("Three", path.Parts[3]); - // Assert.AreEqual(1, path.Parts[4]); - // Assert.AreEqual("Four", path.Parts[5]); - //} - - [Test] - public void IndexerOnly() - { - JPath path = new JPath("[111119990]"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual(111119990, ((ArrayIndexFilter)path.Parts[0]).Index); - } - - [Test] - public void MultipleIndexes() - { - JPath path = new JPath("[111119990,3]"); - Assert.AreEqual(1, path.Parts.Count); - Assert.AreEqual(2, ((ArrayMultipleIndexFilter)path.Parts[0]).Indexes.Count); - Assert.AreEqual(111119990, ((ArrayMultipleIndexFilter)path.Parts[0]).Indexes[0]); - Assert.AreEqual(3, ((ArrayMultipleIndexFilter)path.Parts[0]).Indexes[1]); - } - - [Test] - public void EmptyIndexer() - { - ExceptionAssert.Throws( - "Array index expected.", - () => { new JPath("[]"); }); - } - - [Test] - public void IndexerCloseInProperty() - { - ExceptionAssert.Throws( - "Unexpected character while parsing path: ]", - () => { new JPath("]"); }); - } - - [Test] - public void AdjacentIndexers() - { - JPath path = new JPath("[1][0][0][" + int.MaxValue + "]"); - Assert.AreEqual(4, path.Parts.Count); - Assert.AreEqual(1, ((ArrayIndexFilter)path.Parts[0]).Index); - Assert.AreEqual(0, ((ArrayIndexFilter)path.Parts[1]).Index); - Assert.AreEqual(0, ((ArrayIndexFilter)path.Parts[2]).Index); - Assert.AreEqual(int.MaxValue, ((ArrayIndexFilter)path.Parts[3]).Index); - } - - [Test] - public void MissingDotAfterIndexer() - { - ExceptionAssert.Throws( - "Unexpected character following indexer: B", - () => { new JPath("[1]Blah"); }); - } - [Test] public void EvaluateSingleProperty() { @@ -392,6 +185,54 @@ namespace Newtonsoft.Json.Tests.Linq Assert.AreEqual(2, (int)t); } + [Test] + public void EvaluateArraySlice() + { + JArray a = new JArray(1, 2, 3, 4, 5, 6, 7, 8, 9); + IList t = null; + + t = a.SelectTokens("[-3:]").ToList(); + Assert.AreEqual(3, t.Count); + Assert.AreEqual(7, (int)t[0]); + Assert.AreEqual(8, (int)t[1]); + Assert.AreEqual(9, (int)t[2]); + + t = a.SelectTokens("[-1:-2:-1]").ToList(); + Assert.AreEqual(1, t.Count); + Assert.AreEqual(9, (int)t[0]); + + t = a.SelectTokens("[-2:-1]").ToList(); + Assert.AreEqual(1, t.Count); + Assert.AreEqual(8, (int)t[0]); + + t = a.SelectTokens("[1:1]").ToList(); + Assert.AreEqual(0, t.Count); + + t = a.SelectTokens("[1:2]").ToList(); + Assert.AreEqual(1, t.Count); + Assert.AreEqual(2, (int)t[0]); + + t = a.SelectTokens("[::-1]").ToList(); + Assert.AreEqual(9, t.Count); + Assert.AreEqual(9, (int)t[0]); + Assert.AreEqual(8, (int)t[1]); + Assert.AreEqual(7, (int)t[2]); + Assert.AreEqual(6, (int)t[3]); + Assert.AreEqual(5, (int)t[4]); + Assert.AreEqual(4, (int)t[5]); + Assert.AreEqual(3, (int)t[6]); + Assert.AreEqual(2, (int)t[7]); + Assert.AreEqual(1, (int)t[8]); + + t = a.SelectTokens("[::-2]").ToList(); + Assert.AreEqual(5, t.Count); + Assert.AreEqual(9, (int)t[0]); + Assert.AreEqual(7, (int)t[1]); + Assert.AreEqual(5, (int)t[2]); + Assert.AreEqual(3, (int)t[3]); + Assert.AreEqual(1, (int)t[4]); + } + [Test] public void EvaluateWildcardArray() { diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs new file mode 100644 index 00000000..79b19a55 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathParseTests.cs @@ -0,0 +1,383 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq.JsonPath; +#if !NETFX_CORE +using NUnit.Framework; +#else +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#endif +using Newtonsoft.Json.Linq; +#if NET20 +using Newtonsoft.Json.Utilities.LinqBridge; +#else +using System.Linq; + +#endif + +namespace Newtonsoft.Json.Tests.Linq.JsonPath +{ + [TestFixture] + public class JPathParseTests : TestFixtureBase + { + [Test] + public void SingleProperty() + { + JPath path = new JPath("Blah"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void SingleQuotedProperty() + { + JPath path = new JPath("['Blah']"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void SingleQuotedPropertyWithWhitespace() + { + JPath path = new JPath("[ 'Blah' ]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void SingleQuotedPropertyWithDots() + { + JPath path = new JPath("['Blah.Ha']"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("Blah.Ha", ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void SingleQuotedPropertyWithBrackets() + { + JPath path = new JPath("['[*]']"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("[*]", ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void SinglePropertyWithRoot() + { + JPath path = new JPath("$.Blah"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void SinglePropertyWithRootWithStartAndEndWhitespace() + { + JPath path = new JPath(" $.Blah "); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void RootWithBadWhitespace() + { + ExceptionAssert.Throws( + @"Unexpected character while parsing path: ", + () => { new JPath("$ .Blah"); }); + } + + [Test] + public void RootWithBadWhitespace2() + { + ExceptionAssert.Throws( + @"Unexpected character while parsing path: ", + () => { new JPath("$. Blah"); }); + } + + [Test] + public void WildcardPropertyWithRoot() + { + JPath path = new JPath("$.*"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(null, ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void WildcardArrayWithRoot() + { + JPath path = new JPath("$.[*]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(null, ((ArrayIndexFilter)path.Filters[0]).Index); + } + + [Test] + public void WildcardArray() + { + JPath path = new JPath("[*]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(null, ((ArrayIndexFilter)path.Filters[0]).Index); + } + + [Test] + public void WildcardArrayWithProperty() + { + JPath path = new JPath("[ * ].derp"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual(null, ((ArrayIndexFilter)path.Filters[0]).Index); + Assert.AreEqual("derp", ((FieldFilter)path.Filters[1]).Name); + } + + [Test] + public void QuotedWildcardPropertyWithRoot() + { + JPath path = new JPath("$.['*']"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("*", ((FieldFilter)path.Filters[0]).Name); + } + + [Test] + public void SingleScanWithRoot() + { + JPath path = new JPath("$..Blah"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual("Blah", ((ScanFilter)path.Filters[0]).Name); + } + + [Test] + public void WildcardScanWithRoot() + { + JPath path = new JPath("$..*"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(null, ((ScanFilter)path.Filters[0]).Name); + } + + [Test] + public void WildcardScanWithRootWithWhitespace() + { + JPath path = new JPath("$..* "); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(null, ((ScanFilter)path.Filters[0]).Name); + } + + [Test] + public void TwoProperties() + { + JPath path = new JPath("Blah.Two"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + Assert.AreEqual("Two", ((FieldFilter)path.Filters[1]).Name); + } + + [Test] + public void OnePropertyOneScan() + { + JPath path = new JPath("Blah..Two"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + Assert.AreEqual("Two", ((ScanFilter)path.Filters[1]).Name); + } + + [Test] + public void SinglePropertyAndIndexer() + { + JPath path = new JPath("Blah[0]"); + Assert.AreEqual(2, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name); + Assert.AreEqual(0, ((ArrayIndexFilter)path.Filters[1]).Index); + } + + [Test] + public void SinglePropertyAndFilter() + { + 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); + } + + [Test] + public void MultiplePropertiesAndIndexers() + { + JPath path = new JPath("Blah[0]..Two.Three[1].Four"); + Assert.AreEqual(6, path.Filters.Count); + Assert.AreEqual("Blah", ((FieldFilter) path.Filters[0]).Name); + Assert.AreEqual(0, ((ArrayIndexFilter) path.Filters[1]).Index); + Assert.AreEqual("Two", ((ScanFilter)path.Filters[2]).Name); + Assert.AreEqual("Three", ((FieldFilter)path.Filters[3]).Name); + Assert.AreEqual(1, ((ArrayIndexFilter)path.Filters[4]).Index); + Assert.AreEqual("Four", ((FieldFilter)path.Filters[5]).Name); + } + + [Test] + public void BadCharactersInIndexer() + { + ExceptionAssert.Throws( + @"Unexpected character while parsing path indexer: [", + () => { new JPath("Blah[[0]].Two.Three[1].Four"); }); + } + + [Test] + public void UnclosedIndexer() + { + ExceptionAssert.Throws( + @"Path ended with open indexer.", + () => { new JPath("Blah[0"); }); + } + + [Test] + public void IndexerOnly() + { + JPath path = new JPath("[111119990]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(111119990, ((ArrayIndexFilter)path.Filters[0]).Index); + } + + [Test] + public void IndexerOnlyWithWhitespace() + { + JPath path = new JPath("[ 10 ]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(10, ((ArrayIndexFilter)path.Filters[0]).Index); + } + + [Test] + public void MultipleIndexes() + { + JPath path = new JPath("[111119990,3]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(2, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes.Count); + Assert.AreEqual(111119990, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes[0]); + Assert.AreEqual(3, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes[1]); + } + + [Test] + public void MultipleIndexesWithWhitespace() + { + JPath path = new JPath("[ 111119990 , 3 ]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(2, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes.Count); + Assert.AreEqual(111119990, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes[0]); + Assert.AreEqual(3, ((ArrayMultipleIndexFilter)path.Filters[0]).Indexes[1]); + } + + [Test] + public void SlicingIndexAll() + { + JPath path = new JPath("[111119990:3:2]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(111119990, ((ArraySliceFilter)path.Filters[0]).Start); + Assert.AreEqual(3, ((ArraySliceFilter)path.Filters[0]).End); + Assert.AreEqual(2, ((ArraySliceFilter)path.Filters[0]).Step); + } + + [Test] + public void SlicingIndex() + { + JPath path = new JPath("[111119990:3]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(111119990, ((ArraySliceFilter)path.Filters[0]).Start); + Assert.AreEqual(3, ((ArraySliceFilter)path.Filters[0]).End); + Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).Step); + } + + [Test] + public void SlicingIndexNegative() + { + JPath path = new JPath("[-111119990:-3:-2]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(-111119990, ((ArraySliceFilter)path.Filters[0]).Start); + Assert.AreEqual(-3, ((ArraySliceFilter)path.Filters[0]).End); + Assert.AreEqual(-2, ((ArraySliceFilter)path.Filters[0]).Step); + } + + [Test] + public void SlicingIndexEmptyStop() + { + JPath path = new JPath("[ -3 : ]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(-3, ((ArraySliceFilter)path.Filters[0]).Start); + Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).End); + Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).Step); + } + + [Test] + public void SlicingIndexEmptyStart() + { + JPath path = new JPath("[ : 1 : ]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).Start); + Assert.AreEqual(1, ((ArraySliceFilter)path.Filters[0]).End); + Assert.AreEqual(null, ((ArraySliceFilter)path.Filters[0]).Step); + } + + [Test] + public void SlicingIndexWhitespace() + { + JPath path = new JPath("[ -111119990 : -3 : -2 ]"); + Assert.AreEqual(1, path.Filters.Count); + Assert.AreEqual(-111119990, ((ArraySliceFilter)path.Filters[0]).Start); + Assert.AreEqual(-3, ((ArraySliceFilter)path.Filters[0]).End); + Assert.AreEqual(-2, ((ArraySliceFilter)path.Filters[0]).Step); + } + + [Test] + public void EmptyIndexer() + { + ExceptionAssert.Throws( + "Array index expected.", + () => { new JPath("[]"); }); + } + + [Test] + public void IndexerCloseInProperty() + { + ExceptionAssert.Throws( + "Unexpected character while parsing path: ]", + () => { new JPath("]"); }); + } + + [Test] + public void AdjacentIndexers() + { + JPath path = new JPath("[1][0][0][" + int.MaxValue + "]"); + Assert.AreEqual(4, path.Filters.Count); + Assert.AreEqual(1, ((ArrayIndexFilter)path.Filters[0]).Index); + Assert.AreEqual(0, ((ArrayIndexFilter)path.Filters[1]).Index); + Assert.AreEqual(0, ((ArrayIndexFilter)path.Filters[2]).Index); + Assert.AreEqual(int.MaxValue, ((ArrayIndexFilter)path.Filters[3]).Index); + } + + [Test] + public void MissingDotAfterIndexer() + { + ExceptionAssert.Throws( + "Unexpected character following indexer: B", + () => { new JPath("[1]Blah"); }); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 0c478a8c..dab7a83d 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -207,7 +207,8 @@ - + + diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index 544b1b45..5b75ba0b 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json.Linq.JsonPath; #if !(NET35 || NET20 || PORTABLE40) using System.Dynamic; using System.Linq.Expressions; diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ArrayIndexFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ArrayIndexFilter.cs new file mode 100644 index 00000000..6206edce --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ArrayIndexFilter.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal class ArrayIndexFilter : PathFilter + { + public int? Index { get; set; } + + public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + { + foreach (JToken t in current) + { + if (Index != null) + { + JToken v = GetTokenIndex(t, errorWhenNoMatch, Index.Value); + + if (v != null) + yield return v; + } + else + { + if (t is JArray || t is JConstructor) + { + foreach (JToken v in t) + { + yield return v; + } + } + else + { + if (errorWhenNoMatch) + throw new JsonException("Index * not valid on {0}.".FormatWith(CultureInfo.InvariantCulture, t.GetType().Name)); + } + } + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ArrayMultipleIndexFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ArrayMultipleIndexFilter.cs new file mode 100644 index 00000000..fe01f14f --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ArrayMultipleIndexFilter.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal class ArrayMultipleIndexFilter : PathFilter + { + public List Indexes { get; set; } + + public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + { + foreach (JToken t in current) + { + foreach (int i in Indexes) + { + JToken v = GetTokenIndex(t, errorWhenNoMatch, i); + + if (v != null) + yield return v; + } + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs new file mode 100644 index 00000000..d0213d63 --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ArraySliceFilter.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal class ArraySliceFilter : PathFilter + { + public int? Start { get; set; } + public int? End { get; set; } + public int? Step { get; set; } + + public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + { + if (Step == 0) + throw new JsonException("Step cannot be zero."); + + 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); + int stopIndex = End ?? ((stepCount > 0) ? sourceCollection.Count : -1); + + // start from the end of the list if start is negitive + if (Start < 0) startIndex = sourceCollection.Count + startIndex; + + // end from the start of the list if stop is negitive + if (End < 0) stopIndex = sourceCollection.Count + stopIndex; + + // ensure indexes keep within collection bounds + startIndex = Math.Max(startIndex, (stepCount > 0) ? 0 : int.MinValue); + startIndex = Math.Min(startIndex, (stepCount > 0) ? sourceCollection.Count : sourceCollection.Count - 1); + stopIndex = Math.Max(stopIndex, -1); + stopIndex = Math.Min(stopIndex, sourceCollection.Count); + + for (int i = startIndex; (stepCount > 0) ? i < stopIndex : i > stopIndex; i += stepCount) + { + yield return sourceCollection[i]; + } + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs new file mode 100644 index 00000000..6e715499 --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/FieldFilter.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal class FieldFilter : PathFilter + { + public string Name { get; set; } + + public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + { + foreach (JToken t in current) + { + JObject o = t as JObject; + if (o != null) + { + if (Name != null) + { + JToken v = o[Name]; + + if (v == null && errorWhenNoMatch) + throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, Name)); + + yield return v; + } + else + { + // todo - error when no results + + foreach (KeyValuePair p in o) + { + yield return p.Value; + } + } + } + else + { + if (errorWhenNoMatch) + throw new JsonException("Property '{0}' not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, Name ?? "*", t.GetType().Name)); + } + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JPath.cs b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs similarity index 52% rename from Src/Newtonsoft.Json/Linq/JPath.cs rename to Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs index 16811302..eabcaa5b 100644 --- a/Src/Newtonsoft.Json/Linq/JPath.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs @@ -30,89 +30,12 @@ using System.Linq; using System.Xml.Linq; using Newtonsoft.Json.Utilities; -namespace Newtonsoft.Json.Linq +namespace Newtonsoft.Json.Linq.JsonPath { - internal enum FilterType - { - ArrayIndex, - ArrayMultipleIndex, - ArraySlice, - Field, - Scan, - Query - } - - internal abstract class PathFilter - { - public abstract FilterType Type { get; } - } - - internal class ArrayIndexFilter : PathFilter - { - public int? Index { get; set; } - - public override FilterType Type - { - get { return FilterType.ArrayIndex; } - } - } - - internal class ArrayMultipleIndexFilter : PathFilter - { - public List Indexes { get; set; } - - public override FilterType Type - { - get { return FilterType.ArrayMultipleIndex; } - } - } - - internal class ArraySliceFilter : PathFilter - { - public int? Start { get; set; } - public int? End { get; set; } - public int? Skip { get; set; } - - public override FilterType Type - { - get { return FilterType.ArraySlice; } - } - } - - internal class FieldFilter : PathFilter - { - public string Name { get; set; } - - public override FilterType Type - { - get { return FilterType.Field; } - } - } - - internal class ScanFilter : PathFilter - { - public string Name { get; set; } - - public override FilterType Type - { - get { return FilterType.Scan; } - } - } - - internal class QueryFilter : PathFilter - { - public List Expression { get; set; } - - public override FilterType Type - { - get { return FilterType.Query; } - } - } - internal class JPath { private readonly string _expression; - public List Parts { get; private set; } + public List Filters { get; private set; } private int _currentIndex; @@ -120,7 +43,7 @@ namespace Newtonsoft.Json.Linq { ValidationUtils.ArgumentNotNull(expression, "expression"); _expression = expression; - Parts = new List(); + Filters = new List(); ParseMain(); } @@ -131,7 +54,10 @@ namespace Newtonsoft.Json.Linq bool followingIndexer = false; bool scan = false; - EnsureLength("Empty path."); + if (_expression.Length == 0) + return; + + EatWhitespace(); if (_expression[_currentIndex] == '$') { @@ -140,7 +66,7 @@ namespace Newtonsoft.Json.Linq EnsureLength("Unexpected end while parsing path."); if (_expression[_currentIndex] != '.') - throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]); + throw new JsonException("Unexpected character while parsing path: " + _expression[_currentIndex]); _currentIndex++; @@ -150,12 +76,9 @@ namespace Newtonsoft.Json.Linq { scan = true; _currentIndex++; - currentPartStartIndex = 3; - } - else - { - currentPartStartIndex = 2; } + + currentPartStartIndex = _currentIndex; } while (_currentIndex < _expression.Length) @@ -170,17 +93,23 @@ namespace Newtonsoft.Json.Linq { string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex); PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member }; - Parts.Add(filter); + Filters.Add(filter); scan = false; } ParseIndexer(currentChar); - currentPartStartIndex = _currentIndex + 1; + _currentIndex++; + currentPartStartIndex = _currentIndex; followingIndexer = true; break; case ']': case ')': throw new JsonException("Unexpected character while parsing path: " + currentChar); + case ' ': + EatWhitespace(); + if (_currentIndex < _expression.Length) + throw new JsonException("Unexpected character while parsing path: " + currentChar); + break; case '.': if (_currentIndex > currentPartStartIndex) { @@ -188,7 +117,7 @@ namespace Newtonsoft.Json.Linq if (member == "*") member = null; PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member }; - Parts.Add(filter); + Filters.Add(filter); scan = false; } if (_currentIndex + 1 < _expression.Length && _expression[_currentIndex + 1] == '.') @@ -196,25 +125,26 @@ namespace Newtonsoft.Json.Linq scan = true; _currentIndex++; } - currentPartStartIndex = _currentIndex + 1; + _currentIndex++; + currentPartStartIndex = _currentIndex; followingIndexer = false; break; default: if (followingIndexer) throw new JsonException("Unexpected character following indexer: " + currentChar); + + _currentIndex++; break; } - - _currentIndex++; } if (_currentIndex > currentPartStartIndex) { - string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex); + string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex).TrimEnd(); if (member == "*") member = null; PathFilter filter = (scan) ? (PathFilter)new ScanFilter() { Name = member } : new FieldFilter() { Name = member }; - Parts.Add(filter); + Filters.Add(filter); } } @@ -226,6 +156,8 @@ namespace Newtonsoft.Json.Linq EnsureLength("Path ended with open indexer."); + EatWhitespace(); + if (_expression[_currentIndex] == '\'') { ParseQuotedField(indexerCloseChar); @@ -243,7 +175,9 @@ namespace Newtonsoft.Json.Linq private void ParseArrayIndexer(char indexerCloseChar) { int start = _currentIndex; + int? end = null; List indexes = null; + int colonCount = 0; int? startIndex = null; int? endIndex = null; int? step = null; @@ -252,32 +186,58 @@ namespace Newtonsoft.Json.Linq { char currentCharacter = _expression[_currentIndex]; + if (currentCharacter == ' ') + { + end = _currentIndex; + EatWhitespace(); + continue; + } + if (currentCharacter == indexerCloseChar) { - int length = _currentIndex - start; - - if (length == 0) - throw new JsonException("Array index expected."); - - string indexer = _expression.Substring(start, length); - int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture); + int length = (end ?? _currentIndex) - start; if (indexes != null) { + if (length == 0) + throw new JsonException("Array index expected."); + + string indexer = _expression.Substring(start, length); + int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture); + indexes.Add(index); - Parts.Add(new ArrayMultipleIndexFilter { Indexes = indexes }); + Filters.Add(new ArrayMultipleIndexFilter { Indexes = indexes }); + } + else if (colonCount > 0) + { + if (length > 0) + { + string indexer = _expression.Substring(start, length); + int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture); + + if (colonCount == 1) + endIndex = index; + else + step = index; + } + + Filters.Add(new ArraySliceFilter { Start = startIndex, End = endIndex, Step = step }); } else { - Parts.Add(new ArrayIndexFilter { Index = index }); + if (length == 0) + throw new JsonException("Array index expected."); + + string indexer = _expression.Substring(start, length); + int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture); + + Filters.Add(new ArrayIndexFilter { Index = index }); } return; - } - - if (currentCharacter == ',') + } else if (currentCharacter == ',') { - int length = _currentIndex - start; + int length = (end ?? _currentIndex) - start; if (length == 0) throw new JsonException("Array index expected."); @@ -288,30 +248,79 @@ namespace Newtonsoft.Json.Linq string indexer = _expression.Substring(start, length); indexes.Add(Convert.ToInt32(indexer, CultureInfo.InvariantCulture)); - start = _currentIndex + 1; + _currentIndex++; + + EatWhitespace(); + + start = _currentIndex; + end = null; } else if (currentCharacter == '*') { _currentIndex++; EnsureLength("Path ended with open indexer."); + EatWhitespace(); if (_expression[_currentIndex] != indexerCloseChar) throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter); - Parts.Add(new ArrayIndexFilter()); + Filters.Add(new ArrayIndexFilter()); return; } - else if (!char.IsDigit(currentCharacter)) + else if (currentCharacter == ':') + { + int length = (end ?? _currentIndex) - start; + + if (length > 0) + { + string indexer = _expression.Substring(start, length); + int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture); + + if (colonCount == 0) + startIndex = index; + else if (colonCount == 1) + endIndex = index; + else + step = index; + } + + colonCount++; + + _currentIndex++; + + EatWhitespace(); + + start = _currentIndex; + end = null; + } + else if (!char.IsDigit(currentCharacter) && currentCharacter != '-') { throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter); } + else + { + if (end != null) + throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter); + + _currentIndex++; + } - _currentIndex++; } throw new JsonException("Path ended with open indexer."); } + private void EatWhitespace() + { + while (_currentIndex < _expression.Length) + { + if (_expression[_currentIndex] != ' ') + break; + + _currentIndex++; + } + } + private void ParseQuery(char indexerCloseChar) { _currentIndex++; @@ -334,7 +343,7 @@ namespace Newtonsoft.Json.Linq if (_expression[_currentIndex] != indexerCloseChar) throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]); - Parts.Add(new QueryFilter + Filters.Add(new QueryFilter { Expression = expressions }); @@ -383,11 +392,13 @@ namespace Newtonsoft.Json.Linq // check that character after the quote is to close the index _currentIndex++; EnsureLength("Path ended with open indexer."); + EatWhitespace(); + if (_expression[_currentIndex] != indexerCloseChar) throw new JsonException("Unexpected character while parsing path indexer: " + currentCharacter); string indexer = _expression.Substring(start, length); - Parts.Add(new FieldFilter { Name = indexer }); + Filters.Add(new FieldFilter { Name = indexer }); return; } @@ -408,193 +419,12 @@ namespace Newtonsoft.Json.Linq { IEnumerable current = new [] { root }; - foreach (PathFilter filter in Parts) + foreach (PathFilter filter in Filters) { - switch (filter.Type) - { - case FilterType.ArrayIndex: - current = ExecuteArrayIndexFilter(current, errorWhenNoMatch, (ArrayIndexFilter) filter); - break; - case FilterType.ArrayMultipleIndex: - current = ExecuteArrayMultipleIndexFilter(current, errorWhenNoMatch, (ArrayMultipleIndexFilter) filter); - break; - case FilterType.Field: - current = ExecuteFieldFilter(current, errorWhenNoMatch, (FieldFilter) filter); - break; - case FilterType.Scan: - current = ExecuteScanFilter(current, errorWhenNoMatch, (ScanFilter) filter); - break; - default: - throw new ArgumentOutOfRangeException(); - } + current = filter.ExecuteFilter(current, errorWhenNoMatch); } return current; } - - private static IEnumerable GetDescendants(IEnumerable source, string name) - { - foreach (JToken root in source) - { - if (name == null) - yield return root; - - JToken current = root; - JToken container = root; - - while (true) - { - if (container != null) - { - current = container.First; - } - else - { - while (current != null && current != root && current == current.Parent.Last) - { - current = current.Parent; - } - - if (current == null || current == root) - break; - current = current.Next; - } - - JProperty e = current as JProperty; - if (e != null) - { - if (e.Name == name) - yield return e.Value; - } - else - { - if (name == null) - yield return current; - } - - container = current as JContainer; - } - } - } - - private IEnumerable ExecuteScanFilter(IEnumerable current, bool errorWhenNoMatch, ScanFilter scanFilter) - { - return GetDescendants(current, scanFilter.Name); - } - - private IEnumerable ExecuteArrayMultipleIndexFilter(IEnumerable current, bool errorWhenNoMatch, ArrayMultipleIndexFilter arrayMultipleIndexFilter) - { - foreach (JToken t in current) - { - foreach (int i in arrayMultipleIndexFilter.Indexes) - { - JToken v = GetTokenIndex(t, errorWhenNoMatch, i); - - if (v != null) - yield return v; - } - } - } - - private static IEnumerable ExecuteFieldFilter(IEnumerable current, bool errorWhenNoMatch, FieldFilter fieldFilter) - { - foreach (JToken t in current) - { - JObject o = t as JObject; - if (o != null) - { - if (fieldFilter.Name != null) - { - JToken v = o[fieldFilter.Name]; - - if (v == null && errorWhenNoMatch) - throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, fieldFilter.Name)); - - yield return v; - } - else - { - // todo - error when no results - - foreach (KeyValuePair p in o) - { - yield return p.Value; - } - } - } - else - { - if (errorWhenNoMatch) - throw new JsonException("Property '{0}' not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, fieldFilter.Name ?? "*", t.GetType().Name)); - } - } - } - - private static IEnumerable ExecuteArrayIndexFilter(IEnumerable current, bool errorWhenNoMatch, ArrayIndexFilter arrayIndexFilter) - { - foreach (JToken t in current) - { - if (arrayIndexFilter.Index != null) - { - JToken v = GetTokenIndex(t, errorWhenNoMatch, arrayIndexFilter.Index.Value); - - if (v != null) - yield return v; - } - else - { - if (t is JArray || t is JConstructor) - { - foreach (JToken v in t) - { - yield return v; - } - } - else - { - if (errorWhenNoMatch) - throw new JsonException("Index * not valid on {0}.".FormatWith(CultureInfo.InvariantCulture, t.GetType().Name)); - } - } - } - } - - private static JToken GetTokenIndex(JToken t, bool errorWhenNoMatch, int index) - { - JArray a = t as JArray; - JConstructor c = t as JConstructor; - - if (a != null) - { - if (a.Count <= index) - { - if (errorWhenNoMatch) - throw new JsonException("Index {0} outside the bounds of JArray.".FormatWith(CultureInfo.InvariantCulture, index)); - - return null; - } - - return a[index]; - } - else if (c != null) - { - if (c.Count <= index) - { - if (errorWhenNoMatch) - throw new JsonException("Index {0} outside the bounds of JConstructor.".FormatWith(CultureInfo.InvariantCulture, index)); - - return null; - } - - return c[index]; - } - else - { - if (errorWhenNoMatch) - throw new JsonException("Index {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, index, t.GetType().Name)); - - return null; - } - } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/PathFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/PathFilter.cs new file mode 100644 index 00000000..90cc5bb0 --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/PathFilter.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal abstract class PathFilter + { + public abstract IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch); + + protected static JToken GetTokenIndex(JToken t, bool errorWhenNoMatch, int index) + { + JArray a = t as JArray; + JConstructor c = t as JConstructor; + + if (a != null) + { + if (a.Count <= index) + { + if (errorWhenNoMatch) + throw new JsonException("Index {0} outside the bounds of JArray.".FormatWith(CultureInfo.InvariantCulture, index)); + + return null; + } + + return a[index]; + } + else if (c != null) + { + if (c.Count <= index) + { + if (errorWhenNoMatch) + throw new JsonException("Index {0} outside the bounds of JConstructor.".FormatWith(CultureInfo.InvariantCulture, index)); + + return null; + } + + return c[index]; + } + else + { + if (errorWhenNoMatch) + throw new JsonException("Index {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, index, t.GetType().Name)); + + return null; + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs new file mode 100644 index 00000000..428fae9b --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal class QueryFilter : PathFilter + { + public List Expression { get; set; } + + public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/ScanFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/ScanFilter.cs new file mode 100644 index 00000000..688e60fc --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/ScanFilter.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal class ScanFilter : PathFilter + { + public string Name { get; set; } + + public override IEnumerable ExecuteFilter(IEnumerable current, bool errorWhenNoMatch) + { + foreach (JToken root in current) + { + if (Name == null) + yield return root; + + JToken value = root; + JToken container = root; + + while (true) + { + if (container != null) + { + value = container.First; + } + else + { + while (value != null && value != root && value == value.Parent.Last) + { + value = value.Parent; + } + + if (value == null || value == root) + break; + + value = value.Next; + } + + JProperty e = value as JProperty; + if (e != null) + { + if (e.Name == Name) + yield return e.Value; + } + else + { + if (Name == null) + yield return value; + } + + container = value as JContainer; + } + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index c6b538ab..f08cb031 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -111,11 +111,18 @@ - + + + + + + + +