2021-03-29 14:54:12 +08:00
|
|
|
#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 System.Globalization;
|
|
|
|
using System.Text;
|
|
|
|
using LC.Newtonsoft.Json.Utilities;
|
|
|
|
|
|
|
|
namespace LC.Newtonsoft.Json.Linq.JsonPath
|
|
|
|
{
|
|
|
|
internal class JPath
|
|
|
|
{
|
|
|
|
private static readonly char[] FloatCharacters = new[] {'.', 'E', 'e'};
|
|
|
|
|
|
|
|
private readonly string _expression;
|
|
|
|
public List<PathFilter> Filters { get; }
|
|
|
|
|
|
|
|
private int _currentIndex;
|
|
|
|
|
|
|
|
public JPath(string expression)
|
|
|
|
{
|
|
|
|
ValidationUtils.ArgumentNotNull(expression, nameof(expression));
|
|
|
|
_expression = expression;
|
|
|
|
Filters = new List<PathFilter>();
|
|
|
|
|
|
|
|
ParseMain();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ParseMain()
|
|
|
|
{
|
|
|
|
int currentPartStartIndex = _currentIndex;
|
|
|
|
|
|
|
|
EatWhitespace();
|
|
|
|
|
|
|
|
if (_expression.Length == _currentIndex)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_expression[_currentIndex] == '$')
|
|
|
|
{
|
|
|
|
if (_expression.Length == 1)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// only increment position for "$." or "$["
|
|
|
|
// otherwise assume property that starts with $
|
|
|
|
char c = _expression[_currentIndex + 1];
|
|
|
|
if (c == '.' || c == '[')
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
currentPartStartIndex = _currentIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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<PathFilter> filters, int currentPartStartIndex, bool query)
|
|
|
|
{
|
|
|
|
bool scan = false;
|
|
|
|
bool followingIndexer = false;
|
|
|
|
bool followingDot = false;
|
|
|
|
|
|
|
|
bool ended = false;
|
|
|
|
while (_currentIndex < _expression.Length && !ended)
|
|
|
|
{
|
|
|
|
char currentChar = _expression[_currentIndex];
|
|
|
|
|
|
|
|
switch (currentChar)
|
|
|
|
{
|
|
|
|
case '[':
|
|
|
|
case '(':
|
|
|
|
if (_currentIndex > currentPartStartIndex)
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex);
|
2021-03-29 14:54:12 +08:00
|
|
|
if (member == "*")
|
|
|
|
{
|
|
|
|
member = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
filters.Add(CreatePathFilter(member, scan));
|
|
|
|
scan = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
filters.Add(ParseIndexer(currentChar, scan));
|
|
|
|
_currentIndex++;
|
|
|
|
currentPartStartIndex = _currentIndex;
|
|
|
|
followingIndexer = true;
|
|
|
|
followingDot = false;
|
|
|
|
break;
|
|
|
|
case ']':
|
|
|
|
case ')':
|
|
|
|
ended = true;
|
|
|
|
break;
|
|
|
|
case ' ':
|
|
|
|
if (_currentIndex < _expression.Length)
|
|
|
|
{
|
|
|
|
ended = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '.':
|
|
|
|
if (_currentIndex > currentPartStartIndex)
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex);
|
2021-03-29 14:54:12 +08:00
|
|
|
if (member == "*")
|
|
|
|
{
|
|
|
|
member = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
filters.Add(CreatePathFilter(member, scan));
|
|
|
|
scan = false;
|
|
|
|
}
|
|
|
|
if (_currentIndex + 1 < _expression.Length && _expression[_currentIndex + 1] == '.')
|
|
|
|
{
|
|
|
|
scan = true;
|
|
|
|
_currentIndex++;
|
|
|
|
}
|
|
|
|
_currentIndex++;
|
|
|
|
currentPartStartIndex = _currentIndex;
|
|
|
|
followingIndexer = false;
|
|
|
|
followingDot = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (query && (currentChar == '=' || currentChar == '<' || currentChar == '!' || currentChar == '>' || currentChar == '|' || currentChar == '&'))
|
|
|
|
{
|
|
|
|
ended = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (followingIndexer)
|
|
|
|
{
|
|
|
|
throw new JsonException("Unexpected character following indexer: " + currentChar);
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentIndex++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool atPathEnd = (_currentIndex == _expression.Length);
|
|
|
|
|
|
|
|
if (_currentIndex > currentPartStartIndex)
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
string member = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex).TrimEnd();
|
2021-03-29 14:54:12 +08:00
|
|
|
if (member == "*")
|
|
|
|
{
|
|
|
|
member = null;
|
|
|
|
}
|
|
|
|
filters.Add(CreatePathFilter(member, scan));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// no field name following dot in path and at end of base path/query
|
|
|
|
if (followingDot && (atPathEnd || query))
|
|
|
|
{
|
|
|
|
throw new JsonException("Unexpected end while parsing path.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return atPathEnd;
|
|
|
|
}
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
private static PathFilter CreatePathFilter(string member, bool scan)
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
PathFilter filter = (scan) ? (PathFilter)new ScanFilter {Name = member} : new FieldFilter {Name = member};
|
2021-03-29 14:54:12 +08:00
|
|
|
return filter;
|
|
|
|
}
|
|
|
|
|
|
|
|
private PathFilter ParseIndexer(char indexerOpenChar, bool scan)
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
|
|
|
|
char indexerCloseChar = (indexerOpenChar == '[') ? ']' : ')';
|
|
|
|
|
|
|
|
EnsureLength("Path ended with open indexer.");
|
|
|
|
|
|
|
|
EatWhitespace();
|
|
|
|
|
|
|
|
if (_expression[_currentIndex] == '\'')
|
|
|
|
{
|
|
|
|
return ParseQuotedField(indexerCloseChar, scan);
|
|
|
|
}
|
|
|
|
else if (_expression[_currentIndex] == '?')
|
|
|
|
{
|
|
|
|
return ParseQuery(indexerCloseChar, scan);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return ParseArrayIndexer(indexerCloseChar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private PathFilter ParseArrayIndexer(char indexerCloseChar)
|
|
|
|
{
|
|
|
|
int start = _currentIndex;
|
|
|
|
int? end = null;
|
2021-03-30 10:54:25 +08:00
|
|
|
List<int> indexes = null;
|
2021-03-29 14:54:12 +08:00
|
|
|
int colonCount = 0;
|
|
|
|
int? startIndex = null;
|
|
|
|
int? endIndex = null;
|
|
|
|
int? step = null;
|
|
|
|
|
|
|
|
while (_currentIndex < _expression.Length)
|
|
|
|
{
|
|
|
|
char currentCharacter = _expression[_currentIndex];
|
|
|
|
|
|
|
|
if (currentCharacter == ' ')
|
|
|
|
{
|
|
|
|
end = _currentIndex;
|
|
|
|
EatWhitespace();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentCharacter == indexerCloseChar)
|
|
|
|
{
|
|
|
|
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);
|
2021-03-30 10:54:25 +08:00
|
|
|
return new ArrayMultipleIndexFilter { Indexes = indexes };
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new ArraySliceFilter { Start = startIndex, End = endIndex, Step = step };
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (length == 0)
|
|
|
|
{
|
|
|
|
throw new JsonException("Array index expected.");
|
|
|
|
}
|
|
|
|
|
|
|
|
string indexer = _expression.Substring(start, length);
|
|
|
|
int index = Convert.ToInt32(indexer, CultureInfo.InvariantCulture);
|
|
|
|
|
|
|
|
return new ArrayIndexFilter { Index = index };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (currentCharacter == ',')
|
|
|
|
{
|
|
|
|
int length = (end ?? _currentIndex) - start;
|
|
|
|
|
|
|
|
if (length == 0)
|
|
|
|
{
|
|
|
|
throw new JsonException("Array index expected.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (indexes == null)
|
|
|
|
{
|
|
|
|
indexes = new List<int>();
|
|
|
|
}
|
|
|
|
|
|
|
|
string indexer = _expression.Substring(start, length);
|
|
|
|
indexes.Add(Convert.ToInt32(indexer, CultureInfo.InvariantCulture));
|
|
|
|
|
|
|
|
_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new ArrayIndexFilter();
|
|
|
|
}
|
|
|
|
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++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new JsonException("Path ended with open indexer.");
|
|
|
|
}
|
|
|
|
|
|
|
|
private void EatWhitespace()
|
|
|
|
{
|
|
|
|
while (_currentIndex < _expression.Length)
|
|
|
|
{
|
|
|
|
if (_expression[_currentIndex] != ' ')
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private PathFilter ParseQuery(char indexerCloseChar, bool scan)
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
EnsureLength("Path ended with open indexer.");
|
|
|
|
|
|
|
|
if (_expression[_currentIndex] != '(')
|
|
|
|
{
|
|
|
|
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]);
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentIndex++;
|
|
|
|
|
|
|
|
QueryExpression expression = ParseExpression();
|
|
|
|
|
|
|
|
_currentIndex++;
|
|
|
|
EnsureLength("Path ended with open indexer.");
|
|
|
|
EatWhitespace();
|
|
|
|
|
|
|
|
if (_expression[_currentIndex] != indexerCloseChar)
|
|
|
|
{
|
|
|
|
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!scan)
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
return new QueryFilter
|
|
|
|
{
|
|
|
|
Expression = expression
|
|
|
|
};
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
return new QueryScanFilter
|
|
|
|
{
|
|
|
|
Expression = expression
|
|
|
|
};
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
private bool TryParseExpression(out List<PathFilter> expressionPath)
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
|
|
|
if (_expression[_currentIndex] == '$')
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
expressionPath = new List<PathFilter>();
|
|
|
|
expressionPath.Add(RootFilter.Instance);
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
else if (_expression[_currentIndex] == '@')
|
|
|
|
{
|
|
|
|
expressionPath = new List<PathFilter>();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
expressionPath = null;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentIndex++;
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
if (ParsePath(expressionPath, _currentIndex, true))
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
|
|
|
throw new JsonException("Path ended with open query.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private JsonException CreateUnexpectedCharacterException()
|
|
|
|
{
|
|
|
|
return new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex]);
|
|
|
|
}
|
|
|
|
|
|
|
|
private object ParseSide()
|
|
|
|
{
|
|
|
|
EatWhitespace();
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
if (TryParseExpression(out var expressionPath))
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
|
|
|
EatWhitespace();
|
|
|
|
EnsureLength("Path ended with open query.");
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
return expressionPath;
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (TryParseValue(out var value))
|
|
|
|
{
|
|
|
|
EatWhitespace();
|
|
|
|
EnsureLength("Path ended with open query.");
|
|
|
|
|
|
|
|
return new JValue(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw CreateUnexpectedCharacterException();
|
|
|
|
}
|
|
|
|
|
|
|
|
private QueryExpression ParseExpression()
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
QueryExpression rootExpression = null;
|
|
|
|
CompositeExpression parentExpression = null;
|
2021-03-29 14:54:12 +08:00
|
|
|
|
|
|
|
while (_currentIndex < _expression.Length)
|
|
|
|
{
|
|
|
|
object left = ParseSide();
|
2021-03-30 10:54:25 +08:00
|
|
|
object right = null;
|
2021-03-29 14:54:12 +08:00
|
|
|
|
|
|
|
QueryOperator op;
|
|
|
|
if (_expression[_currentIndex] == ')'
|
|
|
|
|| _expression[_currentIndex] == '|'
|
|
|
|
|| _expression[_currentIndex] == '&')
|
|
|
|
{
|
|
|
|
op = QueryOperator.Exists;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
op = ParseOperator();
|
|
|
|
|
|
|
|
right = ParseSide();
|
|
|
|
}
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
BooleanQueryExpression booleanExpression = new BooleanQueryExpression
|
|
|
|
{
|
|
|
|
Left = left,
|
|
|
|
Operator = op,
|
|
|
|
Right = right
|
|
|
|
};
|
2021-03-29 14:54:12 +08:00
|
|
|
|
|
|
|
if (_expression[_currentIndex] == ')')
|
|
|
|
{
|
|
|
|
if (parentExpression != null)
|
|
|
|
{
|
|
|
|
parentExpression.Expressions.Add(booleanExpression);
|
2021-03-30 10:54:25 +08:00
|
|
|
return rootExpression;
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return booleanExpression;
|
|
|
|
}
|
|
|
|
if (_expression[_currentIndex] == '&')
|
|
|
|
{
|
|
|
|
if (!Match("&&"))
|
|
|
|
{
|
|
|
|
throw CreateUnexpectedCharacterException();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parentExpression == null || parentExpression.Operator != QueryOperator.And)
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
CompositeExpression andExpression = new CompositeExpression { Operator = QueryOperator.And };
|
2021-03-29 14:54:12 +08:00
|
|
|
|
|
|
|
parentExpression?.Expressions.Add(andExpression);
|
|
|
|
|
|
|
|
parentExpression = andExpression;
|
|
|
|
|
|
|
|
if (rootExpression == null)
|
|
|
|
{
|
|
|
|
rootExpression = parentExpression;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parentExpression.Expressions.Add(booleanExpression);
|
|
|
|
}
|
|
|
|
if (_expression[_currentIndex] == '|')
|
|
|
|
{
|
|
|
|
if (!Match("||"))
|
|
|
|
{
|
|
|
|
throw CreateUnexpectedCharacterException();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parentExpression == null || parentExpression.Operator != QueryOperator.Or)
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
CompositeExpression orExpression = new CompositeExpression { Operator = QueryOperator.Or };
|
2021-03-29 14:54:12 +08:00
|
|
|
|
|
|
|
parentExpression?.Expressions.Add(orExpression);
|
|
|
|
|
|
|
|
parentExpression = orExpression;
|
|
|
|
|
|
|
|
if (rootExpression == null)
|
|
|
|
{
|
|
|
|
rootExpression = parentExpression;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parentExpression.Expressions.Add(booleanExpression);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new JsonException("Path ended with open query.");
|
|
|
|
}
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
private bool TryParseValue(out object value)
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
|
|
|
char currentChar = _expression[_currentIndex];
|
|
|
|
if (currentChar == '\'')
|
|
|
|
{
|
|
|
|
value = ReadQuotedString();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
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(FloatCharacters) != -1)
|
|
|
|
{
|
|
|
|
bool result = double.TryParse(numberText, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var d);
|
|
|
|
value = d;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
bool result = long.TryParse(numberText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var l);
|
|
|
|
value = l;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sb.Append(currentChar);
|
|
|
|
_currentIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (currentChar == 't')
|
|
|
|
{
|
|
|
|
if (Match("true"))
|
|
|
|
{
|
|
|
|
value = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (currentChar == 'f')
|
|
|
|
{
|
|
|
|
if (Match("false"))
|
|
|
|
{
|
|
|
|
value = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (currentChar == 'n')
|
|
|
|
{
|
|
|
|
if (Match("null"))
|
|
|
|
{
|
|
|
|
value = null;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (currentChar == '/')
|
|
|
|
{
|
|
|
|
value = ReadRegexString();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
value = null;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private string ReadQuotedString()
|
|
|
|
{
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
|
|
|
_currentIndex++;
|
|
|
|
while (_currentIndex < _expression.Length)
|
|
|
|
{
|
|
|
|
char currentChar = _expression[_currentIndex];
|
|
|
|
if (currentChar == '\\' && _currentIndex + 1 < _expression.Length)
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
currentChar = _expression[_currentIndex];
|
|
|
|
|
|
|
|
char resolvedChar;
|
|
|
|
switch (currentChar)
|
|
|
|
{
|
|
|
|
case 'b':
|
|
|
|
resolvedChar = '\b';
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
resolvedChar = '\t';
|
|
|
|
break;
|
|
|
|
case 'n':
|
|
|
|
resolvedChar = '\n';
|
|
|
|
break;
|
|
|
|
case 'f':
|
|
|
|
resolvedChar = '\f';
|
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
resolvedChar = '\r';
|
|
|
|
break;
|
|
|
|
case '\\':
|
|
|
|
case '"':
|
|
|
|
case '\'':
|
|
|
|
case '/':
|
|
|
|
resolvedChar = currentChar;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new JsonException(@"Unknown escape character: \" + currentChar);
|
|
|
|
}
|
|
|
|
|
|
|
|
sb.Append(resolvedChar);
|
|
|
|
|
|
|
|
_currentIndex++;
|
|
|
|
}
|
|
|
|
else if (currentChar == '\'')
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
return sb.ToString();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
sb.Append(currentChar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new JsonException("Path ended with an open string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
private string ReadRegexString()
|
|
|
|
{
|
|
|
|
int startIndex = _currentIndex;
|
|
|
|
|
|
|
|
_currentIndex++;
|
|
|
|
while (_currentIndex < _expression.Length)
|
|
|
|
{
|
|
|
|
char currentChar = _expression[_currentIndex];
|
|
|
|
|
|
|
|
// handle escaped / character
|
|
|
|
if (currentChar == '\\' && _currentIndex + 1 < _expression.Length)
|
|
|
|
{
|
|
|
|
_currentIndex += 2;
|
|
|
|
}
|
|
|
|
else if (currentChar == '/')
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
|
|
|
|
while (_currentIndex < _expression.Length)
|
|
|
|
{
|
|
|
|
currentChar = _expression[_currentIndex];
|
|
|
|
|
|
|
|
if (char.IsLetter(currentChar))
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return _expression.Substring(startIndex, _currentIndex - startIndex);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new JsonException("Path ended with an open regex.");
|
|
|
|
}
|
|
|
|
|
|
|
|
private bool Match(string s)
|
|
|
|
{
|
|
|
|
int currentPosition = _currentIndex;
|
2021-03-30 10:54:25 +08:00
|
|
|
foreach (char c in s)
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
if (currentPosition < _expression.Length && _expression[currentPosition] == c)
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
|
|
|
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.StrictEquals;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Match("=="))
|
|
|
|
{
|
|
|
|
return QueryOperator.Equals;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Match("=~"))
|
|
|
|
{
|
|
|
|
return QueryOperator.RegexEquals;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Match("!=="))
|
|
|
|
{
|
|
|
|
return QueryOperator.StrictNotEquals;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 PathFilter ParseQuotedField(char indexerCloseChar, bool scan)
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
List<string> fields = null;
|
2021-03-29 14:54:12 +08:00
|
|
|
|
|
|
|
while (_currentIndex < _expression.Length)
|
|
|
|
{
|
|
|
|
string field = ReadQuotedString();
|
|
|
|
|
|
|
|
EatWhitespace();
|
|
|
|
EnsureLength("Path ended with open indexer.");
|
|
|
|
|
|
|
|
if (_expression[_currentIndex] == indexerCloseChar)
|
|
|
|
{
|
|
|
|
if (fields != null)
|
|
|
|
{
|
|
|
|
fields.Add(field);
|
|
|
|
return (scan)
|
2021-03-30 10:54:25 +08:00
|
|
|
? (PathFilter)new ScanMultipleFilter { Names = fields }
|
|
|
|
: (PathFilter)new FieldMultipleFilter { Names = fields };
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return CreatePathFilter(field, scan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (_expression[_currentIndex] == ',')
|
|
|
|
{
|
|
|
|
_currentIndex++;
|
|
|
|
EatWhitespace();
|
|
|
|
|
|
|
|
if (fields == null)
|
|
|
|
{
|
|
|
|
fields = new List<string>();
|
|
|
|
}
|
|
|
|
|
|
|
|
fields.Add(field);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new JsonException("Path ended with open indexer.");
|
|
|
|
}
|
|
|
|
|
|
|
|
private void EnsureLength(string message)
|
|
|
|
{
|
|
|
|
if (_currentIndex >= _expression.Length)
|
|
|
|
{
|
|
|
|
throw new JsonException(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
internal IEnumerable<JToken> Evaluate(JToken root, JToken t, bool errorWhenNoMatch)
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
return Evaluate(Filters, root, t, errorWhenNoMatch);
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
|
2021-03-30 10:54:25 +08:00
|
|
|
internal static IEnumerable<JToken> Evaluate(List<PathFilter> filters, JToken root, JToken t, bool errorWhenNoMatch)
|
2021-03-29 14:54:12 +08:00
|
|
|
{
|
|
|
|
IEnumerable<JToken> current = new[] { t };
|
|
|
|
foreach (PathFilter filter in filters)
|
|
|
|
{
|
2021-03-30 10:54:25 +08:00
|
|
|
current = filter.ExecuteFilter(root, current, errorWhenNoMatch);
|
2021-03-29 14:54:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return current;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|