From 7cab65afc09285358ee74255ce4b285445e25e61 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Arnaud Date: Mon, 10 Jun 2024 11:26:24 +0200 Subject: [PATCH] feat: implement SHACL to RDF issue #83 --- Cargo.toml | 2 + iri_s/src/lib.rs | 2 +- shacl_ast/Cargo.toml | 2 + shacl_ast/src/ast/component.rs | 276 +++++++++++++++++- shacl_ast/src/ast/message_map.rs | 38 ++- shacl_ast/src/ast/node_shape.rs | 116 ++++++-- shacl_ast/src/ast/property_shape.rs | 174 +++++++++-- shacl_ast/src/ast/shape.rs | 27 +- shacl_ast/src/ast/target.rs | 33 ++- .../converter/rdf_to_shacl/shacl_parser.rs | 2 +- .../converter/shacl_to_rdf/shacl_writer.rs | 38 +-- shacl_ast/src/lib.rs | 3 - srdf/src/srdf_basic.rs | 3 + srdf/src/srdf_graph/README.md | 2 - srdf/src/srdf_graph/srdfgraph.rs | 23 +- srdf/src/srdf_sparql/srdfsparql.rs | 4 + 16 files changed, 657 insertions(+), 88 deletions(-) delete mode 100644 srdf/src/srdf_graph/README.md diff --git a/Cargo.toml b/Cargo.toml index 6d0a052d..2c61bcd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ license = "GPL-3.0-or-later" authors = [ "Jose Emilio Labra Gayo ", "Ángel Iglesias Préstamo ", + "Marc-Antoine Arnaud ", ] repository = "https://github.com/weso/shapes-rs" homepage = "https://www.weso.es/shapes-rs/" @@ -50,6 +51,7 @@ license = "MIT OR Apache-2.0" authors = [ "Jose Emilio Labra Gayo ", "Ángel Iglesias Préstamo ", + "Marc-Antoine Arnaud ", ] description = "RDF data shapes implementation in Rust" repository = "https://github.com/weso/shapes-rs" diff --git a/iri_s/src/lib.rs b/iri_s/src/lib.rs index 16b38add..3d76c65a 100644 --- a/iri_s/src/lib.rs +++ b/iri_s/src/lib.rs @@ -44,7 +44,7 @@ macro_rules! iri { ( $lit: tt ) => { - IriS::new_unchecked($lit) + $crate::IriS::new_unchecked($lit) }; } diff --git a/shacl_ast/Cargo.toml b/shacl_ast/Cargo.toml index 6b3b78f3..5a5af186 100644 --- a/shacl_ast/Cargo.toml +++ b/shacl_ast/Cargo.toml @@ -26,4 +26,6 @@ serde_json = "1" const_format = "0.2" itertools = "0.13" +oxrdf = { version = "0.2.0-alpha.5", features = ["oxsdatatypes"] } + [dev-dependencies] diff --git a/shacl_ast/src/ast/component.rs b/shacl_ast/src/ast/component.rs index 8f153505..21cf239f 100644 --- a/shacl_ast/src/ast/component.rs +++ b/shacl_ast/src/ast/component.rs @@ -1,10 +1,18 @@ -use std::fmt::Display; - -use prefixmap::IriRef; -use srdf::{lang::Lang, literal::Literal, RDFNode}; - -use crate::{node_kind::NodeKind, value::Value}; +use crate::{ + node_kind::NodeKind, value::Value, SH_AND_STR, SH_CLASS_STR, SH_CLOSED_STR, SH_DATATYPE_STR, + SH_DISJOINT_STR, SH_EQUALS_STR, SH_FLAGS_STR, SH_HAS_VALUE_STR, SH_IGNORED_PROPERTIES_STR, + SH_IRI_STR, SH_LANGUAGE_IN_STR, SH_LESS_THAN_OR_EQUALS_STR, SH_LESS_THAN_STR, SH_MAX_COUNT_STR, + SH_MAX_EXCLUSIVE_STR, SH_MAX_INCLUSIVE_STR, SH_MAX_LENGTH_STR, SH_MIN_COUNT_STR, + SH_MIN_EXCLUSIVE_STR, SH_MIN_INCLUSIVE_STR, SH_MIN_LENGTH_STR, SH_NODE_STR, SH_OR_STR, + SH_PATTERN_STR, SH_QUALIFIED_MAX_COUNT_STR, SH_QUALIFIED_MIN_COUNT_STR, + SH_QUALIFIED_VALUE_SHAPE_STR, SH_UNIQUE_LANG_STR, SH_XONE_STR, +}; +use iri_s::iri; use itertools::Itertools; +use oxrdf::{Literal as OxLiteral, NamedNode, Term as OxTerm}; +use prefixmap::IriRef; +use srdf::{lang::Lang, literal::Literal, RDFNode, SRDFBuilder, XSD_INTEGER_STR}; +use std::fmt::Display; #[derive(Debug, Clone)] pub enum Component { @@ -64,6 +72,262 @@ pub enum Component { }, } +impl Component { + pub fn write(&self, rdf_node: &RDFNode, rdf: &mut RDF) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + match self { + Self::Class(rdf_node) => { + Self::write_term(&RDF::object_as_term(rdf_node), SH_CLASS_STR, rdf_node, rdf)?; + } + Self::Datatype(iri) => { + Self::write_iri(iri, SH_DATATYPE_STR, rdf_node, rdf)?; + } + Self::NodeKind(node_kind) => { + let iri = match &node_kind { + NodeKind::Iri => SH_IRI_STR, + + _ => unimplemented!(), + }; + + Self::write_iri(&IriRef::Iri(iri!(iri)), SH_DATATYPE_STR, rdf_node, rdf)?; + } + Self::MinCount(value) => { + Self::write_integer(*value, SH_MIN_COUNT_STR, rdf_node, rdf)?; + } + Self::MaxCount(value) => { + Self::write_integer(*value, SH_MAX_COUNT_STR, rdf_node, rdf)?; + } + Self::MinExclusive(value) => { + Self::write_literal(value, SH_MIN_EXCLUSIVE_STR, rdf_node, rdf)?; + } + Self::MaxExclusive(value) => { + Self::write_literal(value, SH_MAX_EXCLUSIVE_STR, rdf_node, rdf)?; + } + Self::MinInclusive(value) => { + Self::write_literal(value, SH_MIN_INCLUSIVE_STR, rdf_node, rdf)?; + } + Self::MaxInclusive(value) => { + Self::write_literal(value, SH_MAX_INCLUSIVE_STR, rdf_node, rdf)?; + } + Self::MinLength(value) => { + Self::write_integer(*value, SH_MIN_LENGTH_STR, rdf_node, rdf)?; + } + Self::MaxLength(value) => { + Self::write_integer(*value, SH_MAX_LENGTH_STR, rdf_node, rdf)?; + } + Self::Pattern { pattern, flags } => { + Self::write_literal(&Literal::str(pattern), SH_PATTERN_STR, rdf_node, rdf)?; + if let Some(flags) = flags { + Self::write_literal(&Literal::str(flags), SH_FLAGS_STR, rdf_node, rdf)?; + } + } + Self::UniqueLang(value) => { + Self::write_boolean( + *value, + SH_UNIQUE_LANG_STR, + rdf_node, + rdf, + )?; + } + Self::LanguageIn { langs } => { + langs.iter().try_for_each(|lang| { + Self::write_literal( + &Literal::str(&lang.value()), + SH_LANGUAGE_IN_STR, + rdf_node, + rdf, + ) + })?; + } + Self::Equals(iri) => { + Self::write_iri(iri, SH_EQUALS_STR, rdf_node, rdf)?; + } + Self::Disjoint(iri) => { + Self::write_iri(iri, SH_DISJOINT_STR, rdf_node, rdf)?; + } + Self::LessThan(iri) => { + Self::write_iri(iri, SH_LESS_THAN_STR, rdf_node, rdf)?; + } + Self::LessThanOrEquals(iri) => { + Self::write_iri(iri, SH_LESS_THAN_OR_EQUALS_STR, rdf_node, rdf)?; + } + Self::Or { shapes } => { + shapes.iter().try_for_each(|shape| { + Self::write_term(&RDF::object_as_term(shape), SH_OR_STR, rdf_node, rdf) + })?; + } + Self::And { shapes } => { + shapes.iter().try_for_each(|shape| { + Self::write_term(&RDF::object_as_term(shape), SH_AND_STR, rdf_node, rdf) + })?; + } + Self::Not { shape } => { + Self::write_term(&RDF::object_as_term(shape), SH_PATTERN_STR, rdf_node, rdf)?; + } + Self::Xone { shapes } => { + shapes.iter().try_for_each(|shape| { + Self::write_term(&RDF::object_as_term(shape), SH_XONE_STR, rdf_node, rdf) + })?; + } + Self::Closed { + is_closed, + ignored_properties, + } => { + Self::write_boolean( + *is_closed, + SH_CLOSED_STR, + rdf_node, + rdf, + )?; + + ignored_properties.iter().try_for_each(|iri| { + Self::write_iri(iri, SH_IGNORED_PROPERTIES_STR, rdf_node, rdf) + })?; + } + Self::Node { shape } => { + Self::write_term(&RDF::object_as_term(shape), SH_NODE_STR, rdf_node, rdf)?; + } + Self::HasValue { value } => match value { + Value::Iri(iri) => { + Self::write_iri(iri, SH_HAS_VALUE_STR, rdf_node, rdf)?; + } + Value::Literal(literal) => { + Self::write_literal( + &Literal::str(&literal.to_string()), + SH_HAS_VALUE_STR, + rdf_node, + rdf, + )?; + } + }, + Self::In { values } => { + values.iter().try_for_each(|value| match value { + Value::Iri(iri) => Self::write_iri(iri, SH_HAS_VALUE_STR, rdf_node, rdf), + Value::Literal(literal) => Self::write_literal( + &Literal::str(&literal.to_string()), + SH_HAS_VALUE_STR, + rdf_node, + rdf, + ), + })?; + } + Self::QualifiedValueShape { + shape, + qualified_min_count, + qualified_max_count, + qualified_value_shapes_disjoint, + } => { + Self::write_term( + &RDF::object_as_term(shape), + SH_QUALIFIED_VALUE_SHAPE_STR, + rdf_node, + rdf, + )?; + + if let Some(value) = qualified_min_count { + Self::write_integer(*value, SH_QUALIFIED_MIN_COUNT_STR, rdf_node, rdf)?; + } + + if let Some(value) = qualified_max_count { + Self::write_integer(*value, SH_QUALIFIED_MAX_COUNT_STR, rdf_node, rdf)?; + } + + if let Some(value) = qualified_value_shapes_disjoint { + Self::write_boolean( + *value, + SH_QUALIFIED_MAX_COUNT_STR, + rdf_node, + rdf, + )?; + } + } + } + Ok(()) + } + + fn write_integer( + value: isize, + predicate: &str, + rdf_node: &RDFNode, + rdf: &mut RDF, + ) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + let decimal_type = NamedNode::new(XSD_INTEGER_STR).unwrap(); + + let term = OxTerm::Literal(OxLiteral::new_typed_literal( + value.to_string(), + decimal_type, + )); + + Self::write_term(&RDF::term_s2term(&term), predicate, rdf_node, rdf) + } + + fn write_boolean( + value: bool, + predicate: &str, + rdf_node: &RDFNode, + rdf: &mut RDF, + ) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + let term = OxTerm::Literal(OxLiteral::from(value)); + + Self::write_term(&RDF::term_s2term(&term), predicate, rdf_node, rdf) + } + + fn write_literal( + value: &Literal, + predicate: &str, + rdf_node: &RDFNode, + rdf: &mut RDF, + ) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + let term = OxTerm::Literal(OxLiteral::new_simple_literal(value.lexical_form())); + + Self::write_term(&RDF::term_s2term(&term), predicate, rdf_node, rdf) + } + + fn write_iri( + value: &IriRef, + predicate: &str, + rdf_node: &RDFNode, + rdf: &mut RDF, + ) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + Self::write_term( + &RDF::iri_s2term(&value.get_iri().unwrap()), + predicate, + rdf_node, + rdf, + ) + } + + fn write_term( + value: &RDF::Term, + predicate: &str, + rdf_node: &RDFNode, + rdf: &mut RDF, + ) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + rdf.add_triple( + &RDF::object_as_subject(rdf_node).unwrap(), + &RDF::iri_s2iri(&iri!(predicate)), + value, + ) + } +} + impl Display for Component { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/shacl_ast/src/ast/message_map.rs b/shacl_ast/src/ast/message_map.rs index d97a14b6..6d63cf20 100644 --- a/shacl_ast/src/ast/message_map.rs +++ b/shacl_ast/src/ast/message_map.rs @@ -1,10 +1,46 @@ +use oxrdf::{Literal as OxLiteral, Term as OxTerm}; +use srdf::lang::Lang; +use std::collections::HashMap; +use std::str::FromStr; + #[derive(Debug, Default, Clone)] pub struct MessageMap { - // mmap: HashMap, String> + messages: HashMap, String>, } impl MessageMap { pub fn new() -> Self { Self::default() } + + pub fn with_message(mut self, lang: Option, message: String) -> Self { + self.messages.insert(lang, message); + self + } + + pub fn messages(&self) -> &HashMap, String> { + &self.messages + } + + pub fn to_term_iter(&self) -> impl Iterator + '_ { + self.messages.iter().map(|(lang, message)| { + let literal = if let Some(lang) = lang { + OxLiteral::new_language_tagged_literal(message, lang.value()).unwrap() + } else { + OxLiteral::new_simple_literal(message) + }; + + OxTerm::Literal(literal) + }) + } +} + +impl FromStr for MessageMap { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(Self { + messages: HashMap::from([(None, s.to_string())]), + }) + } } diff --git a/shacl_ast/src/ast/node_shape.rs b/shacl_ast/src/ast/node_shape.rs index 45c5373a..697145c7 100644 --- a/shacl_ast/src/ast/node_shape.rs +++ b/shacl_ast/src/ast/node_shape.rs @@ -1,8 +1,9 @@ -use srdf::RDFNode; +use crate::{component::Component, message_map::MessageMap, severity::Severity, target::Target, SH_DEACTIVATED_STR, SH_DESCRIPTION_STR, SH_GROUP_STR, SH_INFO_STR, SH_NAME_STR, SH_NODE_SHAPE, SH_PROPERTY_STR, SH_SEVERITY_STR, SH_VIOLATION_STR, SH_WARNING_STR, SH_CLOSED_STR}; +use iri_s::iri; +use oxrdf::{Literal as OxLiteral, Term as OxTerm}; +use srdf::{RDFNode, SRDFBuilder}; use std::fmt::Display; -use crate::{component::Component, target::Target}; - #[derive(Debug, Clone)] pub struct NodeShape { id: RDFNode, @@ -11,16 +12,12 @@ pub struct NodeShape { property_shapes: Vec, closed: bool, // ignored_properties: Vec, - // deactivated: bool, + deactivated: bool, // message: MessageMap, - // severity: Option, - // name: MessageMap, - // description: MessageMap, - - // SHACL spec says that the values of sh:order should be decimals but in the examples they use integers. `NumericLiteral` also includes doubles. - // order: Option, - - // group: Option, + severity: Option, + name: MessageMap, + description: MessageMap, + group: Option, // source_iri: Option, } @@ -33,13 +30,12 @@ impl NodeShape { property_shapes: Vec::new(), closed: false, // ignored_properties: Vec::new(), - // deactivated: false, + deactivated: false, // message: MessageMap::new(), - // severity: None, - // name: MessageMap::new(), - // description: MessageMap::new(), - // order: None, - // group: None, + severity: None, + name: MessageMap::new(), + description: MessageMap::new(), + group: None, // source_iri: None, } } @@ -71,6 +67,90 @@ impl NodeShape { self.closed = closed; self } + + pub fn write(&self, rdf: &mut RDF) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + rdf.add_type(&self.id, RDF::iri_s2term(&SH_NODE_SHAPE))?; + + self.name.to_term_iter().try_for_each(|term| { + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_NAME_STR)), + &RDF::term_s2term(&term), + ) + })?; + + self.description.to_term_iter().try_for_each(|term| { + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_DESCRIPTION_STR)), + &RDF::term_s2term(&term), + ) + })?; + + self.components + .iter() + .try_for_each(|component| component.write(&self.id, rdf))?; + + self.targets + .iter() + .try_for_each(|target| target.write(&self.id, rdf))?; + + self.property_shapes.iter().try_for_each(|property_shape| { + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_PROPERTY_STR)), + &RDF::object_as_term(property_shape), + ) + })?; + + if self.deactivated { + let term = OxTerm::Literal(OxLiteral::new_simple_literal("true")); + + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_DEACTIVATED_STR)), + &RDF::term_s2term(&term), + )?; + } + + if let Some(group) = &self.group { + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_GROUP_STR)), + &RDF::object_as_term(group), + )?; + } + + if let Some(severity) = &self.severity { + let pred = match severity { + Severity::Violation => iri!(SH_VIOLATION_STR), + Severity::Info => iri!(SH_INFO_STR), + Severity::Warning => iri!(SH_WARNING_STR), + Severity::Generic(iri) => iri.get_iri().unwrap(), + }; + + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_SEVERITY_STR)), + &RDF::iri_s2term(&pred), + )?; + } + + if self.closed { + let term = OxTerm::Literal(OxLiteral::from(true)); + + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_CLOSED_STR)), + &RDF::term_s2term(&term), + )?; + } + + Ok(()) + } } impl Display for NodeShape { diff --git a/shacl_ast/src/ast/property_shape.rs b/shacl_ast/src/ast/property_shape.rs index f6ea33de..ffa0d51a 100644 --- a/shacl_ast/src/ast/property_shape.rs +++ b/shacl_ast/src/ast/property_shape.rs @@ -1,53 +1,75 @@ -use srdf::{RDFNode, SHACLPath}; +use iri_s::iri; +use oxrdf::{Literal as OxLiteral, NamedNode, Term as OxTerm}; +use srdf::{numeric_literal::NumericLiteral, RDFNode, SHACLPath, SRDFBuilder, XSD_DECIMAL_STR}; use std::fmt::Display; -use crate::{component::Component, target::Target}; +use crate::{ + component::Component, message_map::MessageMap, severity::Severity, target::Target, + SH_DEACTIVATED_STR, SH_DESCRIPTION_STR, SH_GROUP_STR, SH_INFO_STR, SH_NAME_STR, SH_ORDER_STR, + SH_PATH_STR, SH_PROPERTY_SHAPE, SH_SEVERITY_STR, SH_VIOLATION_STR, SH_WARNING_STR, +}; #[derive(Debug, Clone)] pub struct PropertyShape { - // id: RDFNode, + id: RDFNode, path: SHACLPath, components: Vec, targets: Vec, property_shapes: Vec, closed: bool, // ignored_properties: Vec, - // deactivated: bool, + deactivated: bool, // message: MessageMap, - // severity: Option, - // name: MessageMap, - // description: MessageMap, - - // SHACL spec says that the values of sh:order should be decimals but in the examples they use integers. `NumericLiteral` also includes doubles. - // order: Option, - - // group: Option, + severity: Option, + name: MessageMap, + description: MessageMap, + order: Option, + group: Option, // source_iri: Option, // annotations: Vec<(IriRef, RDFNode)>, } impl PropertyShape { - pub fn new(_id: RDFNode, path: SHACLPath) -> Self { + pub fn new(id: RDFNode, path: SHACLPath) -> Self { PropertyShape { - // id, + id, path, components: Vec::new(), targets: Vec::new(), property_shapes: Vec::new(), closed: false, // ignored_properties: Vec::new(), - // deactivated: false, + deactivated: false, // message: MessageMap::new(), - // severity: None, - // name: MessageMap::new(), - // description: MessageMap::new(), - // order: None, - // group: None, + severity: None, + name: MessageMap::new(), + description: MessageMap::new(), + order: None, + group: None, // source_iri: None, // annotations: Vec::new() } } + pub fn with_name(mut self, name: MessageMap) -> Self { + self.name = name; + self + } + pub fn with_description(mut self, description: MessageMap) -> Self { + self.description = description; + self + } + + pub fn with_order(mut self, order: Option) -> Self { + self.order = order; + self + } + + pub fn with_group(mut self, group: Option) -> Self { + self.group = group; + self + } + pub fn with_targets(mut self, targets: Vec) -> Self { self.targets = targets; self @@ -67,6 +89,118 @@ impl PropertyShape { self.closed = closed; self } + + pub fn with_severity(mut self, severity: Option) -> Self { + self.severity = severity; + self + } + + pub fn id(&self) -> &RDFNode { + &self.id + } + + pub fn path(&self) -> &SHACLPath { + &self.path + } + + pub fn name(&self) -> &MessageMap { + &self.name + } + + pub fn description(&self) -> &MessageMap { + &self.description + } + + pub fn write(&self, rdf: &mut RDF) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + rdf.add_type(&self.id, RDF::iri_s2term(&SH_PROPERTY_SHAPE))?; + + self.name.to_term_iter().try_for_each(|term| { + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_NAME_STR)), + &RDF::term_s2term(&term), + ) + })?; + + self.description.to_term_iter().try_for_each(|term| { + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_DESCRIPTION_STR)), + &RDF::term_s2term(&term), + ) + })?; + + if let Some(order) = &self.order { + let decimal_type = NamedNode::new(XSD_DECIMAL_STR).unwrap(); + + let term = OxTerm::Literal(OxLiteral::new_typed_literal( + order.to_string(), + decimal_type, + )); + + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_ORDER_STR)), + &RDF::term_s2term(&term), + )?; + } + + if let Some(group) = &self.group { + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_GROUP_STR)), + &RDF::object_as_term(group), + )?; + } + + if let SHACLPath::Predicate { pred } = &self.path { + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_PATH_STR)), + &RDF::iri_s2term(pred), + )?; + } else { + unimplemented!() + } + + self.components + .iter() + .try_for_each(|component| component.write(&self.id, rdf))?; + + self.targets + .iter() + .try_for_each(|target| target.write(&self.id, rdf))?; + + if self.deactivated { + let term = OxTerm::Literal(OxLiteral::new_simple_literal("true")); + + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_DEACTIVATED_STR)), + &RDF::term_s2term(&term), + )?; + } + + if let Some(severity) = &self.severity { + let pred = match severity { + Severity::Violation => iri!(SH_VIOLATION_STR), + Severity::Info => iri!(SH_INFO_STR), + Severity::Warning => iri!(SH_WARNING_STR), + Severity::Generic(iri) => iri.get_iri().unwrap(), + }; + + rdf.add_triple( + &RDF::object_as_subject(&self.id).unwrap(), + &RDF::iri_s2iri(&iri!(SH_SEVERITY_STR)), + &RDF::iri_s2term(&pred), + )?; + } + + Ok(()) + } } impl Display for PropertyShape { diff --git a/shacl_ast/src/ast/shape.rs b/shacl_ast/src/ast/shape.rs index e6383db0..7668c15c 100644 --- a/shacl_ast/src/ast/shape.rs +++ b/shacl_ast/src/ast/shape.rs @@ -1,19 +1,36 @@ +use srdf::SRDFBuilder; use std::fmt::Display; use crate::{node_shape::NodeShape, property_shape::PropertyShape}; #[derive(Debug, Clone)] pub enum Shape { - NodeShape(NodeShape), + NodeShape(Box), PropertyShape(PropertyShape), } +impl Shape { + pub fn write(&self, rdf: &mut RDF) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + match self { + Shape::NodeShape(ns) => { + ns.write(rdf)?; + } + Shape::PropertyShape(ps) => { + ps.write(rdf)?; + } + } + Ok(()) + } +} + impl Display for Shape { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self { - Shape::NodeShape(ns) => write!(f, "{ns}")?, - Shape::PropertyShape(ps) => write!(f, "{ps}")?, - }; - Ok(()) + Shape::NodeShape(ns) => write!(f, "{ns}"), + Shape::PropertyShape(ps) => write!(f, "{ps}"), + } } } diff --git a/shacl_ast/src/ast/target.rs b/shacl_ast/src/ast/target.rs index b13bcf7c..f010ce70 100644 --- a/shacl_ast/src/ast/target.rs +++ b/shacl_ast/src/ast/target.rs @@ -1,7 +1,9 @@ +use iri_s::iri; use std::fmt::Display; +use crate::{SH_TARGET_CLASS_STR, SH_TARGET_NODE_STR}; use prefixmap::IriRef; -use srdf::RDFNode; +use srdf::{RDFNode, SRDFBuilder}; #[derive(Debug, Clone)] pub enum Target { @@ -11,6 +13,35 @@ pub enum Target { TargetObjectsOf(IriRef), } +impl Target { + pub fn write(&self, rdf_node: &RDFNode, rdf: &mut RDF) -> Result<(), RDF::Err> + where + RDF: SRDFBuilder, + { + match self { + Self::TargetNode(target_rdf_node) => rdf.add_triple( + &RDF::object_as_subject(rdf_node).unwrap(), + &RDF::iri_s2iri(&iri!(SH_TARGET_NODE_STR)), + &RDF::object_as_term(target_rdf_node), + ), + Self::TargetClass(target_rdf_node) => rdf.add_triple( + &RDF::object_as_subject(rdf_node).unwrap(), + &RDF::iri_s2iri(&iri!(SH_TARGET_CLASS_STR)), + &RDF::object_as_term(target_rdf_node), + ), + Self::TargetSubjectsOf(iri_ref) => rdf.add_triple( + &RDF::object_as_subject(rdf_node).unwrap(), + &RDF::iri_s2iri(&iri!(SH_TARGET_CLASS_STR)), + &RDF::iri_s2term(&iri_ref.get_iri().unwrap()), + ), + Self::TargetObjectsOf(iri_ref) => rdf.add_triple( + &RDF::object_as_subject(rdf_node).unwrap(), + &RDF::iri_s2iri(&iri!(SH_TARGET_CLASS_STR)), + &RDF::iri_s2term(&iri_ref.get_iri().unwrap()), + ), + } + } +} impl Display for Target { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/shacl_ast/src/converter/rdf_to_shacl/shacl_parser.rs b/shacl_ast/src/converter/rdf_to_shacl/shacl_parser.rs index 47b894d6..a3d27ed9 100644 --- a/shacl_ast/src/converter/rdf_to_shacl/shacl_parser.rs +++ b/shacl_ast/src/converter/rdf_to_shacl/shacl_parser.rs @@ -265,7 +265,7 @@ where RDF: FocusRDF + 'a, { node_shape() - .then(move |ns| ok(&Shape::NodeShape(ns))) + .then(move |ns| ok(&Shape::NodeShape(Box::new(ns)))) .or(property_shape(state).then(|ps| ok(&Shape::PropertyShape(ps)))) } } diff --git a/shacl_ast/src/converter/shacl_to_rdf/shacl_writer.rs b/shacl_ast/src/converter/shacl_to_rdf/shacl_writer.rs index 0007a640..5faf278c 100644 --- a/shacl_ast/src/converter/shacl_to_rdf/shacl_writer.rs +++ b/shacl_ast/src/converter/shacl_to_rdf/shacl_writer.rs @@ -1,7 +1,8 @@ -use srdf::{RDFFormat, SRDFBasic, SRDFBuilder}; +use crate::{Schema, SH_STR}; +use iri_s::IriS; +use srdf::{RDFFormat, SRDFBuilder, RDF, XSD}; use std::io::Write; - -use crate::{shape::Shape, Schema, SH_NODE_SHAPE, SH_PROPERTY_SHAPE}; +use std::str::FromStr; pub struct ShaclWriter where @@ -19,14 +20,18 @@ where } pub fn write(&mut self, schema: &Schema) -> Result<(), RDF::Err> { - self.rdf.add_prefix_map(schema.prefix_map())?; + let mut prefix_map = schema.prefix_map(); + prefix_map.insert("rdf", &IriS::from_str(RDF).unwrap()); + prefix_map.insert("xsd", &IriS::from_str(XSD).unwrap()); + prefix_map.insert("sh", &IriS::from_str(SH_STR).unwrap()); + + self.rdf.add_prefix_map(prefix_map)?; self.rdf.add_base(&schema.base())?; - for (node, shape) in schema.iter() { - match shape { - Shape::NodeShape(_) => self.rdf.add_type(node, node_shape::())?, - Shape::PropertyShape(_) => self.rdf.add_type(node, property_shape::())?, - } - } + + schema + .iter() + .try_for_each(|(_, shape)| shape.write(&mut self.rdf))?; + Ok(()) } @@ -43,16 +48,3 @@ where Self::new() } } -fn node_shape() -> RDF::Term -where - RDF: SRDFBasic, -{ - RDF::iri_s2term(&SH_NODE_SHAPE) -} - -fn property_shape() -> RDF::Term -where - RDF: SRDFBasic, -{ - RDF::iri_s2term(&SH_PROPERTY_SHAPE) -} diff --git a/shacl_ast/src/lib.rs b/shacl_ast/src/lib.rs index 90138923..1b9ec025 100644 --- a/shacl_ast/src/lib.rs +++ b/shacl_ast/src/lib.rs @@ -13,6 +13,3 @@ pub mod shacl_vocab; pub use ast::*; pub use converter::*; pub use shacl_vocab::*; - -#[cfg(test)] -mod tests {} diff --git a/srdf/src/srdf_basic.rs b/srdf/src/srdf_basic.rs index 6c03b46c..a1ad69a8 100644 --- a/srdf/src/srdf_basic.rs +++ b/srdf/src/srdf_basic.rs @@ -2,6 +2,7 @@ use std::fmt::{Debug, Display}; use std::hash::Hash; use iri_s::IriS; +use oxrdf::Term as OxTerm; use prefixmap::{PrefixMap, PrefixMapError}; use crate::Object; @@ -103,6 +104,8 @@ pub trait SRDFBasic { } fn iri_s2iri(iri_s: &IriS) -> Self::IRI; + + fn term_s2term(term: &OxTerm) -> Self::Term; fn bnode_id2bnode(id: &str) -> Self::BNode; fn iri_s2subject(iri_s: &IriS) -> Self::Subject { diff --git a/srdf/src/srdf_graph/README.md b/srdf/src/srdf_graph/README.md deleted file mode 100644 index 139597f9..00000000 --- a/srdf/src/srdf_graph/README.md +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/srdf/src/srdf_graph/srdfgraph.rs b/srdf/src/srdf_graph/srdfgraph.rs index 0b87c984..212fd031 100644 --- a/srdf/src/srdf_graph/srdfgraph.rs +++ b/srdf/src/srdf_graph/srdfgraph.rs @@ -235,6 +235,10 @@ impl SRDFBasic for SRDFGraph { IriS::from_named_node(iri) } + fn term_s2term(term: &OxTerm) -> Self::Term { + term.clone() + } + fn term_as_object(term: &OxTerm) -> Object { match term { OxTerm::BlankNode(bn) => Object::BlankNode(bn.as_str().to_string()), @@ -577,10 +581,10 @@ impl SRDFBuilder for SRDFGraph { Ok(()) } - fn add_type(&mut self, node: &crate::RDFNode, type_: Self::Term) -> Result<(), Self::Err> { + fn add_type(&mut self, node: &crate::RDFNode, r#type: Self::Term) -> Result<(), Self::Err> { match Self::object_as_subject(node) { Some(subj) => { - let triple = OxTriple::new(subj, rdf_type(), type_.clone()); + let triple = OxTriple::new(subj, rdf_type(), r#type.clone()); self.graph.insert(&triple); Ok(()) } @@ -600,7 +604,12 @@ impl SRDFBuilder for SRDFGraph { } fn serialize(&self, format: RDFFormat, write: W) -> Result<(), Self::Err> { - let serializer = RdfSerializer::from_format(cnv_rdf_format(format)); + let mut serializer = RdfSerializer::from_format(cnv_rdf_format(format)); + + for (prefix, iri) in &self.pm.map { + serializer = serializer.with_prefix(prefix, iri.as_str()).unwrap(); + } + let mut writer = serializer.serialize_to_write(write); for triple in self.graph.iter() { writer.write_triple(triple)?; @@ -874,7 +883,7 @@ mod tests { fn test_rdf_parser_macro() { use crate::SRDFGraph; use crate::{rdf_parser, satisfy, RDFNodeParse, SRDFBasic}; - use iri_s::{iri, IriS}; + use iri_s::iri; rdf_parser! { fn is_term['a, RDF](term: &'a RDF::Term)(RDF) -> () @@ -901,7 +910,7 @@ mod tests { fn test_rdf_list() { use crate::SRDFGraph; use crate::{property_value, rdf_list, set_focus, RDFNodeParse}; - use iri_s::{iri, IriS}; + use iri_s::iri; let s = r#"prefix : :x :p (1 2). @@ -924,7 +933,7 @@ fn test_rdf_list() { fn test_not() { use crate::SRDFGraph; use crate::{not, property_value, RDFNodeParse}; - use iri_s::{iri, IriS}; + use iri_s::iri; let s = r#"prefix : :x :p 1 . @@ -939,7 +948,7 @@ fn test_not() { fn test_iri() { use crate::SRDFGraph; use crate::{iri, RDFNodeParse}; - use iri_s::{iri, IriS}; + use iri_s::iri; let graph = SRDFGraph::new(); let x = iri!("http://example.org/x"); diff --git a/srdf/src/srdf_sparql/srdfsparql.rs b/srdf/src/srdf_sparql/srdfsparql.rs index d5b8e70b..892b043f 100644 --- a/srdf/src/srdf_sparql/srdfsparql.rs +++ b/srdf/src/srdf_sparql/srdfsparql.rs @@ -171,6 +171,10 @@ impl SRDFBasic for SRDFSparql { iri_s.as_named_node().clone() } + fn term_s2term(term: &OxTerm) -> Self::Term { + term.clone() + } + fn term_as_object(term: &Self::Term) -> Object { match term { Self::Term::BlankNode(bn) => Object::BlankNode(bn.to_string()),