diff --git a/src/lib.rs b/src/lib.rs index 1777b8b6..586d4b87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,3 +6,4 @@ extern crate uuid; pub mod model; pub mod rio; pub mod store; +mod utils; diff --git a/src/model/data.rs b/src/model/data.rs index 854d1ca2..8eb7434f 100644 --- a/src/model/data.rs +++ b/src/model/data.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use std::sync::Arc; use url::ParseError; use url::Url; +use utils::Escaper; use uuid::Uuid; /// A RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) @@ -163,10 +164,10 @@ impl fmt::Display for Literal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_plain() { self.language() - .map(|lang| write!(f, "\"{}\"@{}", self.value(), lang)) - .unwrap_or_else(|| write!(f, "\"{}\"", self.value())) + .map(|lang| write!(f, "\"{}\"@{}", self.value().escape(), lang)) + .unwrap_or_else(|| write!(f, "\"{}\"", self.value().escape())) } else { - write!(f, "\"{}\"^^{}", self.value(), self.datatype()) + write!(f, "\"{}\"^^{}", self.value().escape(), self.datatype()) } } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..eb0960b5 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,73 @@ +pub trait Escaper { + fn escape(&self) -> String; +} + +impl<'a> Escaper for &'a str { + fn escape(&self) -> String { + self.chars().flat_map(|c| EscapeRDF::new(c)).collect() + } +} + +/// Customized version of EscapeDefault of the Rust standard library +struct EscapeRDF { + state: EscapeRdfState, +} + +enum EscapeRdfState { + Done, + Char(char), + Backslash(char), +} + +impl EscapeRDF { + fn new(c: char) -> EscapeRDF { + EscapeRDF { + state: match c { + '\t' => EscapeRdfState::Backslash('t'), + '\u{08}' => EscapeRdfState::Backslash('b'), + '\n' => EscapeRdfState::Backslash('n'), + '\r' => EscapeRdfState::Backslash('r'), + '\u{0C}' => EscapeRdfState::Backslash('f'), + '\\' | '\'' | '"' => EscapeRdfState::Backslash(c), + c => EscapeRdfState::Char(c), + }, + } + } +} + +impl Iterator for EscapeRDF { + type Item = char; + + fn next(&mut self) -> Option { + match self.state { + EscapeRdfState::Backslash(c) => { + self.state = EscapeRdfState::Char(c); + Some('\\') + } + EscapeRdfState::Char(c) => { + self.state = EscapeRdfState::Done; + Some(c) + } + EscapeRdfState::Done => None, + } + } + + fn size_hint(&self) -> (usize, Option) { + let n = self.len(); + (n, Some(n)) + } + + fn count(self) -> usize { + self.len() + } +} + +impl ExactSizeIterator for EscapeRDF { + fn len(&self) -> usize { + match self.state { + EscapeRdfState::Done => 0, + EscapeRdfState::Char(_) => 1, + EscapeRdfState::Backslash(_) => 2, + } + } +}