diff --git a/src/model/blank_node.rs b/src/model/blank_node.rs new file mode 100644 index 00000000..fade3969 --- /dev/null +++ b/src/model/blank_node.rs @@ -0,0 +1,30 @@ +use std::fmt; +use std::ops::Deref; +use uuid::Uuid; + +/// A RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) +#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] +pub struct BlankNode { + id: Uuid, +} + +impl Deref for BlankNode { + type Target = Uuid; + + fn deref(&self) -> &Uuid { + &self.id + } +} + +impl fmt::Display for BlankNode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "_:{}", self.id) + } +} + +impl Default for BlankNode { + /// Builds a new RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) with a unique id + fn default() -> Self { + BlankNode { id: Uuid::new_v4() } + } +} diff --git a/src/model/literal.rs b/src/model/literal.rs new file mode 100644 index 00000000..8c60ee56 --- /dev/null +++ b/src/model/literal.rs @@ -0,0 +1,140 @@ +use model::named_node::NamedNode; +use model::vocab::rdf; +use model::vocab::xsd; +use std::borrow::Cow; +use std::fmt; +use std::option::Option; +use utils::Escaper; + +/// A RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) +#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] +pub enum Literal { + SimpleLiteral(String), + String(String), + LanguageTaggedString { value: String, language: String }, + Boolean(bool), + TypedLiteral { value: String, datatype: NamedNode }, +} + +impl Literal { + /// Builds a RDF [simple literal](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal) + pub fn new_simple_literal(value: impl Into) -> Self { + Literal::SimpleLiteral(value.into()) + } + + /// Builds a RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) with a [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri) + pub fn new_typed_literal(value: impl Into, datatype: impl Into) -> Self { + //TODO: proper casts + Literal::TypedLiteral { + value: value.into(), + datatype: datatype.into(), + } + } + + /// Builds a RDF [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) + pub fn new_language_tagged_literal( + value: impl Into, + language: impl Into, + ) -> Self { + Literal::LanguageTaggedString { + value: value.into(), + language: language.into(), + } + } + + /// The literal [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form) + pub fn value<'a>(&'a self) -> Cow<'a, String> { + match self { + Literal::SimpleLiteral(value) => Cow::Borrowed(value), + Literal::String(value) => Cow::Borrowed(value), + Literal::LanguageTaggedString { value, .. } => Cow::Borrowed(value), + Literal::Boolean(value) => Cow::Owned(value.to_string()), + Literal::TypedLiteral { value, .. } => Cow::Borrowed(value), + } + } + + /// The literal [language tag](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag) if it is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) + pub fn language(&self) -> Option<&str> { + match self { + Literal::LanguageTaggedString { language, .. } => Some(language), + _ => None, + } + } + + /// The literal [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri) + /// The datatype of [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) is always http://www.w3.org/1999/02/22-rdf-syntax-ns#langString + pub fn datatype(&self) -> &NamedNode { + match self { + Literal::SimpleLiteral(_) => &xsd::STRING, + Literal::String(_) => &xsd::STRING, + Literal::LanguageTaggedString { .. } => &rdf::LANG_STRING, + Literal::Boolean(_) => &xsd::BOOLEAN, + Literal::TypedLiteral { datatype, .. } => datatype, + } + } + + pub fn is_plain(&self) -> bool { + match self { + Literal::SimpleLiteral(_) => true, + Literal::LanguageTaggedString { .. } => true, + _ => false, + } + } +} + +impl fmt::Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_plain() { + self.language() + .map(|lang| write!(f, "\"{}\"@{}", self.value().escape(), lang)) + .unwrap_or_else(|| write!(f, "\"{}\"", self.value().escape())) + } else { + write!(f, "\"{}\"^^{}", self.value().escape(), self.datatype()) + } + } +} + +impl<'a> From<&'a str> for Literal { + fn from(value: &'a str) -> Self { + Literal::String(value.into()) + } +} + +impl From for Literal { + fn from(value: String) -> Self { + Literal::String(value) + } +} + +impl From for Literal { + fn from(value: bool) -> Self { + Literal::Boolean(value) + } +} + +impl From for Literal { + fn from(value: usize) -> Self { + Literal::TypedLiteral { + value: value.to_string(), + datatype: xsd::INTEGER.clone(), + } + } +} + +impl From for Literal { + fn from(value: f32) -> Self { + Literal::TypedLiteral { + value: value.to_string(), + datatype: xsd::FLOAT.clone(), + } + } +} + +impl From for Literal { + fn from(value: f64) -> Self { + Literal::TypedLiteral { + value: value.to_string(), + datatype: xsd::DOUBLE.clone(), + } + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index c87dbf9b..444963f5 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,2 +1,17 @@ -pub mod data; +///! Implements data structures for https://www.w3.org/TR/rdf11-concepts/ +///! Inspired by [RDFjs](http://rdf.js.org/) +mod blank_node; +mod literal; +mod named_node; +mod triple; pub mod vocab; + +pub use model::blank_node::BlankNode; +pub use model::literal::Literal; +pub use model::named_node::NamedNode; +pub use model::triple::NamedOrBlankNode; +pub use model::triple::Quad; +pub use model::triple::QuadLike; +pub use model::triple::Term; +pub use model::triple::Triple; +pub use model::triple::TripleLike; diff --git a/src/model/named_node.rs b/src/model/named_node.rs new file mode 100644 index 00000000..ee43d22b --- /dev/null +++ b/src/model/named_node.rs @@ -0,0 +1,57 @@ +use std::fmt; +use std::ops::Deref; +use std::str::FromStr; +use std::sync::Arc; +use url::ParseError; +use url::Url; + +/// A RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) +#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] +pub struct NamedNode { + iri: Arc, +} + +impl fmt::Display for NamedNode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "<{}>", self.iri) + } +} + +impl NamedNode { + /// Builds a RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) + pub fn new(iri: impl Into) -> Self { + Self { + iri: Arc::new(iri.into()), + } + } + + pub fn value(&self) -> &str { + self.iri.as_str() + } + + pub fn url(&self) -> &Url { + &self.iri + } +} + +impl Deref for NamedNode { + type Target = Url; + + fn deref(&self) -> &Url { + &self.iri + } +} + +impl From for NamedNode { + fn from(url: Url) -> Self { + Self { iri: Arc::new(url) } + } +} + +impl FromStr for NamedNode { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + Ok(NamedNode::new(Url::parse(s)?)) + } +} diff --git a/src/model/data.rs b/src/model/triple.rs similarity index 53% rename from src/model/data.rs rename to src/model/triple.rs index 82116529..533941b6 100644 --- a/src/model/data.rs +++ b/src/model/triple.rs @@ -1,235 +1,7 @@ -use std::borrow::Cow; -///! Implements data structures for https://www.w3.org/TR/rdf11-concepts/ -///! Inspired by [RDFjs](http://rdf.js.org/) +use model::blank_node::BlankNode; +use model::literal::Literal; +use model::named_node::NamedNode; use std::fmt; -use std::ops::Deref; -use std::option::Option; -use std::str::FromStr; -use std::sync::Arc; -use url::ParseError; -use url::Url; -use utils::Escaper; -use uuid::Uuid; - -/// A RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) -#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] -pub struct NamedNode { - iri: Arc, -} - -impl NamedNode { - /// Builds a RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) - pub fn new(iri: impl Into) -> Self { - Self { - iri: Arc::new(iri.into()), - } - } - - pub fn value(&self) -> &str { - self.iri.as_str() - } - - pub fn url(&self) -> &Url { - &self.iri - } -} - -impl Deref for NamedNode { - type Target = Url; - - fn deref(&self) -> &Url { - &self.iri - } -} - -impl fmt::Display for NamedNode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "<{}>", self.iri) - } -} - -impl FromStr for NamedNode { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - Ok(NamedNode::new(Url::parse(s)?)) - } -} - -/// A RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) -#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] -pub struct BlankNode { - id: Uuid, -} - -impl Deref for BlankNode { - type Target = Uuid; - - fn deref(&self) -> &Uuid { - &self.id - } -} - -impl fmt::Display for BlankNode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "_:{}", self.id) - } -} - -impl Default for BlankNode { - /// Builds a new RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) with a unique id - fn default() -> Self { - BlankNode { id: Uuid::new_v4() } - } -} - -/// A RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) -#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] -pub enum Literal { - SimpleLiteral(String), - String(String), - LanguageTaggedString { value: String, language: String }, - Boolean(bool), - TypedLiteral { value: String, datatype: NamedNode }, -} - -lazy_static! { - static ref XSD_BOOLEAN: NamedNode = - NamedNode::from_str("http://www.w3.org/2001/XMLSchema#boolean").unwrap(); - static ref XSD_DOUBLE: NamedNode = - NamedNode::from_str("http://www.w3.org/2001/XMLSchema#double").unwrap(); - static ref XSD_FLOAT: NamedNode = - NamedNode::from_str("http://www.w3.org/2001/XMLSchema#float").unwrap(); - static ref XSD_INTEGER: NamedNode = - NamedNode::from_str("http://www.w3.org/2001/XMLSchema#integer").unwrap(); - static ref XSD_STRING: NamedNode = - NamedNode::from_str("http://www.w3.org/2001/XMLSchema#string").unwrap(); - static ref RDF_LANG_STRING: NamedNode = - NamedNode::from_str("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString").unwrap(); -} - -impl Literal { - /// Builds a RDF [simple literal](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal) - pub fn new_simple_literal(value: impl Into) -> Self { - Literal::SimpleLiteral(value.into()) - } - - /// Builds a RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) with a [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri) - pub fn new_typed_literal(value: impl Into, datatype: impl Into) -> Self { - //TODO: proper casts - Literal::TypedLiteral { - value: value.into(), - datatype: datatype.into(), - } - } - - /// Builds a RDF [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) - pub fn new_language_tagged_literal( - value: impl Into, - language: impl Into, - ) -> Self { - Literal::LanguageTaggedString { - value: value.into(), - language: language.into(), - } - } - - /// The literal [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form) - pub fn value<'a>(&'a self) -> Cow<'a, String> { - match self { - Literal::SimpleLiteral(value) => Cow::Borrowed(value), - Literal::String(value) => Cow::Borrowed(value), - Literal::LanguageTaggedString { value, .. } => Cow::Borrowed(value), - Literal::Boolean(value) => Cow::Owned(value.to_string()), - Literal::TypedLiteral { value, .. } => Cow::Borrowed(value), - } - } - - /// The literal [language tag](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag) if it is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) - pub fn language(&self) -> Option<&str> { - match self { - Literal::LanguageTaggedString { language, .. } => Some(language), - _ => None, - } - } - - /// The literal [datatype](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri) - /// The datatype of [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) is always http://www.w3.org/1999/02/22-rdf-syntax-ns#langString - pub fn datatype(&self) -> &NamedNode { - match self { - Literal::SimpleLiteral(_) => &XSD_STRING, - Literal::String(_) => &XSD_STRING, - Literal::LanguageTaggedString { .. } => &RDF_LANG_STRING, - Literal::Boolean(_) => &XSD_BOOLEAN, - Literal::TypedLiteral { datatype, .. } => datatype, - } - } - - pub fn is_plain(&self) -> bool { - match self { - Literal::SimpleLiteral(_) => true, - Literal::LanguageTaggedString { .. } => true, - _ => false, - } - } -} - -impl fmt::Display for Literal { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.is_plain() { - self.language() - .map(|lang| write!(f, "\"{}\"@{}", self.value().escape(), lang)) - .unwrap_or_else(|| write!(f, "\"{}\"", self.value().escape())) - } else { - write!(f, "\"{}\"^^{}", self.value().escape(), self.datatype()) - } - } -} - -impl<'a> From<&'a str> for Literal { - fn from(value: &'a str) -> Self { - Literal::String(value.into()) - } -} - -impl From for Literal { - fn from(value: String) -> Self { - Literal::String(value) - } -} - -impl From for Literal { - fn from(value: bool) -> Self { - Literal::Boolean(value) - } -} - -impl From for Literal { - fn from(value: usize) -> Self { - Literal::TypedLiteral { - value: value.to_string(), - datatype: XSD_INTEGER.clone(), - } - } -} - -impl From for Literal { - fn from(value: f32) -> Self { - Literal::TypedLiteral { - value: value.to_string(), - datatype: XSD_FLOAT.clone(), - } - } -} - -impl From for Literal { - fn from(value: f64) -> Self { - Literal::TypedLiteral { - value: value.to_string(), - datatype: XSD_DOUBLE.clone(), - } - } -} /// The union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) and [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node). #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] diff --git a/src/model/vocab.rs b/src/model/vocab.rs index d2771661..15b8652c 100644 --- a/src/model/vocab.rs +++ b/src/model/vocab.rs @@ -1,7 +1,7 @@ ///! Provides ready to use NamedNode for basic RDF vocabularies pub mod rdf { - use model::data::NamedNode; + use model::named_node::NamedNode; use std::str::FromStr; lazy_static! { @@ -23,7 +23,7 @@ pub mod rdf { } pub mod rdfs { - use model::data::NamedNode; + use model::named_node::NamedNode; use std::str::FromStr; lazy_static! { @@ -34,7 +34,7 @@ pub mod rdfs { pub mod xsd { ///! NamedNodes for [RDF compatible XSD datatypes](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-compatible-xsd-types) - use model::data::NamedNode; + use model::named_node::NamedNode; use std::str::FromStr; lazy_static! { diff --git a/src/rio/ntriples/mod.rs b/src/rio/ntriples/mod.rs index 7ca458bb..75cb1701 100644 --- a/src/rio/ntriples/mod.rs +++ b/src/rio/ntriples/mod.rs @@ -4,7 +4,7 @@ mod grammar { include!(concat!(env!("OUT_DIR"), "/ntriples_grammar.rs")); } -use model::data::*; +use model::*; use rio::*; use std::collections::BTreeMap; use std::io::BufRead; diff --git a/src/rio/ntriples/ntriples_grammar.rustpeg b/src/rio/ntriples/ntriples_grammar.rustpeg index 6ca4c2db..7b1fb861 100644 --- a/src/rio/ntriples/ntriples_grammar.rustpeg +++ b/src/rio/ntriples/ntriples_grammar.rustpeg @@ -3,7 +3,7 @@ use std::iter::FromIterator; use std::char; use std::str::FromStr; -use model::data::*; +use model::*; use std::collections::BTreeMap; #![arguments(bnodes_map: &mut BTreeMap)] diff --git a/src/rio/turtle/mod.rs b/src/rio/turtle/mod.rs index 0b772340..9e791765 100644 --- a/src/rio/turtle/mod.rs +++ b/src/rio/turtle/mod.rs @@ -3,7 +3,7 @@ mod grammar { include!(concat!(env!("OUT_DIR"), "/turtle_grammar.rs")); - use model::data::*; + use model::*; use rio::*; use std::collections::BTreeMap; use std::collections::HashMap; diff --git a/src/sparql/ast.rs b/src/sparql/ast.rs index 4f0151d6..220116c0 100644 --- a/src/sparql/ast.rs +++ b/src/sparql/ast.rs @@ -1,4 +1,4 @@ -use model::data::*; +use model::*; use sparql::model::*; use std::fmt; use std::ops::Add; diff --git a/src/sparql/model.rs b/src/sparql/model.rs index 1b9bfbfa..f64cda9a 100644 --- a/src/sparql/model.rs +++ b/src/sparql/model.rs @@ -1,4 +1,4 @@ -use model::data::*; +use model::*; use std::fmt; use uuid::Uuid; diff --git a/src/sparql/parser.rs b/src/sparql/parser.rs index 3291ef26..4e3bcbf6 100644 --- a/src/sparql/parser.rs +++ b/src/sparql/parser.rs @@ -3,7 +3,7 @@ use std::char; use std::str::Chars; mod grammar { - use model::data::*; + use model::*; use rio::RioError; use rio::RioResult; use sparql::ast::*; diff --git a/src/store/isomorphism.rs b/src/store/isomorphism.rs index fb317c38..a1673af7 100644 --- a/src/store/isomorphism.rs +++ b/src/store/isomorphism.rs @@ -1,4 +1,4 @@ -use model::data::*; +use model::*; use std::collections::hash_map::DefaultHasher; use std::collections::BTreeSet; use std::collections::HashMap; diff --git a/src/store/memory.rs b/src/store/memory.rs index a104125f..a6f8a206 100644 --- a/src/store/memory.rs +++ b/src/store/memory.rs @@ -1,5 +1,5 @@ -use model::data::*; use model::vocab::rdf; +use model::*; use std::collections::HashSet; use std::fmt; use std::iter::FromIterator; diff --git a/tests/rdf_test_cases.rs b/tests/rdf_test_cases.rs index e37b4be3..97af40ea 100644 --- a/tests/rdf_test_cases.rs +++ b/tests/rdf_test_cases.rs @@ -8,9 +8,9 @@ extern crate url; use reqwest::Client; use reqwest::Response; -use rudf::model::data::*; use rudf::model::vocab::rdf; use rudf::model::vocab::rdfs; +use rudf::model::*; use rudf::rio::ntriples::read_ntriples; use rudf::rio::turtle::read_turtle; use rudf::rio::RioError; @@ -244,7 +244,7 @@ impl<'a> TestManifest<'a> { } pub mod mf { - use rudf::model::data::NamedNode; + use rudf::model::NamedNode; use std::str::FromStr; lazy_static! {