Skip to content

Commit

Permalink
feat: implement SHACL to RDF
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcAntoine-Arnaud committed Jun 10, 2024
1 parent 5159ed5 commit 43b4347
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 58 deletions.
2 changes: 1 addition & 1 deletion iri_s/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ macro_rules! iri {
(
$lit: tt
) => {
IriS::new_unchecked($lit)
$crate::IriS::new_unchecked($lit)
};
}

Expand Down
2 changes: 2 additions & 0 deletions shacl_ast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
38 changes: 37 additions & 1 deletion shacl_ast/src/ast/message_map.rs
Original file line number Diff line number Diff line change
@@ -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<Option<Lang>, String>
messages: HashMap<Option<Lang>, String>,
}

impl MessageMap {
pub fn new() -> Self {
Self::default()
}

pub fn with_message(mut self, lang: Option<Lang>, message: String) -> Self {
self.messages.insert(lang, message);
self
}

pub fn messages(&self) -> &HashMap<Option<Lang>, String> {
&self.messages
}

pub fn to_term_iter(&self) -> impl Iterator<Item = OxTerm> + '_ {
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<Self, Self::Err> {
Ok(Self {
messages: HashMap::from([(None, s.to_string())]),
})
}
}
48 changes: 40 additions & 8 deletions shacl_ast/src/ast/node_shape.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use srdf::RDFNode;
use crate::{
component::Component, message_map::MessageMap, target::Target, SH_DESCRIPTION_STR, SH_NAME_STR,
SH_NODE_SHAPE,
};
use iri_s::iri;
use srdf::{RDFNode, SRDFBuilder};
use std::fmt::Display;

use crate::{component::Component, target::Target};

#[derive(Debug, Clone)]
pub struct NodeShape {
id: RDFNode,
Expand All @@ -14,9 +17,8 @@ pub struct NodeShape {
// deactivated: bool,
// message: MessageMap,
// severity: Option<Severity>,
// name: MessageMap,
// description: MessageMap,

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<NumericLiteral>,

Expand All @@ -36,8 +38,8 @@ impl NodeShape {
// deactivated: false,
// message: MessageMap::new(),
// severity: None,
// name: MessageMap::new(),
// description: MessageMap::new(),
name: MessageMap::new(),
description: MessageMap::new(),
// order: None,
// group: None,
// source_iri: None,
Expand Down Expand Up @@ -71,6 +73,36 @@ impl NodeShape {
self.closed = closed;
self
}

pub fn write<RDF>(&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()
.map(|term| {
rdf.add_triple(
&RDF::object_as_subject(&self.id).unwrap(),
&RDF::iri_s2iri(&iri!(SH_NAME_STR)),
&RDF::term_s2term(&term),
)
})
.collect::<Result<(), RDF::Err>>()?;

self.description
.to_term_iter()
.map(|term| {
rdf.add_triple(
&RDF::object_as_subject(&self.id).unwrap(),
&RDF::iri_s2iri(&iri!(SH_DESCRIPTION_STR)),
&RDF::term_s2term(&term),
)
})
.collect::<Result<(), RDF::Err>>()?;
Ok(())
}
}

impl Display for NodeShape {
Expand Down
107 changes: 94 additions & 13 deletions shacl_ast/src/ast/property_shape.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
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, target::Target, SH_DESCRIPTION_STR, SH_NAME_STR,
SH_ORDER_STR, SH_PATH_STR, SH_PROPERTY_SHAPE,
};

#[derive(Debug, Clone)]
pub struct PropertyShape {
// id: RDFNode,
id: RDFNode,
path: SHACLPath,
components: Vec<Component>,
targets: Vec<Target>,
Expand All @@ -15,21 +20,19 @@ pub struct PropertyShape {
// deactivated: bool,
// message: MessageMap,
// severity: Option<Severity>,
// name: MessageMap,
// description: MessageMap,

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<NumericLiteral>,

order: Option<NumericLiteral>,
// group: Option<RDFNode>,
// source_iri: Option<IriRef>,
// 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(),
Expand All @@ -39,15 +42,29 @@ impl PropertyShape {
// deactivated: false,
// message: MessageMap::new(),
// severity: None,
// name: MessageMap::new(),
// description: MessageMap::new(),
// order: 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<NumericLiteral>) -> Self {
self.order = order;
self
}

pub fn with_targets(mut self, targets: Vec<Target>) -> Self {
self.targets = targets;
self
Expand All @@ -67,6 +84,70 @@ impl PropertyShape {
self.closed = closed;
self
}

pub fn path(&self) -> &SHACLPath {
&self.path
}

pub fn name(&self) -> &MessageMap {
&self.name
}

pub fn description(&self) -> &MessageMap {
&self.description
}

pub fn write<RDF>(&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()
.map(|term| {
rdf.add_triple(
&RDF::object_as_subject(&self.id).unwrap(),
&RDF::iri_s2iri(&iri!(SH_NAME_STR)),
&RDF::term_s2term(&term),
)
})
.collect::<Result<(), RDF::Err>>()?;

self.description
.to_term_iter()
.map(|term| {
rdf.add_triple(
&RDF::object_as_subject(&self.id).unwrap(),
&RDF::iri_s2iri(&iri!(SH_DESCRIPTION_STR)),
&RDF::term_s2term(&term),
)
})
.collect::<Result<(), RDF::Err>>()?;

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 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!()
}

Ok(())
}
}

impl Display for PropertyShape {
Expand Down
25 changes: 21 additions & 4 deletions shacl_ast/src/ast/shape.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use srdf::SRDFBuilder;
use std::fmt::Display;

use crate::{node_shape::NodeShape, property_shape::PropertyShape};
Expand All @@ -8,12 +9,28 @@ pub enum Shape {
PropertyShape(PropertyShape),
}

impl Shape {
pub fn write<RDF>(&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}"),
}
}
}
39 changes: 16 additions & 23 deletions shacl_ast/src/converter/shacl_to_rdf/shacl_writer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use srdf::{RDFFormat, SRDFBasic, SRDFBuilder};
use crate::{Schema, SH_STR};
use srdf::{RDF, RDFFormat, SRDFBuilder, XSD};
use std::io::Write;

use crate::{shape::Shape, Schema, SH_NODE_SHAPE, SH_PROPERTY_SHAPE};
use std::str::FromStr;
use iri_s::IriS;

pub struct ShaclWriter<RDF>
where
Expand All @@ -19,14 +20,19 @@ 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::<RDF>())?,
Shape::PropertyShape(_) => self.rdf.add_type(node, property_shape::<RDF>())?,
}
}

schema
.iter()
.map(|(_, shape)| shape.write(&mut self.rdf))
.collect::<Result<(), RDF::Err>>()?;

Ok(())
}

Expand All @@ -43,16 +49,3 @@ where
Self::new()
}
}
fn node_shape<RDF>() -> RDF::Term
where
RDF: SRDFBasic,
{
RDF::iri_s2term(&SH_NODE_SHAPE)
}

fn property_shape<RDF>() -> RDF::Term
where
RDF: SRDFBasic,
{
RDF::iri_s2term(&SH_PROPERTY_SHAPE)
}
3 changes: 0 additions & 3 deletions shacl_ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,3 @@ pub mod shacl_vocab;
pub use ast::*;
pub use converter::*;
pub use shacl_vocab::*;

#[cfg(test)]
mod tests {}
Loading

0 comments on commit 43b4347

Please sign in to comment.