Skip to content
This repository was archived by the owner on Feb 22, 2024. It is now read-only.

Commit ce59cbd

Browse files
authored
Merge pull request #33 from jsonpath-standard/descendant-selectors
Descendant selectors
2 parents 4a893e1 + 22d0c03 commit ce59cbd

File tree

5 files changed

+62
-5
lines changed

5 files changed

+62
-5
lines changed

Diff for: jsonpath-compliance-test-suite

Diff for: src/ast.rs

+31
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ pub enum Selector {
5454
Union(Vec<UnionElement>),
5555
DotName(String),
5656
DotWildcard,
57+
DescendantDotName(String),
58+
DescendantDotWildcard,
59+
DescendantUnion(Vec<UnionElement>),
5760
}
5861

5962
#[derive(Debug)]
@@ -85,6 +88,34 @@ impl Selector {
8588
Value::Array(a) => Box::new(a.iter()),
8689
_ => Box::new(std::iter::empty()),
8790
},
91+
Selector::DescendantDotName(name) => {
92+
Self::traverse(input, move |n: &'a Value| Box::new(n.get(name).into_iter()))
93+
}
94+
Selector::DescendantDotWildcard => {
95+
Self::traverse(input, move |n: &'a Value| Box::new(iter::once(n)))
96+
}
97+
Selector::DescendantUnion(indices) => Self::traverse(input, move |n: &'a Value| {
98+
Box::new(indices.iter().flat_map(move |i| i.find(n)))
99+
}),
100+
}
101+
}
102+
103+
// traverse applies the given closure to all the descendants of the input value and
104+
// returns a nodelist.
105+
fn traverse<'a, F>(input: &'a Value, f: F) -> NodeList<'a>
106+
where
107+
F: Fn(&'a Value) -> NodeList<'a> + Copy + 'a,
108+
{
109+
match input {
110+
Value::Object(m) => Box::new(
111+
m.into_iter()
112+
.flat_map(move |(_k, v)| f(v).chain(Self::traverse::<'a>(v, f))),
113+
),
114+
Value::Array(a) => Box::new(
115+
a.iter()
116+
.flat_map(move |v| f(v).chain(Self::traverse::<'a>(v, f))),
117+
),
118+
_ => Box::new(std::iter::empty()),
88119
}
89120
}
90121
}

Diff for: src/grammar.pest

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ selector = ${ SOI ~ rootSelector ~ matchers ~ EOI }
33
matchers = ${ matcher* }
44
rootSelector = @{ "$" }
55

6-
matcher = !{ dotChild | union }
6+
matcher = !{ dotChild | union | wildcardedIndex | descendant }
77

88
dotChild = _{ wildcardedDotChild | namedDotChild }
99
wildcardedDotChild = { ".*" }
@@ -17,7 +17,7 @@ char = {
1717
| '\u{80}'..'\u{10FFFF}'
1818
}
1919

20-
union = { "[" ~ unionElement ~ ("," ~ unionElement)* ~ "]" }
20+
union = !{ "[" ~ unionElement ~ ("," ~ unionElement)* ~ "]" }
2121
unionElement = _{ unionChild | unionArraySlice | unionArrayIndex }
2222
unionChild = ${ doubleQuotedString | singleQuotedString }
2323
unionArrayIndex = @{ integer }
@@ -27,6 +27,12 @@ sliceStart = @{ integer }
2727
sliceEnd = @{ integer }
2828
sliceStep = @{ integer }
2929

30+
wildcardedIndex = { "[" ~ "*" ~ "]" }
31+
32+
descendant = ${ ".." ~ descendantVariant }
33+
descendantVariant = _{ childName | wildcard | "[" ~ wildcard ~ "]" | union }
34+
wildcard = { "*" }
35+
3036
doubleQuotedString = _{ "\"" ~ doubleInner ~ "\"" }
3137
doubleInner = @{ doubleChar* }
3238
doubleChar = {

Diff for: src/parser.rs

+12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ fn parse_selector(matcher_rule: pest::iterators::Pair<Rule>) -> Result<Selector,
4040
Rule::wildcardedDotChild => Selector::DotWildcard,
4141
Rule::namedDotChild => Selector::DotName(parse_child_name(r)),
4242
Rule::union => Selector::Union(parse_union_indices(r)?),
43+
Rule::wildcardedIndex => Selector::DotWildcard,
44+
Rule::descendant => parse_descendant(r)?,
4345
_ => panic!("invalid parse tree {:?}", r),
4446
})
4547
}
@@ -115,6 +117,16 @@ fn parse_union_array_slice(
115117
}))
116118
}
117119

120+
fn parse_descendant(matcher_rule: pest::iterators::Pair<Rule>) -> Result<Selector, ParseIntError> {
121+
let r = matcher_rule.into_inner().next().unwrap();
122+
123+
Ok(match r.as_rule() {
124+
Rule::childName => Selector::DescendantDotName(r.as_str().to_owned()),
125+
Rule::wildcard => Selector::DescendantDotWildcard,
126+
_ => Selector::DescendantUnion(parse_union_indices(r)?),
127+
})
128+
}
129+
118130
fn unescape(contents: &str) -> String {
119131
let s = format!(r#""{}""#, contents);
120132
serde_json::from_str(&s).unwrap()

Diff for: tests/cts.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,18 @@ mod tests {
9090
assert!(false, "find failed") // should not happen
9191
}
9292
} else {
93-
if !t.invalid_selector {
93+
if t.invalid_selector {
94+
// print failure message
95+
println!(
96+
"{}: parsing `{}` failed with: {}",
97+
t.name,
98+
t.selector,
99+
path.err().expect("should be an error")
100+
);
101+
} else {
94102
assert!(
95103
path.is_ok(),
96-
"{}: parsing {} should have succeeded but failed: {}",
104+
"{}: parsing `{}` should have succeeded but failed: {}",
97105
t.name,
98106
t.selector,
99107
path.err().expect("should be an error")

0 commit comments

Comments
 (0)