JPath
class JPath
using Newtonsoft.Json.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Newtonsoft.Json.Linq.JsonPath
{
internal class JPath
{
private readonly string _expression;
private int _currentIndex;
public List<PathFilter> Filters { get; }
public JPath(string expression)
{
ValidationUtils.ArgumentNotNull(expression, "expression");
_expression = expression;
Filters = new List<PathFilter>();
ParseMain();
}
private void ParseMain()
{
int currentIndex = _currentIndex;
EatWhitespace();
if (_expression.Length != _currentIndex) {
if (_expression[_currentIndex] == '$') {
if (_expression.Length == 1)
return;
char c = _expression[_currentIndex + 1];
if (c == '.' || c == '[') {
_currentIndex++;
currentIndex = _currentIndex;
}
}
if (!ParsePath(Filters, currentIndex, false)) {
int currentIndex2 = _currentIndex;
EatWhitespace();
if (_currentIndex < _expression.Length)
throw new JsonException("Unexpected character while parsing path: " + _expression[currentIndex2].ToString());
}
}
}
private bool ParsePath(List<PathFilter> filters, int currentPartStartIndex, bool query)
{
bool scan = false;
bool flag = false;
bool flag2 = false;
bool flag3 = false;
while (_currentIndex < _expression.Length && !flag3) {
char c = _expression[_currentIndex];
switch (c) {
case '(':
case '[':
if (_currentIndex > currentPartStartIndex) {
string text = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex);
if (text == "*")
text = null;
filters.Add(CreatePathFilter(text, scan));
scan = false;
}
filters.Add(ParseIndexer(c, scan));
_currentIndex++;
currentPartStartIndex = _currentIndex;
flag = true;
flag2 = false;
break;
case ')':
case ']':
flag3 = true;
break;
case ' ':
if (_currentIndex < _expression.Length)
flag3 = true;
break;
case '.':
if (_currentIndex > currentPartStartIndex) {
string text2 = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex);
if (text2 == "*")
text2 = null;
filters.Add(CreatePathFilter(text2, scan));
scan = false;
}
if (_currentIndex + 1 < _expression.Length && _expression[_currentIndex + 1] == '.') {
scan = true;
_currentIndex++;
}
_currentIndex++;
currentPartStartIndex = _currentIndex;
flag = false;
flag2 = true;
break;
default:
if (query && (c == '=' || c == '<' || c == '!' || c == '>' || c == '|' || c == '&'))
flag3 = true;
else {
if (flag)
throw new JsonException("Unexpected character following indexer: " + c.ToString());
_currentIndex++;
}
break;
}
}
bool flag4 = _currentIndex == _expression.Length;
if (_currentIndex > currentPartStartIndex) {
string text3 = _expression.Substring(currentPartStartIndex, _currentIndex - currentPartStartIndex).TrimEnd(new char[0]);
if (text3 == "*")
text3 = null;
filters.Add(CreatePathFilter(text3, scan));
} else if (flag2 && (flag4 | query)) {
throw new JsonException("Unexpected end while parsing path.");
}
return flag4;
}
private static PathFilter CreatePathFilter(string member, bool scan)
{
if (!scan)
return new FieldFilter {
Name = member
};
return new ScanFilter {
Name = member
};
}
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);
if (_expression[_currentIndex] == '?')
return ParseQuery(indexerCloseChar);
return ParseArrayIndexer(indexerCloseChar);
}
private PathFilter ParseArrayIndexer(char indexerCloseChar)
{
int currentIndex = _currentIndex;
int? nullable = null;
List<int> list = null;
int num = 0;
int? start = null;
int? end = null;
int? step = null;
while (_currentIndex < _expression.Length) {
char c = _expression[_currentIndex];
if (c == ' ') {
nullable = _currentIndex;
EatWhitespace();
} else {
if (c == indexerCloseChar) {
int num2 = (nullable ?? _currentIndex) - currentIndex;
if (list != null) {
if (num2 == 0)
throw new JsonException("Array index expected.");
int item = Convert.ToInt32(_expression.Substring(currentIndex, num2), CultureInfo.InvariantCulture);
list.Add(item);
return new ArrayMultipleIndexFilter {
Indexes = list
};
}
if (num > 0) {
if (num2 > 0) {
int value = Convert.ToInt32(_expression.Substring(currentIndex, num2), CultureInfo.InvariantCulture);
if (num != 1)
step = value;
else
end = value;
}
return new ArraySliceFilter {
Start = start,
End = end,
Step = step
};
}
if (num2 == 0)
throw new JsonException("Array index expected.");
int value2 = Convert.ToInt32(_expression.Substring(currentIndex, num2), CultureInfo.InvariantCulture);
return new ArrayIndexFilter {
Index = new int?(value2)
};
}
switch (c) {
case ',': {
int num4 = (nullable ?? _currentIndex) - currentIndex;
if (num4 == 0)
throw new JsonException("Array index expected.");
if (list == null)
list = new List<int>();
string value4 = _expression.Substring(currentIndex, num4);
list.Add(Convert.ToInt32(value4, CultureInfo.InvariantCulture));
_currentIndex++;
EatWhitespace();
currentIndex = _currentIndex;
nullable = null;
break;
}
case '*':
_currentIndex++;
EnsureLength("Path ended with open indexer.");
EatWhitespace();
if (_expression[_currentIndex] != indexerCloseChar)
throw new JsonException("Unexpected character while parsing path indexer: " + c.ToString());
return new ArrayIndexFilter();
case ':': {
int num3 = (nullable ?? _currentIndex) - currentIndex;
if (num3 > 0) {
int value3 = Convert.ToInt32(_expression.Substring(currentIndex, num3), CultureInfo.InvariantCulture);
switch (num) {
case 0:
start = value3;
break;
case 1:
end = value3;
break;
default:
step = value3;
break;
}
}
num++;
_currentIndex++;
EatWhitespace();
currentIndex = _currentIndex;
nullable = null;
break;
}
default:
if (!char.IsDigit(c) && c != '-')
throw new JsonException("Unexpected character while parsing path indexer: " + c.ToString());
if (nullable.HasValue)
throw new JsonException("Unexpected character while parsing path indexer: " + c.ToString());
_currentIndex++;
break;
}
}
}
throw new JsonException("Path ended with open indexer.");
}
private void EatWhitespace()
{
while (_currentIndex < _expression.Length && _expression[_currentIndex] == ' ') {
_currentIndex++;
}
}
private PathFilter ParseQuery(char indexerCloseChar)
{
_currentIndex++;
EnsureLength("Path ended with open indexer.");
char c;
if (_expression[_currentIndex] != '(') {
c = _expression[_currentIndex];
throw new JsonException("Unexpected character while parsing path indexer: " + c.ToString());
}
_currentIndex++;
QueryExpression expression = ParseExpression();
_currentIndex++;
EnsureLength("Path ended with open indexer.");
EatWhitespace();
if (_expression[_currentIndex] != indexerCloseChar) {
c = _expression[_currentIndex];
throw new JsonException("Unexpected character while parsing path indexer: " + c.ToString());
}
return new QueryFilter {
Expression = expression
};
}
private bool TryParseExpression(out List<PathFilter> expressionPath)
{
if (_expression[_currentIndex] == '$') {
expressionPath = new List<PathFilter>();
expressionPath.Add(RootFilter.Instance);
} else {
if (_expression[_currentIndex] != '@') {
expressionPath = null;
return false;
}
expressionPath = new List<PathFilter>();
}
_currentIndex++;
if (ParsePath(expressionPath, _currentIndex, true))
throw new JsonException("Path ended with open query.");
return true;
}
private JsonException CreateUnexpectedCharacterException()
{
return new JsonException("Unexpected character while parsing path query: " + _expression[_currentIndex].ToString());
}
private object ParseSide()
{
EatWhitespace();
if (TryParseExpression(out List<PathFilter> expressionPath)) {
EatWhitespace();
EnsureLength("Path ended with open query.");
return expressionPath;
}
if (TryParseValue(out object value)) {
EatWhitespace();
EnsureLength("Path ended with open query.");
return new JValue(value);
}
throw CreateUnexpectedCharacterException();
}
private QueryExpression ParseExpression()
{
QueryExpression queryExpression = null;
CompositeExpression compositeExpression = null;
while (_currentIndex < _expression.Length) {
object left = ParseSide();
object right = null;
QueryOperator operator;
if (_expression[_currentIndex] == ')' || _expression[_currentIndex] == '|' || _expression[_currentIndex] == '&')
operator = QueryOperator.Exists;
else {
operator = ParseOperator();
right = ParseSide();
}
BooleanQueryExpression booleanQueryExpression = new BooleanQueryExpression {
Left = left,
Operator = operator,
Right = right
};
if (_expression[_currentIndex] == ')') {
if (compositeExpression != null) {
compositeExpression.Expressions.Add(booleanQueryExpression);
return queryExpression;
}
return booleanQueryExpression;
}
if (_expression[_currentIndex] == '&') {
if (!Match("&&"))
throw CreateUnexpectedCharacterException();
if (compositeExpression == null || compositeExpression.Operator != QueryOperator.And) {
CompositeExpression compositeExpression2 = new CompositeExpression {
Operator = QueryOperator.And
};
compositeExpression?.Expressions.Add(compositeExpression2);
compositeExpression = compositeExpression2;
if (queryExpression == null)
queryExpression = compositeExpression;
}
compositeExpression.Expressions.Add(booleanQueryExpression);
}
if (_expression[_currentIndex] == '|') {
if (!Match("||"))
throw CreateUnexpectedCharacterException();
if (compositeExpression == null || compositeExpression.Operator != QueryOperator.Or) {
CompositeExpression compositeExpression3 = new CompositeExpression {
Operator = QueryOperator.Or
};
compositeExpression?.Expressions.Add(compositeExpression3);
compositeExpression = compositeExpression3;
if (queryExpression == null)
queryExpression = compositeExpression;
}
compositeExpression.Expressions.Add(booleanQueryExpression);
}
}
throw new JsonException("Path ended with open query.");
}
private bool TryParseValue(out object value)
{
char c = _expression[_currentIndex];
if (c == '\'') {
value = ReadQuotedString();
return true;
}
if (!char.IsDigit(c)) {
switch (c) {
case '-':
break;
case 't':
if (Match("true")) {
value = true;
return true;
}
goto IL_013f;
case 'f':
if (Match("false")) {
value = false;
return true;
}
goto IL_013f;
case 'n':
if (Match("null")) {
value = null;
return true;
}
goto IL_013f;
default:
goto IL_013f;
}
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(c);
_currentIndex++;
while (_currentIndex < _expression.Length) {
c = _expression[_currentIndex];
if (c == ' ' || c == ')') {
string text = stringBuilder.ToString();
if (text.IndexOfAny(new char[3] {
'.',
'E',
'e'
}) != -1) {
double result;
bool result2 = double.TryParse(text, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out result);
value = result;
return result2;
}
long result3;
bool result4 = long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out result3);
value = result3;
return result4;
}
stringBuilder.Append(c);
_currentIndex++;
}
goto IL_013f;
IL_013f:
value = null;
return false;
}
private string ReadQuotedString()
{
StringBuilder stringBuilder = new StringBuilder();
_currentIndex++;
while (_currentIndex < _expression.Length) {
char c = _expression[_currentIndex];
if (c == '\\' && _currentIndex + 1 < _expression.Length) {
_currentIndex++;
if (_expression[_currentIndex] == '\'')
stringBuilder.Append('\'');
else {
if (_expression[_currentIndex] != '\\')
throw new JsonException("Unknown escape character: \\" + _expression[_currentIndex].ToString());
stringBuilder.Append('\\');
}
_currentIndex++;
} else {
if (c == '\'') {
_currentIndex++;
return stringBuilder.ToString();
}
_currentIndex++;
stringBuilder.Append(c);
}
}
throw new JsonException("Path ended with an open string.");
}
private bool Match(string s)
{
int num = _currentIndex;
foreach (char c in s) {
if (num >= _expression.Length || _expression[num] != c)
return false;
num++;
}
_currentIndex = num;
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 PathFilter ParseQuotedField(char indexerCloseChar, bool scan)
{
List<string> list = null;
while (_currentIndex < _expression.Length) {
string text = ReadQuotedString();
EatWhitespace();
EnsureLength("Path ended with open indexer.");
if (_expression[_currentIndex] == indexerCloseChar) {
if (list != null) {
list.Add(text);
if (!scan)
return new FieldMultipleFilter {
Names = list
};
return new ScanMultipleFilter {
Names = list
};
}
return CreatePathFilter(text, scan);
}
if (_expression[_currentIndex] != ',')
throw new JsonException("Unexpected character while parsing path indexer: " + _expression[_currentIndex].ToString());
_currentIndex++;
EatWhitespace();
if (list == null)
list = new List<string>();
list.Add(text);
}
throw new JsonException("Path ended with open indexer.");
}
private void EnsureLength(string message)
{
if (_currentIndex >= _expression.Length)
throw new JsonException(message);
}
internal IEnumerable<JToken> Evaluate(JToken root, JToken t, bool errorWhenNoMatch)
{
return Evaluate(Filters, root, t, errorWhenNoMatch);
}
internal static IEnumerable<JToken> Evaluate(List<PathFilter> filters, JToken root, JToken t, bool errorWhenNoMatch)
{
IEnumerable<JToken> enumerable = new JToken[1] {
t
};
foreach (PathFilter filter in filters) {
enumerable = filter.ExecuteFilter(root, enumerable, errorWhenNoMatch);
}
return enumerable;
}
}
}