Skip to content

Commit 060c372

Browse files
committed
Fix pasing of regex literals
1 parent cba4fce commit 060c372

File tree

7 files changed

+63
-4
lines changed

7 files changed

+63
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Python JSONPath Change Log
22

3+
## Version 2.0.2 (unreleased)
4+
5+
**Fixes**
6+
7+
- Fixed parsing of non-standard JSONPath regular expression literals containing an escaped solidus (`/`). This affected queries using the regex operator `=~`, like `$.some[?(@.thing =~ /fo\/[a-z]/)]`, not standard `match` and `search` functions. See [#124](https://github.com/jg-rp/python-jsonpath/issues/124).
8+
39
## Version 2.0.1
410

511
**Fixes**

docs/syntax.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,19 @@ list-literal = "[" S literal *(S "," S literal) S "]"
616616
$..products[?(@.description =~ /.*trainers/i)]
617617
```
618618

619+
You can escape a solidus (`/`) with a reverse solidus (`\`).
620+
621+
```
622+
$.some[?(@.thing =~ /fo\/[a-z]/)]
623+
```
624+
625+
As a Python string literal, you'd need to double escape the reverse solidus or use a raw string literal.
626+
627+
```python
628+
query = r"$.some[?(@.thing =~ /fo\/[a-z]/)]"
629+
query = "$.some[?(@.thing =~ /fo\\/[a-z]/)]"
630+
```
631+
619632
### Union and intersection operators
620633

621634
The union or concatenation operator, `|`, combines matches from two or more paths.

jsonpath/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>
22
#
33
# SPDX-License-Identifier: MIT
4-
__version__ = "2.0.1"
4+
__version__ = "2.0.2"

jsonpath/lex.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def __init__(self, *, env: JSONPathEnvironment) -> None:
113113
)
114114

115115
# /pattern/ or /pattern/flags
116-
self.re_pattern = r"/(?P<G_RE>.+?)/(?P<G_RE_FLAGS>[aims]*)"
116+
self.re_pattern = r"/(?P<G_RE>(?:(?!(?<!\\)/).)*)/(?P<G_RE_FLAGS>[aims]*)"
117117

118118
# func(
119119
self.function_pattern = r"(?P<G_FUNC>[a-z][a-z_0-9]+)(?P<G_FUNC_PAREN>\()"

tests/cts

tests/regex_operator.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,36 @@
2525
"tags": ["extra"]
2626
},
2727
{
28-
"name": "regex literal, escaped slash",
28+
"name": "regex literal, escaped backslash",
2929
"selector": "$.some[?(@.thing =~ /fo\\\\[a-z]/)]",
3030
"document": { "some": [{ "thing": "fo\\b" }] },
3131
"result": [{ "thing": "fo\\b" }],
3232
"result_paths": ["$['some'][0]"],
3333
"tags": ["extra"]
34+
},
35+
{
36+
"name": "regex literal, escaped slash",
37+
"selector": "$.some[?(@.thing =~ /fo\\/[a-z]/)]",
38+
"document": { "some": [{ "thing": "fo/b" }] },
39+
"result": [{ "thing": "fo/b" }],
40+
"result_paths": ["$['some'][0]"],
41+
"tags": ["extra"]
42+
},
43+
{
44+
"name": "regex literal, escaped asterisk",
45+
"selector": "$.some[?(@.thing =~ /fo\\*[a-z]/)]",
46+
"document": { "some": [{ "thing": "fo*b" }] },
47+
"result": [{ "thing": "fo*b" }],
48+
"result_paths": ["$['some'][0]"],
49+
"tags": ["extra"]
50+
},
51+
{
52+
"name": "regex literal, escaped dot",
53+
"selector": "$.some[?(@.thing =~ /fo\\.[a-z]/)]",
54+
"document": { "some": [{ "thing": "fo.b" }] },
55+
"result": [{ "thing": "fo.b" }],
56+
"result_paths": ["$['some'][0]"],
57+
"tags": ["extra"]
3458
}
3559
]
3660
}

tests/test_issues.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,19 @@ def test_issue_117() -> None:
115115
data = {"foo": ["bar", "baz"]}
116116
with pytest.raises(JSONPatchError):
117117
patch.apply(data)
118+
119+
120+
def test_issue_124() -> None:
121+
query_raw = r"$[?@type =~ /studio\/material\/.*/]"
122+
query = "$[?@type =~ /studio\\/material\\/.*/]"
123+
124+
data = [
125+
{"type": "studio/material/a"},
126+
{"type": "studio/material/b"},
127+
{"type": "studio foo"},
128+
]
129+
130+
want = [{"type": "studio/material/a"}, {"type": "studio/material/b"}]
131+
132+
assert findall(query, data) == want
133+
assert findall(query_raw, data) == want

0 commit comments

Comments
 (0)