sophia_turtle/serializer/
trig.rs1use rio_turtle::TriGFormatter;
13use sophia_api::quad::Quad;
14use sophia_api::serializer::{QuadSerializer, Stringifier};
15use sophia_api::source::{QuadSource, SinkError, SourceError, StreamResult};
16use sophia_api::term::Term;
17use sophia_rio::serializer::rio_format_quads;
18use std::io;
19
20pub(super) use super::_pretty::*;
21
22pub type TrigConfig = super::turtle::TurtleConfig;
24
25pub struct TrigSerializer<W> {
27 pub(super) config: TrigConfig,
28 pub(super) write: W,
29}
30
31impl<W> TrigSerializer<W>
32where
33 W: io::Write,
34{
35 #[inline]
37 pub fn new(write: W) -> TrigSerializer<W> {
38 Self::new_with_config(write, TrigConfig::default())
39 }
40
41 pub fn new_with_config(write: W, config: TrigConfig) -> TrigSerializer<W> {
43 TrigSerializer { config, write }
44 }
45
46 pub fn config(&self) -> &TrigConfig {
48 &self.config
49 }
50}
51
52impl<W> QuadSerializer for TrigSerializer<W>
53where
54 W: io::Write,
55{
56 type Error = io::Error;
57
58 fn serialize_quads<TS>(
59 &mut self,
60 mut source: TS,
61 ) -> StreamResult<&mut Self, TS::Error, Self::Error>
62 where
63 TS: QuadSource,
64 {
65 if self.config.pretty {
66 let mut dataset = PrettifiableDataset::new();
67 source
68 .for_each_quad(|t| {
69 let (spo, g) = t.spog();
70 let spo = spo.map(|t| t.into_term());
71 let g = g.map(|t| t.into_term());
72 dataset.insert((g, spo));
73 })
74 .map_err(SourceError)?;
75 prettify(dataset, &mut self.write, &self.config, "").map_err(SinkError)?;
76 } else {
77 let mut tf = TriGFormatter::new(&mut self.write);
78 rio_format_quads(&mut tf, source)?;
79 tf.finish().map_err(SinkError)?;
80 }
81 Ok(self)
82 }
83}
84
85impl TrigSerializer<Vec<u8>> {
86 #[inline]
88 pub fn new_stringifier() -> Self {
89 TrigSerializer::new(Vec::new())
90 }
91 #[inline]
93 pub fn new_stringifier_with_config(config: TrigConfig) -> Self {
94 TrigSerializer::new_with_config(Vec::new(), config)
95 }
96}
97
98impl Stringifier for TrigSerializer<Vec<u8>> {
99 fn as_utf8(&self) -> &[u8] {
100 &self.write[..]
101 }
102}
103
104#[cfg(test)]
109pub(crate) mod test {
110 use super::*;
111 use sophia_api::term::SimpleTerm;
112 use sophia_api::{dataset::Dataset, quad::Spog};
113 use sophia_isomorphism::isomorphic_datasets;
114 use std::error::Error;
115
116 const TESTS: &[&str] = &[
117 "#empty trig",
118 r#"# simple quads
119 PREFIX : <http://example.org/ns/>
120 :alice a :Person; :name "Alice"; :age 42.
121
122 GRAPH :g {
123 :bob a :Person, :Man; :nick "bob"@fr, "bobby"@en; :admin true.
124 }
125 "#,
126 r#"# lists
127 GRAPH <tag:g> { <tag:alice> <tag:likes> ( 1 2 ( 3 4 ) 5 6 ), ("a" "b"). }
128 "#,
129 r#"# subject lists
130 GRAPH <tag:g> { (1 2 3) a <tag:List>. }
131 "#,
132 r#"# malformed list
133 PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
134 GRAPH <tag:g> {
135 _:a rdf:first 42, 43; rdf:rest (44 45).
136 _:b rdf:first 42; rdf:rest (43), (44).
137 }
138 "#,
139 r#"# bnode cycles
140 PREFIX : <http://example.org/ns/>
141 GRAPH <tag:g> {
142 _:a :n "a"; :p [ :q [ :r _:a ]].
143 _:b :n "b"; :s [ :s _:b ].
144 _:c :b "c"; :t _:c.
145 }
146 "#,
147 r#"# quoted triples
148 PREFIX : <http://example.org/ns/>
149 GRAPH <tag:g> {
150 << :s :p :o1 >> :a :b.
151 :s :p :o2 {| :c :d |}.
152 }
153 "#,
154 r#"# blank node graph name
155 PREFIX : <http://example.org/ns/>
156 :lois :belives _:b.
157 GRAPH _:b1 { :clark a :Human }
158 "#,
159 r#"# blank node sharred across graphs
160 PREFIX : <http://example.org/ns/>
161 _:a :name "alice".
162 GRAPH <tag:g> { _:a a :Person }
163 "#,
164 r#"# list split over different graphs
165 PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
166 _:a rdf:first 42; rdf:rest _:b.
167
168 GRAPH [] {
169 _:b rdf:first 43; rdf:rest ().
170 }
171 "#,
172 r#"# issue 149
173 PREFIX : <https://example.org/>
174 :s :p :o .
175 GRAPH :g { _:b :p2 :o2 }
176 "#,
177 ];
178
179 #[test]
180 fn roundtrip_not_pretty() -> Result<(), Box<dyn std::error::Error>> {
181 for ttl in TESTS {
182 println!("==========\n{}\n----------", ttl);
183 let g1: Vec<Spog<SimpleTerm>> = crate::parser::trig::parse_str(ttl).collect_quads()?;
184
185 let out = TrigSerializer::new_stringifier()
186 .serialize_quads(g1.quads())?
187 .to_string();
188 println!("{}", &out);
189
190 let g2: Vec<Spog<SimpleTerm>> = crate::parser::trig::parse_str(&out).collect_quads()?;
191
192 assert!(isomorphic_datasets(&g1, &g2)?);
193 }
194 Ok(())
195 }
196
197 #[test]
198 fn roundtrip_pretty() -> Result<(), Box<dyn Error>> {
199 for ttl in TESTS {
200 println!("==========\n{}\n----------", ttl);
201 let g1: Vec<Spog<SimpleTerm>> = crate::parser::trig::parse_str(ttl).collect_quads()?;
202
203 let config = TrigConfig::new().with_pretty(true);
204 let out = TrigSerializer::new_stringifier_with_config(config)
205 .serialize_quads(g1.quads())?
206 .to_string();
207 println!("{}", &out);
208
209 let g2: Vec<Spog<SimpleTerm>> = crate::parser::trig::parse_str(&out).collect_quads()?;
210
211 assert!(isomorphic_datasets(&g1, &g2)?);
212 }
213 Ok(())
214 }
215}