1use crate::diagnostics::{DiagLevel, Diagnostic};
10use crate::graph::{Loaded, term_to_node};
11use crate::path::parse_path;
12use crate::vocab;
13use oxrdf::{Literal, NamedNode, NamedOrBlankNode, Term};
14use shifty_algebra::{
15 Bound, NodeExpr, NodeKindSet, Path, Rule, RuleHead, Schema, Selector, Severity, Shape,
16 ShapeArena, ShapeId, SparqlConstraint, SparqlConstruct, SparqlQueryKind, SparqlTarget,
17 Statement, ValueType,
18};
19use spargebra::{Query, SparqlParser};
20use std::collections::{BTreeSet, HashMap, HashSet};
21
22pub struct Lowered {
23 pub schema: Schema,
24 pub diagnostics: Vec<Diagnostic>,
25}
26
27pub fn lower(g: &Loaded) -> Lowered {
29 let mut l = Lowerer {
30 g,
31 arena: ShapeArena::new(),
32 cache: HashMap::new(),
33 statements: Vec::new(),
34 rules: Vec::new(),
35 diags: Vec::new(),
36 };
37 let shapes = l.discover_shapes();
38 for s in &shapes {
39 l.lower_shape(s);
40 }
41 l.lower_custom_components(&shapes);
42 for s in &shapes {
43 let selectors = l.target_selectors(s);
45 if let Some(shape) = l.cache.get(s).copied() {
46 for sel in &selectors {
47 l.statements.push(Statement {
48 selector: sel.clone(),
49 shape,
50 });
51 }
52 }
53 l.parse_rules(s, &selectors);
54 }
55 let names = l
56 .cache
57 .iter()
58 .filter_map(|(node, id)| match node {
59 NamedOrBlankNode::NamedNode(n) => Some((*id, n.as_str().to_string())),
60 NamedOrBlankNode::BlankNode(_) => None,
61 })
62 .collect();
63 let schema = Schema {
64 arena: l.arena,
65 statements: l.statements,
66 rules: l.rules,
67 names,
68 };
69 schema.arena.debug_assert_finalized();
70 Lowered {
71 schema,
72 diagnostics: l.diags,
73 }
74}
75
76struct Lowerer<'a> {
77 g: &'a Loaded,
78 arena: ShapeArena,
79 cache: HashMap<NamedOrBlankNode, ShapeId>,
80 statements: Vec<Statement>,
81 rules: Vec<Rule>,
82 diags: Vec<Diagnostic>,
83}
84
85struct ComponentDef {
86 iri: NamedNode,
87 params: Vec<ParamDef>,
88 node_validator: Option<ValidatorDef>,
89 property_validator: Option<ValidatorDef>,
90 generic_validator: Option<ValidatorDef>,
91}
92
93struct ParamDef {
94 path: NamedNode,
95 var: String,
96 optional: bool,
97}
98
99struct ValidatorDef {
100 kind: SparqlQueryKind,
101 query: String,
102 messages: Vec<Term>,
103}
104
105fn local_name(iri: &str) -> &str {
108 iri.rsplit(['#', '/']).next().unwrap_or(iri)
109}
110
111impl Lowerer<'_> {
112 fn diag(&mut self, level: DiagLevel, msg: impl Into<String>, subj: &NamedOrBlankNode) {
113 self.diags
114 .push(Diagnostic::new(level, msg, Some(subj.to_string())));
115 }
116
117 fn discover_shapes(&self) -> Vec<NamedOrBlankNode> {
121 let mut found: HashSet<NamedOrBlankNode> = HashSet::new();
122 for triple in self.g.graph.iter() {
123 let p = triple.predicate;
124 let is_target = p == vocab::SH_TARGET_NODE
125 || p == vocab::SH_TARGET_CLASS
126 || p == vocab::SH_TARGET_SUBJECTS_OF
127 || p == vocab::SH_TARGET_OBJECTS_OF
128 || p == vocab::SH_TARGET;
129 if p == vocab::SH_PATH || p == vocab::SH_SPARQL || p == vocab::SH_RULE || is_target {
130 found.insert(triple.subject.into_owned());
131 }
132 if p == vocab::RDF_TYPE
133 && let Term::NamedNode(ty) = triple.object.into_owned()
134 && (ty.as_ref() == vocab::SH_NODE_SHAPE || ty.as_ref() == vocab::SH_PROPERTY_SHAPE)
135 {
136 found.insert(triple.subject.into_owned());
137 }
138 }
139 let mut shapes: Vec<NamedOrBlankNode> = found.into_iter().collect();
140 shapes.sort_by_key(|n| n.to_string());
141 shapes
142 }
143
144 fn lower_shape(&mut self, s: &NamedOrBlankNode) -> ShapeId {
145 if let Some(id) = self.cache.get(s) {
146 return *id;
147 }
148 let id = self.arena.reserve();
149 self.cache.insert(s.clone(), id);
150
151 if self.bool_prop(s, vocab::SH_DEACTIVATED) {
152 self.arena.set(id, Shape::Top);
153 return id;
154 }
155
156 let path = self.parse_shape_path(s);
157 let mut conjuncts: Vec<ShapeId> = Vec::new();
158
159 let value = self.collect_value_constraints(s);
162 if !value.is_empty() {
163 let value_phi = self.arena.and(value);
164 match &path {
165 Some(p) => {
166 let neg = self.arena.not(value_phi);
168 let c = self.arena.count(p.clone(), None, Some(0), neg);
169 conjuncts.push(c);
170 }
171 None => conjuncts.push(value_phi),
172 }
173 }
174
175 self.collect_path_constraints(s, path.as_ref(), &mut conjuncts);
176
177 if self.bool_prop(s, vocab::SH_CLOSED) {
178 let q = self.closed_allowed(s);
179 let c = self.arena.insert(Shape::Closed(q));
180 conjuncts.push(c);
181 }
182
183 for constraint_term in self.g.objects(s, vocab::SH_SPARQL) {
184 let Some(constraint_node) = term_to_node(&constraint_term) else {
185 self.diag(DiagLevel::Error, "sh:sparql must reference a resource", s);
186 continue;
187 };
188 let parsed = if let Some(Term::Literal(query)) =
189 self.g.object(&constraint_node, vocab::SH_SELECT)
190 {
191 self.canonical_sparql(&constraint_node, query.value(), ExpectedQuery::Select)
192 .map(|query| (SparqlQueryKind::Select, query))
193 } else if let Some(Term::Literal(query)) =
194 self.g.object(&constraint_node, vocab::SH_ASK)
195 {
196 self.canonical_sparql(&constraint_node, query.value(), ExpectedQuery::Ask)
197 .map(|query| (SparqlQueryKind::Ask, query))
198 } else {
199 self.diag(
200 DiagLevel::Error,
201 "sh:sparql constraint requires sh:select or sh:ask",
202 &constraint_node,
203 );
204 None
205 };
206 if let Some((kind, query)) = parsed {
207 let shape = Some(match s {
208 NamedOrBlankNode::NamedNode(n) => Term::NamedNode(n.clone()),
209 NamedOrBlankNode::BlankNode(b) => Term::BlankNode(b.clone()),
210 });
211 let mut messages: Vec<Term> = self.g.objects(&constraint_node, vocab::SH_MESSAGE);
214 if messages.is_empty() {
215 messages = self.g.objects(s, vocab::SH_MESSAGE);
216 }
217 let constraint = SparqlConstraint {
218 kind,
219 query,
220 path: path.clone(),
221 shape,
222 messages,
223 extra_bindings: Vec::new(),
224 bind_value_to_this: false,
225 };
226 conjuncts.push(self.arena.insert(Shape::Sparql(constraint)));
227 }
228 }
229
230 for expr_term in self.g.objects(s, vocab::SH_EXPRESSION) {
234 if let Some(expr) = self.parse_node_expr(expr_term, s) {
235 if node_expr_has_function(&expr) {
236 self.diag(
240 DiagLevel::Unsupported,
241 "sh:expression with a function call is not yet evaluated",
242 s,
243 );
244 } else {
245 conjuncts.push(self.arena.insert(Shape::Expression(expr)));
246 }
247 }
248 }
249
250 let body = if conjuncts.is_empty() {
251 self.arena.top()
252 } else if conjuncts.len() == 1 {
253 if conjuncts[0] == id {
254 self.arena.top()
255 } else {
256 conjuncts[0]
257 }
258 } else {
259 self.arena.insert(Shape::And(conjuncts))
260 };
261 self.arena.set(
262 id,
263 Shape::Annotated {
264 severity: self.severity(s),
265 shape: body,
266 },
267 );
268 id
269 }
270
271 fn severity(&self, shape: &NamedOrBlankNode) -> Severity {
272 match self.g.object(shape, vocab::SH_SEVERITY) {
273 Some(Term::NamedNode(value)) => Severity::from_named_node(value),
274 _ => Severity::Violation,
275 }
276 }
277
278 fn collect_value_constraints(&mut self, s: &NamedOrBlankNode) -> Vec<ShapeId> {
279 let mut value: Vec<ShapeId> = Vec::new();
280
281 for c in self.g.objects(s, vocab::SH_CLASS) {
283 let tn = self.arena.insert(Shape::TestConst(c));
284 let cc = self.arena.count(class_path(), Some(1), None, tn);
285 value.push(cc);
286 }
287
288 for d in self.g.objects(s, vocab::SH_DATATYPE) {
290 if let Term::NamedNode(n) = d {
291 let id = self.arena.insert(Shape::TestType(ValueType::Datatype(n)));
292 value.push(id);
293 }
294 }
295
296 for k in self.g.objects(s, vocab::SH_NODE_KIND) {
298 if let Some(set) = map_node_kind(&k) {
299 let id = self.arena.insert(Shape::TestKind(set));
300 value.push(id);
301 } else {
302 self.diag(DiagLevel::Warning, "unrecognized sh:nodeKind value", s);
303 }
304 }
305
306 let lo = self
308 .lit(s, vocab::SH_MIN_INCLUSIVE)
309 .map(|value| Bound {
310 value,
311 inclusive: true,
312 })
313 .or_else(|| {
314 self.lit(s, vocab::SH_MIN_EXCLUSIVE).map(|value| Bound {
315 value,
316 inclusive: false,
317 })
318 });
319 let hi = self
320 .lit(s, vocab::SH_MAX_INCLUSIVE)
321 .map(|value| Bound {
322 value,
323 inclusive: true,
324 })
325 .or_else(|| {
326 self.lit(s, vocab::SH_MAX_EXCLUSIVE).map(|value| Bound {
327 value,
328 inclusive: false,
329 })
330 });
331 if lo.is_some() || hi.is_some() {
332 let id = self
333 .arena
334 .insert(Shape::TestType(ValueType::NumericRange { lo, hi }));
335 value.push(id);
336 }
337
338 let min_len = self.int(s, vocab::SH_MIN_LENGTH);
340 let max_len = self.int(s, vocab::SH_MAX_LENGTH);
341 if min_len.is_some() || max_len.is_some() {
342 let id = self.arena.insert(Shape::TestType(ValueType::Length {
343 min: min_len,
344 max: max_len,
345 }));
346 value.push(id);
347 }
348
349 let flags = self
351 .lit(s, vocab::SH_FLAGS)
352 .map(|l| l.value().to_string())
353 .unwrap_or_default();
354 for pat in self.g.objects(s, vocab::SH_PATTERN) {
355 if let Term::Literal(l) = pat {
356 let id = self.arena.insert(Shape::TestType(ValueType::Pattern {
357 regex: l.value().to_string(),
358 flags: flags.clone(),
359 }));
360 value.push(id);
361 }
362 }
363
364 for li in self.g.objects(s, vocab::SH_LANGUAGE_IN) {
366 let langs: Vec<String> = self
367 .g
368 .read_list(&li)
369 .into_iter()
370 .filter_map(|m| match m {
371 Term::Literal(l) => Some(l.value().to_string()),
372 _ => None,
373 })
374 .collect();
375 let id = self.arena.insert(Shape::TestType(ValueType::LangIn(langs)));
376 value.push(id);
377 }
378
379 for inl in self.g.objects(s, vocab::SH_IN) {
381 let alts: Vec<ShapeId> = self
382 .g
383 .read_list(&inl)
384 .into_iter()
385 .map(|m| self.arena.insert(Shape::TestConst(m)))
386 .collect();
387 let or = self.arena.or(alts);
388 value.push(or);
389 }
390
391 for n in self.g.objects(s, vocab::SH_NODE) {
393 if let Some(nn) = term_to_node(&n) {
394 let id = self.lower_shape(&nn);
395 value.push(id);
396 }
397 }
398
399 for prop in self.g.objects(s, vocab::SH_PROPERTY) {
403 if let Some(pn) = term_to_node(&prop) {
404 let id = self.lower_shape(&pn);
405 value.push(id);
406 }
407 }
408
409 for n in self.g.objects(s, vocab::SH_NOT) {
411 if let Some(nn) = term_to_node(&n) {
412 let id = self.lower_shape(&nn);
413 let neg = self.arena.not(id);
414 value.push(neg);
415 }
416 }
417
418 for l in self.g.objects(s, vocab::SH_AND) {
420 let ids = self.lower_shape_list(&l);
421 let a = self.arena.and(ids);
422 value.push(a);
423 }
424 for l in self.g.objects(s, vocab::SH_OR) {
425 let ids = self.lower_shape_list(&l);
426 let o = self.arena.or(ids);
427 value.push(o);
428 }
429 for l in self.g.objects(s, vocab::SH_XONE) {
430 let ids = self.lower_shape_list(&l);
431 let x = self.arena.xone(ids);
432 value.push(x);
433 }
434
435 value
436 }
437
438 fn collect_path_constraints(
442 &mut self,
443 s: &NamedOrBlankNode,
444 path: Option<&Path>,
445 conjuncts: &mut Vec<ShapeId>,
446 ) {
447 let need_path = |me: &mut Self, what: &str| {
448 me.diag(DiagLevel::Warning, format!("{what} ignored: no sh:path"), s);
449 };
450
451 let min_count = self.int(s, vocab::SH_MIN_COUNT);
452 let max_count = self.int(s, vocab::SH_MAX_COUNT);
453 if min_count.is_some() || max_count.is_some() {
454 match path {
455 Some(p) => {
456 let top = self.arena.top();
457 let c = self.arena.count(p.clone(), min_count, max_count, top);
458 conjuncts.push(c);
459 }
460 None => need_path(self, "sh:minCount/sh:maxCount"),
461 }
462 }
463
464 for v in self.g.objects(s, vocab::SH_HAS_VALUE) {
466 match path {
467 Some(p) => {
468 let tc = self.arena.insert(Shape::TestConst(v));
469 let c = self.arena.count(p.clone(), Some(1), None, tc);
470 conjuncts.push(c);
471 }
472 None => {
473 let tc = self.arena.insert(Shape::TestConst(v));
474 conjuncts.push(tc);
475 }
476 }
477 }
478
479 for q in self.g.objects(s, vocab::SH_QUALIFIED_VALUE_SHAPE) {
481 if let Some(qn) = term_to_node(&q) {
482 let qmin = self.int(s, vocab::SH_QUALIFIED_MIN_COUNT);
483 let qmax = self.int(s, vocab::SH_QUALIFIED_MAX_COUNT);
484 match path {
485 Some(p) => {
486 let mut qualifiers = vec![self.lower_shape(&qn)];
487 if self.bool_prop(s, vocab::SH_QUALIFIED_VALUE_SHAPES_DISJOINT) {
488 for sibling in self.sibling_qualified_shapes(s, &qn) {
489 let sibling = self.lower_shape(&sibling);
490 qualifiers.push(self.arena.not(sibling));
491 }
492 }
493 let qualifier = self.arena.and(qualifiers);
494 let c = self.arena.count(p.clone(), qmin, qmax, qualifier);
495 conjuncts.push(c);
496 }
497 None => need_path(self, "sh:qualifiedValueShape"),
498 }
499 }
500 }
501
502 let pairs = [
504 (vocab::SH_EQUALS, "equals"),
505 (vocab::SH_DISJOINT, "disjoint"),
506 (vocab::SH_LESS_THAN, "lessThan"),
507 (vocab::SH_LESS_THAN_OR_EQUALS, "lessThanOrEquals"),
508 ];
509 for (pred, name) in pairs {
510 for other in self.g.objects(s, pred) {
511 let Term::NamedNode(op) = other else { continue };
512 match path {
513 Some(p) => {
514 let shape = match name {
515 "equals" => Shape::Eq(p.clone(), op),
516 "disjoint" => Shape::Disj(p.clone(), op),
517 "lessThan" => Shape::Lt(p.clone(), op),
518 _ => Shape::Le(p.clone(), op),
519 };
520 let c = self.arena.insert(shape);
521 conjuncts.push(c);
522 }
523 None if matches!(name, "equals" | "disjoint") => {
524 let shape = if name == "equals" {
525 Shape::Eq(Path::Id, op)
526 } else {
527 Shape::Disj(Path::Id, op)
528 };
529 let c = self.arena.insert(shape);
530 conjuncts.push(c);
531 }
532 None => need_path(self, &format!("sh:{name}")),
533 }
534 }
535 }
536
537 if self.bool_prop(s, vocab::SH_UNIQUE_LANG) {
539 match path {
540 Some(p) => {
541 let c = self.arena.insert(Shape::UniqueLang(p.clone()));
542 conjuncts.push(c);
543 }
544 None => need_path(self, "sh:uniqueLang"),
545 }
546 }
547 }
548
549 fn target_selectors(&mut self, s: &NamedOrBlankNode) -> Vec<Selector> {
551 let mut sels = Vec::new();
552
553 for c in self.g.objects(s, vocab::SH_TARGET_NODE) {
554 sels.push(Selector::IsConst(c));
555 }
556 for c in self.g.objects(s, vocab::SH_TARGET_CLASS) {
557 sels.push(self.class_selector(c));
558 }
559 for p in self.g.objects(s, vocab::SH_TARGET_SUBJECTS_OF) {
560 if let Term::NamedNode(n) = p {
561 sels.push(Selector::HasOut(n));
562 }
563 }
564 for p in self.g.objects(s, vocab::SH_TARGET_OBJECTS_OF) {
565 if let Term::NamedNode(n) = p {
566 sels.push(Selector::HasIn(n));
567 }
568 }
569
570 if (self.g.is_instance_of(s, vocab::RDFS_CLASS)
572 || self.g.is_instance_of(s, vocab::OWL_CLASS))
573 && let NamedOrBlankNode::NamedNode(n) = s
574 {
575 sels.push(self.class_selector(Term::NamedNode(n.clone())));
576 }
577
578 for target_term in self.g.objects(s, vocab::SH_TARGET) {
579 let Some(target_node) = term_to_node(&target_term) else {
580 self.diag(DiagLevel::Error, "sh:target must reference a resource", s);
581 continue;
582 };
583 match self.g.object(&target_node, vocab::SH_SELECT) {
584 Some(Term::Literal(query)) => {
585 if let Some(query) =
586 self.canonical_sparql(&target_node, query.value(), ExpectedQuery::Select)
587 {
588 sels.push(Selector::Sparql(SparqlTarget { query }));
589 }
590 }
591 _ => self.diag(
592 DiagLevel::Unsupported,
593 "custom sh:target without sh:select is not yet lowered",
594 &target_node,
595 ),
596 }
597 }
598
599 sels
600 }
601
602 fn lower_custom_components(&mut self, shapes: &[NamedOrBlankNode]) {
612 let components = self.collect_component_defs();
613 if components.is_empty() {
614 return;
615 }
616 for s in shapes {
617 let Some(&shape_id) = self.cache.get(s) else {
618 continue;
619 };
620 let is_property_shape = self.g.object(s, vocab::SH_PATH).is_some();
621 let path = if is_property_shape {
622 self.parse_shape_path(s)
623 } else {
624 None
625 };
626 let shape_term = match s {
627 NamedOrBlankNode::NamedNode(n) => Some(Term::NamedNode(n.clone())),
628 NamedOrBlankNode::BlankNode(b) => Some(Term::BlankNode(b.clone())),
629 };
630
631 for component in &components {
632 let mut extra_bindings: Vec<(String, Term)> = Vec::new();
634 let mut activated = true;
635 for p in &component.params {
636 if let Some(value) = self.g.object(s, p.path.as_ref()) {
637 extra_bindings.push((p.var.clone(), value));
638 } else if !p.optional {
639 activated = false;
640 break;
641 }
642 }
643 if !activated {
644 continue;
645 }
646
647 let validator = if is_property_shape {
649 component
650 .property_validator
651 .as_ref()
652 .or(component.generic_validator.as_ref())
653 } else {
654 component
655 .node_validator
656 .as_ref()
657 .or(component.generic_validator.as_ref())
658 };
659 let Some(validator) = validator else { continue };
660
661 let constraint = SparqlConstraint {
662 kind: validator.kind,
663 query: validator.query.clone(),
664 path: path.clone(),
665 shape: shape_term.clone(),
666 messages: validator.messages.clone(),
667 extra_bindings,
668 bind_value_to_this: !is_property_shape
671 && validator.kind == SparqlQueryKind::Ask,
672 };
673 let raw_id = self.arena.insert(Shape::Sparql(constraint));
674 let sparql_id = if validator.kind == SparqlQueryKind::Ask {
680 self.arena.not(raw_id)
681 } else {
682 raw_id
683 };
684
685 let (severity, inner) = match self.arena.get(shape_id) {
687 Shape::Annotated {
688 severity,
689 shape: inner,
690 } => (severity.clone(), *inner),
691 _ => continue, };
693 let combined = self.arena.and(vec![inner, sparql_id]);
694 self.arena.set(
695 shape_id,
696 Shape::Annotated {
697 severity,
698 shape: combined,
699 },
700 );
701 }
702 }
703 }
704
705 fn collect_component_defs(&mut self) -> Vec<ComponentDef> {
709 let mut out: Vec<ComponentDef> = Vec::new();
710 let mut seen = HashSet::new();
711 for triple in self.g.graph.triples_for_predicate(vocab::SH_PARAMETER) {
712 let subject = triple.subject.into_owned();
713 if !seen.insert(subject.clone()) {
714 continue;
715 }
716 let NamedOrBlankNode::NamedNode(iri) = &subject else {
717 continue;
718 };
719 let node_validator = self
720 .g
721 .object(&subject, vocab::SH_NODE_VALIDATOR)
722 .and_then(|v| self.parse_validator_def(&v));
723 let property_validator = self
724 .g
725 .object(&subject, vocab::SH_PROPERTY_VALIDATOR)
726 .and_then(|v| self.parse_validator_def(&v));
727 let generic_validator = self
728 .g
729 .object(&subject, vocab::SH_VALIDATOR)
730 .and_then(|v| self.parse_validator_def(&v));
731 if node_validator.is_none()
732 && property_validator.is_none()
733 && generic_validator.is_none()
734 {
735 continue; }
737 let mut params: Vec<ParamDef> = Vec::new();
738 for p in self.g.objects(&subject, vocab::SH_PARAMETER) {
739 let Some(pn) = term_to_node(&p) else { continue };
740 let Some(Term::NamedNode(path)) = self.g.object(&pn, vocab::SH_PATH) else {
741 continue;
742 };
743 let var = local_name(path.as_str()).to_string();
744 let optional = matches!(
745 self.g.object(&pn, vocab::SH_OPTIONAL),
746 Some(Term::Literal(ref l)) if l.value() == "true"
747 );
748 params.push(ParamDef {
749 path,
750 var,
751 optional,
752 });
753 }
754 if params.is_empty() {
755 continue;
756 }
757 out.push(ComponentDef {
758 iri: iri.clone(),
759 params,
760 node_validator,
761 property_validator,
762 generic_validator,
763 });
764 }
765 out.sort_by(|a, b| a.iri.as_str().cmp(b.iri.as_str()));
766 out
767 }
768
769 fn parse_validator_def(&mut self, node: &Term) -> Option<ValidatorDef> {
773 let node = term_to_node(node)?;
774 let (kind, raw) = if let Some(Term::Literal(q)) = self.g.object(&node, vocab::SH_ASK) {
775 (SparqlQueryKind::Ask, q.value().to_string())
776 } else if let Some(Term::Literal(q)) = self.g.object(&node, vocab::SH_SELECT) {
777 (SparqlQueryKind::Select, q.value().to_string())
778 } else {
779 return None; };
781 let canonical = match canonical_sparql_query(self.g, &node, &raw) {
782 Ok((_, c)) => c,
783 Err(msg) => {
784 self.diag(
785 DiagLevel::Error,
786 format!("invalid SPARQL in validator: {msg}"),
787 &node,
788 );
789 return None;
790 }
791 };
792 let messages = self.g.objects(&node, vocab::SH_MESSAGE);
793 Some(ValidatorDef {
794 kind,
795 query: canonical,
796 messages,
797 })
798 }
799
800 fn parse_rules(&mut self, s: &NamedOrBlankNode, selectors: &[Selector]) {
803 for rule_term in self.g.objects(s, vocab::SH_RULE) {
804 let Some(rn) = term_to_node(&rule_term) else {
805 continue;
806 };
807 let Some(head) = self.parse_rule_head(&rn) else {
808 continue;
809 };
810
811 let conditions: Vec<ShapeId> = self
812 .g
813 .objects(&rn, vocab::SH_CONDITION)
814 .iter()
815 .filter_map(term_to_node)
816 .map(|c| self.lower_shape(&c))
817 .collect();
818 let order = self.order(&rn);
819 let deactivated = self.bool_prop(&rn, vocab::SH_DEACTIVATED);
820
821 for sel in selectors {
822 self.rules.push(Rule {
823 selector: sel.clone(),
824 conditions: conditions.clone(),
825 head: head.clone(),
826 order,
827 deactivated,
828 });
829 }
830 }
831 }
832
833 fn sibling_qualified_shapes(
836 &self,
837 shape: &NamedOrBlankNode,
838 qualifier: &NamedOrBlankNode,
839 ) -> Vec<NamedOrBlankNode> {
840 let mut siblings = HashSet::new();
841 for triple in self.g.graph.triples_for_predicate(vocab::SH_PROPERTY) {
842 if term_to_node(&triple.object.into_owned()).as_ref() != Some(shape) {
843 continue;
844 }
845 let parent = triple.subject.into_owned();
846 for property in self.g.objects(&parent, vocab::SH_PROPERTY) {
847 let Some(property) = term_to_node(&property) else {
848 continue;
849 };
850 for sibling in self.g.objects(&property, vocab::SH_QUALIFIED_VALUE_SHAPE) {
851 if let Some(sibling) = term_to_node(&sibling) {
852 siblings.insert(sibling);
853 }
854 }
855 }
856 }
857 siblings.remove(qualifier);
858 let mut siblings: Vec<_> = siblings.into_iter().collect();
859 siblings.sort_by_key(|node| node.to_string());
860 siblings
861 }
862
863 fn parse_rule_head(&mut self, rn: &NamedOrBlankNode) -> Option<RuleHead> {
864 if let Some(Term::Literal(q)) = self.g.object(rn, vocab::SH_CONSTRUCT) {
867 let query = self.canonical_sparql(rn, q.value(), ExpectedQuery::Construct)?;
868 return Some(RuleHead::Sparql(SparqlConstruct { query }));
869 }
870 let (subj, pred, obj) = (
872 self.g.object(rn, vocab::SH_SUBJECT),
873 self.g.object(rn, vocab::SH_PREDICATE),
874 self.g.object(rn, vocab::SH_OBJECT),
875 );
876 if subj.is_none() && pred.is_none() && obj.is_none() {
877 self.diag(DiagLevel::Unsupported, "unrecognized sh:rule head", rn);
878 return None;
879 }
880 let (Some(subj), Some(pred), Some(obj)) = (subj, pred, obj) else {
881 self.diag(
882 DiagLevel::Error,
883 "sh:TripleRule missing subject/predicate/object",
884 rn,
885 );
886 return None;
887 };
888 Some(RuleHead::Triple {
889 subject: self.parse_node_expr(subj, rn)?,
890 predicate: self.parse_node_expr(pred, rn)?,
891 object: self.parse_node_expr(obj, rn)?,
892 })
893 }
894
895 fn parse_node_expr(&mut self, term: Term, owner: &NamedOrBlankNode) -> Option<NodeExpr> {
899 match &term {
900 Term::NamedNode(n) if n.as_ref() == vocab::SH_THIS => Some(NodeExpr::This),
901 Term::NamedNode(_) | Term::Literal(_) => Some(NodeExpr::Constant(term)),
902 Term::BlankNode(_) => {
903 let node = term_to_node(&term).expect("blank node");
904 if let Some(path_term) = self.g.object(&node, vocab::SH_PATH) {
905 match parse_path(self.g, &path_term) {
906 Ok(path) => Some(NodeExpr::Path(path)),
907 Err(e) => {
908 self.diag(
909 DiagLevel::Error,
910 format!("invalid node-expression path: {e}"),
911 owner,
912 );
913 None
914 }
915 }
916 } else if self.g.object(&node, vocab::SH_FILTER_SHAPE).is_some() {
917 self.parse_filter_expr(&node, owner)
918 } else if let Some(list) = self.g.object(&node, vocab::SH_INTERSECTION) {
919 self.parse_set_expr(&list, owner)
920 .map(NodeExpr::Intersection)
921 } else if let Some(list) = self.g.object(&node, vocab::SH_UNION) {
922 self.parse_set_expr(&list, owner).map(NodeExpr::Union)
923 } else if let Some(expr) = self.try_function_call(&node, owner) {
924 Some(expr)
925 } else {
926 self.diag(
927 DiagLevel::Unsupported,
928 "complex node expression not yet lowered",
929 owner,
930 );
931 None
932 }
933 }
934 }
935 }
936
937 fn parse_filter_expr(
940 &mut self,
941 node: &NamedOrBlankNode,
942 owner: &NamedOrBlankNode,
943 ) -> Option<NodeExpr> {
944 let Some(shape_term) = self.g.object(node, vocab::SH_FILTER_SHAPE) else {
945 self.diag(DiagLevel::Error, "sh:filterShape missing a value", owner);
946 return None;
947 };
948 let Some(shape_node) = term_to_node(&shape_term) else {
949 self.diag(
950 DiagLevel::Error,
951 "sh:filterShape must reference a shape",
952 owner,
953 );
954 return None;
955 };
956 let Some(nodes_term) = self.g.object(node, vocab::SH_NODES) else {
957 self.diag(
958 DiagLevel::Error,
959 "filter expression missing sh:nodes",
960 owner,
961 );
962 return None;
963 };
964 let input = self.parse_node_expr(nodes_term, owner)?;
965 let shape = self.lower_shape(&shape_node);
966 Some(NodeExpr::Filter {
967 input: Box::new(input),
968 shape,
969 })
970 }
971
972 fn parse_set_expr(
976 &mut self,
977 list_head: &Term,
978 owner: &NamedOrBlankNode,
979 ) -> Option<Vec<NodeExpr>> {
980 let members = self.g.read_list(list_head);
981 if members.is_empty() {
982 self.diag(
983 DiagLevel::Error,
984 "sh:intersection/sh:union expects a non-empty list",
985 owner,
986 );
987 return None;
988 }
989 let n = members.len();
990 let exprs: Vec<NodeExpr> = members
991 .into_iter()
992 .filter_map(|t| self.parse_node_expr(t, owner))
993 .collect();
994 if exprs.len() != n {
995 return None;
996 }
997 Some(exprs)
998 }
999
1000 fn try_function_call(
1005 &mut self,
1006 node: &NamedOrBlankNode,
1007 owner: &NamedOrBlankNode,
1008 ) -> Option<NodeExpr> {
1009 let func_preds: Vec<(NamedNode, Term)> = self
1010 .g
1011 .graph
1012 .triples_for_subject(node)
1013 .map(|t| (t.predicate.into_owned(), t.object.into_owned()))
1014 .filter(|(p, _)| {
1015 let s = p.as_str();
1016 !s.starts_with(vocab::SH)
1017 && !s.starts_with(vocab::RDF)
1018 && !s.starts_with(vocab::RDFS)
1019 && !s.starts_with(vocab::OWL)
1020 })
1021 .collect();
1022
1023 if func_preds.len() != 1 {
1024 return None;
1025 }
1026 let (func_iri, list_head) = func_preds.into_iter().next().unwrap();
1027 let arg_terms = self.g.read_list(&list_head);
1028 let n = arg_terms.len();
1029 let args: Vec<NodeExpr> = arg_terms
1030 .into_iter()
1031 .filter_map(|t| self.parse_node_expr(t, owner))
1032 .collect();
1033 if args.len() != n {
1034 return None;
1035 }
1036 Some(NodeExpr::Function {
1037 iri: func_iri,
1038 args,
1039 })
1040 }
1041
1042 fn order(&self, s: &NamedOrBlankNode) -> Option<i64> {
1043 match self.g.object(s, vocab::SH_ORDER) {
1044 Some(Term::Literal(l)) => l.value().parse().ok(),
1045 _ => None,
1046 }
1047 }
1048
1049 fn class_selector(&mut self, class: Term) -> Selector {
1051 let tn = self.arena.insert(Shape::TestConst(class));
1052 Selector::HasPath(class_path(), tn)
1053 }
1054
1055 fn lower_shape_list(&mut self, list_head: &Term) -> Vec<ShapeId> {
1056 self.g
1057 .read_list(list_head)
1058 .into_iter()
1059 .filter_map(|m| term_to_node(&m))
1060 .map(|n| self.lower_shape(&n))
1061 .collect()
1062 }
1063
1064 fn parse_shape_path(&mut self, s: &NamedOrBlankNode) -> Option<Path> {
1065 let term = self.g.object(s, vocab::SH_PATH)?;
1066 match parse_path(self.g, &term) {
1067 Ok(p) => Some(p),
1068 Err(e) => {
1069 self.diag(DiagLevel::Error, format!("invalid sh:path: {e}"), s);
1070 None
1071 }
1072 }
1073 }
1074
1075 fn closed_allowed(&self, s: &NamedOrBlankNode) -> BTreeSet<oxrdf::NamedNode> {
1076 let mut q = BTreeSet::new();
1077 for prop in self.g.objects(s, vocab::SH_PROPERTY) {
1078 if let Some(pn) = term_to_node(&prop)
1079 && let Some(Term::NamedNode(n)) = self.g.object(&pn, vocab::SH_PATH)
1080 {
1081 q.insert(n);
1082 }
1083 }
1084 for ip in self.g.objects(s, vocab::SH_IGNORED_PROPERTIES) {
1085 for m in self.g.read_list(&ip) {
1086 if let Term::NamedNode(n) = m {
1087 q.insert(n);
1088 }
1089 }
1090 }
1091 q
1092 }
1093
1094 fn bool_prop(&self, s: &NamedOrBlankNode, pred: oxrdf::NamedNodeRef) -> bool {
1095 matches!(self.g.object(s, pred), Some(Term::Literal(l)) if l.value() == "true")
1096 }
1097
1098 fn int(&self, s: &NamedOrBlankNode, pred: oxrdf::NamedNodeRef) -> Option<u64> {
1099 match self.g.object(s, pred) {
1100 Some(Term::Literal(l)) => l.value().parse().ok(),
1101 _ => None,
1102 }
1103 }
1104
1105 fn lit(&self, s: &NamedOrBlankNode, pred: oxrdf::NamedNodeRef) -> Option<Literal> {
1106 match self.g.object(s, pred) {
1107 Some(Term::Literal(l)) => Some(l),
1108 _ => None,
1109 }
1110 }
1111
1112 fn canonical_sparql(
1116 &mut self,
1117 owner: &NamedOrBlankNode,
1118 raw: &str,
1119 expected: ExpectedQuery,
1120 ) -> Option<String> {
1121 let (query, canonical) = match canonical_sparql_query(self.g, owner, raw) {
1122 Ok(result) => result,
1123 Err(message) => {
1124 self.diag(DiagLevel::Error, message, owner);
1125 return None;
1126 }
1127 };
1128 let actual = match &query {
1129 Query::Select { .. } => ExpectedQuery::Select,
1130 Query::Ask { .. } => ExpectedQuery::Ask,
1131 Query::Construct { .. } => ExpectedQuery::Construct,
1132 Query::Describe { .. } => ExpectedQuery::Describe,
1133 };
1134 if actual != expected {
1135 self.diag(
1136 DiagLevel::Error,
1137 format!("expected SPARQL {expected}, found {actual}"),
1138 owner,
1139 );
1140 return None;
1141 }
1142 Some(canonical)
1143 }
1144}
1145
1146pub fn canonical_sparql_query(
1158 g: &Loaded,
1159 owner: &NamedOrBlankNode,
1160 raw: &str,
1161) -> Result<(Query, String), String> {
1162 let mut parser = SparqlParser::new();
1163 if let Some(base) = &g.base {
1164 parser = parser
1165 .with_base_iri(base)
1166 .map_err(|e| format!("invalid SPARQL base IRI: {e}"))?;
1167 }
1168 for (prefix, namespace) in &g.prefixes {
1169 parser = parser
1170 .with_prefix(prefix, namespace)
1171 .map_err(|e| format!("invalid SPARQL prefix declaration {prefix}: {e}"))?;
1172 }
1173 let mut prefix_sources: Vec<NamedOrBlankNode> = g
1174 .objects(owner, vocab::SH_PREFIXES)
1175 .iter()
1176 .filter_map(term_to_node)
1177 .collect();
1178 let mut seen_sources = HashSet::new();
1179 while let Some(source) = prefix_sources.pop() {
1180 if !seen_sources.insert(source.clone()) {
1181 continue;
1182 }
1183 prefix_sources.extend(
1184 g.objects(&source, vocab::OWL_IMPORTS)
1185 .iter()
1186 .filter_map(term_to_node),
1187 );
1188 for declaration_term in g.objects(&source, vocab::SH_DECLARE) {
1189 let Some(declaration) = term_to_node(&declaration_term) else {
1190 continue;
1191 };
1192 let (Some(Term::Literal(prefix)), Some(Term::Literal(namespace))) = (
1193 g.object(&declaration, vocab::SH_PREFIX),
1194 g.object(&declaration, vocab::SH_NAMESPACE),
1195 ) else {
1196 continue;
1197 };
1198 parser = parser
1199 .with_prefix(prefix.value(), namespace.value())
1200 .map_err(|e| format!("invalid SHACL SPARQL prefix declaration: {e}"))?;
1201 }
1202 }
1203 let query = parser
1204 .parse_query(raw)
1205 .map_err(|e| format!("invalid SPARQL query: {e}"))?;
1206 let canonical = query.to_string();
1207 Ok((query, canonical))
1208}
1209
1210#[derive(Clone, Copy, PartialEq, Eq)]
1211enum ExpectedQuery {
1212 Select,
1213 Ask,
1214 Construct,
1215 Describe,
1216}
1217
1218impl std::fmt::Display for ExpectedQuery {
1219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1220 f.write_str(match self {
1221 Self::Select => "SELECT",
1222 Self::Ask => "ASK",
1223 Self::Construct => "CONSTRUCT",
1224 Self::Describe => "DESCRIBE",
1225 })
1226 }
1227}
1228
1229fn class_path() -> Path {
1230 Path::seq(vec![
1231 Path::Pred(vocab::rdf_type()),
1232 Path::star(Path::Pred(vocab::rdfs_subclassof())),
1233 ])
1234}
1235
1236fn node_expr_has_function(e: &NodeExpr) -> bool {
1241 match e {
1242 NodeExpr::Function { .. } => true,
1243 NodeExpr::Filter { input, .. } => node_expr_has_function(input),
1244 NodeExpr::Intersection(es) | NodeExpr::Union(es) => es.iter().any(node_expr_has_function),
1245 NodeExpr::This | NodeExpr::Constant(_) | NodeExpr::Path(_) => false,
1246 }
1247}
1248
1249fn map_node_kind(term: &Term) -> Option<NodeKindSet> {
1250 let Term::NamedNode(n) = term else {
1251 return None;
1252 };
1253 let r = n.as_ref();
1254 Some(if r == vocab::SH_IRI {
1255 NodeKindSet::IRI
1256 } else if r == vocab::SH_BLANK_NODE {
1257 NodeKindSet::BLANK_NODE
1258 } else if r == vocab::SH_LITERAL {
1259 NodeKindSet::LITERAL
1260 } else if r == vocab::SH_BLANK_NODE_OR_IRI {
1261 NodeKindSet::BLANK_NODE_OR_IRI
1262 } else if r == vocab::SH_BLANK_NODE_OR_LITERAL {
1263 NodeKindSet::BLANK_NODE_OR_LITERAL
1264 } else if r == vocab::SH_IRI_OR_LITERAL {
1265 NodeKindSet::IRI_OR_LITERAL
1266 } else {
1267 return None;
1268 })
1269}