shapes_converter/shacl_to_shex/
shacl2shex.rs

1use super::{Shacl2ShExConfig, Shacl2ShExError};
2use iri_s::IriS;
3use prefixmap::IriRef;
4use shacl_ast::{
5    component::Component, node_shape::NodeShape, property_shape::PropertyShape,
6    shape::Shape as ShaclShape, target::Target, Schema as ShaclSchema,
7};
8use shex_ast::{
9    BNode, NodeConstraint, Schema as ShExSchema, Shape as ShExShape, ShapeExpr, ShapeExprLabel,
10    TripleExpr, TripleExprWrapper, ValueSetValue,
11};
12use srdf::{Object, RDFNode, SHACLPath};
13use tracing::debug;
14
15#[allow(dead_code)] // TODO: only for config...
16pub struct Shacl2ShEx {
17    config: Shacl2ShExConfig,
18    current_shex: ShExSchema,
19}
20
21impl Shacl2ShEx {
22    pub fn new(config: &Shacl2ShExConfig) -> Shacl2ShEx {
23        Shacl2ShEx {
24            config: config.clone(),
25            current_shex: ShExSchema::new(),
26        }
27    }
28
29    pub fn current_shex(&self) -> &ShExSchema {
30        &self.current_shex
31    }
32
33    pub fn convert(&mut self, schema: &ShaclSchema) -> Result<(), Shacl2ShExError> {
34        let prefixmap = schema.prefix_map().without_rich_qualifying();
35        self.current_shex = ShExSchema::new().with_prefixmap(Some(prefixmap));
36        for (_, shape) in schema.iter() {
37            match &shape {
38                shacl_ast::shape::Shape::NodeShape(ns) => {
39                    let (label, shape_expr, is_abstract) = self.convert_shape(ns, schema)?;
40                    self.current_shex.add_shape(label, shape_expr, is_abstract)
41                }
42                shacl_ast::shape::Shape::PropertyShape(_) => {
43                    // Ignoring property shapes at top level conversion
44                }
45            }
46        }
47        Ok(())
48    }
49
50    pub fn convert_shape(
51        &self,
52        shape: &NodeShape,
53        schema: &ShaclSchema,
54    ) -> Result<(ShapeExprLabel, ShapeExpr, bool), Shacl2ShExError> {
55        let label = self.rdfnode2label(shape.id())?;
56        let shape_expr = self.node_shape2shape_expr(shape, schema)?;
57        let is_abstract = false; // TODO: No virtual shapes in SHACL so it is always false
58        Ok((label, shape_expr, is_abstract))
59    }
60
61    pub fn rdfnode2label(&self, node: &RDFNode) -> Result<ShapeExprLabel, Shacl2ShExError> {
62        match node {
63            srdf::Object::Iri(iri) => Ok(ShapeExprLabel::iri(iri.clone())),
64            srdf::Object::BlankNode(bn) => Ok(ShapeExprLabel::bnode(BNode::new(bn))),
65            srdf::Object::Literal(lit) => Err(Shacl2ShExError::RDFNode2LabelLiteral {
66                literal: lit.clone(),
67            }),
68        }
69    }
70
71    pub fn node_shape2shape_expr(
72        &self,
73        shape: &NodeShape,
74        schema: &ShaclSchema,
75    ) -> Result<ShapeExpr, Shacl2ShExError> {
76        let mut exprs = Vec::new();
77        for node in shape.property_shapes() {
78            match schema.get_shape(node) {
79                None => todo!(),
80                Some(shape) => match shape {
81                    ShaclShape::PropertyShape(ps) => {
82                        let tc = self.property_shape2triple_constraint(ps)?;
83                        exprs.push(tc);
84                        Ok(())
85                    }
86                    ShaclShape::NodeShape(ns) => Err(Shacl2ShExError::NotExpectedNodeShape {
87                        node_shape: ns.clone(),
88                    }),
89                },
90            }?
91        }
92        let is_closed = None; // TODO: Check real value
93        let extra = None; // TODO: Check if we could find a way to obtain extras in SHACL ?
94        let mut te = if exprs.is_empty() {
95            None
96        } else {
97            Some(TripleExpr::each_of(exprs))
98        };
99        if self.config.add_target_class() {
100            let target_class_expr = self.convert_target_decls(shape.targets(), schema)?;
101            te = match (te, target_class_expr) {
102                (None, None) => None,
103                (None, Some(t)) => Some(t),
104                (Some(t), None) => Some(t),
105                (Some(t1), Some(t2)) => Some(self.merge_triple_exprs(&t1, &t2)),
106            };
107        }
108        let shape = ShExShape::new(is_closed, extra, te);
109        Ok(ShapeExpr::shape(shape))
110    }
111
112    /// Collect targetClass declarations and add a rdf:type constraint for each
113    pub fn convert_target_decls(
114        &self,
115        targets: &Vec<Target>,
116        schema: &ShaclSchema,
117    ) -> Result<Option<TripleExpr>, Shacl2ShExError> {
118        let mut values = Vec::new();
119        for target in targets {
120            if let Some(value) = self.target2value_set_value(target, schema)? {
121                values.push(value);
122            }
123        }
124        let value_cls = ShapeExpr::node_constraint(NodeConstraint::new().with_values(values));
125        let tc = TripleExpr::triple_constraint(
126            None,
127            None,
128            IriRef::iri(IriS::rdf_type()),
129            Some(value_cls),
130            None,
131            None,
132        );
133        Ok(Some(tc))
134    }
135
136    pub fn target2value_set_value(
137        &self,
138        target: &Target,
139        _schema: &ShaclSchema,
140    ) -> Result<Option<ValueSetValue>, Shacl2ShExError> {
141        match target {
142            Target::TargetNode(_) => Ok(None),
143            Target::TargetClass(cls) => {
144                let value_set_value = match cls {
145                    Object::Iri(iri) => Ok(ValueSetValue::iri(IriRef::iri(iri.clone()))),
146                    Object::BlankNode(bn) => {
147                        Err(Shacl2ShExError::UnexpectedBlankNodeForTargetClass {
148                            bnode: bn.clone(),
149                        })
150                    }
151                    Object::Literal(lit) => Err(Shacl2ShExError::UnexpectedLiteralForTargetClass {
152                        literal: lit.clone(),
153                    }),
154                }?;
155                Ok(Some(value_set_value))
156            }
157            Target::TargetSubjectsOf(_) => Ok(None),
158            Target::TargetObjectsOf(_) => Ok(None),
159            Target::TargetImplicitClass(_) => Ok(None),
160        }
161    }
162
163    pub fn merge_triple_exprs(&self, te1: &TripleExpr, te2: &TripleExpr) -> TripleExpr {
164        match te1 {
165            TripleExpr::EachOf {
166                id,
167                expressions,
168                min,
169                max,
170                sem_acts,
171                annotations,
172            } => match te2 {
173                TripleExpr::EachOf {
174                    id: _,
175                    expressions: exprs,
176                    min: _,
177                    max: _,
178                    sem_acts: _,
179                    annotations: _,
180                } => TripleExpr::EachOf {
181                    id: id.clone(),
182                    expressions: Self::merge_expressions(expressions, exprs),
183                    min: *min,
184                    max: *max,
185                    sem_acts: sem_acts.clone(),
186                    annotations: annotations.clone(),
187                },
188                tc @ TripleExpr::TripleConstraint {
189                    id,
190                    negated: _,
191                    inverse: _,
192                    predicate: _,
193                    value_expr: _,
194                    min,
195                    max,
196                    sem_acts,
197                    annotations,
198                } => TripleExpr::EachOf {
199                    id: id.clone(),
200                    expressions: Self::merge_expressions(
201                        expressions,
202                        &vec![TripleExprWrapper { te: tc.clone() }],
203                    ),
204                    min: *min,
205                    max: *max,
206                    sem_acts: sem_acts.clone(),
207                    annotations: annotations.clone(),
208                },
209                _ => todo!(),
210            },
211            tc @ TripleExpr::TripleConstraint {
212                id,
213                negated: _,
214                inverse: _,
215                predicate: _,
216                value_expr: _,
217                min,
218                max,
219                sem_acts,
220                annotations,
221            } => match te2 {
222                TripleExpr::EachOf {
223                    id: _,
224                    expressions: exprs,
225                    min: _,
226                    max: _,
227                    sem_acts: _,
228                    annotations: _,
229                } => TripleExpr::EachOf {
230                    id: id.clone(),
231                    expressions: Self::merge_expressions(
232                        &vec![TripleExprWrapper { te: tc.clone() }],
233                        exprs,
234                    ),
235                    min: *min,
236                    max: *max,
237                    sem_acts: sem_acts.clone(),
238                    annotations: annotations.clone(),
239                },
240                tc2 @ TripleExpr::TripleConstraint {
241                    id,
242                    negated: _,
243                    inverse: _,
244                    predicate: _,
245                    value_expr: _,
246                    min,
247                    max,
248                    sem_acts,
249                    annotations,
250                } => TripleExpr::EachOf {
251                    id: id.clone(),
252                    expressions: vec![
253                        TripleExprWrapper { te: tc.clone() },
254                        TripleExprWrapper { te: tc2.clone() },
255                    ],
256                    min: *min,
257                    max: *max,
258                    sem_acts: sem_acts.clone(),
259                    annotations: annotations.clone(),
260                },
261                _ => todo!(),
262            },
263            _ => todo!(),
264        }
265    }
266
267    pub fn merge_expressions(
268        e1: &Vec<TripleExprWrapper>,
269        e2: &Vec<TripleExprWrapper>,
270    ) -> Vec<TripleExprWrapper> {
271        let mut es = Vec::new();
272        for e in e1 {
273            es.push(e.clone())
274        }
275        for e in e2 {
276            es.push(e.clone())
277        }
278        es
279    }
280
281    pub fn property_shape2triple_constraint(
282        &self,
283        shape: &PropertyShape,
284    ) -> Result<TripleExpr, Shacl2ShExError> {
285        let predicate = self.shacl_path2predicate(shape.path())?;
286        let negated = None;
287        let inverse = None;
288        let se = self.components2shape_expr(shape.components())?;
289        let min = None;
290        let max = None;
291        Ok(TripleExpr::triple_constraint(
292            negated, inverse, predicate, se, min, max,
293        ))
294    }
295
296    pub fn components2shape_expr(
297        &self,
298        components: &Vec<Component>,
299    ) -> Result<Option<ShapeExpr>, Shacl2ShExError> {
300        let mut ses = Vec::new();
301        for c in components {
302            let se = self.component2shape_expr(c)?;
303            ses.push(se);
304        }
305        if ses.is_empty() {
306            Ok(None)
307        } else {
308            match ses.len() {
309                1 => {
310                    let se = &ses[0];
311                    Ok(Some(se.clone()))
312                }
313                _ => {
314                    // Err(Shacl2ShExError::not_implemented("Conversion of shapes with multiple components is not implemented yet: {components:?}"))}
315                    debug!("More than one component: {components:?}, taking only the first one");
316                    let se = &ses[0];
317                    Ok(Some(se.clone()))
318                }
319            }
320        }
321    }
322
323    pub fn create_class_constraint(&self, cls: &RDFNode) -> Result<ShapeExpr, Shacl2ShExError> {
324        let rdf_type = IriRef::iri(IriS::rdf_type());
325        let value = match cls {
326            Object::Iri(iri) => ValueSetValue::iri(IriRef::iri(iri.clone())),
327            Object::BlankNode(_) => todo!(),
328            Object::Literal(_) => todo!(),
329        };
330        let cls = NodeConstraint::new().with_values(vec![value]);
331        let te = TripleExpr::triple_constraint(
332            None,
333            None,
334            rdf_type,
335            Some(ShapeExpr::node_constraint(cls)),
336            None,
337            None,
338        );
339        let se = ShapeExpr::shape(ShExShape::new(None, None, Some(te)));
340        Ok(se)
341    }
342
343    pub fn component2shape_expr(
344        &self,
345        component: &Component,
346    ) -> Result<ShapeExpr, Shacl2ShExError> {
347        match component {
348            Component::Class(cls) => {
349                debug!("TODO: Converting Class components for {cls:?} doesn't match rdfs:subClassOf semantics of SHACL yet");
350                let se = self.create_class_constraint(cls)?;
351                Ok(se)
352            }
353            Component::Datatype(dt) => Ok(ShapeExpr::node_constraint(
354                NodeConstraint::new().with_datatype(dt.clone()),
355            )),
356            Component::NodeKind(_) => todo!(),
357            Component::MinCount(_) => todo!(),
358            Component::MaxCount(_) => todo!(),
359            Component::MinExclusive(_) => todo!(),
360            Component::MaxExclusive(_) => todo!(),
361            Component::MinInclusive(_) => todo!(),
362            Component::MaxInclusive(_) => todo!(),
363            Component::MinLength(_) => todo!(),
364            Component::MaxLength(_) => todo!(),
365            Component::Pattern {
366                pattern: _,
367                flags: _,
368            } => todo!(),
369            Component::UniqueLang(_) => todo!(),
370            Component::LanguageIn { langs: _ } => todo!(),
371            Component::Equals(_) => todo!(),
372            Component::Disjoint(_) => todo!(),
373            Component::LessThan(_) => todo!(),
374            Component::LessThanOrEquals(_) => todo!(),
375            Component::Or { shapes: _ } => {
376                debug!("Not implemented OR Shapes");
377                Ok(ShapeExpr::empty_shape())
378            }
379            Component::And { shapes: _ } => todo!(),
380            Component::Not { shape: _ } => todo!(),
381            Component::Xone { shapes: _ } => todo!(),
382            Component::Closed {
383                is_closed: _,
384                ignored_properties: _,
385            } => todo!(),
386            Component::Node { shape: _ } => todo!(),
387            Component::HasValue { value: _ } => todo!(),
388            Component::In { values: _ } => todo!(),
389            Component::QualifiedValueShape {
390                shape: _,
391                qualified_min_count: _,
392                qualified_max_count: _,
393                qualified_value_shapes_disjoint: _,
394            } => todo!(),
395        }
396    }
397
398    pub fn shacl_path2predicate(&self, path: &SHACLPath) -> Result<IriRef, Shacl2ShExError> {
399        match path {
400            SHACLPath::Predicate { pred } => Ok(IriRef::iri(pred.clone())),
401            _ => todo!(),
402        }
403    }
404}