parent
a33dbb6d06
commit
01a33192eb
@ -1,307 +0,0 @@ |
||||
//! Implementation of [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/)
|
||||
|
||||
use crate::error::invalid_data_error; |
||||
use crate::model::{vocab::xsd, *}; |
||||
use crate::sparql::error::EvaluationError; |
||||
use crate::sparql::model::*; |
||||
use std::io::{self, BufRead, Write}; |
||||
use std::rc::Rc; |
||||
use std::str::FromStr; |
||||
|
||||
pub fn write_csv_results( |
||||
results: QueryResults, |
||||
mut sink: impl Write, |
||||
) -> Result<(), EvaluationError> { |
||||
match results { |
||||
QueryResults::Boolean(value) => { |
||||
sink.write_all(if value { b"true" } else { b"false" })?; |
||||
} |
||||
QueryResults::Solutions(solutions) => { |
||||
let mut start_vars = true; |
||||
for variable in solutions.variables() { |
||||
if start_vars { |
||||
start_vars = false; |
||||
} else { |
||||
sink.write_all(b",")?; |
||||
} |
||||
sink.write_all(variable.as_str().as_bytes())?; |
||||
} |
||||
|
||||
for solution in solutions { |
||||
let solution = solution?; |
||||
sink.write_all(b"\r\n")?; |
||||
let mut start_binding = true; |
||||
for value in solution.values() { |
||||
if start_binding { |
||||
start_binding = false; |
||||
} else { |
||||
sink.write_all(b",")?; |
||||
} |
||||
if let Some(value) = value { |
||||
write_csv_term(value, &mut sink)?; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
QueryResults::Graph(g) => { |
||||
sink.write_all(b"subject,predicate,object")?; |
||||
for t in g { |
||||
let t = t?; |
||||
sink.write_all(b"\r\n")?; |
||||
write_csv_term(&t.subject, &mut sink)?; |
||||
sink.write_all(b",")?; |
||||
write_csv_term(&t.predicate, &mut sink)?; |
||||
sink.write_all(b",")?; |
||||
write_csv_term(&t.object, &mut sink)?; |
||||
} |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
fn write_csv_term<'a>(term: impl Into<TermRef<'a>>, sink: &mut impl Write) -> io::Result<()> { |
||||
match term.into() { |
||||
TermRef::NamedNode(uri) => sink.write_all(uri.as_str().as_bytes()), |
||||
TermRef::BlankNode(bnode) => { |
||||
sink.write_all(b"_:")?; |
||||
sink.write_all(bnode.as_str().as_bytes()) |
||||
} |
||||
TermRef::Literal(literal) => write_escaped_csv_string(literal.value(), sink), |
||||
TermRef::Triple(triple) => { |
||||
write_csv_term(&triple.subject, sink)?; |
||||
sink.write_all(b" ")?; |
||||
write_csv_term(&triple.predicate, sink)?; |
||||
sink.write_all(b" ")?; |
||||
write_csv_term(&triple.object, sink) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn write_escaped_csv_string(s: &str, sink: &mut impl Write) -> io::Result<()> { |
||||
if s.bytes().any(|c| matches!(c, b'"' | b',' | b'\n' | b'\r')) { |
||||
sink.write_all(b"\"")?; |
||||
for c in s.bytes() { |
||||
if c == b'\"' { |
||||
sink.write_all(b"\"\"") |
||||
} else { |
||||
sink.write_all(&[c]) |
||||
}?; |
||||
} |
||||
sink.write_all(b"\"") |
||||
} else { |
||||
sink.write_all(s.as_bytes()) |
||||
} |
||||
} |
||||
|
||||
pub fn write_tsv_results( |
||||
results: QueryResults, |
||||
mut sink: impl Write, |
||||
) -> Result<(), EvaluationError> { |
||||
match results { |
||||
QueryResults::Boolean(value) => { |
||||
sink.write_all(if value { b"true" } else { b"false" })?; |
||||
} |
||||
QueryResults::Solutions(solutions) => { |
||||
let mut start_vars = true; |
||||
for variable in solutions.variables() { |
||||
if start_vars { |
||||
start_vars = false; |
||||
} else { |
||||
sink.write_all(b"\t")?; |
||||
} |
||||
sink.write_all(b"?")?; |
||||
sink.write_all(variable.as_str().as_bytes())?; |
||||
} |
||||
|
||||
for solution in solutions { |
||||
let solution = solution?; |
||||
sink.write_all(b"\n")?; |
||||
let mut start_binding = true; |
||||
for value in solution.values() { |
||||
if start_binding { |
||||
start_binding = false; |
||||
} else { |
||||
sink.write_all(b"\t")?; |
||||
} |
||||
if let Some(value) = value { |
||||
write_tsv_term(value, &mut sink)?; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
QueryResults::Graph(g) => { |
||||
sink.write_all(b"subject\tpredicate\tobject")?; |
||||
for t in g { |
||||
let t = t?; |
||||
sink.write_all(b"\n")?; |
||||
write_tsv_term(&t.subject, &mut sink)?; |
||||
sink.write_all(b"\t")?; |
||||
write_tsv_term(&t.predicate, &mut sink)?; |
||||
sink.write_all(b"\t")?; |
||||
write_tsv_term(&t.object, &mut sink)?; |
||||
} |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
fn write_tsv_term<'a>(term: impl Into<TermRef<'a>>, sink: &mut impl Write) -> io::Result<()> { |
||||
//TODO: full Turtle serialization
|
||||
match term.into() { |
||||
TermRef::NamedNode(node) => write!(sink, "<{}>", node.as_str()), |
||||
TermRef::BlankNode(node) => write!(sink, "_:{}", node.as_str()), |
||||
TermRef::Literal(literal) => match literal.datatype() { |
||||
xsd::BOOLEAN => match literal.value() { |
||||
"true" | "1" => sink.write_all(b"true"), |
||||
"false" | "0" => sink.write_all(b"false"), |
||||
_ => sink.write_all(literal.to_string().as_bytes()), |
||||
}, |
||||
xsd::INTEGER => { |
||||
if literal.value().bytes().all(|c| matches!(c, b'0'..=b'9')) { |
||||
sink.write_all(literal.value().as_bytes()) |
||||
} else { |
||||
sink.write_all(literal.to_string().as_bytes()) |
||||
} |
||||
} |
||||
_ => sink.write_all(literal.to_string().as_bytes()), |
||||
}, |
||||
TermRef::Triple(triple) => { |
||||
sink.write_all(b"<<")?; |
||||
write_tsv_term(&triple.subject, sink)?; |
||||
sink.write_all(b" ")?; |
||||
write_tsv_term(&triple.predicate, sink)?; |
||||
sink.write_all(b" ")?; |
||||
write_tsv_term(&triple.object, sink)?; |
||||
sink.write_all(b">>")?; |
||||
Ok(()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn read_tsv_results(mut source: impl BufRead + 'static) -> io::Result<QueryResults> { |
||||
let mut buffer = String::new(); |
||||
|
||||
// We read the header
|
||||
source.read_line(&mut buffer)?; |
||||
if buffer.trim().eq_ignore_ascii_case("true") { |
||||
return Ok(QueryResults::Boolean(true)); |
||||
} |
||||
if buffer.trim().eq_ignore_ascii_case("false") { |
||||
return Ok(QueryResults::Boolean(false)); |
||||
} |
||||
let variables = buffer |
||||
.split('\t') |
||||
.map(|v| Variable::from_str(v.trim()).map_err(invalid_data_error)) |
||||
.collect::<io::Result<Vec<_>>>()?; |
||||
|
||||
Ok(QueryResults::Solutions(QuerySolutionIter::new( |
||||
Rc::new(variables), |
||||
Box::new(TsvResultsIterator { source, buffer }), |
||||
))) |
||||
} |
||||
|
||||
struct TsvResultsIterator<R: BufRead> { |
||||
source: R, |
||||
buffer: String, |
||||
} |
||||
|
||||
impl<R: BufRead> Iterator for TsvResultsIterator<R> { |
||||
type Item = Result<Vec<Option<Term>>, EvaluationError>; |
||||
|
||||
fn next(&mut self) -> Option<Result<Vec<Option<Term>>, EvaluationError>> { |
||||
self.read_next().transpose() |
||||
} |
||||
} |
||||
|
||||
impl<R: BufRead> TsvResultsIterator<R> { |
||||
fn read_next(&mut self) -> Result<Option<Vec<Option<Term>>>, EvaluationError> { |
||||
self.buffer.clear(); |
||||
if self.source.read_line(&mut self.buffer)? == 0 { |
||||
return Ok(None); |
||||
} |
||||
Ok(Some( |
||||
self.buffer |
||||
.split('\t') |
||||
.map(|v| { |
||||
let v = v.trim(); |
||||
if v.is_empty() { |
||||
Ok(None) |
||||
} else { |
||||
Ok(Some(Term::from_str(v).map_err(invalid_data_error)?)) |
||||
} |
||||
}) |
||||
.collect::<Result<Vec<_>, EvaluationError>>()?, |
||||
)) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
use std::rc::Rc; |
||||
use std::str; |
||||
|
||||
fn build_example() -> QueryResults { |
||||
QuerySolutionIter::new( |
||||
Rc::new(vec![ |
||||
Variable::new_unchecked("x"), |
||||
Variable::new_unchecked("literal"), |
||||
]), |
||||
Box::new( |
||||
vec![ |
||||
Ok(vec![ |
||||
Some(NamedNode::new_unchecked("http://example/x").into()), |
||||
Some(Literal::new_simple_literal("String").into()), |
||||
]), |
||||
Ok(vec![ |
||||
Some(NamedNode::new_unchecked("http://example/x").into()), |
||||
Some(Literal::new_simple_literal("String-with-dquote\"").into()), |
||||
]), |
||||
Ok(vec![ |
||||
Some(BlankNode::new_unchecked("b0").into()), |
||||
Some(Literal::new_simple_literal("Blank node").into()), |
||||
]), |
||||
Ok(vec![ |
||||
None, |
||||
Some(Literal::new_simple_literal("Missing 'x'").into()), |
||||
]), |
||||
Ok(vec![None, None]), |
||||
Ok(vec![ |
||||
Some(NamedNode::new_unchecked("http://example/x").into()), |
||||
None, |
||||
]), |
||||
Ok(vec![ |
||||
Some(BlankNode::new_unchecked("b1").into()), |
||||
Some( |
||||
Literal::new_language_tagged_literal_unchecked( |
||||
"String-with-lang", |
||||
"en", |
||||
) |
||||
.into(), |
||||
), |
||||
]), |
||||
Ok(vec![ |
||||
Some(BlankNode::new_unchecked("b1").into()), |
||||
Some(Literal::new_typed_literal("123", xsd::INTEGER).into()), |
||||
]), |
||||
] |
||||
.into_iter(), |
||||
), |
||||
) |
||||
.into() |
||||
} |
||||
|
||||
#[test] |
||||
fn test_csv_serialization() { |
||||
let mut sink = Vec::new(); |
||||
write_csv_results(build_example(), &mut sink).unwrap(); |
||||
assert_eq!(str::from_utf8(&sink).unwrap(), "x,literal\r\nhttp://example/x,String\r\nhttp://example/x,\"String-with-dquote\"\"\"\r\n_:b0,Blank node\r\n,Missing 'x'\r\n,\r\nhttp://example/x,\r\n_:b1,String-with-lang\r\n_:b1,123"); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_tsv_serialization() { |
||||
let mut sink = Vec::new(); |
||||
write_tsv_results(build_example(), &mut sink).unwrap(); |
||||
assert_eq!(str::from_utf8(&sink).unwrap(), "?x\t?literal\n<http://example/x>\t\"String\"\n<http://example/x>\t\"String-with-dquote\\\"\"\n_:b0\t\"Blank node\"\n\t\"Missing 'x'\"\n\t\n<http://example/x>\t\n_:b1\t\"String-with-lang\"@en\n_:b1\t123"); |
||||
} |
||||
} |
@ -0,0 +1,306 @@ |
||||
//! Implementation of [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/)
|
||||
|
||||
use crate::io::read::{ParserError, SyntaxError}; |
||||
use crate::model::{vocab::xsd, *}; |
||||
use crate::sparql::model::Variable; |
||||
use std::io::{self, BufRead, Write}; |
||||
use std::str::FromStr; |
||||
|
||||
pub fn write_boolean_csv_result<W: Write>(mut sink: W, value: bool) -> io::Result<W> { |
||||
sink.write_all(if value { b"true" } else { b"false" })?; |
||||
Ok(sink) |
||||
} |
||||
|
||||
pub struct CsvSolutionsWriter<W: Write> { |
||||
sink: W, |
||||
} |
||||
|
||||
impl<W: Write> CsvSolutionsWriter<W> { |
||||
pub fn start(mut sink: W, variables: &[Variable]) -> io::Result<Self> { |
||||
let mut start_vars = true; |
||||
for variable in variables { |
||||
if start_vars { |
||||
start_vars = false; |
||||
} else { |
||||
sink.write_all(b",")?; |
||||
} |
||||
sink.write_all(variable.as_str().as_bytes())?; |
||||
} |
||||
Ok(Self { sink }) |
||||
} |
||||
|
||||
pub fn write<'a>( |
||||
&mut self, |
||||
solution: impl IntoIterator<Item = Option<TermRef<'a>>>, |
||||
) -> io::Result<()> { |
||||
self.sink.write_all(b"\r\n")?; |
||||
let mut start_binding = true; |
||||
for value in solution { |
||||
if start_binding { |
||||
start_binding = false; |
||||
} else { |
||||
self.sink.write_all(b",")?; |
||||
} |
||||
if let Some(value) = value { |
||||
write_csv_term(value, &mut self.sink)?; |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn finish(self) -> W { |
||||
self.sink |
||||
} |
||||
} |
||||
|
||||
fn write_csv_term<'a>(term: impl Into<TermRef<'a>>, sink: &mut impl Write) -> io::Result<()> { |
||||
match term.into() { |
||||
TermRef::NamedNode(uri) => sink.write_all(uri.as_str().as_bytes()), |
||||
TermRef::BlankNode(bnode) => { |
||||
sink.write_all(b"_:")?; |
||||
sink.write_all(bnode.as_str().as_bytes()) |
||||
} |
||||
TermRef::Literal(literal) => write_escaped_csv_string(literal.value(), sink), |
||||
TermRef::Triple(triple) => { |
||||
write_csv_term(&triple.subject, sink)?; |
||||
sink.write_all(b" ")?; |
||||
write_csv_term(&triple.predicate, sink)?; |
||||
sink.write_all(b" ")?; |
||||
write_csv_term(&triple.object, sink) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn write_escaped_csv_string(s: &str, sink: &mut impl Write) -> io::Result<()> { |
||||
if s.bytes().any(|c| matches!(c, b'"' | b',' | b'\n' | b'\r')) { |
||||
sink.write_all(b"\"")?; |
||||
for c in s.bytes() { |
||||
if c == b'\"' { |
||||
sink.write_all(b"\"\"") |
||||
} else { |
||||
sink.write_all(&[c]) |
||||
}?; |
||||
} |
||||
sink.write_all(b"\"") |
||||
} else { |
||||
sink.write_all(s.as_bytes()) |
||||
} |
||||
} |
||||
|
||||
pub fn write_boolean_tsv_result<W: Write>(mut sink: W, value: bool) -> io::Result<W> { |
||||
sink.write_all(if value { b"true" } else { b"false" })?; |
||||
Ok(sink) |
||||
} |
||||
|
||||
pub struct TsvSolutionsWriter<W: Write> { |
||||
sink: W, |
||||
} |
||||
|
||||
impl<W: Write> TsvSolutionsWriter<W> { |
||||
pub fn start(mut sink: W, variables: &[Variable]) -> io::Result<Self> { |
||||
let mut start_vars = true; |
||||
for variable in variables { |
||||
if start_vars { |
||||
start_vars = false; |
||||
} else { |
||||
sink.write_all(b"\t")?; |
||||
} |
||||
sink.write_all(b"?")?; |
||||
sink.write_all(variable.as_str().as_bytes())?; |
||||
} |
||||
Ok(Self { sink }) |
||||
} |
||||
|
||||
pub fn write<'a>( |
||||
&mut self, |
||||
solution: impl IntoIterator<Item = Option<TermRef<'a>>>, |
||||
) -> io::Result<()> { |
||||
self.sink.write_all(b"\n")?; |
||||
let mut start_binding = true; |
||||
for value in solution { |
||||
if start_binding { |
||||
start_binding = false; |
||||
} else { |
||||
self.sink.write_all(b"\t")?; |
||||
} |
||||
if let Some(value) = value { |
||||
write_tsv_term(value, &mut self.sink)?; |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn finish(self) -> W { |
||||
self.sink |
||||
} |
||||
} |
||||
|
||||
fn write_tsv_term<'a>(term: impl Into<TermRef<'a>>, sink: &mut impl Write) -> io::Result<()> { |
||||
//TODO: full Turtle serialization
|
||||
match term.into() { |
||||
TermRef::NamedNode(node) => write!(sink, "<{}>", node.as_str()), |
||||
TermRef::BlankNode(node) => write!(sink, "_:{}", node.as_str()), |
||||
TermRef::Literal(literal) => match literal.datatype() { |
||||
xsd::BOOLEAN => match literal.value() { |
||||
"true" | "1" => sink.write_all(b"true"), |
||||
"false" | "0" => sink.write_all(b"false"), |
||||
_ => sink.write_all(literal.to_string().as_bytes()), |
||||
}, |
||||
xsd::INTEGER => { |
||||
if literal.value().bytes().all(|c| matches!(c, b'0'..=b'9')) { |
||||
sink.write_all(literal.value().as_bytes()) |
||||
} else { |
||||
sink.write_all(literal.to_string().as_bytes()) |
||||
} |
||||
} |
||||
_ => sink.write_all(literal.to_string().as_bytes()), |
||||
}, |
||||
TermRef::Triple(triple) => { |
||||
sink.write_all(b"<<")?; |
||||
write_tsv_term(&triple.subject, sink)?; |
||||
sink.write_all(b" ")?; |
||||
write_tsv_term(&triple.predicate, sink)?; |
||||
sink.write_all(b" ")?; |
||||
write_tsv_term(&triple.object, sink)?; |
||||
sink.write_all(b">>")?; |
||||
Ok(()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub enum TsvQueryResultsReader<R: BufRead> { |
||||
Solutions { |
||||
variables: Vec<Variable>, |
||||
solutions: TsvSolutionsReader<R>, |
||||
}, |
||||
Boolean(bool), |
||||
} |
||||
|
||||
impl<R: BufRead> TsvQueryResultsReader<R> { |
||||
pub fn read(mut source: R) -> Result<Self, ParserError> { |
||||
let mut buffer = String::new(); |
||||
|
||||
// We read the header
|
||||
source.read_line(&mut buffer)?; |
||||
if buffer.trim().eq_ignore_ascii_case("true") { |
||||
return Ok(Self::Boolean(true)); |
||||
} |
||||
if buffer.trim().eq_ignore_ascii_case("false") { |
||||
return Ok(Self::Boolean(false)); |
||||
} |
||||
let variables = buffer |
||||
.split('\t') |
||||
.map(|v| { |
||||
Variable::from_str(v.trim()) |
||||
.map_err(|e| SyntaxError::msg(format!("Invalid variable name '{}': {}", v, e))) |
||||
}) |
||||
.collect::<Result<Vec<_>, _>>()?; |
||||
|
||||
Ok(Self::Solutions { |
||||
variables, |
||||
solutions: TsvSolutionsReader { source, buffer }, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
pub struct TsvSolutionsReader<R: BufRead> { |
||||
source: R, |
||||
buffer: String, |
||||
} |
||||
|
||||
impl<R: BufRead> TsvSolutionsReader<R> { |
||||
pub fn read_next(&mut self) -> Result<Option<Vec<Option<Term>>>, ParserError> { |
||||
self.buffer.clear(); |
||||
if self.source.read_line(&mut self.buffer)? == 0 { |
||||
return Ok(None); |
||||
} |
||||
Ok(Some( |
||||
self.buffer |
||||
.split('\t') |
||||
.map(|v| { |
||||
let v = v.trim(); |
||||
if v.is_empty() { |
||||
Ok(None) |
||||
} else { |
||||
Ok(Some( |
||||
Term::from_str(v).map_err(|e| SyntaxError::msg(e.to_string()))?, |
||||
)) |
||||
} |
||||
}) |
||||
.collect::<Result<_, ParserError>>()?, |
||||
)) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
use std::str; |
||||
|
||||
fn build_example() -> (Vec<Variable>, Vec<Vec<Option<Term>>>) { |
||||
( |
||||
vec![ |
||||
Variable::new_unchecked("x"), |
||||
Variable::new_unchecked("literal"), |
||||
], |
||||
vec![ |
||||
vec![ |
||||
Some(NamedNode::new_unchecked("http://example/x").into()), |
||||
Some(Literal::new_simple_literal("String").into()), |
||||
], |
||||
vec![ |
||||
Some(NamedNode::new_unchecked("http://example/x").into()), |
||||
Some(Literal::new_simple_literal("String-with-dquote\"").into()), |
||||
], |
||||
vec![ |
||||
Some(BlankNode::new_unchecked("b0").into()), |
||||
Some(Literal::new_simple_literal("Blank node").into()), |
||||
], |
||||
vec![ |
||||
None, |
||||
Some(Literal::new_simple_literal("Missing 'x'").into()), |
||||
], |
||||
vec![None, None], |
||||
vec![ |
||||
Some(NamedNode::new_unchecked("http://example/x").into()), |
||||
None, |
||||
], |
||||
vec![ |
||||
Some(BlankNode::new_unchecked("b1").into()), |
||||
Some( |
||||
Literal::new_language_tagged_literal_unchecked("String-with-lang", "en") |
||||
.into(), |
||||
), |
||||
], |
||||
vec![ |
||||
Some(BlankNode::new_unchecked("b1").into()), |
||||
Some(Literal::new_typed_literal("123", xsd::INTEGER).into()), |
||||
], |
||||
], |
||||
) |
||||
} |
||||
|
||||
#[test] |
||||
fn test_csv_serialization() -> io::Result<()> { |
||||
let (variables, solutions) = build_example(); |
||||
let mut writer = CsvSolutionsWriter::start(Vec::new(), &variables)?; |
||||
for solution in &solutions { |
||||
writer.write(solution.iter().map(|t| t.as_ref().map(|t| t.as_ref())))?; |
||||
} |
||||
let result = writer.finish(); |
||||
assert_eq!(str::from_utf8(&result).unwrap(), "x,literal\r\nhttp://example/x,String\r\nhttp://example/x,\"String-with-dquote\"\"\"\r\n_:b0,Blank node\r\n,Missing 'x'\r\n,\r\nhttp://example/x,\r\n_:b1,String-with-lang\r\n_:b1,123"); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn test_tsv_serialization() -> io::Result<()> { |
||||
let (variables, solutions) = build_example(); |
||||
let mut writer = TsvSolutionsWriter::start(Vec::new(), &variables)?; |
||||
for solution in &solutions { |
||||
writer.write(solution.iter().map(|t| t.as_ref().map(|t| t.as_ref())))?; |
||||
} |
||||
let result = writer.finish(); |
||||
assert_eq!(str::from_utf8(&result).unwrap(), "?x\t?literal\n<http://example/x>\t\"String\"\n<http://example/x>\t\"String-with-dquote\\\"\"\n_:b0\t\"Blank node\"\n\t\"Missing 'x'\"\n\t\n<http://example/x>\t\n_:b1\t\"String-with-lang\"@en\n_:b1\t123"); |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,703 @@ |
||||
//! Implementation of [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/)
|
||||
|
||||
use crate::io::read::{ParserError, SyntaxError}; |
||||
use crate::model::vocab::rdf; |
||||
use crate::model::*; |
||||
use crate::sparql::error::EvaluationError; |
||||
use crate::sparql::model::Variable; |
||||
use json_event_parser::{JsonEvent, JsonReader, JsonWriter}; |
||||
use std::collections::BTreeMap; |
||||
use std::io::{self, BufRead, Write}; |
||||
|
||||
pub fn write_boolean_json_result<W: Write>(sink: W, value: bool) -> io::Result<W> { |
||||
let mut writer = JsonWriter::from_writer(sink); |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("head"))?; |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("boolean"))?; |
||||
writer.write_event(JsonEvent::Boolean(value))?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
Ok(writer.into_inner()) |
||||
} |
||||
|
||||
pub struct JsonSolutionsWriter<W: Write> { |
||||
writer: JsonWriter<W>, |
||||
variables: Vec<Variable>, |
||||
} |
||||
|
||||
impl<W: Write> JsonSolutionsWriter<W> { |
||||
pub fn start(sink: W, variables: &[Variable]) -> io::Result<Self> { |
||||
let mut writer = JsonWriter::from_writer(sink); |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("head"))?; |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("vars"))?; |
||||
writer.write_event(JsonEvent::StartArray)?; |
||||
for variable in variables { |
||||
writer.write_event(JsonEvent::String(variable.as_str()))?; |
||||
} |
||||
writer.write_event(JsonEvent::EndArray)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("results"))?; |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("bindings"))?; |
||||
writer.write_event(JsonEvent::StartArray)?; |
||||
Ok(Self { |
||||
writer, |
||||
variables: variables.to_vec(), |
||||
}) |
||||
} |
||||
|
||||
pub fn write<'a>( |
||||
&mut self, |
||||
solution: impl IntoIterator<Item = Option<TermRef<'a>>>, |
||||
) -> io::Result<()> { |
||||
self.writer.write_event(JsonEvent::StartObject)?; |
||||
for (value, variable) in solution.into_iter().zip(&self.variables) { |
||||
if let Some(value) = value { |
||||
self.writer |
||||
.write_event(JsonEvent::ObjectKey(variable.as_str()))?; |
||||
write_json_term(value, &mut self.writer)?; |
||||
} |
||||
} |
||||
self.writer.write_event(JsonEvent::EndObject)?; |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn finish(mut self) -> io::Result<W> { |
||||
self.writer.write_event(JsonEvent::EndArray)?; |
||||
self.writer.write_event(JsonEvent::EndObject)?; |
||||
self.writer.write_event(JsonEvent::EndObject)?; |
||||
Ok(self.writer.into_inner()) |
||||
} |
||||
} |
||||
|
||||
fn write_json_term( |
||||
term: TermRef<'_>, |
||||
writer: &mut JsonWriter<impl Write>, |
||||
) -> Result<(), EvaluationError> { |
||||
match term { |
||||
TermRef::NamedNode(uri) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("type"))?; |
||||
writer.write_event(JsonEvent::String("uri"))?; |
||||
writer.write_event(JsonEvent::ObjectKey("value"))?; |
||||
writer.write_event(JsonEvent::String(uri.as_str()))?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
TermRef::BlankNode(bnode) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("type"))?; |
||||
writer.write_event(JsonEvent::String("bnode"))?; |
||||
writer.write_event(JsonEvent::ObjectKey("value"))?; |
||||
writer.write_event(JsonEvent::String(bnode.as_str()))?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
TermRef::Literal(literal) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("type"))?; |
||||
writer.write_event(JsonEvent::String("literal"))?; |
||||
writer.write_event(JsonEvent::ObjectKey("value"))?; |
||||
writer.write_event(JsonEvent::String(literal.value()))?; |
||||
if let Some(language) = literal.language() { |
||||
writer.write_event(JsonEvent::ObjectKey("xml:lang"))?; |
||||
writer.write_event(JsonEvent::String(language))?; |
||||
} else if !literal.is_plain() { |
||||
writer.write_event(JsonEvent::ObjectKey("datatype"))?; |
||||
writer.write_event(JsonEvent::String(literal.datatype().as_str()))?; |
||||
} |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
TermRef::Triple(triple) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("type"))?; |
||||
writer.write_event(JsonEvent::String("triple"))?; |
||||
writer.write_event(JsonEvent::ObjectKey("value"))?; |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("subject"))?; |
||||
write_json_term(triple.subject.as_ref().into(), writer)?; |
||||
writer.write_event(JsonEvent::ObjectKey("predicate"))?; |
||||
write_json_term(triple.predicate.as_ref().into(), writer)?; |
||||
writer.write_event(JsonEvent::ObjectKey("object"))?; |
||||
write_json_term(triple.object.as_ref(), writer)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub enum JsonQueryResultsReader<R: BufRead> { |
||||
Solutions { |
||||
variables: Vec<Variable>, |
||||
solutions: JsonSolutionsReader<R>, |
||||
}, |
||||
Boolean(bool), |
||||
} |
||||
|
||||
impl<R: BufRead> JsonQueryResultsReader<R> { |
||||
pub fn read(source: R) -> Result<Self, ParserError> { |
||||
let mut reader = JsonReader::from_reader(source); |
||||
let mut buffer = Vec::default(); |
||||
let mut variables = None; |
||||
|
||||
if reader.read_event(&mut buffer)? != JsonEvent::StartObject { |
||||
return Err(SyntaxError::msg("SPARQL JSON results should be an object").into()); |
||||
} |
||||
|
||||
loop { |
||||
let event = reader.read_event(&mut buffer)?; |
||||
match event { |
||||
JsonEvent::ObjectKey(key) => match key { |
||||
"head" => variables = Some(read_head(&mut reader, &mut buffer)?), |
||||
"results" => { |
||||
if reader.read_event(&mut buffer)? != JsonEvent::StartObject { |
||||
return Err(SyntaxError::msg("'results' should be an object").into()); |
||||
} |
||||
if reader.read_event(&mut buffer)? != JsonEvent::ObjectKey("bindings") { |
||||
return Err(SyntaxError::msg( |
||||
"'results' should contain a 'bindings' key", |
||||
) |
||||
.into()); |
||||
} |
||||
if reader.read_event(&mut buffer)? != JsonEvent::StartArray { |
||||
return Err(SyntaxError::msg("'bindings' should be an object").into()); |
||||
} |
||||
return if let Some(variables) = variables { |
||||
let mut mapping = BTreeMap::default(); |
||||
for (i, var) in variables.iter().enumerate() { |
||||
mapping.insert(var.clone(), i); |
||||
} |
||||
Ok(Self::Solutions { |
||||
variables: variables |
||||
.into_iter() |
||||
.map(|v| { |
||||
Variable::new(v).map_err(|e| { |
||||
SyntaxError::msg(format!( |
||||
"Invalid variable name: {}", |
||||
e |
||||
)) |
||||
}) |
||||
}) |
||||
.collect::<Result<Vec<_>, _>>()?, |
||||
solutions: JsonSolutionsReader { |
||||
reader, |
||||
buffer, |
||||
mapping, |
||||
}, |
||||
}) |
||||
} else { |
||||
Err(SyntaxError::msg( |
||||
"SPARQL tuple query results should contain a head key", |
||||
) |
||||
.into()) |
||||
}; |
||||
} |
||||
"boolean" => { |
||||
return if let JsonEvent::Boolean(v) = reader.read_event(&mut buffer)? { |
||||
Ok(Self::Boolean(v)) |
||||
} else { |
||||
Err(SyntaxError::msg("Unexpected boolean value").into()) |
||||
} |
||||
} |
||||
_ => { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Expecting head or result key, found {}", |
||||
key |
||||
)) |
||||
.into()); |
||||
} |
||||
}, |
||||
JsonEvent::EndObject => { |
||||
return Err(SyntaxError::msg( |
||||
"SPARQL results should contain a bindings key or a boolean key", |
||||
) |
||||
.into()) |
||||
} |
||||
JsonEvent::Eof => { |
||||
return Err(SyntaxError::msg( |
||||
"Unexpected end of JSON object without 'results' or 'boolean' key", |
||||
) |
||||
.into()) |
||||
} |
||||
_ => return Err(SyntaxError::msg("Invalid SPARQL results serialization").into()), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub struct JsonSolutionsReader<R: BufRead> { |
||||
reader: JsonReader<R>, |
||||
buffer: Vec<u8>, |
||||
mapping: BTreeMap<String, usize>, |
||||
} |
||||
|
||||
impl<R: BufRead> JsonSolutionsReader<R> { |
||||
pub fn read_next(&mut self) -> Result<Option<Vec<Option<Term>>>, ParserError> { |
||||
let mut new_bindings = vec![None; self.mapping.len()]; |
||||
loop { |
||||
match self.reader.read_event(&mut self.buffer)? { |
||||
JsonEvent::StartObject => (), |
||||
JsonEvent::EndObject => return Ok(Some(new_bindings)), |
||||
JsonEvent::EndArray | JsonEvent::Eof => return Ok(None), |
||||
JsonEvent::ObjectKey(key) => { |
||||
let k = *self.mapping.get(key).ok_or_else(|| { |
||||
SyntaxError::msg(format!( |
||||
"The variable {} has not been defined in the header", |
||||
key |
||||
)) |
||||
})?; |
||||
new_bindings[k] = Some(self.read_value()?) |
||||
} |
||||
_ => return Err(SyntaxError::msg("Invalid result serialization").into()), |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn read_value(&mut self) -> Result<Term, ParserError> { |
||||
enum Type { |
||||
Uri, |
||||
BNode, |
||||
Literal, |
||||
Triple, |
||||
} |
||||
#[derive(Eq, PartialEq)] |
||||
enum State { |
||||
Type, |
||||
Value, |
||||
Lang, |
||||
Datatype, |
||||
} |
||||
let mut state = None; |
||||
let mut t = None; |
||||
let mut value = None; |
||||
let mut lang = None; |
||||
let mut datatype = None; |
||||
let mut subject = None; |
||||
let mut predicate = None; |
||||
let mut object = None; |
||||
if self.reader.read_event(&mut self.buffer)? != JsonEvent::StartObject { |
||||
return Err(SyntaxError::msg("Term serializations should be an object").into()); |
||||
} |
||||
loop { |
||||
match self.reader.read_event(&mut self.buffer)? { |
||||
JsonEvent::ObjectKey(key) => match key { |
||||
"type" => state = Some(State::Type), |
||||
"value" => state = Some(State::Value), |
||||
"xml:lang" => state = Some(State::Lang), |
||||
"datatype" => state = Some(State::Datatype), |
||||
"subject" => subject = Some(self.read_value()?), |
||||
"predicate" => predicate = Some(self.read_value()?), |
||||
"object" => object = Some(self.read_value()?), |
||||
_ => { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Unexpected key in term serialization: '{}'", |
||||
key |
||||
)) |
||||
.into()) |
||||
} |
||||
}, |
||||
JsonEvent::StartObject => { |
||||
if state != Some(State::Value) { |
||||
return Err(SyntaxError::msg( |
||||
"Unexpected nested object in term serialization", |
||||
) |
||||
.into()); |
||||
} |
||||
} |
||||
JsonEvent::String(s) => match state { |
||||
Some(State::Type) => { |
||||
match s { |
||||
"uri" => t = Some(Type::Uri), |
||||
"bnode" => t = Some(Type::BNode), |
||||
"literal" => t = Some(Type::Literal), |
||||
"triple" => t = Some(Type::Triple), |
||||
_ => { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Unexpected term type: '{}'", |
||||
s |
||||
)) |
||||
.into()) |
||||
} |
||||
}; |
||||
state = None; |
||||
} |
||||
Some(State::Value) => { |
||||
value = Some(s.to_owned()); |
||||
state = None; |
||||
} |
||||
Some(State::Lang) => { |
||||
lang = Some(s.to_owned()); |
||||
state = None; |
||||
} |
||||
Some(State::Datatype) => { |
||||
datatype = Some(NamedNode::new(s).map_err(|e| { |
||||
SyntaxError::msg(format!("Invalid datatype IRI: {}", e)) |
||||
})?); |
||||
state = None; |
||||
} |
||||
_ => (), // impossible
|
||||
}, |
||||
JsonEvent::EndObject => { |
||||
if let Some(s) = state { |
||||
if s == State::Value { |
||||
state = None; //End of triple
|
||||
} else { |
||||
return Err(SyntaxError::msg( |
||||
"Term description values should be string", |
||||
) |
||||
.into()); |
||||
} |
||||
} else { |
||||
return match t { |
||||
None => Err(SyntaxError::msg( |
||||
"Term serialization should have a 'type' key", |
||||
) |
||||
.into()), |
||||
Some(Type::Uri) => Ok(NamedNode::new(value.ok_or_else(|| { |
||||
SyntaxError::msg("uri serialization should have a 'value' key") |
||||
})?) |
||||
.map_err(|e| SyntaxError::msg(format!("Invalid uri value: {}", e)))? |
||||
.into()), |
||||
Some(Type::BNode) => Ok(BlankNode::new(value.ok_or_else(|| { |
||||
SyntaxError::msg("bnode serialization should have a 'value' key") |
||||
})?) |
||||
.map_err(|e| SyntaxError::msg(format!("Invalid bnode value: {}", e)))? |
||||
.into()), |
||||
Some(Type::Literal) => { |
||||
let value = value.ok_or_else(|| { |
||||
SyntaxError::msg( |
||||
"literal serialization should have a 'value' key", |
||||
) |
||||
})?; |
||||
Ok(match lang { |
||||
Some(lang) => { |
||||
if let Some(datatype) = datatype { |
||||
if datatype.as_ref() != rdf::LANG_STRING { |
||||
return Err(SyntaxError::msg(format!( |
||||
"xml:lang value '{}' provided with the datatype {}", |
||||
lang, datatype |
||||
)).into()) |
||||
} |
||||
} |
||||
Literal::new_language_tagged_literal(value, &lang).map_err(|e| { |
||||
SyntaxError::msg(format!("Invalid xml:lang value '{}': {}", lang, e)) |
||||
})? |
||||
} |
||||
None => if let Some(datatype) = datatype { |
||||
Literal::new_typed_literal(value, datatype) |
||||
} else { |
||||
Literal::new_simple_literal(value) |
||||
} |
||||
} |
||||
.into()) |
||||
} |
||||
Some(Type::Triple) => Ok(Triple::new( |
||||
match subject.ok_or_else(|| { |
||||
SyntaxError::msg( |
||||
"triple serialization should have a 'subject' key", |
||||
) |
||||
})? { |
||||
Term::NamedNode(subject) => subject.into(), |
||||
Term::BlankNode(subject) => subject.into(), |
||||
Term::Triple(subject) => Subject::Triple(subject), |
||||
Term::Literal(_) => { |
||||
return Err(SyntaxError::msg( |
||||
"The 'subject' value should not be a literal", |
||||
) |
||||
.into()) |
||||
} |
||||
}, |
||||
match predicate.ok_or_else(|| { |
||||
SyntaxError::msg( |
||||
"triple serialization should have a 'predicate' key", |
||||
) |
||||
})? { |
||||
Term::NamedNode(predicate) => predicate, |
||||
_ => { |
||||
return Err(SyntaxError::msg( |
||||
"The 'predicate' value should be a uri", |
||||
) |
||||
.into()) |
||||
} |
||||
}, |
||||
object.ok_or_else(|| { |
||||
SyntaxError::msg( |
||||
"triple serialization should have a 'object' key", |
||||
) |
||||
})?, |
||||
) |
||||
.into()), |
||||
}; |
||||
} |
||||
} |
||||
_ => return Err(SyntaxError::msg("Invalid term serialization").into()), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn read_head<R: BufRead>( |
||||
reader: &mut JsonReader<R>, |
||||
buffer: &mut Vec<u8>, |
||||
) -> Result<Vec<String>, ParserError> { |
||||
if reader.read_event(buffer)? != JsonEvent::StartObject { |
||||
return Err(SyntaxError::msg("head should be an object").into()); |
||||
} |
||||
let mut variables = None; |
||||
loop { |
||||
match reader.read_event(buffer)? { |
||||
JsonEvent::ObjectKey(key) => match key { |
||||
"vars" => variables = Some(read_string_array(reader, buffer)?), |
||||
"link" => { |
||||
read_string_array(reader, buffer)?; |
||||
} |
||||
_ => { |
||||
return Err( |
||||
SyntaxError::msg(format!("Unexpected key in head: '{}'", key)).into(), |
||||
) |
||||
} |
||||
}, |
||||
JsonEvent::EndObject => return Ok(variables.unwrap_or_else(Vec::new)), |
||||
_ => return Err(SyntaxError::msg("Invalid head serialization").into()), |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn read_string_array<R: BufRead>( |
||||
reader: &mut JsonReader<R>, |
||||
buffer: &mut Vec<u8>, |
||||
) -> Result<Vec<String>, ParserError> { |
||||
if reader.read_event(buffer)? != JsonEvent::StartArray { |
||||
return Err(SyntaxError::msg("Variable list should be an array").into()); |
||||
} |
||||
let mut elements = Vec::new(); |
||||
loop { |
||||
match reader.read_event(buffer)? { |
||||
JsonEvent::String(s) => { |
||||
elements.push(s.into()); |
||||
} |
||||
JsonEvent::EndArray => return Ok(elements), |
||||
_ => return Err(SyntaxError::msg("Variable names should be strings").into()), |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct ResultsIterator<R: BufRead> { |
||||
reader: JsonReader<R>, |
||||
buffer: Vec<u8>, |
||||
mapping: BTreeMap<String, usize>, |
||||
} |
||||
|
||||
impl<R: BufRead> Iterator for ResultsIterator<R> { |
||||
type Item = Result<Vec<Option<Term>>, EvaluationError>; |
||||
|
||||
fn next(&mut self) -> Option<Result<Vec<Option<Term>>, EvaluationError>> { |
||||
self.read_next().map_err(EvaluationError::from).transpose() |
||||
} |
||||
} |
||||
|
||||
impl<R: BufRead> ResultsIterator<R> { |
||||
fn read_next(&mut self) -> Result<Option<Vec<Option<Term>>>, ParserError> { |
||||
let mut new_bindings = vec![None; self.mapping.len()]; |
||||
loop { |
||||
match self.reader.read_event(&mut self.buffer)? { |
||||
JsonEvent::StartObject => (), |
||||
JsonEvent::EndObject => return Ok(Some(new_bindings)), |
||||
JsonEvent::EndArray | JsonEvent::Eof => return Ok(None), |
||||
JsonEvent::ObjectKey(key) => { |
||||
let k = *self.mapping.get(key).ok_or_else(|| { |
||||
SyntaxError::msg(format!( |
||||
"The variable {} has not been defined in the header", |
||||
key |
||||
)) |
||||
})?; |
||||
new_bindings[k] = Some(self.read_value()?) |
||||
} |
||||
_ => return Err(SyntaxError::msg("Invalid result serialization").into()), |
||||
} |
||||
} |
||||
} |
||||
fn read_value(&mut self) -> Result<Term, ParserError> { |
||||
enum Type { |
||||
Uri, |
||||
BNode, |
||||
Literal, |
||||
Triple, |
||||
} |
||||
#[derive(Eq, PartialEq)] |
||||
enum State { |
||||
Type, |
||||
Value, |
||||
Lang, |
||||
Datatype, |
||||
} |
||||
let mut state = None; |
||||
let mut t = None; |
||||
let mut value = None; |
||||
let mut lang = None; |
||||
let mut datatype = None; |
||||
let mut subject = None; |
||||
let mut predicate = None; |
||||
let mut object = None; |
||||
if self.reader.read_event(&mut self.buffer)? != JsonEvent::StartObject { |
||||
return Err(SyntaxError::msg("Term serializations should be an object").into()); |
||||
} |
||||
loop { |
||||
match self.reader.read_event(&mut self.buffer)? { |
||||
JsonEvent::ObjectKey(key) => match key { |
||||
"type" => state = Some(State::Type), |
||||
"value" => state = Some(State::Value), |
||||
"xml:lang" => state = Some(State::Lang), |
||||
"datatype" => state = Some(State::Datatype), |
||||
"subject" => subject = Some(self.read_value()?), |
||||
"predicate" => predicate = Some(self.read_value()?), |
||||
"object" => object = Some(self.read_value()?), |
||||
_ => { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Unexpected key in term serialization: '{}'", |
||||
key |
||||
)) |
||||
.into()) |
||||
} |
||||
}, |
||||
JsonEvent::StartObject => { |
||||
if state != Some(State::Value) { |
||||
return Err(SyntaxError::msg( |
||||
"Unexpected nested object in term serialization", |
||||
) |
||||
.into()); |
||||
} |
||||
} |
||||
JsonEvent::String(s) => match state { |
||||
Some(State::Type) => { |
||||
match s { |
||||
"uri" => t = Some(Type::Uri), |
||||
"bnode" => t = Some(Type::BNode), |
||||
"literal" => t = Some(Type::Literal), |
||||
"triple" => t = Some(Type::Triple), |
||||
_ => { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Unexpected term type: '{}'", |
||||
s |
||||
)) |
||||
.into()) |
||||
} |
||||
}; |
||||
state = None; |
||||
} |
||||
Some(State::Value) => { |
||||
value = Some(s.to_owned()); |
||||
state = None; |
||||
} |
||||
Some(State::Lang) => { |
||||
lang = Some(s.to_owned()); |
||||
state = None; |
||||
} |
||||
Some(State::Datatype) => { |
||||
datatype = Some(NamedNode::new(s).map_err(|e| { |
||||
SyntaxError::msg(format!("Invalid datatype value: {}", e)) |
||||
})?); |
||||
state = None; |
||||
} |
||||
_ => (), // impossible
|
||||
}, |
||||
JsonEvent::EndObject => { |
||||
if let Some(s) = state { |
||||
if s == State::Value { |
||||
state = None; //End of triple
|
||||
} else { |
||||
return Err(SyntaxError::msg( |
||||
"Term description values should be string", |
||||
) |
||||
.into()); |
||||
} |
||||
} else { |
||||
return match t { |
||||
None => Err(SyntaxError::msg( |
||||
"Term serialization should have a 'type' key", |
||||
) |
||||
.into()), |
||||
Some(Type::Uri) => Ok(NamedNode::new(value.ok_or_else(|| { |
||||
SyntaxError::msg("uri serialization should have a 'value' key") |
||||
})?) |
||||
.map_err(|e| SyntaxError::msg(format!("Invalid uri value: {}", e)))? |
||||
.into()), |
||||
Some(Type::BNode) => Ok(BlankNode::new(value.ok_or_else(|| { |
||||
SyntaxError::msg("bnode serialization should have a 'value' key") |
||||
})?) |
||||
.map_err(|e| SyntaxError::msg(format!("Invalid bnode value: {}", e)))? |
||||
.into()), |
||||
Some(Type::Literal) => { |
||||
let value = value.ok_or_else(|| { |
||||
SyntaxError::msg( |
||||
"literal serialization should have a 'value' key", |
||||
) |
||||
})?; |
||||
Ok(match lang { |
||||
Some(lang) => { |
||||
if let Some(datatype) = datatype { |
||||
if datatype.as_ref() != rdf::LANG_STRING { |
||||
return Err(SyntaxError::msg(format!( |
||||
"xml:lang value '{}' provided with the datatype {}", |
||||
lang, datatype |
||||
)).into()) |
||||
} |
||||
} |
||||
Literal::new_language_tagged_literal(value, &lang).map_err(|e| { |
||||
SyntaxError::msg(format!("Invalid xml:lang value '{}': {}", lang, e)) |
||||
})? |
||||
} |
||||
None => if let Some(datatype) = datatype { |
||||
Literal::new_typed_literal(value, datatype) |
||||
} else { |
||||
Literal::new_simple_literal(value) |
||||
} |
||||
} |
||||
.into()) |
||||
} |
||||
Some(Type::Triple) => Ok(Triple::new( |
||||
match subject.ok_or_else(|| { |
||||
SyntaxError::msg( |
||||
"triple serialization should have a 'subject' key", |
||||
) |
||||
})? { |
||||
Term::NamedNode(subject) => subject.into(), |
||||
Term::BlankNode(subject) => subject.into(), |
||||
Term::Triple(subject) => Subject::Triple(subject), |
||||
Term::Literal(_) => { |
||||
return Err(SyntaxError::msg( |
||||
"The 'subject' value should not be a literal", |
||||
) |
||||
.into()) |
||||
} |
||||
}, |
||||
match predicate.ok_or_else(|| { |
||||
SyntaxError::msg( |
||||
"triple serialization should have a 'predicate' key", |
||||
) |
||||
})? { |
||||
Term::NamedNode(predicate) => predicate, |
||||
_ => { |
||||
return Err(SyntaxError::msg( |
||||
"The 'predicate' value should be a uri", |
||||
) |
||||
.into()) |
||||
} |
||||
}, |
||||
object.ok_or_else(|| { |
||||
SyntaxError::msg( |
||||
"triple serialization should have a 'object' key", |
||||
) |
||||
})?, |
||||
) |
||||
.into()), |
||||
}; |
||||
} |
||||
} |
||||
_ => return Err(SyntaxError::msg("Invalid term serialization").into()), |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,337 @@ |
||||
mod csv; |
||||
mod json; |
||||
mod xml; |
||||
|
||||
use crate::io::read::{ParserError, SyntaxError}; |
||||
use crate::model::{Term, TermRef}; |
||||
use crate::sparql::io::csv::*; |
||||
use crate::sparql::io::json::*; |
||||
use crate::sparql::io::xml::*; |
||||
use crate::sparql::{EvaluationError, QueryResults, QuerySolution, QuerySolutionIter, Variable}; |
||||
use std::io::{self, BufRead, Write}; |
||||
use std::rc::Rc; |
||||
|
||||
/// [SPARQL query](https://www.w3.org/TR/sparql11-query/) results serialization formats.
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] |
||||
#[non_exhaustive] |
||||
pub enum QueryResultsFormat { |
||||
/// [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/)
|
||||
Xml, |
||||
/// [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/)
|
||||
Json, |
||||
/// [SPARQL Query Results CSV Format](https://www.w3.org/TR/sparql11-results-csv-tsv/)
|
||||
Csv, |
||||
/// [SPARQL Query Results TSV Format](https://www.w3.org/TR/sparql11-results-csv-tsv/)
|
||||
Tsv, |
||||
} |
||||
|
||||
impl QueryResultsFormat { |
||||
/// The format canonical IRI according to the [Unique URIs for file formats registry](https://www.w3.org/ns/formats/).
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::sparql::QueryResultsFormat;
|
||||
///
|
||||
/// assert_eq!(QueryResultsFormat::Json.iri(), "http://www.w3.org/ns/formats/SPARQL_Results_JSON")
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn iri(self) -> &'static str { |
||||
match self { |
||||
QueryResultsFormat::Xml => "http://www.w3.org/ns/formats/SPARQL_Results_XML", |
||||
QueryResultsFormat::Json => "http://www.w3.org/ns/formats/SPARQL_Results_JSON", |
||||
QueryResultsFormat::Csv => "http://www.w3.org/ns/formats/SPARQL_Results_CSV", |
||||
QueryResultsFormat::Tsv => "http://www.w3.org/ns/formats/SPARQL_Results_TSV", |
||||
} |
||||
} |
||||
/// The format [IANA media type](https://tools.ietf.org/html/rfc2046).
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::sparql::QueryResultsFormat;
|
||||
///
|
||||
/// assert_eq!(QueryResultsFormat::Json.media_type(), "application/sparql-results+json")
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn media_type(self) -> &'static str { |
||||
match self { |
||||
QueryResultsFormat::Xml => "application/sparql-results+xml", |
||||
QueryResultsFormat::Json => "application/sparql-results+json", |
||||
QueryResultsFormat::Csv => "text/csv; charset=utf-8", |
||||
QueryResultsFormat::Tsv => "text/tab-separated-values; charset=utf-8", |
||||
} |
||||
} |
||||
|
||||
/// The format [IANA-registered](https://tools.ietf.org/html/rfc2046) file extension.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::sparql::QueryResultsFormat;
|
||||
///
|
||||
/// assert_eq!(QueryResultsFormat::Json.file_extension(), "srj")
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn file_extension(self) -> &'static str { |
||||
match self { |
||||
QueryResultsFormat::Xml => "srx", |
||||
QueryResultsFormat::Json => "srj", |
||||
QueryResultsFormat::Csv => "csv", |
||||
QueryResultsFormat::Tsv => "tsv", |
||||
} |
||||
} |
||||
|
||||
/// Looks for a known format from a media type.
|
||||
///
|
||||
/// It supports some media type aliases.
|
||||
/// For example "application/xml" is going to return `Xml` even if it is not its canonical media type.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// use oxigraph::sparql::QueryResultsFormat;
|
||||
///
|
||||
/// assert_eq!(QueryResultsFormat::from_media_type("application/sparql-results+json; charset=utf-8"), Some(QueryResultsFormat::Json))
|
||||
/// ```
|
||||
pub fn from_media_type(media_type: &str) -> Option<Self> { |
||||
match media_type.split(';').next()?.trim() { |
||||
"application/sparql-results+xml" | "application/xml" | "text/xml" => Some(Self::Xml), |
||||
"application/sparql-results+json" | "application/json" | "text/json" => { |
||||
Some(Self::Json) |
||||
} |
||||
"text/csv" => Some(Self::Csv), |
||||
"text/tab-separated-values" | "text/tsv" => Some(Self::Tsv), |
||||
_ => None, |
||||
} |
||||
} |
||||
|
||||
/// Looks for a known format from an extension.
|
||||
///
|
||||
/// It supports some aliases.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// use oxigraph::sparql::QueryResultsFormat;
|
||||
///
|
||||
/// assert_eq!(QueryResultsFormat::from_extension("json"), Some(QueryResultsFormat::Json))
|
||||
/// ```
|
||||
pub fn from_extension(extension: &str) -> Option<Self> { |
||||
match extension { |
||||
"srx" | "xml" => Some(Self::Xml), |
||||
"srj" | "json" => Some(Self::Json), |
||||
"csv" | "txt" => Some(Self::Csv), |
||||
"tsv" => Some(Self::Tsv), |
||||
_ => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Parsers for [SPARQL query](https://www.w3.org/TR/sparql11-query/) results serialization formats.
|
||||
///
|
||||
/// It currently supports the following formats:
|
||||
/// * [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/) ([`QueryResultsFormat::Xml`](QueryResultsFormat::Xml))
|
||||
/// * [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) ([`QueryResultsFormat::Json`](QueryResultsFormat::Json))
|
||||
/// * [SPARQL Query Results TSV Format](https://www.w3.org/TR/sparql11-results-csv-tsv/) ([`QueryResultsFormat::Tsv`](QueryResultsFormat::Tsv))
|
||||
#[allow(missing_copy_implementations)] |
||||
pub struct QueryResultsParser { |
||||
format: QueryResultsFormat, |
||||
} |
||||
|
||||
impl QueryResultsParser { |
||||
/// Builds a parser for the given format.
|
||||
pub fn from_format(format: QueryResultsFormat) -> Self { |
||||
Self { format } |
||||
} |
||||
|
||||
pub fn read_results<R: BufRead>( |
||||
&self, |
||||
reader: R, |
||||
) -> Result<QueryResultsReader<R>, ParserError> { |
||||
Ok(match self.format { |
||||
QueryResultsFormat::Xml => match XmlQueryResultsReader::read(reader)? { |
||||
XmlQueryResultsReader::Boolean(r) => QueryResultsReader::Boolean(r), |
||||
XmlQueryResultsReader::Solutions { |
||||
solutions, |
||||
variables, |
||||
} => QueryResultsReader::Solutions(SolutionsReader { |
||||
variables: Rc::new(variables), |
||||
solutions: SolutionsReaderKind::Xml(solutions), |
||||
}), |
||||
}, |
||||
QueryResultsFormat::Json => match JsonQueryResultsReader::read(reader)? { |
||||
JsonQueryResultsReader::Boolean(r) => QueryResultsReader::Boolean(r), |
||||
JsonQueryResultsReader::Solutions { |
||||
solutions, |
||||
variables, |
||||
} => QueryResultsReader::Solutions(SolutionsReader { |
||||
variables: Rc::new(variables), |
||||
solutions: SolutionsReaderKind::Json(solutions), |
||||
}), |
||||
}, |
||||
QueryResultsFormat::Csv => return Err(SyntaxError::msg("CSV SPARQL results syntax is lossy and can't be parsed to a proper RDF representation").into()), |
||||
QueryResultsFormat::Tsv => match TsvQueryResultsReader::read(reader)? { |
||||
TsvQueryResultsReader::Boolean(r) => QueryResultsReader::Boolean(r), |
||||
TsvQueryResultsReader::Solutions { |
||||
solutions, |
||||
variables, |
||||
} => QueryResultsReader::Solutions(SolutionsReader { |
||||
variables: Rc::new(variables), |
||||
solutions: SolutionsReaderKind::Tsv(solutions), |
||||
}), |
||||
}, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
pub enum QueryResultsReader<R: BufRead> { |
||||
Solutions(SolutionsReader<R>), |
||||
Boolean(bool), |
||||
} |
||||
|
||||
pub struct SolutionsReader<R: BufRead> { |
||||
variables: Rc<Vec<Variable>>, |
||||
solutions: SolutionsReaderKind<R>, |
||||
} |
||||
|
||||
enum SolutionsReaderKind<R: BufRead> { |
||||
Xml(XmlSolutionsReader<R>), |
||||
Json(JsonSolutionsReader<R>), |
||||
Tsv(TsvSolutionsReader<R>), |
||||
} |
||||
|
||||
impl<R: BufRead> SolutionsReader<R> { |
||||
#[inline] |
||||
pub fn variables(&self) -> &[Variable] { |
||||
&self.variables |
||||
} |
||||
} |
||||
|
||||
impl<R: BufRead> Iterator for SolutionsReaderKind<R> { |
||||
type Item = Result<Vec<Option<Term>>, ParserError>; |
||||
|
||||
fn next(&mut self) -> Option<Result<Vec<Option<Term>>, ParserError>> { |
||||
match self { |
||||
Self::Xml(reader) => reader.read_next(), |
||||
Self::Json(reader) => reader.read_next(), |
||||
Self::Tsv(reader) => reader.read_next(), |
||||
} |
||||
.transpose() |
||||
} |
||||
} |
||||
|
||||
impl<R: BufRead> Iterator for SolutionsReader<R> { |
||||
type Item = Result<QuerySolution, ParserError>; |
||||
|
||||
fn next(&mut self) -> Option<Result<QuerySolution, ParserError>> { |
||||
Some(self.solutions.next()?.map(|values| QuerySolution { |
||||
values, |
||||
variables: self.variables.clone(), |
||||
})) |
||||
} |
||||
} |
||||
|
||||
impl<R: BufRead + 'static> From<SolutionsReader<R>> for QuerySolutionIter { |
||||
fn from(reader: SolutionsReader<R>) -> Self { |
||||
Self::new( |
||||
reader.variables.clone(), |
||||
Box::new(reader.solutions.map(|r| r.map_err(EvaluationError::from))), |
||||
) |
||||
} |
||||
} |
||||
|
||||
impl<R: BufRead + 'static> From<QueryResultsReader<R>> for QueryResults { |
||||
fn from(reader: QueryResultsReader<R>) -> Self { |
||||
match reader { |
||||
QueryResultsReader::Solutions(s) => Self::Solutions(s.into()), |
||||
QueryResultsReader::Boolean(v) => Self::Boolean(v), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// A serializer for [SPARQL query](https://www.w3.org/TR/sparql11-query/) results serialization formats.
|
||||
///
|
||||
/// It currently supports the following formats:
|
||||
/// * [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/) ([`QueryResultsFormat::Xml`](QueryResultsFormat::Xml))
|
||||
/// * [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) ([`QueryResultsFormat::Json`](QueryResultsFormat::Json))
|
||||
/// * [SPARQL Query Results CSV Format](https://www.w3.org/TR/sparql11-results-csv-tsv/) ([`QueryResultsFormat::Csv`](QueryResultsFormat::Csv))
|
||||
/// * [SPARQL Query Results TSV Format](https://www.w3.org/TR/sparql11-results-csv-tsv/) ([`QueryResultsFormat::Tsv`](QueryResultsFormat::Tsv))
|
||||
#[allow(missing_copy_implementations)] |
||||
pub struct QueryResultsSerializer { |
||||
format: QueryResultsFormat, |
||||
} |
||||
|
||||
impl QueryResultsSerializer { |
||||
/// Builds a serializer for the given format
|
||||
pub fn from_format(format: QueryResultsFormat) -> Self { |
||||
Self { format } |
||||
} |
||||
|
||||
pub fn write_boolean_result<W: Write>(&self, writer: W, value: bool) -> io::Result<W> { |
||||
match self.format { |
||||
QueryResultsFormat::Xml => write_boolean_xml_result(writer, value), |
||||
QueryResultsFormat::Json => write_boolean_json_result(writer, value), |
||||
QueryResultsFormat::Csv => write_boolean_csv_result(writer, value), |
||||
QueryResultsFormat::Tsv => write_boolean_tsv_result(writer, value), |
||||
} |
||||
} |
||||
|
||||
/// Returns a `SolutionsWriter` allowing writing query solutions into the given [`Write`](std::io::Write) implementation
|
||||
pub fn solutions_writer<W: Write>( |
||||
&self, |
||||
writer: W, |
||||
variables: &[Variable], |
||||
) -> io::Result<SolutionsWriter<W>> { |
||||
Ok(SolutionsWriter { |
||||
formatter: match self.format { |
||||
QueryResultsFormat::Xml => { |
||||
SolutionsWriterKind::Xml(XmlSolutionsWriter::start(writer, variables)?) |
||||
} |
||||
QueryResultsFormat::Json => { |
||||
SolutionsWriterKind::Json(JsonSolutionsWriter::start(writer, variables)?) |
||||
} |
||||
QueryResultsFormat::Csv => { |
||||
SolutionsWriterKind::Csv(CsvSolutionsWriter::start(writer, variables)?) |
||||
} |
||||
QueryResultsFormat::Tsv => { |
||||
SolutionsWriterKind::Tsv(TsvSolutionsWriter::start(writer, variables)?) |
||||
} |
||||
}, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/// Allows writing query results.
|
||||
/// Could be built using a [`QueryResultsSerializer`].
|
||||
///
|
||||
/// Warning: Do not forget to run the [`finish`](SolutionsWriter::finish()) method to properly write the last bytes of the file.
|
||||
#[must_use] |
||||
pub struct SolutionsWriter<W: Write> { |
||||
formatter: SolutionsWriterKind<W>, |
||||
} |
||||
|
||||
enum SolutionsWriterKind<W: Write> { |
||||
Xml(XmlSolutionsWriter<W>), |
||||
Json(JsonSolutionsWriter<W>), |
||||
Csv(CsvSolutionsWriter<W>), |
||||
Tsv(TsvSolutionsWriter<W>), |
||||
} |
||||
|
||||
impl<W: Write> SolutionsWriter<W> { |
||||
/// Writes a solution
|
||||
pub fn write<'a>( |
||||
&mut self, |
||||
solution: impl IntoIterator<Item = Option<TermRef<'a>>>, |
||||
) -> io::Result<()> { |
||||
match &mut self.formatter { |
||||
SolutionsWriterKind::Xml(writer) => writer.write(solution), |
||||
SolutionsWriterKind::Json(writer) => writer.write(solution), |
||||
SolutionsWriterKind::Csv(writer) => writer.write(solution), |
||||
SolutionsWriterKind::Tsv(writer) => writer.write(solution), |
||||
} |
||||
} |
||||
|
||||
/// Writes the last bytes of the file
|
||||
pub fn finish(self) -> io::Result<()> { |
||||
match self.formatter { |
||||
SolutionsWriterKind::Xml(write) => write.finish()?, |
||||
SolutionsWriterKind::Json(write) => write.finish()?, |
||||
SolutionsWriterKind::Csv(write) => write.finish(), |
||||
SolutionsWriterKind::Tsv(write) => write.finish(), |
||||
}; |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,597 @@ |
||||
//! Implementation of [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/)
|
||||
|
||||
use crate::io::read::{ParserError, SyntaxError}; |
||||
use crate::model::vocab::rdf; |
||||
use crate::model::*; |
||||
use crate::sparql::model::Variable; |
||||
use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event}; |
||||
use quick_xml::Reader; |
||||
use quick_xml::Writer; |
||||
use std::collections::BTreeMap; |
||||
use std::io::{self, BufRead, Write}; |
||||
|
||||
pub fn write_boolean_xml_result<W: Write>(sink: W, value: bool) -> io::Result<W> { |
||||
do_write_boolean_xml_result(sink, value).map_err(map_xml_error) |
||||
} |
||||
|
||||
fn do_write_boolean_xml_result<W: Write>(sink: W, value: bool) -> Result<W, 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(writer.into_inner()) |
||||
} |
||||
|
||||
pub struct XmlSolutionsWriter<W: Write> { |
||||
writer: Writer<W>, |
||||
variables: Vec<Variable>, |
||||
} |
||||
|
||||
impl<W: Write> XmlSolutionsWriter<W> { |
||||
pub fn start(sink: W, variables: &[Variable]) -> io::Result<Self> { |
||||
Self::do_start(sink, variables).map_err(map_xml_error) |
||||
} |
||||
|
||||
fn do_start(sink: W, variables: &[Variable]) -> Result<Self, 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")))?; |
||||
for variable in 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))?; |
||||
} |
||||
writer.write_event(Event::End(BytesEnd::borrowed(b"head")))?; |
||||
writer.write_event(Event::Start(BytesStart::borrowed_name(b"results")))?; |
||||
Ok(Self { |
||||
writer, |
||||
variables: variables.to_vec(), |
||||
}) |
||||
} |
||||
|
||||
pub fn write<'a>( |
||||
&mut self, |
||||
solution: impl IntoIterator<Item = Option<TermRef<'a>>>, |
||||
) -> io::Result<()> { |
||||
self.do_write(solution).map_err(map_xml_error) |
||||
} |
||||
|
||||
fn do_write<'a>( |
||||
&mut self, |
||||
solution: impl IntoIterator<Item = Option<TermRef<'a>>>, |
||||
) -> Result<(), quick_xml::Error> { |
||||
self.writer |
||||
.write_event(Event::Start(BytesStart::borrowed_name(b"result")))?; |
||||
for (value, variable) in solution.into_iter().zip(&self.variables) { |
||||
if let Some(value) = value { |
||||
let mut binding_tag = BytesStart::borrowed_name(b"binding"); |
||||
binding_tag.push_attribute(("name", variable.as_str())); |
||||
self.writer.write_event(Event::Start(binding_tag))?; |
||||
write_xml_term(value, &mut self.writer)?; |
||||
self.writer |
||||
.write_event(Event::End(BytesEnd::borrowed(b"binding")))?; |
||||
} |
||||
} |
||||
self.writer |
||||
.write_event(Event::End(BytesEnd::borrowed(b"result"))) |
||||
} |
||||
|
||||
pub fn finish(self) -> io::Result<W> { |
||||
self.do_finish().map_err(map_xml_error) |
||||
} |
||||
|
||||
fn do_finish(mut self) -> Result<W, quick_xml::Error> { |
||||
self.writer |
||||
.write_event(Event::End(BytesEnd::borrowed(b"results")))?; |
||||
self.writer |
||||
.write_event(Event::End(BytesEnd::borrowed(b"sparql")))?; |
||||
Ok(self.writer.into_inner()) |
||||
} |
||||
} |
||||
|
||||
fn write_xml_term( |
||||
term: TermRef<'_>, |
||||
writer: &mut Writer<impl Write>, |
||||
) -> Result<(), quick_xml::Error> { |
||||
match term { |
||||
TermRef::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")))?; |
||||
} |
||||
TermRef::BlankNode(bnode) => { |
||||
writer.write_event(Event::Start(BytesStart::borrowed_name(b"bnode")))?; |
||||
writer.write_event(Event::Text(BytesText::from_plain_str(bnode.as_str())))?; |
||||
writer.write_event(Event::End(BytesEnd::borrowed(b"bnode")))?; |
||||
} |
||||
TermRef::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")))?; |
||||
} |
||||
TermRef::Triple(triple) => { |
||||
writer.write_event(Event::Start(BytesStart::borrowed_name(b"triple")))?; |
||||
writer.write_event(Event::Start(BytesStart::borrowed_name(b"subject")))?; |
||||
write_xml_term(triple.subject.as_ref().into(), writer)?; |
||||
writer.write_event(Event::End(BytesEnd::borrowed(b"subject")))?; |
||||
writer.write_event(Event::Start(BytesStart::borrowed_name(b"predicate")))?; |
||||
write_xml_term(triple.predicate.as_ref().into(), writer)?; |
||||
writer.write_event(Event::End(BytesEnd::borrowed(b"predicate")))?; |
||||
writer.write_event(Event::Start(BytesStart::borrowed_name(b"object")))?; |
||||
write_xml_term(triple.object.as_ref(), writer)?; |
||||
writer.write_event(Event::End(BytesEnd::borrowed(b"object")))?; |
||||
writer.write_event(Event::End(BytesEnd::borrowed(b"triple")))?; |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub enum XmlQueryResultsReader<R: BufRead> { |
||||
Solutions { |
||||
variables: Vec<Variable>, |
||||
solutions: XmlSolutionsReader<R>, |
||||
}, |
||||
Boolean(bool), |
||||
} |
||||
|
||||
impl<R: BufRead> XmlQueryResultsReader<R> { |
||||
pub fn read(source: R) -> Result<Self, ParserError> { |
||||
enum State { |
||||
Start, |
||||
Sparql, |
||||
Head, |
||||
AfterHead, |
||||
Boolean, |
||||
} |
||||
|
||||
let mut reader = Reader::from_reader(source); |
||||
reader.trim_text(true); |
||||
reader.expand_empty_elements(true); |
||||
|
||||
let mut buffer = Vec::default(); |
||||
let mut namespace_buffer = Vec::default(); |
||||
let mut variables = Vec::default(); |
||||
let mut state = State::Start; |
||||
|
||||
//Read header
|
||||
loop { |
||||
let event = { |
||||
let (ns, event) = |
||||
reader.read_namespaced_event(&mut buffer, &mut namespace_buffer)?; |
||||
if let Some(ns) = ns { |
||||
if ns != b"http://www.w3.org/2005/sparql-results#".as_ref() { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Unexpected namespace found in RDF/XML query result: {}", |
||||
reader.decode(ns)? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
event |
||||
}; |
||||
match event { |
||||
Event::Start(event) => match state { |
||||
State::Start => { |
||||
if event.name() == b"sparql" { |
||||
state = State::Sparql; |
||||
} else { |
||||
return Err(SyntaxError::msg(format!("Expecting <sparql> tag, found {}", reader.decode(event.name())?)).into()); |
||||
} |
||||
} |
||||
State::Sparql => { |
||||
if event.name() == b"head" { |
||||
state = State::Head; |
||||
} else { |
||||
return Err(SyntaxError::msg(format!("Expecting <head> tag, found {}", reader.decode(event.name())?)).into()); |
||||
} |
||||
} |
||||
State::Head => { |
||||
if event.name() == b"variable" { |
||||
let name = event.attributes() |
||||
.filter_map(std::result::Result::ok) |
||||
.find(|attr| attr.key == b"name") |
||||
.ok_or_else(|| SyntaxError::msg("No name attribute found for the <variable> tag"))? |
||||
.unescape_and_decode_value(&reader)?; |
||||
variables.push(Variable::new(name).map_err(|e| SyntaxError::msg(format!("Invalid variable name: {}", e)))?); |
||||
} else if event.name() == b"link" { |
||||
// no op
|
||||
} else { |
||||
return Err(SyntaxError::msg(format!("Expecting <variable> or <link> tag, found {}", reader.decode(event.name())?)).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_str().as_bytes().to_vec(), i); |
||||
} |
||||
return Ok(Self::Solutions { variables, |
||||
solutions: XmlSolutionsReader { |
||||
reader, |
||||
buffer, |
||||
namespace_buffer, |
||||
mapping, |
||||
stack: Vec::new(), |
||||
subject_stack: Vec::new(), |
||||
predicate_stack: Vec::new(), |
||||
object_stack: Vec::new(), |
||||
}}); |
||||
} else if event.name() != b"link" && event.name() != b"results" && event.name() != b"boolean" { |
||||
return Err(SyntaxError::msg(format!("Expecting sparql tag, found {}", reader.decode(event.name())?)).into()); |
||||
} |
||||
} |
||||
State::Boolean => return Err(SyntaxError::msg(format!("Unexpected tag inside of <boolean> tag: {}", reader.decode(event.name())?)).into()) |
||||
}, |
||||
Event::Text(event) => { |
||||
let value = event.unescaped()?; |
||||
return match state { |
||||
State::Boolean => { |
||||
return if value.as_ref() == b"true" { |
||||
Ok(Self::Boolean(true)) |
||||
} else if value.as_ref() == b"false" { |
||||
Ok(Self::Boolean(false)) |
||||
} else { |
||||
Err(SyntaxError::msg(format!("Unexpected boolean value. Found {}", reader.decode(&value)?)).into()) |
||||
}; |
||||
} |
||||
_ => Err(SyntaxError::msg(format!("Unexpected textual value found: {}", reader.decode(&value)?)).into()) |
||||
}; |
||||
}, |
||||
Event::End(event) => { |
||||
if let State::Head = state { |
||||
if event.name() == b"head" { |
||||
state = State::AfterHead |
||||
} |
||||
} else { |
||||
return Err(SyntaxError::msg("Unexpected early file end. All results file should have a <head> and a <result> or <boolean> tag").into()); |
||||
} |
||||
}, |
||||
Event::Eof => return Err(SyntaxError::msg("Unexpected early file end. All results file should have a <head> and a <result> or <boolean> tag").into()), |
||||
_ => (), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
enum State { |
||||
Start, |
||||
Result, |
||||
Binding, |
||||
Uri, |
||||
BNode, |
||||
Literal, |
||||
Triple, |
||||
Subject, |
||||
Predicate, |
||||
Object, |
||||
End, |
||||
} |
||||
|
||||
pub struct XmlSolutionsReader<R: BufRead> { |
||||
reader: Reader<R>, |
||||
buffer: Vec<u8>, |
||||
namespace_buffer: Vec<u8>, |
||||
mapping: BTreeMap<Vec<u8>, usize>, |
||||
stack: Vec<State>, |
||||
subject_stack: Vec<Term>, |
||||
predicate_stack: Vec<Term>, |
||||
object_stack: Vec<Term>, |
||||
} |
||||
|
||||
impl<R: BufRead> XmlSolutionsReader<R> { |
||||
pub fn read_next(&mut self) -> Result<Option<Vec<Option<Term>>>, ParserError> { |
||||
let mut state = State::Start; |
||||
|
||||
let mut new_bindings = vec![None; self.mapping.len()]; |
||||
|
||||
let mut current_var = None; |
||||
let mut term: Option<Term> = 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)?; |
||||
if let Some(ns) = ns { |
||||
if ns != b"http://www.w3.org/2005/sparql-results#".as_ref() { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Unexpected namespace found in RDF/XML query result: {}", |
||||
self.reader.decode(ns)? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
match event { |
||||
Event::Start(event) => match state { |
||||
State::Start => { |
||||
if event.name() == b"result" { |
||||
state = State::Result; |
||||
} else { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Expecting <result>, found {}", |
||||
self.reader.decode(event.name())? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
State::Result => { |
||||
if event.name() == b"binding" { |
||||
match event |
||||
.attributes() |
||||
.filter_map(std::result::Result::ok) |
||||
.find(|attr| attr.key == b"name") |
||||
{ |
||||
Some(attr) => current_var = Some(attr.unescaped_value()?.to_vec()), |
||||
None => { |
||||
return Err(SyntaxError::msg( |
||||
"No name attribute found for the <binding> tag", |
||||
) |
||||
.into()); |
||||
} |
||||
} |
||||
state = State::Binding; |
||||
} else { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Expecting <binding>, found {}", |
||||
self.reader.decode(event.name())? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
State::Binding | State::Subject | State::Predicate | State::Object => { |
||||
if term.is_some() { |
||||
return Err(SyntaxError::msg( |
||||
"There is already a value for the current binding", |
||||
) |
||||
.into()); |
||||
} |
||||
self.stack.push(state); |
||||
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().flatten() { |
||||
if attr.key == b"xml:lang" { |
||||
lang = Some(attr.unescape_and_decode_value(&self.reader)?); |
||||
} else if attr.key == b"datatype" { |
||||
let iri = attr.unescape_and_decode_value(&self.reader)?; |
||||
datatype = Some(NamedNode::new(&iri).map_err(|e| { |
||||
SyntaxError::msg(format!( |
||||
"Invalid datatype IRI '{}': {}", |
||||
iri, e |
||||
)) |
||||
})?); |
||||
} |
||||
} |
||||
state = State::Literal; |
||||
} else if event.name() == b"triple" { |
||||
state = State::Triple; |
||||
} else { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Expecting <uri>, <bnode> or <literal> found {}", |
||||
self.reader.decode(event.name())? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
State::Triple => { |
||||
if event.name() == b"subject" { |
||||
state = State::Subject |
||||
} else if event.name() == b"predicate" { |
||||
state = State::Predicate |
||||
} else if event.name() == b"object" { |
||||
state = State::Object |
||||
} else { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Expecting <subject>, <predicate> or <object> found {}", |
||||
self.reader.decode(event.name())? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
_ => (), |
||||
}, |
||||
Event::Text(event) => { |
||||
let data = event.unescaped()?; |
||||
match state { |
||||
State::Uri => { |
||||
let iri = self.reader.decode(&data)?; |
||||
term = Some( |
||||
NamedNode::new(iri) |
||||
.map_err(|e| { |
||||
SyntaxError::msg(format!( |
||||
"Invalid IRI value '{}': {}", |
||||
iri, e |
||||
)) |
||||
})? |
||||
.into(), |
||||
) |
||||
} |
||||
State::BNode => { |
||||
let bnode = self.reader.decode(&data)?; |
||||
term = Some( |
||||
BlankNode::new(bnode) |
||||
.map_err(|e| { |
||||
SyntaxError::msg(format!( |
||||
"Invalid blank node value '{}': {}", |
||||
bnode, e |
||||
)) |
||||
})? |
||||
.into(), |
||||
) |
||||
} |
||||
State::Literal => { |
||||
term = Some( |
||||
build_literal( |
||||
self.reader.decode(&data)?, |
||||
lang.take(), |
||||
datatype.take(), |
||||
)? |
||||
.into(), |
||||
); |
||||
} |
||||
_ => { |
||||
return Err(SyntaxError::msg(format!( |
||||
"Unexpected textual value found: {}", |
||||
self.reader.decode(&data)? |
||||
)) |
||||
.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 { |
||||
if let Some(var) = self.mapping.get(var) { |
||||
new_bindings[*var] = term.take() |
||||
} else { |
||||
return Err( |
||||
SyntaxError::msg(format!("The variable '{}' is used in a binding but not declared in the variables list", self.reader.decode(var)?)).into() |
||||
); |
||||
} |
||||
} else { |
||||
return Err(SyntaxError::msg("No name found for <binding> tag").into()); |
||||
} |
||||
state = State::Result; |
||||
} |
||||
State::Subject => { |
||||
if let Some(subject) = term.take() { |
||||
self.subject_stack.push(subject) |
||||
} |
||||
state = State::Triple; |
||||
} |
||||
State::Predicate => { |
||||
if let Some(predicate) = term.take() { |
||||
self.predicate_stack.push(predicate) |
||||
} |
||||
state = State::Triple; |
||||
} |
||||
State::Object => { |
||||
if let Some(object) = term.take() { |
||||
self.object_stack.push(object) |
||||
} |
||||
state = State::Triple; |
||||
} |
||||
State::Uri => state = self.stack.pop().unwrap(), |
||||
State::BNode => { |
||||
if term.is_none() { |
||||
//We default to a random bnode
|
||||
term = Some(BlankNode::default().into()) |
||||
} |
||||
state = self.stack.pop().unwrap() |
||||
} |
||||
State::Literal => { |
||||
if term.is_none() { |
||||
//We default to the empty literal
|
||||
term = Some(build_literal("", lang.take(), datatype.take())?.into()) |
||||
} |
||||
state = self.stack.pop().unwrap(); |
||||
} |
||||
State::Triple => { |
||||
if let (Some(subject), Some(predicate), Some(object)) = ( |
||||
self.subject_stack.pop(), |
||||
self.predicate_stack.pop(), |
||||
self.object_stack.pop(), |
||||
) { |
||||
term = Some( |
||||
Triple::new( |
||||
match subject { |
||||
Term::NamedNode(subject) => subject.into(), |
||||
Term::BlankNode(subject) => subject.into(), |
||||
Term::Triple(subject) => Subject::Triple(subject), |
||||
Term::Literal(_) => { |
||||
return Err(SyntaxError::msg( |
||||
"The <subject> value should not be a <literal>", |
||||
) |
||||
.into()) |
||||
} |
||||
}, |
||||
match predicate { |
||||
Term::NamedNode(predicate) => predicate, |
||||
_ => { |
||||
return Err(SyntaxError::msg( |
||||
"The <predicate> value should be an <uri>", |
||||
) |
||||
.into()) |
||||
} |
||||
}, |
||||
object, |
||||
) |
||||
.into(), |
||||
); |
||||
state = self.stack.pop().unwrap(); |
||||
} else { |
||||
return Err( |
||||
SyntaxError::msg("A <triple> should contain a <subject>, a <predicate> and an <object>").into() |
||||
); |
||||
} |
||||
} |
||||
State::End => (), |
||||
}, |
||||
Event::Eof => return Ok(None), |
||||
_ => (), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn build_literal( |
||||
value: impl Into<String>, |
||||
lang: Option<String>, |
||||
datatype: Option<NamedNode>, |
||||
) -> Result<Literal, ParserError> { |
||||
match lang { |
||||
Some(lang) => { |
||||
if let Some(datatype) = datatype { |
||||
if datatype.as_ref() != rdf::LANG_STRING { |
||||
return Err(SyntaxError::msg(format!( |
||||
"xml:lang value '{}' provided with the datatype {}", |
||||
lang, datatype |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
Literal::new_language_tagged_literal(value, &lang).map_err(|e| { |
||||
SyntaxError::msg(format!("Invalid xml:lang value '{}': {}", lang, e)).into() |
||||
}) |
||||
} |
||||
None => Ok(if let Some(datatype) = datatype { |
||||
Literal::new_typed_literal(value, datatype) |
||||
} else { |
||||
Literal::new_simple_literal(value) |
||||
}), |
||||
} |
||||
} |
||||
|
||||
fn map_xml_error(error: quick_xml::Error) -> io::Error { |
||||
match error { |
||||
quick_xml::Error::Io(error) => error, |
||||
quick_xml::Error::UnexpectedEof(_) => io::Error::new(io::ErrorKind::UnexpectedEof, error), |
||||
_ => io::Error::new(io::ErrorKind::InvalidData, error), |
||||
} |
||||
} |
@ -1,455 +0,0 @@ |
||||
//! Implementation of [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/)
|
||||
|
||||
use crate::error::{invalid_data_error, invalid_input_error}; |
||||
use crate::model::vocab::rdf; |
||||
use crate::model::*; |
||||
use crate::sparql::error::EvaluationError; |
||||
use crate::sparql::model::*; |
||||
use json_event_parser::{JsonEvent, JsonReader, JsonWriter}; |
||||
use std::collections::BTreeMap; |
||||
use std::io; |
||||
use std::io::{BufRead, Write}; |
||||
use std::rc::Rc; |
||||
|
||||
pub fn write_json_results(results: QueryResults, sink: impl Write) -> Result<(), EvaluationError> { |
||||
let mut writer = JsonWriter::from_writer(sink); |
||||
match results { |
||||
QueryResults::Boolean(value) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("head"))?; |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("boolean"))?; |
||||
writer.write_event(JsonEvent::Boolean(value))?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
Ok(()) |
||||
} |
||||
QueryResults::Solutions(solutions) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("head"))?; |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("vars"))?; |
||||
writer.write_event(JsonEvent::StartArray)?; |
||||
for variable in solutions.variables() { |
||||
writer.write_event(JsonEvent::String(variable.as_str()))?; |
||||
} |
||||
writer.write_event(JsonEvent::EndArray)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("results"))?; |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("bindings"))?; |
||||
writer.write_event(JsonEvent::StartArray)?; |
||||
for solution in solutions { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
|
||||
let solution = solution?; |
||||
for (variable, value) in solution.iter() { |
||||
writer.write_event(JsonEvent::ObjectKey(variable.as_str()))?; |
||||
write_json_term(value.as_ref(), &mut writer)?; |
||||
} |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
writer.write_event(JsonEvent::EndArray)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
Ok(()) |
||||
} |
||||
QueryResults::Graph(_) => Err(invalid_input_error( |
||||
"Graphs could not be formatted to SPARQL query results XML format", |
||||
) |
||||
.into()), |
||||
} |
||||
} |
||||
|
||||
fn write_json_term( |
||||
term: TermRef<'_>, |
||||
writer: &mut JsonWriter<impl Write>, |
||||
) -> Result<(), EvaluationError> { |
||||
match term { |
||||
TermRef::NamedNode(uri) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("type"))?; |
||||
writer.write_event(JsonEvent::String("uri"))?; |
||||
writer.write_event(JsonEvent::ObjectKey("value"))?; |
||||
writer.write_event(JsonEvent::String(uri.as_str()))?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
TermRef::BlankNode(bnode) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("type"))?; |
||||
writer.write_event(JsonEvent::String("bnode"))?; |
||||
writer.write_event(JsonEvent::ObjectKey("value"))?; |
||||
writer.write_event(JsonEvent::String(bnode.as_str()))?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
TermRef::Literal(literal) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("type"))?; |
||||
writer.write_event(JsonEvent::String("literal"))?; |
||||
writer.write_event(JsonEvent::ObjectKey("value"))?; |
||||
writer.write_event(JsonEvent::String(literal.value()))?; |
||||
if let Some(language) = literal.language() { |
||||
writer.write_event(JsonEvent::ObjectKey("xml:lang"))?; |
||||
writer.write_event(JsonEvent::String(language))?; |
||||
} else if !literal.is_plain() { |
||||
writer.write_event(JsonEvent::ObjectKey("datatype"))?; |
||||
writer.write_event(JsonEvent::String(literal.datatype().as_str()))?; |
||||
} |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
TermRef::Triple(triple) => { |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("type"))?; |
||||
writer.write_event(JsonEvent::String("triple"))?; |
||||
writer.write_event(JsonEvent::ObjectKey("value"))?; |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
writer.write_event(JsonEvent::ObjectKey("subject"))?; |
||||
write_json_term(triple.subject.as_ref().into(), writer)?; |
||||
writer.write_event(JsonEvent::ObjectKey("predicate"))?; |
||||
write_json_term(triple.predicate.as_ref().into(), writer)?; |
||||
writer.write_event(JsonEvent::ObjectKey("object"))?; |
||||
write_json_term(triple.object.as_ref(), writer)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
writer.write_event(JsonEvent::EndObject)?; |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn read_json_results(source: impl BufRead + 'static) -> io::Result<QueryResults> { |
||||
let mut reader = JsonReader::from_reader(source); |
||||
let mut buffer = Vec::default(); |
||||
let mut variables = None; |
||||
|
||||
if reader.read_event(&mut buffer)? != JsonEvent::StartObject { |
||||
return Err(invalid_data_error( |
||||
"SPARQL JSON results should be an object", |
||||
)); |
||||
} |
||||
|
||||
loop { |
||||
let event = reader.read_event(&mut buffer)?; |
||||
match event { |
||||
JsonEvent::ObjectKey(key) => match key { |
||||
"head" => variables = Some(read_head(&mut reader, &mut buffer)?), |
||||
"results" => { |
||||
if reader.read_event(&mut buffer)? != JsonEvent::StartObject { |
||||
return Err(invalid_data_error("'results' should be an object")); |
||||
} |
||||
if reader.read_event(&mut buffer)? != JsonEvent::ObjectKey("bindings") { |
||||
return Err(invalid_data_error( |
||||
"'results' should contain a 'bindings' key", |
||||
)); |
||||
} |
||||
if reader.read_event(&mut buffer)? != JsonEvent::StartArray { |
||||
return Err(invalid_data_error("'bindings' should be an object")); |
||||
} |
||||
return if let Some(variables) = variables { |
||||
let mut mapping = BTreeMap::default(); |
||||
for (i, var) in variables.iter().enumerate() { |
||||
mapping.insert(var.clone(), i); |
||||
} |
||||
Ok(QueryResults::Solutions(QuerySolutionIter::new( |
||||
Rc::new( |
||||
variables |
||||
.into_iter() |
||||
.map(Variable::new) |
||||
.collect::<Result<Vec<_>, _>>() |
||||
.map_err(invalid_data_error)?, |
||||
), |
||||
Box::new(ResultsIterator { |
||||
reader, |
||||
buffer, |
||||
mapping, |
||||
}), |
||||
))) |
||||
} else { |
||||
Err(invalid_data_error( |
||||
"SPARQL tuple query results should contain a head key", |
||||
)) |
||||
}; |
||||
} |
||||
"boolean" => { |
||||
return if let JsonEvent::Boolean(v) = reader.read_event(&mut buffer)? { |
||||
Ok(QueryResults::Boolean(v)) |
||||
} else { |
||||
Err(invalid_data_error("Unexpected boolean value")) |
||||
} |
||||
} |
||||
_ => { |
||||
return Err(invalid_data_error(format!( |
||||
"Expecting head or result key, found {}", |
||||
key |
||||
))); |
||||
} |
||||
}, |
||||
JsonEvent::EndObject => { |
||||
return Err(invalid_data_error( |
||||
"SPARQL results should contain a bindings key or a boolean key", |
||||
)) |
||||
} |
||||
JsonEvent::Eof => return Err(io::Error::from(io::ErrorKind::UnexpectedEof)), |
||||
_ => return Err(invalid_data_error("Invalid SPARQL results serialization")), |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn read_head<R: BufRead>( |
||||
reader: &mut JsonReader<R>, |
||||
buffer: &mut Vec<u8>, |
||||
) -> io::Result<Vec<String>> { |
||||
if reader.read_event(buffer)? != JsonEvent::StartObject { |
||||
return Err(invalid_data_error("head should be an object")); |
||||
} |
||||
let mut variables = None; |
||||
loop { |
||||
match reader.read_event(buffer)? { |
||||
JsonEvent::ObjectKey(key) => match key { |
||||
"vars" => variables = Some(read_string_array(reader, buffer)?), |
||||
"link" => { |
||||
read_string_array(reader, buffer)?; |
||||
} |
||||
_ => { |
||||
return Err(invalid_data_error(format!( |
||||
"Unexpected key in head: '{}'", |
||||
key |
||||
))) |
||||
} |
||||
}, |
||||
JsonEvent::EndObject => return Ok(variables.unwrap_or_else(Vec::new)), |
||||
_ => return Err(invalid_data_error("Invalid head serialization")), |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn read_string_array<R: BufRead>( |
||||
reader: &mut JsonReader<R>, |
||||
buffer: &mut Vec<u8>, |
||||
) -> io::Result<Vec<String>> { |
||||
if reader.read_event(buffer)? != JsonEvent::StartArray { |
||||
return Err(invalid_data_error("Variable list should be an array")); |
||||
} |
||||
let mut elements = Vec::new(); |
||||
loop { |
||||
match reader.read_event(buffer)? { |
||||
JsonEvent::String(s) => { |
||||
elements.push(s.into()); |
||||
} |
||||
JsonEvent::EndArray => return Ok(elements), |
||||
_ => return Err(invalid_data_error("Variable names should be strings")), |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct ResultsIterator<R: BufRead> { |
||||
reader: JsonReader<R>, |
||||
buffer: Vec<u8>, |
||||
mapping: BTreeMap<String, usize>, |
||||
} |
||||
|
||||
impl<R: BufRead> Iterator for ResultsIterator<R> { |
||||
type Item = Result<Vec<Option<Term>>, EvaluationError>; |
||||
|
||||
fn next(&mut self) -> Option<Result<Vec<Option<Term>>, EvaluationError>> { |
||||
self.read_next().map_err(EvaluationError::from).transpose() |
||||
} |
||||
} |
||||
|
||||
impl<R: BufRead> ResultsIterator<R> { |
||||
fn read_next(&mut self) -> io::Result<Option<Vec<Option<Term>>>> { |
||||
let mut new_bindings = vec![None; self.mapping.len()]; |
||||
loop { |
||||
match self.reader.read_event(&mut self.buffer)? { |
||||
JsonEvent::StartObject => (), |
||||
JsonEvent::EndObject => return Ok(Some(new_bindings)), |
||||
JsonEvent::EndArray | JsonEvent::Eof => return Ok(None), |
||||
JsonEvent::ObjectKey(key) => { |
||||
let k = *self.mapping.get(key).ok_or_else(|| { |
||||
invalid_data_error(format!( |
||||
"The variable {} has not been defined in the header", |
||||
key |
||||
)) |
||||
})?; |
||||
new_bindings[k] = Some(self.read_value()?) |
||||
} |
||||
_ => return Err(invalid_data_error("Invalid result serialization")), |
||||
} |
||||
} |
||||
} |
||||
fn read_value(&mut self) -> io::Result<Term> { |
||||
enum Type { |
||||
Uri, |
||||
BNode, |
||||
Literal, |
||||
Triple, |
||||
} |
||||
#[derive(Eq, PartialEq)] |
||||
enum State { |
||||
Type, |
||||
Value, |
||||
Lang, |
||||
Datatype, |
||||
} |
||||
let mut state = None; |
||||
let mut t = None; |
||||
let mut value = None; |
||||
let mut lang = None; |
||||
let mut datatype = None; |
||||
let mut subject = None; |
||||
let mut predicate = None; |
||||
let mut object = None; |
||||
if self.reader.read_event(&mut self.buffer)? != JsonEvent::StartObject { |
||||
return Err(invalid_data_error( |
||||
"Term serializations should be an object", |
||||
)); |
||||
} |
||||
loop { |
||||
match self.reader.read_event(&mut self.buffer)? { |
||||
JsonEvent::ObjectKey(key) => match key { |
||||
"type" => state = Some(State::Type), |
||||
"value" => state = Some(State::Value), |
||||
"xml:lang" => state = Some(State::Lang), |
||||
"datatype" => state = Some(State::Datatype), |
||||
"subject" => subject = Some(self.read_value()?), |
||||
"predicate" => predicate = Some(self.read_value()?), |
||||
"object" => object = Some(self.read_value()?), |
||||
_ => { |
||||
return Err(invalid_data_error(format!( |
||||
"Unexpected key in term serialization: '{}'", |
||||
key |
||||
))) |
||||
} |
||||
}, |
||||
JsonEvent::StartObject => { |
||||
if state != Some(State::Value) { |
||||
return Err(invalid_data_error( |
||||
"Unexpected nested object in term serialization", |
||||
)); |
||||
} |
||||
} |
||||
JsonEvent::String(s) => match state { |
||||
Some(State::Type) => { |
||||
match s { |
||||
"uri" => t = Some(Type::Uri), |
||||
"bnode" => t = Some(Type::BNode), |
||||
"literal" => t = Some(Type::Literal), |
||||
"triple" => t = Some(Type::Triple), |
||||
_ => { |
||||
return Err(invalid_data_error(format!( |
||||
"Unexpected term type: '{}'", |
||||
s |
||||
))) |
||||
} |
||||
}; |
||||
state = None; |
||||
} |
||||
Some(State::Value) => { |
||||
value = Some(s.to_owned()); |
||||
state = None; |
||||
} |
||||
Some(State::Lang) => { |
||||
lang = Some(s.to_owned()); |
||||
state = None; |
||||
} |
||||
Some(State::Datatype) => { |
||||
datatype = Some(NamedNode::new(s).map_err(|e| { |
||||
invalid_data_error(format!("Invalid datatype value: {}", e)) |
||||
})?); |
||||
state = None; |
||||
} |
||||
_ => (), // impossible
|
||||
}, |
||||
JsonEvent::EndObject => { |
||||
if let Some(s) = state { |
||||
if s == State::Value { |
||||
state = None; //End of triple
|
||||
} else { |
||||
return Err(invalid_data_error( |
||||
"Term description values should be string", |
||||
)); |
||||
} |
||||
} else { |
||||
return match t { |
||||
None => Err(invalid_data_error( |
||||
"Term serialization should have a 'type' key", |
||||
)), |
||||
Some(Type::Uri) => Ok(NamedNode::new(value.ok_or_else(|| { |
||||
invalid_data_error("uri serialization should have a 'value' key") |
||||
})?) |
||||
.map_err(|e| invalid_data_error(format!("Invalid uri value: {}", e)))? |
||||
.into()), |
||||
Some(Type::BNode) => Ok(BlankNode::new(value.ok_or_else(|| { |
||||
invalid_data_error("bnode serialization should have a 'value' key") |
||||
})?) |
||||
.map_err(|e| invalid_data_error(format!("Invalid bnode value: {}", e)))? |
||||
.into()), |
||||
Some(Type::Literal) => { |
||||
let value = value.ok_or_else(|| { |
||||
invalid_data_error( |
||||
"literal serialization should have a 'value' key", |
||||
) |
||||
})?; |
||||
Ok(match lang { |
||||
Some(lang) => { |
||||
if let Some(datatype) = datatype { |
||||
if datatype.as_ref() != rdf::LANG_STRING { |
||||
return Err(invalid_data_error(format!( |
||||
"xml:lang value '{}' provided with the datatype {}", |
||||
lang, datatype |
||||
))) |
||||
} |
||||
} |
||||
Literal::new_language_tagged_literal(value, &lang).map_err(|e| { |
||||
invalid_data_error(format!("Invalid xml:lang value '{}': {}", lang, e)) |
||||
})? |
||||
} |
||||
None => if let Some(datatype) = datatype { |
||||
Literal::new_typed_literal(value, datatype) |
||||
} else { |
||||
Literal::new_simple_literal(value) |
||||
} |
||||
} |
||||
.into()) |
||||
} |
||||
Some(Type::Triple) => Ok(Triple::new( |
||||
match subject.ok_or_else(|| { |
||||
invalid_data_error( |
||||
"triple serialization should have a 'subject' key", |
||||
) |
||||
})? { |
||||
Term::NamedNode(subject) => subject.into(), |
||||
Term::BlankNode(subject) => subject.into(), |
||||
Term::Triple(subject) => Subject::Triple(subject), |
||||
Term::Literal(_) => { |
||||
return Err(invalid_data_error( |
||||
"The 'subject' value should not be a literal", |
||||
)) |
||||
} |
||||
}, |
||||
match predicate.ok_or_else(|| { |
||||
invalid_data_error( |
||||
"triple serialization should have a 'predicate' key", |
||||
) |
||||
})? { |
||||
Term::NamedNode(predicate) => predicate, |
||||
_ => { |
||||
return Err(invalid_data_error( |
||||
"The 'predicate' value should be a uri", |
||||
)) |
||||
} |
||||
}, |
||||
object.ok_or_else(|| { |
||||
invalid_data_error( |
||||
"triple serialization should have a 'object' key", |
||||
) |
||||
})?, |
||||
) |
||||
.into()), |
||||
}; |
||||
} |
||||
} |
||||
_ => return Err(invalid_data_error("Invalid term serialization")), |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,674 +0,0 @@ |
||||
//! 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::vocab::rdf; |
||||
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; |
||||
use std::io::BufRead; |
||||
use std::io::Write; |
||||
use std::iter::empty; |
||||
use std::rc::Rc; |
||||
|
||||
pub fn write_xml_results(results: QueryResults, sink: impl Write) -> Result<(), EvaluationError> { |
||||
match results { |
||||
QueryResults::Boolean(value) => { |
||||
write_boolean(value, sink).map_err(map_xml_error)?; |
||||
Ok(()) |
||||
} |
||||
QueryResults::Solutions(solutions) => write_solutions(solutions, sink), |
||||
QueryResults::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: QuerySolutionIter, 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)?; |
||||
write_xml_term(value.as_ref(), &mut writer)?; |
||||
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(()) |
||||
} |
||||
|
||||
fn write_xml_term( |
||||
term: TermRef<'_>, |
||||
writer: &mut Writer<impl Write>, |
||||
) -> Result<(), EvaluationError> { |
||||
match term { |
||||
TermRef::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)?; |
||||
} |
||||
TermRef::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)?; |
||||
} |
||||
TermRef::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)?; |
||||
} |
||||
TermRef::Triple(triple) => { |
||||
writer |
||||
.write_event(Event::Start(BytesStart::borrowed_name(b"triple"))) |
||||
.map_err(map_xml_error)?; |
||||
writer |
||||
.write_event(Event::Start(BytesStart::borrowed_name(b"subject"))) |
||||
.map_err(map_xml_error)?; |
||||
write_xml_term(triple.subject.as_ref().into(), writer)?; |
||||
writer |
||||
.write_event(Event::End(BytesEnd::borrowed(b"subject"))) |
||||
.map_err(map_xml_error)?; |
||||
writer |
||||
.write_event(Event::Start(BytesStart::borrowed_name(b"predicate"))) |
||||
.map_err(map_xml_error)?; |
||||
write_xml_term(triple.predicate.as_ref().into(), writer)?; |
||||
writer |
||||
.write_event(Event::End(BytesEnd::borrowed(b"predicate"))) |
||||
.map_err(map_xml_error)?; |
||||
writer |
||||
.write_event(Event::Start(BytesStart::borrowed_name(b"object"))) |
||||
.map_err(map_xml_error)?; |
||||
write_xml_term(triple.object.as_ref(), writer)?; |
||||
writer |
||||
.write_event(Event::End(BytesEnd::borrowed(b"object"))) |
||||
.map_err(map_xml_error)?; |
||||
writer |
||||
.write_event(Event::End(BytesEnd::borrowed(b"triple"))) |
||||
.map_err(map_xml_error)?; |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn read_xml_results(source: impl BufRead + 'static) -> io::Result<QueryResults> { |
||||
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<String> = 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)? |
||||
))); |
||||
} |
||||
} |
||||
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 <sparql> tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?))); |
||||
} |
||||
} |
||||
State::Sparql => { |
||||
if event.name() == b"head" { |
||||
state = State::Head; |
||||
} else { |
||||
return Err(invalid_data_error(format!("Expecting <head> tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?))); |
||||
} |
||||
} |
||||
State::Head => { |
||||
if event.name() == b"variable" { |
||||
let name = event.attributes() |
||||
.filter_map(std::result::Result::ok) |
||||
.find(|attr| attr.key == b"name") |
||||
.ok_or_else(|| invalid_data_error("No name attribute found for the <variable> 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 <variable> or <link> tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?))); |
||||
} |
||||
} |
||||
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(QueryResults::Solutions(QuerySolutionIter::new( |
||||
Rc::new(variables.into_iter().map(Variable::new).collect::<Result<Vec<_>,_>>().map_err(invalid_data_error)?), |
||||
Box::new(ResultsIterator { |
||||
reader, |
||||
buffer, |
||||
namespace_buffer, |
||||
mapping, |
||||
stack: Vec::new(), |
||||
subject_stack: Vec::new(), |
||||
predicate_stack: Vec::new(), |
||||
object_stack:Vec::new(), |
||||
}), |
||||
))); |
||||
} 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)?))); |
||||
} |
||||
} |
||||
State::Boolean => return Err(invalid_data_error(format!("Unexpected tag inside of <boolean> tag: {}", reader.decode(event.name()).map_err(map_xml_error)?))) |
||||
}, |
||||
Event::Empty(event) => match state { |
||||
State::Sparql => { |
||||
if event.name() == b"head" { |
||||
state = State::AfterHead; |
||||
} else { |
||||
return Err(invalid_data_error(format!("Expecting <head> tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?))); |
||||
} |
||||
} |
||||
State::Head => { |
||||
if event.name() == b"variable" { |
||||
let name = event.attributes() |
||||
.filter_map(std::result::Result::ok) |
||||
.find(|attr| attr.key == b"name") |
||||
.ok_or_else(|| invalid_data_error("No name attribute found for the <variable> 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 <variable> or <link> tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?))); |
||||
} |
||||
}, |
||||
State::AfterHead => { |
||||
return if event.name() == b"results" { |
||||
Ok(QueryResults::Solutions(QuerySolutionIter::new( |
||||
Rc::new(variables.into_iter().map(Variable::new).collect::<Result<Vec<_>,_>>().map_err(invalid_data_error)?), |
||||
Box::new(empty()), |
||||
))) |
||||
} else { |
||||
Err(invalid_data_error(format!("Unexpected autoclosing tag <{}>", reader.decode(event.name()).map_err(map_xml_error)?))) |
||||
} |
||||
} |
||||
_ => return Err(invalid_data_error(format!("Unexpected autoclosing tag <{}>", reader.decode(event.name()).map_err(map_xml_error)?))) |
||||
}, |
||||
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(QueryResults::Boolean(true)) |
||||
} else if value.as_ref() == b"false" { |
||||
Ok(QueryResults::Boolean(false)) |
||||
} else { |
||||
Err(invalid_data_error(format!("Unexpected boolean value. Found {}", reader.decode(&value).map_err(map_xml_error)?))) |
||||
}; |
||||
} |
||||
_ => Err(invalid_data_error(format!("Unexpected textual value found: {}", reader.decode(&value).map_err(map_xml_error)?))) |
||||
}; |
||||
}, |
||||
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 <head> and a <result> or <boolean> tag")); |
||||
}, |
||||
Event::Eof => return Err(invalid_data_error("Unexpected early file end. All results file should have a <head> and a <result> or <boolean> tag")), |
||||
_ => (), |
||||
} |
||||
} |
||||
} |
||||
|
||||
enum State { |
||||
Start, |
||||
Result, |
||||
Binding, |
||||
Uri, |
||||
BNode, |
||||
Literal, |
||||
Triple, |
||||
Subject, |
||||
Predicate, |
||||
Object, |
||||
End, |
||||
} |
||||
|
||||
struct ResultsIterator<R: BufRead> { |
||||
reader: Reader<R>, |
||||
buffer: Vec<u8>, |
||||
namespace_buffer: Vec<u8>, |
||||
mapping: BTreeMap<Vec<u8>, usize>, |
||||
stack: Vec<State>, |
||||
subject_stack: Vec<Term>, |
||||
predicate_stack: Vec<Term>, |
||||
object_stack: Vec<Term>, |
||||
} |
||||
|
||||
impl<R: BufRead> Iterator for ResultsIterator<R> { |
||||
type Item = Result<Vec<Option<Term>>, EvaluationError>; |
||||
|
||||
fn next(&mut self) -> Option<Result<Vec<Option<Term>>, EvaluationError>> { |
||||
self.read_next().transpose() |
||||
} |
||||
} |
||||
|
||||
impl<R: BufRead> ResultsIterator<R> { |
||||
fn read_next(&mut self) -> Result<Option<Vec<Option<Term>>>, EvaluationError> { |
||||
let mut state = State::Start; |
||||
|
||||
let mut new_bindings = vec![None; self.mapping.len()]; |
||||
|
||||
let mut current_var = None; |
||||
let mut term: Option<Term> = 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 <result>, found {}", |
||||
self.reader.decode(event.name()).map_err(map_xml_error)? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
State::Result => { |
||||
if event.name() == b"binding" { |
||||
match event |
||||
.attributes() |
||||
.filter_map(std::result::Result::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 <binding> tag", |
||||
) |
||||
.into()); |
||||
} |
||||
} |
||||
state = State::Binding; |
||||
} else { |
||||
return Err(invalid_data_error(format!( |
||||
"Expecting <binding>, found {}", |
||||
self.reader.decode(event.name()).map_err(map_xml_error)? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
State::Binding | State::Subject | State::Predicate | State::Object => { |
||||
if term.is_some() { |
||||
return Err(invalid_data_error( |
||||
"There is already a value for the current binding", |
||||
) |
||||
.into()); |
||||
} |
||||
self.stack.push(state); |
||||
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().flatten() { |
||||
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 if event.name() == b"triple" { |
||||
state = State::Triple; |
||||
} else { |
||||
return Err(invalid_data_error(format!( |
||||
"Expecting <uri>, <bnode> or <literal> found {}", |
||||
self.reader.decode(event.name()).map_err(map_xml_error)? |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
State::Triple => { |
||||
if event.name() == b"subject" { |
||||
state = State::Subject |
||||
} else if event.name() == b"predicate" { |
||||
state = State::Predicate |
||||
} else if event.name() == b"object" { |
||||
state = State::Object |
||||
} else { |
||||
return Err(invalid_data_error(format!( |
||||
"Expecting <subject>, <predicate> or <object> 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 { |
||||
if let Some(var) = self.mapping.get(var) { |
||||
new_bindings[*var] = term.take() |
||||
} else { |
||||
return Err( |
||||
invalid_data_error(format!("The variable '{}' is used in a binding but not declared in the variables list", self.reader.decode(var).map_err(map_xml_error)?)).into() |
||||
); |
||||
} |
||||
} else { |
||||
return Err( |
||||
invalid_data_error("No name found for <binding> tag").into() |
||||
); |
||||
} |
||||
state = State::Result; |
||||
} |
||||
State::Subject => { |
||||
if let Some(subject) = term.take() { |
||||
self.subject_stack.push(subject) |
||||
} |
||||
state = State::Triple; |
||||
} |
||||
State::Predicate => { |
||||
if let Some(predicate) = term.take() { |
||||
self.predicate_stack.push(predicate) |
||||
} |
||||
state = State::Triple; |
||||
} |
||||
State::Object => { |
||||
if let Some(object) = term.take() { |
||||
self.object_stack.push(object) |
||||
} |
||||
state = State::Triple; |
||||
} |
||||
State::Uri => state = self.stack.pop().unwrap(), |
||||
State::BNode => { |
||||
if term.is_none() { |
||||
//We default to a random bnode
|
||||
term = Some(BlankNode::default().into()) |
||||
} |
||||
state = self.stack.pop().unwrap() |
||||
} |
||||
State::Literal => { |
||||
if term.is_none() { |
||||
//We default to the empty literal
|
||||
term = Some(build_literal("", lang.take(), datatype.take())?.into()) |
||||
} |
||||
state = self.stack.pop().unwrap(); |
||||
} |
||||
State::Triple => { |
||||
if let (Some(subject), Some(predicate), Some(object)) = ( |
||||
self.subject_stack.pop(), |
||||
self.predicate_stack.pop(), |
||||
self.object_stack.pop(), |
||||
) { |
||||
term = Some( |
||||
Triple::new( |
||||
match subject { |
||||
Term::NamedNode(subject) => subject.into(), |
||||
Term::BlankNode(subject) => subject.into(), |
||||
Term::Triple(subject) => Subject::Triple(subject), |
||||
Term::Literal(_) => { |
||||
return Err(invalid_data_error( |
||||
"The <subject> value should not be a <literal>", |
||||
) |
||||
.into()) |
||||
} |
||||
}, |
||||
match predicate { |
||||
Term::NamedNode(predicate) => predicate, |
||||
_ => { |
||||
return Err(invalid_data_error( |
||||
"The <predicate> value should be an <uri>", |
||||
) |
||||
.into()) |
||||
} |
||||
}, |
||||
object, |
||||
) |
||||
.into(), |
||||
); |
||||
state = self.stack.pop().unwrap(); |
||||
} else { |
||||
return Err( |
||||
invalid_data_error("A <triple> should contain a <subject>, a <predicate> and an <object>").into() |
||||
); |
||||
} |
||||
} |
||||
State::End => (), |
||||
}, |
||||
Event::Eof => return Ok(None), |
||||
_ => (), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn build_literal( |
||||
value: impl Into<String>, |
||||
lang: Option<String>, |
||||
datatype: Option<NamedNode>, |
||||
) -> Result<Literal, EvaluationError> { |
||||
match lang { |
||||
Some(lang) => { |
||||
if let Some(datatype) = datatype { |
||||
if datatype.as_ref() != rdf::LANG_STRING { |
||||
return Err(invalid_data_error(format!( |
||||
"xml:lang value '{}' provided with the datatype {}", |
||||
lang, datatype |
||||
)) |
||||
.into()); |
||||
} |
||||
} |
||||
Literal::new_language_tagged_literal(value, &lang).map_err(|e| { |
||||
invalid_data_error(format!("Invalid xml:lang value '{}': {}", lang, e)).into() |
||||
}) |
||||
} |
||||
None => Ok(if let Some(datatype) = datatype { |
||||
Literal::new_typed_literal(value, datatype) |
||||
} else { |
||||
Literal::new_simple_literal(value) |
||||
}), |
||||
} |
||||
} |
||||
|
||||
fn map_xml_error(error: quick_xml::Error) -> io::Error { |
||||
match error { |
||||
quick_xml::Error::Io(error) => error, |
||||
quick_xml::Error::UnexpectedEof(_) => io::Error::new(io::ErrorKind::UnexpectedEof, error), |
||||
_ => invalid_data_error(error), |
||||
} |
||||
} |
Loading…
Reference in new issue