From 97dfab5df925a6bd5b32a08aeee52848f31a2396 Mon Sep 17 00:00:00 2001 From: f3ath Date: Wed, 29 Jul 2020 20:54:00 -0700 Subject: [PATCH] Bracket field notation support --- CHANGELOG.md | 3 +++ README.md | 2 +- lib/src/quote.dart | 6 ++++-- lib/src/tokenize.dart | 43 ++++++++++++++++++++++++---------------- test/json_path_test.dart | 28 +++++++++++++++++++++----- 5 files changed, 57 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c69be3..07f1bb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ ## [Unreleased] +### Added +- Bracket field notation support + ## [0.0.0+dev.3] - 2020-07-28 ### Added - Partial implementation of bracket field notation diff --git a/README.md b/README.md index 758a9ee..44430f9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ - [x] Basic selectors: fields, indices - [x] Recursive descent (`$..`) - [x] Wildcard (`$.store.*`) -- [ ] Square-bracket field notation (`['foo']`, `$['"some" \'special\' [chars]']`) +- [x] Square-bracket field notation (`['foo']`, `$['"some" \'special\' [chars]']`) - [ ] Subscript (`books[:2]`) - [ ] Slice (`articles[1:10:2]`) - [ ] Union (`book[0, 1]`, `book[author, title, price]`) diff --git a/lib/src/quote.dart b/lib/src/quote.dart index 956c94c..e74ca1d 100644 --- a/lib/src/quote.dart +++ b/lib/src/quote.dart @@ -1,9 +1,11 @@ /// A single-quoted string. /// Example: /// `hello` => `'hello'` -/// `i'm ok` => `'i\'m ok'` +/// `i'm \ok` => `'i\'m \\ok'` class Quote { - Quote(String value) : value = "'${value.replaceAll("'", r"\'")}'"; + Quote(String value) + : value = + "'" + value.replaceAll(r'\', r'\\').replaceAll("'", r"\'") + "'"; final String value; diff --git a/lib/src/tokenize.dart b/lib/src/tokenize.dart index 7b098f8..a6967c9 100644 --- a/lib/src/tokenize.dart +++ b/lib/src/tokenize.dart @@ -2,26 +2,45 @@ List tokenize(String expr) { final tokens = []; var pos = 0; + var insideQuotedString = false; while (pos < expr.length) { var token = ''; while (pos < expr.length) { - if (_singles.contains(expr[pos])) { + final char = expr[pos]; + if (char == "'") { + insideQuotedString = !insideQuotedString; + } + if (insideQuotedString) { + if (char == r'\') { + if (expr.length <= pos + 1) throw FormatException('Dangling escape'); + token += expr[pos + 1]; + pos += 2; + } else { + token += char; + pos += 1; + } + continue; + } + if (_singles.contains(char)) { if (token.isNotEmpty) { break; } if (expr.length > pos + 1) { - final nextTwoChars = expr[pos] + expr[pos + 1]; + final nextTwoChars = char + expr[pos + 1]; if (_doubles.contains(nextTwoChars)) { token = nextTwoChars; pos += 2; break; } } - token = expr[pos++]; + token = char; + pos += 1; break; } - token += expr[pos++]; + token += char; + pos += 1; } + if (insideQuotedString) throw FormatException('Unmatched quote'); tokens.add(token); } return tokens; @@ -31,20 +50,10 @@ const _singles = [ r'$', '[', ']', - '(', - ')', - '?', '.', '*', - '@', - '<', - '>', - '-', - '=', - '&', - '|', - ':', - ' ' ]; -const _doubles = ['..', '&&', '||']; +const _doubles = [ + '..', +]; diff --git a/test/json_path_test.dart b/test/json_path_test.dart index fd52f48..9a477bf 100644 --- a/test/json_path_test.dart +++ b/test/json_path_test.dart @@ -38,11 +38,29 @@ void main() { test('Mixed brackets and fields', () { final store = JsonPath(r"$['store'].bicycle['price']"); expect(store.toString(), r"$['store']['bicycle']['price']"); - expect(store.select(json).single.value, json['store']['bicycle']['price']); + expect( + store.select(json).single.value, json['store']['bicycle']['price']); expect(store.select(json).single.path, r"$['store']['bicycle']['price']"); }); }); + group('Invalid format', () { + test('Unmatched quote', () { + expect(() => JsonPath(r"$['hello"), throwsFormatException); + expect(() => JsonPath(r"$['hello\"), throwsFormatException); + }); + }); + + group('Uncommon brackets', () { + test('Escape single quote', () { + final j = {r"sq'sq s\s qs\'qs": 'value'}; + final path = JsonPath(r"$['sq\'sq s\\s qs\\\'qs']"); + final select = path.select(j); + expect(select.single.value, 'value'); + expect(select.single.path, r"$['sq\'sq s\\s qs\\\'qs']"); + }); + }); + group('Wildcards', () { test('All in root', () { final allInRoot = JsonPath(r'$.*'); @@ -87,10 +105,10 @@ void main() { final path = JsonPath(r'$..price'); expect(path.toString(), r"$..['price']"); expect(path.select(json).length, 5); -// expect(path.select(json).first.value, json['store']); -// expect(path.select(json).first.path, r"$['store']"); -// expect(path.select(json).last.value, json['store']['bicycle']['price']); -// expect(path.select(json).last.path, r"$['store']['bicycle']['price']"); + expect(path.select(json).first.value, json['store']['book'][0]['price']); + expect(path.select(json).first.path, r"$['store']['book'][0]['price']"); + expect(path.select(json).last.value, json['store']['bicycle']['price']); + expect(path.select(json).last.path, r"$['store']['bicycle']['price']"); }); });