From f0e9cac86eaeae6d7b28d098fa06a73d8c8986f7 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Sun, 16 Sep 2018 16:49:56 -0700 Subject: [PATCH 01/23] Implement JSONPath Copy files from https://github.com/markborkum/pacifica-jsonpath@jsonpath2 and merge conflicts. --- .github/ISSUE_TEMPLATE.md | 4 +- .travis.yml | 2 +- README.md | 143 +- jsonpath2/Expression.py | 17 + jsonpath2/Node.py | 31 + jsonpath2/Path.py | 34 + jsonpath2/Subscript.py | 7 + jsonpath2/ToJSONPath.py | 16 + jsonpath2/__init__.py | 16 +- jsonpath2/expressions/OperatorExpression.py | 160 +++ jsonpath2/expressions/SomeExpression.py | 22 + jsonpath2/expressions/__init__.py | 2 + jsonpath2/nodes/CurrentNode.py | 21 + jsonpath2/nodes/RecursiveDescentNode.py | 27 + jsonpath2/nodes/RootNode.py | 21 + jsonpath2/nodes/SubscriptNode.py | 46 + jsonpath2/nodes/TerminalNode.py | 16 + jsonpath2/nodes/__init__.py | 2 + jsonpath2/parser/JSONPath.g4 | 152 +++ jsonpath2/parser/JSONPath.interp | 85 ++ jsonpath2/parser/JSONPath.tokens | 56 + jsonpath2/parser/JSONPathLexer.interp | 113 ++ jsonpath2/parser/JSONPathLexer.py | 172 +++ jsonpath2/parser/JSONPathLexer.tokens | 56 + jsonpath2/parser/JSONPathListener.py | 136 ++ jsonpath2/parser/JSONPathParser.py | 1264 ++++++++++++++++++ jsonpath2/parser/__init__.py | 322 +++++ jsonpath2/subscripts/ArrayIndexSubscript.py | 36 + jsonpath2/subscripts/ArraySliceSubscript.py | 64 + jsonpath2/subscripts/FilterSubscript.py | 31 + jsonpath2/subscripts/ObjectIndexSubscript.py | 26 + jsonpath2/subscripts/WildcardSubscript.py | 26 + jsonpath2/subscripts/__init__.py | 2 + jsonpath2/test/example_test.py | 23 - jsonpath2/test/jsonpath2_test.py | 204 +++ requirements.txt | 3 + setup.py | 9 +- 37 files changed, 3320 insertions(+), 47 deletions(-) create mode 100644 jsonpath2/Expression.py create mode 100644 jsonpath2/Node.py create mode 100644 jsonpath2/Path.py create mode 100644 jsonpath2/Subscript.py create mode 100644 jsonpath2/ToJSONPath.py create mode 100644 jsonpath2/expressions/OperatorExpression.py create mode 100644 jsonpath2/expressions/SomeExpression.py create mode 100644 jsonpath2/expressions/__init__.py create mode 100644 jsonpath2/nodes/CurrentNode.py create mode 100644 jsonpath2/nodes/RecursiveDescentNode.py create mode 100644 jsonpath2/nodes/RootNode.py create mode 100644 jsonpath2/nodes/SubscriptNode.py create mode 100644 jsonpath2/nodes/TerminalNode.py create mode 100644 jsonpath2/nodes/__init__.py create mode 100644 jsonpath2/parser/JSONPath.g4 create mode 100644 jsonpath2/parser/JSONPath.interp create mode 100644 jsonpath2/parser/JSONPath.tokens create mode 100644 jsonpath2/parser/JSONPathLexer.interp create mode 100644 jsonpath2/parser/JSONPathLexer.py create mode 100644 jsonpath2/parser/JSONPathLexer.tokens create mode 100644 jsonpath2/parser/JSONPathListener.py create mode 100644 jsonpath2/parser/JSONPathParser.py create mode 100644 jsonpath2/parser/__init__.py create mode 100644 jsonpath2/subscripts/ArrayIndexSubscript.py create mode 100644 jsonpath2/subscripts/ArraySliceSubscript.py create mode 100644 jsonpath2/subscripts/FilterSubscript.py create mode 100644 jsonpath2/subscripts/ObjectIndexSubscript.py create mode 100644 jsonpath2/subscripts/WildcardSubscript.py create mode 100644 jsonpath2/subscripts/__init__.py delete mode 100644 jsonpath2/test/example_test.py create mode 100644 jsonpath2/test/jsonpath2_test.py diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 73aaadd..c94db12 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,5 +1,5 @@ -### JSONPath2 version -[Version of the JSONPath2 software where you are encountering the issue] +### jsonpath2 version +[Version of jsonpath2 where you are encountering the issue] ### Platform Details [Operating system distribution and release version. Cloud provider if running in the cloud] diff --git a/.travis.yml b/.travis.yml index 787b5dc..e9a5e2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ stages: install: pip install -r requirements-dev.txt script: -- coverage run --include='jsonpath2/*' -m pytest -v +- coverage run --include='jsonpath2/test/*' -m pytest -v - coverage report -m --fail-under 100 - pip install . - python setup.py bdist_wheel diff --git a/README.md b/README.md index ad4828f..cc68ef8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,142 @@ -# Python JSONPath2 +# jsonpath2 [![Build Status](https://travis-ci.org/pacifica/python-jsonpath2.svg?branch=master)](https://travis-ci.org/pacifica/python-jsonpath2) -A JSONPath implementation for Python (but better than jsonpath). + +This repository contains an implementation of [JSONPath](http://goessner.net/articles/JsonPath/) ([XPath](https://www.w3.org/TR/xpath/all/) for [JSON](https://www.json.org/)) for the Python programming language. + +## API + +### `Path` class + +The `jsonpath2.Path.Path` class represents a JSONPath. + +```python +>>> s = '{"hello":"Hello, world!"}' +'{"hello":"Hello, world!"}' +>>> import json +>>> d = json.loads(s) +{'hello':'Hello, world!'} +>>> from jsonpath2.Path import Path +>>> p = Path.parse_str('$["hello"]') + +>>> list(map(lambda match_data: match_data.current_value, p.match(d))) +['Hello, world!'] +>>> list(map(lambda match_data: match_data.node.tojsonpath(), p.match(d))) +['$["hello"]'] +``` + +This class is constructed with respect to the given instance of the `jsonpath2.Path.RootNode` class (viz., the `root_node` property). + +#### `parse_str(strdata)` class method + +Parse the given string and return a new instance of this class. + +#### `parse_file(fileName, encoding='ascii')` class method + +Parse the contents of the given file and return a new instance of this class. + +#### `match(root_value)` instance method + +Match the given JSON data structure against this instance. +For each match, yield an instance of the `jsonpath2.Node.MatchData` class. + +#### `__eq__(other)` instance method + +Tests if two instances are equal. + +#### `__str__()` instance method + +Returns the string representation of this instance. + +#### `root_node` property + +The root node of the abstract syntax tree for this instance. + +### `Node` abstract class + +The `jsonpath2.Node.Node` class represents the abstract syntax tree for a JSONPath. + +#### `__eq__(other)` instance method + +Tests if two instances are equal. + +#### `__jsonpath__()` instance method + +Yields the lexer tokens for the string representation of this instance. + +#### `match(root_value, current_value)` instance method + +Match the given root and current JSON data structures against this instance. +For each match, yield an instance of the `jsonpath2.Node.MatchData` class. + +#### `tojsonpath()` instance method + +Returns the string representation of this instance. + +### `MatchData` class + +The `jsonpath2.Node.MatchData` class represents the JSON value and context for a JSONPath match. + +This class is constructed with respect to a root JSON value, a current JSON value, and an abstract syntax tree node. + +#### `__eq__(other)` instance method + +Tests if two instances are equal. + +#### `root_value` property + +The root JSON value. + +#### `current_value` property + +The current JSON value (i.e., the matching JSON value). + +#### `node` property + +The abstract syntax tree node. + +## Syntax + +| XPath | JSONPath | Description | +| - | - | - | +| `/` | `$` | the root JSON value | +| `.` | `@` | the current JSON value | +| `/` | `.` or `[]` | child operator | +| `//` | `..` | recursive descent (depth-first search) | +| `*` | `*` | wildcard (all elements of a JSON array; all values of a JSON object; otherwise none) | +| `[]` | `[]` | subscript operator | +| | | `[,]` | union operator (for two or more subscript operators) | +| n/a | `[start:end:step]` | slice operator (subset of elements of a JSON array) | +| `[]` | `?()` | filter expression (for use with subscript operator) | + +| JSONPath Filter Expression | Description | +| - | - | +| `$` or `@` | nested JSONPath (returns `true` if any match exists; otherwise, returns `false`) | +| `=`, `!=`, `>`, `>=`, `<`, `<=` | binary operator, where left-hand operand is a nested JSONPath and right-right operand is a JSON value (returns `true` if any match exists; otherwise, returns `false`) | +| `and`, `or`, `not` | Boolean operator, where operands are JSONPath filter expressions | +| `(` ... `)` | parentheses | + +## Grammar and parser + +The [ANTLR v4](https://github.com/antlr/antlr4) grammar for JSONPath is available at `jsonpath2/parser/JSONPath.g4`. + +### Installing ANTLR v4 + +Adapted from https://github.com/antlr/antlr4/blob/master/doc/getting-started.md. + +```bash +cd /usr/local/lib +curl -O http://www.antlr.org/download/antlr-4.7.1-complete.jar + +export CLASSPATH=".:/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH" + +alias antlr4='java -Xmx500M -cp "/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool' +alias grun='java org.antlr.v4.gui.TestRig' +``` + +### Building the parser for the grammar + +Adapted from https://github.com/antlr/antlr4/blob/master/doc/python-target.md. + +```bash +antlr4 -Dlanguage=Python3 -o . -lib . jsonpath2/parser/JSONPath.g4 +``` diff --git a/jsonpath2/Expression.py b/jsonpath2/Expression.py new file mode 100644 index 0000000..f60d212 --- /dev/null +++ b/jsonpath2/Expression.py @@ -0,0 +1,17 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from abc import abstractmethod + +from jsonpath2.ToJSONPath import ToJSONPath + +class Expression(ToJSONPath): + def __init__(self): + super(Expression, self).__init__() + + def __eq__(self, other:object) -> bool: + return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) + + @abstractmethod + def evaluate(self, root_value:object, current_value:object) -> bool: + raise NotImplementedError() diff --git a/jsonpath2/Node.py b/jsonpath2/Node.py new file mode 100644 index 0000000..7115590 --- /dev/null +++ b/jsonpath2/Node.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from abc import abstractmethod +from typing import Generator + +from jsonpath2.ToJSONPath import ToJSONPath + +class MatchData(object): + def __init__(self, node, root_value, current_value): + super(MatchData, self).__init__() + + self.node = node + + self.root_value = root_value + + self.current_value = current_value + + def __eq__(self, other:object) -> bool: + return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) + +class Node(ToJSONPath): + def __init__(self): + super(Node, self).__init__() + + def __eq__(self, other:object) -> bool: + return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) + + @abstractmethod + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + raise NotImplementedError() diff --git a/jsonpath2/Path.py b/jsonpath2/Path.py new file mode 100644 index 0000000..10b3a77 --- /dev/null +++ b/jsonpath2/Path.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from typing import Generator + +from jsonpath2.Node import MatchData +from jsonpath2.nodes.RootNode import RootNode +import jsonpath2.parser as _parser + +class Path(object): + def __init__(self, root_node:RootNode): + super(Path, self).__init__() + + if isinstance(root_node, RootNode): + self.root_node = root_node + else: + raise ValueError() + + def __eq__(self, other:object) -> bool: + return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) + + def __str__(self) -> str: + return self.root_node.tojsonpath() + + def match(self, root_value:object) -> Generator[MatchData, None, None]: + return self.root_node.match(root_value, root_value) + + @classmethod + def parse_file(self, *args, **kwargs): + return Path(_parser.parse_file(*args, **kwargs)) + + @classmethod + def parse_str(self, *args, **kwargs): + return Path(_parser.parse_str(*args, **kwargs)) diff --git a/jsonpath2/Subscript.py b/jsonpath2/Subscript.py new file mode 100644 index 0000000..9269399 --- /dev/null +++ b/jsonpath2/Subscript.py @@ -0,0 +1,7 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from jsonpath2.Node import MatchData, Node + +class Subscript(Node): + pass diff --git a/jsonpath2/ToJSONPath.py b/jsonpath2/ToJSONPath.py new file mode 100644 index 0000000..c17725b --- /dev/null +++ b/jsonpath2/ToJSONPath.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from abc import ABC, abstractmethod +from typing import Generator + +class ToJSONPath(ABC): + def __init__(self): + super(ToJSONPath, self).__init__() + + def tojsonpath(self) -> str: + return ''.join(list(self.__jsonpath__())) + + @abstractmethod + def __jsonpath__(self) -> Generator[str, None, None]: + raise NotImplementedError() diff --git a/jsonpath2/__init__.py b/jsonpath2/__init__.py index 7f286c7..3224a2a 100644 --- a/jsonpath2/__init__.py +++ b/jsonpath2/__init__.py @@ -1,17 +1,3 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -"""Example Module.""" - - -class Example(object): - """This is an example class in the example module.""" - - @staticmethod - def add(thing1, thing2): - """Add thing one and thing two together.""" - return thing1 + thing2 - - @staticmethod - def mul(thing1, thing2): - """Multiply thing one and thing two together.""" - return thing1 * thing2 +__import__('pkg_resources').declare_namespace(__name__) diff --git a/jsonpath2/expressions/OperatorExpression.py b/jsonpath2/expressions/OperatorExpression.py new file mode 100644 index 0000000..33703f5 --- /dev/null +++ b/jsonpath2/expressions/OperatorExpression.py @@ -0,0 +1,160 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from abc import abstractmethod +import json +from typing import Callable, Generator, List + +from jsonpath2.Expression import Expression +from jsonpath2.Node import Node + +from jsonpath2.expressions.SomeExpression import SomeExpression + +class OperatorExpression(Expression): + pass + +class BinaryOperatorExpression(OperatorExpression): + def __init__(self, token:str, callback:Callable[[object, object], bool], left_node:Node, right_value:object): + super(BinaryOperatorExpression, self).__init__() + + self.token = token + + self.callback = callback + + self.left_node = left_node + + self.right_value = right_value + + def __jsonpath__(self) -> Generator[str, None, None]: + for left_node_token in self.left_node.__jsonpath__(): + yield left_node_token + + yield ' ' + yield self.token + yield ' ' + + yield json.dumps(self.right_value) + + def evaluate(self, root_value:object, current_value:object) -> bool: + return any(map(lambda left_node_match_data: self.callback(left_node_match_data.current_value, self.right_value), self.left_node.match(root_value, current_value))) + +LAMBDA_EQUAL_ = lambda x, y: x == y + +LAMBDA_NOT_EQUAL_ = lambda x, y: x != y + +class EqualBinaryOperatorExpression(BinaryOperatorExpression): + def __init__(self, *args, **kwargs): + super(EqualBinaryOperatorExpression, self).__init__('=', LAMBDA_EQUAL_, *args, **kwargs) + +class NotEqualBinaryOperatorExpression(BinaryOperatorExpression): + def __init__(self, *args, **kwargs): + super(NotEqualBinaryOperatorExpression, self).__init__('!=', LAMBDA_NOT_EQUAL_, *args, **kwargs) + +def _wrap_callback(callback): + def wrapped_callback(x, y): + if (isinstance(x, float) or isinstance(x, int)) and ((isinstance(y, float) or isinstance(y, int))): + return callback(x, y) + else: + return False + return wrapped_callback + +LAMBDA_LESS_THAN_ = _wrap_callback(lambda x, y: x < y) + +LAMBDA_LESS_THAN_OR_EQUAL_TO_ = _wrap_callback(lambda x, y: x <= y) + +LAMBDA_GREATER_THAN_ = _wrap_callback(lambda x, y: x > y) + +LAMBDA_GREATER_THAN_OR_EQUAL_TO_ = _wrap_callback(lambda x, y: x >= y) + +class LessThanBinaryOperatorExpression(BinaryOperatorExpression): + def __init__(self, *args, **kwargs): + super(LessThanBinaryOperatorExpression, self).__init__('<', LAMBDA_LESS_THAN_, *args, **kwargs) + +class LessThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): + def __init__(self, *args, **kwargs): + super(LessThanOrEqualToBinaryOperatorExpression, self).__init__('<=', LAMBDA_LESS_THAN_OR_EQUAL_TO_, *args, **kwargs) + +class GreaterThanBinaryOperatorExpression(BinaryOperatorExpression): + def __init__(self, *args, **kwargs): + super(GreaterThanBinaryOperatorExpression, self).__init__('>', LAMBDA_GREATER_THAN_, *args, **kwargs) + +class GreaterThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): + def __init__(self, *args, **kwargs): + super(GreaterThanOrEqualToBinaryOperatorExpression, self).__init__('>=', LAMBDA_GREATER_THAN_OR_EQUAL_TO_, *args, **kwargs) + +class UnaryOperatorExpression(OperatorExpression): + def __init__(self, token:str, callback:Callable[[bool], bool], expression:Expression): + super(UnaryOperatorExpression, self).__init__() + + self.token = token + + self.callback = callback + + self.expression = expression + + def __jsonpath__(self) -> Generator[str, None, None]: + yield self.token + yield ' ' + + if isinstance(self.expression, UnaryOperatorExpression) or isinstance(self.expression, VariadicOperatorExpression): + yield '(' + + for expression_token in self.expression.__jsonpath__(): + yield expression_token + + if isinstance(self.expression, UnaryOperatorExpression) or isinstance(self.expression, VariadicOperatorExpression): + yield ')' + + def evaluate(self, root_value:object, current_value:object) -> bool: + return self.callback(self.expression.evaluate(root_value, current_value)) + +LAMBDA_NOT_ = lambda x: not x + +class NotUnaryOperatorExpression(UnaryOperatorExpression): + def __init__(self, *args, **kwargs): + super(NotUnaryOperatorExpression, self).__init__('not', LAMBDA_NOT_, *args, **kwargs) + +class VariadicOperatorExpression(OperatorExpression): + def __init__(self, token:str, callback:Callable[[List[bool]], bool], expressions:List[Expression]=[]): + super(VariadicOperatorExpression, self).__init__() + + self.token = token + + self.callback = callback + + self.expressions = expressions + + def __jsonpath__(self) -> Generator[str, None, None]: + expressions_count = len(self.expressions) + + if expressions_count == 0: + pass + elif expressions_count == 1: + for expression_token in self.expressions[0].__jsonpath__(): + yield expression_token + else: + for expression_index, expression in enumerate(self.expressions): + if expression_index > 0: + yield ' ' + yield self.token + yield ' ' + + if isinstance(expression, VariadicOperatorExpression): + yield '(' + + for expression_token in expression.__jsonpath__(): + yield expression_token + + if isinstance(expression, VariadicOperatorExpression): + yield ')' + + def evaluate(self, root_value:object, current_value:object) -> bool: + return self.callback(map(lambda expression: expression.evaluate(root_value, current_value), self.expressions)) + +class AndVariadicOperatorExpression(VariadicOperatorExpression): + def __init__(self, *args, **kwargs): + super(AndVariadicOperatorExpression, self).__init__('and', all, *args, **kwargs) + +class OrVariadicOperatorExpression(VariadicOperatorExpression): + def __init__(self, *args, **kwargs): + super(OrVariadicOperatorExpression, self).__init__('or', any, *args, **kwargs) diff --git a/jsonpath2/expressions/SomeExpression.py b/jsonpath2/expressions/SomeExpression.py new file mode 100644 index 0000000..67344ed --- /dev/null +++ b/jsonpath2/expressions/SomeExpression.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from typing import Generator + +from jsonpath2.Expression import Expression +from jsonpath2.Node import Node + +class SomeExpression(Expression): + def __init__(self, next_node:Node): + super(SomeExpression, self).__init__() + + self.next_node = next_node + + def __jsonpath__(self) -> Generator[str, None, None]: + return self.next_node.__jsonpath__() + + def evaluate(self, root_value:object, current_value:object) -> bool: + for next_node_match_data in self.next_node.match(root_value, current_value): + return True + + return False diff --git a/jsonpath2/expressions/__init__.py b/jsonpath2/expressions/__init__.py new file mode 100644 index 0000000..836e3e8 --- /dev/null +++ b/jsonpath2/expressions/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- diff --git a/jsonpath2/nodes/CurrentNode.py b/jsonpath2/nodes/CurrentNode.py new file mode 100644 index 0000000..3bbb3de --- /dev/null +++ b/jsonpath2/nodes/CurrentNode.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from typing import Generator + +from jsonpath2.Node import MatchData, Node + +class CurrentNode(Node): + def __init__(self, next_node:Node): + super(CurrentNode, self).__init__() + + self.next_node = next_node + + def __jsonpath__(self) -> Generator[str, None, None]: + yield '@' + + for next_node_token in self.next_node.__jsonpath__(): + yield next_node_token + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + return map(lambda next_node_match_data: MatchData(CurrentNode(next_node_match_data.node), next_node_match_data.root_value, next_node_match_data.current_value), self.next_node.match(root_value, current_value)) diff --git a/jsonpath2/nodes/RecursiveDescentNode.py b/jsonpath2/nodes/RecursiveDescentNode.py new file mode 100644 index 0000000..57771ee --- /dev/null +++ b/jsonpath2/nodes/RecursiveDescentNode.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import itertools +from typing import Generator + +from jsonpath2.Node import MatchData, Node + +from jsonpath2.nodes.SubscriptNode import SubscriptNode + +from jsonpath2.subscripts.WildcardSubscript import WildcardSubscript + +class RecursiveDescentNode(Node): + def __init__(self, next_node:Node): + super(RecursiveDescentNode, self).__init__() + + self.next_node = next_node + + def __jsonpath__(self) -> Generator[str, None, None]: + yield '..' + + for next_node_token in self.next_node.__jsonpath__(): + yield next_node_token + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + # NOTE Depth-first + return itertools.chain(self.next_node.match(root_value, current_value), SubscriptNode(self, [WildcardSubscript()]).match(root_value, current_value)) diff --git a/jsonpath2/nodes/RootNode.py b/jsonpath2/nodes/RootNode.py new file mode 100644 index 0000000..bd65549 --- /dev/null +++ b/jsonpath2/nodes/RootNode.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from typing import Generator + +from jsonpath2.Node import MatchData, Node + +class RootNode(Node): + def __init__(self, next_node:Node): + super(RootNode, self).__init__() + + self.next_node = next_node + + def __jsonpath__(self) -> Generator[str, None, None]: + yield '$' + + for next_node_token in self.next_node.__jsonpath__(): + yield next_node_token + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + return map(lambda next_node_match_data: MatchData(RootNode(next_node_match_data.node), next_node_match_data.root_value, next_node_match_data.current_value), self.next_node.match(root_value, root_value)) diff --git a/jsonpath2/nodes/SubscriptNode.py b/jsonpath2/nodes/SubscriptNode.py new file mode 100644 index 0000000..62e9e55 --- /dev/null +++ b/jsonpath2/nodes/SubscriptNode.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from typing import Generator, List + +from jsonpath2.Node import MatchData, Node +from jsonpath2.Subscript import Subscript + +from jsonpath2.nodes.TerminalNode import TerminalNode + +class SubscriptNode(Node): + def __init__(self, next_node:Node, subscripts:List[Subscript]=[]): + super(SubscriptNode, self).__init__() + + self.next_node = next_node + + self.subscripts = subscripts + + def __jsonpath__(self) -> Generator[str, None, None]: + yield '[' + + for subscript_index, subscript in enumerate(self.subscripts): + if subscript_index > 0: + yield ',' + + for subscript_token in subscript.__jsonpath__(): + yield subscript_token + + yield ']' + + for next_node_token in self.next_node.__jsonpath__(): + yield next_node_token + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + for subscript in self.subscripts: + for subscript_match_data in subscript.match(root_value, current_value): + for next_node_match_data in self.next_node.match(subscript_match_data.root_value, subscript_match_data.current_value): + if isinstance(subscript_match_data.node, TerminalNode): + yield next_node_match_data + elif isinstance(subscript_match_data.node, SubscriptNode): + if isinstance(subscript_match_data.node.next_node, TerminalNode): + yield MatchData(SubscriptNode(next_node_match_data.node, subscript_match_data.node.subscripts), next_node_match_data.root_value, next_node_match_data.current_value) + else: + raise ValueError() + else: + raise ValueError() diff --git a/jsonpath2/nodes/TerminalNode.py b/jsonpath2/nodes/TerminalNode.py new file mode 100644 index 0000000..bd93ecd --- /dev/null +++ b/jsonpath2/nodes/TerminalNode.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from typing import Generator + +from jsonpath2.Node import MatchData, Node + +class TerminalNode(Node): + def __init__(self): + super(TerminalNode, self).__init__() + + def __jsonpath__(self) -> Generator[str, None, None]: + return [] + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + return [MatchData(self, root_value, current_value)] diff --git a/jsonpath2/nodes/__init__.py b/jsonpath2/nodes/__init__.py new file mode 100644 index 0000000..836e3e8 --- /dev/null +++ b/jsonpath2/nodes/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- diff --git a/jsonpath2/parser/JSONPath.g4 b/jsonpath2/parser/JSONPath.g4 new file mode 100644 index 0000000..c059868 --- /dev/null +++ b/jsonpath2/parser/JSONPath.g4 @@ -0,0 +1,152 @@ +grammar JSONPath; + +CURRENT_VALUE : '@' ; +RECURSIVE_DESCENT : '..' ; +ROOT_VALUE : '$' ; +SUBSCRIPT : '.' ; +WILDCARD_SUBSCRIPT : '*' ; + +AND : 'and' ; +EQ : '=' ; +GE : '>=' ; +GT : '>' ; +LE : '<=' ; +LT : '<' ; +NE : '!=' ; +NOT : 'not' ; +OR : 'or' ; + +TRUE : 'true' ; +FALSE : 'false' ; +NULL : 'null' ; + +BRACE_LEFT : '{' ; +BRACE_RIGHT : '}' ; +BRACKET_LEFT : '[' ; +BRACKET_RIGHT : ']' ; +COLON : ':' ; +COMMA : ',' ; +PAREN_LEFT : '(' ; +PAREN_RIGHT : ')' ; +QUESTION : '?' ; + +jsonpath + : ROOT_VALUE subscript? EOF + ; + +subscript + : RECURSIVE_DESCENT ( subscriptableBareword | subscriptables ) subscript? + | SUBSCRIPT subscriptableBareword subscript? + | subscriptables subscript? + ; + +subscriptables + : BRACKET_LEFT subscriptable ( COMMA subscriptable )* BRACKET_RIGHT + ; + +subscriptableBareword + : ID + | WILDCARD_SUBSCRIPT + ; + +subscriptable + : STRING + | NUMBER{self.tryCast(int)}? ( COLON ( NUMBER{self.tryCast(int)}? )? ( COLON NUMBER{self.tryCast(int)}? )? )? + | WILDCARD_SUBSCRIPT + | QUESTION PAREN_LEFT expression PAREN_RIGHT + ; + +expression + : andExpression + ; + +andExpression + : orExpression ( AND andExpression )? + ; + +orExpression + : notExpression ( OR orExpression )? + ; + +notExpression + : NOT notExpression + | PAREN_LEFT expression PAREN_RIGHT + | ( ROOT_VALUE | CURRENT_VALUE ) subscript? ( ( EQ | NE | LT | LE | GT | GE ) value )? + ; + + +ID + : [_A-Za-z] [_A-Za-z0-9]* + ; + + +/* c.f., https://github.com/antlr/grammars-v4/blob/master/json/JSON.g4 */ + +json + : value + ; + +obj + : BRACE_LEFT pair ( COMMA pair )* BRACE_RIGHT + | BRACE_LEFT BRACE_RIGHT + ; + +pair + : STRING COLON value + ; + +array + : BRACKET_LEFT value ( COMMA value )* BRACKET_RIGHT + | BRACKET_LEFT BRACKET_RIGHT + ; + +value + : STRING + | NUMBER + | obj + | array + | TRUE + | FALSE + | NULL + ; + + +STRING + : '"' (ESC | SAFECODEPOINT)* '"' + ; + + +fragment ESC + : '\\' (["\\/bfnrt] | UNICODE) + ; +fragment UNICODE + : 'u' HEX HEX HEX HEX + ; +fragment HEX + : [0-9a-fA-F] + ; +fragment SAFECODEPOINT + : ~ ["\\\u0000-\u001F] + ; + + +NUMBER + : '-'? INT ('.' [0-9] +)? EXP? + ; + + +fragment INT + : '0' | [1-9] [0-9]* + ; + +// no leading zeros + +fragment EXP + : [Ee] [+\-]? INT + ; + +// \- since - means "range" inside [...] + +WS + : [ \t\n\r] + -> skip + ; diff --git a/jsonpath2/parser/JSONPath.interp b/jsonpath2/parser/JSONPath.interp new file mode 100644 index 0000000..2fdafae --- /dev/null +++ b/jsonpath2/parser/JSONPath.interp @@ -0,0 +1,85 @@ +token literal names: +null +'@' +'..' +'$' +'.' +'*' +'and' +'=' +'>=' +'>' +'<=' +'<' +'!=' +'not' +'or' +'true' +'false' +'null' +'{' +'}' +'[' +']' +':' +',' +'(' +')' +'?' +null +null +null +null + +token symbolic names: +null +CURRENT_VALUE +RECURSIVE_DESCENT +ROOT_VALUE +SUBSCRIPT +WILDCARD_SUBSCRIPT +AND +EQ +GE +GT +LE +LT +NE +NOT +OR +TRUE +FALSE +NULL +BRACE_LEFT +BRACE_RIGHT +BRACKET_LEFT +BRACKET_RIGHT +COLON +COMMA +PAREN_LEFT +PAREN_RIGHT +QUESTION +ID +STRING +NUMBER +WS + +rule names: +jsonpath +subscript +subscriptables +subscriptableBareword +subscriptable +expression +andExpression +orExpression +notExpression +json +obj +pair +array +value + + +atn: +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 32, 165, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 3, 2, 3, 2, 5, 2, 33, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 5, 3, 40, 10, 3, 3, 3, 5, 3, 43, 10, 3, 3, 3, 3, 3, 3, 3, 5, 3, 48, 10, 3, 3, 3, 3, 3, 5, 3, 52, 10, 3, 5, 3, 54, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 60, 10, 4, 12, 4, 14, 4, 63, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 75, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 80, 10, 6, 5, 6, 82, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 90, 10, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 5, 8, 97, 10, 8, 3, 9, 3, 9, 3, 9, 5, 9, 102, 10, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 112, 10, 10, 3, 10, 3, 10, 5, 10, 116, 10, 10, 5, 10, 118, 10, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 126, 10, 12, 12, 12, 14, 12, 129, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 135, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 145, 10, 14, 12, 14, 14, 14, 148, 11, 14, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 154, 10, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 163, 10, 15, 3, 15, 2, 2, 16, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 2, 5, 4, 2, 7, 7, 29, 29, 4, 2, 3, 3, 5, 5, 3, 2, 9, 14, 2, 180, 2, 30, 3, 2, 2, 2, 4, 53, 3, 2, 2, 2, 6, 55, 3, 2, 2, 2, 8, 66, 3, 2, 2, 2, 10, 89, 3, 2, 2, 2, 12, 91, 3, 2, 2, 2, 14, 93, 3, 2, 2, 2, 16, 98, 3, 2, 2, 2, 18, 117, 3, 2, 2, 2, 20, 119, 3, 2, 2, 2, 22, 134, 3, 2, 2, 2, 24, 136, 3, 2, 2, 2, 26, 153, 3, 2, 2, 2, 28, 162, 3, 2, 2, 2, 30, 32, 7, 5, 2, 2, 31, 33, 5, 4, 3, 2, 32, 31, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 34, 3, 2, 2, 2, 34, 35, 7, 2, 2, 3, 35, 3, 3, 2, 2, 2, 36, 39, 7, 4, 2, 2, 37, 40, 5, 8, 5, 2, 38, 40, 5, 6, 4, 2, 39, 37, 3, 2, 2, 2, 39, 38, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 43, 5, 4, 3, 2, 42, 41, 3, 2, 2, 2, 42, 43, 3, 2, 2, 2, 43, 54, 3, 2, 2, 2, 44, 45, 7, 6, 2, 2, 45, 47, 5, 8, 5, 2, 46, 48, 5, 4, 3, 2, 47, 46, 3, 2, 2, 2, 47, 48, 3, 2, 2, 2, 48, 54, 3, 2, 2, 2, 49, 51, 5, 6, 4, 2, 50, 52, 5, 4, 3, 2, 51, 50, 3, 2, 2, 2, 51, 52, 3, 2, 2, 2, 52, 54, 3, 2, 2, 2, 53, 36, 3, 2, 2, 2, 53, 44, 3, 2, 2, 2, 53, 49, 3, 2, 2, 2, 54, 5, 3, 2, 2, 2, 55, 56, 7, 22, 2, 2, 56, 61, 5, 10, 6, 2, 57, 58, 7, 25, 2, 2, 58, 60, 5, 10, 6, 2, 59, 57, 3, 2, 2, 2, 60, 63, 3, 2, 2, 2, 61, 59, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 64, 3, 2, 2, 2, 63, 61, 3, 2, 2, 2, 64, 65, 7, 23, 2, 2, 65, 7, 3, 2, 2, 2, 66, 67, 9, 2, 2, 2, 67, 9, 3, 2, 2, 2, 68, 90, 7, 30, 2, 2, 69, 70, 7, 31, 2, 2, 70, 81, 6, 6, 2, 2, 71, 74, 7, 24, 2, 2, 72, 73, 7, 31, 2, 2, 73, 75, 6, 6, 3, 2, 74, 72, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 79, 3, 2, 2, 2, 76, 77, 7, 24, 2, 2, 77, 78, 7, 31, 2, 2, 78, 80, 6, 6, 4, 2, 79, 76, 3, 2, 2, 2, 79, 80, 3, 2, 2, 2, 80, 82, 3, 2, 2, 2, 81, 71, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 90, 3, 2, 2, 2, 83, 90, 7, 7, 2, 2, 84, 85, 7, 28, 2, 2, 85, 86, 7, 26, 2, 2, 86, 87, 5, 12, 7, 2, 87, 88, 7, 27, 2, 2, 88, 90, 3, 2, 2, 2, 89, 68, 3, 2, 2, 2, 89, 69, 3, 2, 2, 2, 89, 83, 3, 2, 2, 2, 89, 84, 3, 2, 2, 2, 90, 11, 3, 2, 2, 2, 91, 92, 5, 14, 8, 2, 92, 13, 3, 2, 2, 2, 93, 96, 5, 16, 9, 2, 94, 95, 7, 8, 2, 2, 95, 97, 5, 14, 8, 2, 96, 94, 3, 2, 2, 2, 96, 97, 3, 2, 2, 2, 97, 15, 3, 2, 2, 2, 98, 101, 5, 18, 10, 2, 99, 100, 7, 16, 2, 2, 100, 102, 5, 16, 9, 2, 101, 99, 3, 2, 2, 2, 101, 102, 3, 2, 2, 2, 102, 17, 3, 2, 2, 2, 103, 104, 7, 15, 2, 2, 104, 118, 5, 18, 10, 2, 105, 106, 7, 26, 2, 2, 106, 107, 5, 12, 7, 2, 107, 108, 7, 27, 2, 2, 108, 118, 3, 2, 2, 2, 109, 111, 9, 3, 2, 2, 110, 112, 5, 4, 3, 2, 111, 110, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 115, 3, 2, 2, 2, 113, 114, 9, 4, 2, 2, 114, 116, 5, 28, 15, 2, 115, 113, 3, 2, 2, 2, 115, 116, 3, 2, 2, 2, 116, 118, 3, 2, 2, 2, 117, 103, 3, 2, 2, 2, 117, 105, 3, 2, 2, 2, 117, 109, 3, 2, 2, 2, 118, 19, 3, 2, 2, 2, 119, 120, 5, 28, 15, 2, 120, 21, 3, 2, 2, 2, 121, 122, 7, 20, 2, 2, 122, 127, 5, 24, 13, 2, 123, 124, 7, 25, 2, 2, 124, 126, 5, 24, 13, 2, 125, 123, 3, 2, 2, 2, 126, 129, 3, 2, 2, 2, 127, 125, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 130, 3, 2, 2, 2, 129, 127, 3, 2, 2, 2, 130, 131, 7, 21, 2, 2, 131, 135, 3, 2, 2, 2, 132, 133, 7, 20, 2, 2, 133, 135, 7, 21, 2, 2, 134, 121, 3, 2, 2, 2, 134, 132, 3, 2, 2, 2, 135, 23, 3, 2, 2, 2, 136, 137, 7, 30, 2, 2, 137, 138, 7, 24, 2, 2, 138, 139, 5, 28, 15, 2, 139, 25, 3, 2, 2, 2, 140, 141, 7, 22, 2, 2, 141, 146, 5, 28, 15, 2, 142, 143, 7, 25, 2, 2, 143, 145, 5, 28, 15, 2, 144, 142, 3, 2, 2, 2, 145, 148, 3, 2, 2, 2, 146, 144, 3, 2, 2, 2, 146, 147, 3, 2, 2, 2, 147, 149, 3, 2, 2, 2, 148, 146, 3, 2, 2, 2, 149, 150, 7, 23, 2, 2, 150, 154, 3, 2, 2, 2, 151, 152, 7, 22, 2, 2, 152, 154, 7, 23, 2, 2, 153, 140, 3, 2, 2, 2, 153, 151, 3, 2, 2, 2, 154, 27, 3, 2, 2, 2, 155, 163, 7, 30, 2, 2, 156, 163, 7, 31, 2, 2, 157, 163, 5, 22, 12, 2, 158, 163, 5, 26, 14, 2, 159, 163, 7, 17, 2, 2, 160, 163, 7, 18, 2, 2, 161, 163, 7, 19, 2, 2, 162, 155, 3, 2, 2, 2, 162, 156, 3, 2, 2, 2, 162, 157, 3, 2, 2, 2, 162, 158, 3, 2, 2, 2, 162, 159, 3, 2, 2, 2, 162, 160, 3, 2, 2, 2, 162, 161, 3, 2, 2, 2, 163, 29, 3, 2, 2, 2, 23, 32, 39, 42, 47, 51, 53, 61, 74, 79, 81, 89, 96, 101, 111, 115, 117, 127, 134, 146, 153, 162] \ No newline at end of file diff --git a/jsonpath2/parser/JSONPath.tokens b/jsonpath2/parser/JSONPath.tokens new file mode 100644 index 0000000..37a8444 --- /dev/null +++ b/jsonpath2/parser/JSONPath.tokens @@ -0,0 +1,56 @@ +CURRENT_VALUE=1 +RECURSIVE_DESCENT=2 +ROOT_VALUE=3 +SUBSCRIPT=4 +WILDCARD_SUBSCRIPT=5 +AND=6 +EQ=7 +GE=8 +GT=9 +LE=10 +LT=11 +NE=12 +NOT=13 +OR=14 +TRUE=15 +FALSE=16 +NULL=17 +BRACE_LEFT=18 +BRACE_RIGHT=19 +BRACKET_LEFT=20 +BRACKET_RIGHT=21 +COLON=22 +COMMA=23 +PAREN_LEFT=24 +PAREN_RIGHT=25 +QUESTION=26 +ID=27 +STRING=28 +NUMBER=29 +WS=30 +'@'=1 +'..'=2 +'$'=3 +'.'=4 +'*'=5 +'and'=6 +'='=7 +'>='=8 +'>'=9 +'<='=10 +'<'=11 +'!='=12 +'not'=13 +'or'=14 +'true'=15 +'false'=16 +'null'=17 +'{'=18 +'}'=19 +'['=20 +']'=21 +':'=22 +','=23 +'('=24 +')'=25 +'?'=26 diff --git a/jsonpath2/parser/JSONPathLexer.interp b/jsonpath2/parser/JSONPathLexer.interp new file mode 100644 index 0000000..6fbc9ab --- /dev/null +++ b/jsonpath2/parser/JSONPathLexer.interp @@ -0,0 +1,113 @@ +token literal names: +null +'@' +'..' +'$' +'.' +'*' +'and' +'=' +'>=' +'>' +'<=' +'<' +'!=' +'not' +'or' +'true' +'false' +'null' +'{' +'}' +'[' +']' +':' +',' +'(' +')' +'?' +null +null +null +null + +token symbolic names: +null +CURRENT_VALUE +RECURSIVE_DESCENT +ROOT_VALUE +SUBSCRIPT +WILDCARD_SUBSCRIPT +AND +EQ +GE +GT +LE +LT +NE +NOT +OR +TRUE +FALSE +NULL +BRACE_LEFT +BRACE_RIGHT +BRACKET_LEFT +BRACKET_RIGHT +COLON +COMMA +PAREN_LEFT +PAREN_RIGHT +QUESTION +ID +STRING +NUMBER +WS + +rule names: +CURRENT_VALUE +RECURSIVE_DESCENT +ROOT_VALUE +SUBSCRIPT +WILDCARD_SUBSCRIPT +AND +EQ +GE +GT +LE +LT +NE +NOT +OR +TRUE +FALSE +NULL +BRACE_LEFT +BRACE_RIGHT +BRACKET_LEFT +BRACKET_RIGHT +COLON +COMMA +PAREN_LEFT +PAREN_RIGHT +QUESTION +ID +STRING +ESC +UNICODE +HEX +SAFECODEPOINT +NUMBER +INT +EXP +WS + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 32, 216, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 28, 3, 28, 7, 28, 149, 10, 28, 12, 28, 14, 28, 152, 11, 28, 3, 29, 3, 29, 3, 29, 7, 29, 157, 10, 29, 12, 29, 14, 29, 160, 11, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 5, 30, 167, 10, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 33, 3, 33, 3, 34, 5, 34, 180, 10, 34, 3, 34, 3, 34, 3, 34, 6, 34, 185, 10, 34, 13, 34, 14, 34, 186, 5, 34, 189, 10, 34, 3, 34, 5, 34, 192, 10, 34, 3, 35, 3, 35, 3, 35, 7, 35, 197, 10, 35, 12, 35, 14, 35, 200, 11, 35, 5, 35, 202, 10, 35, 3, 36, 3, 36, 5, 36, 206, 10, 36, 3, 36, 3, 36, 3, 37, 6, 37, 211, 10, 37, 13, 37, 14, 37, 212, 3, 37, 3, 37, 2, 2, 38, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 13, 25, 14, 27, 15, 29, 16, 31, 17, 33, 18, 35, 19, 37, 20, 39, 21, 41, 22, 43, 23, 45, 24, 47, 25, 49, 26, 51, 27, 53, 28, 55, 29, 57, 30, 59, 2, 61, 2, 63, 2, 65, 2, 67, 31, 69, 2, 71, 2, 73, 32, 3, 2, 12, 5, 2, 67, 92, 97, 97, 99, 124, 6, 2, 50, 59, 67, 92, 97, 97, 99, 124, 10, 2, 36, 36, 49, 49, 94, 94, 100, 100, 104, 104, 112, 112, 116, 116, 118, 118, 5, 2, 50, 59, 67, 72, 99, 104, 5, 2, 2, 33, 36, 36, 94, 94, 3, 2, 50, 59, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 5, 2, 11, 12, 15, 15, 34, 34, 2, 221, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 3, 75, 3, 2, 2, 2, 5, 77, 3, 2, 2, 2, 7, 80, 3, 2, 2, 2, 9, 82, 3, 2, 2, 2, 11, 84, 3, 2, 2, 2, 13, 86, 3, 2, 2, 2, 15, 90, 3, 2, 2, 2, 17, 92, 3, 2, 2, 2, 19, 95, 3, 2, 2, 2, 21, 97, 3, 2, 2, 2, 23, 100, 3, 2, 2, 2, 25, 102, 3, 2, 2, 2, 27, 105, 3, 2, 2, 2, 29, 109, 3, 2, 2, 2, 31, 112, 3, 2, 2, 2, 33, 117, 3, 2, 2, 2, 35, 123, 3, 2, 2, 2, 37, 128, 3, 2, 2, 2, 39, 130, 3, 2, 2, 2, 41, 132, 3, 2, 2, 2, 43, 134, 3, 2, 2, 2, 45, 136, 3, 2, 2, 2, 47, 138, 3, 2, 2, 2, 49, 140, 3, 2, 2, 2, 51, 142, 3, 2, 2, 2, 53, 144, 3, 2, 2, 2, 55, 146, 3, 2, 2, 2, 57, 153, 3, 2, 2, 2, 59, 163, 3, 2, 2, 2, 61, 168, 3, 2, 2, 2, 63, 174, 3, 2, 2, 2, 65, 176, 3, 2, 2, 2, 67, 179, 3, 2, 2, 2, 69, 201, 3, 2, 2, 2, 71, 203, 3, 2, 2, 2, 73, 210, 3, 2, 2, 2, 75, 76, 7, 66, 2, 2, 76, 4, 3, 2, 2, 2, 77, 78, 7, 48, 2, 2, 78, 79, 7, 48, 2, 2, 79, 6, 3, 2, 2, 2, 80, 81, 7, 38, 2, 2, 81, 8, 3, 2, 2, 2, 82, 83, 7, 48, 2, 2, 83, 10, 3, 2, 2, 2, 84, 85, 7, 44, 2, 2, 85, 12, 3, 2, 2, 2, 86, 87, 7, 99, 2, 2, 87, 88, 7, 112, 2, 2, 88, 89, 7, 102, 2, 2, 89, 14, 3, 2, 2, 2, 90, 91, 7, 63, 2, 2, 91, 16, 3, 2, 2, 2, 92, 93, 7, 64, 2, 2, 93, 94, 7, 63, 2, 2, 94, 18, 3, 2, 2, 2, 95, 96, 7, 64, 2, 2, 96, 20, 3, 2, 2, 2, 97, 98, 7, 62, 2, 2, 98, 99, 7, 63, 2, 2, 99, 22, 3, 2, 2, 2, 100, 101, 7, 62, 2, 2, 101, 24, 3, 2, 2, 2, 102, 103, 7, 35, 2, 2, 103, 104, 7, 63, 2, 2, 104, 26, 3, 2, 2, 2, 105, 106, 7, 112, 2, 2, 106, 107, 7, 113, 2, 2, 107, 108, 7, 118, 2, 2, 108, 28, 3, 2, 2, 2, 109, 110, 7, 113, 2, 2, 110, 111, 7, 116, 2, 2, 111, 30, 3, 2, 2, 2, 112, 113, 7, 118, 2, 2, 113, 114, 7, 116, 2, 2, 114, 115, 7, 119, 2, 2, 115, 116, 7, 103, 2, 2, 116, 32, 3, 2, 2, 2, 117, 118, 7, 104, 2, 2, 118, 119, 7, 99, 2, 2, 119, 120, 7, 110, 2, 2, 120, 121, 7, 117, 2, 2, 121, 122, 7, 103, 2, 2, 122, 34, 3, 2, 2, 2, 123, 124, 7, 112, 2, 2, 124, 125, 7, 119, 2, 2, 125, 126, 7, 110, 2, 2, 126, 127, 7, 110, 2, 2, 127, 36, 3, 2, 2, 2, 128, 129, 7, 125, 2, 2, 129, 38, 3, 2, 2, 2, 130, 131, 7, 127, 2, 2, 131, 40, 3, 2, 2, 2, 132, 133, 7, 93, 2, 2, 133, 42, 3, 2, 2, 2, 134, 135, 7, 95, 2, 2, 135, 44, 3, 2, 2, 2, 136, 137, 7, 60, 2, 2, 137, 46, 3, 2, 2, 2, 138, 139, 7, 46, 2, 2, 139, 48, 3, 2, 2, 2, 140, 141, 7, 42, 2, 2, 141, 50, 3, 2, 2, 2, 142, 143, 7, 43, 2, 2, 143, 52, 3, 2, 2, 2, 144, 145, 7, 65, 2, 2, 145, 54, 3, 2, 2, 2, 146, 150, 9, 2, 2, 2, 147, 149, 9, 3, 2, 2, 148, 147, 3, 2, 2, 2, 149, 152, 3, 2, 2, 2, 150, 148, 3, 2, 2, 2, 150, 151, 3, 2, 2, 2, 151, 56, 3, 2, 2, 2, 152, 150, 3, 2, 2, 2, 153, 158, 7, 36, 2, 2, 154, 157, 5, 59, 30, 2, 155, 157, 5, 65, 33, 2, 156, 154, 3, 2, 2, 2, 156, 155, 3, 2, 2, 2, 157, 160, 3, 2, 2, 2, 158, 156, 3, 2, 2, 2, 158, 159, 3, 2, 2, 2, 159, 161, 3, 2, 2, 2, 160, 158, 3, 2, 2, 2, 161, 162, 7, 36, 2, 2, 162, 58, 3, 2, 2, 2, 163, 166, 7, 94, 2, 2, 164, 167, 9, 4, 2, 2, 165, 167, 5, 61, 31, 2, 166, 164, 3, 2, 2, 2, 166, 165, 3, 2, 2, 2, 167, 60, 3, 2, 2, 2, 168, 169, 7, 119, 2, 2, 169, 170, 5, 63, 32, 2, 170, 171, 5, 63, 32, 2, 171, 172, 5, 63, 32, 2, 172, 173, 5, 63, 32, 2, 173, 62, 3, 2, 2, 2, 174, 175, 9, 5, 2, 2, 175, 64, 3, 2, 2, 2, 176, 177, 10, 6, 2, 2, 177, 66, 3, 2, 2, 2, 178, 180, 7, 47, 2, 2, 179, 178, 3, 2, 2, 2, 179, 180, 3, 2, 2, 2, 180, 181, 3, 2, 2, 2, 181, 188, 5, 69, 35, 2, 182, 184, 7, 48, 2, 2, 183, 185, 9, 7, 2, 2, 184, 183, 3, 2, 2, 2, 185, 186, 3, 2, 2, 2, 186, 184, 3, 2, 2, 2, 186, 187, 3, 2, 2, 2, 187, 189, 3, 2, 2, 2, 188, 182, 3, 2, 2, 2, 188, 189, 3, 2, 2, 2, 189, 191, 3, 2, 2, 2, 190, 192, 5, 71, 36, 2, 191, 190, 3, 2, 2, 2, 191, 192, 3, 2, 2, 2, 192, 68, 3, 2, 2, 2, 193, 202, 7, 50, 2, 2, 194, 198, 9, 8, 2, 2, 195, 197, 9, 7, 2, 2, 196, 195, 3, 2, 2, 2, 197, 200, 3, 2, 2, 2, 198, 196, 3, 2, 2, 2, 198, 199, 3, 2, 2, 2, 199, 202, 3, 2, 2, 2, 200, 198, 3, 2, 2, 2, 201, 193, 3, 2, 2, 2, 201, 194, 3, 2, 2, 2, 202, 70, 3, 2, 2, 2, 203, 205, 9, 9, 2, 2, 204, 206, 9, 10, 2, 2, 205, 204, 3, 2, 2, 2, 205, 206, 3, 2, 2, 2, 206, 207, 3, 2, 2, 2, 207, 208, 5, 69, 35, 2, 208, 72, 3, 2, 2, 2, 209, 211, 9, 11, 2, 2, 210, 209, 3, 2, 2, 2, 211, 212, 3, 2, 2, 2, 212, 210, 3, 2, 2, 2, 212, 213, 3, 2, 2, 2, 213, 214, 3, 2, 2, 2, 214, 215, 8, 37, 2, 2, 215, 74, 3, 2, 2, 2, 15, 2, 150, 156, 158, 166, 179, 186, 188, 191, 198, 201, 205, 212, 3, 8, 2, 2] \ No newline at end of file diff --git a/jsonpath2/parser/JSONPathLexer.py b/jsonpath2/parser/JSONPathLexer.py new file mode 100644 index 0000000..1fb2fac --- /dev/null +++ b/jsonpath2/parser/JSONPathLexer.py @@ -0,0 +1,172 @@ +# Generated from pacifica/jsonpath/parser/JSONPath.g4 by ANTLR 4.7.1 +from antlr4 import * +from io import StringIO +from typing.io import TextIO +import sys + + +def serializedATN(): + with StringIO() as buf: + buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2 ") + buf.write("\u00d8\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7") + buf.write("\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r") + buf.write("\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22\4\23") + buf.write("\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30") + buf.write("\4\31\t\31\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36") + buf.write("\t\36\4\37\t\37\4 \t \4!\t!\4\"\t\"\4#\t#\4$\t$\4%\t%") + buf.write("\3\2\3\2\3\3\3\3\3\3\3\4\3\4\3\5\3\5\3\6\3\6\3\7\3\7\3") + buf.write("\7\3\7\3\b\3\b\3\t\3\t\3\t\3\n\3\n\3\13\3\13\3\13\3\f") + buf.write("\3\f\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\17\3\17\3\17\3") + buf.write("\20\3\20\3\20\3\20\3\20\3\21\3\21\3\21\3\21\3\21\3\21") + buf.write("\3\22\3\22\3\22\3\22\3\22\3\23\3\23\3\24\3\24\3\25\3\25") + buf.write("\3\26\3\26\3\27\3\27\3\30\3\30\3\31\3\31\3\32\3\32\3\33") + buf.write("\3\33\3\34\3\34\7\34\u0095\n\34\f\34\16\34\u0098\13\34") + buf.write("\3\35\3\35\3\35\7\35\u009d\n\35\f\35\16\35\u00a0\13\35") + buf.write("\3\35\3\35\3\36\3\36\3\36\5\36\u00a7\n\36\3\37\3\37\3") + buf.write("\37\3\37\3\37\3\37\3 \3 \3!\3!\3\"\5\"\u00b4\n\"\3\"\3") + buf.write("\"\3\"\6\"\u00b9\n\"\r\"\16\"\u00ba\5\"\u00bd\n\"\3\"") + buf.write("\5\"\u00c0\n\"\3#\3#\3#\7#\u00c5\n#\f#\16#\u00c8\13#\5") + buf.write("#\u00ca\n#\3$\3$\5$\u00ce\n$\3$\3$\3%\6%\u00d3\n%\r%\16") + buf.write("%\u00d4\3%\3%\2\2&\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n") + buf.write("\23\13\25\f\27\r\31\16\33\17\35\20\37\21!\22#\23%\24\'") + buf.write("\25)\26+\27-\30/\31\61\32\63\33\65\34\67\359\36;\2=\2") + buf.write("?\2A\2C\37E\2G\2I \3\2\f\5\2C\\aac|\6\2\62;C\\aac|\n\2") + buf.write("$$\61\61^^ddhhppttvv\5\2\62;CHch\5\2\2!$$^^\3\2\62;\3") + buf.write("\2\63;\4\2GGgg\4\2--//\5\2\13\f\17\17\"\"\2\u00dd\2\3") + buf.write("\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2") + buf.write("\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2") + buf.write("\2\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2") + buf.write("\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2\2\2\2%\3") + buf.write("\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2") + buf.write("/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67") + buf.write("\3\2\2\2\29\3\2\2\2\2C\3\2\2\2\2I\3\2\2\2\3K\3\2\2\2\5") + buf.write("M\3\2\2\2\7P\3\2\2\2\tR\3\2\2\2\13T\3\2\2\2\rV\3\2\2\2") + buf.write("\17Z\3\2\2\2\21\\\3\2\2\2\23_\3\2\2\2\25a\3\2\2\2\27d") + buf.write("\3\2\2\2\31f\3\2\2\2\33i\3\2\2\2\35m\3\2\2\2\37p\3\2\2") + buf.write("\2!u\3\2\2\2#{\3\2\2\2%\u0080\3\2\2\2\'\u0082\3\2\2\2") + buf.write(")\u0084\3\2\2\2+\u0086\3\2\2\2-\u0088\3\2\2\2/\u008a\3") + buf.write("\2\2\2\61\u008c\3\2\2\2\63\u008e\3\2\2\2\65\u0090\3\2") + buf.write("\2\2\67\u0092\3\2\2\29\u0099\3\2\2\2;\u00a3\3\2\2\2=\u00a8") + buf.write("\3\2\2\2?\u00ae\3\2\2\2A\u00b0\3\2\2\2C\u00b3\3\2\2\2") + buf.write("E\u00c9\3\2\2\2G\u00cb\3\2\2\2I\u00d2\3\2\2\2KL\7B\2\2") + buf.write("L\4\3\2\2\2MN\7\60\2\2NO\7\60\2\2O\6\3\2\2\2PQ\7&\2\2") + buf.write("Q\b\3\2\2\2RS\7\60\2\2S\n\3\2\2\2TU\7,\2\2U\f\3\2\2\2") + buf.write("VW\7c\2\2WX\7p\2\2XY\7f\2\2Y\16\3\2\2\2Z[\7?\2\2[\20\3") + buf.write("\2\2\2\\]\7@\2\2]^\7?\2\2^\22\3\2\2\2_`\7@\2\2`\24\3\2") + buf.write("\2\2ab\7>\2\2bc\7?\2\2c\26\3\2\2\2de\7>\2\2e\30\3\2\2") + buf.write("\2fg\7#\2\2gh\7?\2\2h\32\3\2\2\2ij\7p\2\2jk\7q\2\2kl\7") + buf.write("v\2\2l\34\3\2\2\2mn\7q\2\2no\7t\2\2o\36\3\2\2\2pq\7v\2") + buf.write("\2qr\7t\2\2rs\7w\2\2st\7g\2\2t \3\2\2\2uv\7h\2\2vw\7c") + buf.write("\2\2wx\7n\2\2xy\7u\2\2yz\7g\2\2z\"\3\2\2\2{|\7p\2\2|}") + buf.write("\7w\2\2}~\7n\2\2~\177\7n\2\2\177$\3\2\2\2\u0080\u0081") + buf.write("\7}\2\2\u0081&\3\2\2\2\u0082\u0083\7\177\2\2\u0083(\3") + buf.write("\2\2\2\u0084\u0085\7]\2\2\u0085*\3\2\2\2\u0086\u0087\7") + buf.write("_\2\2\u0087,\3\2\2\2\u0088\u0089\7<\2\2\u0089.\3\2\2\2") + buf.write("\u008a\u008b\7.\2\2\u008b\60\3\2\2\2\u008c\u008d\7*\2") + buf.write("\2\u008d\62\3\2\2\2\u008e\u008f\7+\2\2\u008f\64\3\2\2") + buf.write("\2\u0090\u0091\7A\2\2\u0091\66\3\2\2\2\u0092\u0096\t\2") + buf.write("\2\2\u0093\u0095\t\3\2\2\u0094\u0093\3\2\2\2\u0095\u0098") + buf.write("\3\2\2\2\u0096\u0094\3\2\2\2\u0096\u0097\3\2\2\2\u0097") + buf.write("8\3\2\2\2\u0098\u0096\3\2\2\2\u0099\u009e\7$\2\2\u009a") + buf.write("\u009d\5;\36\2\u009b\u009d\5A!\2\u009c\u009a\3\2\2\2\u009c") + buf.write("\u009b\3\2\2\2\u009d\u00a0\3\2\2\2\u009e\u009c\3\2\2\2") + buf.write("\u009e\u009f\3\2\2\2\u009f\u00a1\3\2\2\2\u00a0\u009e\3") + buf.write("\2\2\2\u00a1\u00a2\7$\2\2\u00a2:\3\2\2\2\u00a3\u00a6\7") + buf.write("^\2\2\u00a4\u00a7\t\4\2\2\u00a5\u00a7\5=\37\2\u00a6\u00a4") + buf.write("\3\2\2\2\u00a6\u00a5\3\2\2\2\u00a7<\3\2\2\2\u00a8\u00a9") + buf.write("\7w\2\2\u00a9\u00aa\5? \2\u00aa\u00ab\5? \2\u00ab\u00ac") + buf.write("\5? \2\u00ac\u00ad\5? \2\u00ad>\3\2\2\2\u00ae\u00af\t") + buf.write("\5\2\2\u00af@\3\2\2\2\u00b0\u00b1\n\6\2\2\u00b1B\3\2\2") + buf.write("\2\u00b2\u00b4\7/\2\2\u00b3\u00b2\3\2\2\2\u00b3\u00b4") + buf.write("\3\2\2\2\u00b4\u00b5\3\2\2\2\u00b5\u00bc\5E#\2\u00b6\u00b8") + buf.write("\7\60\2\2\u00b7\u00b9\t\7\2\2\u00b8\u00b7\3\2\2\2\u00b9") + buf.write("\u00ba\3\2\2\2\u00ba\u00b8\3\2\2\2\u00ba\u00bb\3\2\2\2") + buf.write("\u00bb\u00bd\3\2\2\2\u00bc\u00b6\3\2\2\2\u00bc\u00bd\3") + buf.write("\2\2\2\u00bd\u00bf\3\2\2\2\u00be\u00c0\5G$\2\u00bf\u00be") + buf.write("\3\2\2\2\u00bf\u00c0\3\2\2\2\u00c0D\3\2\2\2\u00c1\u00ca") + buf.write("\7\62\2\2\u00c2\u00c6\t\b\2\2\u00c3\u00c5\t\7\2\2\u00c4") + buf.write("\u00c3\3\2\2\2\u00c5\u00c8\3\2\2\2\u00c6\u00c4\3\2\2\2") + buf.write("\u00c6\u00c7\3\2\2\2\u00c7\u00ca\3\2\2\2\u00c8\u00c6\3") + buf.write("\2\2\2\u00c9\u00c1\3\2\2\2\u00c9\u00c2\3\2\2\2\u00caF") + buf.write("\3\2\2\2\u00cb\u00cd\t\t\2\2\u00cc\u00ce\t\n\2\2\u00cd") + buf.write("\u00cc\3\2\2\2\u00cd\u00ce\3\2\2\2\u00ce\u00cf\3\2\2\2") + buf.write("\u00cf\u00d0\5E#\2\u00d0H\3\2\2\2\u00d1\u00d3\t\13\2\2") + buf.write("\u00d2\u00d1\3\2\2\2\u00d3\u00d4\3\2\2\2\u00d4\u00d2\3") + buf.write("\2\2\2\u00d4\u00d5\3\2\2\2\u00d5\u00d6\3\2\2\2\u00d6\u00d7") + buf.write("\b%\2\2\u00d7J\3\2\2\2\17\2\u0096\u009c\u009e\u00a6\u00b3") + buf.write("\u00ba\u00bc\u00bf\u00c6\u00c9\u00cd\u00d4\3\b\2\2") + return buf.getvalue() + + +class JSONPathLexer(Lexer): + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] + + CURRENT_VALUE = 1 + RECURSIVE_DESCENT = 2 + ROOT_VALUE = 3 + SUBSCRIPT = 4 + WILDCARD_SUBSCRIPT = 5 + AND = 6 + EQ = 7 + GE = 8 + GT = 9 + LE = 10 + LT = 11 + NE = 12 + NOT = 13 + OR = 14 + TRUE = 15 + FALSE = 16 + NULL = 17 + BRACE_LEFT = 18 + BRACE_RIGHT = 19 + BRACKET_LEFT = 20 + BRACKET_RIGHT = 21 + COLON = 22 + COMMA = 23 + PAREN_LEFT = 24 + PAREN_RIGHT = 25 + QUESTION = 26 + ID = 27 + STRING = 28 + NUMBER = 29 + WS = 30 + + channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ] + + modeNames = [ "DEFAULT_MODE" ] + + literalNames = [ "", + "'@'", "'..'", "'$'", "'.'", "'*'", "'and'", "'='", "'>='", + "'>'", "'<='", "'<'", "'!='", "'not'", "'or'", "'true'", "'false'", + "'null'", "'{'", "'}'", "'['", "']'", "':'", "','", "'('", "')'", + "'?'" ] + + symbolicNames = [ "", + "CURRENT_VALUE", "RECURSIVE_DESCENT", "ROOT_VALUE", "SUBSCRIPT", + "WILDCARD_SUBSCRIPT", "AND", "EQ", "GE", "GT", "LE", "LT", "NE", + "NOT", "OR", "TRUE", "FALSE", "NULL", "BRACE_LEFT", "BRACE_RIGHT", + "BRACKET_LEFT", "BRACKET_RIGHT", "COLON", "COMMA", "PAREN_LEFT", + "PAREN_RIGHT", "QUESTION", "ID", "STRING", "NUMBER", "WS" ] + + ruleNames = [ "CURRENT_VALUE", "RECURSIVE_DESCENT", "ROOT_VALUE", "SUBSCRIPT", + "WILDCARD_SUBSCRIPT", "AND", "EQ", "GE", "GT", "LE", "LT", + "NE", "NOT", "OR", "TRUE", "FALSE", "NULL", "BRACE_LEFT", + "BRACE_RIGHT", "BRACKET_LEFT", "BRACKET_RIGHT", "COLON", + "COMMA", "PAREN_LEFT", "PAREN_RIGHT", "QUESTION", "ID", + "STRING", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "NUMBER", + "INT", "EXP", "WS" ] + + grammarFileName = "JSONPath.g4" + + def __init__(self, input=None, output:TextIO = sys.stdout): + super().__init__(input, output) + self.checkVersion("4.7.1") + self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache()) + self._actions = None + self._predicates = None + + diff --git a/jsonpath2/parser/JSONPathLexer.tokens b/jsonpath2/parser/JSONPathLexer.tokens new file mode 100644 index 0000000..37a8444 --- /dev/null +++ b/jsonpath2/parser/JSONPathLexer.tokens @@ -0,0 +1,56 @@ +CURRENT_VALUE=1 +RECURSIVE_DESCENT=2 +ROOT_VALUE=3 +SUBSCRIPT=4 +WILDCARD_SUBSCRIPT=5 +AND=6 +EQ=7 +GE=8 +GT=9 +LE=10 +LT=11 +NE=12 +NOT=13 +OR=14 +TRUE=15 +FALSE=16 +NULL=17 +BRACE_LEFT=18 +BRACE_RIGHT=19 +BRACKET_LEFT=20 +BRACKET_RIGHT=21 +COLON=22 +COMMA=23 +PAREN_LEFT=24 +PAREN_RIGHT=25 +QUESTION=26 +ID=27 +STRING=28 +NUMBER=29 +WS=30 +'@'=1 +'..'=2 +'$'=3 +'.'=4 +'*'=5 +'and'=6 +'='=7 +'>='=8 +'>'=9 +'<='=10 +'<'=11 +'!='=12 +'not'=13 +'or'=14 +'true'=15 +'false'=16 +'null'=17 +'{'=18 +'}'=19 +'['=20 +']'=21 +':'=22 +','=23 +'('=24 +')'=25 +'?'=26 diff --git a/jsonpath2/parser/JSONPathListener.py b/jsonpath2/parser/JSONPathListener.py new file mode 100644 index 0000000..a4edb5e --- /dev/null +++ b/jsonpath2/parser/JSONPathListener.py @@ -0,0 +1,136 @@ +# Generated from pacifica/jsonpath/parser/JSONPath.g4 by ANTLR 4.7.1 +from antlr4 import * +if __name__ is not None and "." in __name__: + from .JSONPathParser import JSONPathParser +else: + from JSONPathParser import JSONPathParser + +# This class defines a complete listener for a parse tree produced by JSONPathParser. +class JSONPathListener(ParseTreeListener): + + # Enter a parse tree produced by JSONPathParser#jsonpath. + def enterJsonpath(self, ctx:JSONPathParser.JsonpathContext): + pass + + # Exit a parse tree produced by JSONPathParser#jsonpath. + def exitJsonpath(self, ctx:JSONPathParser.JsonpathContext): + pass + + + # Enter a parse tree produced by JSONPathParser#subscript. + def enterSubscript(self, ctx:JSONPathParser.SubscriptContext): + pass + + # Exit a parse tree produced by JSONPathParser#subscript. + def exitSubscript(self, ctx:JSONPathParser.SubscriptContext): + pass + + + # Enter a parse tree produced by JSONPathParser#subscriptables. + def enterSubscriptables(self, ctx:JSONPathParser.SubscriptablesContext): + pass + + # Exit a parse tree produced by JSONPathParser#subscriptables. + def exitSubscriptables(self, ctx:JSONPathParser.SubscriptablesContext): + pass + + + # Enter a parse tree produced by JSONPathParser#subscriptableBareword. + def enterSubscriptableBareword(self, ctx:JSONPathParser.SubscriptableBarewordContext): + pass + + # Exit a parse tree produced by JSONPathParser#subscriptableBareword. + def exitSubscriptableBareword(self, ctx:JSONPathParser.SubscriptableBarewordContext): + pass + + + # Enter a parse tree produced by JSONPathParser#subscriptable. + def enterSubscriptable(self, ctx:JSONPathParser.SubscriptableContext): + pass + + # Exit a parse tree produced by JSONPathParser#subscriptable. + def exitSubscriptable(self, ctx:JSONPathParser.SubscriptableContext): + pass + + + # Enter a parse tree produced by JSONPathParser#expression. + def enterExpression(self, ctx:JSONPathParser.ExpressionContext): + pass + + # Exit a parse tree produced by JSONPathParser#expression. + def exitExpression(self, ctx:JSONPathParser.ExpressionContext): + pass + + + # Enter a parse tree produced by JSONPathParser#andExpression. + def enterAndExpression(self, ctx:JSONPathParser.AndExpressionContext): + pass + + # Exit a parse tree produced by JSONPathParser#andExpression. + def exitAndExpression(self, ctx:JSONPathParser.AndExpressionContext): + pass + + + # Enter a parse tree produced by JSONPathParser#orExpression. + def enterOrExpression(self, ctx:JSONPathParser.OrExpressionContext): + pass + + # Exit a parse tree produced by JSONPathParser#orExpression. + def exitOrExpression(self, ctx:JSONPathParser.OrExpressionContext): + pass + + + # Enter a parse tree produced by JSONPathParser#notExpression. + def enterNotExpression(self, ctx:JSONPathParser.NotExpressionContext): + pass + + # Exit a parse tree produced by JSONPathParser#notExpression. + def exitNotExpression(self, ctx:JSONPathParser.NotExpressionContext): + pass + + + # Enter a parse tree produced by JSONPathParser#json. + def enterJson(self, ctx:JSONPathParser.JsonContext): + pass + + # Exit a parse tree produced by JSONPathParser#json. + def exitJson(self, ctx:JSONPathParser.JsonContext): + pass + + + # Enter a parse tree produced by JSONPathParser#obj. + def enterObj(self, ctx:JSONPathParser.ObjContext): + pass + + # Exit a parse tree produced by JSONPathParser#obj. + def exitObj(self, ctx:JSONPathParser.ObjContext): + pass + + + # Enter a parse tree produced by JSONPathParser#pair. + def enterPair(self, ctx:JSONPathParser.PairContext): + pass + + # Exit a parse tree produced by JSONPathParser#pair. + def exitPair(self, ctx:JSONPathParser.PairContext): + pass + + + # Enter a parse tree produced by JSONPathParser#array. + def enterArray(self, ctx:JSONPathParser.ArrayContext): + pass + + # Exit a parse tree produced by JSONPathParser#array. + def exitArray(self, ctx:JSONPathParser.ArrayContext): + pass + + + # Enter a parse tree produced by JSONPathParser#value. + def enterValue(self, ctx:JSONPathParser.ValueContext): + pass + + # Exit a parse tree produced by JSONPathParser#value. + def exitValue(self, ctx:JSONPathParser.ValueContext): + pass + + diff --git a/jsonpath2/parser/JSONPathParser.py b/jsonpath2/parser/JSONPathParser.py new file mode 100644 index 0000000..76ccdf6 --- /dev/null +++ b/jsonpath2/parser/JSONPathParser.py @@ -0,0 +1,1264 @@ +# Generated from pacifica/jsonpath/parser/JSONPath.g4 by ANTLR 4.7.1 +# encoding: utf-8 +from antlr4 import * +from io import StringIO +from typing.io import TextIO +import sys + +def serializedATN(): + with StringIO() as buf: + buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3 ") + buf.write("\u00a5\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") + buf.write("\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16") + buf.write("\t\16\4\17\t\17\3\2\3\2\5\2!\n\2\3\2\3\2\3\3\3\3\3\3\5") + buf.write("\3(\n\3\3\3\5\3+\n\3\3\3\3\3\3\3\5\3\60\n\3\3\3\3\3\5") + buf.write("\3\64\n\3\5\3\66\n\3\3\4\3\4\3\4\3\4\7\4<\n\4\f\4\16\4") + buf.write("?\13\4\3\4\3\4\3\5\3\5\3\6\3\6\3\6\3\6\3\6\3\6\5\6K\n") + buf.write("\6\3\6\3\6\3\6\5\6P\n\6\5\6R\n\6\3\6\3\6\3\6\3\6\3\6\3") + buf.write("\6\5\6Z\n\6\3\7\3\7\3\b\3\b\3\b\5\ba\n\b\3\t\3\t\3\t\5") + buf.write("\tf\n\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\5\np\n\n\3\n\3") + buf.write("\n\5\nt\n\n\5\nv\n\n\3\13\3\13\3\f\3\f\3\f\3\f\7\f~\n") + buf.write("\f\f\f\16\f\u0081\13\f\3\f\3\f\3\f\3\f\5\f\u0087\n\f\3") + buf.write("\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\7\16\u0091\n\16\f\16") + buf.write("\16\16\u0094\13\16\3\16\3\16\3\16\3\16\5\16\u009a\n\16") + buf.write("\3\17\3\17\3\17\3\17\3\17\3\17\3\17\5\17\u00a3\n\17\3") + buf.write("\17\2\2\20\2\4\6\b\n\f\16\20\22\24\26\30\32\34\2\5\4\2") + buf.write("\7\7\35\35\4\2\3\3\5\5\3\2\t\16\2\u00b4\2\36\3\2\2\2\4") + buf.write("\65\3\2\2\2\6\67\3\2\2\2\bB\3\2\2\2\nY\3\2\2\2\f[\3\2") + buf.write("\2\2\16]\3\2\2\2\20b\3\2\2\2\22u\3\2\2\2\24w\3\2\2\2\26") + buf.write("\u0086\3\2\2\2\30\u0088\3\2\2\2\32\u0099\3\2\2\2\34\u00a2") + buf.write("\3\2\2\2\36 \7\5\2\2\37!\5\4\3\2 \37\3\2\2\2 !\3\2\2\2") + buf.write("!\"\3\2\2\2\"#\7\2\2\3#\3\3\2\2\2$\'\7\4\2\2%(\5\b\5\2") + buf.write("&(\5\6\4\2\'%\3\2\2\2\'&\3\2\2\2(*\3\2\2\2)+\5\4\3\2*") + buf.write(")\3\2\2\2*+\3\2\2\2+\66\3\2\2\2,-\7\6\2\2-/\5\b\5\2.\60") + buf.write("\5\4\3\2/.\3\2\2\2/\60\3\2\2\2\60\66\3\2\2\2\61\63\5\6") + buf.write("\4\2\62\64\5\4\3\2\63\62\3\2\2\2\63\64\3\2\2\2\64\66\3") + buf.write("\2\2\2\65$\3\2\2\2\65,\3\2\2\2\65\61\3\2\2\2\66\5\3\2") + buf.write("\2\2\678\7\26\2\28=\5\n\6\29:\7\31\2\2:<\5\n\6\2;9\3\2") + buf.write("\2\2\3\2\2\2>@\3\2\2\2?=\3\2\2\2") + buf.write("@A\7\27\2\2A\7\3\2\2\2BC\t\2\2\2C\t\3\2\2\2DZ\7\36\2\2") + buf.write("EF\7\37\2\2FQ\6\6\2\2GJ\7\30\2\2HI\7\37\2\2IK\6\6\3\2") + buf.write("JH\3\2\2\2JK\3\2\2\2KO\3\2\2\2LM\7\30\2\2MN\7\37\2\2N") + buf.write("P\6\6\4\2OL\3\2\2\2OP\3\2\2\2PR\3\2\2\2QG\3\2\2\2QR\3") + buf.write("\2\2\2RZ\3\2\2\2SZ\7\7\2\2TU\7\34\2\2UV\7\32\2\2VW\5\f") + buf.write("\7\2WX\7\33\2\2XZ\3\2\2\2YD\3\2\2\2YE\3\2\2\2YS\3\2\2") + buf.write("\2YT\3\2\2\2Z\13\3\2\2\2[\\\5\16\b\2\\\r\3\2\2\2]`\5\20") + buf.write("\t\2^_\7\b\2\2_a\5\16\b\2`^\3\2\2\2`a\3\2\2\2a\17\3\2") + buf.write("\2\2be\5\22\n\2cd\7\20\2\2df\5\20\t\2ec\3\2\2\2ef\3\2") + buf.write("\2\2f\21\3\2\2\2gh\7\17\2\2hv\5\22\n\2ij\7\32\2\2jk\5") + buf.write("\f\7\2kl\7\33\2\2lv\3\2\2\2mo\t\3\2\2np\5\4\3\2on\3\2") + buf.write("\2\2op\3\2\2\2ps\3\2\2\2qr\t\4\2\2rt\5\34\17\2sq\3\2\2") + buf.write("\2st\3\2\2\2tv\3\2\2\2ug\3\2\2\2ui\3\2\2\2um\3\2\2\2v") + buf.write("\23\3\2\2\2wx\5\34\17\2x\25\3\2\2\2yz\7\24\2\2z\177\5") + buf.write("\30\r\2{|\7\31\2\2|~\5\30\r\2}{\3\2\2\2~\u0081\3\2\2\2") + buf.write("\177}\3\2\2\2\177\u0080\3\2\2\2\u0080\u0082\3\2\2\2\u0081") + buf.write("\177\3\2\2\2\u0082\u0083\7\25\2\2\u0083\u0087\3\2\2\2") + buf.write("\u0084\u0085\7\24\2\2\u0085\u0087\7\25\2\2\u0086y\3\2") + buf.write("\2\2\u0086\u0084\3\2\2\2\u0087\27\3\2\2\2\u0088\u0089") + buf.write("\7\36\2\2\u0089\u008a\7\30\2\2\u008a\u008b\5\34\17\2\u008b") + buf.write("\31\3\2\2\2\u008c\u008d\7\26\2\2\u008d\u0092\5\34\17\2") + buf.write("\u008e\u008f\7\31\2\2\u008f\u0091\5\34\17\2\u0090\u008e") + buf.write("\3\2\2\2\u0091\u0094\3\2\2\2\u0092\u0090\3\2\2\2\u0092") + buf.write("\u0093\3\2\2\2\u0093\u0095\3\2\2\2\u0094\u0092\3\2\2\2") + buf.write("\u0095\u0096\7\27\2\2\u0096\u009a\3\2\2\2\u0097\u0098") + buf.write("\7\26\2\2\u0098\u009a\7\27\2\2\u0099\u008c\3\2\2\2\u0099") + buf.write("\u0097\3\2\2\2\u009a\33\3\2\2\2\u009b\u00a3\7\36\2\2\u009c") + buf.write("\u00a3\7\37\2\2\u009d\u00a3\5\26\f\2\u009e\u00a3\5\32") + buf.write("\16\2\u009f\u00a3\7\21\2\2\u00a0\u00a3\7\22\2\2\u00a1") + buf.write("\u00a3\7\23\2\2\u00a2\u009b\3\2\2\2\u00a2\u009c\3\2\2") + buf.write("\2\u00a2\u009d\3\2\2\2\u00a2\u009e\3\2\2\2\u00a2\u009f") + buf.write("\3\2\2\2\u00a2\u00a0\3\2\2\2\u00a2\u00a1\3\2\2\2\u00a3") + buf.write("\35\3\2\2\2\27 \'*/\63\65=JOQY`eosu\177\u0086\u0092\u0099") + buf.write("\u00a2") + return buf.getvalue() + + +class JSONPathParser ( Parser ): + + grammarFileName = "JSONPath.g4" + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] + + sharedContextCache = PredictionContextCache() + + literalNames = [ "", "'@'", "'..'", "'$'", "'.'", "'*'", "'and'", + "'='", "'>='", "'>'", "'<='", "'<'", "'!='", "'not'", + "'or'", "'true'", "'false'", "'null'", "'{'", "'}'", + "'['", "']'", "':'", "','", "'('", "')'", "'?'" ] + + symbolicNames = [ "", "CURRENT_VALUE", "RECURSIVE_DESCENT", + "ROOT_VALUE", "SUBSCRIPT", "WILDCARD_SUBSCRIPT", "AND", + "EQ", "GE", "GT", "LE", "LT", "NE", "NOT", "OR", "TRUE", + "FALSE", "NULL", "BRACE_LEFT", "BRACE_RIGHT", "BRACKET_LEFT", + "BRACKET_RIGHT", "COLON", "COMMA", "PAREN_LEFT", "PAREN_RIGHT", + "QUESTION", "ID", "STRING", "NUMBER", "WS" ] + + RULE_jsonpath = 0 + RULE_subscript = 1 + RULE_subscriptables = 2 + RULE_subscriptableBareword = 3 + RULE_subscriptable = 4 + RULE_expression = 5 + RULE_andExpression = 6 + RULE_orExpression = 7 + RULE_notExpression = 8 + RULE_json = 9 + RULE_obj = 10 + RULE_pair = 11 + RULE_array = 12 + RULE_value = 13 + + ruleNames = [ "jsonpath", "subscript", "subscriptables", "subscriptableBareword", + "subscriptable", "expression", "andExpression", "orExpression", + "notExpression", "json", "obj", "pair", "array", "value" ] + + EOF = Token.EOF + CURRENT_VALUE=1 + RECURSIVE_DESCENT=2 + ROOT_VALUE=3 + SUBSCRIPT=4 + WILDCARD_SUBSCRIPT=5 + AND=6 + EQ=7 + GE=8 + GT=9 + LE=10 + LT=11 + NE=12 + NOT=13 + OR=14 + TRUE=15 + FALSE=16 + NULL=17 + BRACE_LEFT=18 + BRACE_RIGHT=19 + BRACKET_LEFT=20 + BRACKET_RIGHT=21 + COLON=22 + COMMA=23 + PAREN_LEFT=24 + PAREN_RIGHT=25 + QUESTION=26 + ID=27 + STRING=28 + NUMBER=29 + WS=30 + + def __init__(self, input:TokenStream, output:TextIO = sys.stdout): + super().__init__(input, output) + self.checkVersion("4.7.1") + self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache) + self._predicates = None + + + + class JsonpathContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ROOT_VALUE(self): + return self.getToken(JSONPathParser.ROOT_VALUE, 0) + + def EOF(self): + return self.getToken(JSONPathParser.EOF, 0) + + def subscript(self): + return self.getTypedRuleContext(JSONPathParser.SubscriptContext,0) + + + def getRuleIndex(self): + return JSONPathParser.RULE_jsonpath + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterJsonpath" ): + listener.enterJsonpath(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitJsonpath" ): + listener.exitJsonpath(self) + + + + + def jsonpath(self): + + localctx = JSONPathParser.JsonpathContext(self, self._ctx, self.state) + self.enterRule(localctx, 0, self.RULE_jsonpath) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 28 + self.match(JSONPathParser.ROOT_VALUE) + self.state = 30 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): + self.state = 29 + self.subscript() + + + self.state = 32 + self.match(JSONPathParser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class SubscriptContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def RECURSIVE_DESCENT(self): + return self.getToken(JSONPathParser.RECURSIVE_DESCENT, 0) + + def subscriptableBareword(self): + return self.getTypedRuleContext(JSONPathParser.SubscriptableBarewordContext,0) + + + def subscriptables(self): + return self.getTypedRuleContext(JSONPathParser.SubscriptablesContext,0) + + + def subscript(self): + return self.getTypedRuleContext(JSONPathParser.SubscriptContext,0) + + + def SUBSCRIPT(self): + return self.getToken(JSONPathParser.SUBSCRIPT, 0) + + def getRuleIndex(self): + return JSONPathParser.RULE_subscript + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSubscript" ): + listener.enterSubscript(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSubscript" ): + listener.exitSubscript(self) + + + + + def subscript(self): + + localctx = JSONPathParser.SubscriptContext(self, self._ctx, self.state) + self.enterRule(localctx, 2, self.RULE_subscript) + self._la = 0 # Token type + try: + self.state = 51 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [JSONPathParser.RECURSIVE_DESCENT]: + self.enterOuterAlt(localctx, 1) + self.state = 34 + self.match(JSONPathParser.RECURSIVE_DESCENT) + self.state = 37 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [JSONPathParser.WILDCARD_SUBSCRIPT, JSONPathParser.ID]: + self.state = 35 + self.subscriptableBareword() + pass + elif token in [JSONPathParser.BRACKET_LEFT]: + self.state = 36 + self.subscriptables() + pass + else: + raise NoViableAltException(self) + + self.state = 40 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): + self.state = 39 + self.subscript() + + + pass + elif token in [JSONPathParser.SUBSCRIPT]: + self.enterOuterAlt(localctx, 2) + self.state = 42 + self.match(JSONPathParser.SUBSCRIPT) + self.state = 43 + self.subscriptableBareword() + self.state = 45 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): + self.state = 44 + self.subscript() + + + pass + elif token in [JSONPathParser.BRACKET_LEFT]: + self.enterOuterAlt(localctx, 3) + self.state = 47 + self.subscriptables() + self.state = 49 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): + self.state = 48 + self.subscript() + + + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class SubscriptablesContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def BRACKET_LEFT(self): + return self.getToken(JSONPathParser.BRACKET_LEFT, 0) + + def subscriptable(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(JSONPathParser.SubscriptableContext) + else: + return self.getTypedRuleContext(JSONPathParser.SubscriptableContext,i) + + + def BRACKET_RIGHT(self): + return self.getToken(JSONPathParser.BRACKET_RIGHT, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(JSONPathParser.COMMA) + else: + return self.getToken(JSONPathParser.COMMA, i) + + def getRuleIndex(self): + return JSONPathParser.RULE_subscriptables + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSubscriptables" ): + listener.enterSubscriptables(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSubscriptables" ): + listener.exitSubscriptables(self) + + + + + def subscriptables(self): + + localctx = JSONPathParser.SubscriptablesContext(self, self._ctx, self.state) + self.enterRule(localctx, 4, self.RULE_subscriptables) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 53 + self.match(JSONPathParser.BRACKET_LEFT) + self.state = 54 + self.subscriptable() + self.state = 59 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==JSONPathParser.COMMA: + self.state = 55 + self.match(JSONPathParser.COMMA) + self.state = 56 + self.subscriptable() + self.state = 61 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 62 + self.match(JSONPathParser.BRACKET_RIGHT) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class SubscriptableBarewordContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ID(self): + return self.getToken(JSONPathParser.ID, 0) + + def WILDCARD_SUBSCRIPT(self): + return self.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) + + def getRuleIndex(self): + return JSONPathParser.RULE_subscriptableBareword + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSubscriptableBareword" ): + listener.enterSubscriptableBareword(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSubscriptableBareword" ): + listener.exitSubscriptableBareword(self) + + + + + def subscriptableBareword(self): + + localctx = JSONPathParser.SubscriptableBarewordContext(self, self._ctx, self.state) + self.enterRule(localctx, 6, self.RULE_subscriptableBareword) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 64 + _la = self._input.LA(1) + if not(_la==JSONPathParser.WILDCARD_SUBSCRIPT or _la==JSONPathParser.ID): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class SubscriptableContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def STRING(self): + return self.getToken(JSONPathParser.STRING, 0) + + def NUMBER(self, i:int=None): + if i is None: + return self.getTokens(JSONPathParser.NUMBER) + else: + return self.getToken(JSONPathParser.NUMBER, i) + + def COLON(self, i:int=None): + if i is None: + return self.getTokens(JSONPathParser.COLON) + else: + return self.getToken(JSONPathParser.COLON, i) + + def WILDCARD_SUBSCRIPT(self): + return self.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) + + def QUESTION(self): + return self.getToken(JSONPathParser.QUESTION, 0) + + def PAREN_LEFT(self): + return self.getToken(JSONPathParser.PAREN_LEFT, 0) + + def expression(self): + return self.getTypedRuleContext(JSONPathParser.ExpressionContext,0) + + + def PAREN_RIGHT(self): + return self.getToken(JSONPathParser.PAREN_RIGHT, 0) + + def getRuleIndex(self): + return JSONPathParser.RULE_subscriptable + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSubscriptable" ): + listener.enterSubscriptable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSubscriptable" ): + listener.exitSubscriptable(self) + + + + + def subscriptable(self): + + localctx = JSONPathParser.SubscriptableContext(self, self._ctx, self.state) + self.enterRule(localctx, 8, self.RULE_subscriptable) + self._la = 0 # Token type + try: + self.state = 87 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [JSONPathParser.STRING]: + self.enterOuterAlt(localctx, 1) + self.state = 66 + self.match(JSONPathParser.STRING) + pass + elif token in [JSONPathParser.NUMBER]: + self.enterOuterAlt(localctx, 2) + self.state = 67 + self.match(JSONPathParser.NUMBER) + self.state = 68 + if not self.tryCast(int): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.tryCast(int)") + self.state = 79 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==JSONPathParser.COLON: + self.state = 69 + self.match(JSONPathParser.COLON) + self.state = 72 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==JSONPathParser.NUMBER: + self.state = 70 + self.match(JSONPathParser.NUMBER) + self.state = 71 + if not self.tryCast(int): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.tryCast(int)") + + + self.state = 77 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==JSONPathParser.COLON: + self.state = 74 + self.match(JSONPathParser.COLON) + self.state = 75 + self.match(JSONPathParser.NUMBER) + self.state = 76 + if not self.tryCast(int): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.tryCast(int)") + + + + + pass + elif token in [JSONPathParser.WILDCARD_SUBSCRIPT]: + self.enterOuterAlt(localctx, 3) + self.state = 81 + self.match(JSONPathParser.WILDCARD_SUBSCRIPT) + pass + elif token in [JSONPathParser.QUESTION]: + self.enterOuterAlt(localctx, 4) + self.state = 82 + self.match(JSONPathParser.QUESTION) + self.state = 83 + self.match(JSONPathParser.PAREN_LEFT) + self.state = 84 + self.expression() + self.state = 85 + self.match(JSONPathParser.PAREN_RIGHT) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class ExpressionContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def andExpression(self): + return self.getTypedRuleContext(JSONPathParser.AndExpressionContext,0) + + + def getRuleIndex(self): + return JSONPathParser.RULE_expression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExpression" ): + listener.enterExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExpression" ): + listener.exitExpression(self) + + + + + def expression(self): + + localctx = JSONPathParser.ExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 10, self.RULE_expression) + try: + self.enterOuterAlt(localctx, 1) + self.state = 89 + self.andExpression() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class AndExpressionContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def orExpression(self): + return self.getTypedRuleContext(JSONPathParser.OrExpressionContext,0) + + + def AND(self): + return self.getToken(JSONPathParser.AND, 0) + + def andExpression(self): + return self.getTypedRuleContext(JSONPathParser.AndExpressionContext,0) + + + def getRuleIndex(self): + return JSONPathParser.RULE_andExpression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAndExpression" ): + listener.enterAndExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAndExpression" ): + listener.exitAndExpression(self) + + + + + def andExpression(self): + + localctx = JSONPathParser.AndExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 12, self.RULE_andExpression) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 91 + self.orExpression() + self.state = 94 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==JSONPathParser.AND: + self.state = 92 + self.match(JSONPathParser.AND) + self.state = 93 + self.andExpression() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class OrExpressionContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def notExpression(self): + return self.getTypedRuleContext(JSONPathParser.NotExpressionContext,0) + + + def OR(self): + return self.getToken(JSONPathParser.OR, 0) + + def orExpression(self): + return self.getTypedRuleContext(JSONPathParser.OrExpressionContext,0) + + + def getRuleIndex(self): + return JSONPathParser.RULE_orExpression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterOrExpression" ): + listener.enterOrExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitOrExpression" ): + listener.exitOrExpression(self) + + + + + def orExpression(self): + + localctx = JSONPathParser.OrExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 14, self.RULE_orExpression) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 96 + self.notExpression() + self.state = 99 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==JSONPathParser.OR: + self.state = 97 + self.match(JSONPathParser.OR) + self.state = 98 + self.orExpression() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class NotExpressionContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def NOT(self): + return self.getToken(JSONPathParser.NOT, 0) + + def notExpression(self): + return self.getTypedRuleContext(JSONPathParser.NotExpressionContext,0) + + + def PAREN_LEFT(self): + return self.getToken(JSONPathParser.PAREN_LEFT, 0) + + def expression(self): + return self.getTypedRuleContext(JSONPathParser.ExpressionContext,0) + + + def PAREN_RIGHT(self): + return self.getToken(JSONPathParser.PAREN_RIGHT, 0) + + def ROOT_VALUE(self): + return self.getToken(JSONPathParser.ROOT_VALUE, 0) + + def CURRENT_VALUE(self): + return self.getToken(JSONPathParser.CURRENT_VALUE, 0) + + def subscript(self): + return self.getTypedRuleContext(JSONPathParser.SubscriptContext,0) + + + def value(self): + return self.getTypedRuleContext(JSONPathParser.ValueContext,0) + + + def EQ(self): + return self.getToken(JSONPathParser.EQ, 0) + + def NE(self): + return self.getToken(JSONPathParser.NE, 0) + + def LT(self): + return self.getToken(JSONPathParser.LT, 0) + + def LE(self): + return self.getToken(JSONPathParser.LE, 0) + + def GT(self): + return self.getToken(JSONPathParser.GT, 0) + + def GE(self): + return self.getToken(JSONPathParser.GE, 0) + + def getRuleIndex(self): + return JSONPathParser.RULE_notExpression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNotExpression" ): + listener.enterNotExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNotExpression" ): + listener.exitNotExpression(self) + + + + + def notExpression(self): + + localctx = JSONPathParser.NotExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 16, self.RULE_notExpression) + self._la = 0 # Token type + try: + self.state = 115 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [JSONPathParser.NOT]: + self.enterOuterAlt(localctx, 1) + self.state = 101 + self.match(JSONPathParser.NOT) + self.state = 102 + self.notExpression() + pass + elif token in [JSONPathParser.PAREN_LEFT]: + self.enterOuterAlt(localctx, 2) + self.state = 103 + self.match(JSONPathParser.PAREN_LEFT) + self.state = 104 + self.expression() + self.state = 105 + self.match(JSONPathParser.PAREN_RIGHT) + pass + elif token in [JSONPathParser.CURRENT_VALUE, JSONPathParser.ROOT_VALUE]: + self.enterOuterAlt(localctx, 3) + self.state = 107 + _la = self._input.LA(1) + if not(_la==JSONPathParser.CURRENT_VALUE or _la==JSONPathParser.ROOT_VALUE): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 109 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): + self.state = 108 + self.subscript() + + + self.state = 113 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.EQ) | (1 << JSONPathParser.GE) | (1 << JSONPathParser.GT) | (1 << JSONPathParser.LE) | (1 << JSONPathParser.LT) | (1 << JSONPathParser.NE))) != 0): + self.state = 111 + _la = self._input.LA(1) + if not((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.EQ) | (1 << JSONPathParser.GE) | (1 << JSONPathParser.GT) | (1 << JSONPathParser.LE) | (1 << JSONPathParser.LT) | (1 << JSONPathParser.NE))) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 112 + self.value() + + + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class JsonContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def value(self): + return self.getTypedRuleContext(JSONPathParser.ValueContext,0) + + + def getRuleIndex(self): + return JSONPathParser.RULE_json + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterJson" ): + listener.enterJson(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitJson" ): + listener.exitJson(self) + + + + + def json(self): + + localctx = JSONPathParser.JsonContext(self, self._ctx, self.state) + self.enterRule(localctx, 18, self.RULE_json) + try: + self.enterOuterAlt(localctx, 1) + self.state = 117 + self.value() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class ObjContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def BRACE_LEFT(self): + return self.getToken(JSONPathParser.BRACE_LEFT, 0) + + def pair(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(JSONPathParser.PairContext) + else: + return self.getTypedRuleContext(JSONPathParser.PairContext,i) + + + def BRACE_RIGHT(self): + return self.getToken(JSONPathParser.BRACE_RIGHT, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(JSONPathParser.COMMA) + else: + return self.getToken(JSONPathParser.COMMA, i) + + def getRuleIndex(self): + return JSONPathParser.RULE_obj + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterObj" ): + listener.enterObj(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitObj" ): + listener.exitObj(self) + + + + + def obj(self): + + localctx = JSONPathParser.ObjContext(self, self._ctx, self.state) + self.enterRule(localctx, 20, self.RULE_obj) + self._la = 0 # Token type + try: + self.state = 132 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,17,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 119 + self.match(JSONPathParser.BRACE_LEFT) + self.state = 120 + self.pair() + self.state = 125 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==JSONPathParser.COMMA: + self.state = 121 + self.match(JSONPathParser.COMMA) + self.state = 122 + self.pair() + self.state = 127 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 128 + self.match(JSONPathParser.BRACE_RIGHT) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 130 + self.match(JSONPathParser.BRACE_LEFT) + self.state = 131 + self.match(JSONPathParser.BRACE_RIGHT) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class PairContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def STRING(self): + return self.getToken(JSONPathParser.STRING, 0) + + def COLON(self): + return self.getToken(JSONPathParser.COLON, 0) + + def value(self): + return self.getTypedRuleContext(JSONPathParser.ValueContext,0) + + + def getRuleIndex(self): + return JSONPathParser.RULE_pair + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPair" ): + listener.enterPair(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPair" ): + listener.exitPair(self) + + + + + def pair(self): + + localctx = JSONPathParser.PairContext(self, self._ctx, self.state) + self.enterRule(localctx, 22, self.RULE_pair) + try: + self.enterOuterAlt(localctx, 1) + self.state = 134 + self.match(JSONPathParser.STRING) + self.state = 135 + self.match(JSONPathParser.COLON) + self.state = 136 + self.value() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class ArrayContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def BRACKET_LEFT(self): + return self.getToken(JSONPathParser.BRACKET_LEFT, 0) + + def value(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(JSONPathParser.ValueContext) + else: + return self.getTypedRuleContext(JSONPathParser.ValueContext,i) + + + def BRACKET_RIGHT(self): + return self.getToken(JSONPathParser.BRACKET_RIGHT, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(JSONPathParser.COMMA) + else: + return self.getToken(JSONPathParser.COMMA, i) + + def getRuleIndex(self): + return JSONPathParser.RULE_array + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterArray" ): + listener.enterArray(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitArray" ): + listener.exitArray(self) + + + + + def array(self): + + localctx = JSONPathParser.ArrayContext(self, self._ctx, self.state) + self.enterRule(localctx, 24, self.RULE_array) + self._la = 0 # Token type + try: + self.state = 151 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,19,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 138 + self.match(JSONPathParser.BRACKET_LEFT) + self.state = 139 + self.value() + self.state = 144 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==JSONPathParser.COMMA: + self.state = 140 + self.match(JSONPathParser.COMMA) + self.state = 141 + self.value() + self.state = 146 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 147 + self.match(JSONPathParser.BRACKET_RIGHT) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 149 + self.match(JSONPathParser.BRACKET_LEFT) + self.state = 150 + self.match(JSONPathParser.BRACKET_RIGHT) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class ValueContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def STRING(self): + return self.getToken(JSONPathParser.STRING, 0) + + def NUMBER(self): + return self.getToken(JSONPathParser.NUMBER, 0) + + def obj(self): + return self.getTypedRuleContext(JSONPathParser.ObjContext,0) + + + def array(self): + return self.getTypedRuleContext(JSONPathParser.ArrayContext,0) + + + def TRUE(self): + return self.getToken(JSONPathParser.TRUE, 0) + + def FALSE(self): + return self.getToken(JSONPathParser.FALSE, 0) + + def NULL(self): + return self.getToken(JSONPathParser.NULL, 0) + + def getRuleIndex(self): + return JSONPathParser.RULE_value + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterValue" ): + listener.enterValue(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitValue" ): + listener.exitValue(self) + + + + + def value(self): + + localctx = JSONPathParser.ValueContext(self, self._ctx, self.state) + self.enterRule(localctx, 26, self.RULE_value) + try: + self.state = 160 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [JSONPathParser.STRING]: + self.enterOuterAlt(localctx, 1) + self.state = 153 + self.match(JSONPathParser.STRING) + pass + elif token in [JSONPathParser.NUMBER]: + self.enterOuterAlt(localctx, 2) + self.state = 154 + self.match(JSONPathParser.NUMBER) + pass + elif token in [JSONPathParser.BRACE_LEFT]: + self.enterOuterAlt(localctx, 3) + self.state = 155 + self.obj() + pass + elif token in [JSONPathParser.BRACKET_LEFT]: + self.enterOuterAlt(localctx, 4) + self.state = 156 + self.array() + pass + elif token in [JSONPathParser.TRUE]: + self.enterOuterAlt(localctx, 5) + self.state = 157 + self.match(JSONPathParser.TRUE) + pass + elif token in [JSONPathParser.FALSE]: + self.enterOuterAlt(localctx, 6) + self.state = 158 + self.match(JSONPathParser.FALSE) + pass + elif token in [JSONPathParser.NULL]: + self.enterOuterAlt(localctx, 7) + self.state = 159 + self.match(JSONPathParser.NULL) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + + def sempred(self, localctx:RuleContext, ruleIndex:int, predIndex:int): + if self._predicates == None: + self._predicates = dict() + self._predicates[4] = self.subscriptable_sempred + pred = self._predicates.get(ruleIndex, None) + if pred is None: + raise Exception("No predicate with index:" + str(ruleIndex)) + else: + return pred(localctx, predIndex) + + def subscriptable_sempred(self, localctx:SubscriptableContext, predIndex:int): + if predIndex == 0: + return self.tryCast(int) + + + if predIndex == 1: + return self.tryCast(int) + + + if predIndex == 2: + return self.tryCast(int) + + + + + diff --git a/jsonpath2/parser/__init__.py b/jsonpath2/parser/__init__.py new file mode 100644 index 0000000..caa771c --- /dev/null +++ b/jsonpath2/parser/__init__.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import antlr4 + +from jsonpath2.expressions.OperatorExpression import AndVariadicOperatorExpression, EqualBinaryOperatorExpression, GreaterThanBinaryOperatorExpression, GreaterThanOrEqualToBinaryOperatorExpression, LessThanBinaryOperatorExpression, LessThanOrEqualToBinaryOperatorExpression, NotEqualBinaryOperatorExpression, NotUnaryOperatorExpression, OrVariadicOperatorExpression +from jsonpath2.expressions.SomeExpression import SomeExpression + +from jsonpath2.nodes.CurrentNode import CurrentNode +from jsonpath2.nodes.RecursiveDescentNode import RecursiveDescentNode +from jsonpath2.nodes.RootNode import RootNode +from jsonpath2.nodes.SubscriptNode import SubscriptNode +from jsonpath2.nodes.TerminalNode import TerminalNode + +from jsonpath2.parser.JSONPathLexer import JSONPathLexer +from jsonpath2.parser.JSONPathListener import JSONPathListener +from jsonpath2.parser.JSONPathParser import JSONPathParser + +from jsonpath2.subscripts.ArrayIndexSubscript import ArrayIndexSubscript +from jsonpath2.subscripts.ArraySliceSubscript import ArraySliceSubscript +from jsonpath2.subscripts.FilterSubscript import FilterSubscript +from jsonpath2.subscripts.ObjectIndexSubscript import ObjectIndexSubscript +from jsonpath2.subscripts.WildcardSubscript import WildcardSubscript + +class _ConsoleErrorListener(antlr4.error.ErrorListener.ConsoleErrorListener): + def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): + raise ValueError('line {}:{} {}'.format(line, column, msg)) + +class _JSONPathListener(JSONPathListener): + def __init__(self, _stack=[]): + super(_JSONPathListener, self).__init__() + + self._stack = _stack + + def exitJsonpath(self, ctx:JSONPathParser.JsonpathContext): + if ctx.getToken(JSONPathParser.ROOT_VALUE, 0) is not None: + if bool(ctx.subscript()): + next_node = self._stack.pop() + else: + next_node = TerminalNode() + + self._stack.append(RootNode(next_node)) + else: + raise ValueError() + + def exitSubscript(self, ctx:JSONPathParser.SubscriptContext): + if ctx.getToken(JSONPathParser.RECURSIVE_DESCENT, 0) is not None: + if bool(ctx.subscript()): + next_node = self._stack.pop() + else: + next_node = TerminalNode() + + if bool(ctx.subscriptableBareword()): + subscriptable_nodes = [self._stack.pop()] + elif bool(ctx.subscriptables()): + subscriptable_nodes = self._stack.pop() + else: + raise ValueError() + + self._stack.append(RecursiveDescentNode(SubscriptNode(next_node, subscriptable_nodes))) + elif ctx.getToken(JSONPathParser.SUBSCRIPT, 0) is not None: + if bool(ctx.subscript()): + next_node = self._stack.pop() + else: + next_node = TerminalNode() + + if bool(ctx.subscriptableBareword()): + subscriptable_nodes = [self._stack.pop()] + else: + raise ValueError() + + self._stack.append(SubscriptNode(next_node, subscriptable_nodes)) + else: + if bool(ctx.subscript()): + next_node = self._stack.pop() + else: + next_node = TerminalNode() + + if bool(ctx.subscriptables()): + subscriptable_nodes = self._stack.pop() + else: + raise ValueError() + + self._stack.append(SubscriptNode(next_node, subscriptable_nodes)) + + def exitSubscriptables(self, ctx:JSONPathParser.SubscriptablesContext): + subscriptable_nodes = [] + + for subscriptable_ctx in ctx.getTypedRuleContexts(JSONPathParser.SubscriptableContext): + subscriptable_node = self._stack.pop() + + subscriptable_nodes.insert(0, subscriptable_node) + + self._stack.append(subscriptable_nodes) + + def exitSubscriptableBareword(self, ctx:JSONPathParser.SubscriptableBarewordContext): + if bool(ctx.ID()): + text = ctx.ID().getText() + + self._stack.append(ObjectIndexSubscript(text)) + elif ctx.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) is not None: + self._stack.append(WildcardSubscript()) + else: + raise ValueError() + + def exitSubscriptable(self, ctx:JSONPathParser.SubscriptableContext): + if bool(ctx.STRING()): + text = ctx.STRING().getText()[1:-1] + + self._stack.append(ObjectIndexSubscript(text)) + elif bool(ctx.NUMBER()): + if ctx.getToken(JSONPathParser.COLON, 0) is not None: + start = int(ctx.NUMBER(0).getText()) if bool(ctx.NUMBER(0)) else None + + end = int(ctx.NUMBER(1).getText()) if bool(ctx.NUMBER(1)) else None + + step = int(ctx.NUMBER(2).getText()) if bool(ctx.NUMBER(2)) else None + + self._stack.append(ArraySliceSubscript(start, end, step)) + else: + index = int(ctx.NUMBER(0).getText()) + + self._stack.append(ArrayIndexSubscript(index)) + elif ctx.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) is not None: + self._stack.append(WildcardSubscript()) + elif ctx.getToken(JSONPathParser.QUESTION, 0) is not None: + expression = self._stack.pop() + + self._stack.append(FilterSubscript(expression)) + else: + raise ValueError() + + def exitAndExpression(self, ctx:JSONPathParser.AndExpressionContext): + expressions = [] + + if bool(ctx.andExpression()): + expression = self._stack.pop() + + if isinstance(expression, AndVariadicOperatorExpression): + expressions = expression.expressions + expressions + else: + expressions.insert(0, expression) + + expression = self._stack.pop() + + if isinstance(expression, AndVariadicOperatorExpression): + expressions = expression.expressions + expressions + else: + expressions.insert(0, expression) + + if len(expressions) == 0: + raise ValueError() + if len(expressions) == 1: + self._stack.append(expressions[0]) + else: + self._stack.append(AndVariadicOperatorExpression(expressions)) + + def exitOrExpression(self, ctx:JSONPathParser.OrExpressionContext): + expressions = [] + + if bool(ctx.orExpression()): + expression = self._stack.pop() + + if isinstance(expression, OrVariadicOperatorExpression): + expressions = expression.expressions + expressions + else: + expressions.insert(0, expression) + + expression = self._stack.pop() + + if isinstance(expression, OrVariadicOperatorExpression): + expressions = expression.expressions + expressions + else: + expressions.insert(0, expression) + + if len(expressions) == 0: + raise ValueError() + if len(expressions) == 1: + self._stack.append(expressions[0]) + else: + self._stack.append(OrVariadicOperatorExpression(expressions)) + + def exitNotExpression(self, ctx:JSONPathParser.NotExpressionContext): + if ctx.getToken(JSONPathParser.NOT, 0) is not None: + expression = self._stack.pop() + + if isinstance(expression, NotUnaryOperatorExpression): + self._stack.append(expression.expression) + else: + self._stack.append(NotUnaryOperatorExpression(expression)) + elif (ctx.getToken(JSONPathParser.ROOT_VALUE, 0) is not None) or (ctx.getToken(JSONPathParser.CURRENT_VALUE, 0) is not None): + if bool(ctx.value()): + right_value = self._stack.pop() + + if bool(ctx.subscript()): + next_node = self._stack.pop() + else: + next_node = TerminalNode() + + if ctx.getToken(JSONPathParser.ROOT_VALUE, 0) is not None: + left_node = RootNode(next_node) + elif ctx.getToken(JSONPathParser.CURRENT_VALUE, 0) is not None: + left_node = CurrentNode(next_node) + else: + raise ValueError() + + if ctx.getToken(JSONPathParser.EQ, 0) is not None: + self._stack.append(EqualBinaryOperatorExpression(left_node, right_value)) + elif ctx.getToken(JSONPathParser.NE, 0) is not None: + self._stack.append(NotEqualBinaryOperatorExpression(left_node, right_value)) + elif ctx.getToken(JSONPathParser.LT, 0) is not None: + self._stack.append(LessThanBinaryOperatorExpression(left_node, right_value)) + elif ctx.getToken(JSONPathParser.LE, 0) is not None: + self._stack.append(LessThanOrEqualToBinaryOperatorExpression(left_node, right_value)) + elif ctx.getToken(JSONPathParser.GT, 0) is not None: + self._stack.append(GreaterThanBinaryOperatorExpression(left_node, right_value)) + elif ctx.getToken(JSONPathParser.GE, 0) is not None: + self._stack.append(GreaterThanOrEqualToBinaryOperatorExpression(left_node, right_value)) + else: + raise ValueError() + else: + if bool(ctx.subscript()): + next_node = self._stack.pop() + else: + next_node = TerminalNode() + + self._stack.append(SomeExpression(CurrentNode(next_node))) + else: + pass + + def exitObj(self, ctx:JSONPathParser.ObjContext): + values = [] + + for pair_ctx in ctx.getTypedRuleContexts(JSONPathParser.PairContext): + value = self._stack.pop() + + values.insert(0, value) + + obj = {} + + for index, pair_ctx in enumerate(ctx.getTypedRuleContexts(JSONPathParser.PairContext)): + key = pair_ctx.STRING().getText()[1:-1] + + obj[key] = values[index] + + self._stack.append(obj) + + def exitArray(self, ctx:JSONPathParser.ArrayContext): + array = [] + + for value_ctx in ctx.getTypedRuleContexts(JSONPathParser.ValueContext): + value = self._stack.pop() + + array.insert(0, value) + + self._stack.append(array) + + def exitValue(self, ctx:JSONPathParser.ValueContext): + if bool(ctx.STRING()): + text = ctx.STRING().getText()[1:-1] + + self._stack.append(text) + elif bool(ctx.NUMBER()): + text = ctx.NUMBER().getText() + + if ('.' in text) or ('E' in text) or ('e' in text): + self._stack.append(float(text)) + else: + self._stack.append(int(text)) + elif bool(ctx.obj()): + pass + elif bool(ctx.array()): + pass + elif ctx.getToken(JSONPathParser.TRUE, 0) is not None: + self._stack.append(True) + elif ctx.getToken(JSONPathParser.FALSE, 0) is not None: + self._stack.append(False) + elif ctx.getToken(JSONPathParser.NULL, 0) is not None: + self._stack.append(None) + else: + raise ValueError() + +class _JSONPathParser(JSONPathParser): + def tryCast(self, cls): + try: + cls(self._input.LT(-1).text) + + return True + except ValueError: + return False + +def _parse_input_stream(input_stream:antlr4.InputStream) -> RootNode: + error_listener = _ConsoleErrorListener() + + lexer = JSONPathLexer(input_stream) + + lexer.addErrorListener(error_listener) + + token_stream = antlr4.CommonTokenStream(lexer) + + parser = _JSONPathParser(token_stream) + + parser.addErrorListener(error_listener) + + tree = parser.jsonpath() + + listener = _JSONPathListener(_stack=[]) + + walker = antlr4.ParseTreeWalker() + walker.walk(listener, tree) + + return listener._stack.pop() + +def parse_file(*args, **kwargs) -> RootNode: + file_stream = antlr4.FileStream(*args, **kwargs) + + return _parse_input_stream(file_stream) + +def parse_str(*args, **kwargs) -> RootNode: + input_stream = antlr4.InputStream(*args, **kwargs) + + return _parse_input_stream(input_stream) diff --git a/jsonpath2/subscripts/ArrayIndexSubscript.py b/jsonpath2/subscripts/ArrayIndexSubscript.py new file mode 100644 index 0000000..8ca3f35 --- /dev/null +++ b/jsonpath2/subscripts/ArrayIndexSubscript.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import json +from typing import Generator + +from jsonpath2.Node import MatchData +from jsonpath2.Subscript import Subscript + +from jsonpath2.nodes.SubscriptNode import SubscriptNode +from jsonpath2.nodes.TerminalNode import TerminalNode + +class ArrayIndexSubscript(Subscript): + def __init__(self, index:int): + super(ArrayIndexSubscript, self).__init__() + + self.index = index + + def __jsonpath__(self) -> Generator[str, None, None]: + yield json.dumps(self.index) + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + if isinstance(current_value, list): + if self.index < 0: + new_index = self.index + len(current_value) + + if (new_index >= 0) and (new_index < len(current_value)): + return [MatchData(SubscriptNode(TerminalNode(), [self]), root_value, current_value[new_index])] + else: + return [] + elif self.index < len(current_value): + return [MatchData(SubscriptNode(TerminalNode(), [self]), root_value, current_value[self.index])] + else: + return [] + else: + return [] diff --git a/jsonpath2/subscripts/ArraySliceSubscript.py b/jsonpath2/subscripts/ArraySliceSubscript.py new file mode 100644 index 0000000..5bd3719 --- /dev/null +++ b/jsonpath2/subscripts/ArraySliceSubscript.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import itertools +import json +from typing import Generator + +from jsonpath2.Node import MatchData +from jsonpath2.Subscript import Subscript + +class ArraySliceSubscript(Subscript): + def __init__(self, start:int=None, end:int=None, step:int=None): + super(ArraySliceSubscript, self).__init__() + + self.start = start + self.end = end + self.step = step + + def __jsonpath__(self) -> Generator[str, None, None]: + if self.start is not None: + yield json.dumps(self.start) + + yield ':' + + if self.end is not None: + yield json.dumps(self.end) + + if self.step is not None: + yield ':' + + yield json.dumps(self.step) + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + if isinstance(current_value, list): + start = None if (self.start is None) else (self.start + (len(current_value) if (self.start < 0) else 0)) + + end = None if (self.end is None) else (self.end + (len(current_value) if (self.end < 0) else 0)) + + if start is None: + if end is None: + if self.step is None: + indices = range(0, len(current_value)) + else: + indices = range(0, len(current_value), self.step) + else: + if self.step is None: + indices = range(0, end) + else: + indices = range(0, end, self.step) + else: + if end is None: + if self.step is None: + indices = range(start, len(current_value)) + else: + indices = range(start, len(current_value), self.step) + else: + if self.step is None: + indices = range(start, end) + else: + indices = range(start, end, self.step) + + return itertools.chain(*map(lambda index: ArrayIndexSubscript(index).match(root_value, current_value), indices)) + else: + return [] diff --git a/jsonpath2/subscripts/FilterSubscript.py b/jsonpath2/subscripts/FilterSubscript.py new file mode 100644 index 0000000..4829334 --- /dev/null +++ b/jsonpath2/subscripts/FilterSubscript.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from typing import Generator + +from jsonpath2.Expression import Expression +from jsonpath2.Node import MatchData +from jsonpath2.Subscript import Subscript + +from jsonpath2.nodes.TerminalNode import TerminalNode + +class FilterSubscript(Subscript): + def __init__(self, expression:Expression): + super(FilterSubscript, self).__init__() + + self.expression = expression + + def __jsonpath__(self) -> Generator[str, None, None]: + yield '?' + yield '(' + + for expression_token in self.expression.__jsonpath__(): + yield expression_token + + yield ')' + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + if self.expression.evaluate(root_value, current_value): + return [MatchData(TerminalNode(), root_value, current_value)] + else: + return [] diff --git a/jsonpath2/subscripts/ObjectIndexSubscript.py b/jsonpath2/subscripts/ObjectIndexSubscript.py new file mode 100644 index 0000000..50bfccc --- /dev/null +++ b/jsonpath2/subscripts/ObjectIndexSubscript.py @@ -0,0 +1,26 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import json +from typing import Generator + +from jsonpath2.Node import MatchData +from jsonpath2.Subscript import Subscript + +from jsonpath2.nodes.SubscriptNode import SubscriptNode +from jsonpath2.nodes.TerminalNode import TerminalNode + +class ObjectIndexSubscript(Subscript): + def __init__(self, index:str): + super(ObjectIndexSubscript, self).__init__() + + self.index = index + + def __jsonpath__(self) -> Generator[str, None, None]: + yield json.dumps(self.index) + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + if isinstance(current_value, dict) and (self.index in current_value): + return [MatchData(SubscriptNode(TerminalNode(), [self]), root_value, current_value[self.index])] + else: + return [] diff --git a/jsonpath2/subscripts/WildcardSubscript.py b/jsonpath2/subscripts/WildcardSubscript.py new file mode 100644 index 0000000..eed9903 --- /dev/null +++ b/jsonpath2/subscripts/WildcardSubscript.py @@ -0,0 +1,26 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import itertools +from typing import Generator + +from jsonpath2.Node import MatchData +from jsonpath2.Subscript import Subscript + +from jsonpath2.subscripts.ArrayIndexSubscript import ArrayIndexSubscript +from jsonpath2.subscripts.ObjectIndexSubscript import ObjectIndexSubscript + +class WildcardSubscript(Subscript): + def __init__(self): + super(WildcardSubscript, self).__init__() + + def __jsonpath__(self) -> Generator[str, None, None]: + yield '*' + + def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + if isinstance(current_value, dict): + return itertools.chain(*map(lambda index: ObjectIndexSubscript(index).match(root_value, current_value), current_value.keys())) + elif isinstance(current_value, list): + return itertools.chain(*map(lambda index: ArrayIndexSubscript(index).match(root_value, current_value), range(len(current_value)))) + else: + return [] diff --git a/jsonpath2/subscripts/__init__.py b/jsonpath2/subscripts/__init__.py new file mode 100644 index 0000000..836e3e8 --- /dev/null +++ b/jsonpath2/subscripts/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- diff --git a/jsonpath2/test/example_test.py b/jsonpath2/test/example_test.py deleted file mode 100644 index 6a9e063..0000000 --- a/jsonpath2/test/example_test.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -"""Test the example module.""" -from unittest import TestCase -from jsonpath2 import Example - - -class TestExample(TestCase): - """Test the example class.""" - - def test_add(self): - """Test the add method in example class.""" - self.assertEqual(Example().add('123', 'abc'), - '123abc', 'sum of strings should work') - self.assertEqual(Example().add(123, 456), 579, - 'sum of integers should work') - - def test_mul(self): - """Test the mul method in example class.""" - self.assertEqual(Example().mul('a', 4), 'aaaa', - 'multiply of string and number should work') - self.assertEqual(Example().mul(2, 3), 6, - 'multiply of two integers should work') diff --git a/jsonpath2/test/jsonpath2_test.py b/jsonpath2/test/jsonpath2_test.py new file mode 100644 index 0000000..f929f09 --- /dev/null +++ b/jsonpath2/test/jsonpath2_test.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from unittest import TestCase + +from jsonpath2.Node import MatchData + +from jsonpath2.expressions.SomeExpression import SomeExpression + +from jsonpath2.nodes.CurrentNode import CurrentNode +from jsonpath2.nodes.RecursiveDescentNode import RecursiveDescentNode +from jsonpath2.nodes.RootNode import RootNode +from jsonpath2.nodes.SubscriptNode import SubscriptNode +from jsonpath2.nodes.TerminalNode import TerminalNode + +from jsonpath2.subscripts.ArrayIndexSubscript import ArrayIndexSubscript +from jsonpath2.subscripts.FilterSubscript import FilterSubscript +from jsonpath2.subscripts.ObjectIndexSubscript import ObjectIndexSubscript +from jsonpath2.subscripts.WildcardSubscript import WildcardSubscript + +class TestNode(TestCase): + def setUp(self): + root_value = { + 'hello': 'Hello, world!', + 'languages': [ + 'en-GB', + 'en-US', + ], + } + current_value = root_value['hello'] + + self._state = [ + { + '__jsonpath__': '', + 'node': TerminalNode(), + 'root_value': root_value, + 'current_value': current_value, + 'match_data_list': [ + MatchData(TerminalNode(), root_value, current_value), + ], + }, + { + '__jsonpath__': '$', + 'node': RootNode(TerminalNode()), + 'root_value': root_value, + 'current_value': current_value, + 'match_data_list': [ + MatchData(RootNode(TerminalNode()), root_value, root_value), + ], + }, + { + '__jsonpath__': '@', + 'node': CurrentNode(TerminalNode()), + 'root_value': root_value, + 'current_value': current_value, + 'match_data_list': [ + MatchData(CurrentNode(TerminalNode()), root_value, current_value), + ], + }, + { + '__jsonpath__': '[]', + 'node': SubscriptNode(TerminalNode(), []), + 'root_value': root_value, + 'current_value': current_value, + 'match_data_list': [], + }, + { + '__jsonpath__': '[?(@)]', + 'node': SubscriptNode(TerminalNode(), [FilterSubscript(SomeExpression(CurrentNode(TerminalNode())))]), + 'root_value': root_value, + 'current_value': current_value, + 'match_data_list': [ + MatchData(TerminalNode(), root_value, current_value), + ], + }, + { + '__jsonpath__': '[?(@),?(@)]', + 'node': SubscriptNode(TerminalNode(), [FilterSubscript(SomeExpression(CurrentNode(TerminalNode()))), FilterSubscript(SomeExpression(CurrentNode(TerminalNode())))]), + 'root_value': root_value, + 'current_value': current_value, + 'match_data_list': [ + MatchData(TerminalNode(), root_value, current_value), + MatchData(TerminalNode(), root_value, current_value), + ], + }, + { + '__jsonpath__': '[*]', + 'node': SubscriptNode(TerminalNode(), [WildcardSubscript()]), + 'root_value': root_value, + 'current_value': current_value, + 'match_data_list': [], + }, + { + '__jsonpath__': '[*]', + 'node': SubscriptNode(TerminalNode(), [WildcardSubscript()]), + 'root_value': root_value, + 'current_value': root_value, + 'match_data_list': [ + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), + ], + }, + { + '__jsonpath__': '["languages"][*]', + 'node': SubscriptNode(SubscriptNode(TerminalNode(), [WildcardSubscript()]), [ObjectIndexSubscript('languages')]), + 'root_value': root_value, + 'current_value': root_value, + 'match_data_list': [ + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][1]), + ], + }, + { + '__jsonpath__': '["hello","languages"]', + 'node': SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello'), ObjectIndexSubscript('languages')]), + 'root_value': root_value, + 'current_value': root_value, + 'match_data_list': [ + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), + ], + }, + { + '__jsonpath__': '..', + 'node': RecursiveDescentNode(TerminalNode()), + 'root_value': root_value, + 'current_value': current_value, + 'match_data_list': [ + MatchData(TerminalNode(), root_value, current_value), + ], + }, + { + '__jsonpath__': '..', + 'node': RecursiveDescentNode(TerminalNode()), + 'root_value': root_value, + 'current_value': root_value, + 'match_data_list': [ + MatchData(TerminalNode(), root_value, root_value), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][1]), + ], + }, + { + '__jsonpath__': '..[?(@)]', + 'node': RecursiveDescentNode(SubscriptNode(TerminalNode(), [FilterSubscript(SomeExpression(CurrentNode(TerminalNode())))])), + 'root_value': root_value, + 'current_value': root_value, + 'match_data_list': [ + MatchData(TerminalNode(), root_value, root_value), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][1]), + ], + }, + { + '__jsonpath__': '..[*]', + 'node': RecursiveDescentNode(SubscriptNode(TerminalNode(), [WildcardSubscript()])), + 'root_value': root_value, + 'current_value': root_value, + 'match_data_list': [ + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][1]), + ], + }, + { + '__jsonpath__': '..["hello"]', + 'node': RecursiveDescentNode(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')])), + 'root_value': root_value, + 'current_value': root_value, + 'match_data_list': [ + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), + ], + }, + { + '__jsonpath__': '..[0]', + 'node': RecursiveDescentNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)])), + 'root_value': root_value, + 'current_value': root_value, + 'match_data_list': [ + MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), + ], + }, + ] + + def _assertNodeTestCase(self, **kwargs): + self.assertEqual(kwargs['__jsonpath__'], kwargs['node'].tojsonpath()) + + match_data_list = list(kwargs['node'].match(kwargs['root_value'], kwargs['current_value'])) + + self.assertEqual(kwargs['match_data_list'], match_data_list) + + for match_data in match_data_list: + new_match_data_list = list(match_data.node.match(kwargs['root_value'], kwargs['current_value'])) + + self.assertEqual([match_data], new_match_data_list) + + def test_state(self): + for kwargs in self._state: + self._assertNodeTestCase(**kwargs) diff --git a/requirements.txt b/requirements.txt index e69de29..b58d864 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,3 @@ +antlr4-python3-runtime +setuptools +six diff --git a/setup.py b/setup.py index a67eed3..9e1794b 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -"""Setup and install JSONPath2 library.""" + try: # pip version 9 from pip.req import parse_requirements except ImportError: @@ -16,9 +16,10 @@ setup_requires=['setuptools_scm'], license='LGPLv3', url='https://pypi.python.org/pypi/jsonpath2/', - description='JSONPath Parser', - author='David Brown', - author_email='dmlb2000@gmail.com', + description='JSONPath implementation for Python', + author='Mark Borkum', + author_email='mark.borkum@pnnl.gov', packages=find_packages(), + namespace_packages=['jsonpath2'], install_requires=[str(ir.req) for ir in INSTALL_REQS] ) From 7b10e61950dc347e824a06bbf15ef6acf3c7d014 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Sun, 16 Sep 2018 17:15:00 -0700 Subject: [PATCH 02/23] Fix some Flake8 warnings --- .flake8 | 14 ++++++++++++++ jsonpath2/Subscript.py | 2 +- jsonpath2/expressions/OperatorExpression.py | 3 --- jsonpath2/subscripts/ArraySliceSubscript.py | 2 ++ 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..fb7b94a --- /dev/null +++ b/.flake8 @@ -0,0 +1,14 @@ +[flake8] +exclude = + # Ignore ANTLR v4 targets: + jsonpath2/parser/JSONPath.g4, + jsonpath2/parser/JSONPath.interp, + jsonpath2/parser/JSONPath.tokens, + jsonpath2/parser/JSONPathLexer.interp, + jsonpath2/parser/JSONPathLexer.py, + jsonpath2/parser/JSONPathLexer.tokens, + jsonpath2/parser/JSONPathListener.py, + jsonpath2/parser/JSONPathParser.py +# 79 chars is too strict and we don't have 80-char terminals nowadays, +# 160 chars is too much since it doesn't let us use split view efficiently: +max-line-length = 120 diff --git a/jsonpath2/Subscript.py b/jsonpath2/Subscript.py index 9269399..d7673ac 100644 --- a/jsonpath2/Subscript.py +++ b/jsonpath2/Subscript.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from jsonpath2.Node import MatchData, Node +from jsonpath2.Node import Node class Subscript(Node): pass diff --git a/jsonpath2/expressions/OperatorExpression.py b/jsonpath2/expressions/OperatorExpression.py index 33703f5..bddfd15 100644 --- a/jsonpath2/expressions/OperatorExpression.py +++ b/jsonpath2/expressions/OperatorExpression.py @@ -1,15 +1,12 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from abc import abstractmethod import json from typing import Callable, Generator, List from jsonpath2.Expression import Expression from jsonpath2.Node import Node -from jsonpath2.expressions.SomeExpression import SomeExpression - class OperatorExpression(Expression): pass diff --git a/jsonpath2/subscripts/ArraySliceSubscript.py b/jsonpath2/subscripts/ArraySliceSubscript.py index 5bd3719..6c0ebcb 100644 --- a/jsonpath2/subscripts/ArraySliceSubscript.py +++ b/jsonpath2/subscripts/ArraySliceSubscript.py @@ -8,6 +8,8 @@ from jsonpath2.Node import MatchData from jsonpath2.Subscript import Subscript +from jsonpath2.subscripts.ArrayIndexSubscript import ArrayIndexSubscript + class ArraySliceSubscript(Subscript): def __init__(self, start:int=None, end:int=None, step:int=None): super(ArraySliceSubscript, self).__init__() From 67f23e8c6caa2cde4804589304cc7a07c5fa8287 Mon Sep 17 00:00:00 2001 From: David Brown Date: Sun, 16 Sep 2018 20:18:33 -0700 Subject: [PATCH 03/23] Initial pre-commit run Signed-off-by: David Brown --- .flake8 | 10 - .pre-commit-config.yaml | 1 + .travis.yml | 19 +- README.md | 4 +- jsonpath2/Expression.py | 17 -- jsonpath2/Node.py | 31 --- jsonpath2/Path.py | 34 --- jsonpath2/Subscript.py | 7 - jsonpath2/ToJSONPath.py | 16 -- jsonpath2/__init__.py | 2 +- jsonpath2/expression.py | 18 ++ jsonpath2/expressions/OperatorExpression.py | 157 ------------ jsonpath2/expressions/SomeExpression.py | 22 -- jsonpath2/expressions/__init__.py | 1 + jsonpath2/expressions/operator.py | 229 +++++++++++++++++ jsonpath2/expressions/some.py | 25 ++ jsonpath2/node.py | 39 +++ jsonpath2/nodes/CurrentNode.py | 21 -- jsonpath2/nodes/RecursiveDescentNode.py | 27 -- jsonpath2/nodes/RootNode.py | 21 -- jsonpath2/nodes/TerminalNode.py | 16 -- jsonpath2/nodes/__init__.py | 1 + jsonpath2/nodes/current.py | 31 +++ jsonpath2/nodes/recursivedescent.py | 32 +++ jsonpath2/nodes/root.py | 31 +++ .../nodes/{SubscriptNode.py => subscript.py} | 35 +-- jsonpath2/nodes/terminal.py | 17 ++ jsonpath2/parser/__init__.py | 137 +++++----- jsonpath2/path.py | 41 +++ jsonpath2/subscript.py | 20 ++ jsonpath2/subscripts/FilterSubscript.py | 31 --- jsonpath2/subscripts/ObjectIndexSubscript.py | 26 -- jsonpath2/subscripts/WildcardSubscript.py | 26 -- jsonpath2/subscripts/__init__.py | 1 + .../{ArrayIndexSubscript.py => arrayindex.py} | 28 +-- .../{ArraySliceSubscript.py => arrayslice.py} | 37 +-- jsonpath2/subscripts/filter.py | 31 +++ jsonpath2/subscripts/objectindex.py | 28 +++ jsonpath2/subscripts/wildcard.py | 31 +++ jsonpath2/test/expression_test.py | 15 ++ jsonpath2/test/jsonpath2_test.py | 235 ++++++++++++++---- jsonpath2/tojsonpath.py | 18 ++ requirements-dev.txt | 2 + setup.py | 8 +- 44 files changed, 944 insertions(+), 635 deletions(-) delete mode 100644 jsonpath2/Expression.py delete mode 100644 jsonpath2/Node.py delete mode 100644 jsonpath2/Path.py delete mode 100644 jsonpath2/Subscript.py delete mode 100644 jsonpath2/ToJSONPath.py create mode 100644 jsonpath2/expression.py delete mode 100644 jsonpath2/expressions/OperatorExpression.py delete mode 100644 jsonpath2/expressions/SomeExpression.py create mode 100644 jsonpath2/expressions/operator.py create mode 100644 jsonpath2/expressions/some.py create mode 100644 jsonpath2/node.py delete mode 100644 jsonpath2/nodes/CurrentNode.py delete mode 100644 jsonpath2/nodes/RecursiveDescentNode.py delete mode 100644 jsonpath2/nodes/RootNode.py delete mode 100644 jsonpath2/nodes/TerminalNode.py create mode 100644 jsonpath2/nodes/current.py create mode 100644 jsonpath2/nodes/recursivedescent.py create mode 100644 jsonpath2/nodes/root.py rename jsonpath2/nodes/{SubscriptNode.py => subscript.py} (54%) create mode 100644 jsonpath2/nodes/terminal.py create mode 100644 jsonpath2/path.py create mode 100644 jsonpath2/subscript.py delete mode 100644 jsonpath2/subscripts/FilterSubscript.py delete mode 100644 jsonpath2/subscripts/ObjectIndexSubscript.py delete mode 100644 jsonpath2/subscripts/WildcardSubscript.py rename jsonpath2/subscripts/{ArrayIndexSubscript.py => arrayindex.py} (57%) rename jsonpath2/subscripts/{ArraySliceSubscript.py => arrayslice.py} (60%) create mode 100644 jsonpath2/subscripts/filter.py create mode 100644 jsonpath2/subscripts/objectindex.py create mode 100644 jsonpath2/subscripts/wildcard.py create mode 100644 jsonpath2/test/expression_test.py create mode 100644 jsonpath2/tojsonpath.py diff --git a/.flake8 b/.flake8 index fb7b94a..05e00da 100644 --- a/.flake8 +++ b/.flake8 @@ -1,14 +1,4 @@ [flake8] -exclude = - # Ignore ANTLR v4 targets: - jsonpath2/parser/JSONPath.g4, - jsonpath2/parser/JSONPath.interp, - jsonpath2/parser/JSONPath.tokens, - jsonpath2/parser/JSONPathLexer.interp, - jsonpath2/parser/JSONPathLexer.py, - jsonpath2/parser/JSONPathLexer.tokens, - jsonpath2/parser/JSONPathListener.py, - jsonpath2/parser/JSONPathParser.py # 79 chars is too strict and we don't have 80-char terminals nowadays, # 160 chars is too much since it doesn't let us use split view efficiently: max-line-length = 120 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bffd74f..f6f42f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: 'jsonpath2/parser/JSONPath.*' repos: - repo: git://github.com/pre-commit/pre-commit-hooks rev: v1.2.3 diff --git a/.travis.yml b/.travis.yml index 1c6660b..457e4b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python python: -- 2.7 +- 3.5 - 3.6 stages: - lint @@ -9,7 +9,7 @@ stages: install: pip install -r requirements-dev.txt script: -- coverage run --include='jsonpath2/test/*' -m pytest -v +- coverage run --include='jsonpath2/*' -m pytest -v - coverage report -m --fail-under 100 - pip install . - python setup.py bdist_wheel @@ -17,9 +17,9 @@ script: jobs: include: - stage: lint - python: 3.6 + python: 3.5 script: pre-commit run -a - - python: 2.7 + - python: 3.6 script: pre-commit run -a - stage: deploy python: 3.6 @@ -33,14 +33,3 @@ jobs: distributions: "sdist bdist_wheel" on: tags: true - - python: 2.7 - script: skip - deploy: - skip_cleanup: true - provider: pypi - user: dmlb2000 - password: - secure: MeskzH+TIGe4iboe/VL0+3dSJ5yL/0f8CVH7AcLBmToEArWAOdx5v42fDbOGxSio9ezYdlGyP1fTeBZybIhCvnv44W43iXol2DSkNILdIkfPhsp/RWvZh+qylSldfwiS+gKRtWRCnMpItpmIDMpbBBf/malDLgo41JrhUMeJ2EgvAlRAIDN58VcgZFCyq/cYpo8aRnqvjAmHKwNwEVZP9fFttpys7JXnxxXgP66Yr7WZIVp1v3wv5KwJdqdLlWAL/ZDftTy61ad23sZn0sv3DWYRJ8eJxb2UXQssLyhoZDvAKFoymFhBWoNINpwYDkTZeSQkRPuf1BHgSYRe3nT+71IpXIBF0H7kbmStOttk2Z2kPrlprkZhoZlUwYhRmwgTKWPR2BCyzepDfNKFGoGLz1a98ymb/iqJbBhtuo2ZHH6xsodfKmjVRS8Cx6xCXYyUG5ZW9NK/luMYSNmM78vL6HNcY+yGZ1GS6kXtjUVLPh9CSXld6fuDY/sWWzpXWuhutbfM8+TKNXNF/JOnolJVAgpseDLW3rlNM8jKFLYv1ut/MR/qyoTeMzGe03BgMxX4o5LesVHaWQfvlDubCnXmeRdgYWuxGmFCmRphIu7d3+NwI/ZWWV6dhlqdID1YbdmQJcfz/NPslAn3sXvgLpsmiuSyr2FIuXBbhQozc+xstsQ= - distributions: "bdist_wheel" - on: - tags: true diff --git a/README.md b/README.md index cc68ef8..12641f9 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ The `jsonpath2.Path.Path` class represents a JSONPath. >>> import json >>> d = json.loads(s) {'hello':'Hello, world!'} ->>> from jsonpath2.Path import Path +>>> from jsonpath2.path import Path >>> p = Path.parse_str('$["hello"]') - + >>> list(map(lambda match_data: match_data.current_value, p.match(d))) ['Hello, world!'] >>> list(map(lambda match_data: match_data.node.tojsonpath(), p.match(d))) diff --git a/jsonpath2/Expression.py b/jsonpath2/Expression.py deleted file mode 100644 index f60d212..0000000 --- a/jsonpath2/Expression.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from abc import abstractmethod - -from jsonpath2.ToJSONPath import ToJSONPath - -class Expression(ToJSONPath): - def __init__(self): - super(Expression, self).__init__() - - def __eq__(self, other:object) -> bool: - return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) - - @abstractmethod - def evaluate(self, root_value:object, current_value:object) -> bool: - raise NotImplementedError() diff --git a/jsonpath2/Node.py b/jsonpath2/Node.py deleted file mode 100644 index 7115590..0000000 --- a/jsonpath2/Node.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from abc import abstractmethod -from typing import Generator - -from jsonpath2.ToJSONPath import ToJSONPath - -class MatchData(object): - def __init__(self, node, root_value, current_value): - super(MatchData, self).__init__() - - self.node = node - - self.root_value = root_value - - self.current_value = current_value - - def __eq__(self, other:object) -> bool: - return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) - -class Node(ToJSONPath): - def __init__(self): - super(Node, self).__init__() - - def __eq__(self, other:object) -> bool: - return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) - - @abstractmethod - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: - raise NotImplementedError() diff --git a/jsonpath2/Path.py b/jsonpath2/Path.py deleted file mode 100644 index 10b3a77..0000000 --- a/jsonpath2/Path.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from typing import Generator - -from jsonpath2.Node import MatchData -from jsonpath2.nodes.RootNode import RootNode -import jsonpath2.parser as _parser - -class Path(object): - def __init__(self, root_node:RootNode): - super(Path, self).__init__() - - if isinstance(root_node, RootNode): - self.root_node = root_node - else: - raise ValueError() - - def __eq__(self, other:object) -> bool: - return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) - - def __str__(self) -> str: - return self.root_node.tojsonpath() - - def match(self, root_value:object) -> Generator[MatchData, None, None]: - return self.root_node.match(root_value, root_value) - - @classmethod - def parse_file(self, *args, **kwargs): - return Path(_parser.parse_file(*args, **kwargs)) - - @classmethod - def parse_str(self, *args, **kwargs): - return Path(_parser.parse_str(*args, **kwargs)) diff --git a/jsonpath2/Subscript.py b/jsonpath2/Subscript.py deleted file mode 100644 index d7673ac..0000000 --- a/jsonpath2/Subscript.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from jsonpath2.Node import Node - -class Subscript(Node): - pass diff --git a/jsonpath2/ToJSONPath.py b/jsonpath2/ToJSONPath.py deleted file mode 100644 index c17725b..0000000 --- a/jsonpath2/ToJSONPath.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from abc import ABC, abstractmethod -from typing import Generator - -class ToJSONPath(ABC): - def __init__(self): - super(ToJSONPath, self).__init__() - - def tojsonpath(self) -> str: - return ''.join(list(self.__jsonpath__())) - - @abstractmethod - def __jsonpath__(self) -> Generator[str, None, None]: - raise NotImplementedError() diff --git a/jsonpath2/__init__.py b/jsonpath2/__init__.py index 3224a2a..a3668d7 100644 --- a/jsonpath2/__init__.py +++ b/jsonpath2/__init__.py @@ -1,3 +1,3 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -__import__('pkg_resources').declare_namespace(__name__) +"""The jsonpath2 module.""" diff --git a/jsonpath2/expression.py b/jsonpath2/expression.py new file mode 100644 index 0000000..4b6eb3c --- /dev/null +++ b/jsonpath2/expression.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Expression module.""" +from abc import abstractmethod +from jsonpath2.tojsonpath import ToJSONPath + + +class Expression(ToJSONPath): + """Add the expression methods to the jsonpath object.""" + + def __eq__(self, other: object) -> bool: + """Test self the same as the other object.""" + return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) + + @abstractmethod + def evaluate(self, root_value: object, current_value: object) -> bool: # pragma: no cover abstract method + """Abstract method to evaluate the expression.""" + raise NotImplementedError() diff --git a/jsonpath2/expressions/OperatorExpression.py b/jsonpath2/expressions/OperatorExpression.py deleted file mode 100644 index bddfd15..0000000 --- a/jsonpath2/expressions/OperatorExpression.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import json -from typing import Callable, Generator, List - -from jsonpath2.Expression import Expression -from jsonpath2.Node import Node - -class OperatorExpression(Expression): - pass - -class BinaryOperatorExpression(OperatorExpression): - def __init__(self, token:str, callback:Callable[[object, object], bool], left_node:Node, right_value:object): - super(BinaryOperatorExpression, self).__init__() - - self.token = token - - self.callback = callback - - self.left_node = left_node - - self.right_value = right_value - - def __jsonpath__(self) -> Generator[str, None, None]: - for left_node_token in self.left_node.__jsonpath__(): - yield left_node_token - - yield ' ' - yield self.token - yield ' ' - - yield json.dumps(self.right_value) - - def evaluate(self, root_value:object, current_value:object) -> bool: - return any(map(lambda left_node_match_data: self.callback(left_node_match_data.current_value, self.right_value), self.left_node.match(root_value, current_value))) - -LAMBDA_EQUAL_ = lambda x, y: x == y - -LAMBDA_NOT_EQUAL_ = lambda x, y: x != y - -class EqualBinaryOperatorExpression(BinaryOperatorExpression): - def __init__(self, *args, **kwargs): - super(EqualBinaryOperatorExpression, self).__init__('=', LAMBDA_EQUAL_, *args, **kwargs) - -class NotEqualBinaryOperatorExpression(BinaryOperatorExpression): - def __init__(self, *args, **kwargs): - super(NotEqualBinaryOperatorExpression, self).__init__('!=', LAMBDA_NOT_EQUAL_, *args, **kwargs) - -def _wrap_callback(callback): - def wrapped_callback(x, y): - if (isinstance(x, float) or isinstance(x, int)) and ((isinstance(y, float) or isinstance(y, int))): - return callback(x, y) - else: - return False - return wrapped_callback - -LAMBDA_LESS_THAN_ = _wrap_callback(lambda x, y: x < y) - -LAMBDA_LESS_THAN_OR_EQUAL_TO_ = _wrap_callback(lambda x, y: x <= y) - -LAMBDA_GREATER_THAN_ = _wrap_callback(lambda x, y: x > y) - -LAMBDA_GREATER_THAN_OR_EQUAL_TO_ = _wrap_callback(lambda x, y: x >= y) - -class LessThanBinaryOperatorExpression(BinaryOperatorExpression): - def __init__(self, *args, **kwargs): - super(LessThanBinaryOperatorExpression, self).__init__('<', LAMBDA_LESS_THAN_, *args, **kwargs) - -class LessThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): - def __init__(self, *args, **kwargs): - super(LessThanOrEqualToBinaryOperatorExpression, self).__init__('<=', LAMBDA_LESS_THAN_OR_EQUAL_TO_, *args, **kwargs) - -class GreaterThanBinaryOperatorExpression(BinaryOperatorExpression): - def __init__(self, *args, **kwargs): - super(GreaterThanBinaryOperatorExpression, self).__init__('>', LAMBDA_GREATER_THAN_, *args, **kwargs) - -class GreaterThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): - def __init__(self, *args, **kwargs): - super(GreaterThanOrEqualToBinaryOperatorExpression, self).__init__('>=', LAMBDA_GREATER_THAN_OR_EQUAL_TO_, *args, **kwargs) - -class UnaryOperatorExpression(OperatorExpression): - def __init__(self, token:str, callback:Callable[[bool], bool], expression:Expression): - super(UnaryOperatorExpression, self).__init__() - - self.token = token - - self.callback = callback - - self.expression = expression - - def __jsonpath__(self) -> Generator[str, None, None]: - yield self.token - yield ' ' - - if isinstance(self.expression, UnaryOperatorExpression) or isinstance(self.expression, VariadicOperatorExpression): - yield '(' - - for expression_token in self.expression.__jsonpath__(): - yield expression_token - - if isinstance(self.expression, UnaryOperatorExpression) or isinstance(self.expression, VariadicOperatorExpression): - yield ')' - - def evaluate(self, root_value:object, current_value:object) -> bool: - return self.callback(self.expression.evaluate(root_value, current_value)) - -LAMBDA_NOT_ = lambda x: not x - -class NotUnaryOperatorExpression(UnaryOperatorExpression): - def __init__(self, *args, **kwargs): - super(NotUnaryOperatorExpression, self).__init__('not', LAMBDA_NOT_, *args, **kwargs) - -class VariadicOperatorExpression(OperatorExpression): - def __init__(self, token:str, callback:Callable[[List[bool]], bool], expressions:List[Expression]=[]): - super(VariadicOperatorExpression, self).__init__() - - self.token = token - - self.callback = callback - - self.expressions = expressions - - def __jsonpath__(self) -> Generator[str, None, None]: - expressions_count = len(self.expressions) - - if expressions_count == 0: - pass - elif expressions_count == 1: - for expression_token in self.expressions[0].__jsonpath__(): - yield expression_token - else: - for expression_index, expression in enumerate(self.expressions): - if expression_index > 0: - yield ' ' - yield self.token - yield ' ' - - if isinstance(expression, VariadicOperatorExpression): - yield '(' - - for expression_token in expression.__jsonpath__(): - yield expression_token - - if isinstance(expression, VariadicOperatorExpression): - yield ')' - - def evaluate(self, root_value:object, current_value:object) -> bool: - return self.callback(map(lambda expression: expression.evaluate(root_value, current_value), self.expressions)) - -class AndVariadicOperatorExpression(VariadicOperatorExpression): - def __init__(self, *args, **kwargs): - super(AndVariadicOperatorExpression, self).__init__('and', all, *args, **kwargs) - -class OrVariadicOperatorExpression(VariadicOperatorExpression): - def __init__(self, *args, **kwargs): - super(OrVariadicOperatorExpression, self).__init__('or', any, *args, **kwargs) diff --git a/jsonpath2/expressions/SomeExpression.py b/jsonpath2/expressions/SomeExpression.py deleted file mode 100644 index 67344ed..0000000 --- a/jsonpath2/expressions/SomeExpression.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from typing import Generator - -from jsonpath2.Expression import Expression -from jsonpath2.Node import Node - -class SomeExpression(Expression): - def __init__(self, next_node:Node): - super(SomeExpression, self).__init__() - - self.next_node = next_node - - def __jsonpath__(self) -> Generator[str, None, None]: - return self.next_node.__jsonpath__() - - def evaluate(self, root_value:object, current_value:object) -> bool: - for next_node_match_data in self.next_node.match(root_value, current_value): - return True - - return False diff --git a/jsonpath2/expressions/__init__.py b/jsonpath2/expressions/__init__.py index 836e3e8..2ad25b0 100644 --- a/jsonpath2/expressions/__init__.py +++ b/jsonpath2/expressions/__init__.py @@ -1,2 +1,3 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +"""Expressions used in jsonpath module.""" diff --git a/jsonpath2/expressions/operator.py b/jsonpath2/expressions/operator.py new file mode 100644 index 0000000..ebd77a4 --- /dev/null +++ b/jsonpath2/expressions/operator.py @@ -0,0 +1,229 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""The operator expression module.""" +import json +from typing import Callable, Generator, List +from jsonpath2.expression import Expression +from jsonpath2.node import Node + + +class OperatorExpression(Expression): + """Basic operator expression object.""" + + def __jsonpath__(self) -> Generator[str, None, None]: # pragma: no cover abstract method + """Abstract method to return the jsonpath.""" + pass + + def evaluate(self, root_value: object, current_value: object) -> bool: # pragma: no cover abstract method + """Abstract method to evaluate the expression.""" + pass + + +class BinaryOperatorExpression(OperatorExpression): + """Binary operator expression.""" + + def __init__(self, token: str, callback: Callable[[object, object], bool], left_node: Node, right_value: object): + """Constructor save the left right and token.""" + super(BinaryOperatorExpression, self).__init__() + self.token = token + self.callback = callback + self.left_node = left_node + self.right_value = right_value + + def __jsonpath__(self) -> Generator[str, None, None]: + """Return the string json path of this expression.""" + for left_node_token in self.left_node.__jsonpath__(): + yield left_node_token + yield ' ' + yield self.token + yield ' ' + yield json.dumps(self.right_value) + + def evaluate(self, root_value: object, current_value: object) -> bool: + """Evaluate the left and right values given the token.""" + return any( + map( + lambda left_node_match_data: self.callback( + left_node_match_data.current_value, + self.right_value + ), + self.left_node.match(root_value, current_value) + ) + ) + + +class EqualBinaryOperatorExpression(BinaryOperatorExpression): + """Binary Equal operator expression.""" + + def __init__(self, *args, **kwargs): + """Constructor with the right function.""" + super(EqualBinaryOperatorExpression, self).__init__( + '=', lambda x, y: x == y, + *args, **kwargs) + + +class NotEqualBinaryOperatorExpression(BinaryOperatorExpression): + """Binary Equal operator expression.""" + + def __init__(self, *args, **kwargs): + """Constructor with the right function.""" + super(NotEqualBinaryOperatorExpression, self).__init__( + '!=', lambda x, y: x != y, + *args, **kwargs) + + +def _wrap_callback(callback): + """Decorator to verify types of arguments.""" + def wrapped_callback(x_obj, y_obj): + """Check the types of the arguments are int or floats.""" + if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)): + return callback(x_obj, y_obj) + return False + return wrapped_callback + + +class LessThanBinaryOperatorExpression(BinaryOperatorExpression): + """Expression to handle less than.""" + + def __init__(self, *args, **kwargs): + """Construct the binary operator with appropriate method.""" + @_wrap_callback + def _less_than_(x_num, y_num): + """Perform a less than on int or float.""" + return x_num < y_num + super(LessThanBinaryOperatorExpression, self).__init__( + '<', _less_than_, *args, **kwargs) + + +class LessThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): + """Expression to handle less than or equal.""" + + def __init__(self, *args, **kwargs): + """Construct the binary operator with appropriate method.""" + @_wrap_callback + def _less_than_or_equal_to_(x_num, y_num): + """Perform a less than or equal on int or float.""" + return x_num <= y_num + super(LessThanOrEqualToBinaryOperatorExpression, self).__init__( + '<=', _less_than_or_equal_to_, *args, **kwargs) + + +class GreaterThanBinaryOperatorExpression(BinaryOperatorExpression): + """Expression to handle greater than.""" + + def __init__(self, *args, **kwargs): + """Construct the binary operator with appropriate method.""" + @_wrap_callback + def _greater_than_(x_num, y_num): + """Perform a greater than on int or float.""" + return x_num > y_num + super(GreaterThanBinaryOperatorExpression, self).__init__( + '>', _greater_than_, *args, **kwargs) + + +class GreaterThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): + """Expression to handle greater than or equal.""" + + def __init__(self, *args, **kwargs): + """Construct the binary operator with appropriate method.""" + @_wrap_callback + def _greater_than_or_equal_to_(x_num, y_num): + """Perform a greater than on int or float.""" + return x_num >= y_num + super(GreaterThanOrEqualToBinaryOperatorExpression, self).__init__( + '>=', _greater_than_or_equal_to_, *args, **kwargs) + + +class UnaryOperatorExpression(OperatorExpression): + """Unary operator expression base class.""" + + def __init__(self, token: str, callback: Callable[[bool], bool], expression: Expression): + """Save the callback operator the token and expression.""" + super(UnaryOperatorExpression, self).__init__() + self.token = token + self.callback = callback + self.expression = expression + + def __jsonpath__(self) -> Generator[str, None, None]: + """Generate the jsonpath for a unary operator.""" + yield self.token + yield ' ' + if isinstance(self.expression, (UnaryOperatorExpression, VariadicOperatorExpression)): + yield '(' + for expression_token in self.expression.__jsonpath__(): + yield expression_token + if isinstance(self.expression, (UnaryOperatorExpression, VariadicOperatorExpression)): + yield ')' + + def evaluate(self, root_value: object, current_value: object) -> bool: + """Evaluate the unary expression.""" + return self.callback(self.expression.evaluate(root_value, current_value)) + + +class NotUnaryOperatorExpression(UnaryOperatorExpression): + """Unary class to handle the 'not' expression.""" + + def __init__(self, *args, **kwargs): + """Call the unary operator expression with the right method.""" + def _not_(x_obj): + """The unary not function.""" + return not x_obj + super(NotUnaryOperatorExpression, self).__init__( + 'not', _not_, *args, **kwargs) + + +class VariadicOperatorExpression(OperatorExpression): + """Base class to handle boolean expressions of variadic type.""" + + def __init__(self, token: str, callback: Callable[[List[bool]], bool], expressions: List[Expression] = None): + """Save the operator token, callback and the list of expressions.""" + super(VariadicOperatorExpression, self).__init__() + self.token = token + self.callback = callback + self.expressions = expressions if expressions else [] + + def __jsonpath__(self) -> Generator[str, None, None]: + """Yield the string of the expression.""" + expressions_count = len(self.expressions) + if expressions_count == 0: + pass + elif expressions_count == 1: + for expression_token in self.expressions[0].__jsonpath__(): + yield expression_token + else: + for expression_index, expression in enumerate(self.expressions): + if expression_index > 0: + yield ' ' + yield self.token + yield ' ' + + if isinstance(expression, VariadicOperatorExpression): + yield '(' + + for expression_token in expression.__jsonpath__(): + yield expression_token + + if isinstance(expression, VariadicOperatorExpression): + yield ')' + + def evaluate(self, root_value: object, current_value: object) -> bool: + """Evaluate the expressions against the boolean callback.""" + return self.callback(map(lambda expression: expression.evaluate(root_value, current_value), self.expressions)) + + +class AndVariadicOperatorExpression(VariadicOperatorExpression): + """The boolean 'and' operator expression.""" + + def __init__(self, *args, **kwargs): + """Call the super with the 'and' boolean method.""" + super(AndVariadicOperatorExpression, self).__init__( + 'and', all, *args, **kwargs) + + +class OrVariadicOperatorExpression(VariadicOperatorExpression): + """The boolean 'or' operator expression.""" + + def __init__(self, *args, **kwargs): + """Call the super with the 'or' boolean method.""" + super(OrVariadicOperatorExpression, self).__init__( + 'or', any, *args, **kwargs) diff --git a/jsonpath2/expressions/some.py b/jsonpath2/expressions/some.py new file mode 100644 index 0000000..c128718 --- /dev/null +++ b/jsonpath2/expressions/some.py @@ -0,0 +1,25 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Some expression module.""" +from typing import Generator +from jsonpath2.expression import Expression +from jsonpath2.node import Node + + +class SomeExpression(Expression): + """The some expression class.""" + + def __init__(self, next_node: Node): + """Save the next node.""" + super(SomeExpression, self).__init__() + self.next_node = next_node + + def __jsonpath__(self) -> Generator[str, None, None]: + """Return the next nodes jsonpath.""" + return self.next_node.__jsonpath__() + + def evaluate(self, root_value: object, current_value: object) -> bool: + """Evaluate the next node.""" + for _next_node_match_data in self.next_node.match(root_value, current_value): + return True + return False diff --git a/jsonpath2/node.py b/jsonpath2/node.py new file mode 100644 index 0000000..d22a826 --- /dev/null +++ b/jsonpath2/node.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""The parse tree node module.""" +from abc import abstractmethod +from typing import Generator +from jsonpath2.tojsonpath import ToJSONPath + + +# pylint: disable=too-few-public-methods +class MatchData(object): + """Match data object for storing node values.""" + + def __init__(self, node, root_value, current_value): + """Constructor to save root and current node values.""" + super(MatchData, self).__init__() + self.node = node + self.root_value = root_value + self.current_value = current_value + + def __eq__(self, other: object) -> bool: + """Test if two MatchData objects are the same.""" + return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) +# pylint: enable=too-few-public-methods + + +class Node(ToJSONPath): + """Node object for the jsonpath parsetree.""" + + def __eq__(self, other: object) -> bool: + """Determine if two Nodes are the same.""" + return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) + + @abstractmethod + def match( + self, + root_value: object, + current_value: object) -> Generator[MatchData, None, None]: # pragma: no cover abstract method. + """Abstract method to determine a node match.""" + raise NotImplementedError() diff --git a/jsonpath2/nodes/CurrentNode.py b/jsonpath2/nodes/CurrentNode.py deleted file mode 100644 index 3bbb3de..0000000 --- a/jsonpath2/nodes/CurrentNode.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from typing import Generator - -from jsonpath2.Node import MatchData, Node - -class CurrentNode(Node): - def __init__(self, next_node:Node): - super(CurrentNode, self).__init__() - - self.next_node = next_node - - def __jsonpath__(self) -> Generator[str, None, None]: - yield '@' - - for next_node_token in self.next_node.__jsonpath__(): - yield next_node_token - - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: - return map(lambda next_node_match_data: MatchData(CurrentNode(next_node_match_data.node), next_node_match_data.root_value, next_node_match_data.current_value), self.next_node.match(root_value, current_value)) diff --git a/jsonpath2/nodes/RecursiveDescentNode.py b/jsonpath2/nodes/RecursiveDescentNode.py deleted file mode 100644 index 57771ee..0000000 --- a/jsonpath2/nodes/RecursiveDescentNode.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import itertools -from typing import Generator - -from jsonpath2.Node import MatchData, Node - -from jsonpath2.nodes.SubscriptNode import SubscriptNode - -from jsonpath2.subscripts.WildcardSubscript import WildcardSubscript - -class RecursiveDescentNode(Node): - def __init__(self, next_node:Node): - super(RecursiveDescentNode, self).__init__() - - self.next_node = next_node - - def __jsonpath__(self) -> Generator[str, None, None]: - yield '..' - - for next_node_token in self.next_node.__jsonpath__(): - yield next_node_token - - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: - # NOTE Depth-first - return itertools.chain(self.next_node.match(root_value, current_value), SubscriptNode(self, [WildcardSubscript()]).match(root_value, current_value)) diff --git a/jsonpath2/nodes/RootNode.py b/jsonpath2/nodes/RootNode.py deleted file mode 100644 index bd65549..0000000 --- a/jsonpath2/nodes/RootNode.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from typing import Generator - -from jsonpath2.Node import MatchData, Node - -class RootNode(Node): - def __init__(self, next_node:Node): - super(RootNode, self).__init__() - - self.next_node = next_node - - def __jsonpath__(self) -> Generator[str, None, None]: - yield '$' - - for next_node_token in self.next_node.__jsonpath__(): - yield next_node_token - - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: - return map(lambda next_node_match_data: MatchData(RootNode(next_node_match_data.node), next_node_match_data.root_value, next_node_match_data.current_value), self.next_node.match(root_value, root_value)) diff --git a/jsonpath2/nodes/TerminalNode.py b/jsonpath2/nodes/TerminalNode.py deleted file mode 100644 index bd93ecd..0000000 --- a/jsonpath2/nodes/TerminalNode.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from typing import Generator - -from jsonpath2.Node import MatchData, Node - -class TerminalNode(Node): - def __init__(self): - super(TerminalNode, self).__init__() - - def __jsonpath__(self) -> Generator[str, None, None]: - return [] - - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: - return [MatchData(self, root_value, current_value)] diff --git a/jsonpath2/nodes/__init__.py b/jsonpath2/nodes/__init__.py index 836e3e8..378a3bd 100644 --- a/jsonpath2/nodes/__init__.py +++ b/jsonpath2/nodes/__init__.py @@ -1,2 +1,3 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +"""Nodes module contains all the node definitions.""" diff --git a/jsonpath2/nodes/current.py b/jsonpath2/nodes/current.py new file mode 100644 index 0000000..97621cf --- /dev/null +++ b/jsonpath2/nodes/current.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""The current node module.""" +from typing import Generator +from jsonpath2.node import MatchData, Node + + +class CurrentNode(Node): + """Current node class to store current node info.""" + + def __init__(self, next_node: Node): + """Save the current node.""" + super(CurrentNode, self).__init__() + self.next_node = next_node + + def __jsonpath__(self) -> Generator[str, None, None]: + """Return the current node string.""" + yield '@' + for next_node_token in self.next_node.__jsonpath__(): + yield next_node_token + + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match the current value and root value.""" + return map( + lambda next_node_match_data: MatchData( + CurrentNode(next_node_match_data.node), + next_node_match_data.root_value, + next_node_match_data.current_value + ), + self.next_node.match(root_value, current_value) + ) diff --git a/jsonpath2/nodes/recursivedescent.py b/jsonpath2/nodes/recursivedescent.py new file mode 100644 index 0000000..140d733 --- /dev/null +++ b/jsonpath2/nodes/recursivedescent.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Recursive descent module.""" +import itertools +from typing import Generator +from jsonpath2.node import MatchData, Node +from jsonpath2.nodes.subscript import SubscriptNode +from jsonpath2.subscripts.wildcard import WildcardSubscript + + +class RecursiveDescentNode(Node): + """Recursive descent node class.""" + + def __init__(self, next_node: Node): + """Save the next node.""" + super(RecursiveDescentNode, self).__init__() + self.next_node = next_node + + def __jsonpath__(self) -> Generator[str, None, None]: + """Dump the string for the previous node.""" + yield '..' + for next_node_token in self.next_node.__jsonpath__(): + yield next_node_token + + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match the root value with the current value.""" + # NOTE Depth-first + return itertools.chain( + self.next_node.match(root_value, current_value), + SubscriptNode(self, [WildcardSubscript()]).match( + root_value, current_value) + ) diff --git a/jsonpath2/nodes/root.py b/jsonpath2/nodes/root.py new file mode 100644 index 0000000..729a49a --- /dev/null +++ b/jsonpath2/nodes/root.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Root node type.""" +from typing import Generator +from jsonpath2.node import MatchData, Node + + +class RootNode(Node): + """Root node to start the process.""" + + def __init__(self, next_node: Node): + """Save the next node object.""" + super(RootNode, self).__init__() + self.next_node = next_node + + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match the root value with the current value.""" + return map( + lambda next_node_match_data: MatchData( + RootNode(next_node_match_data.node), + next_node_match_data.root_value, + next_node_match_data.current_value + ), + self.next_node.match(root_value, root_value) + ) + + def __jsonpath__(self) -> Generator[str, None, None]: + """Yield the start character string and the next node.""" + yield '$' + for next_node_token in self.next_node.__jsonpath__(): + yield next_node_token diff --git a/jsonpath2/nodes/SubscriptNode.py b/jsonpath2/nodes/subscript.py similarity index 54% rename from jsonpath2/nodes/SubscriptNode.py rename to jsonpath2/nodes/subscript.py index 62e9e55..099def4 100644 --- a/jsonpath2/nodes/SubscriptNode.py +++ b/jsonpath2/nodes/subscript.py @@ -1,45 +1,48 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +"""The subscript module.""" from typing import Generator, List +from jsonpath2.node import MatchData, Node +from jsonpath2.subscript import Subscript +from jsonpath2.nodes.terminal import TerminalNode -from jsonpath2.Node import MatchData, Node -from jsonpath2.Subscript import Subscript - -from jsonpath2.nodes.TerminalNode import TerminalNode class SubscriptNode(Node): - def __init__(self, next_node:Node, subscripts:List[Subscript]=[]): - super(SubscriptNode, self).__init__() + """The subscript node class to handle '[]'.""" + def __init__(self, next_node: Node, subscripts: List[Subscript] = None): + """Save the next node and subscripts.""" + super(SubscriptNode, self).__init__() self.next_node = next_node - - self.subscripts = subscripts + self.subscripts = subscripts if subscripts else [] def __jsonpath__(self) -> Generator[str, None, None]: + """Yield the subscript characters and subscript classes.""" yield '[' - for subscript_index, subscript in enumerate(self.subscripts): if subscript_index > 0: yield ',' - for subscript_token in subscript.__jsonpath__(): yield subscript_token - yield ']' - for next_node_token in self.next_node.__jsonpath__(): yield next_node_token - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match root value and current value for subscripts.""" for subscript in self.subscripts: for subscript_match_data in subscript.match(root_value, current_value): - for next_node_match_data in self.next_node.match(subscript_match_data.root_value, subscript_match_data.current_value): + for next_node_match_data in self.next_node.match( + subscript_match_data.root_value, subscript_match_data.current_value): if isinstance(subscript_match_data.node, TerminalNode): yield next_node_match_data elif isinstance(subscript_match_data.node, SubscriptNode): if isinstance(subscript_match_data.node.next_node, TerminalNode): - yield MatchData(SubscriptNode(next_node_match_data.node, subscript_match_data.node.subscripts), next_node_match_data.root_value, next_node_match_data.current_value) + yield MatchData( + SubscriptNode( + next_node_match_data.node, subscript_match_data.node.subscripts), + next_node_match_data.root_value, next_node_match_data.current_value + ) else: raise ValueError() else: diff --git a/jsonpath2/nodes/terminal.py b/jsonpath2/nodes/terminal.py new file mode 100644 index 0000000..eade3aa --- /dev/null +++ b/jsonpath2/nodes/terminal.py @@ -0,0 +1,17 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Terminal node object.""" +from typing import Generator +from jsonpath2.node import MatchData, Node + + +class TerminalNode(Node): + """Terminal node class.""" + + def __jsonpath__(self) -> Generator[str, None, None]: + """Return the empty array not yield.""" + return [] + + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match a termainal node.""" + return [MatchData(self, root_value, current_value)] diff --git a/jsonpath2/parser/__init__.py b/jsonpath2/parser/__init__.py index caa771c..c142be2 100644 --- a/jsonpath2/parser/__init__.py +++ b/jsonpath2/parser/__init__.py @@ -1,120 +1,121 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +"""The jsonpath parser module.""" import antlr4 - -from jsonpath2.expressions.OperatorExpression import AndVariadicOperatorExpression, EqualBinaryOperatorExpression, GreaterThanBinaryOperatorExpression, GreaterThanOrEqualToBinaryOperatorExpression, LessThanBinaryOperatorExpression, LessThanOrEqualToBinaryOperatorExpression, NotEqualBinaryOperatorExpression, NotUnaryOperatorExpression, OrVariadicOperatorExpression -from jsonpath2.expressions.SomeExpression import SomeExpression - -from jsonpath2.nodes.CurrentNode import CurrentNode -from jsonpath2.nodes.RecursiveDescentNode import RecursiveDescentNode -from jsonpath2.nodes.RootNode import RootNode -from jsonpath2.nodes.SubscriptNode import SubscriptNode -from jsonpath2.nodes.TerminalNode import TerminalNode +from jsonpath2.expressions.operator import AndVariadicOperatorExpression, EqualBinaryOperatorExpression, \ + GreaterThanBinaryOperatorExpression, GreaterThanOrEqualToBinaryOperatorExpression, \ + LessThanBinaryOperatorExpression, LessThanOrEqualToBinaryOperatorExpression, \ + NotEqualBinaryOperatorExpression, NotUnaryOperatorExpression, OrVariadicOperatorExpression +from jsonpath2.expressions.some import SomeExpression +from jsonpath2.nodes.current import CurrentNode +from jsonpath2.nodes.recursivedescent import RecursiveDescentNode +from jsonpath2.nodes.root import RootNode +from jsonpath2.nodes.subscript import SubscriptNode +from jsonpath2.nodes.terminal import TerminalNode from jsonpath2.parser.JSONPathLexer import JSONPathLexer from jsonpath2.parser.JSONPathListener import JSONPathListener from jsonpath2.parser.JSONPathParser import JSONPathParser -from jsonpath2.subscripts.ArrayIndexSubscript import ArrayIndexSubscript -from jsonpath2.subscripts.ArraySliceSubscript import ArraySliceSubscript -from jsonpath2.subscripts.FilterSubscript import FilterSubscript -from jsonpath2.subscripts.ObjectIndexSubscript import ObjectIndexSubscript -from jsonpath2.subscripts.WildcardSubscript import WildcardSubscript +from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript +from jsonpath2.subscripts.arrayslice import ArraySliceSubscript +from jsonpath2.subscripts.filter import FilterSubscript +from jsonpath2.subscripts.objectindex import ObjectIndexSubscript +from jsonpath2.subscripts.wildcard import WildcardSubscript + class _ConsoleErrorListener(antlr4.error.ErrorListener.ConsoleErrorListener): + # pylint: disable=too-many-arguments + # this is an error handling issue with antlr def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): raise ValueError('line {}:{} {}'.format(line, column, msg)) + # pylint: enable=too-many-arguments + class _JSONPathListener(JSONPathListener): - def __init__(self, _stack=[]): + def __init__(self, _stack=None): super(_JSONPathListener, self).__init__() + self._stack = _stack if _stack else [] - self._stack = _stack - - def exitJsonpath(self, ctx:JSONPathParser.JsonpathContext): + def exitJsonpath(self, ctx: JSONPathParser.JsonpathContext): if ctx.getToken(JSONPathParser.ROOT_VALUE, 0) is not None: if bool(ctx.subscript()): next_node = self._stack.pop() else: next_node = TerminalNode() - self._stack.append(RootNode(next_node)) else: raise ValueError() - def exitSubscript(self, ctx:JSONPathParser.SubscriptContext): + # pylint: disable=too-many-branches + # It would sure be nice if we had a case statement. + def exitSubscript(self, ctx: JSONPathParser.SubscriptContext): if ctx.getToken(JSONPathParser.RECURSIVE_DESCENT, 0) is not None: if bool(ctx.subscript()): next_node = self._stack.pop() else: next_node = TerminalNode() - if bool(ctx.subscriptableBareword()): subscriptable_nodes = [self._stack.pop()] elif bool(ctx.subscriptables()): subscriptable_nodes = self._stack.pop() else: raise ValueError() - - self._stack.append(RecursiveDescentNode(SubscriptNode(next_node, subscriptable_nodes))) + self._stack.append(RecursiveDescentNode( + SubscriptNode(next_node, subscriptable_nodes))) elif ctx.getToken(JSONPathParser.SUBSCRIPT, 0) is not None: if bool(ctx.subscript()): next_node = self._stack.pop() else: next_node = TerminalNode() - if bool(ctx.subscriptableBareword()): subscriptable_nodes = [self._stack.pop()] else: raise ValueError() - self._stack.append(SubscriptNode(next_node, subscriptable_nodes)) else: if bool(ctx.subscript()): next_node = self._stack.pop() else: next_node = TerminalNode() - if bool(ctx.subscriptables()): subscriptable_nodes = self._stack.pop() else: raise ValueError() - self._stack.append(SubscriptNode(next_node, subscriptable_nodes)) + # pylint: enable=too-many-branches - def exitSubscriptables(self, ctx:JSONPathParser.SubscriptablesContext): + def exitSubscriptables(self, ctx: JSONPathParser.SubscriptablesContext): subscriptable_nodes = [] - - for subscriptable_ctx in ctx.getTypedRuleContexts(JSONPathParser.SubscriptableContext): + for _subscriptable_ctx in ctx.getTypedRuleContexts(JSONPathParser.SubscriptableContext): subscriptable_node = self._stack.pop() - subscriptable_nodes.insert(0, subscriptable_node) - self._stack.append(subscriptable_nodes) - def exitSubscriptableBareword(self, ctx:JSONPathParser.SubscriptableBarewordContext): + def exitSubscriptableBareword(self, ctx: JSONPathParser.SubscriptableBarewordContext): if bool(ctx.ID()): text = ctx.ID().getText() - self._stack.append(ObjectIndexSubscript(text)) elif ctx.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) is not None: self._stack.append(WildcardSubscript()) else: raise ValueError() - def exitSubscriptable(self, ctx:JSONPathParser.SubscriptableContext): + def exitSubscriptable(self, ctx: JSONPathParser.SubscriptableContext): if bool(ctx.STRING()): text = ctx.STRING().getText()[1:-1] self._stack.append(ObjectIndexSubscript(text)) elif bool(ctx.NUMBER()): if ctx.getToken(JSONPathParser.COLON, 0) is not None: - start = int(ctx.NUMBER(0).getText()) if bool(ctx.NUMBER(0)) else None + start = int(ctx.NUMBER(0).getText()) if bool( + ctx.NUMBER(0)) else None - end = int(ctx.NUMBER(1).getText()) if bool(ctx.NUMBER(1)) else None + end = int(ctx.NUMBER(1).getText()) if bool( + ctx.NUMBER(1)) else None - step = int(ctx.NUMBER(2).getText()) if bool(ctx.NUMBER(2)) else None + step = int(ctx.NUMBER(2).getText()) if bool( + ctx.NUMBER(2)) else None self._stack.append(ArraySliceSubscript(start, end, step)) else: @@ -130,7 +131,7 @@ def exitSubscriptable(self, ctx:JSONPathParser.SubscriptableContext): else: raise ValueError() - def exitAndExpression(self, ctx:JSONPathParser.AndExpressionContext): + def exitAndExpression(self, ctx: JSONPathParser.AndExpressionContext): expressions = [] if bool(ctx.andExpression()): @@ -148,14 +149,14 @@ def exitAndExpression(self, ctx:JSONPathParser.AndExpressionContext): else: expressions.insert(0, expression) - if len(expressions) == 0: + if not expressions: raise ValueError() if len(expressions) == 1: self._stack.append(expressions[0]) else: self._stack.append(AndVariadicOperatorExpression(expressions)) - def exitOrExpression(self, ctx:JSONPathParser.OrExpressionContext): + def exitOrExpression(self, ctx: JSONPathParser.OrExpressionContext): expressions = [] if bool(ctx.orExpression()): @@ -173,14 +174,15 @@ def exitOrExpression(self, ctx:JSONPathParser.OrExpressionContext): else: expressions.insert(0, expression) - if len(expressions) == 0: + if not expressions: raise ValueError() if len(expressions) == 1: self._stack.append(expressions[0]) else: self._stack.append(OrVariadicOperatorExpression(expressions)) - def exitNotExpression(self, ctx:JSONPathParser.NotExpressionContext): + # pylint: disable=too-many-branches + def exitNotExpression(self, ctx: JSONPathParser.NotExpressionContext): if ctx.getToken(JSONPathParser.NOT, 0) is not None: expression = self._stack.pop() @@ -188,7 +190,8 @@ def exitNotExpression(self, ctx:JSONPathParser.NotExpressionContext): self._stack.append(expression.expression) else: self._stack.append(NotUnaryOperatorExpression(expression)) - elif (ctx.getToken(JSONPathParser.ROOT_VALUE, 0) is not None) or (ctx.getToken(JSONPathParser.CURRENT_VALUE, 0) is not None): + elif (ctx.getToken(JSONPathParser.ROOT_VALUE, 0) is not None) or \ + (ctx.getToken(JSONPathParser.CURRENT_VALUE, 0) is not None): if bool(ctx.value()): right_value = self._stack.pop() @@ -205,17 +208,23 @@ def exitNotExpression(self, ctx:JSONPathParser.NotExpressionContext): raise ValueError() if ctx.getToken(JSONPathParser.EQ, 0) is not None: - self._stack.append(EqualBinaryOperatorExpression(left_node, right_value)) + self._stack.append( + EqualBinaryOperatorExpression(left_node, right_value)) elif ctx.getToken(JSONPathParser.NE, 0) is not None: - self._stack.append(NotEqualBinaryOperatorExpression(left_node, right_value)) + self._stack.append( + NotEqualBinaryOperatorExpression(left_node, right_value)) elif ctx.getToken(JSONPathParser.LT, 0) is not None: - self._stack.append(LessThanBinaryOperatorExpression(left_node, right_value)) + self._stack.append( + LessThanBinaryOperatorExpression(left_node, right_value)) elif ctx.getToken(JSONPathParser.LE, 0) is not None: - self._stack.append(LessThanOrEqualToBinaryOperatorExpression(left_node, right_value)) + self._stack.append( + LessThanOrEqualToBinaryOperatorExpression(left_node, right_value)) elif ctx.getToken(JSONPathParser.GT, 0) is not None: - self._stack.append(GreaterThanBinaryOperatorExpression(left_node, right_value)) + self._stack.append( + GreaterThanBinaryOperatorExpression(left_node, right_value)) elif ctx.getToken(JSONPathParser.GE, 0) is not None: - self._stack.append(GreaterThanOrEqualToBinaryOperatorExpression(left_node, right_value)) + self._stack.append( + GreaterThanOrEqualToBinaryOperatorExpression(left_node, right_value)) else: raise ValueError() else: @@ -227,8 +236,9 @@ def exitNotExpression(self, ctx:JSONPathParser.NotExpressionContext): self._stack.append(SomeExpression(CurrentNode(next_node))) else: pass + # pylint: enable=too-many-branches - def exitObj(self, ctx:JSONPathParser.ObjContext): + def exitObj(self, ctx: JSONPathParser.ObjContext): values = [] for pair_ctx in ctx.getTypedRuleContexts(JSONPathParser.PairContext): @@ -245,17 +255,17 @@ def exitObj(self, ctx:JSONPathParser.ObjContext): self._stack.append(obj) - def exitArray(self, ctx:JSONPathParser.ArrayContext): + def exitArray(self, ctx: JSONPathParser.ArrayContext): array = [] - for value_ctx in ctx.getTypedRuleContexts(JSONPathParser.ValueContext): + for _value_ctx in ctx.getTypedRuleContexts(JSONPathParser.ValueContext): value = self._stack.pop() array.insert(0, value) self._stack.append(array) - def exitValue(self, ctx:JSONPathParser.ValueContext): + def exitValue(self, ctx: JSONPathParser.ValueContext): if bool(ctx.STRING()): text = ctx.STRING().getText()[1:-1] @@ -280,16 +290,21 @@ def exitValue(self, ctx:JSONPathParser.ValueContext): else: raise ValueError() + class _JSONPathParser(JSONPathParser): + # pylint: disable=invalid-name + # this is a antlr ism... def tryCast(self, cls): + """Override the antlr tryCast method.""" try: cls(self._input.LT(-1).text) - return True except ValueError: return False + # pylint: enable=invalid-name + -def _parse_input_stream(input_stream:antlr4.InputStream) -> RootNode: +def _parse_input_stream(input_stream: antlr4.InputStream) -> RootNode: error_listener = _ConsoleErrorListener() lexer = JSONPathLexer(input_stream) @@ -309,14 +324,18 @@ def _parse_input_stream(input_stream:antlr4.InputStream) -> RootNode: walker = antlr4.ParseTreeWalker() walker.walk(listener, tree) + # pylint: disable=protected-access return listener._stack.pop() + # pylint: enable=protected-access + def parse_file(*args, **kwargs) -> RootNode: + """Parse a json path from a file.""" file_stream = antlr4.FileStream(*args, **kwargs) - return _parse_input_stream(file_stream) + def parse_str(*args, **kwargs) -> RootNode: + """Parse a json path from a string.""" input_stream = antlr4.InputStream(*args, **kwargs) - return _parse_input_stream(input_stream) diff --git a/jsonpath2/path.py b/jsonpath2/path.py new file mode 100644 index 0000000..7f771eb --- /dev/null +++ b/jsonpath2/path.py @@ -0,0 +1,41 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""The path module.""" +from typing import Generator +from jsonpath2.node import MatchData +from jsonpath2.nodes.root import RootNode +import jsonpath2.parser as _parser + + +class Path(object): + """Path parsetree object.""" + + def __init__(self, root_node: RootNode): + """Constructor saving the root node.""" + super(Path, self).__init__() + if isinstance(root_node, RootNode): + self.root_node = root_node + else: + raise ValueError() + + def __eq__(self, other: object) -> bool: + """Check to see if two paths are the same.""" + return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__) + + def __str__(self) -> str: + """Stringify the path object.""" + return self.root_node.tojsonpath() + + def match(self, root_value: object) -> Generator[MatchData, None, None]: + """Match root value of the path.""" + return self.root_node.match(root_value, root_value) + + @classmethod + def parse_file(cls, *args, **kwargs): + """A handler to parse a file.""" + return cls(_parser.parse_file(*args, **kwargs)) + + @classmethod + def parse_str(cls, *args, **kwargs): + """A handler to parse a string.""" + return cls(_parser.parse_str(*args, **kwargs)) diff --git a/jsonpath2/subscript.py b/jsonpath2/subscript.py new file mode 100644 index 0000000..59e1c9e --- /dev/null +++ b/jsonpath2/subscript.py @@ -0,0 +1,20 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""The Subscript module.""" +from typing import Generator +from jsonpath2.node import Node, MatchData + + +class Subscript(Node): + """Subscript has no value beyond a node other than type.""" + + def __jsonpath__(self) -> Generator[str, None, None]: # pragma: no cover abstract method + """Abstract method to return the jsonpath.""" + pass + + def match( + self, + root_value: object, + current_value: object) -> Generator[MatchData, None, None]: # pragma: no cover abstract method. + """Abstract method to determine a node match.""" + pass diff --git a/jsonpath2/subscripts/FilterSubscript.py b/jsonpath2/subscripts/FilterSubscript.py deleted file mode 100644 index 4829334..0000000 --- a/jsonpath2/subscripts/FilterSubscript.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from typing import Generator - -from jsonpath2.Expression import Expression -from jsonpath2.Node import MatchData -from jsonpath2.Subscript import Subscript - -from jsonpath2.nodes.TerminalNode import TerminalNode - -class FilterSubscript(Subscript): - def __init__(self, expression:Expression): - super(FilterSubscript, self).__init__() - - self.expression = expression - - def __jsonpath__(self) -> Generator[str, None, None]: - yield '?' - yield '(' - - for expression_token in self.expression.__jsonpath__(): - yield expression_token - - yield ')' - - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: - if self.expression.evaluate(root_value, current_value): - return [MatchData(TerminalNode(), root_value, current_value)] - else: - return [] diff --git a/jsonpath2/subscripts/ObjectIndexSubscript.py b/jsonpath2/subscripts/ObjectIndexSubscript.py deleted file mode 100644 index 50bfccc..0000000 --- a/jsonpath2/subscripts/ObjectIndexSubscript.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import json -from typing import Generator - -from jsonpath2.Node import MatchData -from jsonpath2.Subscript import Subscript - -from jsonpath2.nodes.SubscriptNode import SubscriptNode -from jsonpath2.nodes.TerminalNode import TerminalNode - -class ObjectIndexSubscript(Subscript): - def __init__(self, index:str): - super(ObjectIndexSubscript, self).__init__() - - self.index = index - - def __jsonpath__(self) -> Generator[str, None, None]: - yield json.dumps(self.index) - - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: - if isinstance(current_value, dict) and (self.index in current_value): - return [MatchData(SubscriptNode(TerminalNode(), [self]), root_value, current_value[self.index])] - else: - return [] diff --git a/jsonpath2/subscripts/WildcardSubscript.py b/jsonpath2/subscripts/WildcardSubscript.py deleted file mode 100644 index eed9903..0000000 --- a/jsonpath2/subscripts/WildcardSubscript.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import itertools -from typing import Generator - -from jsonpath2.Node import MatchData -from jsonpath2.Subscript import Subscript - -from jsonpath2.subscripts.ArrayIndexSubscript import ArrayIndexSubscript -from jsonpath2.subscripts.ObjectIndexSubscript import ObjectIndexSubscript - -class WildcardSubscript(Subscript): - def __init__(self): - super(WildcardSubscript, self).__init__() - - def __jsonpath__(self) -> Generator[str, None, None]: - yield '*' - - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: - if isinstance(current_value, dict): - return itertools.chain(*map(lambda index: ObjectIndexSubscript(index).match(root_value, current_value), current_value.keys())) - elif isinstance(current_value, list): - return itertools.chain(*map(lambda index: ArrayIndexSubscript(index).match(root_value, current_value), range(len(current_value)))) - else: - return [] diff --git a/jsonpath2/subscripts/__init__.py b/jsonpath2/subscripts/__init__.py index 836e3e8..001373a 100644 --- a/jsonpath2/subscripts/__init__.py +++ b/jsonpath2/subscripts/__init__.py @@ -1,2 +1,3 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +"""Subscripts module contains the various subscripting classes.""" diff --git a/jsonpath2/subscripts/ArrayIndexSubscript.py b/jsonpath2/subscripts/arrayindex.py similarity index 57% rename from jsonpath2/subscripts/ArrayIndexSubscript.py rename to jsonpath2/subscripts/arrayindex.py index 8ca3f35..90971c6 100644 --- a/jsonpath2/subscripts/ArrayIndexSubscript.py +++ b/jsonpath2/subscripts/arrayindex.py @@ -1,36 +1,34 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +"""Array Index subscript of the parse tree.""" import json from typing import Generator +from jsonpath2.node import MatchData +from jsonpath2.subscript import Subscript +from jsonpath2.nodes.subscript import SubscriptNode +from jsonpath2.nodes.terminal import TerminalNode -from jsonpath2.Node import MatchData -from jsonpath2.Subscript import Subscript - -from jsonpath2.nodes.SubscriptNode import SubscriptNode -from jsonpath2.nodes.TerminalNode import TerminalNode class ArrayIndexSubscript(Subscript): - def __init__(self, index:int): - super(ArrayIndexSubscript, self).__init__() + """Array index subscript object.""" + def __init__(self, index: int): + """Save the index of the subscript.""" + super(ArrayIndexSubscript, self).__init__() self.index = index def __jsonpath__(self) -> Generator[str, None, None]: + """Dump the json index when rendering jsonpath.""" yield json.dumps(self.index) - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match the root value against the current value.""" if isinstance(current_value, list): if self.index < 0: new_index = self.index + len(current_value) if (new_index >= 0) and (new_index < len(current_value)): return [MatchData(SubscriptNode(TerminalNode(), [self]), root_value, current_value[new_index])] - else: - return [] elif self.index < len(current_value): return [MatchData(SubscriptNode(TerminalNode(), [self]), root_value, current_value[self.index])] - else: - return [] - else: - return [] + return [] diff --git a/jsonpath2/subscripts/ArraySliceSubscript.py b/jsonpath2/subscripts/arrayslice.py similarity index 60% rename from jsonpath2/subscripts/ArraySliceSubscript.py rename to jsonpath2/subscripts/arrayslice.py index 6c0ebcb..b2ef2f0 100644 --- a/jsonpath2/subscripts/ArraySliceSubscript.py +++ b/jsonpath2/subscripts/arrayslice.py @@ -1,42 +1,43 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +"""Array slicing module.""" import itertools import json from typing import Generator +from jsonpath2.node import MatchData +from jsonpath2.subscript import Subscript +from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript -from jsonpath2.Node import MatchData -from jsonpath2.Subscript import Subscript - -from jsonpath2.subscripts.ArrayIndexSubscript import ArrayIndexSubscript class ArraySliceSubscript(Subscript): - def __init__(self, start:int=None, end:int=None, step:int=None): - super(ArraySliceSubscript, self).__init__() + """Array slice class for the parse tree.""" + def __init__(self, start: int = None, end: int = None, step: int = None): + """Save the start end and step in the array slice.""" + super(ArraySliceSubscript, self).__init__() self.start = start self.end = end self.step = step def __jsonpath__(self) -> Generator[str, None, None]: + """Return the jsonpath of the array slice.""" if self.start is not None: yield json.dumps(self.start) - yield ':' - if self.end is not None: yield json.dumps(self.end) - if self.step is not None: yield ':' - yield json.dumps(self.step) - def match(self, root_value:object, current_value:object) -> Generator[MatchData, None, None]: + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match an array slice between values.""" if isinstance(current_value, list): - start = None if (self.start is None) else (self.start + (len(current_value) if (self.start < 0) else 0)) + start = None if (self.start is None) else ( + self.start + (len(current_value) if (self.start < 0) else 0)) - end = None if (self.end is None) else (self.end + (len(current_value) if (self.end < 0) else 0)) + end = None if (self.end is None) else ( + self.end + (len(current_value) if (self.end < 0) else 0)) if start is None: if end is None: @@ -61,6 +62,8 @@ def match(self, root_value:object, current_value:object) -> Generator[MatchData, else: indices = range(start, end, self.step) - return itertools.chain(*map(lambda index: ArrayIndexSubscript(index).match(root_value, current_value), indices)) - else: - return [] + return itertools.chain(*map( + lambda index: ArrayIndexSubscript( + index).match(root_value, current_value), + indices)) + return [] diff --git a/jsonpath2/subscripts/filter.py b/jsonpath2/subscripts/filter.py new file mode 100644 index 0000000..6ba85a6 --- /dev/null +++ b/jsonpath2/subscripts/filter.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Filter parse tree.""" +from typing import Generator +from jsonpath2.expression import Expression +from jsonpath2.node import MatchData +from jsonpath2.subscript import Subscript +from jsonpath2.nodes.terminal import TerminalNode + + +class FilterSubscript(Subscript): + """Filter subscript in the parse tree.""" + + def __init__(self, expression: Expression): + """Save the filter expression.""" + super(FilterSubscript, self).__init__() + self.expression = expression + + def __jsonpath__(self) -> Generator[str, None, None]: + """generate the jsonpath for the filter.""" + yield '?' + yield '(' + for expression_token in self.expression.__jsonpath__(): + yield expression_token + yield ')' + + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match the filter subscript against the current value.""" + if self.expression.evaluate(root_value, current_value): + return [MatchData(TerminalNode(), root_value, current_value)] + return [] diff --git a/jsonpath2/subscripts/objectindex.py b/jsonpath2/subscripts/objectindex.py new file mode 100644 index 0000000..a4dd71a --- /dev/null +++ b/jsonpath2/subscripts/objectindex.py @@ -0,0 +1,28 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Object index subscript module.""" +import json +from typing import Generator +from jsonpath2.node import MatchData +from jsonpath2.subscript import Subscript +from jsonpath2.nodes.subscript import SubscriptNode +from jsonpath2.nodes.terminal import TerminalNode + + +class ObjectIndexSubscript(Subscript): + """Object index subscript part of the jsonpath parse tree.""" + + def __init__(self, index: str): + """Save the string index into the json object.""" + super(ObjectIndexSubscript, self).__init__() + self.index = index + + def __jsonpath__(self) -> Generator[str, None, None]: + """Yield the dumps of the index.""" + yield json.dumps(self.index) + + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match the current value against the root value.""" + if isinstance(current_value, dict) and (self.index in current_value): + return [MatchData(SubscriptNode(TerminalNode(), [self]), root_value, current_value[self.index])] + return [] diff --git a/jsonpath2/subscripts/wildcard.py b/jsonpath2/subscripts/wildcard.py new file mode 100644 index 0000000..704dcca --- /dev/null +++ b/jsonpath2/subscripts/wildcard.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Wild cart subscript module.""" +import itertools +from typing import Generator +from jsonpath2.node import MatchData +from jsonpath2.subscript import Subscript +from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript +from jsonpath2.subscripts.objectindex import ObjectIndexSubscript + + +class WildcardSubscript(Subscript): + """Wild card subscript part of the parse tree.""" + + def __jsonpath__(self) -> Generator[str, None, None]: + """Yield the '*' wild card character.""" + yield '*' + + def match(self, root_value: object, current_value: object) -> Generator[MatchData, None, None]: + """Match the root value against the current value.""" + if isinstance(current_value, dict): + return itertools.chain(*map( + lambda index: ObjectIndexSubscript( + index).match(root_value, current_value), + current_value.keys())) + elif isinstance(current_value, list): + return itertools.chain(*map( + lambda index: ArrayIndexSubscript( + index).match(root_value, current_value), + range(len(current_value)))) + return [] diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py new file mode 100644 index 0000000..49f2ebd --- /dev/null +++ b/jsonpath2/test/expression_test.py @@ -0,0 +1,15 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Test the expression object.""" +from unittest import TestCase +from jsonpath2.expressions.operator import OperatorExpression + + +class TestExpression(TestCase): + """Test the expression base class.""" + + def test_expression(self): + """Test the base expression equal.""" + obj_a = OperatorExpression() + obj_b = OperatorExpression() + self.assertTrue(obj_a == obj_b) diff --git a/jsonpath2/test/jsonpath2_test.py b/jsonpath2/test/jsonpath2_test.py index f929f09..0cf052f 100644 --- a/jsonpath2/test/jsonpath2_test.py +++ b/jsonpath2/test/jsonpath2_test.py @@ -1,25 +1,27 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +"""Test the jsonpath module.""" from unittest import TestCase +from json import loads +from jsonpath2.node import MatchData +from jsonpath2.expressions.some import SomeExpression +from jsonpath2.nodes.current import CurrentNode +from jsonpath2.nodes.recursivedescent import RecursiveDescentNode +from jsonpath2.nodes.root import RootNode +from jsonpath2.nodes.subscript import SubscriptNode +from jsonpath2.nodes.terminal import TerminalNode +from jsonpath2.path import Path +from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript +from jsonpath2.subscripts.filter import FilterSubscript +from jsonpath2.subscripts.objectindex import ObjectIndexSubscript +from jsonpath2.subscripts.wildcard import WildcardSubscript -from jsonpath2.Node import MatchData - -from jsonpath2.expressions.SomeExpression import SomeExpression - -from jsonpath2.nodes.CurrentNode import CurrentNode -from jsonpath2.nodes.RecursiveDescentNode import RecursiveDescentNode -from jsonpath2.nodes.RootNode import RootNode -from jsonpath2.nodes.SubscriptNode import SubscriptNode -from jsonpath2.nodes.TerminalNode import TerminalNode - -from jsonpath2.subscripts.ArrayIndexSubscript import ArrayIndexSubscript -from jsonpath2.subscripts.FilterSubscript import FilterSubscript -from jsonpath2.subscripts.ObjectIndexSubscript import ObjectIndexSubscript -from jsonpath2.subscripts.WildcardSubscript import WildcardSubscript class TestNode(TestCase): + """Test the node object.""" + def setUp(self): + """Setup the class.""" root_value = { 'hello': 'Hello, world!', 'languages': [ @@ -45,7 +47,8 @@ def setUp(self): 'root_value': root_value, 'current_value': current_value, 'match_data_list': [ - MatchData(RootNode(TerminalNode()), root_value, root_value), + MatchData(RootNode(TerminalNode()), + root_value, root_value), ], }, { @@ -54,7 +57,8 @@ def setUp(self): 'root_value': root_value, 'current_value': current_value, 'match_data_list': [ - MatchData(CurrentNode(TerminalNode()), root_value, current_value), + MatchData(CurrentNode(TerminalNode()), + root_value, current_value), ], }, { @@ -75,7 +79,15 @@ def setUp(self): }, { '__jsonpath__': '[?(@),?(@)]', - 'node': SubscriptNode(TerminalNode(), [FilterSubscript(SomeExpression(CurrentNode(TerminalNode()))), FilterSubscript(SomeExpression(CurrentNode(TerminalNode())))]), + 'node': SubscriptNode( + TerminalNode(), + [ + FilterSubscript(SomeExpression( + CurrentNode(TerminalNode()))), + FilterSubscript(SomeExpression( + CurrentNode(TerminalNode()))) + ] + ), 'root_value': root_value, 'current_value': current_value, 'match_data_list': [ @@ -96,28 +108,58 @@ def setUp(self): 'root_value': root_value, 'current_value': root_value, 'match_data_list': [ - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'languages')]), root_value, root_value['languages']), ], }, { '__jsonpath__': '["languages"][*]', - 'node': SubscriptNode(SubscriptNode(TerminalNode(), [WildcardSubscript()]), [ObjectIndexSubscript('languages')]), + 'node': SubscriptNode( + SubscriptNode(TerminalNode(), [WildcardSubscript()]), + [ObjectIndexSubscript('languages')] + ), 'root_value': root_value, 'current_value': root_value, 'match_data_list': [ - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][1]), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(0)] + ), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][0] + ), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(1)]), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][1] + ), ], }, { '__jsonpath__': '["hello","languages"]', - 'node': SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello'), ObjectIndexSubscript('languages')]), + 'node': SubscriptNode( + TerminalNode(), + [ObjectIndexSubscript('hello'), + ObjectIndexSubscript('languages')] + ), 'root_value': root_value, 'current_value': root_value, 'match_data_list': [ - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'languages')]), root_value, root_value['languages']), ], }, { @@ -136,23 +178,75 @@ def setUp(self): 'current_value': root_value, 'match_data_list': [ MatchData(TerminalNode(), root_value, root_value), - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][1]), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'languages')]), root_value, root_value['languages']), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(0)] + ), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][0] + ), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(1)] + ), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][1] + ), ], }, { '__jsonpath__': '..[?(@)]', - 'node': RecursiveDescentNode(SubscriptNode(TerminalNode(), [FilterSubscript(SomeExpression(CurrentNode(TerminalNode())))])), + 'node': RecursiveDescentNode( + SubscriptNode( + TerminalNode(), + [ + FilterSubscript(SomeExpression( + CurrentNode(TerminalNode()))) + ] + ) + ), 'root_value': root_value, 'current_value': root_value, 'match_data_list': [ MatchData(TerminalNode(), root_value, root_value), - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][1]), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'languages')]), root_value, root_value['languages']), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(0)] + ), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][0] + ), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(1)] + ), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][1] + ), ], }, { @@ -161,10 +255,32 @@ def setUp(self): 'root_value': root_value, 'current_value': root_value, 'match_data_list': [ - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('languages')]), root_value, root_value['languages']), - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][1]), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'languages')]), root_value, root_value['languages']), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(0)] + ), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][0] + ), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(1)] + ), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][1] + ), ], }, { @@ -173,7 +289,8 @@ def setUp(self): 'root_value': root_value, 'current_value': root_value, 'match_data_list': [ - MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript('hello')]), root_value, root_value['hello']), + MatchData(SubscriptNode(TerminalNode(), [ObjectIndexSubscript( + 'hello')]), root_value, root_value['hello']), ], }, { @@ -182,23 +299,49 @@ def setUp(self): 'root_value': root_value, 'current_value': root_value, 'match_data_list': [ - MatchData(SubscriptNode(SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]), [ObjectIndexSubscript('languages')]), root_value, root_value['languages'][0]), + MatchData( + SubscriptNode( + SubscriptNode( + TerminalNode(), + [ArrayIndexSubscript(0)] + ), + [ObjectIndexSubscript('languages')] + ), + root_value, + root_value['languages'][0] + ), ], }, ] - def _assertNodeTestCase(self, **kwargs): + def _assert_node_test_case(self, **kwargs): self.assertEqual(kwargs['__jsonpath__'], kwargs['node'].tojsonpath()) - match_data_list = list(kwargs['node'].match(kwargs['root_value'], kwargs['current_value'])) + match_data_list = list(kwargs['node'].match( + kwargs['root_value'], kwargs['current_value'])) - self.assertEqual(kwargs['match_data_list'], match_data_list) + for index, value in enumerate(match_data_list): + self.assertEqual(kwargs['match_data_list'][index], value) for match_data in match_data_list: - new_match_data_list = list(match_data.node.match(kwargs['root_value'], kwargs['current_value'])) + new_match_data_list = list(match_data.node.match( + kwargs['root_value'], kwargs['current_value'])) self.assertEqual([match_data], new_match_data_list) def test_state(self): + """Test the state.""" for kwargs in self._state: - self._assertNodeTestCase(**kwargs) + self._assert_node_test_case(**kwargs) + + def test_example(self): + """Test the example from the README.md.""" + test_string = '{"hello":"Hello, world!"}' + test_json = loads(test_string) + path_expr = Path.parse_str('$["hello"]') + result = list( + map(lambda match_data: match_data.current_value, path_expr.match(test_json))) + self.assertEqual(result[0], 'Hello, world!') + result = list( + map(lambda match_data: match_data.node.tojsonpath(), path_expr.match(test_json))) + self.assertEqual(result[0], '$["hello"]') diff --git a/jsonpath2/tojsonpath.py b/jsonpath2/tojsonpath.py new file mode 100644 index 0000000..345f913 --- /dev/null +++ b/jsonpath2/tojsonpath.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""A JSONPath abstract class.""" +from abc import ABC, abstractmethod +from typing import Generator + + +class ToJSONPath(ABC): + """Abstract class which calls internal method.""" + + def tojsonpath(self) -> str: + """Get the json path from self and return it.""" + return ''.join(list(self.__jsonpath__())) + + @abstractmethod + def __jsonpath__(self) -> Generator[str, None, None]: # pragma: no cover abstract method + """Abstract method to return the jsonpath.""" + raise NotImplementedError() diff --git a/requirements-dev.txt b/requirements-dev.txt index f698807..57c100e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,8 @@ +antlr4-python3-runtime coverage pep257 pre-commit pylint<2.0 pytest setuptools +six diff --git a/setup.py b/setup.py index 9e1794b..ecbc3e8 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +"""Setup the python package for jsonpath2.""" +from os import path try: # pip version 9 from pip.req import parse_requirements except ImportError: @@ -17,9 +18,12 @@ license='LGPLv3', url='https://pypi.python.org/pypi/jsonpath2/', description='JSONPath implementation for Python', + long_description=open(path.join( + path.abspath(path.dirname(__file__)), + 'README.md'), encoding='utf-8').read(), + long_description_content_type='text/markdown', author='Mark Borkum', author_email='mark.borkum@pnnl.gov', packages=find_packages(), - namespace_packages=['jsonpath2'], install_requires=[str(ir.req) for ir in INSTALL_REQS] ) From 11fe47eb59405f2b88e1be1219a331d94d47e542 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Mon, 17 Sep 2018 17:17:25 -0700 Subject: [PATCH 04/23] Test parse_str method. --- jsonpath2/test/jsonpath2_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jsonpath2/test/jsonpath2_test.py b/jsonpath2/test/jsonpath2_test.py index 0cf052f..137bfa2 100644 --- a/jsonpath2/test/jsonpath2_test.py +++ b/jsonpath2/test/jsonpath2_test.py @@ -317,6 +317,12 @@ def setUp(self): def _assert_node_test_case(self, **kwargs): self.assertEqual(kwargs['__jsonpath__'], kwargs['node'].tojsonpath()) + if isinstance(kwargs['node'], RootNode): + self.assertEqual(kwargs['node'], Path.parse_str(kwargs['__jsonpath__']).root_node) + else: + with self.assertRaises(ValueError): + Path.parse_str('__jsonpath__') + match_data_list = list(kwargs['node'].match( kwargs['root_value'], kwargs['current_value'])) From 9d418842339af642c8b9d5bda1487e948c715764 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Mon, 17 Sep 2018 17:32:42 -0700 Subject: [PATCH 05/23] Operator callables should be static Flake8 warned that the operator callables were globals. In a previous commit, the operator callables were refactored into method-locals in the operator's constructor. This is sufficient to resolve the flake8 warnings, but introduces a bug: since callables are created per instance, they have different memory locations, and hence, are not compatible with the `Node`-level definition of `__eq__`, which naively compares each `__dict__` of each operand. The fix is to refactor the operator callables into static methods. --- jsonpath2/expressions/operator.py | 88 +++++++++++++++++++------------ 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/jsonpath2/expressions/operator.py b/jsonpath2/expressions/operator.py index ebd77a4..9c6ba98 100644 --- a/jsonpath2/expressions/operator.py +++ b/jsonpath2/expressions/operator.py @@ -58,9 +58,14 @@ class EqualBinaryOperatorExpression(BinaryOperatorExpression): def __init__(self, *args, **kwargs): """Constructor with the right function.""" super(EqualBinaryOperatorExpression, self).__init__( - '=', lambda x, y: x == y, + '=', EqualBinaryOperatorExpression.__evaluate__, *args, **kwargs) + @staticmethod + def __evaluate__(x, y): + """Perform an equal on int or float.""" + return x == y + class NotEqualBinaryOperatorExpression(BinaryOperatorExpression): """Binary Equal operator expression.""" @@ -68,18 +73,13 @@ class NotEqualBinaryOperatorExpression(BinaryOperatorExpression): def __init__(self, *args, **kwargs): """Constructor with the right function.""" super(NotEqualBinaryOperatorExpression, self).__init__( - '!=', lambda x, y: x != y, + '!=', NotEqualBinaryOperatorExpression.__evaluate__, *args, **kwargs) - -def _wrap_callback(callback): - """Decorator to verify types of arguments.""" - def wrapped_callback(x_obj, y_obj): - """Check the types of the arguments are int or floats.""" - if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)): - return callback(x_obj, y_obj) - return False - return wrapped_callback + @staticmethod + def __evaluate__(x, y): + """Perform a not equal on int or float.""" + return x != y class LessThanBinaryOperatorExpression(BinaryOperatorExpression): @@ -87,12 +87,16 @@ class LessThanBinaryOperatorExpression(BinaryOperatorExpression): def __init__(self, *args, **kwargs): """Construct the binary operator with appropriate method.""" - @_wrap_callback - def _less_than_(x_num, y_num): - """Perform a less than on int or float.""" - return x_num < y_num super(LessThanBinaryOperatorExpression, self).__init__( - '<', _less_than_, *args, **kwargs) + '<', LessThanBinaryOperatorExpression.__evaluate__, *args, **kwargs) + + @staticmethod + def __evaluate__(x, y): + """Perform a less than on int or float.""" + if isinstance(x, (float, int)) and isinstance(y, (float, int)): + return x < y + else: + return False class LessThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): @@ -100,12 +104,16 @@ class LessThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): def __init__(self, *args, **kwargs): """Construct the binary operator with appropriate method.""" - @_wrap_callback - def _less_than_or_equal_to_(x_num, y_num): - """Perform a less than or equal on int or float.""" - return x_num <= y_num super(LessThanOrEqualToBinaryOperatorExpression, self).__init__( - '<=', _less_than_or_equal_to_, *args, **kwargs) + '<=', LessThanOrEqualToBinaryOperatorExpression.__evaluate__, *args, **kwargs) + + @staticmethod + def __evaluate__(x, y): + """Perform a less than or equal to on int or float.""" + if isinstance(x, (float, int)) and isinstance(y, (float, int)): + return x <= y + else: + return False class GreaterThanBinaryOperatorExpression(BinaryOperatorExpression): @@ -113,12 +121,16 @@ class GreaterThanBinaryOperatorExpression(BinaryOperatorExpression): def __init__(self, *args, **kwargs): """Construct the binary operator with appropriate method.""" - @_wrap_callback - def _greater_than_(x_num, y_num): - """Perform a greater than on int or float.""" - return x_num > y_num super(GreaterThanBinaryOperatorExpression, self).__init__( - '>', _greater_than_, *args, **kwargs) + '>', GreaterThanBinaryOperatorExpression.__evaluate__, *args, **kwargs) + + @staticmethod + def __evaluate__(x, y): + """Perform a greater than on int or float.""" + if isinstance(x, (float, int)) and isinstance(y, (float, int)): + return x > y + else: + return False class GreaterThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): @@ -126,12 +138,16 @@ class GreaterThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): def __init__(self, *args, **kwargs): """Construct the binary operator with appropriate method.""" - @_wrap_callback - def _greater_than_or_equal_to_(x_num, y_num): - """Perform a greater than on int or float.""" - return x_num >= y_num super(GreaterThanOrEqualToBinaryOperatorExpression, self).__init__( - '>=', _greater_than_or_equal_to_, *args, **kwargs) + '>=', GreaterThanOrEqualToBinaryOperatorExpression.__evaluate__, *args, **kwargs) + + @staticmethod + def __evaluate__(x, y): + """Perform a greater than or equal to on int or float.""" + if isinstance(x, (float, int)) and isinstance(y, (float, int)): + return x >= y + else: + return False class UnaryOperatorExpression(OperatorExpression): @@ -165,11 +181,13 @@ class NotUnaryOperatorExpression(UnaryOperatorExpression): def __init__(self, *args, **kwargs): """Call the unary operator expression with the right method.""" - def _not_(x_obj): - """The unary not function.""" - return not x_obj super(NotUnaryOperatorExpression, self).__init__( - 'not', _not_, *args, **kwargs) + 'not', NotUnaryOperatorExpression.__evaluate__, *args, **kwargs) + + @staticmethod + def __evaluate__(x): + """The unary not function.""" + return not x class VariadicOperatorExpression(OperatorExpression): From 87486097c537998f71bbf9177437daf44e0a2683 Mon Sep 17 00:00:00 2001 From: David Brown Date: Mon, 17 Sep 2018 17:48:32 -0700 Subject: [PATCH 06/23] pre-commit fixes --- jsonpath2/expressions/operator.py | 48 ++++++++++++++----------------- jsonpath2/test/jsonpath2_test.py | 3 +- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/jsonpath2/expressions/operator.py b/jsonpath2/expressions/operator.py index 9c6ba98..85ac2c0 100644 --- a/jsonpath2/expressions/operator.py +++ b/jsonpath2/expressions/operator.py @@ -62,9 +62,9 @@ def __init__(self, *args, **kwargs): *args, **kwargs) @staticmethod - def __evaluate__(x, y): + def __evaluate__(x_obj, y_obj): """Perform an equal on int or float.""" - return x == y + return x_obj == y_obj class NotEqualBinaryOperatorExpression(BinaryOperatorExpression): @@ -77,9 +77,9 @@ def __init__(self, *args, **kwargs): *args, **kwargs) @staticmethod - def __evaluate__(x, y): + def __evaluate__(x_obj, y_obj): """Perform a not equal on int or float.""" - return x != y + return x_obj != y_obj class LessThanBinaryOperatorExpression(BinaryOperatorExpression): @@ -91,12 +91,11 @@ def __init__(self, *args, **kwargs): '<', LessThanBinaryOperatorExpression.__evaluate__, *args, **kwargs) @staticmethod - def __evaluate__(x, y): + def __evaluate__(x_obj, y_obj): """Perform a less than on int or float.""" - if isinstance(x, (float, int)) and isinstance(y, (float, int)): - return x < y - else: - return False + if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)): + return x_obj < y_obj + return False class LessThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): @@ -108,12 +107,11 @@ def __init__(self, *args, **kwargs): '<=', LessThanOrEqualToBinaryOperatorExpression.__evaluate__, *args, **kwargs) @staticmethod - def __evaluate__(x, y): + def __evaluate__(x_obj, y_obj): """Perform a less than or equal to on int or float.""" - if isinstance(x, (float, int)) and isinstance(y, (float, int)): - return x <= y - else: - return False + if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)): + return x_obj <= y_obj + return False class GreaterThanBinaryOperatorExpression(BinaryOperatorExpression): @@ -125,12 +123,11 @@ def __init__(self, *args, **kwargs): '>', GreaterThanBinaryOperatorExpression.__evaluate__, *args, **kwargs) @staticmethod - def __evaluate__(x, y): + def __evaluate__(x_obj, y_obj): """Perform a greater than on int or float.""" - if isinstance(x, (float, int)) and isinstance(y, (float, int)): - return x > y - else: - return False + if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)): + return x_obj > y_obj + return False class GreaterThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression): @@ -142,12 +139,11 @@ def __init__(self, *args, **kwargs): '>=', GreaterThanOrEqualToBinaryOperatorExpression.__evaluate__, *args, **kwargs) @staticmethod - def __evaluate__(x, y): + def __evaluate__(x_obj, y_obj): """Perform a greater than or equal to on int or float.""" - if isinstance(x, (float, int)) and isinstance(y, (float, int)): - return x >= y - else: - return False + if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)): + return x_obj >= y_obj + return False class UnaryOperatorExpression(OperatorExpression): @@ -185,9 +181,9 @@ def __init__(self, *args, **kwargs): 'not', NotUnaryOperatorExpression.__evaluate__, *args, **kwargs) @staticmethod - def __evaluate__(x): + def __evaluate__(x_obj): """The unary not function.""" - return not x + return not x_obj class VariadicOperatorExpression(OperatorExpression): diff --git a/jsonpath2/test/jsonpath2_test.py b/jsonpath2/test/jsonpath2_test.py index 137bfa2..13de535 100644 --- a/jsonpath2/test/jsonpath2_test.py +++ b/jsonpath2/test/jsonpath2_test.py @@ -318,7 +318,8 @@ def _assert_node_test_case(self, **kwargs): self.assertEqual(kwargs['__jsonpath__'], kwargs['node'].tojsonpath()) if isinstance(kwargs['node'], RootNode): - self.assertEqual(kwargs['node'], Path.parse_str(kwargs['__jsonpath__']).root_node) + self.assertEqual(kwargs['node'], Path.parse_str( + kwargs['__jsonpath__']).root_node) else: with self.assertRaises(ValueError): Path.parse_str('__jsonpath__') From 1af8e0e01f61edbc6bf88cc6accb811259ce9ca8 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 18 Sep 2018 09:33:31 -0700 Subject: [PATCH 07/23] add some bookstore examples for testing Signed-off-by: David Brown --- jsonpath2/parser/JSONPath.g4 | 1 + jsonpath2/parser/JSONPath.interp | 2 +- jsonpath2/parser/JSONPathLexer.py | 2 +- jsonpath2/parser/JSONPathListener.py | 2 +- jsonpath2/parser/JSONPathParser.py | 209 +++++++++++++++------------ jsonpath2/test/bookstore_test.py | 156 ++++++++++++++++++++ jsonpath2/test/expression_test.py | 4 +- 7 files changed, 280 insertions(+), 96 deletions(-) create mode 100644 jsonpath2/test/bookstore_test.py diff --git a/jsonpath2/parser/JSONPath.g4 b/jsonpath2/parser/JSONPath.g4 index c059868..03a8a3f 100644 --- a/jsonpath2/parser/JSONPath.g4 +++ b/jsonpath2/parser/JSONPath.g4 @@ -72,6 +72,7 @@ notExpression : NOT notExpression | PAREN_LEFT expression PAREN_RIGHT | ( ROOT_VALUE | CURRENT_VALUE ) subscript? ( ( EQ | NE | LT | LE | GT | GE ) value )? + | value ( ( EQ | NE | LT | LE | GT | GE ) value )? ; diff --git a/jsonpath2/parser/JSONPath.interp b/jsonpath2/parser/JSONPath.interp index 2fdafae..fa93e0b 100644 --- a/jsonpath2/parser/JSONPath.interp +++ b/jsonpath2/parser/JSONPath.interp @@ -82,4 +82,4 @@ value atn: -[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 32, 165, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 3, 2, 3, 2, 5, 2, 33, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 5, 3, 40, 10, 3, 3, 3, 5, 3, 43, 10, 3, 3, 3, 3, 3, 3, 3, 5, 3, 48, 10, 3, 3, 3, 3, 3, 5, 3, 52, 10, 3, 5, 3, 54, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 60, 10, 4, 12, 4, 14, 4, 63, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 75, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 80, 10, 6, 5, 6, 82, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 90, 10, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 5, 8, 97, 10, 8, 3, 9, 3, 9, 3, 9, 5, 9, 102, 10, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 112, 10, 10, 3, 10, 3, 10, 5, 10, 116, 10, 10, 5, 10, 118, 10, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 126, 10, 12, 12, 12, 14, 12, 129, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 135, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 145, 10, 14, 12, 14, 14, 14, 148, 11, 14, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 154, 10, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 163, 10, 15, 3, 15, 2, 2, 16, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 2, 5, 4, 2, 7, 7, 29, 29, 4, 2, 3, 3, 5, 5, 3, 2, 9, 14, 2, 180, 2, 30, 3, 2, 2, 2, 4, 53, 3, 2, 2, 2, 6, 55, 3, 2, 2, 2, 8, 66, 3, 2, 2, 2, 10, 89, 3, 2, 2, 2, 12, 91, 3, 2, 2, 2, 14, 93, 3, 2, 2, 2, 16, 98, 3, 2, 2, 2, 18, 117, 3, 2, 2, 2, 20, 119, 3, 2, 2, 2, 22, 134, 3, 2, 2, 2, 24, 136, 3, 2, 2, 2, 26, 153, 3, 2, 2, 2, 28, 162, 3, 2, 2, 2, 30, 32, 7, 5, 2, 2, 31, 33, 5, 4, 3, 2, 32, 31, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 34, 3, 2, 2, 2, 34, 35, 7, 2, 2, 3, 35, 3, 3, 2, 2, 2, 36, 39, 7, 4, 2, 2, 37, 40, 5, 8, 5, 2, 38, 40, 5, 6, 4, 2, 39, 37, 3, 2, 2, 2, 39, 38, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 43, 5, 4, 3, 2, 42, 41, 3, 2, 2, 2, 42, 43, 3, 2, 2, 2, 43, 54, 3, 2, 2, 2, 44, 45, 7, 6, 2, 2, 45, 47, 5, 8, 5, 2, 46, 48, 5, 4, 3, 2, 47, 46, 3, 2, 2, 2, 47, 48, 3, 2, 2, 2, 48, 54, 3, 2, 2, 2, 49, 51, 5, 6, 4, 2, 50, 52, 5, 4, 3, 2, 51, 50, 3, 2, 2, 2, 51, 52, 3, 2, 2, 2, 52, 54, 3, 2, 2, 2, 53, 36, 3, 2, 2, 2, 53, 44, 3, 2, 2, 2, 53, 49, 3, 2, 2, 2, 54, 5, 3, 2, 2, 2, 55, 56, 7, 22, 2, 2, 56, 61, 5, 10, 6, 2, 57, 58, 7, 25, 2, 2, 58, 60, 5, 10, 6, 2, 59, 57, 3, 2, 2, 2, 60, 63, 3, 2, 2, 2, 61, 59, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 64, 3, 2, 2, 2, 63, 61, 3, 2, 2, 2, 64, 65, 7, 23, 2, 2, 65, 7, 3, 2, 2, 2, 66, 67, 9, 2, 2, 2, 67, 9, 3, 2, 2, 2, 68, 90, 7, 30, 2, 2, 69, 70, 7, 31, 2, 2, 70, 81, 6, 6, 2, 2, 71, 74, 7, 24, 2, 2, 72, 73, 7, 31, 2, 2, 73, 75, 6, 6, 3, 2, 74, 72, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 79, 3, 2, 2, 2, 76, 77, 7, 24, 2, 2, 77, 78, 7, 31, 2, 2, 78, 80, 6, 6, 4, 2, 79, 76, 3, 2, 2, 2, 79, 80, 3, 2, 2, 2, 80, 82, 3, 2, 2, 2, 81, 71, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 90, 3, 2, 2, 2, 83, 90, 7, 7, 2, 2, 84, 85, 7, 28, 2, 2, 85, 86, 7, 26, 2, 2, 86, 87, 5, 12, 7, 2, 87, 88, 7, 27, 2, 2, 88, 90, 3, 2, 2, 2, 89, 68, 3, 2, 2, 2, 89, 69, 3, 2, 2, 2, 89, 83, 3, 2, 2, 2, 89, 84, 3, 2, 2, 2, 90, 11, 3, 2, 2, 2, 91, 92, 5, 14, 8, 2, 92, 13, 3, 2, 2, 2, 93, 96, 5, 16, 9, 2, 94, 95, 7, 8, 2, 2, 95, 97, 5, 14, 8, 2, 96, 94, 3, 2, 2, 2, 96, 97, 3, 2, 2, 2, 97, 15, 3, 2, 2, 2, 98, 101, 5, 18, 10, 2, 99, 100, 7, 16, 2, 2, 100, 102, 5, 16, 9, 2, 101, 99, 3, 2, 2, 2, 101, 102, 3, 2, 2, 2, 102, 17, 3, 2, 2, 2, 103, 104, 7, 15, 2, 2, 104, 118, 5, 18, 10, 2, 105, 106, 7, 26, 2, 2, 106, 107, 5, 12, 7, 2, 107, 108, 7, 27, 2, 2, 108, 118, 3, 2, 2, 2, 109, 111, 9, 3, 2, 2, 110, 112, 5, 4, 3, 2, 111, 110, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 115, 3, 2, 2, 2, 113, 114, 9, 4, 2, 2, 114, 116, 5, 28, 15, 2, 115, 113, 3, 2, 2, 2, 115, 116, 3, 2, 2, 2, 116, 118, 3, 2, 2, 2, 117, 103, 3, 2, 2, 2, 117, 105, 3, 2, 2, 2, 117, 109, 3, 2, 2, 2, 118, 19, 3, 2, 2, 2, 119, 120, 5, 28, 15, 2, 120, 21, 3, 2, 2, 2, 121, 122, 7, 20, 2, 2, 122, 127, 5, 24, 13, 2, 123, 124, 7, 25, 2, 2, 124, 126, 5, 24, 13, 2, 125, 123, 3, 2, 2, 2, 126, 129, 3, 2, 2, 2, 127, 125, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 130, 3, 2, 2, 2, 129, 127, 3, 2, 2, 2, 130, 131, 7, 21, 2, 2, 131, 135, 3, 2, 2, 2, 132, 133, 7, 20, 2, 2, 133, 135, 7, 21, 2, 2, 134, 121, 3, 2, 2, 2, 134, 132, 3, 2, 2, 2, 135, 23, 3, 2, 2, 2, 136, 137, 7, 30, 2, 2, 137, 138, 7, 24, 2, 2, 138, 139, 5, 28, 15, 2, 139, 25, 3, 2, 2, 2, 140, 141, 7, 22, 2, 2, 141, 146, 5, 28, 15, 2, 142, 143, 7, 25, 2, 2, 143, 145, 5, 28, 15, 2, 144, 142, 3, 2, 2, 2, 145, 148, 3, 2, 2, 2, 146, 144, 3, 2, 2, 2, 146, 147, 3, 2, 2, 2, 147, 149, 3, 2, 2, 2, 148, 146, 3, 2, 2, 2, 149, 150, 7, 23, 2, 2, 150, 154, 3, 2, 2, 2, 151, 152, 7, 22, 2, 2, 152, 154, 7, 23, 2, 2, 153, 140, 3, 2, 2, 2, 153, 151, 3, 2, 2, 2, 154, 27, 3, 2, 2, 2, 155, 163, 7, 30, 2, 2, 156, 163, 7, 31, 2, 2, 157, 163, 5, 22, 12, 2, 158, 163, 5, 26, 14, 2, 159, 163, 7, 17, 2, 2, 160, 163, 7, 18, 2, 2, 161, 163, 7, 19, 2, 2, 162, 155, 3, 2, 2, 2, 162, 156, 3, 2, 2, 2, 162, 157, 3, 2, 2, 2, 162, 158, 3, 2, 2, 2, 162, 159, 3, 2, 2, 2, 162, 160, 3, 2, 2, 2, 162, 161, 3, 2, 2, 2, 163, 29, 3, 2, 2, 2, 23, 32, 39, 42, 47, 51, 53, 61, 74, 79, 81, 89, 96, 101, 111, 115, 117, 127, 134, 146, 153, 162] \ No newline at end of file +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 32, 170, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 3, 2, 3, 2, 5, 2, 33, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 5, 3, 40, 10, 3, 3, 3, 5, 3, 43, 10, 3, 3, 3, 3, 3, 3, 3, 5, 3, 48, 10, 3, 3, 3, 3, 3, 5, 3, 52, 10, 3, 5, 3, 54, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 60, 10, 4, 12, 4, 14, 4, 63, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 75, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 80, 10, 6, 5, 6, 82, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 90, 10, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 5, 8, 97, 10, 8, 3, 9, 3, 9, 3, 9, 5, 9, 102, 10, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 112, 10, 10, 3, 10, 3, 10, 5, 10, 116, 10, 10, 3, 10, 3, 10, 3, 10, 5, 10, 121, 10, 10, 5, 10, 123, 10, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 131, 10, 12, 12, 12, 14, 12, 134, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 140, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 150, 10, 14, 12, 14, 14, 14, 153, 11, 14, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 159, 10, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 168, 10, 15, 3, 15, 2, 2, 16, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 2, 5, 4, 2, 7, 7, 29, 29, 4, 2, 3, 3, 5, 5, 3, 2, 9, 14, 2, 187, 2, 30, 3, 2, 2, 2, 4, 53, 3, 2, 2, 2, 6, 55, 3, 2, 2, 2, 8, 66, 3, 2, 2, 2, 10, 89, 3, 2, 2, 2, 12, 91, 3, 2, 2, 2, 14, 93, 3, 2, 2, 2, 16, 98, 3, 2, 2, 2, 18, 122, 3, 2, 2, 2, 20, 124, 3, 2, 2, 2, 22, 139, 3, 2, 2, 2, 24, 141, 3, 2, 2, 2, 26, 158, 3, 2, 2, 2, 28, 167, 3, 2, 2, 2, 30, 32, 7, 5, 2, 2, 31, 33, 5, 4, 3, 2, 32, 31, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 34, 3, 2, 2, 2, 34, 35, 7, 2, 2, 3, 35, 3, 3, 2, 2, 2, 36, 39, 7, 4, 2, 2, 37, 40, 5, 8, 5, 2, 38, 40, 5, 6, 4, 2, 39, 37, 3, 2, 2, 2, 39, 38, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 43, 5, 4, 3, 2, 42, 41, 3, 2, 2, 2, 42, 43, 3, 2, 2, 2, 43, 54, 3, 2, 2, 2, 44, 45, 7, 6, 2, 2, 45, 47, 5, 8, 5, 2, 46, 48, 5, 4, 3, 2, 47, 46, 3, 2, 2, 2, 47, 48, 3, 2, 2, 2, 48, 54, 3, 2, 2, 2, 49, 51, 5, 6, 4, 2, 50, 52, 5, 4, 3, 2, 51, 50, 3, 2, 2, 2, 51, 52, 3, 2, 2, 2, 52, 54, 3, 2, 2, 2, 53, 36, 3, 2, 2, 2, 53, 44, 3, 2, 2, 2, 53, 49, 3, 2, 2, 2, 54, 5, 3, 2, 2, 2, 55, 56, 7, 22, 2, 2, 56, 61, 5, 10, 6, 2, 57, 58, 7, 25, 2, 2, 58, 60, 5, 10, 6, 2, 59, 57, 3, 2, 2, 2, 60, 63, 3, 2, 2, 2, 61, 59, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 64, 3, 2, 2, 2, 63, 61, 3, 2, 2, 2, 64, 65, 7, 23, 2, 2, 65, 7, 3, 2, 2, 2, 66, 67, 9, 2, 2, 2, 67, 9, 3, 2, 2, 2, 68, 90, 7, 30, 2, 2, 69, 70, 7, 31, 2, 2, 70, 81, 6, 6, 2, 2, 71, 74, 7, 24, 2, 2, 72, 73, 7, 31, 2, 2, 73, 75, 6, 6, 3, 2, 74, 72, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 79, 3, 2, 2, 2, 76, 77, 7, 24, 2, 2, 77, 78, 7, 31, 2, 2, 78, 80, 6, 6, 4, 2, 79, 76, 3, 2, 2, 2, 79, 80, 3, 2, 2, 2, 80, 82, 3, 2, 2, 2, 81, 71, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 90, 3, 2, 2, 2, 83, 90, 7, 7, 2, 2, 84, 85, 7, 28, 2, 2, 85, 86, 7, 26, 2, 2, 86, 87, 5, 12, 7, 2, 87, 88, 7, 27, 2, 2, 88, 90, 3, 2, 2, 2, 89, 68, 3, 2, 2, 2, 89, 69, 3, 2, 2, 2, 89, 83, 3, 2, 2, 2, 89, 84, 3, 2, 2, 2, 90, 11, 3, 2, 2, 2, 91, 92, 5, 14, 8, 2, 92, 13, 3, 2, 2, 2, 93, 96, 5, 16, 9, 2, 94, 95, 7, 8, 2, 2, 95, 97, 5, 14, 8, 2, 96, 94, 3, 2, 2, 2, 96, 97, 3, 2, 2, 2, 97, 15, 3, 2, 2, 2, 98, 101, 5, 18, 10, 2, 99, 100, 7, 16, 2, 2, 100, 102, 5, 16, 9, 2, 101, 99, 3, 2, 2, 2, 101, 102, 3, 2, 2, 2, 102, 17, 3, 2, 2, 2, 103, 104, 7, 15, 2, 2, 104, 123, 5, 18, 10, 2, 105, 106, 7, 26, 2, 2, 106, 107, 5, 12, 7, 2, 107, 108, 7, 27, 2, 2, 108, 123, 3, 2, 2, 2, 109, 111, 9, 3, 2, 2, 110, 112, 5, 4, 3, 2, 111, 110, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 115, 3, 2, 2, 2, 113, 114, 9, 4, 2, 2, 114, 116, 5, 28, 15, 2, 115, 113, 3, 2, 2, 2, 115, 116, 3, 2, 2, 2, 116, 123, 3, 2, 2, 2, 117, 120, 5, 28, 15, 2, 118, 119, 9, 4, 2, 2, 119, 121, 5, 28, 15, 2, 120, 118, 3, 2, 2, 2, 120, 121, 3, 2, 2, 2, 121, 123, 3, 2, 2, 2, 122, 103, 3, 2, 2, 2, 122, 105, 3, 2, 2, 2, 122, 109, 3, 2, 2, 2, 122, 117, 3, 2, 2, 2, 123, 19, 3, 2, 2, 2, 124, 125, 5, 28, 15, 2, 125, 21, 3, 2, 2, 2, 126, 127, 7, 20, 2, 2, 127, 132, 5, 24, 13, 2, 128, 129, 7, 25, 2, 2, 129, 131, 5, 24, 13, 2, 130, 128, 3, 2, 2, 2, 131, 134, 3, 2, 2, 2, 132, 130, 3, 2, 2, 2, 132, 133, 3, 2, 2, 2, 133, 135, 3, 2, 2, 2, 134, 132, 3, 2, 2, 2, 135, 136, 7, 21, 2, 2, 136, 140, 3, 2, 2, 2, 137, 138, 7, 20, 2, 2, 138, 140, 7, 21, 2, 2, 139, 126, 3, 2, 2, 2, 139, 137, 3, 2, 2, 2, 140, 23, 3, 2, 2, 2, 141, 142, 7, 30, 2, 2, 142, 143, 7, 24, 2, 2, 143, 144, 5, 28, 15, 2, 144, 25, 3, 2, 2, 2, 145, 146, 7, 22, 2, 2, 146, 151, 5, 28, 15, 2, 147, 148, 7, 25, 2, 2, 148, 150, 5, 28, 15, 2, 149, 147, 3, 2, 2, 2, 150, 153, 3, 2, 2, 2, 151, 149, 3, 2, 2, 2, 151, 152, 3, 2, 2, 2, 152, 154, 3, 2, 2, 2, 153, 151, 3, 2, 2, 2, 154, 155, 7, 23, 2, 2, 155, 159, 3, 2, 2, 2, 156, 157, 7, 22, 2, 2, 157, 159, 7, 23, 2, 2, 158, 145, 3, 2, 2, 2, 158, 156, 3, 2, 2, 2, 159, 27, 3, 2, 2, 2, 160, 168, 7, 30, 2, 2, 161, 168, 7, 31, 2, 2, 162, 168, 5, 22, 12, 2, 163, 168, 5, 26, 14, 2, 164, 168, 7, 17, 2, 2, 165, 168, 7, 18, 2, 2, 166, 168, 7, 19, 2, 2, 167, 160, 3, 2, 2, 2, 167, 161, 3, 2, 2, 2, 167, 162, 3, 2, 2, 2, 167, 163, 3, 2, 2, 2, 167, 164, 3, 2, 2, 2, 167, 165, 3, 2, 2, 2, 167, 166, 3, 2, 2, 2, 168, 29, 3, 2, 2, 2, 24, 32, 39, 42, 47, 51, 53, 61, 74, 79, 81, 89, 96, 101, 111, 115, 120, 122, 132, 139, 151, 158, 167] \ No newline at end of file diff --git a/jsonpath2/parser/JSONPathLexer.py b/jsonpath2/parser/JSONPathLexer.py index 1fb2fac..3dbd77e 100644 --- a/jsonpath2/parser/JSONPathLexer.py +++ b/jsonpath2/parser/JSONPathLexer.py @@ -1,4 +1,4 @@ -# Generated from pacifica/jsonpath/parser/JSONPath.g4 by ANTLR 4.7.1 +# Generated from JSONPath.g4 by ANTLR 4.7.1 from antlr4 import * from io import StringIO from typing.io import TextIO diff --git a/jsonpath2/parser/JSONPathListener.py b/jsonpath2/parser/JSONPathListener.py index a4edb5e..fe76159 100644 --- a/jsonpath2/parser/JSONPathListener.py +++ b/jsonpath2/parser/JSONPathListener.py @@ -1,4 +1,4 @@ -# Generated from pacifica/jsonpath/parser/JSONPath.g4 by ANTLR 4.7.1 +# Generated from JSONPath.g4 by ANTLR 4.7.1 from antlr4 import * if __name__ is not None and "." in __name__: from .JSONPathParser import JSONPathParser diff --git a/jsonpath2/parser/JSONPathParser.py b/jsonpath2/parser/JSONPathParser.py index 76ccdf6..112ab73 100644 --- a/jsonpath2/parser/JSONPathParser.py +++ b/jsonpath2/parser/JSONPathParser.py @@ -1,4 +1,4 @@ -# Generated from pacifica/jsonpath/parser/JSONPath.g4 by ANTLR 4.7.1 +# Generated from JSONPath.g4 by ANTLR 4.7.1 # encoding: utf-8 from antlr4 import * from io import StringIO @@ -8,7 +8,7 @@ def serializedATN(): with StringIO() as buf: buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3 ") - buf.write("\u00a5\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") + buf.write("\u00aa\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") buf.write("\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16") buf.write("\t\16\4\17\t\17\3\2\3\2\5\2!\n\2\3\2\3\2\3\3\3\3\3\3\5") buf.write("\3(\n\3\3\3\5\3+\n\3\3\3\3\3\3\3\5\3\60\n\3\3\3\3\3\5") @@ -17,59 +17,61 @@ def serializedATN(): buf.write("\6\3\6\3\6\3\6\5\6P\n\6\5\6R\n\6\3\6\3\6\3\6\3\6\3\6\3") buf.write("\6\5\6Z\n\6\3\7\3\7\3\b\3\b\3\b\5\ba\n\b\3\t\3\t\3\t\5") buf.write("\tf\n\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\5\np\n\n\3\n\3") - buf.write("\n\5\nt\n\n\5\nv\n\n\3\13\3\13\3\f\3\f\3\f\3\f\7\f~\n") - buf.write("\f\f\f\16\f\u0081\13\f\3\f\3\f\3\f\3\f\5\f\u0087\n\f\3") - buf.write("\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\7\16\u0091\n\16\f\16") - buf.write("\16\16\u0094\13\16\3\16\3\16\3\16\3\16\5\16\u009a\n\16") - buf.write("\3\17\3\17\3\17\3\17\3\17\3\17\3\17\5\17\u00a3\n\17\3") - buf.write("\17\2\2\20\2\4\6\b\n\f\16\20\22\24\26\30\32\34\2\5\4\2") - buf.write("\7\7\35\35\4\2\3\3\5\5\3\2\t\16\2\u00b4\2\36\3\2\2\2\4") - buf.write("\65\3\2\2\2\6\67\3\2\2\2\bB\3\2\2\2\nY\3\2\2\2\f[\3\2") - buf.write("\2\2\16]\3\2\2\2\20b\3\2\2\2\22u\3\2\2\2\24w\3\2\2\2\26") - buf.write("\u0086\3\2\2\2\30\u0088\3\2\2\2\32\u0099\3\2\2\2\34\u00a2") - buf.write("\3\2\2\2\36 \7\5\2\2\37!\5\4\3\2 \37\3\2\2\2 !\3\2\2\2") - buf.write("!\"\3\2\2\2\"#\7\2\2\3#\3\3\2\2\2$\'\7\4\2\2%(\5\b\5\2") - buf.write("&(\5\6\4\2\'%\3\2\2\2\'&\3\2\2\2(*\3\2\2\2)+\5\4\3\2*") - buf.write(")\3\2\2\2*+\3\2\2\2+\66\3\2\2\2,-\7\6\2\2-/\5\b\5\2.\60") - buf.write("\5\4\3\2/.\3\2\2\2/\60\3\2\2\2\60\66\3\2\2\2\61\63\5\6") - buf.write("\4\2\62\64\5\4\3\2\63\62\3\2\2\2\63\64\3\2\2\2\64\66\3") - buf.write("\2\2\2\65$\3\2\2\2\65,\3\2\2\2\65\61\3\2\2\2\66\5\3\2") - buf.write("\2\2\678\7\26\2\28=\5\n\6\29:\7\31\2\2:<\5\n\6\2;9\3\2") - buf.write("\2\2\3\2\2\2>@\3\2\2\2?=\3\2\2\2") - buf.write("@A\7\27\2\2A\7\3\2\2\2BC\t\2\2\2C\t\3\2\2\2DZ\7\36\2\2") - buf.write("EF\7\37\2\2FQ\6\6\2\2GJ\7\30\2\2HI\7\37\2\2IK\6\6\3\2") - buf.write("JH\3\2\2\2JK\3\2\2\2KO\3\2\2\2LM\7\30\2\2MN\7\37\2\2N") - buf.write("P\6\6\4\2OL\3\2\2\2OP\3\2\2\2PR\3\2\2\2QG\3\2\2\2QR\3") - buf.write("\2\2\2RZ\3\2\2\2SZ\7\7\2\2TU\7\34\2\2UV\7\32\2\2VW\5\f") - buf.write("\7\2WX\7\33\2\2XZ\3\2\2\2YD\3\2\2\2YE\3\2\2\2YS\3\2\2") - buf.write("\2YT\3\2\2\2Z\13\3\2\2\2[\\\5\16\b\2\\\r\3\2\2\2]`\5\20") - buf.write("\t\2^_\7\b\2\2_a\5\16\b\2`^\3\2\2\2`a\3\2\2\2a\17\3\2") - buf.write("\2\2be\5\22\n\2cd\7\20\2\2df\5\20\t\2ec\3\2\2\2ef\3\2") - buf.write("\2\2f\21\3\2\2\2gh\7\17\2\2hv\5\22\n\2ij\7\32\2\2jk\5") - buf.write("\f\7\2kl\7\33\2\2lv\3\2\2\2mo\t\3\2\2np\5\4\3\2on\3\2") - buf.write("\2\2op\3\2\2\2ps\3\2\2\2qr\t\4\2\2rt\5\34\17\2sq\3\2\2") - buf.write("\2st\3\2\2\2tv\3\2\2\2ug\3\2\2\2ui\3\2\2\2um\3\2\2\2v") - buf.write("\23\3\2\2\2wx\5\34\17\2x\25\3\2\2\2yz\7\24\2\2z\177\5") - buf.write("\30\r\2{|\7\31\2\2|~\5\30\r\2}{\3\2\2\2~\u0081\3\2\2\2") - buf.write("\177}\3\2\2\2\177\u0080\3\2\2\2\u0080\u0082\3\2\2\2\u0081") - buf.write("\177\3\2\2\2\u0082\u0083\7\25\2\2\u0083\u0087\3\2\2\2") - buf.write("\u0084\u0085\7\24\2\2\u0085\u0087\7\25\2\2\u0086y\3\2") - buf.write("\2\2\u0086\u0084\3\2\2\2\u0087\27\3\2\2\2\u0088\u0089") - buf.write("\7\36\2\2\u0089\u008a\7\30\2\2\u008a\u008b\5\34\17\2\u008b") - buf.write("\31\3\2\2\2\u008c\u008d\7\26\2\2\u008d\u0092\5\34\17\2") - buf.write("\u008e\u008f\7\31\2\2\u008f\u0091\5\34\17\2\u0090\u008e") - buf.write("\3\2\2\2\u0091\u0094\3\2\2\2\u0092\u0090\3\2\2\2\u0092") - buf.write("\u0093\3\2\2\2\u0093\u0095\3\2\2\2\u0094\u0092\3\2\2\2") - buf.write("\u0095\u0096\7\27\2\2\u0096\u009a\3\2\2\2\u0097\u0098") - buf.write("\7\26\2\2\u0098\u009a\7\27\2\2\u0099\u008c\3\2\2\2\u0099") - buf.write("\u0097\3\2\2\2\u009a\33\3\2\2\2\u009b\u00a3\7\36\2\2\u009c") - buf.write("\u00a3\7\37\2\2\u009d\u00a3\5\26\f\2\u009e\u00a3\5\32") - buf.write("\16\2\u009f\u00a3\7\21\2\2\u00a0\u00a3\7\22\2\2\u00a1") - buf.write("\u00a3\7\23\2\2\u00a2\u009b\3\2\2\2\u00a2\u009c\3\2\2") - buf.write("\2\u00a2\u009d\3\2\2\2\u00a2\u009e\3\2\2\2\u00a2\u009f") - buf.write("\3\2\2\2\u00a2\u00a0\3\2\2\2\u00a2\u00a1\3\2\2\2\u00a3") - buf.write("\35\3\2\2\2\27 \'*/\63\65=JOQY`eosu\177\u0086\u0092\u0099") - buf.write("\u00a2") + buf.write("\n\5\nt\n\n\3\n\3\n\3\n\5\ny\n\n\5\n{\n\n\3\13\3\13\3") + buf.write("\f\3\f\3\f\3\f\7\f\u0083\n\f\f\f\16\f\u0086\13\f\3\f\3") + buf.write("\f\3\f\3\f\5\f\u008c\n\f\3\r\3\r\3\r\3\r\3\16\3\16\3\16") + buf.write("\3\16\7\16\u0096\n\16\f\16\16\16\u0099\13\16\3\16\3\16") + buf.write("\3\16\3\16\5\16\u009f\n\16\3\17\3\17\3\17\3\17\3\17\3") + buf.write("\17\3\17\5\17\u00a8\n\17\3\17\2\2\20\2\4\6\b\n\f\16\20") + buf.write("\22\24\26\30\32\34\2\5\4\2\7\7\35\35\4\2\3\3\5\5\3\2\t") + buf.write("\16\2\u00bb\2\36\3\2\2\2\4\65\3\2\2\2\6\67\3\2\2\2\bB") + buf.write("\3\2\2\2\nY\3\2\2\2\f[\3\2\2\2\16]\3\2\2\2\20b\3\2\2\2") + buf.write("\22z\3\2\2\2\24|\3\2\2\2\26\u008b\3\2\2\2\30\u008d\3\2") + buf.write("\2\2\32\u009e\3\2\2\2\34\u00a7\3\2\2\2\36 \7\5\2\2\37") + buf.write("!\5\4\3\2 \37\3\2\2\2 !\3\2\2\2!\"\3\2\2\2\"#\7\2\2\3") + buf.write("#\3\3\2\2\2$\'\7\4\2\2%(\5\b\5\2&(\5\6\4\2\'%\3\2\2\2") + buf.write("\'&\3\2\2\2(*\3\2\2\2)+\5\4\3\2*)\3\2\2\2*+\3\2\2\2+\66") + buf.write("\3\2\2\2,-\7\6\2\2-/\5\b\5\2.\60\5\4\3\2/.\3\2\2\2/\60") + buf.write("\3\2\2\2\60\66\3\2\2\2\61\63\5\6\4\2\62\64\5\4\3\2\63") + buf.write("\62\3\2\2\2\63\64\3\2\2\2\64\66\3\2\2\2\65$\3\2\2\2\65") + buf.write(",\3\2\2\2\65\61\3\2\2\2\66\5\3\2\2\2\678\7\26\2\28=\5") + buf.write("\n\6\29:\7\31\2\2:<\5\n\6\2;9\3\2\2\2\3\2\2\2>@\3\2\2\2?=\3\2\2\2@A\7\27\2\2A\7\3\2\2") + buf.write("\2BC\t\2\2\2C\t\3\2\2\2DZ\7\36\2\2EF\7\37\2\2FQ\6\6\2") + buf.write("\2GJ\7\30\2\2HI\7\37\2\2IK\6\6\3\2JH\3\2\2\2JK\3\2\2\2") + buf.write("KO\3\2\2\2LM\7\30\2\2MN\7\37\2\2NP\6\6\4\2OL\3\2\2\2O") + buf.write("P\3\2\2\2PR\3\2\2\2QG\3\2\2\2QR\3\2\2\2RZ\3\2\2\2SZ\7") + buf.write("\7\2\2TU\7\34\2\2UV\7\32\2\2VW\5\f\7\2WX\7\33\2\2XZ\3") + buf.write("\2\2\2YD\3\2\2\2YE\3\2\2\2YS\3\2\2\2YT\3\2\2\2Z\13\3\2") + buf.write("\2\2[\\\5\16\b\2\\\r\3\2\2\2]`\5\20\t\2^_\7\b\2\2_a\5") + buf.write("\16\b\2`^\3\2\2\2`a\3\2\2\2a\17\3\2\2\2be\5\22\n\2cd\7") + buf.write("\20\2\2df\5\20\t\2ec\3\2\2\2ef\3\2\2\2f\21\3\2\2\2gh\7") + buf.write("\17\2\2h{\5\22\n\2ij\7\32\2\2jk\5\f\7\2kl\7\33\2\2l{\3") + buf.write("\2\2\2mo\t\3\2\2np\5\4\3\2on\3\2\2\2op\3\2\2\2ps\3\2\2") + buf.write("\2qr\t\4\2\2rt\5\34\17\2sq\3\2\2\2st\3\2\2\2t{\3\2\2\2") + buf.write("ux\5\34\17\2vw\t\4\2\2wy\5\34\17\2xv\3\2\2\2xy\3\2\2\2") + buf.write("y{\3\2\2\2zg\3\2\2\2zi\3\2\2\2zm\3\2\2\2zu\3\2\2\2{\23") + buf.write("\3\2\2\2|}\5\34\17\2}\25\3\2\2\2~\177\7\24\2\2\177\u0084") + buf.write("\5\30\r\2\u0080\u0081\7\31\2\2\u0081\u0083\5\30\r\2\u0082") + buf.write("\u0080\3\2\2\2\u0083\u0086\3\2\2\2\u0084\u0082\3\2\2\2") + buf.write("\u0084\u0085\3\2\2\2\u0085\u0087\3\2\2\2\u0086\u0084\3") + buf.write("\2\2\2\u0087\u0088\7\25\2\2\u0088\u008c\3\2\2\2\u0089") + buf.write("\u008a\7\24\2\2\u008a\u008c\7\25\2\2\u008b~\3\2\2\2\u008b") + buf.write("\u0089\3\2\2\2\u008c\27\3\2\2\2\u008d\u008e\7\36\2\2\u008e") + buf.write("\u008f\7\30\2\2\u008f\u0090\5\34\17\2\u0090\31\3\2\2\2") + buf.write("\u0091\u0092\7\26\2\2\u0092\u0097\5\34\17\2\u0093\u0094") + buf.write("\7\31\2\2\u0094\u0096\5\34\17\2\u0095\u0093\3\2\2\2\u0096") + buf.write("\u0099\3\2\2\2\u0097\u0095\3\2\2\2\u0097\u0098\3\2\2\2") + buf.write("\u0098\u009a\3\2\2\2\u0099\u0097\3\2\2\2\u009a\u009b\7") + buf.write("\27\2\2\u009b\u009f\3\2\2\2\u009c\u009d\7\26\2\2\u009d") + buf.write("\u009f\7\27\2\2\u009e\u0091\3\2\2\2\u009e\u009c\3\2\2") + buf.write("\2\u009f\33\3\2\2\2\u00a0\u00a8\7\36\2\2\u00a1\u00a8\7") + buf.write("\37\2\2\u00a2\u00a8\5\26\f\2\u00a3\u00a8\5\32\16\2\u00a4") + buf.write("\u00a8\7\21\2\2\u00a5\u00a8\7\22\2\2\u00a6\u00a8\7\23") + buf.write("\2\2\u00a7\u00a0\3\2\2\2\u00a7\u00a1\3\2\2\2\u00a7\u00a2") + buf.write("\3\2\2\2\u00a7\u00a3\3\2\2\2\u00a7\u00a4\3\2\2\2\u00a7") + buf.write("\u00a5\3\2\2\2\u00a7\u00a6\3\2\2\2\u00a8\35\3\2\2\2\30") + buf.write(" \'*/\63\65=JOQY`eosxz\u0084\u008b\u0097\u009e\u00a7") return buf.getvalue() @@ -768,8 +770,11 @@ def subscript(self): return self.getTypedRuleContext(JSONPathParser.SubscriptContext,0) - def value(self): - return self.getTypedRuleContext(JSONPathParser.ValueContext,0) + def value(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(JSONPathParser.ValueContext) + else: + return self.getTypedRuleContext(JSONPathParser.ValueContext,i) def EQ(self): @@ -810,7 +815,7 @@ def notExpression(self): self.enterRule(localctx, 16, self.RULE_notExpression) self._la = 0 # Token type try: - self.state = 115 + self.state = 120 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.NOT]: @@ -861,6 +866,26 @@ def notExpression(self): self.value() + pass + elif token in [JSONPathParser.TRUE, JSONPathParser.FALSE, JSONPathParser.NULL, JSONPathParser.BRACE_LEFT, JSONPathParser.BRACKET_LEFT, JSONPathParser.STRING, JSONPathParser.NUMBER]: + self.enterOuterAlt(localctx, 4) + self.state = 115 + self.value() + self.state = 118 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.EQ) | (1 << JSONPathParser.GE) | (1 << JSONPathParser.GT) | (1 << JSONPathParser.LE) | (1 << JSONPathParser.LT) | (1 << JSONPathParser.NE))) != 0): + self.state = 116 + _la = self._input.LA(1) + if not((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.EQ) | (1 << JSONPathParser.GE) | (1 << JSONPathParser.GT) | (1 << JSONPathParser.LE) | (1 << JSONPathParser.LT) | (1 << JSONPathParser.NE))) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 117 + self.value() + + pass else: raise NoViableAltException(self) @@ -903,7 +928,7 @@ def json(self): self.enterRule(localctx, 18, self.RULE_json) try: self.enterOuterAlt(localctx, 1) - self.state = 117 + self.state = 122 self.value() except RecognitionException as re: localctx.exception = re @@ -958,36 +983,36 @@ def obj(self): self.enterRule(localctx, 20, self.RULE_obj) self._la = 0 # Token type try: - self.state = 132 + self.state = 137 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,17,self._ctx) + la_ = self._interp.adaptivePredict(self._input,18,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 119 + self.state = 124 self.match(JSONPathParser.BRACE_LEFT) - self.state = 120 - self.pair() self.state = 125 + self.pair() + self.state = 130 self._errHandler.sync(self) _la = self._input.LA(1) while _la==JSONPathParser.COMMA: - self.state = 121 + self.state = 126 self.match(JSONPathParser.COMMA) - self.state = 122 - self.pair() self.state = 127 + self.pair() + self.state = 132 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 128 + self.state = 133 self.match(JSONPathParser.BRACE_RIGHT) pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 130 + self.state = 135 self.match(JSONPathParser.BRACE_LEFT) - self.state = 131 + self.state = 136 self.match(JSONPathParser.BRACE_RIGHT) pass @@ -1036,11 +1061,11 @@ def pair(self): self.enterRule(localctx, 22, self.RULE_pair) try: self.enterOuterAlt(localctx, 1) - self.state = 134 + self.state = 139 self.match(JSONPathParser.STRING) - self.state = 135 + self.state = 140 self.match(JSONPathParser.COLON) - self.state = 136 + self.state = 141 self.value() except RecognitionException as re: localctx.exception = re @@ -1095,36 +1120,36 @@ def array(self): self.enterRule(localctx, 24, self.RULE_array) self._la = 0 # Token type try: - self.state = 151 + self.state = 156 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,19,self._ctx) + la_ = self._interp.adaptivePredict(self._input,20,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 138 + self.state = 143 self.match(JSONPathParser.BRACKET_LEFT) - self.state = 139 - self.value() self.state = 144 + self.value() + self.state = 149 self._errHandler.sync(self) _la = self._input.LA(1) while _la==JSONPathParser.COMMA: - self.state = 140 + self.state = 145 self.match(JSONPathParser.COMMA) - self.state = 141 - self.value() self.state = 146 + self.value() + self.state = 151 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 147 + self.state = 152 self.match(JSONPathParser.BRACKET_RIGHT) pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 149 + self.state = 154 self.match(JSONPathParser.BRACKET_LEFT) - self.state = 150 + self.state = 155 self.match(JSONPathParser.BRACKET_RIGHT) pass @@ -1185,42 +1210,42 @@ def value(self): localctx = JSONPathParser.ValueContext(self, self._ctx, self.state) self.enterRule(localctx, 26, self.RULE_value) try: - self.state = 160 + self.state = 165 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.STRING]: self.enterOuterAlt(localctx, 1) - self.state = 153 + self.state = 158 self.match(JSONPathParser.STRING) pass elif token in [JSONPathParser.NUMBER]: self.enterOuterAlt(localctx, 2) - self.state = 154 + self.state = 159 self.match(JSONPathParser.NUMBER) pass elif token in [JSONPathParser.BRACE_LEFT]: self.enterOuterAlt(localctx, 3) - self.state = 155 + self.state = 160 self.obj() pass elif token in [JSONPathParser.BRACKET_LEFT]: self.enterOuterAlt(localctx, 4) - self.state = 156 + self.state = 161 self.array() pass elif token in [JSONPathParser.TRUE]: self.enterOuterAlt(localctx, 5) - self.state = 157 + self.state = 162 self.match(JSONPathParser.TRUE) pass elif token in [JSONPathParser.FALSE]: self.enterOuterAlt(localctx, 6) - self.state = 158 + self.state = 163 self.match(JSONPathParser.FALSE) pass elif token in [JSONPathParser.NULL]: self.enterOuterAlt(localctx, 7) - self.state = 159 + self.state = 164 self.match(JSONPathParser.NULL) pass else: diff --git a/jsonpath2/test/bookstore_test.py b/jsonpath2/test/bookstore_test.py new file mode 100644 index 0000000..384898d --- /dev/null +++ b/jsonpath2/test/bookstore_test.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Test the jsonpath module.""" +from unittest import TestCase +from json import loads +from jsonpath2.node import MatchData +from jsonpath2.expressions.some import SomeExpression +from jsonpath2.nodes.current import CurrentNode +from jsonpath2.nodes.recursivedescent import RecursiveDescentNode +from jsonpath2.nodes.root import RootNode +from jsonpath2.nodes.subscript import SubscriptNode +from jsonpath2.nodes.terminal import TerminalNode +from jsonpath2.path import Path +from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript +from jsonpath2.subscripts.filter import FilterSubscript +from jsonpath2.subscripts.objectindex import ObjectIndexSubscript +from jsonpath2.subscripts.wildcard import WildcardSubscript + + +class TestBookStore(TestCase): + """ + Test the bookstore from original jsonpath article. + + http://goessner.net/articles/JsonPath/ + """ + + def setUp(self): + """Setup the class.""" + self.root_value = { + 'store': { + 'book': [ + { + 'category': 'reference', + 'author': 'Nigel Rees', + 'title': 'Sayings of the Century', + 'price': 8.95 + }, + { + 'category': 'fiction', + 'author': 'Evelyn Waugh', + 'title': 'Sword of Honour', + 'price': 12.99 + }, + { + 'category': 'fiction', + 'author': 'Herman Melville', + 'title': 'Moby Dick', + 'isbn': '0-553-21311-3', + 'price': 8.99 + }, + { + 'category': 'fiction', + 'author': 'J. R. R. Tolkien', + 'title': 'The Lord of the Rings', + 'isbn': '0-395-19395-8', + 'price': 22.99 + } + ], + 'bicycle': { + 'color': 'red', + 'price': 19.95 + } + } + } + + def test_bookstore_examples_1(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$.store.book[*].author') + matches = [x.current_value for x in expr.match(self.root_value)] + for auth in ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']: + self.assertTrue(auth in matches) + + def test_bookstore_examples_2(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$..author') + matches = [x.current_value for x in expr.match(self.root_value)] + for auth in ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']: + self.assertTrue(auth in matches) + + def test_bookstore_examples_3(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$.store.*') + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertTrue(isinstance(matches[0], list)) + self.assertTrue(isinstance(matches[1], dict)) + self.assertEqual(matches[0][0]['author'], 'Nigel Rees') + self.assertEqual(matches[1]['color'], 'red') + + def test_bookstore_examples_4(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$.store..price') + matches = [x.current_value for x in expr.match(self.root_value)] + for price in [8.95, 12.99, 8.99, 22.99, 19.95]: + self.assertTrue(price in matches) + + def test_bookstore_examples_5(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$..book[2]') + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'fiction') + self.assertEqual(matches[0]['author'], 'Herman Melville') + self.assertEqual(matches[0]['title'], 'Moby Dick') + self.assertEqual(matches[0]['isbn'], '0-553-21311-3') + self.assertEqual(matches[0]['price'], 8.99) + + def test_bookstore_examples_6(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$..book[-1:]') + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'fiction') + self.assertEqual(matches[0]['author'], 'J. R. R. Tolkien') + self.assertEqual(matches[0]['title'], 'The Lord of the Rings') + self.assertEqual(matches[0]['isbn'], '0-395-19395-8') + self.assertEqual(matches[0]['price'], 22.99) + + def test_bookstore_examples_7(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$..book[0,1]') + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'reference') + self.assertEqual(matches[0]['author'], 'Nigel Rees') + self.assertEqual(matches[0]['title'], 'Sayings of the Century') + self.assertEqual(matches[0]['price'], 8.95) + self.assertEqual(matches[1]['category'], 'fiction') + self.assertEqual(matches[1]['author'], 'Evelyn Waugh') + self.assertEqual(matches[1]['title'], 'Sword of Honour') + self.assertEqual(matches[1]['price'], 12.99) + + def test_bookstore_examples_8(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$..book[?(@.isbn)]') + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'fiction') + self.assertEqual(matches[0]['author'], 'Herman Melville') + self.assertEqual(matches[0]['title'], 'Moby Dick') + self.assertEqual(matches[0]['isbn'], '0-553-21311-3') + self.assertEqual(matches[0]['price'], 8.99) + self.assertEqual(matches[1]['category'], 'fiction') + self.assertEqual(matches[1]['author'], 'J. R. R. Tolkien') + self.assertEqual(matches[1]['title'], 'The Lord of the Rings') + self.assertEqual(matches[1]['isbn'], '0-395-19395-8') + self.assertEqual(matches[1]['price'], 22.99) + + def test_bookstore_examples_9(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$..book[?(@.price<10)]') + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'reference') + self.assertEqual(matches[0]['author'], 'Nigel Rees') + self.assertEqual(matches[0]['title'], 'Sayings of the Century') + self.assertEqual(matches[0]['price'], 8.95) + self.assertEqual(matches[1]['category'], 'fiction') + self.assertEqual(matches[1]['author'], 'Herman Melville') + self.assertEqual(matches[1]['title'], 'Moby Dick') + self.assertEqual(matches[1]['isbn'], '0-553-21311-3') + self.assertEqual(matches[1]['price'], 8.99) diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index 49f2ebd..7944784 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -2,7 +2,9 @@ # -*- coding: utf-8 -*- """Test the expression object.""" from unittest import TestCase -from jsonpath2.expressions.operator import OperatorExpression +from jsonpath2.nodes.subscript import SubscriptNode +from jsonpath2.nodes.terminal import TerminalNode +from jsonpath2.expressions.operator import OperatorExpression, BinaryOperatorExpression class TestExpression(TestCase): From 3abd07d079f23602510f12a44f71ce184ea9a415 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 18 Sep 2018 09:35:32 -0700 Subject: [PATCH 08/23] pre-commit fixes Signed-off-by: David Brown --- jsonpath2/test/bookstore_test.py | 12 ------------ jsonpath2/test/expression_test.py | 4 +--- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/jsonpath2/test/bookstore_test.py b/jsonpath2/test/bookstore_test.py index 384898d..0b31515 100644 --- a/jsonpath2/test/bookstore_test.py +++ b/jsonpath2/test/bookstore_test.py @@ -2,19 +2,7 @@ # -*- coding: utf-8 -*- """Test the jsonpath module.""" from unittest import TestCase -from json import loads -from jsonpath2.node import MatchData -from jsonpath2.expressions.some import SomeExpression -from jsonpath2.nodes.current import CurrentNode -from jsonpath2.nodes.recursivedescent import RecursiveDescentNode -from jsonpath2.nodes.root import RootNode -from jsonpath2.nodes.subscript import SubscriptNode -from jsonpath2.nodes.terminal import TerminalNode from jsonpath2.path import Path -from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript -from jsonpath2.subscripts.filter import FilterSubscript -from jsonpath2.subscripts.objectindex import ObjectIndexSubscript -from jsonpath2.subscripts.wildcard import WildcardSubscript class TestBookStore(TestCase): diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index 7944784..49f2ebd 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -2,9 +2,7 @@ # -*- coding: utf-8 -*- """Test the expression object.""" from unittest import TestCase -from jsonpath2.nodes.subscript import SubscriptNode -from jsonpath2.nodes.terminal import TerminalNode -from jsonpath2.expressions.operator import OperatorExpression, BinaryOperatorExpression +from jsonpath2.expressions.operator import OperatorExpression class TestExpression(TestCase): From 6976755a0ddd8559b85e202d441f57336fa45d16 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Tue, 18 Sep 2018 09:54:59 -0700 Subject: [PATCH 09/23] Rollback modification to grammar --- jsonpath2/parser/JSONPath.g4 | 1 - jsonpath2/parser/JSONPath.interp | 2 +- jsonpath2/parser/JSONPathLexer.py | 2 +- jsonpath2/parser/JSONPathListener.py | 2 +- jsonpath2/parser/JSONPathParser.py | 209 ++++++++++++--------------- 5 files changed, 95 insertions(+), 121 deletions(-) diff --git a/jsonpath2/parser/JSONPath.g4 b/jsonpath2/parser/JSONPath.g4 index 03a8a3f..c059868 100644 --- a/jsonpath2/parser/JSONPath.g4 +++ b/jsonpath2/parser/JSONPath.g4 @@ -72,7 +72,6 @@ notExpression : NOT notExpression | PAREN_LEFT expression PAREN_RIGHT | ( ROOT_VALUE | CURRENT_VALUE ) subscript? ( ( EQ | NE | LT | LE | GT | GE ) value )? - | value ( ( EQ | NE | LT | LE | GT | GE ) value )? ; diff --git a/jsonpath2/parser/JSONPath.interp b/jsonpath2/parser/JSONPath.interp index fa93e0b..2fdafae 100644 --- a/jsonpath2/parser/JSONPath.interp +++ b/jsonpath2/parser/JSONPath.interp @@ -82,4 +82,4 @@ value atn: -[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 32, 170, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 3, 2, 3, 2, 5, 2, 33, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 5, 3, 40, 10, 3, 3, 3, 5, 3, 43, 10, 3, 3, 3, 3, 3, 3, 3, 5, 3, 48, 10, 3, 3, 3, 3, 3, 5, 3, 52, 10, 3, 5, 3, 54, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 60, 10, 4, 12, 4, 14, 4, 63, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 75, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 80, 10, 6, 5, 6, 82, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 90, 10, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 5, 8, 97, 10, 8, 3, 9, 3, 9, 3, 9, 5, 9, 102, 10, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 112, 10, 10, 3, 10, 3, 10, 5, 10, 116, 10, 10, 3, 10, 3, 10, 3, 10, 5, 10, 121, 10, 10, 5, 10, 123, 10, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 131, 10, 12, 12, 12, 14, 12, 134, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 140, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 150, 10, 14, 12, 14, 14, 14, 153, 11, 14, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 159, 10, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 168, 10, 15, 3, 15, 2, 2, 16, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 2, 5, 4, 2, 7, 7, 29, 29, 4, 2, 3, 3, 5, 5, 3, 2, 9, 14, 2, 187, 2, 30, 3, 2, 2, 2, 4, 53, 3, 2, 2, 2, 6, 55, 3, 2, 2, 2, 8, 66, 3, 2, 2, 2, 10, 89, 3, 2, 2, 2, 12, 91, 3, 2, 2, 2, 14, 93, 3, 2, 2, 2, 16, 98, 3, 2, 2, 2, 18, 122, 3, 2, 2, 2, 20, 124, 3, 2, 2, 2, 22, 139, 3, 2, 2, 2, 24, 141, 3, 2, 2, 2, 26, 158, 3, 2, 2, 2, 28, 167, 3, 2, 2, 2, 30, 32, 7, 5, 2, 2, 31, 33, 5, 4, 3, 2, 32, 31, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 34, 3, 2, 2, 2, 34, 35, 7, 2, 2, 3, 35, 3, 3, 2, 2, 2, 36, 39, 7, 4, 2, 2, 37, 40, 5, 8, 5, 2, 38, 40, 5, 6, 4, 2, 39, 37, 3, 2, 2, 2, 39, 38, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 43, 5, 4, 3, 2, 42, 41, 3, 2, 2, 2, 42, 43, 3, 2, 2, 2, 43, 54, 3, 2, 2, 2, 44, 45, 7, 6, 2, 2, 45, 47, 5, 8, 5, 2, 46, 48, 5, 4, 3, 2, 47, 46, 3, 2, 2, 2, 47, 48, 3, 2, 2, 2, 48, 54, 3, 2, 2, 2, 49, 51, 5, 6, 4, 2, 50, 52, 5, 4, 3, 2, 51, 50, 3, 2, 2, 2, 51, 52, 3, 2, 2, 2, 52, 54, 3, 2, 2, 2, 53, 36, 3, 2, 2, 2, 53, 44, 3, 2, 2, 2, 53, 49, 3, 2, 2, 2, 54, 5, 3, 2, 2, 2, 55, 56, 7, 22, 2, 2, 56, 61, 5, 10, 6, 2, 57, 58, 7, 25, 2, 2, 58, 60, 5, 10, 6, 2, 59, 57, 3, 2, 2, 2, 60, 63, 3, 2, 2, 2, 61, 59, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 64, 3, 2, 2, 2, 63, 61, 3, 2, 2, 2, 64, 65, 7, 23, 2, 2, 65, 7, 3, 2, 2, 2, 66, 67, 9, 2, 2, 2, 67, 9, 3, 2, 2, 2, 68, 90, 7, 30, 2, 2, 69, 70, 7, 31, 2, 2, 70, 81, 6, 6, 2, 2, 71, 74, 7, 24, 2, 2, 72, 73, 7, 31, 2, 2, 73, 75, 6, 6, 3, 2, 74, 72, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 79, 3, 2, 2, 2, 76, 77, 7, 24, 2, 2, 77, 78, 7, 31, 2, 2, 78, 80, 6, 6, 4, 2, 79, 76, 3, 2, 2, 2, 79, 80, 3, 2, 2, 2, 80, 82, 3, 2, 2, 2, 81, 71, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 90, 3, 2, 2, 2, 83, 90, 7, 7, 2, 2, 84, 85, 7, 28, 2, 2, 85, 86, 7, 26, 2, 2, 86, 87, 5, 12, 7, 2, 87, 88, 7, 27, 2, 2, 88, 90, 3, 2, 2, 2, 89, 68, 3, 2, 2, 2, 89, 69, 3, 2, 2, 2, 89, 83, 3, 2, 2, 2, 89, 84, 3, 2, 2, 2, 90, 11, 3, 2, 2, 2, 91, 92, 5, 14, 8, 2, 92, 13, 3, 2, 2, 2, 93, 96, 5, 16, 9, 2, 94, 95, 7, 8, 2, 2, 95, 97, 5, 14, 8, 2, 96, 94, 3, 2, 2, 2, 96, 97, 3, 2, 2, 2, 97, 15, 3, 2, 2, 2, 98, 101, 5, 18, 10, 2, 99, 100, 7, 16, 2, 2, 100, 102, 5, 16, 9, 2, 101, 99, 3, 2, 2, 2, 101, 102, 3, 2, 2, 2, 102, 17, 3, 2, 2, 2, 103, 104, 7, 15, 2, 2, 104, 123, 5, 18, 10, 2, 105, 106, 7, 26, 2, 2, 106, 107, 5, 12, 7, 2, 107, 108, 7, 27, 2, 2, 108, 123, 3, 2, 2, 2, 109, 111, 9, 3, 2, 2, 110, 112, 5, 4, 3, 2, 111, 110, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 115, 3, 2, 2, 2, 113, 114, 9, 4, 2, 2, 114, 116, 5, 28, 15, 2, 115, 113, 3, 2, 2, 2, 115, 116, 3, 2, 2, 2, 116, 123, 3, 2, 2, 2, 117, 120, 5, 28, 15, 2, 118, 119, 9, 4, 2, 2, 119, 121, 5, 28, 15, 2, 120, 118, 3, 2, 2, 2, 120, 121, 3, 2, 2, 2, 121, 123, 3, 2, 2, 2, 122, 103, 3, 2, 2, 2, 122, 105, 3, 2, 2, 2, 122, 109, 3, 2, 2, 2, 122, 117, 3, 2, 2, 2, 123, 19, 3, 2, 2, 2, 124, 125, 5, 28, 15, 2, 125, 21, 3, 2, 2, 2, 126, 127, 7, 20, 2, 2, 127, 132, 5, 24, 13, 2, 128, 129, 7, 25, 2, 2, 129, 131, 5, 24, 13, 2, 130, 128, 3, 2, 2, 2, 131, 134, 3, 2, 2, 2, 132, 130, 3, 2, 2, 2, 132, 133, 3, 2, 2, 2, 133, 135, 3, 2, 2, 2, 134, 132, 3, 2, 2, 2, 135, 136, 7, 21, 2, 2, 136, 140, 3, 2, 2, 2, 137, 138, 7, 20, 2, 2, 138, 140, 7, 21, 2, 2, 139, 126, 3, 2, 2, 2, 139, 137, 3, 2, 2, 2, 140, 23, 3, 2, 2, 2, 141, 142, 7, 30, 2, 2, 142, 143, 7, 24, 2, 2, 143, 144, 5, 28, 15, 2, 144, 25, 3, 2, 2, 2, 145, 146, 7, 22, 2, 2, 146, 151, 5, 28, 15, 2, 147, 148, 7, 25, 2, 2, 148, 150, 5, 28, 15, 2, 149, 147, 3, 2, 2, 2, 150, 153, 3, 2, 2, 2, 151, 149, 3, 2, 2, 2, 151, 152, 3, 2, 2, 2, 152, 154, 3, 2, 2, 2, 153, 151, 3, 2, 2, 2, 154, 155, 7, 23, 2, 2, 155, 159, 3, 2, 2, 2, 156, 157, 7, 22, 2, 2, 157, 159, 7, 23, 2, 2, 158, 145, 3, 2, 2, 2, 158, 156, 3, 2, 2, 2, 159, 27, 3, 2, 2, 2, 160, 168, 7, 30, 2, 2, 161, 168, 7, 31, 2, 2, 162, 168, 5, 22, 12, 2, 163, 168, 5, 26, 14, 2, 164, 168, 7, 17, 2, 2, 165, 168, 7, 18, 2, 2, 166, 168, 7, 19, 2, 2, 167, 160, 3, 2, 2, 2, 167, 161, 3, 2, 2, 2, 167, 162, 3, 2, 2, 2, 167, 163, 3, 2, 2, 2, 167, 164, 3, 2, 2, 2, 167, 165, 3, 2, 2, 2, 167, 166, 3, 2, 2, 2, 168, 29, 3, 2, 2, 2, 24, 32, 39, 42, 47, 51, 53, 61, 74, 79, 81, 89, 96, 101, 111, 115, 120, 122, 132, 139, 151, 158, 167] \ No newline at end of file +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 32, 165, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 3, 2, 3, 2, 5, 2, 33, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 5, 3, 40, 10, 3, 3, 3, 5, 3, 43, 10, 3, 3, 3, 3, 3, 3, 3, 5, 3, 48, 10, 3, 3, 3, 3, 3, 5, 3, 52, 10, 3, 5, 3, 54, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 60, 10, 4, 12, 4, 14, 4, 63, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 75, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 80, 10, 6, 5, 6, 82, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 90, 10, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 5, 8, 97, 10, 8, 3, 9, 3, 9, 3, 9, 5, 9, 102, 10, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 112, 10, 10, 3, 10, 3, 10, 5, 10, 116, 10, 10, 5, 10, 118, 10, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 126, 10, 12, 12, 12, 14, 12, 129, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 135, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 145, 10, 14, 12, 14, 14, 14, 148, 11, 14, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 154, 10, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 163, 10, 15, 3, 15, 2, 2, 16, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 2, 5, 4, 2, 7, 7, 29, 29, 4, 2, 3, 3, 5, 5, 3, 2, 9, 14, 2, 180, 2, 30, 3, 2, 2, 2, 4, 53, 3, 2, 2, 2, 6, 55, 3, 2, 2, 2, 8, 66, 3, 2, 2, 2, 10, 89, 3, 2, 2, 2, 12, 91, 3, 2, 2, 2, 14, 93, 3, 2, 2, 2, 16, 98, 3, 2, 2, 2, 18, 117, 3, 2, 2, 2, 20, 119, 3, 2, 2, 2, 22, 134, 3, 2, 2, 2, 24, 136, 3, 2, 2, 2, 26, 153, 3, 2, 2, 2, 28, 162, 3, 2, 2, 2, 30, 32, 7, 5, 2, 2, 31, 33, 5, 4, 3, 2, 32, 31, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 34, 3, 2, 2, 2, 34, 35, 7, 2, 2, 3, 35, 3, 3, 2, 2, 2, 36, 39, 7, 4, 2, 2, 37, 40, 5, 8, 5, 2, 38, 40, 5, 6, 4, 2, 39, 37, 3, 2, 2, 2, 39, 38, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 43, 5, 4, 3, 2, 42, 41, 3, 2, 2, 2, 42, 43, 3, 2, 2, 2, 43, 54, 3, 2, 2, 2, 44, 45, 7, 6, 2, 2, 45, 47, 5, 8, 5, 2, 46, 48, 5, 4, 3, 2, 47, 46, 3, 2, 2, 2, 47, 48, 3, 2, 2, 2, 48, 54, 3, 2, 2, 2, 49, 51, 5, 6, 4, 2, 50, 52, 5, 4, 3, 2, 51, 50, 3, 2, 2, 2, 51, 52, 3, 2, 2, 2, 52, 54, 3, 2, 2, 2, 53, 36, 3, 2, 2, 2, 53, 44, 3, 2, 2, 2, 53, 49, 3, 2, 2, 2, 54, 5, 3, 2, 2, 2, 55, 56, 7, 22, 2, 2, 56, 61, 5, 10, 6, 2, 57, 58, 7, 25, 2, 2, 58, 60, 5, 10, 6, 2, 59, 57, 3, 2, 2, 2, 60, 63, 3, 2, 2, 2, 61, 59, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 64, 3, 2, 2, 2, 63, 61, 3, 2, 2, 2, 64, 65, 7, 23, 2, 2, 65, 7, 3, 2, 2, 2, 66, 67, 9, 2, 2, 2, 67, 9, 3, 2, 2, 2, 68, 90, 7, 30, 2, 2, 69, 70, 7, 31, 2, 2, 70, 81, 6, 6, 2, 2, 71, 74, 7, 24, 2, 2, 72, 73, 7, 31, 2, 2, 73, 75, 6, 6, 3, 2, 74, 72, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 79, 3, 2, 2, 2, 76, 77, 7, 24, 2, 2, 77, 78, 7, 31, 2, 2, 78, 80, 6, 6, 4, 2, 79, 76, 3, 2, 2, 2, 79, 80, 3, 2, 2, 2, 80, 82, 3, 2, 2, 2, 81, 71, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 90, 3, 2, 2, 2, 83, 90, 7, 7, 2, 2, 84, 85, 7, 28, 2, 2, 85, 86, 7, 26, 2, 2, 86, 87, 5, 12, 7, 2, 87, 88, 7, 27, 2, 2, 88, 90, 3, 2, 2, 2, 89, 68, 3, 2, 2, 2, 89, 69, 3, 2, 2, 2, 89, 83, 3, 2, 2, 2, 89, 84, 3, 2, 2, 2, 90, 11, 3, 2, 2, 2, 91, 92, 5, 14, 8, 2, 92, 13, 3, 2, 2, 2, 93, 96, 5, 16, 9, 2, 94, 95, 7, 8, 2, 2, 95, 97, 5, 14, 8, 2, 96, 94, 3, 2, 2, 2, 96, 97, 3, 2, 2, 2, 97, 15, 3, 2, 2, 2, 98, 101, 5, 18, 10, 2, 99, 100, 7, 16, 2, 2, 100, 102, 5, 16, 9, 2, 101, 99, 3, 2, 2, 2, 101, 102, 3, 2, 2, 2, 102, 17, 3, 2, 2, 2, 103, 104, 7, 15, 2, 2, 104, 118, 5, 18, 10, 2, 105, 106, 7, 26, 2, 2, 106, 107, 5, 12, 7, 2, 107, 108, 7, 27, 2, 2, 108, 118, 3, 2, 2, 2, 109, 111, 9, 3, 2, 2, 110, 112, 5, 4, 3, 2, 111, 110, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 115, 3, 2, 2, 2, 113, 114, 9, 4, 2, 2, 114, 116, 5, 28, 15, 2, 115, 113, 3, 2, 2, 2, 115, 116, 3, 2, 2, 2, 116, 118, 3, 2, 2, 2, 117, 103, 3, 2, 2, 2, 117, 105, 3, 2, 2, 2, 117, 109, 3, 2, 2, 2, 118, 19, 3, 2, 2, 2, 119, 120, 5, 28, 15, 2, 120, 21, 3, 2, 2, 2, 121, 122, 7, 20, 2, 2, 122, 127, 5, 24, 13, 2, 123, 124, 7, 25, 2, 2, 124, 126, 5, 24, 13, 2, 125, 123, 3, 2, 2, 2, 126, 129, 3, 2, 2, 2, 127, 125, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 130, 3, 2, 2, 2, 129, 127, 3, 2, 2, 2, 130, 131, 7, 21, 2, 2, 131, 135, 3, 2, 2, 2, 132, 133, 7, 20, 2, 2, 133, 135, 7, 21, 2, 2, 134, 121, 3, 2, 2, 2, 134, 132, 3, 2, 2, 2, 135, 23, 3, 2, 2, 2, 136, 137, 7, 30, 2, 2, 137, 138, 7, 24, 2, 2, 138, 139, 5, 28, 15, 2, 139, 25, 3, 2, 2, 2, 140, 141, 7, 22, 2, 2, 141, 146, 5, 28, 15, 2, 142, 143, 7, 25, 2, 2, 143, 145, 5, 28, 15, 2, 144, 142, 3, 2, 2, 2, 145, 148, 3, 2, 2, 2, 146, 144, 3, 2, 2, 2, 146, 147, 3, 2, 2, 2, 147, 149, 3, 2, 2, 2, 148, 146, 3, 2, 2, 2, 149, 150, 7, 23, 2, 2, 150, 154, 3, 2, 2, 2, 151, 152, 7, 22, 2, 2, 152, 154, 7, 23, 2, 2, 153, 140, 3, 2, 2, 2, 153, 151, 3, 2, 2, 2, 154, 27, 3, 2, 2, 2, 155, 163, 7, 30, 2, 2, 156, 163, 7, 31, 2, 2, 157, 163, 5, 22, 12, 2, 158, 163, 5, 26, 14, 2, 159, 163, 7, 17, 2, 2, 160, 163, 7, 18, 2, 2, 161, 163, 7, 19, 2, 2, 162, 155, 3, 2, 2, 2, 162, 156, 3, 2, 2, 2, 162, 157, 3, 2, 2, 2, 162, 158, 3, 2, 2, 2, 162, 159, 3, 2, 2, 2, 162, 160, 3, 2, 2, 2, 162, 161, 3, 2, 2, 2, 163, 29, 3, 2, 2, 2, 23, 32, 39, 42, 47, 51, 53, 61, 74, 79, 81, 89, 96, 101, 111, 115, 117, 127, 134, 146, 153, 162] \ No newline at end of file diff --git a/jsonpath2/parser/JSONPathLexer.py b/jsonpath2/parser/JSONPathLexer.py index 3dbd77e..8cea2f0 100644 --- a/jsonpath2/parser/JSONPathLexer.py +++ b/jsonpath2/parser/JSONPathLexer.py @@ -1,4 +1,4 @@ -# Generated from JSONPath.g4 by ANTLR 4.7.1 +# Generated from jsonpath2/parser/JSONPath.g4 by ANTLR 4.7.1 from antlr4 import * from io import StringIO from typing.io import TextIO diff --git a/jsonpath2/parser/JSONPathListener.py b/jsonpath2/parser/JSONPathListener.py index fe76159..20bdfee 100644 --- a/jsonpath2/parser/JSONPathListener.py +++ b/jsonpath2/parser/JSONPathListener.py @@ -1,4 +1,4 @@ -# Generated from JSONPath.g4 by ANTLR 4.7.1 +# Generated from jsonpath2/parser/JSONPath.g4 by ANTLR 4.7.1 from antlr4 import * if __name__ is not None and "." in __name__: from .JSONPathParser import JSONPathParser diff --git a/jsonpath2/parser/JSONPathParser.py b/jsonpath2/parser/JSONPathParser.py index 112ab73..e6ac0d3 100644 --- a/jsonpath2/parser/JSONPathParser.py +++ b/jsonpath2/parser/JSONPathParser.py @@ -1,4 +1,4 @@ -# Generated from JSONPath.g4 by ANTLR 4.7.1 +# Generated from jsonpath2/parser/JSONPath.g4 by ANTLR 4.7.1 # encoding: utf-8 from antlr4 import * from io import StringIO @@ -8,7 +8,7 @@ def serializedATN(): with StringIO() as buf: buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3 ") - buf.write("\u00aa\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") + buf.write("\u00a5\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") buf.write("\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16") buf.write("\t\16\4\17\t\17\3\2\3\2\5\2!\n\2\3\2\3\2\3\3\3\3\3\3\5") buf.write("\3(\n\3\3\3\5\3+\n\3\3\3\3\3\3\3\5\3\60\n\3\3\3\3\3\5") @@ -17,61 +17,59 @@ def serializedATN(): buf.write("\6\3\6\3\6\3\6\5\6P\n\6\5\6R\n\6\3\6\3\6\3\6\3\6\3\6\3") buf.write("\6\5\6Z\n\6\3\7\3\7\3\b\3\b\3\b\5\ba\n\b\3\t\3\t\3\t\5") buf.write("\tf\n\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\5\np\n\n\3\n\3") - buf.write("\n\5\nt\n\n\3\n\3\n\3\n\5\ny\n\n\5\n{\n\n\3\13\3\13\3") - buf.write("\f\3\f\3\f\3\f\7\f\u0083\n\f\f\f\16\f\u0086\13\f\3\f\3") - buf.write("\f\3\f\3\f\5\f\u008c\n\f\3\r\3\r\3\r\3\r\3\16\3\16\3\16") - buf.write("\3\16\7\16\u0096\n\16\f\16\16\16\u0099\13\16\3\16\3\16") - buf.write("\3\16\3\16\5\16\u009f\n\16\3\17\3\17\3\17\3\17\3\17\3") - buf.write("\17\3\17\5\17\u00a8\n\17\3\17\2\2\20\2\4\6\b\n\f\16\20") - buf.write("\22\24\26\30\32\34\2\5\4\2\7\7\35\35\4\2\3\3\5\5\3\2\t") - buf.write("\16\2\u00bb\2\36\3\2\2\2\4\65\3\2\2\2\6\67\3\2\2\2\bB") - buf.write("\3\2\2\2\nY\3\2\2\2\f[\3\2\2\2\16]\3\2\2\2\20b\3\2\2\2") - buf.write("\22z\3\2\2\2\24|\3\2\2\2\26\u008b\3\2\2\2\30\u008d\3\2") - buf.write("\2\2\32\u009e\3\2\2\2\34\u00a7\3\2\2\2\36 \7\5\2\2\37") - buf.write("!\5\4\3\2 \37\3\2\2\2 !\3\2\2\2!\"\3\2\2\2\"#\7\2\2\3") - buf.write("#\3\3\2\2\2$\'\7\4\2\2%(\5\b\5\2&(\5\6\4\2\'%\3\2\2\2") - buf.write("\'&\3\2\2\2(*\3\2\2\2)+\5\4\3\2*)\3\2\2\2*+\3\2\2\2+\66") - buf.write("\3\2\2\2,-\7\6\2\2-/\5\b\5\2.\60\5\4\3\2/.\3\2\2\2/\60") - buf.write("\3\2\2\2\60\66\3\2\2\2\61\63\5\6\4\2\62\64\5\4\3\2\63") - buf.write("\62\3\2\2\2\63\64\3\2\2\2\64\66\3\2\2\2\65$\3\2\2\2\65") - buf.write(",\3\2\2\2\65\61\3\2\2\2\66\5\3\2\2\2\678\7\26\2\28=\5") - buf.write("\n\6\29:\7\31\2\2:<\5\n\6\2;9\3\2\2\2\3\2\2\2>@\3\2\2\2?=\3\2\2\2@A\7\27\2\2A\7\3\2\2") - buf.write("\2BC\t\2\2\2C\t\3\2\2\2DZ\7\36\2\2EF\7\37\2\2FQ\6\6\2") - buf.write("\2GJ\7\30\2\2HI\7\37\2\2IK\6\6\3\2JH\3\2\2\2JK\3\2\2\2") - buf.write("KO\3\2\2\2LM\7\30\2\2MN\7\37\2\2NP\6\6\4\2OL\3\2\2\2O") - buf.write("P\3\2\2\2PR\3\2\2\2QG\3\2\2\2QR\3\2\2\2RZ\3\2\2\2SZ\7") - buf.write("\7\2\2TU\7\34\2\2UV\7\32\2\2VW\5\f\7\2WX\7\33\2\2XZ\3") - buf.write("\2\2\2YD\3\2\2\2YE\3\2\2\2YS\3\2\2\2YT\3\2\2\2Z\13\3\2") - buf.write("\2\2[\\\5\16\b\2\\\r\3\2\2\2]`\5\20\t\2^_\7\b\2\2_a\5") - buf.write("\16\b\2`^\3\2\2\2`a\3\2\2\2a\17\3\2\2\2be\5\22\n\2cd\7") - buf.write("\20\2\2df\5\20\t\2ec\3\2\2\2ef\3\2\2\2f\21\3\2\2\2gh\7") - buf.write("\17\2\2h{\5\22\n\2ij\7\32\2\2jk\5\f\7\2kl\7\33\2\2l{\3") - buf.write("\2\2\2mo\t\3\2\2np\5\4\3\2on\3\2\2\2op\3\2\2\2ps\3\2\2") - buf.write("\2qr\t\4\2\2rt\5\34\17\2sq\3\2\2\2st\3\2\2\2t{\3\2\2\2") - buf.write("ux\5\34\17\2vw\t\4\2\2wy\5\34\17\2xv\3\2\2\2xy\3\2\2\2") - buf.write("y{\3\2\2\2zg\3\2\2\2zi\3\2\2\2zm\3\2\2\2zu\3\2\2\2{\23") - buf.write("\3\2\2\2|}\5\34\17\2}\25\3\2\2\2~\177\7\24\2\2\177\u0084") - buf.write("\5\30\r\2\u0080\u0081\7\31\2\2\u0081\u0083\5\30\r\2\u0082") - buf.write("\u0080\3\2\2\2\u0083\u0086\3\2\2\2\u0084\u0082\3\2\2\2") - buf.write("\u0084\u0085\3\2\2\2\u0085\u0087\3\2\2\2\u0086\u0084\3") - buf.write("\2\2\2\u0087\u0088\7\25\2\2\u0088\u008c\3\2\2\2\u0089") - buf.write("\u008a\7\24\2\2\u008a\u008c\7\25\2\2\u008b~\3\2\2\2\u008b") - buf.write("\u0089\3\2\2\2\u008c\27\3\2\2\2\u008d\u008e\7\36\2\2\u008e") - buf.write("\u008f\7\30\2\2\u008f\u0090\5\34\17\2\u0090\31\3\2\2\2") - buf.write("\u0091\u0092\7\26\2\2\u0092\u0097\5\34\17\2\u0093\u0094") - buf.write("\7\31\2\2\u0094\u0096\5\34\17\2\u0095\u0093\3\2\2\2\u0096") - buf.write("\u0099\3\2\2\2\u0097\u0095\3\2\2\2\u0097\u0098\3\2\2\2") - buf.write("\u0098\u009a\3\2\2\2\u0099\u0097\3\2\2\2\u009a\u009b\7") - buf.write("\27\2\2\u009b\u009f\3\2\2\2\u009c\u009d\7\26\2\2\u009d") - buf.write("\u009f\7\27\2\2\u009e\u0091\3\2\2\2\u009e\u009c\3\2\2") - buf.write("\2\u009f\33\3\2\2\2\u00a0\u00a8\7\36\2\2\u00a1\u00a8\7") - buf.write("\37\2\2\u00a2\u00a8\5\26\f\2\u00a3\u00a8\5\32\16\2\u00a4") - buf.write("\u00a8\7\21\2\2\u00a5\u00a8\7\22\2\2\u00a6\u00a8\7\23") - buf.write("\2\2\u00a7\u00a0\3\2\2\2\u00a7\u00a1\3\2\2\2\u00a7\u00a2") - buf.write("\3\2\2\2\u00a7\u00a3\3\2\2\2\u00a7\u00a4\3\2\2\2\u00a7") - buf.write("\u00a5\3\2\2\2\u00a7\u00a6\3\2\2\2\u00a8\35\3\2\2\2\30") - buf.write(" \'*/\63\65=JOQY`eosxz\u0084\u008b\u0097\u009e\u00a7") + buf.write("\n\5\nt\n\n\5\nv\n\n\3\13\3\13\3\f\3\f\3\f\3\f\7\f~\n") + buf.write("\f\f\f\16\f\u0081\13\f\3\f\3\f\3\f\3\f\5\f\u0087\n\f\3") + buf.write("\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\7\16\u0091\n\16\f\16") + buf.write("\16\16\u0094\13\16\3\16\3\16\3\16\3\16\5\16\u009a\n\16") + buf.write("\3\17\3\17\3\17\3\17\3\17\3\17\3\17\5\17\u00a3\n\17\3") + buf.write("\17\2\2\20\2\4\6\b\n\f\16\20\22\24\26\30\32\34\2\5\4\2") + buf.write("\7\7\35\35\4\2\3\3\5\5\3\2\t\16\2\u00b4\2\36\3\2\2\2\4") + buf.write("\65\3\2\2\2\6\67\3\2\2\2\bB\3\2\2\2\nY\3\2\2\2\f[\3\2") + buf.write("\2\2\16]\3\2\2\2\20b\3\2\2\2\22u\3\2\2\2\24w\3\2\2\2\26") + buf.write("\u0086\3\2\2\2\30\u0088\3\2\2\2\32\u0099\3\2\2\2\34\u00a2") + buf.write("\3\2\2\2\36 \7\5\2\2\37!\5\4\3\2 \37\3\2\2\2 !\3\2\2\2") + buf.write("!\"\3\2\2\2\"#\7\2\2\3#\3\3\2\2\2$\'\7\4\2\2%(\5\b\5\2") + buf.write("&(\5\6\4\2\'%\3\2\2\2\'&\3\2\2\2(*\3\2\2\2)+\5\4\3\2*") + buf.write(")\3\2\2\2*+\3\2\2\2+\66\3\2\2\2,-\7\6\2\2-/\5\b\5\2.\60") + buf.write("\5\4\3\2/.\3\2\2\2/\60\3\2\2\2\60\66\3\2\2\2\61\63\5\6") + buf.write("\4\2\62\64\5\4\3\2\63\62\3\2\2\2\63\64\3\2\2\2\64\66\3") + buf.write("\2\2\2\65$\3\2\2\2\65,\3\2\2\2\65\61\3\2\2\2\66\5\3\2") + buf.write("\2\2\678\7\26\2\28=\5\n\6\29:\7\31\2\2:<\5\n\6\2;9\3\2") + buf.write("\2\2\3\2\2\2>@\3\2\2\2?=\3\2\2\2") + buf.write("@A\7\27\2\2A\7\3\2\2\2BC\t\2\2\2C\t\3\2\2\2DZ\7\36\2\2") + buf.write("EF\7\37\2\2FQ\6\6\2\2GJ\7\30\2\2HI\7\37\2\2IK\6\6\3\2") + buf.write("JH\3\2\2\2JK\3\2\2\2KO\3\2\2\2LM\7\30\2\2MN\7\37\2\2N") + buf.write("P\6\6\4\2OL\3\2\2\2OP\3\2\2\2PR\3\2\2\2QG\3\2\2\2QR\3") + buf.write("\2\2\2RZ\3\2\2\2SZ\7\7\2\2TU\7\34\2\2UV\7\32\2\2VW\5\f") + buf.write("\7\2WX\7\33\2\2XZ\3\2\2\2YD\3\2\2\2YE\3\2\2\2YS\3\2\2") + buf.write("\2YT\3\2\2\2Z\13\3\2\2\2[\\\5\16\b\2\\\r\3\2\2\2]`\5\20") + buf.write("\t\2^_\7\b\2\2_a\5\16\b\2`^\3\2\2\2`a\3\2\2\2a\17\3\2") + buf.write("\2\2be\5\22\n\2cd\7\20\2\2df\5\20\t\2ec\3\2\2\2ef\3\2") + buf.write("\2\2f\21\3\2\2\2gh\7\17\2\2hv\5\22\n\2ij\7\32\2\2jk\5") + buf.write("\f\7\2kl\7\33\2\2lv\3\2\2\2mo\t\3\2\2np\5\4\3\2on\3\2") + buf.write("\2\2op\3\2\2\2ps\3\2\2\2qr\t\4\2\2rt\5\34\17\2sq\3\2\2") + buf.write("\2st\3\2\2\2tv\3\2\2\2ug\3\2\2\2ui\3\2\2\2um\3\2\2\2v") + buf.write("\23\3\2\2\2wx\5\34\17\2x\25\3\2\2\2yz\7\24\2\2z\177\5") + buf.write("\30\r\2{|\7\31\2\2|~\5\30\r\2}{\3\2\2\2~\u0081\3\2\2\2") + buf.write("\177}\3\2\2\2\177\u0080\3\2\2\2\u0080\u0082\3\2\2\2\u0081") + buf.write("\177\3\2\2\2\u0082\u0083\7\25\2\2\u0083\u0087\3\2\2\2") + buf.write("\u0084\u0085\7\24\2\2\u0085\u0087\7\25\2\2\u0086y\3\2") + buf.write("\2\2\u0086\u0084\3\2\2\2\u0087\27\3\2\2\2\u0088\u0089") + buf.write("\7\36\2\2\u0089\u008a\7\30\2\2\u008a\u008b\5\34\17\2\u008b") + buf.write("\31\3\2\2\2\u008c\u008d\7\26\2\2\u008d\u0092\5\34\17\2") + buf.write("\u008e\u008f\7\31\2\2\u008f\u0091\5\34\17\2\u0090\u008e") + buf.write("\3\2\2\2\u0091\u0094\3\2\2\2\u0092\u0090\3\2\2\2\u0092") + buf.write("\u0093\3\2\2\2\u0093\u0095\3\2\2\2\u0094\u0092\3\2\2\2") + buf.write("\u0095\u0096\7\27\2\2\u0096\u009a\3\2\2\2\u0097\u0098") + buf.write("\7\26\2\2\u0098\u009a\7\27\2\2\u0099\u008c\3\2\2\2\u0099") + buf.write("\u0097\3\2\2\2\u009a\33\3\2\2\2\u009b\u00a3\7\36\2\2\u009c") + buf.write("\u00a3\7\37\2\2\u009d\u00a3\5\26\f\2\u009e\u00a3\5\32") + buf.write("\16\2\u009f\u00a3\7\21\2\2\u00a0\u00a3\7\22\2\2\u00a1") + buf.write("\u00a3\7\23\2\2\u00a2\u009b\3\2\2\2\u00a2\u009c\3\2\2") + buf.write("\2\u00a2\u009d\3\2\2\2\u00a2\u009e\3\2\2\2\u00a2\u009f") + buf.write("\3\2\2\2\u00a2\u00a0\3\2\2\2\u00a2\u00a1\3\2\2\2\u00a3") + buf.write("\35\3\2\2\2\27 \'*/\63\65=JOQY`eosu\177\u0086\u0092\u0099") + buf.write("\u00a2") return buf.getvalue() @@ -770,11 +768,8 @@ def subscript(self): return self.getTypedRuleContext(JSONPathParser.SubscriptContext,0) - def value(self, i:int=None): - if i is None: - return self.getTypedRuleContexts(JSONPathParser.ValueContext) - else: - return self.getTypedRuleContext(JSONPathParser.ValueContext,i) + def value(self): + return self.getTypedRuleContext(JSONPathParser.ValueContext,0) def EQ(self): @@ -815,7 +810,7 @@ def notExpression(self): self.enterRule(localctx, 16, self.RULE_notExpression) self._la = 0 # Token type try: - self.state = 120 + self.state = 115 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.NOT]: @@ -866,26 +861,6 @@ def notExpression(self): self.value() - pass - elif token in [JSONPathParser.TRUE, JSONPathParser.FALSE, JSONPathParser.NULL, JSONPathParser.BRACE_LEFT, JSONPathParser.BRACKET_LEFT, JSONPathParser.STRING, JSONPathParser.NUMBER]: - self.enterOuterAlt(localctx, 4) - self.state = 115 - self.value() - self.state = 118 - self._errHandler.sync(self) - _la = self._input.LA(1) - if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.EQ) | (1 << JSONPathParser.GE) | (1 << JSONPathParser.GT) | (1 << JSONPathParser.LE) | (1 << JSONPathParser.LT) | (1 << JSONPathParser.NE))) != 0): - self.state = 116 - _la = self._input.LA(1) - if not((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.EQ) | (1 << JSONPathParser.GE) | (1 << JSONPathParser.GT) | (1 << JSONPathParser.LE) | (1 << JSONPathParser.LT) | (1 << JSONPathParser.NE))) != 0)): - self._errHandler.recoverInline(self) - else: - self._errHandler.reportMatch(self) - self.consume() - self.state = 117 - self.value() - - pass else: raise NoViableAltException(self) @@ -928,7 +903,7 @@ def json(self): self.enterRule(localctx, 18, self.RULE_json) try: self.enterOuterAlt(localctx, 1) - self.state = 122 + self.state = 117 self.value() except RecognitionException as re: localctx.exception = re @@ -983,36 +958,36 @@ def obj(self): self.enterRule(localctx, 20, self.RULE_obj) self._la = 0 # Token type try: - self.state = 137 + self.state = 132 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,18,self._ctx) + la_ = self._interp.adaptivePredict(self._input,17,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 124 + self.state = 119 self.match(JSONPathParser.BRACE_LEFT) - self.state = 125 + self.state = 120 self.pair() - self.state = 130 + self.state = 125 self._errHandler.sync(self) _la = self._input.LA(1) while _la==JSONPathParser.COMMA: - self.state = 126 + self.state = 121 self.match(JSONPathParser.COMMA) - self.state = 127 + self.state = 122 self.pair() - self.state = 132 + self.state = 127 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 133 + self.state = 128 self.match(JSONPathParser.BRACE_RIGHT) pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 135 + self.state = 130 self.match(JSONPathParser.BRACE_LEFT) - self.state = 136 + self.state = 131 self.match(JSONPathParser.BRACE_RIGHT) pass @@ -1061,11 +1036,11 @@ def pair(self): self.enterRule(localctx, 22, self.RULE_pair) try: self.enterOuterAlt(localctx, 1) - self.state = 139 + self.state = 134 self.match(JSONPathParser.STRING) - self.state = 140 + self.state = 135 self.match(JSONPathParser.COLON) - self.state = 141 + self.state = 136 self.value() except RecognitionException as re: localctx.exception = re @@ -1120,36 +1095,36 @@ def array(self): self.enterRule(localctx, 24, self.RULE_array) self._la = 0 # Token type try: - self.state = 156 + self.state = 151 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,20,self._ctx) + la_ = self._interp.adaptivePredict(self._input,19,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 143 + self.state = 138 self.match(JSONPathParser.BRACKET_LEFT) - self.state = 144 + self.state = 139 self.value() - self.state = 149 + self.state = 144 self._errHandler.sync(self) _la = self._input.LA(1) while _la==JSONPathParser.COMMA: - self.state = 145 + self.state = 140 self.match(JSONPathParser.COMMA) - self.state = 146 + self.state = 141 self.value() - self.state = 151 + self.state = 146 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 152 + self.state = 147 self.match(JSONPathParser.BRACKET_RIGHT) pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 154 + self.state = 149 self.match(JSONPathParser.BRACKET_LEFT) - self.state = 155 + self.state = 150 self.match(JSONPathParser.BRACKET_RIGHT) pass @@ -1210,42 +1185,42 @@ def value(self): localctx = JSONPathParser.ValueContext(self, self._ctx, self.state) self.enterRule(localctx, 26, self.RULE_value) try: - self.state = 165 + self.state = 160 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.STRING]: self.enterOuterAlt(localctx, 1) - self.state = 158 + self.state = 153 self.match(JSONPathParser.STRING) pass elif token in [JSONPathParser.NUMBER]: self.enterOuterAlt(localctx, 2) - self.state = 159 + self.state = 154 self.match(JSONPathParser.NUMBER) pass elif token in [JSONPathParser.BRACE_LEFT]: self.enterOuterAlt(localctx, 3) - self.state = 160 + self.state = 155 self.obj() pass elif token in [JSONPathParser.BRACKET_LEFT]: self.enterOuterAlt(localctx, 4) - self.state = 161 + self.state = 156 self.array() pass elif token in [JSONPathParser.TRUE]: self.enterOuterAlt(localctx, 5) - self.state = 162 + self.state = 157 self.match(JSONPathParser.TRUE) pass elif token in [JSONPathParser.FALSE]: self.enterOuterAlt(localctx, 6) - self.state = 163 + self.state = 158 self.match(JSONPathParser.FALSE) pass elif token in [JSONPathParser.NULL]: self.enterOuterAlt(localctx, 7) - self.state = 164 + self.state = 159 self.match(JSONPathParser.NULL) pass else: From f50eb5256cc6610f0ec182ed4c2faf0a26ad30ee Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Tue, 18 Sep 2018 09:57:23 -0700 Subject: [PATCH 10/23] Fix bookstore tests In previous implementations of JSONPath, the current value is implicitly cast to array or object if the next node is an array index subscript or object index subscript. This introduces ambiguity: "is the index subscript referring to the current value, or a child value of the current value?" The solution is to use the wildcard "*" to refer to the child values explicitly. --- jsonpath2/test/bookstore_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsonpath2/test/bookstore_test.py b/jsonpath2/test/bookstore_test.py index 0b31515..4925606 100644 --- a/jsonpath2/test/bookstore_test.py +++ b/jsonpath2/test/bookstore_test.py @@ -116,7 +116,7 @@ def test_bookstore_examples_7(self): def test_bookstore_examples_8(self): """Test the bookstore examples.""" - expr = Path.parse_str('$..book[?(@.isbn)]') + expr = Path.parse_str('$..book[*][?(@.isbn)]') matches = [x.current_value for x in expr.match(self.root_value)] self.assertEqual(matches[0]['category'], 'fiction') self.assertEqual(matches[0]['author'], 'Herman Melville') @@ -131,7 +131,7 @@ def test_bookstore_examples_8(self): def test_bookstore_examples_9(self): """Test the bookstore examples.""" - expr = Path.parse_str('$..book[?(@.price<10)]') + expr = Path.parse_str('$..book[*][?(@.price<10)]') matches = [x.current_value for x in expr.match(self.root_value)] self.assertEqual(matches[0]['category'], 'reference') self.assertEqual(matches[0]['author'], 'Nigel Rees') From 29bb9976d865154354f181320e46b165082395dc Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 18 Sep 2018 11:31:49 -0700 Subject: [PATCH 11/23] add more testing to increase coverage Signed-off-by: David Brown --- .travis.yml | 2 +- jsonpath2/test/bookstore_test.py | 58 +++++++++++++++++++++++++++---- jsonpath2/test/expression_test.py | 25 +++++++++++++ 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 457e4b4..42716fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ stages: install: pip install -r requirements-dev.txt script: -- coverage run --include='jsonpath2/*' -m pytest -v +- coverage run --include='jsonpath2/*' --omit='jsonpath2/parser/JSONPath*' -m pytest -v - coverage report -m --fail-under 100 - pip install . - python setup.py bdist_wheel diff --git a/jsonpath2/test/bookstore_test.py b/jsonpath2/test/bookstore_test.py index 4925606..726ed5c 100644 --- a/jsonpath2/test/bookstore_test.py +++ b/jsonpath2/test/bookstore_test.py @@ -54,6 +54,7 @@ def setUp(self): def test_bookstore_examples_1(self): """Test the bookstore examples.""" expr = Path.parse_str('$.store.book[*].author') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] for auth in ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']: self.assertTrue(auth in matches) @@ -61,6 +62,7 @@ def test_bookstore_examples_1(self): def test_bookstore_examples_2(self): """Test the bookstore examples.""" expr = Path.parse_str('$..author') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] for auth in ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']: self.assertTrue(auth in matches) @@ -68,6 +70,7 @@ def test_bookstore_examples_2(self): def test_bookstore_examples_3(self): """Test the bookstore examples.""" expr = Path.parse_str('$.store.*') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] self.assertTrue(isinstance(matches[0], list)) self.assertTrue(isinstance(matches[1], dict)) @@ -77,6 +80,7 @@ def test_bookstore_examples_3(self): def test_bookstore_examples_4(self): """Test the bookstore examples.""" expr = Path.parse_str('$.store..price') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] for price in [8.95, 12.99, 8.99, 22.99, 19.95]: self.assertTrue(price in matches) @@ -84,6 +88,7 @@ def test_bookstore_examples_4(self): def test_bookstore_examples_5(self): """Test the bookstore examples.""" expr = Path.parse_str('$..book[2]') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] self.assertEqual(matches[0]['category'], 'fiction') self.assertEqual(matches[0]['author'], 'Herman Melville') @@ -94,6 +99,7 @@ def test_bookstore_examples_5(self): def test_bookstore_examples_6(self): """Test the bookstore examples.""" expr = Path.parse_str('$..book[-1:]') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] self.assertEqual(matches[0]['category'], 'fiction') self.assertEqual(matches[0]['author'], 'J. R. R. Tolkien') @@ -104,6 +110,7 @@ def test_bookstore_examples_6(self): def test_bookstore_examples_7(self): """Test the bookstore examples.""" expr = Path.parse_str('$..book[0,1]') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] self.assertEqual(matches[0]['category'], 'reference') self.assertEqual(matches[0]['author'], 'Nigel Rees') @@ -117,6 +124,7 @@ def test_bookstore_examples_7(self): def test_bookstore_examples_8(self): """Test the bookstore examples.""" expr = Path.parse_str('$..book[*][?(@.isbn)]') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] self.assertEqual(matches[0]['category'], 'fiction') self.assertEqual(matches[0]['author'], 'Herman Melville') @@ -131,14 +139,52 @@ def test_bookstore_examples_8(self): def test_bookstore_examples_9(self): """Test the bookstore examples.""" - expr = Path.parse_str('$..book[*][?(@.price<10)]') + for path in ['$..book[*][?(@.price<10)]', '$..book[*][?(@.price<=10)]']: + expr = Path.parse_str(path) + self.assertEqual(Path.parse_str(str(expr)), expr) + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'reference') + self.assertEqual(matches[0]['author'], 'Nigel Rees') + self.assertEqual(matches[0]['title'], 'Sayings of the Century') + self.assertEqual(matches[0]['price'], 8.95) + self.assertEqual(matches[1]['category'], 'fiction') + self.assertEqual(matches[1]['author'], 'Herman Melville') + self.assertEqual(matches[1]['title'], 'Moby Dick') + self.assertEqual(matches[1]['isbn'], '0-553-21311-3') + self.assertEqual(matches[1]['price'], 8.99) + + def test_bookstore_examples_10(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$..book[*][?(@.author = "Nigel Rees")]') + self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] self.assertEqual(matches[0]['category'], 'reference') self.assertEqual(matches[0]['author'], 'Nigel Rees') self.assertEqual(matches[0]['title'], 'Sayings of the Century') self.assertEqual(matches[0]['price'], 8.95) - self.assertEqual(matches[1]['category'], 'fiction') - self.assertEqual(matches[1]['author'], 'Herman Melville') - self.assertEqual(matches[1]['title'], 'Moby Dick') - self.assertEqual(matches[1]['isbn'], '0-553-21311-3') - self.assertEqual(matches[1]['price'], 8.99) + + def test_bookstore_examples_11(self): + """Test the bookstore examples.""" + expr = Path.parse_str('$..book[*][?(@.category != "fiction")]') + self.assertEqual(Path.parse_str(str(expr)), expr) + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'reference') + self.assertEqual(matches[0]['author'], 'Nigel Rees') + self.assertEqual(matches[0]['title'], 'Sayings of the Century') + self.assertEqual(matches[0]['price'], 8.95) + + def test_bookstore_examples_12(self): + """Test the bookstore examples.""" + for path in ['$..book[*][?(@.price>10)]', '$..book[*][?(@.price>=10)]']: + expr = Path.parse_str(path) + self.assertEqual(Path.parse_str(str(expr)), expr) + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'fiction') + self.assertEqual(matches[0]['author'], 'Evelyn Waugh') + self.assertEqual(matches[0]['title'], 'Sword of Honour') + self.assertEqual(matches[0]['price'], 12.99) + self.assertEqual(matches[1]['category'], 'fiction') + self.assertEqual(matches[1]['author'], 'J. R. R. Tolkien') + self.assertEqual(matches[1]['title'], 'The Lord of the Rings') + self.assertEqual(matches[1]['isbn'], '0-395-19395-8') + self.assertEqual(matches[1]['price'], 22.99) diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index 49f2ebd..41ecdbd 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- """Test the expression object.""" from unittest import TestCase +from jsonpath2.path import Path from jsonpath2.expressions.operator import OperatorExpression @@ -13,3 +14,27 @@ def test_expression(self): obj_a = OperatorExpression() obj_b = OperatorExpression() self.assertTrue(obj_a == obj_b) + + def test_failure_expressions(self): + """Test expressions that make no sense.""" + data = {"hello": "Hello, World!"} + for path in [ + '$[?(@.hello < "string")]', + '$[?(@.hello <= "string")]', + '$[?(@.hello > "string")]', + '$[?(@.hello >= "string")]' + ]: + expr = Path.parse_str(path) + self.assertFalse([ x for x in expr.match(data) ]) + + def test_unary_operator(self): + """Test the unary not in a path.""" + data = [ + { + "hello": "Hello, World!", + "bar": False + } + ] + expr = Path.parse_str('$[?(not @.bar)]') + self.assertEqual(Path.parse_str(str(expr)), expr) + self.assertTrue([ x for x in expr.match(data) ]) From c936f126a4286843a9214a7ba6a56b16d7458684 Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 18 Sep 2018 11:33:50 -0700 Subject: [PATCH 12/23] pre-commit fix Signed-off-by: David Brown --- jsonpath2/test/expression_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index 41ecdbd..048f64e 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -17,24 +17,24 @@ def test_expression(self): def test_failure_expressions(self): """Test expressions that make no sense.""" - data = {"hello": "Hello, World!"} + data = {'hello': 'Hello, World!'} for path in [ '$[?(@.hello < "string")]', '$[?(@.hello <= "string")]', '$[?(@.hello > "string")]', '$[?(@.hello >= "string")]' - ]: + ]: expr = Path.parse_str(path) - self.assertFalse([ x for x in expr.match(data) ]) + self.assertFalse([x for x in expr.match(data)]) def test_unary_operator(self): """Test the unary not in a path.""" data = [ { - "hello": "Hello, World!", - "bar": False + 'hello': 'Hello, World!', + 'bar': False } ] expr = Path.parse_str('$[?(not @.bar)]') self.assertEqual(Path.parse_str(str(expr)), expr) - self.assertTrue([ x for x in expr.match(data) ]) + self.assertTrue([x for x in expr.match(data)]) From df8f033d6cf894d790c4faf8dc04fef0e5b7ecad Mon Sep 17 00:00:00 2001 From: David Brown Date: Tue, 18 Sep 2018 14:20:37 -0700 Subject: [PATCH 13/23] more testing coverage --- jsonpath2/test/bookstore_test.py | 40 ++++++++++++++++--------------- jsonpath2/test/expression_test.py | 12 ++++++++++ 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/jsonpath2/test/bookstore_test.py b/jsonpath2/test/bookstore_test.py index 726ed5c..9a53f00 100644 --- a/jsonpath2/test/bookstore_test.py +++ b/jsonpath2/test/bookstore_test.py @@ -98,28 +98,30 @@ def test_bookstore_examples_5(self): def test_bookstore_examples_6(self): """Test the bookstore examples.""" - expr = Path.parse_str('$..book[-1:]') - self.assertEqual(Path.parse_str(str(expr)), expr) - matches = [x.current_value for x in expr.match(self.root_value)] - self.assertEqual(matches[0]['category'], 'fiction') - self.assertEqual(matches[0]['author'], 'J. R. R. Tolkien') - self.assertEqual(matches[0]['title'], 'The Lord of the Rings') - self.assertEqual(matches[0]['isbn'], '0-395-19395-8') - self.assertEqual(matches[0]['price'], 22.99) + for path in ['$..book[-1:]', '$..book[-1]', '$..book[3:4:1]']: + expr = Path.parse_str(path) + self.assertEqual(Path.parse_str(str(expr)), expr) + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'fiction') + self.assertEqual(matches[0]['author'], 'J. R. R. Tolkien') + self.assertEqual(matches[0]['title'], 'The Lord of the Rings') + self.assertEqual(matches[0]['isbn'], '0-395-19395-8') + self.assertEqual(matches[0]['price'], 22.99) def test_bookstore_examples_7(self): """Test the bookstore examples.""" - expr = Path.parse_str('$..book[0,1]') - self.assertEqual(Path.parse_str(str(expr)), expr) - matches = [x.current_value for x in expr.match(self.root_value)] - self.assertEqual(matches[0]['category'], 'reference') - self.assertEqual(matches[0]['author'], 'Nigel Rees') - self.assertEqual(matches[0]['title'], 'Sayings of the Century') - self.assertEqual(matches[0]['price'], 8.95) - self.assertEqual(matches[1]['category'], 'fiction') - self.assertEqual(matches[1]['author'], 'Evelyn Waugh') - self.assertEqual(matches[1]['title'], 'Sword of Honour') - self.assertEqual(matches[1]['price'], 12.99) + for path in ['$..book[0,1]', '$..book[0:2]', '$..book[0:2:1]']: + expr = Path.parse_str(path) + self.assertEqual(Path.parse_str(str(expr)), expr) + matches = [x.current_value for x in expr.match(self.root_value)] + self.assertEqual(matches[0]['category'], 'reference') + self.assertEqual(matches[0]['author'], 'Nigel Rees') + self.assertEqual(matches[0]['title'], 'Sayings of the Century') + self.assertEqual(matches[0]['price'], 8.95) + self.assertEqual(matches[1]['category'], 'fiction') + self.assertEqual(matches[1]['author'], 'Evelyn Waugh') + self.assertEqual(matches[1]['title'], 'Sword of Honour') + self.assertEqual(matches[1]['price'], 12.99) def test_bookstore_examples_8(self): """Test the bookstore examples.""" diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index 048f64e..d0211e7 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -38,3 +38,15 @@ def test_unary_operator(self): expr = Path.parse_str('$[?(not @.bar)]') self.assertEqual(Path.parse_str(str(expr)), expr) self.assertTrue([x for x in expr.match(data)]) + + def test_unary_boolean_operator(self): + """Test the unary not in a path.""" + data = [ + { + 'hello': 'Hello, World!', + 'bar': False + } + ] + expr = Path.parse_str('$[?(not (@.bar or (@.fiz > 2 and @.biz > 2)))]') + self.assertEqual(Path.parse_str(str(expr)), expr) + self.assertTrue([x for x in expr.match(data)]) From b838d41d03c4606cdfad83a8463af1ff9a0d4541 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Tue, 18 Sep 2018 14:37:42 -0700 Subject: [PATCH 14/23] Improve slice notation Aim for feature parity with equivalent in Python programming language --- jsonpath2/parser/JSONPath.g4 | 7 +- jsonpath2/parser/JSONPath.interp | 3 +- jsonpath2/parser/JSONPathListener.py | 9 + jsonpath2/parser/JSONPathParser.py | 475 +++++++++++++++------------ jsonpath2/parser/__init__.py | 31 +- jsonpath2/test/bookstore_test.py | 2 +- 6 files changed, 304 insertions(+), 223 deletions(-) diff --git a/jsonpath2/parser/JSONPath.g4 b/jsonpath2/parser/JSONPath.g4 index c059868..f422697 100644 --- a/jsonpath2/parser/JSONPath.g4 +++ b/jsonpath2/parser/JSONPath.g4 @@ -51,11 +51,16 @@ subscriptableBareword subscriptable : STRING - | NUMBER{self.tryCast(int)}? ( COLON ( NUMBER{self.tryCast(int)}? )? ( COLON NUMBER{self.tryCast(int)}? )? )? + | NUMBER{self.tryCast(int)}? sliceable? + | sliceable | WILDCARD_SUBSCRIPT | QUESTION PAREN_LEFT expression PAREN_RIGHT ; +sliceable + : COLON ( NUMBER{self.tryCast(int)}? )? ( COLON NUMBER{self.tryCast(int)}? )? + ; + expression : andExpression ; diff --git a/jsonpath2/parser/JSONPath.interp b/jsonpath2/parser/JSONPath.interp index 2fdafae..35da05e 100644 --- a/jsonpath2/parser/JSONPath.interp +++ b/jsonpath2/parser/JSONPath.interp @@ -70,6 +70,7 @@ subscript subscriptables subscriptableBareword subscriptable +sliceable expression andExpression orExpression @@ -82,4 +83,4 @@ value atn: -[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 32, 165, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 3, 2, 3, 2, 5, 2, 33, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 5, 3, 40, 10, 3, 3, 3, 5, 3, 43, 10, 3, 3, 3, 3, 3, 3, 3, 5, 3, 48, 10, 3, 3, 3, 3, 3, 5, 3, 52, 10, 3, 5, 3, 54, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 60, 10, 4, 12, 4, 14, 4, 63, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 75, 10, 6, 3, 6, 3, 6, 3, 6, 5, 6, 80, 10, 6, 5, 6, 82, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 90, 10, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 5, 8, 97, 10, 8, 3, 9, 3, 9, 3, 9, 5, 9, 102, 10, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 112, 10, 10, 3, 10, 3, 10, 5, 10, 116, 10, 10, 5, 10, 118, 10, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 126, 10, 12, 12, 12, 14, 12, 129, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 135, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 145, 10, 14, 12, 14, 14, 14, 148, 11, 14, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 154, 10, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 163, 10, 15, 3, 15, 2, 2, 16, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 2, 5, 4, 2, 7, 7, 29, 29, 4, 2, 3, 3, 5, 5, 3, 2, 9, 14, 2, 180, 2, 30, 3, 2, 2, 2, 4, 53, 3, 2, 2, 2, 6, 55, 3, 2, 2, 2, 8, 66, 3, 2, 2, 2, 10, 89, 3, 2, 2, 2, 12, 91, 3, 2, 2, 2, 14, 93, 3, 2, 2, 2, 16, 98, 3, 2, 2, 2, 18, 117, 3, 2, 2, 2, 20, 119, 3, 2, 2, 2, 22, 134, 3, 2, 2, 2, 24, 136, 3, 2, 2, 2, 26, 153, 3, 2, 2, 2, 28, 162, 3, 2, 2, 2, 30, 32, 7, 5, 2, 2, 31, 33, 5, 4, 3, 2, 32, 31, 3, 2, 2, 2, 32, 33, 3, 2, 2, 2, 33, 34, 3, 2, 2, 2, 34, 35, 7, 2, 2, 3, 35, 3, 3, 2, 2, 2, 36, 39, 7, 4, 2, 2, 37, 40, 5, 8, 5, 2, 38, 40, 5, 6, 4, 2, 39, 37, 3, 2, 2, 2, 39, 38, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 43, 5, 4, 3, 2, 42, 41, 3, 2, 2, 2, 42, 43, 3, 2, 2, 2, 43, 54, 3, 2, 2, 2, 44, 45, 7, 6, 2, 2, 45, 47, 5, 8, 5, 2, 46, 48, 5, 4, 3, 2, 47, 46, 3, 2, 2, 2, 47, 48, 3, 2, 2, 2, 48, 54, 3, 2, 2, 2, 49, 51, 5, 6, 4, 2, 50, 52, 5, 4, 3, 2, 51, 50, 3, 2, 2, 2, 51, 52, 3, 2, 2, 2, 52, 54, 3, 2, 2, 2, 53, 36, 3, 2, 2, 2, 53, 44, 3, 2, 2, 2, 53, 49, 3, 2, 2, 2, 54, 5, 3, 2, 2, 2, 55, 56, 7, 22, 2, 2, 56, 61, 5, 10, 6, 2, 57, 58, 7, 25, 2, 2, 58, 60, 5, 10, 6, 2, 59, 57, 3, 2, 2, 2, 60, 63, 3, 2, 2, 2, 61, 59, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 64, 3, 2, 2, 2, 63, 61, 3, 2, 2, 2, 64, 65, 7, 23, 2, 2, 65, 7, 3, 2, 2, 2, 66, 67, 9, 2, 2, 2, 67, 9, 3, 2, 2, 2, 68, 90, 7, 30, 2, 2, 69, 70, 7, 31, 2, 2, 70, 81, 6, 6, 2, 2, 71, 74, 7, 24, 2, 2, 72, 73, 7, 31, 2, 2, 73, 75, 6, 6, 3, 2, 74, 72, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 79, 3, 2, 2, 2, 76, 77, 7, 24, 2, 2, 77, 78, 7, 31, 2, 2, 78, 80, 6, 6, 4, 2, 79, 76, 3, 2, 2, 2, 79, 80, 3, 2, 2, 2, 80, 82, 3, 2, 2, 2, 81, 71, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 90, 3, 2, 2, 2, 83, 90, 7, 7, 2, 2, 84, 85, 7, 28, 2, 2, 85, 86, 7, 26, 2, 2, 86, 87, 5, 12, 7, 2, 87, 88, 7, 27, 2, 2, 88, 90, 3, 2, 2, 2, 89, 68, 3, 2, 2, 2, 89, 69, 3, 2, 2, 2, 89, 83, 3, 2, 2, 2, 89, 84, 3, 2, 2, 2, 90, 11, 3, 2, 2, 2, 91, 92, 5, 14, 8, 2, 92, 13, 3, 2, 2, 2, 93, 96, 5, 16, 9, 2, 94, 95, 7, 8, 2, 2, 95, 97, 5, 14, 8, 2, 96, 94, 3, 2, 2, 2, 96, 97, 3, 2, 2, 2, 97, 15, 3, 2, 2, 2, 98, 101, 5, 18, 10, 2, 99, 100, 7, 16, 2, 2, 100, 102, 5, 16, 9, 2, 101, 99, 3, 2, 2, 2, 101, 102, 3, 2, 2, 2, 102, 17, 3, 2, 2, 2, 103, 104, 7, 15, 2, 2, 104, 118, 5, 18, 10, 2, 105, 106, 7, 26, 2, 2, 106, 107, 5, 12, 7, 2, 107, 108, 7, 27, 2, 2, 108, 118, 3, 2, 2, 2, 109, 111, 9, 3, 2, 2, 110, 112, 5, 4, 3, 2, 111, 110, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 115, 3, 2, 2, 2, 113, 114, 9, 4, 2, 2, 114, 116, 5, 28, 15, 2, 115, 113, 3, 2, 2, 2, 115, 116, 3, 2, 2, 2, 116, 118, 3, 2, 2, 2, 117, 103, 3, 2, 2, 2, 117, 105, 3, 2, 2, 2, 117, 109, 3, 2, 2, 2, 118, 19, 3, 2, 2, 2, 119, 120, 5, 28, 15, 2, 120, 21, 3, 2, 2, 2, 121, 122, 7, 20, 2, 2, 122, 127, 5, 24, 13, 2, 123, 124, 7, 25, 2, 2, 124, 126, 5, 24, 13, 2, 125, 123, 3, 2, 2, 2, 126, 129, 3, 2, 2, 2, 127, 125, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 130, 3, 2, 2, 2, 129, 127, 3, 2, 2, 2, 130, 131, 7, 21, 2, 2, 131, 135, 3, 2, 2, 2, 132, 133, 7, 20, 2, 2, 133, 135, 7, 21, 2, 2, 134, 121, 3, 2, 2, 2, 134, 132, 3, 2, 2, 2, 135, 23, 3, 2, 2, 2, 136, 137, 7, 30, 2, 2, 137, 138, 7, 24, 2, 2, 138, 139, 5, 28, 15, 2, 139, 25, 3, 2, 2, 2, 140, 141, 7, 22, 2, 2, 141, 146, 5, 28, 15, 2, 142, 143, 7, 25, 2, 2, 143, 145, 5, 28, 15, 2, 144, 142, 3, 2, 2, 2, 145, 148, 3, 2, 2, 2, 146, 144, 3, 2, 2, 2, 146, 147, 3, 2, 2, 2, 147, 149, 3, 2, 2, 2, 148, 146, 3, 2, 2, 2, 149, 150, 7, 23, 2, 2, 150, 154, 3, 2, 2, 2, 151, 152, 7, 22, 2, 2, 152, 154, 7, 23, 2, 2, 153, 140, 3, 2, 2, 2, 153, 151, 3, 2, 2, 2, 154, 27, 3, 2, 2, 2, 155, 163, 7, 30, 2, 2, 156, 163, 7, 31, 2, 2, 157, 163, 5, 22, 12, 2, 158, 163, 5, 26, 14, 2, 159, 163, 7, 17, 2, 2, 160, 163, 7, 18, 2, 2, 161, 163, 7, 19, 2, 2, 162, 155, 3, 2, 2, 2, 162, 156, 3, 2, 2, 2, 162, 157, 3, 2, 2, 2, 162, 158, 3, 2, 2, 2, 162, 159, 3, 2, 2, 2, 162, 160, 3, 2, 2, 2, 162, 161, 3, 2, 2, 2, 163, 29, 3, 2, 2, 2, 23, 32, 39, 42, 47, 51, 53, 61, 74, 79, 81, 89, 96, 101, 111, 115, 117, 127, 134, 146, 153, 162] \ No newline at end of file +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 32, 169, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 3, 2, 3, 2, 5, 2, 35, 10, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 5, 3, 42, 10, 3, 3, 3, 5, 3, 45, 10, 3, 3, 3, 3, 3, 3, 3, 5, 3, 50, 10, 3, 3, 3, 3, 3, 5, 3, 54, 10, 3, 5, 3, 56, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 7, 4, 62, 10, 4, 12, 4, 14, 4, 65, 11, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 75, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 84, 10, 6, 3, 7, 3, 7, 3, 7, 5, 7, 89, 10, 7, 3, 7, 3, 7, 3, 7, 5, 7, 94, 10, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 5, 9, 101, 10, 9, 3, 10, 3, 10, 3, 10, 5, 10, 106, 10, 10, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 5, 11, 116, 10, 11, 3, 11, 3, 11, 5, 11, 120, 10, 11, 5, 11, 122, 10, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 13, 7, 13, 130, 10, 13, 12, 13, 14, 13, 133, 11, 13, 3, 13, 3, 13, 3, 13, 3, 13, 5, 13, 139, 10, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 15, 7, 15, 149, 10, 15, 12, 15, 14, 15, 152, 11, 15, 3, 15, 3, 15, 3, 15, 3, 15, 5, 15, 158, 10, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 5, 16, 167, 10, 16, 3, 16, 2, 2, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 2, 5, 4, 2, 7, 7, 29, 29, 4, 2, 3, 3, 5, 5, 3, 2, 9, 14, 2, 184, 2, 32, 3, 2, 2, 2, 4, 55, 3, 2, 2, 2, 6, 57, 3, 2, 2, 2, 8, 68, 3, 2, 2, 2, 10, 83, 3, 2, 2, 2, 12, 85, 3, 2, 2, 2, 14, 95, 3, 2, 2, 2, 16, 97, 3, 2, 2, 2, 18, 102, 3, 2, 2, 2, 20, 121, 3, 2, 2, 2, 22, 123, 3, 2, 2, 2, 24, 138, 3, 2, 2, 2, 26, 140, 3, 2, 2, 2, 28, 157, 3, 2, 2, 2, 30, 166, 3, 2, 2, 2, 32, 34, 7, 5, 2, 2, 33, 35, 5, 4, 3, 2, 34, 33, 3, 2, 2, 2, 34, 35, 3, 2, 2, 2, 35, 36, 3, 2, 2, 2, 36, 37, 7, 2, 2, 3, 37, 3, 3, 2, 2, 2, 38, 41, 7, 4, 2, 2, 39, 42, 5, 8, 5, 2, 40, 42, 5, 6, 4, 2, 41, 39, 3, 2, 2, 2, 41, 40, 3, 2, 2, 2, 42, 44, 3, 2, 2, 2, 43, 45, 5, 4, 3, 2, 44, 43, 3, 2, 2, 2, 44, 45, 3, 2, 2, 2, 45, 56, 3, 2, 2, 2, 46, 47, 7, 6, 2, 2, 47, 49, 5, 8, 5, 2, 48, 50, 5, 4, 3, 2, 49, 48, 3, 2, 2, 2, 49, 50, 3, 2, 2, 2, 50, 56, 3, 2, 2, 2, 51, 53, 5, 6, 4, 2, 52, 54, 5, 4, 3, 2, 53, 52, 3, 2, 2, 2, 53, 54, 3, 2, 2, 2, 54, 56, 3, 2, 2, 2, 55, 38, 3, 2, 2, 2, 55, 46, 3, 2, 2, 2, 55, 51, 3, 2, 2, 2, 56, 5, 3, 2, 2, 2, 57, 58, 7, 22, 2, 2, 58, 63, 5, 10, 6, 2, 59, 60, 7, 25, 2, 2, 60, 62, 5, 10, 6, 2, 61, 59, 3, 2, 2, 2, 62, 65, 3, 2, 2, 2, 63, 61, 3, 2, 2, 2, 63, 64, 3, 2, 2, 2, 64, 66, 3, 2, 2, 2, 65, 63, 3, 2, 2, 2, 66, 67, 7, 23, 2, 2, 67, 7, 3, 2, 2, 2, 68, 69, 9, 2, 2, 2, 69, 9, 3, 2, 2, 2, 70, 84, 7, 30, 2, 2, 71, 72, 7, 31, 2, 2, 72, 74, 6, 6, 2, 2, 73, 75, 5, 12, 7, 2, 74, 73, 3, 2, 2, 2, 74, 75, 3, 2, 2, 2, 75, 84, 3, 2, 2, 2, 76, 84, 5, 12, 7, 2, 77, 84, 7, 7, 2, 2, 78, 79, 7, 28, 2, 2, 79, 80, 7, 26, 2, 2, 80, 81, 5, 14, 8, 2, 81, 82, 7, 27, 2, 2, 82, 84, 3, 2, 2, 2, 83, 70, 3, 2, 2, 2, 83, 71, 3, 2, 2, 2, 83, 76, 3, 2, 2, 2, 83, 77, 3, 2, 2, 2, 83, 78, 3, 2, 2, 2, 84, 11, 3, 2, 2, 2, 85, 88, 7, 24, 2, 2, 86, 87, 7, 31, 2, 2, 87, 89, 6, 7, 3, 2, 88, 86, 3, 2, 2, 2, 88, 89, 3, 2, 2, 2, 89, 93, 3, 2, 2, 2, 90, 91, 7, 24, 2, 2, 91, 92, 7, 31, 2, 2, 92, 94, 6, 7, 4, 2, 93, 90, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 13, 3, 2, 2, 2, 95, 96, 5, 16, 9, 2, 96, 15, 3, 2, 2, 2, 97, 100, 5, 18, 10, 2, 98, 99, 7, 8, 2, 2, 99, 101, 5, 16, 9, 2, 100, 98, 3, 2, 2, 2, 100, 101, 3, 2, 2, 2, 101, 17, 3, 2, 2, 2, 102, 105, 5, 20, 11, 2, 103, 104, 7, 16, 2, 2, 104, 106, 5, 18, 10, 2, 105, 103, 3, 2, 2, 2, 105, 106, 3, 2, 2, 2, 106, 19, 3, 2, 2, 2, 107, 108, 7, 15, 2, 2, 108, 122, 5, 20, 11, 2, 109, 110, 7, 26, 2, 2, 110, 111, 5, 14, 8, 2, 111, 112, 7, 27, 2, 2, 112, 122, 3, 2, 2, 2, 113, 115, 9, 3, 2, 2, 114, 116, 5, 4, 3, 2, 115, 114, 3, 2, 2, 2, 115, 116, 3, 2, 2, 2, 116, 119, 3, 2, 2, 2, 117, 118, 9, 4, 2, 2, 118, 120, 5, 30, 16, 2, 119, 117, 3, 2, 2, 2, 119, 120, 3, 2, 2, 2, 120, 122, 3, 2, 2, 2, 121, 107, 3, 2, 2, 2, 121, 109, 3, 2, 2, 2, 121, 113, 3, 2, 2, 2, 122, 21, 3, 2, 2, 2, 123, 124, 5, 30, 16, 2, 124, 23, 3, 2, 2, 2, 125, 126, 7, 20, 2, 2, 126, 131, 5, 26, 14, 2, 127, 128, 7, 25, 2, 2, 128, 130, 5, 26, 14, 2, 129, 127, 3, 2, 2, 2, 130, 133, 3, 2, 2, 2, 131, 129, 3, 2, 2, 2, 131, 132, 3, 2, 2, 2, 132, 134, 3, 2, 2, 2, 133, 131, 3, 2, 2, 2, 134, 135, 7, 21, 2, 2, 135, 139, 3, 2, 2, 2, 136, 137, 7, 20, 2, 2, 137, 139, 7, 21, 2, 2, 138, 125, 3, 2, 2, 2, 138, 136, 3, 2, 2, 2, 139, 25, 3, 2, 2, 2, 140, 141, 7, 30, 2, 2, 141, 142, 7, 24, 2, 2, 142, 143, 5, 30, 16, 2, 143, 27, 3, 2, 2, 2, 144, 145, 7, 22, 2, 2, 145, 150, 5, 30, 16, 2, 146, 147, 7, 25, 2, 2, 147, 149, 5, 30, 16, 2, 148, 146, 3, 2, 2, 2, 149, 152, 3, 2, 2, 2, 150, 148, 3, 2, 2, 2, 150, 151, 3, 2, 2, 2, 151, 153, 3, 2, 2, 2, 152, 150, 3, 2, 2, 2, 153, 154, 7, 23, 2, 2, 154, 158, 3, 2, 2, 2, 155, 156, 7, 22, 2, 2, 156, 158, 7, 23, 2, 2, 157, 144, 3, 2, 2, 2, 157, 155, 3, 2, 2, 2, 158, 29, 3, 2, 2, 2, 159, 167, 7, 30, 2, 2, 160, 167, 7, 31, 2, 2, 161, 167, 5, 24, 13, 2, 162, 167, 5, 28, 15, 2, 163, 167, 7, 17, 2, 2, 164, 167, 7, 18, 2, 2, 165, 167, 7, 19, 2, 2, 166, 159, 3, 2, 2, 2, 166, 160, 3, 2, 2, 2, 166, 161, 3, 2, 2, 2, 166, 162, 3, 2, 2, 2, 166, 163, 3, 2, 2, 2, 166, 164, 3, 2, 2, 2, 166, 165, 3, 2, 2, 2, 167, 31, 3, 2, 2, 2, 23, 34, 41, 44, 49, 53, 55, 63, 74, 83, 88, 93, 100, 105, 115, 119, 121, 131, 138, 150, 157, 166] \ No newline at end of file diff --git a/jsonpath2/parser/JSONPathListener.py b/jsonpath2/parser/JSONPathListener.py index 20bdfee..1795d21 100644 --- a/jsonpath2/parser/JSONPathListener.py +++ b/jsonpath2/parser/JSONPathListener.py @@ -53,6 +53,15 @@ def exitSubscriptable(self, ctx:JSONPathParser.SubscriptableContext): pass + # Enter a parse tree produced by JSONPathParser#sliceable. + def enterSliceable(self, ctx:JSONPathParser.SliceableContext): + pass + + # Exit a parse tree produced by JSONPathParser#sliceable. + def exitSliceable(self, ctx:JSONPathParser.SliceableContext): + pass + + # Enter a parse tree produced by JSONPathParser#expression. def enterExpression(self, ctx:JSONPathParser.ExpressionContext): pass diff --git a/jsonpath2/parser/JSONPathParser.py b/jsonpath2/parser/JSONPathParser.py index e6ac0d3..e8c9163 100644 --- a/jsonpath2/parser/JSONPathParser.py +++ b/jsonpath2/parser/JSONPathParser.py @@ -8,68 +8,70 @@ def serializedATN(): with StringIO() as buf: buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3 ") - buf.write("\u00a5\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") + buf.write("\u00a9\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") buf.write("\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16") - buf.write("\t\16\4\17\t\17\3\2\3\2\5\2!\n\2\3\2\3\2\3\3\3\3\3\3\5") - buf.write("\3(\n\3\3\3\5\3+\n\3\3\3\3\3\3\3\5\3\60\n\3\3\3\3\3\5") - buf.write("\3\64\n\3\5\3\66\n\3\3\4\3\4\3\4\3\4\7\4<\n\4\f\4\16\4") - buf.write("?\13\4\3\4\3\4\3\5\3\5\3\6\3\6\3\6\3\6\3\6\3\6\5\6K\n") - buf.write("\6\3\6\3\6\3\6\5\6P\n\6\5\6R\n\6\3\6\3\6\3\6\3\6\3\6\3") - buf.write("\6\5\6Z\n\6\3\7\3\7\3\b\3\b\3\b\5\ba\n\b\3\t\3\t\3\t\5") - buf.write("\tf\n\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\5\np\n\n\3\n\3") - buf.write("\n\5\nt\n\n\5\nv\n\n\3\13\3\13\3\f\3\f\3\f\3\f\7\f~\n") - buf.write("\f\f\f\16\f\u0081\13\f\3\f\3\f\3\f\3\f\5\f\u0087\n\f\3") - buf.write("\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\7\16\u0091\n\16\f\16") - buf.write("\16\16\u0094\13\16\3\16\3\16\3\16\3\16\5\16\u009a\n\16") - buf.write("\3\17\3\17\3\17\3\17\3\17\3\17\3\17\5\17\u00a3\n\17\3") - buf.write("\17\2\2\20\2\4\6\b\n\f\16\20\22\24\26\30\32\34\2\5\4\2") - buf.write("\7\7\35\35\4\2\3\3\5\5\3\2\t\16\2\u00b4\2\36\3\2\2\2\4") - buf.write("\65\3\2\2\2\6\67\3\2\2\2\bB\3\2\2\2\nY\3\2\2\2\f[\3\2") - buf.write("\2\2\16]\3\2\2\2\20b\3\2\2\2\22u\3\2\2\2\24w\3\2\2\2\26") - buf.write("\u0086\3\2\2\2\30\u0088\3\2\2\2\32\u0099\3\2\2\2\34\u00a2") - buf.write("\3\2\2\2\36 \7\5\2\2\37!\5\4\3\2 \37\3\2\2\2 !\3\2\2\2") - buf.write("!\"\3\2\2\2\"#\7\2\2\3#\3\3\2\2\2$\'\7\4\2\2%(\5\b\5\2") - buf.write("&(\5\6\4\2\'%\3\2\2\2\'&\3\2\2\2(*\3\2\2\2)+\5\4\3\2*") - buf.write(")\3\2\2\2*+\3\2\2\2+\66\3\2\2\2,-\7\6\2\2-/\5\b\5\2.\60") - buf.write("\5\4\3\2/.\3\2\2\2/\60\3\2\2\2\60\66\3\2\2\2\61\63\5\6") - buf.write("\4\2\62\64\5\4\3\2\63\62\3\2\2\2\63\64\3\2\2\2\64\66\3") - buf.write("\2\2\2\65$\3\2\2\2\65,\3\2\2\2\65\61\3\2\2\2\66\5\3\2") - buf.write("\2\2\678\7\26\2\28=\5\n\6\29:\7\31\2\2:<\5\n\6\2;9\3\2") - buf.write("\2\2\3\2\2\2>@\3\2\2\2?=\3\2\2\2") - buf.write("@A\7\27\2\2A\7\3\2\2\2BC\t\2\2\2C\t\3\2\2\2DZ\7\36\2\2") - buf.write("EF\7\37\2\2FQ\6\6\2\2GJ\7\30\2\2HI\7\37\2\2IK\6\6\3\2") - buf.write("JH\3\2\2\2JK\3\2\2\2KO\3\2\2\2LM\7\30\2\2MN\7\37\2\2N") - buf.write("P\6\6\4\2OL\3\2\2\2OP\3\2\2\2PR\3\2\2\2QG\3\2\2\2QR\3") - buf.write("\2\2\2RZ\3\2\2\2SZ\7\7\2\2TU\7\34\2\2UV\7\32\2\2VW\5\f") - buf.write("\7\2WX\7\33\2\2XZ\3\2\2\2YD\3\2\2\2YE\3\2\2\2YS\3\2\2") - buf.write("\2YT\3\2\2\2Z\13\3\2\2\2[\\\5\16\b\2\\\r\3\2\2\2]`\5\20") - buf.write("\t\2^_\7\b\2\2_a\5\16\b\2`^\3\2\2\2`a\3\2\2\2a\17\3\2") - buf.write("\2\2be\5\22\n\2cd\7\20\2\2df\5\20\t\2ec\3\2\2\2ef\3\2") - buf.write("\2\2f\21\3\2\2\2gh\7\17\2\2hv\5\22\n\2ij\7\32\2\2jk\5") - buf.write("\f\7\2kl\7\33\2\2lv\3\2\2\2mo\t\3\2\2np\5\4\3\2on\3\2") - buf.write("\2\2op\3\2\2\2ps\3\2\2\2qr\t\4\2\2rt\5\34\17\2sq\3\2\2") - buf.write("\2st\3\2\2\2tv\3\2\2\2ug\3\2\2\2ui\3\2\2\2um\3\2\2\2v") - buf.write("\23\3\2\2\2wx\5\34\17\2x\25\3\2\2\2yz\7\24\2\2z\177\5") - buf.write("\30\r\2{|\7\31\2\2|~\5\30\r\2}{\3\2\2\2~\u0081\3\2\2\2") - buf.write("\177}\3\2\2\2\177\u0080\3\2\2\2\u0080\u0082\3\2\2\2\u0081") - buf.write("\177\3\2\2\2\u0082\u0083\7\25\2\2\u0083\u0087\3\2\2\2") - buf.write("\u0084\u0085\7\24\2\2\u0085\u0087\7\25\2\2\u0086y\3\2") - buf.write("\2\2\u0086\u0084\3\2\2\2\u0087\27\3\2\2\2\u0088\u0089") - buf.write("\7\36\2\2\u0089\u008a\7\30\2\2\u008a\u008b\5\34\17\2\u008b") - buf.write("\31\3\2\2\2\u008c\u008d\7\26\2\2\u008d\u0092\5\34\17\2") - buf.write("\u008e\u008f\7\31\2\2\u008f\u0091\5\34\17\2\u0090\u008e") - buf.write("\3\2\2\2\u0091\u0094\3\2\2\2\u0092\u0090\3\2\2\2\u0092") - buf.write("\u0093\3\2\2\2\u0093\u0095\3\2\2\2\u0094\u0092\3\2\2\2") - buf.write("\u0095\u0096\7\27\2\2\u0096\u009a\3\2\2\2\u0097\u0098") - buf.write("\7\26\2\2\u0098\u009a\7\27\2\2\u0099\u008c\3\2\2\2\u0099") - buf.write("\u0097\3\2\2\2\u009a\33\3\2\2\2\u009b\u00a3\7\36\2\2\u009c") - buf.write("\u00a3\7\37\2\2\u009d\u00a3\5\26\f\2\u009e\u00a3\5\32") - buf.write("\16\2\u009f\u00a3\7\21\2\2\u00a0\u00a3\7\22\2\2\u00a1") - buf.write("\u00a3\7\23\2\2\u00a2\u009b\3\2\2\2\u00a2\u009c\3\2\2") - buf.write("\2\u00a2\u009d\3\2\2\2\u00a2\u009e\3\2\2\2\u00a2\u009f") - buf.write("\3\2\2\2\u00a2\u00a0\3\2\2\2\u00a2\u00a1\3\2\2\2\u00a3") - buf.write("\35\3\2\2\2\27 \'*/\63\65=JOQY`eosu\177\u0086\u0092\u0099") - buf.write("\u00a2") + buf.write("\t\16\4\17\t\17\4\20\t\20\3\2\3\2\5\2#\n\2\3\2\3\2\3\3") + buf.write("\3\3\3\3\5\3*\n\3\3\3\5\3-\n\3\3\3\3\3\3\3\5\3\62\n\3") + buf.write("\3\3\3\3\5\3\66\n\3\5\38\n\3\3\4\3\4\3\4\3\4\7\4>\n\4") + buf.write("\f\4\16\4A\13\4\3\4\3\4\3\5\3\5\3\6\3\6\3\6\3\6\5\6K\n") + buf.write("\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\5\6T\n\6\3\7\3\7\3\7\5") + buf.write("\7Y\n\7\3\7\3\7\3\7\5\7^\n\7\3\b\3\b\3\t\3\t\3\t\5\te") + buf.write("\n\t\3\n\3\n\3\n\5\nj\n\n\3\13\3\13\3\13\3\13\3\13\3\13") + buf.write("\3\13\3\13\5\13t\n\13\3\13\3\13\5\13x\n\13\5\13z\n\13") + buf.write("\3\f\3\f\3\r\3\r\3\r\3\r\7\r\u0082\n\r\f\r\16\r\u0085") + buf.write("\13\r\3\r\3\r\3\r\3\r\5\r\u008b\n\r\3\16\3\16\3\16\3\16") + buf.write("\3\17\3\17\3\17\3\17\7\17\u0095\n\17\f\17\16\17\u0098") + buf.write("\13\17\3\17\3\17\3\17\3\17\5\17\u009e\n\17\3\20\3\20\3") + buf.write("\20\3\20\3\20\3\20\3\20\5\20\u00a7\n\20\3\20\2\2\21\2") + buf.write("\4\6\b\n\f\16\20\22\24\26\30\32\34\36\2\5\4\2\7\7\35\35") + buf.write("\4\2\3\3\5\5\3\2\t\16\2\u00b8\2 \3\2\2\2\4\67\3\2\2\2") + buf.write("\69\3\2\2\2\bD\3\2\2\2\nS\3\2\2\2\fU\3\2\2\2\16_\3\2\2") + buf.write("\2\20a\3\2\2\2\22f\3\2\2\2\24y\3\2\2\2\26{\3\2\2\2\30") + buf.write("\u008a\3\2\2\2\32\u008c\3\2\2\2\34\u009d\3\2\2\2\36\u00a6") + buf.write("\3\2\2\2 \"\7\5\2\2!#\5\4\3\2\"!\3\2\2\2\"#\3\2\2\2#$") + buf.write("\3\2\2\2$%\7\2\2\3%\3\3\2\2\2&)\7\4\2\2\'*\5\b\5\2(*\5") + buf.write("\6\4\2)\'\3\2\2\2)(\3\2\2\2*,\3\2\2\2+-\5\4\3\2,+\3\2") + buf.write("\2\2,-\3\2\2\2-8\3\2\2\2./\7\6\2\2/\61\5\b\5\2\60\62\5") + buf.write("\4\3\2\61\60\3\2\2\2\61\62\3\2\2\2\628\3\2\2\2\63\65\5") + buf.write("\6\4\2\64\66\5\4\3\2\65\64\3\2\2\2\65\66\3\2\2\2\668\3") + buf.write("\2\2\2\67&\3\2\2\2\67.\3\2\2\2\67\63\3\2\2\28\5\3\2\2") + buf.write("\29:\7\26\2\2:?\5\n\6\2;<\7\31\2\2<>\5\n\6\2=;\3\2\2\2") + buf.write(">A\3\2\2\2?=\3\2\2\2?@\3\2\2\2@B\3\2\2\2A?\3\2\2\2BC\7") + buf.write("\27\2\2C\7\3\2\2\2DE\t\2\2\2E\t\3\2\2\2FT\7\36\2\2GH\7") + buf.write("\37\2\2HJ\6\6\2\2IK\5\f\7\2JI\3\2\2\2JK\3\2\2\2KT\3\2") + buf.write("\2\2LT\5\f\7\2MT\7\7\2\2NO\7\34\2\2OP\7\32\2\2PQ\5\16") + buf.write("\b\2QR\7\33\2\2RT\3\2\2\2SF\3\2\2\2SG\3\2\2\2SL\3\2\2") + buf.write("\2SM\3\2\2\2SN\3\2\2\2T\13\3\2\2\2UX\7\30\2\2VW\7\37\2") + buf.write("\2WY\6\7\3\2XV\3\2\2\2XY\3\2\2\2Y]\3\2\2\2Z[\7\30\2\2") + buf.write("[\\\7\37\2\2\\^\6\7\4\2]Z\3\2\2\2]^\3\2\2\2^\r\3\2\2\2") + buf.write("_`\5\20\t\2`\17\3\2\2\2ad\5\22\n\2bc\7\b\2\2ce\5\20\t") + buf.write("\2db\3\2\2\2de\3\2\2\2e\21\3\2\2\2fi\5\24\13\2gh\7\20") + buf.write("\2\2hj\5\22\n\2ig\3\2\2\2ij\3\2\2\2j\23\3\2\2\2kl\7\17") + buf.write("\2\2lz\5\24\13\2mn\7\32\2\2no\5\16\b\2op\7\33\2\2pz\3") + buf.write("\2\2\2qs\t\3\2\2rt\5\4\3\2sr\3\2\2\2st\3\2\2\2tw\3\2\2") + buf.write("\2uv\t\4\2\2vx\5\36\20\2wu\3\2\2\2wx\3\2\2\2xz\3\2\2\2") + buf.write("yk\3\2\2\2ym\3\2\2\2yq\3\2\2\2z\25\3\2\2\2{|\5\36\20\2") + buf.write("|\27\3\2\2\2}~\7\24\2\2~\u0083\5\32\16\2\177\u0080\7\31") + buf.write("\2\2\u0080\u0082\5\32\16\2\u0081\177\3\2\2\2\u0082\u0085") + buf.write("\3\2\2\2\u0083\u0081\3\2\2\2\u0083\u0084\3\2\2\2\u0084") + buf.write("\u0086\3\2\2\2\u0085\u0083\3\2\2\2\u0086\u0087\7\25\2") + buf.write("\2\u0087\u008b\3\2\2\2\u0088\u0089\7\24\2\2\u0089\u008b") + buf.write("\7\25\2\2\u008a}\3\2\2\2\u008a\u0088\3\2\2\2\u008b\31") + buf.write("\3\2\2\2\u008c\u008d\7\36\2\2\u008d\u008e\7\30\2\2\u008e") + buf.write("\u008f\5\36\20\2\u008f\33\3\2\2\2\u0090\u0091\7\26\2\2") + buf.write("\u0091\u0096\5\36\20\2\u0092\u0093\7\31\2\2\u0093\u0095") + buf.write("\5\36\20\2\u0094\u0092\3\2\2\2\u0095\u0098\3\2\2\2\u0096") + buf.write("\u0094\3\2\2\2\u0096\u0097\3\2\2\2\u0097\u0099\3\2\2\2") + buf.write("\u0098\u0096\3\2\2\2\u0099\u009a\7\27\2\2\u009a\u009e") + buf.write("\3\2\2\2\u009b\u009c\7\26\2\2\u009c\u009e\7\27\2\2\u009d") + buf.write("\u0090\3\2\2\2\u009d\u009b\3\2\2\2\u009e\35\3\2\2\2\u009f") + buf.write("\u00a7\7\36\2\2\u00a0\u00a7\7\37\2\2\u00a1\u00a7\5\30") + buf.write("\r\2\u00a2\u00a7\5\34\17\2\u00a3\u00a7\7\21\2\2\u00a4") + buf.write("\u00a7\7\22\2\2\u00a5\u00a7\7\23\2\2\u00a6\u009f\3\2\2") + buf.write("\2\u00a6\u00a0\3\2\2\2\u00a6\u00a1\3\2\2\2\u00a6\u00a2") + buf.write("\3\2\2\2\u00a6\u00a3\3\2\2\2\u00a6\u00a4\3\2\2\2\u00a6") + buf.write("\u00a5\3\2\2\2\u00a7\37\3\2\2\2\27\"),\61\65\67?JSX]d") + buf.write("iswy\u0083\u008a\u0096\u009d\u00a6") return buf.getvalue() @@ -100,19 +102,21 @@ class JSONPathParser ( Parser ): RULE_subscriptables = 2 RULE_subscriptableBareword = 3 RULE_subscriptable = 4 - RULE_expression = 5 - RULE_andExpression = 6 - RULE_orExpression = 7 - RULE_notExpression = 8 - RULE_json = 9 - RULE_obj = 10 - RULE_pair = 11 - RULE_array = 12 - RULE_value = 13 + RULE_sliceable = 5 + RULE_expression = 6 + RULE_andExpression = 7 + RULE_orExpression = 8 + RULE_notExpression = 9 + RULE_json = 10 + RULE_obj = 11 + RULE_pair = 12 + RULE_array = 13 + RULE_value = 14 ruleNames = [ "jsonpath", "subscript", "subscriptables", "subscriptableBareword", - "subscriptable", "expression", "andExpression", "orExpression", - "notExpression", "json", "obj", "pair", "array", "value" ] + "subscriptable", "sliceable", "expression", "andExpression", + "orExpression", "notExpression", "json", "obj", "pair", + "array", "value" ] EOF = Token.EOF CURRENT_VALUE=1 @@ -191,17 +195,17 @@ def jsonpath(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 28 - self.match(JSONPathParser.ROOT_VALUE) self.state = 30 + self.match(JSONPathParser.ROOT_VALUE) + self.state = 32 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): - self.state = 29 + self.state = 31 self.subscript() - self.state = 32 + self.state = 34 self.match(JSONPathParser.EOF) except RecognitionException as re: localctx.exception = re @@ -255,60 +259,60 @@ def subscript(self): self.enterRule(localctx, 2, self.RULE_subscript) self._la = 0 # Token type try: - self.state = 51 + self.state = 53 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.RECURSIVE_DESCENT]: self.enterOuterAlt(localctx, 1) - self.state = 34 + self.state = 36 self.match(JSONPathParser.RECURSIVE_DESCENT) - self.state = 37 + self.state = 39 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.WILDCARD_SUBSCRIPT, JSONPathParser.ID]: - self.state = 35 + self.state = 37 self.subscriptableBareword() pass elif token in [JSONPathParser.BRACKET_LEFT]: - self.state = 36 + self.state = 38 self.subscriptables() pass else: raise NoViableAltException(self) - self.state = 40 + self.state = 42 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): - self.state = 39 + self.state = 41 self.subscript() pass elif token in [JSONPathParser.SUBSCRIPT]: self.enterOuterAlt(localctx, 2) - self.state = 42 + self.state = 44 self.match(JSONPathParser.SUBSCRIPT) - self.state = 43 - self.subscriptableBareword() self.state = 45 + self.subscriptableBareword() + self.state = 47 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): - self.state = 44 + self.state = 46 self.subscript() pass elif token in [JSONPathParser.BRACKET_LEFT]: self.enterOuterAlt(localctx, 3) - self.state = 47 - self.subscriptables() self.state = 49 + self.subscriptables() + self.state = 51 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): - self.state = 48 + self.state = 50 self.subscript() @@ -370,23 +374,23 @@ def subscriptables(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 53 + self.state = 55 self.match(JSONPathParser.BRACKET_LEFT) - self.state = 54 + self.state = 56 self.subscriptable() - self.state = 59 + self.state = 61 self._errHandler.sync(self) _la = self._input.LA(1) while _la==JSONPathParser.COMMA: - self.state = 55 + self.state = 57 self.match(JSONPathParser.COMMA) - self.state = 56 + self.state = 58 self.subscriptable() - self.state = 61 + self.state = 63 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 62 + self.state = 64 self.match(JSONPathParser.BRACKET_RIGHT) except RecognitionException as re: localctx.exception = re @@ -429,7 +433,7 @@ def subscriptableBareword(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 64 + self.state = 66 _la = self._input.LA(1) if not(_la==JSONPathParser.WILDCARD_SUBSCRIPT or _la==JSONPathParser.ID): self._errHandler.recoverInline(self) @@ -453,17 +457,12 @@ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): def STRING(self): return self.getToken(JSONPathParser.STRING, 0) - def NUMBER(self, i:int=None): - if i is None: - return self.getTokens(JSONPathParser.NUMBER) - else: - return self.getToken(JSONPathParser.NUMBER, i) + def NUMBER(self): + return self.getToken(JSONPathParser.NUMBER, 0) + + def sliceable(self): + return self.getTypedRuleContext(JSONPathParser.SliceableContext,0) - def COLON(self, i:int=None): - if i is None: - return self.getTokens(JSONPathParser.COLON) - else: - return self.getToken(JSONPathParser.COLON, i) def WILDCARD_SUBSCRIPT(self): return self.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) @@ -501,71 +500,50 @@ def subscriptable(self): self.enterRule(localctx, 8, self.RULE_subscriptable) self._la = 0 # Token type try: - self.state = 87 + self.state = 81 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.STRING]: self.enterOuterAlt(localctx, 1) - self.state = 66 + self.state = 68 self.match(JSONPathParser.STRING) pass elif token in [JSONPathParser.NUMBER]: self.enterOuterAlt(localctx, 2) - self.state = 67 + self.state = 69 self.match(JSONPathParser.NUMBER) - self.state = 68 + self.state = 70 if not self.tryCast(int): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.tryCast(int)") - self.state = 79 + self.state = 72 self._errHandler.sync(self) _la = self._input.LA(1) if _la==JSONPathParser.COLON: - self.state = 69 - self.match(JSONPathParser.COLON) - self.state = 72 - self._errHandler.sync(self) - _la = self._input.LA(1) - if _la==JSONPathParser.NUMBER: - self.state = 70 - self.match(JSONPathParser.NUMBER) - self.state = 71 - if not self.tryCast(int): - from antlr4.error.Errors import FailedPredicateException - raise FailedPredicateException(self, "self.tryCast(int)") - - - self.state = 77 - self._errHandler.sync(self) - _la = self._input.LA(1) - if _la==JSONPathParser.COLON: - self.state = 74 - self.match(JSONPathParser.COLON) - self.state = 75 - self.match(JSONPathParser.NUMBER) - self.state = 76 - if not self.tryCast(int): - from antlr4.error.Errors import FailedPredicateException - raise FailedPredicateException(self, "self.tryCast(int)") - - + self.state = 71 + self.sliceable() pass - elif token in [JSONPathParser.WILDCARD_SUBSCRIPT]: + elif token in [JSONPathParser.COLON]: self.enterOuterAlt(localctx, 3) - self.state = 81 + self.state = 74 + self.sliceable() + pass + elif token in [JSONPathParser.WILDCARD_SUBSCRIPT]: + self.enterOuterAlt(localctx, 4) + self.state = 75 self.match(JSONPathParser.WILDCARD_SUBSCRIPT) pass elif token in [JSONPathParser.QUESTION]: - self.enterOuterAlt(localctx, 4) - self.state = 82 + self.enterOuterAlt(localctx, 5) + self.state = 76 self.match(JSONPathParser.QUESTION) - self.state = 83 + self.state = 77 self.match(JSONPathParser.PAREN_LEFT) - self.state = 84 + self.state = 78 self.expression() - self.state = 85 + self.state = 79 self.match(JSONPathParser.PAREN_RIGHT) pass else: @@ -579,6 +557,81 @@ def subscriptable(self): self.exitRule() return localctx + class SliceableContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def COLON(self, i:int=None): + if i is None: + return self.getTokens(JSONPathParser.COLON) + else: + return self.getToken(JSONPathParser.COLON, i) + + def NUMBER(self, i:int=None): + if i is None: + return self.getTokens(JSONPathParser.NUMBER) + else: + return self.getToken(JSONPathParser.NUMBER, i) + + def getRuleIndex(self): + return JSONPathParser.RULE_sliceable + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSliceable" ): + listener.enterSliceable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSliceable" ): + listener.exitSliceable(self) + + + + + def sliceable(self): + + localctx = JSONPathParser.SliceableContext(self, self._ctx, self.state) + self.enterRule(localctx, 10, self.RULE_sliceable) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 83 + self.match(JSONPathParser.COLON) + self.state = 86 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==JSONPathParser.NUMBER: + self.state = 84 + self.match(JSONPathParser.NUMBER) + self.state = 85 + if not self.tryCast(int): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.tryCast(int)") + + + self.state = 91 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==JSONPathParser.COLON: + self.state = 88 + self.match(JSONPathParser.COLON) + self.state = 89 + self.match(JSONPathParser.NUMBER) + self.state = 90 + if not self.tryCast(int): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.tryCast(int)") + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + class ExpressionContext(ParserRuleContext): def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): @@ -606,10 +659,10 @@ def exitRule(self, listener:ParseTreeListener): def expression(self): localctx = JSONPathParser.ExpressionContext(self, self._ctx, self.state) - self.enterRule(localctx, 10, self.RULE_expression) + self.enterRule(localctx, 12, self.RULE_expression) try: self.enterOuterAlt(localctx, 1) - self.state = 89 + self.state = 93 self.andExpression() except RecognitionException as re: localctx.exception = re @@ -653,19 +706,19 @@ def exitRule(self, listener:ParseTreeListener): def andExpression(self): localctx = JSONPathParser.AndExpressionContext(self, self._ctx, self.state) - self.enterRule(localctx, 12, self.RULE_andExpression) + self.enterRule(localctx, 14, self.RULE_andExpression) self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 91 + self.state = 95 self.orExpression() - self.state = 94 + self.state = 98 self._errHandler.sync(self) _la = self._input.LA(1) if _la==JSONPathParser.AND: - self.state = 92 + self.state = 96 self.match(JSONPathParser.AND) - self.state = 93 + self.state = 97 self.andExpression() @@ -711,19 +764,19 @@ def exitRule(self, listener:ParseTreeListener): def orExpression(self): localctx = JSONPathParser.OrExpressionContext(self, self._ctx, self.state) - self.enterRule(localctx, 14, self.RULE_orExpression) + self.enterRule(localctx, 16, self.RULE_orExpression) self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 96 + self.state = 100 self.notExpression() - self.state = 99 + self.state = 103 self._errHandler.sync(self) _la = self._input.LA(1) if _la==JSONPathParser.OR: - self.state = 97 + self.state = 101 self.match(JSONPathParser.OR) - self.state = 98 + self.state = 102 self.orExpression() @@ -807,57 +860,57 @@ def exitRule(self, listener:ParseTreeListener): def notExpression(self): localctx = JSONPathParser.NotExpressionContext(self, self._ctx, self.state) - self.enterRule(localctx, 16, self.RULE_notExpression) + self.enterRule(localctx, 18, self.RULE_notExpression) self._la = 0 # Token type try: - self.state = 115 + self.state = 119 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.NOT]: self.enterOuterAlt(localctx, 1) - self.state = 101 + self.state = 105 self.match(JSONPathParser.NOT) - self.state = 102 + self.state = 106 self.notExpression() pass elif token in [JSONPathParser.PAREN_LEFT]: self.enterOuterAlt(localctx, 2) - self.state = 103 + self.state = 107 self.match(JSONPathParser.PAREN_LEFT) - self.state = 104 + self.state = 108 self.expression() - self.state = 105 + self.state = 109 self.match(JSONPathParser.PAREN_RIGHT) pass elif token in [JSONPathParser.CURRENT_VALUE, JSONPathParser.ROOT_VALUE]: self.enterOuterAlt(localctx, 3) - self.state = 107 + self.state = 111 _la = self._input.LA(1) if not(_la==JSONPathParser.CURRENT_VALUE or _la==JSONPathParser.ROOT_VALUE): self._errHandler.recoverInline(self) else: self._errHandler.reportMatch(self) self.consume() - self.state = 109 + self.state = 113 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.RECURSIVE_DESCENT) | (1 << JSONPathParser.SUBSCRIPT) | (1 << JSONPathParser.BRACKET_LEFT))) != 0): - self.state = 108 + self.state = 112 self.subscript() - self.state = 113 + self.state = 117 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.EQ) | (1 << JSONPathParser.GE) | (1 << JSONPathParser.GT) | (1 << JSONPathParser.LE) | (1 << JSONPathParser.LT) | (1 << JSONPathParser.NE))) != 0): - self.state = 111 + self.state = 115 _la = self._input.LA(1) if not((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << JSONPathParser.EQ) | (1 << JSONPathParser.GE) | (1 << JSONPathParser.GT) | (1 << JSONPathParser.LE) | (1 << JSONPathParser.LT) | (1 << JSONPathParser.NE))) != 0)): self._errHandler.recoverInline(self) else: self._errHandler.reportMatch(self) self.consume() - self.state = 112 + self.state = 116 self.value() @@ -900,10 +953,10 @@ def exitRule(self, listener:ParseTreeListener): def json(self): localctx = JSONPathParser.JsonContext(self, self._ctx, self.state) - self.enterRule(localctx, 18, self.RULE_json) + self.enterRule(localctx, 20, self.RULE_json) try: self.enterOuterAlt(localctx, 1) - self.state = 117 + self.state = 121 self.value() except RecognitionException as re: localctx.exception = re @@ -955,39 +1008,39 @@ def exitRule(self, listener:ParseTreeListener): def obj(self): localctx = JSONPathParser.ObjContext(self, self._ctx, self.state) - self.enterRule(localctx, 20, self.RULE_obj) + self.enterRule(localctx, 22, self.RULE_obj) self._la = 0 # Token type try: - self.state = 132 + self.state = 136 self._errHandler.sync(self) la_ = self._interp.adaptivePredict(self._input,17,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 119 + self.state = 123 self.match(JSONPathParser.BRACE_LEFT) - self.state = 120 + self.state = 124 self.pair() - self.state = 125 + self.state = 129 self._errHandler.sync(self) _la = self._input.LA(1) while _la==JSONPathParser.COMMA: - self.state = 121 + self.state = 125 self.match(JSONPathParser.COMMA) - self.state = 122 + self.state = 126 self.pair() - self.state = 127 + self.state = 131 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 128 + self.state = 132 self.match(JSONPathParser.BRACE_RIGHT) pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 130 + self.state = 134 self.match(JSONPathParser.BRACE_LEFT) - self.state = 131 + self.state = 135 self.match(JSONPathParser.BRACE_RIGHT) pass @@ -1033,14 +1086,14 @@ def exitRule(self, listener:ParseTreeListener): def pair(self): localctx = JSONPathParser.PairContext(self, self._ctx, self.state) - self.enterRule(localctx, 22, self.RULE_pair) + self.enterRule(localctx, 24, self.RULE_pair) try: self.enterOuterAlt(localctx, 1) - self.state = 134 + self.state = 138 self.match(JSONPathParser.STRING) - self.state = 135 + self.state = 139 self.match(JSONPathParser.COLON) - self.state = 136 + self.state = 140 self.value() except RecognitionException as re: localctx.exception = re @@ -1092,39 +1145,39 @@ def exitRule(self, listener:ParseTreeListener): def array(self): localctx = JSONPathParser.ArrayContext(self, self._ctx, self.state) - self.enterRule(localctx, 24, self.RULE_array) + self.enterRule(localctx, 26, self.RULE_array) self._la = 0 # Token type try: - self.state = 151 + self.state = 155 self._errHandler.sync(self) la_ = self._interp.adaptivePredict(self._input,19,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 138 + self.state = 142 self.match(JSONPathParser.BRACKET_LEFT) - self.state = 139 + self.state = 143 self.value() - self.state = 144 + self.state = 148 self._errHandler.sync(self) _la = self._input.LA(1) while _la==JSONPathParser.COMMA: - self.state = 140 + self.state = 144 self.match(JSONPathParser.COMMA) - self.state = 141 + self.state = 145 self.value() - self.state = 146 + self.state = 150 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 147 + self.state = 151 self.match(JSONPathParser.BRACKET_RIGHT) pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 149 + self.state = 153 self.match(JSONPathParser.BRACKET_LEFT) - self.state = 150 + self.state = 154 self.match(JSONPathParser.BRACKET_RIGHT) pass @@ -1183,44 +1236,44 @@ def exitRule(self, listener:ParseTreeListener): def value(self): localctx = JSONPathParser.ValueContext(self, self._ctx, self.state) - self.enterRule(localctx, 26, self.RULE_value) + self.enterRule(localctx, 28, self.RULE_value) try: - self.state = 160 + self.state = 164 self._errHandler.sync(self) token = self._input.LA(1) if token in [JSONPathParser.STRING]: self.enterOuterAlt(localctx, 1) - self.state = 153 + self.state = 157 self.match(JSONPathParser.STRING) pass elif token in [JSONPathParser.NUMBER]: self.enterOuterAlt(localctx, 2) - self.state = 154 + self.state = 158 self.match(JSONPathParser.NUMBER) pass elif token in [JSONPathParser.BRACE_LEFT]: self.enterOuterAlt(localctx, 3) - self.state = 155 + self.state = 159 self.obj() pass elif token in [JSONPathParser.BRACKET_LEFT]: self.enterOuterAlt(localctx, 4) - self.state = 156 + self.state = 160 self.array() pass elif token in [JSONPathParser.TRUE]: self.enterOuterAlt(localctx, 5) - self.state = 157 + self.state = 161 self.match(JSONPathParser.TRUE) pass elif token in [JSONPathParser.FALSE]: self.enterOuterAlt(localctx, 6) - self.state = 158 + self.state = 162 self.match(JSONPathParser.FALSE) pass elif token in [JSONPathParser.NULL]: self.enterOuterAlt(localctx, 7) - self.state = 159 + self.state = 163 self.match(JSONPathParser.NULL) pass else: @@ -1240,6 +1293,7 @@ def sempred(self, localctx:RuleContext, ruleIndex:int, predIndex:int): if self._predicates == None: self._predicates = dict() self._predicates[4] = self.subscriptable_sempred + self._predicates[5] = self.sliceable_sempred pred = self._predicates.get(ruleIndex, None) if pred is None: raise Exception("No predicate with index:" + str(ruleIndex)) @@ -1251,6 +1305,7 @@ def subscriptable_sempred(self, localctx:SubscriptableContext, predIndex:int): return self.tryCast(int) + def sliceable_sempred(self, localctx:SliceableContext, predIndex:int): if predIndex == 1: return self.tryCast(int) diff --git a/jsonpath2/parser/__init__.py b/jsonpath2/parser/__init__.py index c142be2..623859e 100644 --- a/jsonpath2/parser/__init__.py +++ b/jsonpath2/parser/__init__.py @@ -107,21 +107,23 @@ def exitSubscriptable(self, ctx: JSONPathParser.SubscriptableContext): self._stack.append(ObjectIndexSubscript(text)) elif bool(ctx.NUMBER()): - if ctx.getToken(JSONPathParser.COLON, 0) is not None: - start = int(ctx.NUMBER(0).getText()) if bool( - ctx.NUMBER(0)) else None + if bool(ctx.sliceable()): + func = self._stack.pop() - end = int(ctx.NUMBER(1).getText()) if bool( - ctx.NUMBER(1)) else None + start = int(ctx.NUMBER().getText()) if bool( + ctx.NUMBER()) else None - step = int(ctx.NUMBER(2).getText()) if bool( - ctx.NUMBER(2)) else None - - self._stack.append(ArraySliceSubscript(start, end, step)) + self._stack.append(func(start)) else: - index = int(ctx.NUMBER(0).getText()) + index = int(ctx.NUMBER().getText()) self._stack.append(ArrayIndexSubscript(index)) + elif bool(ctx.sliceable()): + func = self._stack.pop() + + start = None + + self._stack.append(func(start)) elif ctx.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) is not None: self._stack.append(WildcardSubscript()) elif ctx.getToken(JSONPathParser.QUESTION, 0) is not None: @@ -131,6 +133,15 @@ def exitSubscriptable(self, ctx: JSONPathParser.SubscriptableContext): else: raise ValueError() + def exitSliceable(self, ctx: JSONPathParser.SliceableContext): + end = int(ctx.NUMBER(0).getText()) if bool( + ctx.NUMBER(0)) else None + + step = int(ctx.NUMBER(1).getText()) if bool( + ctx.NUMBER(1)) else None + + self._stack.append(lambda start: ArraySliceSubscript(start, end, step)) + def exitAndExpression(self, ctx: JSONPathParser.AndExpressionContext): expressions = [] diff --git a/jsonpath2/test/bookstore_test.py b/jsonpath2/test/bookstore_test.py index 9a53f00..f2e5487 100644 --- a/jsonpath2/test/bookstore_test.py +++ b/jsonpath2/test/bookstore_test.py @@ -110,7 +110,7 @@ def test_bookstore_examples_6(self): def test_bookstore_examples_7(self): """Test the bookstore examples.""" - for path in ['$..book[0,1]', '$..book[0:2]', '$..book[0:2:1]']: + for path in ['$..book[0,1]', '$..book[:2]', '$..book[:2:1]']: expr = Path.parse_str(path) self.assertEqual(Path.parse_str(str(expr)), expr) matches = [x.current_value for x in expr.match(self.root_value)] From 8881cdef8535f822229692c44e9f97d41268c965 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Tue, 18 Sep 2018 19:15:51 -0700 Subject: [PATCH 15/23] Tests to increase coverage --- jsonpath2/test/arrayslice_subscript_test.py | 77 +++++++++++++++++++++ jsonpath2/test/expression_test.py | 12 +++- jsonpath2/test/jsonpath2_test.py | 11 +++ jsonpath2/test/subscript_node_test.py | 54 +++++++++++++++ 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 jsonpath2/test/arrayslice_subscript_test.py create mode 100644 jsonpath2/test/subscript_node_test.py diff --git a/jsonpath2/test/arrayslice_subscript_test.py b/jsonpath2/test/arrayslice_subscript_test.py new file mode 100644 index 0000000..ea3eb20 --- /dev/null +++ b/jsonpath2/test/arrayslice_subscript_test.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Test the arrayslice object.""" +from unittest import TestCase +from jsonpath2.subscripts.arrayslice import ArraySliceSubscript + + +class TestArraySliceSubscript(TestCase): + """Test the arrayslice base class.""" + + def setUp(self): + self.root_value = None + self.current_value = list(range(10)) + + + def test_arrayslice0(self): + subscript = ArraySliceSubscript(None, None, None) + + self.assertEqual(':', subscript.tojsonpath()) + self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) + + + def test_arrayslice1(self): + subscript = ArraySliceSubscript(None, None, 2) + + self.assertEqual('::2', subscript.tojsonpath()) + self.assertEqual([0, 2, 4, 6, 8], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) + + + def test_arrayslice2(self): + subscript = ArraySliceSubscript(None, 7, None) + + self.assertEqual(':7', subscript.tojsonpath()) + self.assertEqual([0, 1, 2, 3, 4, 5, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) + + + def test_arrayslice3(self): + subscript = ArraySliceSubscript(None, 7, 2) + + self.assertEqual(':7:2', subscript.tojsonpath()) + self.assertEqual([0, 2, 4, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) + + + def test_arrayslice4(self): + subscript = ArraySliceSubscript(5, None, None) + + self.assertEqual('5:', subscript.tojsonpath()) + self.assertEqual([5, 6, 7, 8, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) + + + def test_arrayslice5(self): + subscript = ArraySliceSubscript(5, None, 2) + + self.assertEqual('5::2', subscript.tojsonpath()) + self.assertEqual([5, 7, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) + + + def test_arrayslice6(self): + subscript = ArraySliceSubscript(5, 7, None) + + self.assertEqual('5:7', subscript.tojsonpath()) + self.assertEqual([5, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) + + + def test_arrayslice7(self): + subscript = ArraySliceSubscript(5, 7, 2) + + self.assertEqual('5:7:2', subscript.tojsonpath()) + self.assertEqual([5], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) + + + def test_arrayslice_not_list(self): + subscript = ArraySliceSubscript() + + x = None + self.assertTrue(not isinstance(x, list)) + self.assertEqual([], list(subscript.match(x, x))) diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index d0211e7..68f7888 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -2,8 +2,11 @@ # -*- coding: utf-8 -*- """Test the expression object.""" from unittest import TestCase +from jsonpath2.expressions.operator import OperatorExpression, AndVariadicOperatorExpression +from jsonpath2.expressions.some import SomeExpression +from jsonpath2.nodes.current import CurrentNode +from jsonpath2.nodes.terminal import TerminalNode from jsonpath2.path import Path -from jsonpath2.expressions.operator import OperatorExpression class TestExpression(TestCase): @@ -50,3 +53,10 @@ def test_unary_boolean_operator(self): expr = Path.parse_str('$[?(not (@.bar or (@.fiz > 2 and @.biz > 2)))]') self.assertEqual(Path.parse_str(str(expr)), expr) self.assertTrue([x for x in expr.match(data)]) + + def test_variadic_operator(self): + """Test a variadic operator.""" + expr = AndVariadicOperatorExpression([]) + self.assertEqual('', expr.tojsonpath()) + expr = AndVariadicOperatorExpression([SomeExpression(CurrentNode(TerminalNode()))]) + self.assertEqual('@', expr.tojsonpath()) diff --git a/jsonpath2/test/jsonpath2_test.py b/jsonpath2/test/jsonpath2_test.py index 13de535..a134ef6 100644 --- a/jsonpath2/test/jsonpath2_test.py +++ b/jsonpath2/test/jsonpath2_test.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- """Test the jsonpath module.""" +from tempfile import NamedTemporaryFile from unittest import TestCase from json import loads from jsonpath2.node import MatchData @@ -320,7 +321,17 @@ def _assert_node_test_case(self, **kwargs): if isinstance(kwargs['node'], RootNode): self.assertEqual(kwargs['node'], Path.parse_str( kwargs['__jsonpath__']).root_node) + + with NamedTemporaryFile() as f: + f.write(bytes(kwargs['__jsonpath__'], 'utf-8')) + f.seek(0) + + self.assertEqual(kwargs['node'], Path.parse_file(f.name).root_node) + else: + with self.assertRaises(ValueError): + Path(kwargs['node']) + with self.assertRaises(ValueError): Path.parse_str('__jsonpath__') diff --git a/jsonpath2/test/subscript_node_test.py b/jsonpath2/test/subscript_node_test.py new file mode 100644 index 0000000..5ac8746 --- /dev/null +++ b/jsonpath2/test/subscript_node_test.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Test the subscript object.""" +from unittest import TestCase +from jsonpath2.node import MatchData, Node +from jsonpath2.nodes.subscript import SubscriptNode +from jsonpath2.nodes.terminal import TerminalNode +from jsonpath2.path import Path +from jsonpath2.subscript import Subscript + + +class TestSubscriptNode(TestCase): + """Test the subscript base class.""" + + def test_badsubscript1(self): + """Test subscript that does not provide terminal or other subscript.""" + + class BadSubscript1(Subscript): + """Subscript that does not provide terminal or other subscript.""" + + def __jsonpath__(self): + """Empty.""" + return [] + + def match(self, root_value, current_value): + """One.""" + yield MatchData(None, root_value, current_value) + + self.assertEqual('', BadSubscript1().tojsonpath()) + + with self.assertRaises(ValueError): + # NOTE Use 'list' to force the computation to occur, raising any exceptions. + list(SubscriptNode(TerminalNode(), [BadSubscript1()]).match(None, None)) + + + def test_badsubscript2(self): + """Test subscript that provides other subscript but not subscripted-terminal.""" + + class BadSubscript2(Subscript): + """Subscript that provides other subscript but not subscripted-terminal.""" + + def __jsonpath__(self): + """Empty.""" + return [] + + def match(self, root_value, current_value): + """One.""" + yield MatchData(SubscriptNode(None), root_value, current_value) + + self.assertEqual('', BadSubscript2().tojsonpath()) + + with self.assertRaises(ValueError): + # NOTE Use 'list' to force the computation to occur, raising any exceptions. + list(SubscriptNode(TerminalNode(), [BadSubscript2()]).match(None, None)) From 83e1f6f25da2bd846ff4d8bcb3e383a4b7965ea7 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Tue, 18 Sep 2018 19:33:32 -0700 Subject: [PATCH 16/23] pre-commit fixes --- jsonpath2/test/arrayslice_subscript_test.py | 25 +++++++++++---------- jsonpath2/test/jsonpath2_test.py | 8 +++---- jsonpath2/test/subscript_node_test.py | 5 +---- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/jsonpath2/test/arrayslice_subscript_test.py b/jsonpath2/test/arrayslice_subscript_test.py index ea3eb20..b0a0f0e 100644 --- a/jsonpath2/test/arrayslice_subscript_test.py +++ b/jsonpath2/test/arrayslice_subscript_test.py @@ -9,69 +9,70 @@ class TestArraySliceSubscript(TestCase): """Test the arrayslice base class.""" def setUp(self): + """Setup the class.""" self.root_value = None self.current_value = list(range(10)) def test_arrayslice0(self): + """Test the arrayslice with configuration 000 (base 2).""" subscript = ArraySliceSubscript(None, None, None) - self.assertEqual(':', subscript.tojsonpath()) self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice1(self): + """Test the arrayslice with configuration 001 (base 2).""" subscript = ArraySliceSubscript(None, None, 2) - self.assertEqual('::2', subscript.tojsonpath()) self.assertEqual([0, 2, 4, 6, 8], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice2(self): + """Test the arrayslice with configuration 010 (base 2).""" subscript = ArraySliceSubscript(None, 7, None) - self.assertEqual(':7', subscript.tojsonpath()) self.assertEqual([0, 1, 2, 3, 4, 5, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice3(self): + """Test the arrayslice with configuration 011 (base 2).""" subscript = ArraySliceSubscript(None, 7, 2) - self.assertEqual(':7:2', subscript.tojsonpath()) self.assertEqual([0, 2, 4, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice4(self): + """Test the arrayslice with configuration 100 (base 2).""" subscript = ArraySliceSubscript(5, None, None) - self.assertEqual('5:', subscript.tojsonpath()) self.assertEqual([5, 6, 7, 8, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice5(self): + """Test the arrayslice with configuration 101 (base 2).""" subscript = ArraySliceSubscript(5, None, 2) - self.assertEqual('5::2', subscript.tojsonpath()) self.assertEqual([5, 7, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice6(self): + """Test the arrayslice with configuration 110 (base 2).""" subscript = ArraySliceSubscript(5, 7, None) - self.assertEqual('5:7', subscript.tojsonpath()) self.assertEqual([5, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice7(self): + """Test the arrayslice with configuration 111 (base 2).""" subscript = ArraySliceSubscript(5, 7, 2) - self.assertEqual('5:7:2', subscript.tojsonpath()) self.assertEqual([5], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice_not_list(self): + """Test the arrayslice with non-list input.""" subscript = ArraySliceSubscript() - - x = None - self.assertTrue(not isinstance(x, list)) - self.assertEqual([], list(subscript.match(x, x))) + root_value = None + self.assertTrue(not isinstance(root_value, list)) + self.assertEqual([], list(subscript.match(root_value, root_value))) diff --git a/jsonpath2/test/jsonpath2_test.py b/jsonpath2/test/jsonpath2_test.py index a134ef6..2bde494 100644 --- a/jsonpath2/test/jsonpath2_test.py +++ b/jsonpath2/test/jsonpath2_test.py @@ -322,11 +322,11 @@ def _assert_node_test_case(self, **kwargs): self.assertEqual(kwargs['node'], Path.parse_str( kwargs['__jsonpath__']).root_node) - with NamedTemporaryFile() as f: - f.write(bytes(kwargs['__jsonpath__'], 'utf-8')) - f.seek(0) + with NamedTemporaryFile() as temp_file: + temp_file.write(bytes(kwargs['__jsonpath__'], 'utf-8')) + temp_file.seek(0) - self.assertEqual(kwargs['node'], Path.parse_file(f.name).root_node) + self.assertEqual(kwargs['node'], Path.parse_file(temp_file.name).root_node) else: with self.assertRaises(ValueError): diff --git a/jsonpath2/test/subscript_node_test.py b/jsonpath2/test/subscript_node_test.py index 5ac8746..a37305a 100644 --- a/jsonpath2/test/subscript_node_test.py +++ b/jsonpath2/test/subscript_node_test.py @@ -2,10 +2,9 @@ # -*- coding: utf-8 -*- """Test the subscript object.""" from unittest import TestCase -from jsonpath2.node import MatchData, Node +from jsonpath2.node import MatchData from jsonpath2.nodes.subscript import SubscriptNode from jsonpath2.nodes.terminal import TerminalNode -from jsonpath2.path import Path from jsonpath2.subscript import Subscript @@ -14,7 +13,6 @@ class TestSubscriptNode(TestCase): def test_badsubscript1(self): """Test subscript that does not provide terminal or other subscript.""" - class BadSubscript1(Subscript): """Subscript that does not provide terminal or other subscript.""" @@ -35,7 +33,6 @@ def match(self, root_value, current_value): def test_badsubscript2(self): """Test subscript that provides other subscript but not subscripted-terminal.""" - class BadSubscript2(Subscript): """Subscript that provides other subscript but not subscripted-terminal.""" From a59b3e8f9c5a0ef7a56d941c457b6302e0686357 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 19 Sep 2018 07:05:42 -0700 Subject: [PATCH 17/23] fix pre-commit --- jsonpath2/test/arrayslice_subscript_test.py | 33 ++++++++++----------- jsonpath2/test/expression_test.py | 3 +- jsonpath2/test/jsonpath2_test.py | 3 +- jsonpath2/test/subscript_node_test.py | 11 +++++-- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/jsonpath2/test/arrayslice_subscript_test.py b/jsonpath2/test/arrayslice_subscript_test.py index b0a0f0e..fed8319 100644 --- a/jsonpath2/test/arrayslice_subscript_test.py +++ b/jsonpath2/test/arrayslice_subscript_test.py @@ -13,62 +13,61 @@ def setUp(self): self.root_value = None self.current_value = list(range(10)) - def test_arrayslice0(self): """Test the arrayslice with configuration 000 (base 2).""" subscript = ArraySliceSubscript(None, None, None) self.assertEqual(':', subscript.tojsonpath()) - self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) - + self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], list(map( + lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice1(self): """Test the arrayslice with configuration 001 (base 2).""" subscript = ArraySliceSubscript(None, None, 2) self.assertEqual('::2', subscript.tojsonpath()) - self.assertEqual([0, 2, 4, 6, 8], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) - + self.assertEqual([0, 2, 4, 6, 8], list(map( + lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice2(self): """Test the arrayslice with configuration 010 (base 2).""" subscript = ArraySliceSubscript(None, 7, None) self.assertEqual(':7', subscript.tojsonpath()) - self.assertEqual([0, 1, 2, 3, 4, 5, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) - + self.assertEqual([0, 1, 2, 3, 4, 5, 6], list(map( + lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice3(self): """Test the arrayslice with configuration 011 (base 2).""" subscript = ArraySliceSubscript(None, 7, 2) self.assertEqual(':7:2', subscript.tojsonpath()) - self.assertEqual([0, 2, 4, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) - + self.assertEqual([0, 2, 4, 6], list(map( + lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice4(self): """Test the arrayslice with configuration 100 (base 2).""" subscript = ArraySliceSubscript(5, None, None) self.assertEqual('5:', subscript.tojsonpath()) - self.assertEqual([5, 6, 7, 8, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) - + self.assertEqual([5, 6, 7, 8, 9], list(map( + lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) def test_arrayslice5(self): """Test the arrayslice with configuration 101 (base 2).""" subscript = ArraySliceSubscript(5, None, 2) self.assertEqual('5::2', subscript.tojsonpath()) - self.assertEqual([5, 7, 9], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) - + self.assertEqual([5, 7, 9], list(map(lambda match_data: match_data.current_value, + subscript.match(self.root_value, self.current_value)))) def test_arrayslice6(self): """Test the arrayslice with configuration 110 (base 2).""" subscript = ArraySliceSubscript(5, 7, None) self.assertEqual('5:7', subscript.tojsonpath()) - self.assertEqual([5, 6], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) - + self.assertEqual([5, 6], list(map(lambda match_data: match_data.current_value, + subscript.match(self.root_value, self.current_value)))) def test_arrayslice7(self): """Test the arrayslice with configuration 111 (base 2).""" subscript = ArraySliceSubscript(5, 7, 2) self.assertEqual('5:7:2', subscript.tojsonpath()) - self.assertEqual([5], list(map(lambda match_data: match_data.current_value, subscript.match(self.root_value, self.current_value)))) - + self.assertEqual([5], list(map(lambda match_data: match_data.current_value, + subscript.match(self.root_value, self.current_value)))) def test_arrayslice_not_list(self): """Test the arrayslice with non-list input.""" diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index 68f7888..bdb9bfc 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -58,5 +58,6 @@ def test_variadic_operator(self): """Test a variadic operator.""" expr = AndVariadicOperatorExpression([]) self.assertEqual('', expr.tojsonpath()) - expr = AndVariadicOperatorExpression([SomeExpression(CurrentNode(TerminalNode()))]) + expr = AndVariadicOperatorExpression( + [SomeExpression(CurrentNode(TerminalNode()))]) self.assertEqual('@', expr.tojsonpath()) diff --git a/jsonpath2/test/jsonpath2_test.py b/jsonpath2/test/jsonpath2_test.py index 2bde494..52421af 100644 --- a/jsonpath2/test/jsonpath2_test.py +++ b/jsonpath2/test/jsonpath2_test.py @@ -326,7 +326,8 @@ def _assert_node_test_case(self, **kwargs): temp_file.write(bytes(kwargs['__jsonpath__'], 'utf-8')) temp_file.seek(0) - self.assertEqual(kwargs['node'], Path.parse_file(temp_file.name).root_node) + self.assertEqual(kwargs['node'], Path.parse_file( + temp_file.name).root_node) else: with self.assertRaises(ValueError): diff --git a/jsonpath2/test/subscript_node_test.py b/jsonpath2/test/subscript_node_test.py index a37305a..b03fb12 100644 --- a/jsonpath2/test/subscript_node_test.py +++ b/jsonpath2/test/subscript_node_test.py @@ -28,8 +28,10 @@ def match(self, root_value, current_value): with self.assertRaises(ValueError): # NOTE Use 'list' to force the computation to occur, raising any exceptions. - list(SubscriptNode(TerminalNode(), [BadSubscript1()]).match(None, None)) - + list(SubscriptNode( + TerminalNode(), + [BadSubscript1()] + ).match(None, None)) def test_badsubscript2(self): """Test subscript that provides other subscript but not subscripted-terminal.""" @@ -48,4 +50,7 @@ def match(self, root_value, current_value): with self.assertRaises(ValueError): # NOTE Use 'list' to force the computation to occur, raising any exceptions. - list(SubscriptNode(TerminalNode(), [BadSubscript2()]).match(None, None)) + list(SubscriptNode( + TerminalNode(), + [BadSubscript2()] + ).match(None, None)) From d10b74245d4ea54509865081d83f9b16e9334ef6 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 19 Sep 2018 08:30:27 -0700 Subject: [PATCH 18/23] try some more non-sense tests --- jsonpath2/test/expression_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index bdb9bfc..3083074 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -25,8 +25,12 @@ def test_failure_expressions(self): '$[?(@.hello < "string")]', '$[?(@.hello <= "string")]', '$[?(@.hello > "string")]', - '$[?(@.hello >= "string")]' + '$[?(@.hello >= "string")]', + '$[?(@.hello = {"bar": "baz"})]', + '$[?(@.hello = false)]', + '$[?(@.hello = true)]' ]: + print(path) expr = Path.parse_str(path) self.assertFalse([x for x in expr.match(data)]) From 91325b9b4856c5947f6879fe1fc3c05cfbbc7352 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 19 Sep 2018 08:40:01 -0700 Subject: [PATCH 19/23] add array to test --- jsonpath2/test/expression_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index 3083074..b1445b2 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -27,6 +27,7 @@ def test_failure_expressions(self): '$[?(@.hello > "string")]', '$[?(@.hello >= "string")]', '$[?(@.hello = {"bar": "baz"})]', + '$[?(@.hello = ["bar", "baz"])]', '$[?(@.hello = false)]', '$[?(@.hello = true)]' ]: From 67591108b952a58a1f190e82a100601763b400bb Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 19 Sep 2018 08:41:21 -0700 Subject: [PATCH 20/23] Python 3.5 doesn't seem to be working great --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 42716fa..3094c4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: -- 3.5 - 3.6 stages: - lint @@ -17,9 +16,7 @@ script: jobs: include: - stage: lint - python: 3.5 - script: pre-commit run -a - - python: 3.6 + python: 3.6 script: pre-commit run -a - stage: deploy python: 3.6 From 0c8841a52984b0ee22b039036e9a73a6ff94292a Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Wed, 19 Sep 2018 08:53:22 -0700 Subject: [PATCH 21/23] Add coverage pragmas --- jsonpath2/parser/__init__.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/jsonpath2/parser/__init__.py b/jsonpath2/parser/__init__.py index 623859e..327c7bb 100644 --- a/jsonpath2/parser/__init__.py +++ b/jsonpath2/parser/__init__.py @@ -45,7 +45,8 @@ def exitJsonpath(self, ctx: JSONPathParser.JsonpathContext): next_node = TerminalNode() self._stack.append(RootNode(next_node)) else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover # pylint: disable=too-many-branches # It would sure be nice if we had a case statement. @@ -60,7 +61,8 @@ def exitSubscript(self, ctx: JSONPathParser.SubscriptContext): elif bool(ctx.subscriptables()): subscriptable_nodes = self._stack.pop() else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover self._stack.append(RecursiveDescentNode( SubscriptNode(next_node, subscriptable_nodes))) elif ctx.getToken(JSONPathParser.SUBSCRIPT, 0) is not None: @@ -71,7 +73,8 @@ def exitSubscript(self, ctx: JSONPathParser.SubscriptContext): if bool(ctx.subscriptableBareword()): subscriptable_nodes = [self._stack.pop()] else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover self._stack.append(SubscriptNode(next_node, subscriptable_nodes)) else: if bool(ctx.subscript()): @@ -81,7 +84,8 @@ def exitSubscript(self, ctx: JSONPathParser.SubscriptContext): if bool(ctx.subscriptables()): subscriptable_nodes = self._stack.pop() else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover self._stack.append(SubscriptNode(next_node, subscriptable_nodes)) # pylint: enable=too-many-branches @@ -99,7 +103,8 @@ def exitSubscriptableBareword(self, ctx: JSONPathParser.SubscriptableBarewordCon elif ctx.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) is not None: self._stack.append(WildcardSubscript()) else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover def exitSubscriptable(self, ctx: JSONPathParser.SubscriptableContext): if bool(ctx.STRING()): @@ -131,7 +136,8 @@ def exitSubscriptable(self, ctx: JSONPathParser.SubscriptableContext): self._stack.append(FilterSubscript(expression)) else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover def exitSliceable(self, ctx: JSONPathParser.SliceableContext): end = int(ctx.NUMBER(0).getText()) if bool( @@ -161,7 +167,8 @@ def exitAndExpression(self, ctx: JSONPathParser.AndExpressionContext): expressions.insert(0, expression) if not expressions: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover if len(expressions) == 1: self._stack.append(expressions[0]) else: @@ -186,7 +193,8 @@ def exitOrExpression(self, ctx: JSONPathParser.OrExpressionContext): expressions.insert(0, expression) if not expressions: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover if len(expressions) == 1: self._stack.append(expressions[0]) else: @@ -216,7 +224,8 @@ def exitNotExpression(self, ctx: JSONPathParser.NotExpressionContext): elif ctx.getToken(JSONPathParser.CURRENT_VALUE, 0) is not None: left_node = CurrentNode(next_node) else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover if ctx.getToken(JSONPathParser.EQ, 0) is not None: self._stack.append( @@ -237,7 +246,8 @@ def exitNotExpression(self, ctx: JSONPathParser.NotExpressionContext): self._stack.append( GreaterThanOrEqualToBinaryOperatorExpression(left_node, right_value)) else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover else: if bool(ctx.subscript()): next_node = self._stack.pop() @@ -299,7 +309,8 @@ def exitValue(self, ctx: JSONPathParser.ValueContext): elif ctx.getToken(JSONPathParser.NULL, 0) is not None: self._stack.append(None) else: - raise ValueError() + # NOTE Unreachable when listener is used as tree walker. + raise ValueError() # pragma: no cover class _JSONPathParser(JSONPathParser): From c13bf7b1f2cb27c54047f374815c40a733b803c5 Mon Sep 17 00:00:00 2001 From: Mark Borkum Date: Wed, 19 Sep 2018 08:53:29 -0700 Subject: [PATCH 22/23] More coverage tests --- jsonpath2/test/expression_test.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/jsonpath2/test/expression_test.py b/jsonpath2/test/expression_test.py index b1445b2..2406ddc 100644 --- a/jsonpath2/test/expression_test.py +++ b/jsonpath2/test/expression_test.py @@ -28,8 +28,11 @@ def test_failure_expressions(self): '$[?(@.hello >= "string")]', '$[?(@.hello = {"bar": "baz"})]', '$[?(@.hello = ["bar", "baz"])]', + '$[?(@.hello = 42)]', + '$[?(@.hello = 3.14159)]', '$[?(@.hello = false)]', - '$[?(@.hello = true)]' + '$[?(@.hello = true)]', + '$[?(@.hello = null)]' ]: print(path) expr = Path.parse_str(path) @@ -66,3 +69,16 @@ def test_variadic_operator(self): expr = AndVariadicOperatorExpression( [SomeExpression(CurrentNode(TerminalNode()))]) self.assertEqual('@', expr.tojsonpath()) + + def test_binary_operator(self): + """Test a binary operator.""" + expr = Path.parse_str('$[?(@ and (@ and @))]') + self.assertEqual('$[?(@ and @ and @)]', str(expr)) + expr = Path.parse_str('$[?(@ or (@ or @))]') + self.assertEqual('$[?(@ or @ or @)]', str(expr)) + expr = Path.parse_str('$[?(not not @)]') + self.assertEqual('$[?(@)]', str(expr)) + expr = Path.parse_str('$[?($ = null)]') + self.assertEqual('$[?($ = null)]', str(expr)) + with self.assertRaises(ValueError): + Path.parse_str('$[3.14156:]') From 6550b4f55f5249aba7319d15264d7a47d9e9da2a Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 19 Sep 2018 08:59:40 -0700 Subject: [PATCH 23/23] try getting pre-commit and testing right --- .travis.yml | 17 +++++++++-------- jsonpath2/parser/__init__.py | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3094c4e..af726a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,24 @@ language: python -python: -- 3.6 +python: 3.6 stages: - lint - test - deploy install: pip install -r requirements-dev.txt -script: -- coverage run --include='jsonpath2/*' --omit='jsonpath2/parser/JSONPath*' -m pytest -v -- coverage report -m --fail-under 100 -- pip install . -- python setup.py bdist_wheel -- python setup.py sdist jobs: include: - stage: lint python: 3.6 script: pre-commit run -a + - stage: test + python: 3.6 + script: + - coverage run --include='jsonpath2/*' --omit='jsonpath2/parser/JSONPath*' -m pytest -v + - coverage report -m --fail-under 100 + - pip install . + - python setup.py bdist_wheel + - python setup.py sdist - stage: deploy python: 3.6 script: skip diff --git a/jsonpath2/parser/__init__.py b/jsonpath2/parser/__init__.py index 327c7bb..8c592c1 100644 --- a/jsonpath2/parser/__init__.py +++ b/jsonpath2/parser/__init__.py @@ -46,7 +46,7 @@ def exitJsonpath(self, ctx: JSONPathParser.JsonpathContext): self._stack.append(RootNode(next_node)) else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover # pylint: disable=too-many-branches # It would sure be nice if we had a case statement. @@ -62,7 +62,7 @@ def exitSubscript(self, ctx: JSONPathParser.SubscriptContext): subscriptable_nodes = self._stack.pop() else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover self._stack.append(RecursiveDescentNode( SubscriptNode(next_node, subscriptable_nodes))) elif ctx.getToken(JSONPathParser.SUBSCRIPT, 0) is not None: @@ -74,7 +74,7 @@ def exitSubscript(self, ctx: JSONPathParser.SubscriptContext): subscriptable_nodes = [self._stack.pop()] else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover self._stack.append(SubscriptNode(next_node, subscriptable_nodes)) else: if bool(ctx.subscript()): @@ -85,7 +85,7 @@ def exitSubscript(self, ctx: JSONPathParser.SubscriptContext): subscriptable_nodes = self._stack.pop() else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover self._stack.append(SubscriptNode(next_node, subscriptable_nodes)) # pylint: enable=too-many-branches @@ -104,7 +104,7 @@ def exitSubscriptableBareword(self, ctx: JSONPathParser.SubscriptableBarewordCon self._stack.append(WildcardSubscript()) else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover def exitSubscriptable(self, ctx: JSONPathParser.SubscriptableContext): if bool(ctx.STRING()): @@ -137,7 +137,7 @@ def exitSubscriptable(self, ctx: JSONPathParser.SubscriptableContext): self._stack.append(FilterSubscript(expression)) else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover def exitSliceable(self, ctx: JSONPathParser.SliceableContext): end = int(ctx.NUMBER(0).getText()) if bool( @@ -168,7 +168,7 @@ def exitAndExpression(self, ctx: JSONPathParser.AndExpressionContext): if not expressions: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover if len(expressions) == 1: self._stack.append(expressions[0]) else: @@ -194,7 +194,7 @@ def exitOrExpression(self, ctx: JSONPathParser.OrExpressionContext): if not expressions: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover if len(expressions) == 1: self._stack.append(expressions[0]) else: @@ -225,7 +225,7 @@ def exitNotExpression(self, ctx: JSONPathParser.NotExpressionContext): left_node = CurrentNode(next_node) else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover if ctx.getToken(JSONPathParser.EQ, 0) is not None: self._stack.append( @@ -247,7 +247,7 @@ def exitNotExpression(self, ctx: JSONPathParser.NotExpressionContext): GreaterThanOrEqualToBinaryOperatorExpression(left_node, right_value)) else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover else: if bool(ctx.subscript()): next_node = self._stack.pop() @@ -310,7 +310,7 @@ def exitValue(self, ctx: JSONPathParser.ValueContext): self._stack.append(None) else: # NOTE Unreachable when listener is used as tree walker. - raise ValueError() # pragma: no cover + raise ValueError() # pragma: no cover class _JSONPathParser(JSONPathParser):