|
|
@ -1,6 +1,5 @@ |
|
|
|
//! Implementation of [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/)
|
|
|
|
//! Implementation of [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/)
|
|
|
|
|
|
|
|
|
|
|
|
use crate::error::invalid_input_error; |
|
|
|
|
|
|
|
use crate::model::{vocab::xsd, *}; |
|
|
|
use crate::model::{vocab::xsd, *}; |
|
|
|
use crate::sparql::error::EvaluationError; |
|
|
|
use crate::sparql::error::EvaluationError; |
|
|
|
use crate::sparql::model::*; |
|
|
|
use crate::sparql::model::*; |
|
|
@ -11,10 +10,9 @@ pub fn write_csv_results( |
|
|
|
mut sink: impl Write, |
|
|
|
mut sink: impl Write, |
|
|
|
) -> Result<(), EvaluationError> { |
|
|
|
) -> Result<(), EvaluationError> { |
|
|
|
match results { |
|
|
|
match results { |
|
|
|
QueryResults::Boolean(_) => Err(invalid_input_error( |
|
|
|
QueryResults::Boolean(value) => { |
|
|
|
"boolean could not be formatted to SPARQL query results CSV format", |
|
|
|
sink.write_all(if value { b"true" } else { b"false" })?; |
|
|
|
) |
|
|
|
} |
|
|
|
.into()), |
|
|
|
|
|
|
|
QueryResults::Solutions(solutions) => { |
|
|
|
QueryResults::Solutions(solutions) => { |
|
|
|
let mut start_vars = true; |
|
|
|
let mut start_vars = true; |
|
|
|
for variable in solutions.variables() { |
|
|
|
for variable in solutions.variables() { |
|
|
@ -37,31 +35,39 @@ pub fn write_csv_results( |
|
|
|
sink.write_all(b",")?; |
|
|
|
sink.write_all(b",")?; |
|
|
|
} |
|
|
|
} |
|
|
|
if let Some(value) = value { |
|
|
|
if let Some(value) = value { |
|
|
|
match value { |
|
|
|
write_csv_term(value, &mut sink)?; |
|
|
|
Term::NamedNode(uri) => { |
|
|
|
|
|
|
|
sink.write_all(uri.as_str().as_bytes())?; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Term::BlankNode(bnode) => { |
|
|
|
|
|
|
|
sink.write_all(b"_:")?; |
|
|
|
|
|
|
|
sink.write_all(bnode.as_str().as_bytes())?; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Term::Literal(literal) => { |
|
|
|
|
|
|
|
write_escaped_csv_string(literal.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(()) |
|
|
|
Ok(()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn write_csv_term<'a>(term: impl Into<TermRef<'a>>, mut sink: 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()) |
|
|
|
} |
|
|
|
} |
|
|
|
QueryResults::Graph(_) => Err(invalid_input_error( |
|
|
|
TermRef::Literal(literal) => write_escaped_csv_string(literal.value(), &mut sink), |
|
|
|
"Graphs could not be formatted to SPARQL query results CSV format", |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.into()), |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn write_escaped_csv_string(s: &str, mut sink: impl Write) -> Result<(), io::Error> { |
|
|
|
fn write_escaped_csv_string(s: &str, mut sink: impl Write) -> io::Result<()> { |
|
|
|
if s.bytes().any(|c| match c { |
|
|
|
if s.bytes().any(|c| match c { |
|
|
|
b'"' | b',' | b'\n' | b'\r' => true, |
|
|
|
b'"' | b',' | b'\n' | b'\r' => true, |
|
|
|
_ => false, |
|
|
|
_ => false, |
|
|
@ -85,10 +91,9 @@ pub fn write_tsv_results( |
|
|
|
mut sink: impl Write, |
|
|
|
mut sink: impl Write, |
|
|
|
) -> Result<(), EvaluationError> { |
|
|
|
) -> Result<(), EvaluationError> { |
|
|
|
match results { |
|
|
|
match results { |
|
|
|
QueryResults::Boolean(_) => Err(invalid_input_error( |
|
|
|
QueryResults::Boolean(value) => { |
|
|
|
"boolean could not be formatted to SPARQL query results TSV format", |
|
|
|
sink.write_all(if value { b"true" } else { b"false" })?; |
|
|
|
) |
|
|
|
} |
|
|
|
.into()), |
|
|
|
|
|
|
|
QueryResults::Solutions(solutions) => { |
|
|
|
QueryResults::Solutions(solutions) => { |
|
|
|
let mut start_vars = true; |
|
|
|
let mut start_vars = true; |
|
|
|
for variable in solutions.variables() { |
|
|
|
for variable in solutions.variables() { |
|
|
@ -112,42 +117,51 @@ pub fn write_tsv_results( |
|
|
|
sink.write_all(b"\t")?; |
|
|
|
sink.write_all(b"\t")?; |
|
|
|
} |
|
|
|
} |
|
|
|
if let Some(value) = value { |
|
|
|
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>>, mut sink: impl Write) -> io::Result<()> { |
|
|
|
//TODO: full Turtle serialization
|
|
|
|
//TODO: full Turtle serialization
|
|
|
|
sink.write_all( |
|
|
|
match term.into() { |
|
|
|
match value { |
|
|
|
TermRef::NamedNode(node) => write!(sink, "<{}>", node.as_str()), |
|
|
|
Term::NamedNode(node) => node.to_string(), |
|
|
|
TermRef::BlankNode(node) => write!(sink, "_:{}", node.as_str()), |
|
|
|
Term::BlankNode(node) => node.to_string(), |
|
|
|
TermRef::Literal(literal) => match literal.datatype() { |
|
|
|
Term::Literal(literal) => match literal.datatype() { |
|
|
|
|
|
|
|
xsd::BOOLEAN => match literal.value() { |
|
|
|
xsd::BOOLEAN => match literal.value() { |
|
|
|
"true" | "1" => "true".to_owned(), |
|
|
|
"true" | "1" => sink.write_all(b"true"), |
|
|
|
"false" | "0" => "false".to_owned(), |
|
|
|
"false" | "0" => sink.write_all(b"false"), |
|
|
|
_ => literal.to_string(), |
|
|
|
_ => sink.write_all(literal.to_string().as_bytes()), |
|
|
|
}, |
|
|
|
}, |
|
|
|
xsd::INTEGER => { |
|
|
|
xsd::INTEGER => { |
|
|
|
if literal.value().bytes().all(|c| match c { |
|
|
|
if literal.value().bytes().all(|c| match c { |
|
|
|
b'0'..=b'9' => true, |
|
|
|
b'0'..=b'9' => true, |
|
|
|
_ => false, |
|
|
|
_ => false, |
|
|
|
}) { |
|
|
|
}) { |
|
|
|
literal.value().to_owned() |
|
|
|
sink.write_all(literal.value().as_bytes()) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
literal.to_string() |
|
|
|
sink.write_all(literal.to_string().as_bytes()) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
_ => literal.to_string(), |
|
|
|
_ => sink.write_all(literal.to_string().as_bytes()), |
|
|
|
}, |
|
|
|
}, |
|
|
|
} |
|
|
|
} |
|
|
|
.as_bytes(), |
|
|
|
|
|
|
|
)?; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
QueryResults::Graph(_) => Err(invalid_input_error( |
|
|
|
|
|
|
|
"Graphs could not be formatted to SPARQL query results TSV format", |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.into()), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
#[cfg(test)] |
|
|
|