use super::error::IcddError;
use super::vocab;
use oxrdf::{NamedOrBlankNode, Term};
use std::collections::BTreeMap;
use std::io::Read;
#[derive(Debug, Clone)]
pub enum Obj {
Resource(String),
Literal(String),
}
impl Obj {
pub fn as_literal(&self) -> Option<&str> {
match self {
Obj::Literal(s) => Some(s),
_ => None,
}
}
pub fn as_resource(&self) -> Option<&str> {
match self {
Obj::Resource(s) => Some(s),
_ => None,
}
}
}
#[derive(Debug, Default)]
pub struct RdfGraph {
by_subject: BTreeMap<String, Vec<(String, Obj)>>,
}
impl RdfGraph {
pub fn parse<R: Read>(reader: R) -> Result<Self, IcddError> {
let mut graph = RdfGraph::default();
for (index, triple) in oxrdfxml::RdfXmlParser::new().for_reader(reader).enumerate() {
if index == crate::rdf::MAX_RDF_TRIPLES {
return Err(IcddError::NotConformant(format!(
"RDF graph exceeds the {}-triple limit",
crate::rdf::MAX_RDF_TRIPLES
)));
}
let triple = triple.map_err(|error| IcddError::Rdf(error.to_string()))?;
let subject = match triple.subject {
NamedOrBlankNode::NamedNode(n) => n.into_string(),
NamedOrBlankNode::BlankNode(b) => format!("_:{}", b.as_str()),
};
let predicate = triple.predicate.into_string();
let object = match triple.object {
Term::NamedNode(node) => Obj::Resource(node.into_string()),
Term::BlankNode(node) => Obj::Resource(format!("_:{}", node.as_str())),
Term::Literal(literal) => Obj::Literal(literal.value().to_string()),
#[allow(unreachable_patterns)]
_ => continue,
};
graph
.by_subject
.entry(subject)
.or_default()
.push((predicate, object));
}
Ok(graph)
}
pub fn subjects_of_type_ns(&self, namespace: &str, type_local: &str) -> Vec<&str> {
let expected = format!("{namespace}{type_local}");
self.by_subject
.iter()
.filter_map(|(subject, pairs)| {
pairs
.iter()
.any(|(predicate, object)| {
predicate == vocab::RDF_TYPE
&& matches!(object, Obj::Resource(iri) if iri == &expected)
})
.then_some(subject.as_str())
})
.collect()
}
pub fn has_type_ns(&self, subject: &str, namespace: &str, type_local: &str) -> bool {
let expected = format!("{namespace}{type_local}");
self.objects(subject, vocab::RDF_TYPE)
.iter()
.any(|object| matches!(object, Obj::Resource(iri) if iri == &expected))
}
pub fn objects(&self, subject: &str, predicate_iri: &str) -> Vec<&Obj> {
self.by_subject
.get(subject)
.map(|po| {
po.iter()
.filter(|(p, _)| p == predicate_iri)
.map(|(_, o)| o)
.collect()
})
.unwrap_or_default()
}
pub fn objects_ns(&self, subject: &str, namespace: &str, pred_local: &str) -> Vec<&Obj> {
self.objects(subject, &format!("{namespace}{pred_local}"))
}
pub fn literal_ns(&self, subject: &str, namespace: &str, pred_local: &str) -> Option<String> {
self.objects_ns(subject, namespace, pred_local)
.into_iter()
.find_map(|object| object.as_literal().map(str::to_string))
}
pub fn resource_ns(&self, subject: &str, namespace: &str, pred_local: &str) -> Option<String> {
self.objects_ns(subject, namespace, pred_local)
.into_iter()
.find_map(|object| object.as_resource().map(str::to_string))
}
}