Skip to main content

shifty_parse/
path.rs

1//! Parse a `sh:path` value into the [`Path`] algebra (gap-analysis **P1** for the
2//! `oneOrMore`/`zeroOrOne` sugar).
3
4use crate::graph::{Loaded, term_to_node};
5use crate::vocab;
6use oxrdf::Term;
7use shifty_algebra::Path;
8
9/// Parse a SHACL path term. Returns an error string for malformed paths.
10pub fn parse_path(g: &Loaded, term: &Term) -> Result<Path, String> {
11    match term {
12        Term::NamedNode(n) => Ok(Path::Pred(n.clone())),
13        Term::Literal(_) => Err("sh:path value is a literal".to_string()),
14        Term::BlankNode(_) => {
15            let node = term_to_node(term).expect("blank node is a node");
16
17            // Prefer a complete sequence path if a malformed node also carries
18            // another path predicate.
19            if g.object(&node, vocab::RDF_FIRST).is_some() {
20                let members = g.read_list(term);
21                let parts = members
22                    .iter()
23                    .map(|m| parse_path(g, m))
24                    .collect::<Result<Vec<_>, _>>()?;
25                return Ok(Path::seq(parts));
26            }
27            if let Some(x) = g.object(&node, vocab::SH_INVERSE_PATH) {
28                return Ok(parse_path(g, &x)?.inverse());
29            }
30            if let Some(list) = g.object(&node, vocab::SH_ALTERNATIVE_PATH) {
31                let members = g.read_list(&list);
32                let parts = members
33                    .iter()
34                    .map(|m| parse_path(g, m))
35                    .collect::<Result<Vec<_>, _>>()?;
36                return Ok(Path::alt(parts));
37            }
38            if let Some(x) = g.object(&node, vocab::SH_ZERO_OR_MORE_PATH) {
39                return Ok(parse_path(g, &x)?.star());
40            }
41            if let Some(x) = g.object(&node, vocab::SH_ONE_OR_MORE_PATH) {
42                return Ok(parse_path(g, &x)?.one_or_more());
43            }
44            if let Some(x) = g.object(&node, vocab::SH_ZERO_OR_ONE_PATH) {
45                return Ok(parse_path(g, &x)?.zero_or_one());
46            }
47            Err("unrecognized blank-node path expression".to_string())
48        }
49    }
50}