//! Implementation of [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/) use crate::error::{invalid_data_error, invalid_input_error}; use crate::model::*; use crate::sparql::error::EvaluationError; use crate::sparql::model::*; use quick_xml::events::BytesDecl; use quick_xml::events::BytesEnd; use quick_xml::events::BytesStart; use quick_xml::events::BytesText; use quick_xml::events::Event; use quick_xml::Reader; use quick_xml::Writer; use std::collections::BTreeMap; use std::io::BufRead; use std::io::Write; use std::iter::empty; use std::rc::Rc; pub fn write_xml_results(results: QueryResult, sink: impl Write) -> Result<(), EvaluationError> { match results { QueryResult::Boolean(value) => { write_boolean(value, sink).map_err(map_xml_error)?; Ok(()) } QueryResult::Solutions(solutions) => write_solutions(solutions, sink), QueryResult::Graph(_) => Err(invalid_input_error( "Graphs could not be formatted to SPARQL query results XML format", ) .into()), } } fn write_boolean(value: bool, sink: impl Write) -> Result<(), quick_xml::Error> { let mut writer = Writer::new(sink); writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))?; let mut sparql_open = BytesStart::borrowed_name(b"sparql"); sparql_open.push_attribute(("xmlns", "http://www.w3.org/2005/sparql-results#")); writer.write_event(Event::Start(sparql_open))?; writer.write_event(Event::Start(BytesStart::borrowed_name(b"head")))?; writer.write_event(Event::End(BytesEnd::borrowed(b"head")))?; writer.write_event(Event::Start(BytesStart::borrowed_name(b"boolean")))?; writer.write_event(Event::Text(BytesText::from_plain_str(if value { "true" } else { "false" })))?; writer.write_event(Event::End(BytesEnd::borrowed(b"boolean")))?; writer.write_event(Event::End(BytesEnd::borrowed(b"sparql")))?; Ok(()) } fn write_solutions( solutions: QuerySolutionsIterator, sink: impl Write, ) -> Result<(), EvaluationError> { let mut writer = Writer::new(sink); writer .write_event(Event::Decl(BytesDecl::new(b"1.0", None, None))) .map_err(map_xml_error)?; let mut sparql_open = BytesStart::borrowed_name(b"sparql"); sparql_open.push_attribute(("xmlns", "http://www.w3.org/2005/sparql-results#")); writer .write_event(Event::Start(sparql_open)) .map_err(map_xml_error)?; writer .write_event(Event::Start(BytesStart::borrowed_name(b"head"))) .map_err(map_xml_error)?; for variable in solutions.variables() { let mut variable_tag = BytesStart::borrowed_name(b"variable"); variable_tag.push_attribute(("name", variable.as_str())); writer .write_event(Event::Empty(variable_tag)) .map_err(map_xml_error)?; } writer .write_event(Event::End(BytesEnd::borrowed(b"head"))) .map_err(map_xml_error)?; writer .write_event(Event::Start(BytesStart::borrowed_name(b"results"))) .map_err(map_xml_error)?; for solution in solutions { let solution = solution?; writer .write_event(Event::Start(BytesStart::borrowed_name(b"result"))) .map_err(map_xml_error)?; for (variable, value) in solution.iter() { let mut binding_tag = BytesStart::borrowed_name(b"binding"); binding_tag.push_attribute(("name", variable.as_str())); writer .write_event(Event::Start(binding_tag)) .map_err(map_xml_error)?; match value { Term::NamedNode(uri) => { writer .write_event(Event::Start(BytesStart::borrowed_name(b"uri"))) .map_err(map_xml_error)?; writer .write_event(Event::Text(BytesText::from_plain_str(uri.as_str()))) .map_err(map_xml_error)?; writer .write_event(Event::End(BytesEnd::borrowed(b"uri"))) .map_err(map_xml_error)?; } Term::BlankNode(bnode) => { writer .write_event(Event::Start(BytesStart::borrowed_name(b"bnode"))) .map_err(map_xml_error)?; writer .write_event(Event::Text(BytesText::from_plain_str(bnode.as_str()))) .map_err(map_xml_error)?; writer .write_event(Event::End(BytesEnd::borrowed(b"bnode"))) .map_err(map_xml_error)?; } Term::Literal(literal) => { let mut literal_tag = BytesStart::borrowed_name(b"literal"); if let Some(language) = literal.language() { literal_tag.push_attribute(("xml:lang", language)); } else if !literal.is_plain() { literal_tag.push_attribute(("datatype", literal.datatype().as_str())); } writer .write_event(Event::Start(literal_tag)) .map_err(map_xml_error)?; writer .write_event(Event::Text(BytesText::from_plain_str(literal.value()))) .map_err(map_xml_error)?; writer .write_event(Event::End(BytesEnd::borrowed(b"literal"))) .map_err(map_xml_error)?; } } writer .write_event(Event::End(BytesEnd::borrowed(b"binding"))) .map_err(map_xml_error)?; } writer .write_event(Event::End(BytesEnd::borrowed(b"result"))) .map_err(map_xml_error)?; } writer .write_event(Event::End(BytesEnd::borrowed(b"results"))) .map_err(map_xml_error)?; writer .write_event(Event::End(BytesEnd::borrowed(b"sparql"))) .map_err(map_xml_error)?; Ok(()) } pub fn read_xml_results(source: impl BufRead + 'static) -> Result { enum State { Start, Sparql, Head, AfterHead, Boolean, } let mut reader = Reader::from_reader(source); reader.trim_text(true); let mut buffer = Vec::default(); let mut namespace_buffer = Vec::default(); let mut variables: Vec = Vec::default(); let mut state = State::Start; //Read header loop { let event = { let (ns, event) = reader .read_namespaced_event(&mut buffer, &mut namespace_buffer) .map_err(map_xml_error)?; if let Some(ns) = ns { if ns != b"http://www.w3.org/2005/sparql-results#".as_ref() { return Err(invalid_data_error(format!( "Unexpected namespace found in RDF/XML query result: {}", reader.decode(ns).map_err(map_xml_error)? )) .into()); } } event }; match event { Event::Start(event) => match state { State::Start => { if event.name() == b"sparql" { state = State::Sparql; } else { return Err(invalid_data_error(format!("Expecting tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::Sparql => { if event.name() == b"head" { state = State::Head; } else { return Err(invalid_data_error(format!("Expecting tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::Head => { if event.name() == b"variable" { let name = event.attributes() .filter_map(|attr| attr.ok()) .find(|attr| attr.key == b"name") .ok_or_else(|| invalid_data_error("No name attribute found for the tag"))?; variables.push(name.unescape_and_decode_value(&reader).map_err(map_xml_error)?); } else if event.name() == b"link" { // no op } else { return Err(invalid_data_error(format!("Expecting or tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::AfterHead => { if event.name() == b"boolean" { state = State::Boolean } else if event.name() == b"results" { let mut mapping = BTreeMap::default(); for (i,var) in variables.iter().enumerate() { mapping.insert(var.as_bytes().to_vec(), i); } return Ok(QueryResult::Solutions(QuerySolutionsIterator::new( Rc::new(variables.into_iter().map(Variable::new).collect()), Box::new(ResultsIterator { reader, buffer: Vec::default(), namespace_buffer, mapping, }), ))); } else if event.name() != b"link" && event.name() != b"results" && event.name() != b"boolean" { return Err(invalid_data_error(format!("Expecting sparql tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::Boolean => return Err(invalid_data_error(format!("Unexpected tag inside of tag: {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()) }, Event::Empty(event) => match state { State::Sparql => { if event.name() == b"head" { state = State::AfterHead; } else { return Err(invalid_data_error(format!("Expecting tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::Head => { if event.name() == b"variable" { let name = event.attributes() .filter_map(|v| v.ok()) .find(|attr| attr.key == b"name") .ok_or_else(|| invalid_data_error("No name attribute found for the tag"))?; variables.push(name.unescape_and_decode_value(&reader).map_err(map_xml_error)?); } else if event.name() == b"link" { // no op } else { return Err(invalid_data_error(format!("Expecting or tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } }, State::AfterHead => { if event.name() == b"results" { return Ok(QueryResult::Solutions(QuerySolutionsIterator::new( Rc::new(variables.into_iter().map(Variable::new).collect()), Box::new(empty()), ))) } else { return Err(invalid_data_error(format!("Unexpected autoclosing tag <{}>", reader.decode(event.name()).map_err(map_xml_error)?)).into()) } } _ => return Err(invalid_data_error(format!("Unexpected autoclosing tag <{}>", reader.decode(event.name()).map_err(map_xml_error)?)).into()) }, Event::Text(event) => { let value = event.unescaped().map_err(map_xml_error)?; return match state { State::Boolean => { return if value.as_ref() == b"true" { Ok(QueryResult::Boolean(true)) } else if value.as_ref() == b"false" { Ok(QueryResult::Boolean(false)) } else { Err(invalid_data_error(format!("Unexpected boolean value. Found {}", reader.decode(&value).map_err(map_xml_error)?)).into()) }; } _ => Err(invalid_data_error(format!("Unexpected textual value found: {}", reader.decode(&value).map_err(map_xml_error)?)).into()) }; }, Event::End(_) => if let State::Head = state { state = State::AfterHead; } else { return Err(invalid_data_error("Unexpected early file end. All results file should have a and a or tag").into()); }, Event::Eof => return Err(invalid_data_error("Unexpected early file end. All results file should have a and a or tag").into()), _ => (), } } } struct ResultsIterator { reader: Reader, buffer: Vec, namespace_buffer: Vec, mapping: BTreeMap, usize>, } impl Iterator for ResultsIterator { type Item = Result>, EvaluationError>; fn next(&mut self) -> Option>, EvaluationError>> { self.read_next().transpose() } } impl ResultsIterator { fn read_next(&mut self) -> Result>>, EvaluationError> { enum State { Start, Result, Binding, Uri, BNode, Literal, End, } let mut state = State::Start; let mut new_bindings = Vec::default(); new_bindings.resize(self.mapping.len(), None); let mut current_var = None; let mut term: Option = None; let mut lang = None; let mut datatype = None; loop { let (ns, event) = self .reader .read_namespaced_event(&mut self.buffer, &mut self.namespace_buffer) .map_err(map_xml_error)?; if let Some(ns) = ns { if ns != b"http://www.w3.org/2005/sparql-results#".as_ref() { return Err(invalid_data_error(format!( "Unexpected namespace found in RDF/XML query result: {}", self.reader.decode(ns).map_err(map_xml_error)? )) .into()); } } match event { Event::Start(event) => match state { State::Start => { if event.name() == b"result" { state = State::Result; } else { return Err(invalid_data_error(format!( "Expecting , found {}", self.reader.decode(event.name()).map_err(map_xml_error)? )) .into()); } } State::Result => { if event.name() == b"binding" { match event .attributes() .filter_map(|v| v.ok()) .find(|attr| attr.key == b"name") { Some(attr) => { current_var = Some( attr.unescaped_value().map_err(map_xml_error)?.to_vec(), ) } None => { return Err(invalid_data_error( "No name attribute found for the tag", ) .into()); } } state = State::Binding; } else { return Err(invalid_data_error(format!( "Expecting , found {}", self.reader.decode(event.name()).map_err(map_xml_error)? )) .into()); } } State::Binding => { if term.is_some() { return Err(invalid_data_error( "There is already a value for the current binding", ) .into()); } if event.name() == b"uri" { state = State::Uri; } else if event.name() == b"bnode" { state = State::BNode; } else if event.name() == b"literal" { for attr in event.attributes() { if let Ok(attr) = attr { if attr.key == b"xml:lang" { lang = Some( attr.unescape_and_decode_value(&self.reader) .map_err(map_xml_error)?, ); } else if attr.key == b"datatype" { let iri = attr .unescape_and_decode_value(&self.reader) .map_err(map_xml_error)?; datatype = Some(NamedNode::new(&iri).map_err(|e| { invalid_data_error(format!( "Invalid datatype IRI '{}': {}", iri, e )) })?); } } } state = State::Literal; } else { return Err(invalid_data_error(format!( "Expecting , or found {}", self.reader.decode(event.name()).map_err(map_xml_error)? )) .into()); } } _ => (), }, Event::Text(event) => { let data = event.unescaped().map_err(map_xml_error)?; match state { State::Uri => { let iri = self.reader.decode(&data).map_err(map_xml_error)?; term = Some( NamedNode::new(iri) .map_err(|e| { invalid_data_error(format!( "Invalid IRI value '{}': {}", iri, e )) })? .into(), ) } State::BNode => { let bnode = self.reader.decode(&data).map_err(map_xml_error)?; term = Some( BlankNode::new(bnode) .map_err(|e| { invalid_data_error(format!( "Invalid blank node value '{}': {}", bnode, e )) })? .into(), ) } State::Literal => { term = Some( build_literal( self.reader.decode(&data).map_err(map_xml_error)?, lang.take(), datatype.take(), )? .into(), ); } _ => { return Err(invalid_data_error(format!( "Unexpected textual value found: {}", self.reader.decode(&data).map_err(map_xml_error)? )) .into()); } } } Event::End(_) => match state { State::Start => state = State::End, State::Result => return Ok(Some(new_bindings)), State::Binding => { if let Some(var) = ¤t_var { new_bindings[self.mapping[var]] = term.clone() } else { return Err( invalid_data_error("No name found for tag").into() ); } term = None; state = State::Result; } State::Uri | State::BNode => state = State::Binding, State::Literal => { if term.is_none() { //We default to the empty literal term = Some(build_literal("", lang.take(), datatype.take())?.into()) } state = State::Binding; } _ => (), }, Event::Eof => return Ok(None), _ => (), } } } } fn build_literal( value: impl Into, lang: Option, datatype: Option, ) -> Result { match datatype { Some(datatype) => Ok(Literal::new_typed_literal(value, datatype)), None => match lang { Some(lang) => Literal::new_language_tagged_literal(value, &lang).map_err(|e| { invalid_data_error(format!("Invalid xml:lang value '{}': {}", lang, e)).into() }), None => Ok(Literal::new_simple_literal(value)), }, } } fn map_xml_error(error: quick_xml::Error) -> EvaluationError { match error { quick_xml::Error::Io(error) => error, _ => invalid_data_error(error), } .into() }