use crate::{Term as RuleTerm, RuleAtom};
use oxirs_core::model::{
Term as RdfTerm, Subject, Predicate, Object, Triple,
NamedNode, BlankNode, Literal, Variable,
};
use anyhow::{anyhow, Result};
use std::convert::TryFrom;
pub fn rule_term_to_rdf_term(rule_term: &RuleTerm) -> Result<RdfTerm> {
match rule_term {
RuleTerm::Variable(var) => {
Ok(RdfTerm::Variable(Variable::new(var)?))
}
RuleTerm::Constant(iri) => {
if iri.starts_with("http://") || iri.starts_with("https://") || iri.contains(':') {
Ok(RdfTerm::NamedNode(NamedNode::new(iri)?))
} else if iri.starts_with("_:") {
Ok(RdfTerm::BlankNode(BlankNode::new(&iri[2..])?))
} else {
Ok(RdfTerm::Literal(Literal::new_simple_literal(iri)))
}
}
RuleTerm::Literal(value) => {
if let Some(type_sep) = value.find("^^") {
let literal_value = &value[..type_sep].trim_matches('"');
let datatype_iri = &value[type_sep + 2..].trim_matches('<').trim_matches('>');
Ok(RdfTerm::Literal(Literal::new_typed_literal(
literal_value,
NamedNode::new(datatype_iri)?
)))
} else if let Some(lang_sep) = value.find('@') {
let literal_value = &value[..lang_sep].trim_matches('"');
let lang_tag = &value[lang_sep + 1..];
Ok(RdfTerm::Literal(Literal::new_language_tagged_literal(
literal_value,
lang_tag,
)?))
} else {
Ok(RdfTerm::Literal(Literal::new_simple_literal(value.trim_matches('"'))))
}
}
}
}
pub fn rdf_term_to_rule_term(rdf_term: &RdfTerm) -> RuleTerm {
match rdf_term {
RdfTerm::Variable(var) => RuleTerm::Variable(var.name().to_string()),
RdfTerm::NamedNode(node) => RuleTerm::Constant(node.as_str().to_string()),
RdfTerm::BlankNode(node) => RuleTerm::Constant(format!("_:{}", node.id())),
RdfTerm::Literal(lit) => {
match lit.datatype() {
Some(dt) if dt.as_str() != "http://www.w3.org/2001/XMLSchema#string" => {
RuleTerm::Literal(format!("\"{}\"^^<{}>", lit.value(), dt.as_str()))
}
_ => match lit.language() {
Some(lang) => RuleTerm::Literal(format!("\"{}\"@{}", lit.value(), lang)),
None => RuleTerm::Literal(format!("\"{}\"", lit.value())),
}
}
}
RdfTerm::QuotedTriple(_) => {
RuleTerm::Constant("<<quoted-triple>>".to_string())
}
}
}
pub fn rule_atom_to_triple(atom: &RuleAtom) -> Result<Triple> {
match atom {
RuleAtom::Triple { subject, predicate, object } => {
let rdf_subject = rule_term_to_subject(subject)?;
let rdf_predicate = rule_term_to_predicate(predicate)?;
let rdf_object = rule_term_to_object(object)?;
Ok(Triple::new(rdf_subject, rdf_predicate, rdf_object))
}
RuleAtom::Builtin { .. } => {
Err(anyhow!("Cannot convert builtin atom to RDF triple"))
}
}
}
fn rule_term_to_subject(term: &RuleTerm) -> Result<Subject> {
let rdf_term = rule_term_to_rdf_term(term)?;
match rdf_term {
RdfTerm::NamedNode(n) => Ok(Subject::NamedNode(n)),
RdfTerm::BlankNode(b) => Ok(Subject::BlankNode(b)),
RdfTerm::Variable(v) => Ok(Subject::Variable(v)),
RdfTerm::QuotedTriple(qt) => Ok(Subject::QuotedTriple(qt)),
RdfTerm::Literal(_) => Err(anyhow!("Literals cannot be used as subjects")),
}
}
fn rule_term_to_predicate(term: &RuleTerm) -> Result<Predicate> {
let rdf_term = rule_term_to_rdf_term(term)?;
match rdf_term {
RdfTerm::NamedNode(n) => Ok(Predicate::NamedNode(n)),
RdfTerm::Variable(v) => Ok(Predicate::Variable(v)),
_ => Err(anyhow!("Only named nodes and variables can be used as predicates")),
}
}
fn rule_term_to_object(term: &RuleTerm) -> Result<Object> {
let rdf_term = rule_term_to_rdf_term(term)?;
match rdf_term {
RdfTerm::NamedNode(n) => Ok(Object::NamedNode(n)),
RdfTerm::BlankNode(b) => Ok(Object::BlankNode(b)),
RdfTerm::Literal(l) => Ok(Object::Literal(l)),
RdfTerm::Variable(v) => Ok(Object::Variable(v)),
RdfTerm::QuotedTriple(qt) => Ok(Object::QuotedTriple(qt)),
}
}
pub fn triple_to_rule_atom(triple: &Triple) -> RuleAtom {
let subject = subject_to_rule_term(triple.subject());
let predicate = predicate_to_rule_term(triple.predicate());
let object = object_to_rule_term(triple.object());
RuleAtom::Triple { subject, predicate, object }
}
fn subject_to_rule_term(subject: &Subject) -> RuleTerm {
match subject {
Subject::NamedNode(n) => RuleTerm::Constant(n.as_str().to_string()),
Subject::BlankNode(b) => RuleTerm::Constant(format!("_:{}", b.id())),
Subject::Variable(v) => RuleTerm::Variable(v.name().to_string()),
Subject::QuotedTriple(_) => RuleTerm::Constant("<<quoted-triple>>".to_string()),
}
}
fn predicate_to_rule_term(predicate: &Predicate) -> RuleTerm {
match predicate {
Predicate::NamedNode(n) => RuleTerm::Constant(n.as_str().to_string()),
Predicate::Variable(v) => RuleTerm::Variable(v.name().to_string()),
}
}
fn object_to_rule_term(object: &Object) -> RuleTerm {
match object {
Object::NamedNode(n) => RuleTerm::Constant(n.as_str().to_string()),
Object::BlankNode(b) => RuleTerm::Constant(format!("_:{}", b.id())),
Object::Literal(l) => {
match l.datatype() {
Some(dt) if dt.as_str() != "http://www.w3.org/2001/XMLSchema#string" => {
RuleTerm::Literal(format!("\"{}\"^^<{}>", l.value(), dt.as_str()))
}
_ => match l.language() {
Some(lang) => RuleTerm::Literal(format!("\"{}\"@{}", l.value(), lang)),
None => RuleTerm::Literal(l.value().to_string()),
}
}
}
Object::Variable(v) => RuleTerm::Variable(v.name().to_string()),
Object::QuotedTriple(_) => RuleTerm::Constant("<<quoted-triple>>".to_string()),
}
}
pub fn load_rdf_data(triples: Vec<Triple>) -> Vec<RuleAtom> {
triples.iter()
.map(triple_to_rule_atom)
.collect()
}
pub fn export_rule_atoms(atoms: Vec<RuleAtom>) -> Result<Vec<Triple>> {
atoms.iter()
.filter_map(|atom| match atom {
RuleAtom::Triple { .. } => Some(rule_atom_to_triple(atom)),
RuleAtom::Builtin { .. } => None,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_variable_conversion() -> anyhow::Result<()> {
let rule_var = RuleTerm::Variable("x".to_string());
let rdf_term = rule_term_to_rdf_term(&rule_var)?;
assert!(matches!(rdf_term, RdfTerm::Variable(_)));
if let RdfTerm::Variable(v) = rdf_term {
assert_eq!(v.name(), "x");
}
let back = rdf_term_to_rule_term(&RdfTerm::Variable(Variable::new("x")?));
assert_eq!(back, rule_var);
Ok(())
}
#[test]
fn test_iri_conversion() -> anyhow::Result<()> {
let rule_iri = RuleTerm::Constant("http://example.org/test".to_string());
let rdf_term = rule_term_to_rdf_term(&rule_iri)?;
assert!(matches!(rdf_term, RdfTerm::NamedNode(_)));
if let RdfTerm::NamedNode(n) = &rdf_term {
assert_eq!(n.as_str(), "http://example.org/test");
}
let back = rdf_term_to_rule_term(&rdf_term);
assert_eq!(back, rule_iri);
Ok(())
}
#[test]
fn test_blank_node_conversion() -> anyhow::Result<()> {
let rule_blank = RuleTerm::Constant("_:b1".to_string());
let rdf_term = rule_term_to_rdf_term(&rule_blank)?;
assert!(matches!(rdf_term, RdfTerm::BlankNode(_)));
if let RdfTerm::BlankNode(b) = &rdf_term {
assert_eq!(b.id(), "b1");
}
Ok(())
}
#[test]
fn test_literal_conversion() -> anyhow::Result<()> {
let rule_lit = RuleTerm::Literal("\"hello\"".to_string());
let rdf_term = rule_term_to_rdf_term(&rule_lit)?;
assert!(matches!(rdf_term, RdfTerm::Literal(_)));
if let RdfTerm::Literal(l) = &rdf_term {
assert_eq!(l.value(), "hello");
}
let typed_lit = RuleTerm::Literal("\"42\"^^<http://www.w3.org/2001/XMLSchema#integer>".to_string());
let rdf_typed = rule_term_to_rdf_term(&typed_lit)?;
if let RdfTerm::Literal(l) = &rdf_typed {
assert_eq!(l.value(), "42");
assert!(l.datatype().is_some());
}
let lang_lit = RuleTerm::Literal("\"bonjour\"@fr".to_string());
let rdf_lang = rule_term_to_rdf_term(&lang_lit)?;
if let RdfTerm::Literal(l) = &rdf_lang {
assert_eq!(l.value(), "bonjour");
assert_eq!(l.language(), Some("fr"));
}
Ok(())
}
#[test]
fn test_triple_conversion() -> anyhow::Result<()> {
let rule_atom = RuleAtom::Triple {
subject: RuleTerm::Constant("http://example.org/alice".to_string()),
predicate: RuleTerm::Constant("http://xmlns.com/foaf/0.1/knows".to_string()),
object: RuleTerm::Constant("http://example.org/bob".to_string()),
};
let triple = rule_atom_to_triple(&rule_atom)?;
assert_eq!(triple.subject().to_string(), "<http://example.org/alice>");
assert_eq!(triple.predicate().to_string(), "<http://xmlns.com/foaf/0.1/knows>");
assert_eq!(triple.object().to_string(), "<http://example.org/bob>");
let back = triple_to_rule_atom(&triple);
assert_eq!(back, rule_atom);
Ok(())
}
}