use crate::model::named_node::NamedNode; use crate::model::vocab::rdf; use crate::model::vocab::xsd; use crate::utils::Escaper; use chrono::prelude::*; use num_traits::identities::Zero; use num_traits::FromPrimitive; use num_traits::One; use num_traits::ToPrimitive; use ordered_float::OrderedFloat; use rust_decimal::Decimal; use std::borrow::Cow; use std::fmt; use std::option::Option; /// A RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal) /// /// The default string formatter is returning a N-Triples, Turtle and SPARQL compatible representation: /// ``` /// use rudf::model::Literal; /// use rudf::model::vocab::xsd; /// /// assert_eq!( /// "\"foo\\tbar\"", /// Literal::new_simple_literal("foo\tbar").to_string() /// ); /// /// assert_eq!( /// "\"1999-01-01\"^^", /// Literal::new_typed_literal("1999-01-01", xsd::DATE.clone()).to_string() /// ); /// /// assert_eq!( /// "\"foo\"@en", /// Literal::new_language_tagged_literal("foo", "en").to_string() /// ); /// ``` #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] pub struct Literal(LiteralContent); #[derive(PartialEq, Eq, Ord, PartialOrd, Debug, Clone, Hash)] enum LiteralContent { SimpleLiteral(String), String(String), LanguageTaggedString { value: String, language: String }, Boolean(bool), Float(OrderedFloat), Double(OrderedFloat), Integer(i128), Decimal(Decimal), NaiveDate(NaiveDate), NaiveTime(NaiveTime), DateTime(DateTime), NaiveDateTime(NaiveDateTime), 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(LiteralContent::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 { let value = value.into(); let datatype = datatype.into(); Literal(if datatype == *xsd::BOOLEAN { match value.as_str() { "true" | "1" => LiteralContent::Boolean(true), "false" | "0" => LiteralContent::Boolean(false), _ => LiteralContent::TypedLiteral { value, datatype }, } } else if datatype == *xsd::STRING { LiteralContent::String(value) } else if datatype == *xsd::FLOAT { match value.parse() { Ok(value) => LiteralContent::Float(OrderedFloat(value)), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } } else if datatype == *xsd::DOUBLE { match value.parse() { Ok(value) => LiteralContent::Double(OrderedFloat(value)), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } } else if datatype == *xsd::INTEGER || datatype == *xsd::BYTE || datatype == *xsd::SHORT || datatype == *xsd::INT || datatype == *xsd::LONG || datatype == *xsd::UNSIGNED_BYTE || datatype == *xsd::UNSIGNED_SHORT || datatype == *xsd::UNSIGNED_INT || datatype == *xsd::UNSIGNED_LONG || datatype == *xsd::POSITIVE_INTEGER || datatype == *xsd::NEGATIVE_INTEGER || datatype == *xsd::NON_POSITIVE_INTEGER || datatype == *xsd::NON_NEGATIVE_INTEGER { match value.parse() { Ok(value) => LiteralContent::Integer(value), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } } else if datatype == *xsd::DECIMAL { match value.parse() { Ok(value) => LiteralContent::Decimal(value), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } } else if datatype == *xsd::DATE { match NaiveDate::parse_from_str(&value, "%Y-%m-%d") { Ok(value) => LiteralContent::NaiveDate(value), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } } else if datatype == *xsd::TIME { match NaiveTime::parse_from_str(&value, "%H:%M:%S") { Ok(value) => LiteralContent::NaiveTime(value), Err(_) => LiteralContent::TypedLiteral { value, datatype }, } } else if datatype == *xsd::DATE_TIME || datatype == *xsd::DATE_TIME_STAMP { match DateTime::parse_from_rfc3339(&value) { Ok(value) => LiteralContent::DateTime(value), Err(_) => match NaiveDateTime::parse_from_str(&value, "%Y-%m-%dT%H:%M:%S") { Ok(value) => LiteralContent::NaiveDateTime(value), Err(_) => LiteralContent::TypedLiteral { value, datatype }, }, } } else { LiteralContent::TypedLiteral { value, datatype } }) } /// 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(LiteralContent::LanguageTaggedString { value: value.into(), language: language.into(), }) } /// The literal [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form) pub fn value(&self) -> Cow<'_, str> { match self.0 { LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) | LiteralContent::LanguageTaggedString { ref value, .. } | LiteralContent::TypedLiteral { ref value, .. } => Cow::Borrowed(value), LiteralContent::Boolean(value) => Cow::Owned(value.to_string()), LiteralContent::Float(value) => Cow::Owned(value.to_string()), LiteralContent::Double(value) => Cow::Owned(value.to_string()), LiteralContent::Integer(value) => Cow::Owned(value.to_string()), LiteralContent::Decimal(value) => Cow::Owned(value.to_string()), LiteralContent::NaiveDate(value) => Cow::Owned(value.to_string()), LiteralContent::NaiveTime(value) => Cow::Owned(value.to_string()), LiteralContent::DateTime(value) => Cow::Owned(value.to_string()), LiteralContent::NaiveDateTime(value) => Cow::Owned(value.to_string()), } } /// 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). /// Language tags are defined by the [BCP47](https://tools.ietf.org/html/bcp47). pub fn language(&self) -> Option<&str> { match self.0 { LiteralContent::LanguageTaggedString { ref 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 [rdf:langString](http://www.w3.org/1999/02/22-rdf-syntax-ns#langString). /// The datatype of [simple literals](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal) is [xsd:string](http://www.w3.org/2001/XMLSchema#string). pub fn datatype(&self) -> &NamedNode { match self.0 { LiteralContent::SimpleLiteral(_) | LiteralContent::String(_) => &xsd::STRING, LiteralContent::LanguageTaggedString { .. } => &rdf::LANG_STRING, LiteralContent::Boolean(_) => &xsd::BOOLEAN, LiteralContent::Float(_) => &xsd::FLOAT, LiteralContent::Double(_) => &xsd::DOUBLE, LiteralContent::Integer(_) => &xsd::INTEGER, LiteralContent::Decimal(_) => &xsd::DECIMAL, LiteralContent::NaiveDate(_) => &xsd::DATE, LiteralContent::NaiveTime(_) => &xsd::TIME, LiteralContent::DateTime(_) | LiteralContent::NaiveDateTime(_) => &xsd::DATE_TIME, LiteralContent::TypedLiteral { ref datatype, .. } => datatype, } } /// Checks if it could be considered as an RDF 1.0 [plain literal](https://www.w3.org/TR/rdf-concepts/#dfn-plain-literal). /// /// It returns true if the literal is a [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string) /// or have been created by `Literal::new_simple_literal`. pub fn is_plain(&self) -> bool { match self.0 { LiteralContent::SimpleLiteral(_) | LiteralContent::LanguageTaggedString { .. } => true, _ => false, } } /// Checks if the literal has the datatype [xsd:string](http://www.w3.org/2001/XMLSchema#string) and is valid pub fn is_string(&self) -> bool { match self.0 { LiteralContent::String(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:boolean](http://www.w3.org/2001/XMLSchema#boolean) and is valid pub fn is_boolean(&self) -> bool { match self.0 { LiteralContent::Boolean(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:float](http://www.w3.org/2001/XMLSchema#float) and is valid pub fn is_float(&self) -> bool { match self.0 { LiteralContent::Float(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:double](http://www.w3.org/2001/XMLSchema#double) and is valid pub fn is_double(&self) -> bool { match self.0 { LiteralContent::Double(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:integer](http://www.w3.org/2001/XMLSchema#integer) and is valid pub fn is_integer(&self) -> bool { match self.0 { LiteralContent::Integer(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:decimal](http://www.w3.org/2001/XMLSchema#decimal) or one of its sub datatype and is valid pub fn is_decimal(&self) -> bool { match self.0 { LiteralContent::Integer(_) | LiteralContent::Decimal(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:date](http://www.w3.org/2001/XMLSchema#date) and is valid pub fn is_date(&self) -> bool { match self.0 { LiteralContent::NaiveDate(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:date](http://www.w3.org/2001/XMLSchema#time) and is valid pub fn is_time(&self) -> bool { match self.0 { LiteralContent::NaiveTime(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:dateTime](http://www.w3.org/2001/XMLSchema#dateTime) or one of its sub datatype and is valid pub fn is_date_time(&self) -> bool { match self.0 { LiteralContent::DateTime(_) | LiteralContent::NaiveDateTime(_) => true, _ => false, } } /// Checks if the literal has the datatype [xsd:dateTimeStamp](http://www.w3.org/2001/XMLSchema#dateTimeStamp) or [xsd:dateTime](http://www.w3.org/2001/XMLSchema#dateTime) with a fixed timezone and is valid pub fn is_date_time_stamp(&self) -> bool { match self.0 { LiteralContent::DateTime(_) => true, _ => false, } } /// Returns the [effective boolean value](https://www.w3.org/TR/sparql11-query/#ebv) of the literal if it exists pub fn to_bool(&self) -> Option { match self.0 { LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { Some(!value.is_empty()) } LiteralContent::Boolean(value) => Some(value), LiteralContent::Float(value) => Some(!value.is_zero()), LiteralContent::Double(value) => Some(!value.is_zero()), LiteralContent::Integer(value) => Some(!value.is_zero()), LiteralContent::Decimal(value) => Some(!value.is_zero()), _ => None, } } /// Returns the value of this literal as an f32 if it exists following the rules of [XPath xsd:float casting](https://www.w3.org/TR/xpath-functions/#casting-to-float) pub fn to_float(&self) -> Option { match self.0 { LiteralContent::Float(value) => value.to_f32(), LiteralContent::Double(value) => value.to_f32(), LiteralContent::Integer(value) => value.to_f32(), LiteralContent::Decimal(value) => value.to_f32(), LiteralContent::Boolean(value) => Some(if value { 1. } else { 0. }), LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { value.parse().ok() } _ => None, } } /// Returns the value of this literal as an f64 if it exists following the rules of [XPath xsd:double casting](https://www.w3.org/TR/xpath-functions/#casting-to-double) pub fn to_double(&self) -> Option { match self.0 { LiteralContent::Float(value) => value.to_f64(), LiteralContent::Double(value) => value.to_f64(), LiteralContent::Integer(value) => value.to_f64(), LiteralContent::Decimal(value) => value.to_f64(), LiteralContent::Boolean(value) => Some(if value { 1. } else { 0. }), LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { value.parse().ok() } _ => None, } } /// Returns the value of this literal as an i128 if it exists following the rules of [XPath xsd:integer casting](https://www.w3.org/TR/xpath-functions/#casting-to-integer) pub fn to_integer(&self) -> Option { match self.0 { LiteralContent::Float(value) => value.to_i128(), LiteralContent::Double(value) => value.to_i128(), LiteralContent::Integer(value) => value.to_i128(), LiteralContent::Decimal(value) => value.to_i128(), LiteralContent::Boolean(value) => Some(if value { 1 } else { 0 }), LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { value.parse().ok() } _ => None, } } /// Returns the value of this literal as Decimal if it exists following the rules of [XPath xsd:decimal casting](https://www.w3.org/TR/xpath-functions/#casting-to-decimal) pub(crate) fn to_decimal(&self) -> Option { match self.0 { LiteralContent::Float(value) => Decimal::from_f32(*value), LiteralContent::Double(value) => Decimal::from_f64(*value), LiteralContent::Integer(value) => Decimal::from_i128(value), LiteralContent::Decimal(value) => Some(value), LiteralContent::Boolean(value) => Some(if value { Decimal::one() } else { Decimal::zero() }), LiteralContent::SimpleLiteral(ref value) | LiteralContent::String(ref value) => { value.parse().ok() } _ => None, } } /// Returns the value of this literal as NaiveDate if possible pub(crate) fn to_date(&self) -> Option { match self.0 { LiteralContent::NaiveDate(value) => Some(value), _ => None, } } /// Returns the value of this literal as NaiveTime if possible pub(crate) fn to_time(&self) -> Option { match self.0 { LiteralContent::NaiveTime(value) => Some(value), _ => None, } } /// Returns the value of this literal as NaiveDateTime if possible pub(crate) fn to_date_time(&self) -> Option { match self.0 { LiteralContent::DateTime(value) => Some(value.naive_utc()), LiteralContent::NaiveDateTime(value) => Some(value), _ => None, } } /// Returns the value of this literal as DateTime if possible pub(crate) fn to_date_time_stamp(&self) -> Option> { if let LiteralContent::DateTime(value) = self.0 { Some(value) } else { None } } } 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(LiteralContent::String(value.into())) } } impl From for Literal { fn from(value: String) -> Self { Literal(LiteralContent::String(value)) } } impl From for Literal { fn from(value: bool) -> Self { Literal(LiteralContent::Boolean(value)) } } impl From for Literal { fn from(value: i128) -> Self { Literal(LiteralContent::Integer(value)) } } impl From for Literal { fn from(value: i64) -> Self { Literal(LiteralContent::Integer(value.into())) } } impl From for Literal { fn from(value: i32) -> Self { Literal(LiteralContent::Integer(value.into())) } } impl From for Literal { fn from(value: i16) -> Self { Literal(LiteralContent::Integer(value.into())) } } impl From for Literal { fn from(value: u64) -> Self { Literal(LiteralContent::Integer(value.into())) } } impl From for Literal { fn from(value: u32) -> Self { Literal(LiteralContent::Integer(value.into())) } } impl From for Literal { fn from(value: u16) -> Self { Literal(LiteralContent::Integer(value.into())) } } impl From for Literal { fn from(value: f32) -> Self { Literal(LiteralContent::Float(value.into())) } } impl From for Literal { fn from(value: f64) -> Self { Literal(LiteralContent::Double(value.into())) } } impl From for Literal { fn from(value: Decimal) -> Self { Literal(LiteralContent::Decimal(value)) } } impl From for Literal { fn from(value: NaiveDate) -> Self { Literal(LiteralContent::NaiveDate(value)) } } impl From for Literal { fn from(value: NaiveTime) -> Self { Literal(LiteralContent::NaiveTime(value)) } } impl From> for Literal { fn from(value: DateTime) -> Self { Literal(LiteralContent::DateTime(value)) } } impl From for Literal { fn from(value: NaiveDateTime) -> Self { Literal(LiteralContent::NaiveDateTime(value)) } }