use crate::data::{Pre, RdfName, Subject, TriplesError};
use crate::turtle::LineParser;
use std::collections::HashMap;
use std::fmt;
use tracing::trace;
#[allow(dead_code)] #[derive(Debug, Eq, PartialEq, Clone)]
pub enum ParsedLine {
Prefix(Pre, RdfName),
Subject(Option<Pre>, RdfName),
PredObj(Option<Pre>, RdfName, Option<Pre>, String, bool),
PredObjTerm(Option<Pre>, RdfName, Option<Pre>, String),
ContinueObj(Option<Pre>, RdfName, bool),
SubjectPredObj(
Option<Pre>,
RdfName,
Option<Pre>,
RdfName,
Option<Pre>,
String,
bool,
),
}
pub struct TurtleStream {
state: ParserState,
parser: LineParser,
prefixes: HashMap<Pre, RdfName>,
current_subject: Option<Subject>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum ParserState {
SubjectLoading,
PredicateLoading,
ObjectLoading(Option<Pre>, RdfName),
}
impl fmt::Display for ParserState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::SubjectLoading => write!(f, "subject loading state"),
Self::PredicateLoading => write!(f, "predicate loading state"),
Self::ObjectLoading(pre, name) => write!(f, "object loading state: {pre:?}:{name}"),
}
}
}
impl Default for TurtleStream {
fn default() -> Self {
Self::new()
}
}
impl TurtleStream {
#[must_use]
pub fn get_state(&self) -> ParserState {
self.state.clone()
}
#[must_use]
pub fn new() -> Self {
let parser = LineParser::new();
Self {
state: ParserState::SubjectLoading,
parser,
prefixes: HashMap::new(),
current_subject: None,
}
}
fn resolve_obj_iri(
&self,
prefix: Option<&Pre>,
object: &String,
) -> Result<String, TriplesError> {
prefix.map_or_else(
|| Ok(object.clone()),
|ns| {
self.prefixes.get(ns).map_or_else(
|| {
Err(TriplesError::UnresolvableURIPrefix {
prefix_name: ns.to_string(),
name: object.to_string(),
})
},
|ns| {
let ns_str = ns.to_string();
if ns_str.ends_with('#') {
Ok(format!("{ns_str}{object}"))
} else {
Ok(format!("{ns_str}/{object}"))
}
},
)
},
)
}
fn resolve_iri(
&self,
prefix: Option<&Pre>,
local_name: &RdfName,
) -> Result<String, TriplesError> {
prefix.map_or_else(
|| Ok(local_name.to_string()),
|ns| {
self.prefixes.get(ns).map_or_else(
|| {
Err(TriplesError::UnresolvableURIPrefix {
prefix_name: ns.to_string(),
name: local_name.to_string(),
})
},
|ns| {
let ns_str = ns.to_string();
trace!("resolve_iri for ns: {ns_str}");
if ns_str.ends_with('#') {
Ok(format!("{ns_str}{local_name}"))
} else {
Ok(format!("{ns_str}/{local_name}"))
}
},
)
},
)
}
fn handle_subject(
&mut self,
prefix: Option<&Pre>,
name: &RdfName,
) -> Result<Option<Subject>, TriplesError> {
if self.current_subject.is_some() {
return Err(TriplesError::PreviousSubjectNotComplete);
}
let subject_iri_text = self.resolve_iri(prefix, name)?;
let subject_iri = RdfName::new(subject_iri_text);
self.current_subject = Some(Subject::new(subject_iri));
self.state = ParserState::PredicateLoading;
Ok(None)
}
fn handle_predicate(
&mut self,
prefix: Option<&Pre>,
predicate: &RdfName,
opre: Option<&Pre>,
object: &str,
has_more: bool,
) -> Result<Option<Subject>, TriplesError> {
let predicate_iri_text = self.resolve_iri(prefix, predicate)?;
let predicate_iri = RdfName::new(predicate_iri_text);
let object_iri_text = self.resolve_obj_iri(opre, &object.to_string())?;
if has_more {
self.state = ParserState::ObjectLoading(prefix.cloned(), predicate.clone());
}
self.current_subject
.as_mut()
.map_or(Err(TriplesError::NoSubjectDeclaired), |subject| {
subject.add(predicate_iri, object_iri_text);
Ok(None)
})
}
fn handle_predicate_term(
&mut self,
prefix: Option<&Pre>,
predicate: &RdfName,
objpre: Option<&Pre>,
object: &str,
) -> Result<Option<Subject>, TriplesError> {
let result = self.handle_predicate(prefix, predicate, objpre, object, false)?;
if self.current_subject.is_some() {
let finished_subject = self.current_subject.clone();
self.current_subject = None;
self.state = ParserState::SubjectLoading;
match finished_subject {
Some(s) => {
return Ok(Some(s));
}
_ => return Err(TriplesError::NoSubjectDeclaired),
}
}
Ok(result)
}
pub fn load(&mut self, line: &str) -> Result<Option<Subject>, TriplesError> {
trace!("load: {line}");
let parsed = &self
.parser
.parse(line)
.map_err(|e| TriplesError::ParseError {
reason: e.to_string(),
})?;
match self.state.clone() {
ParserState::ObjectLoading(pre, predicate) => {
self.handle_object_loading_state(parsed, pre.as_ref(), &predicate)
}
ParserState::SubjectLoading => self.handle_subject_loading_state(parsed),
ParserState::PredicateLoading => self.handle_predicate_loading_state(parsed),
}
}
fn handle_object_loading_state(
&mut self,
parsed: &ParsedLine,
pre: Option<&Pre>,
predicate: &RdfName,
) -> Result<Option<Subject>, TriplesError> {
trace!("object loading");
trace!("object loading");
match parsed {
ParsedLine::Prefix(_, _) => Err(TriplesError::NotImplemented {
trace: "object loading prefix".to_string(),
}),
ParsedLine::Subject(_, _) => Err(TriplesError::NotImplemented {
trace: "object loading subject".to_string(),
}),
ParsedLine::PredObj(prefix, predicate, objpre, object, is_end) => {
self.handle_predicate(prefix.as_ref(), predicate, objpre.as_ref(), object, *is_end)
}
ParsedLine::PredObjTerm(prefix, predicate, objpre, object) => {
self.handle_predicate_term(prefix.as_ref(), predicate, objpre.as_ref(), object)
}
ParsedLine::SubjectPredObj(_, _, _, _, _, _, _) => Err(TriplesError::NotImplemented {
trace: "object loading subpredobj".to_string(),
}),
ParsedLine::ContinueObj(objpre, obj, has_more) => {
if *has_more {
self.handle_predicate(
pre,
predicate,
objpre.as_ref(),
&obj.to_string(),
*has_more,
)
} else {
self.handle_predicate_term(pre, predicate, objpre.as_ref(), &obj.to_string())
}
}
}
}
fn handle_subject_loading_state(
&mut self,
parsed: &ParsedLine,
) -> Result<Option<Subject>, TriplesError> {
trace!("subject loading");
match parsed {
ParsedLine::Prefix(pre, uri) => {
self.prefixes.insert(pre.clone(), uri.clone());
Ok(None)
}
ParsedLine::Subject(prefix, name) => self.handle_subject(prefix.as_ref(), name),
ParsedLine::PredObj(_, _, _, _, _) | ParsedLine::PredObjTerm(_, _, _, _) => {
Err(TriplesError::NotImplemented {
trace: "subject loading predobj".to_string(),
})
}
ParsedLine::SubjectPredObj(
name_prefix,
name,
prefix,
predicate,
objpre,
object,
has_more,
) => {
self.handle_subject(name_prefix.as_ref(), name)?;
let result = self.handle_predicate(
prefix.as_ref(),
predicate,
objpre.as_ref(),
object,
*has_more,
);
if *has_more {
self.state = ParserState::ObjectLoading(prefix.clone(), predicate.clone());
} else {
self.current_subject = None;
self.state = ParserState::SubjectLoading;
};
result
}
ParsedLine::ContinueObj(_, _, _) => Err(TriplesError::NotImplemented {
trace: "subject loading contobj".to_string(),
}),
}
}
fn handle_predicate_loading_state(
&mut self,
parsed: &ParsedLine,
) -> Result<Option<Subject>, TriplesError> {
trace!("predicate loading");
match parsed {
ParsedLine::Prefix(name, uri) => {
if self.current_subject.is_some() {
return Err(TriplesError::PreviousSubjectNotComplete);
}
self.prefixes.insert(name.clone(), uri.clone());
Ok(None)
}
ParsedLine::PredObj(prefix, predicate, opre, object, is_end) => {
self.handle_predicate(prefix.as_ref(), predicate, opre.as_ref(), object, *is_end)
}
ParsedLine::PredObjTerm(prefix, predicate, opre, object) => {
self.handle_predicate_term(prefix.as_ref(), predicate, opre.as_ref(), object)
}
ParsedLine::Subject(_, _) => Err(TriplesError::NotImplemented {
trace: "predicate loading subj".to_string(),
}),
ParsedLine::SubjectPredObj(_, _, _, _, _, _, _) => Err(TriplesError::NotImplemented {
trace: "predicate loading subjpredobj".to_string(),
}),
ParsedLine::ContinueObj(_, _, _) => Err(TriplesError::NotImplemented {
trace: "predicate loading contobj".to_string(),
}),
}
}
}