diff --git a/lib/src/sparql/xml_results.rs b/lib/src/sparql/xml_results.rs index fa7d4c29..7c0d4fc6 100644 --- a/lib/src/sparql/xml_results.rs +++ b/lib/src/sparql/xml_results.rs @@ -1,15 +1,109 @@ +//! Implementation of [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/) + use model::*; +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 sparql::algebra::BindingsIterator; use sparql::algebra::QueryResult; use sparql::algebra::Variable; use std::collections::BTreeMap; use std::io::BufRead; +use std::io::Write; use std::iter::empty; use std::str::FromStr; use Result; +pub fn write_xml_results(results: QueryResult, sink: W) -> Result { + let mut writer = Writer::new(sink); + match results { + QueryResult::Boolean(value) => { + 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")))?; + } + QueryResult::Bindings(bindings) => { + let (variables, results) = bindings.destruct(); + 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")))?; + for variable in &variables { + let mut variable_tag = BytesStart::borrowed_name(b"variable"); + variable_tag.push_attribute(("name", variable.name()?)); + writer.write_event(Event::Empty(variable_tag))?; + } + writer.write_event(Event::End(BytesEnd::borrowed(b"head")))?; + writer.write_event(Event::Start(BytesStart::borrowed_name(b"results")))?; + for result in results { + let result = result?; + writer.write_event(Event::Start(BytesStart::borrowed_name(b"result")))?; + for (k, value) in result.into_iter().enumerate() { + if let Some(term) = value { + writer.write_event(Event::Start(BytesStart::borrowed_name(b"binding")))?; + match term { + Term::NamedNode(uri) => { + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"uri")))?; + writer.write_event(Event::Text(BytesText::from_plain_str( + uri.as_str(), + )))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"uri")))?; + } + Term::BlankNode(bnode) => { + writer.write_event(Event::Start(BytesStart::borrowed_name( + b"bnode", + )))?; + writer.write_event(Event::Text(BytesText::from_plain_str( + &bnode.as_uuid().to_simple().to_string(), + )))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"bnode")))?; + } + 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))?; + writer.write_event(Event::Text(BytesText::from_plain_str( + &literal.value(), + )))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"literal")))?; + } + } + writer.write_event(Event::End(BytesEnd::borrowed(b"binding")))?; + } + } + writer.write_event(Event::End(BytesEnd::borrowed(b"result")))?; + } + writer.write_event(Event::End(BytesEnd::borrowed(b"results")))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"sparql")))?; + } + QueryResult::Graph(_) => { + return Err(format_err!( + "Graphs could not be formatted to SPARQL query results XML format" + )) + } + } + Ok(writer.into_inner()) +} + pub fn read_xml_results(source: impl BufRead + 'static) -> Result> { enum State { Start, @@ -130,7 +224,7 @@ pub fn read_xml_results(source: impl BufRead + 'static) -> Result if let State::Head = state { state = State::AfterHead; } else { - return Err(format_err!("Unexpected early file end. All results file should have a and a or tag")); + return Err(format_err!("Unexpected early file end. All results file should have a and a or tag")); }, Event::Eof => return Err(format_err!("Unexpected early file end. All results file should have a and a or tag")), _ => (),