1use crate::sparql_ast::*;
6use std::io::Write;
7
8pub struct ResultFormatter;
10
11impl ResultFormatter {
12 fn format_value_xml<W: Write>(
13 writer: &mut W,
14 value: u64,
15 lexicon: Option<&crate::q42_lex::Q42LexMmap>,
16 ) -> std::io::Result<()> {
17 if (value & crate::resolver::MSB_FLAG) == 0
20 && (value & crate::resolver::INLINE_TAG_MASK) == crate::resolver::TAG_EMBEDDED
21 {
22 if let Some(lex) = lexicon {
23 if let Some([s, p, o]) = lex.lookup_embedded_triple(value) {
24 writeln!(writer, r#" <triple>"#)?;
25 writeln!(writer, r#" <subject>"#)?;
26 Self::format_value_xml(writer, s, lexicon)?;
27 writeln!(writer, r#" </subject>"#)?;
28 writeln!(writer, r#" <predicate>"#)?;
29 Self::format_value_xml(writer, p, lexicon)?;
30 writeln!(writer, r#" </predicate>"#)?;
31 writeln!(writer, r#" <object>"#)?;
32 Self::format_value_xml(writer, o, lexicon)?;
33 writeln!(writer, r#" </object>"#)?;
34 return writeln!(writer, r#" </triple>"#);
35 }
36 }
37 }
38
39 if let Some(lit) = crate::resolver::classify_inline_literal(value) {
42 return writeln!(
43 writer,
44 r#" <literal datatype="{}">{}</literal>"#,
45 lit.datatype_iri(),
46 lit
47 );
48 }
49
50 let uri = if let Some(bytes) = crate::resolver::resolve_hash(value) {
52 String::from_utf8_lossy(bytes).into_owned()
53 } else if (value & crate::resolver::MSB_FLAG) != 0 {
54 format!("did:q42:ptr/{:016x}", value & !crate::resolver::MSB_FLAG)
55 } else {
56 format!("urn:hash:{:016x}", value)
57 };
58 writeln!(writer, r#" <uri>{}</uri>"#, uri)
59 }
60
61 fn format_value_json<W: Write>(
62 writer: &mut W,
63 value: u64,
64 lexicon: Option<&crate::q42_lex::Q42LexMmap>,
65 ) -> std::io::Result<()> {
66 if (value & crate::resolver::MSB_FLAG) == 0
70 && (value & crate::resolver::INLINE_TAG_MASK) == crate::resolver::TAG_EMBEDDED
71 {
72 if let Some(lex) = lexicon {
73 if let Some([s, p, o]) = lex.lookup_embedded_triple(value) {
74 writeln!(writer, r#" {{"#)?;
75 writeln!(writer, r#" "type": "triple","#)?;
76 writeln!(writer, r#" "value": {{"#)?;
77 write!(writer, r#" "subject": "#)?;
78 Self::format_value_json(writer, s, lexicon)?;
79 writeln!(writer, r#","#)?;
80 write!(writer, r#" "predicate": "#)?;
81 Self::format_value_json(writer, p, lexicon)?;
82 writeln!(writer, r#","#)?;
83 write!(writer, r#" "object": "#)?;
84 Self::format_value_json(writer, o, lexicon)?;
85 writeln!(writer, r#""#)?;
86 writeln!(writer, r#" }}"#)?;
87 return write!(writer, r#" }}"#);
88 }
89 }
90 }
91
92 if let Some(lit) = crate::resolver::classify_inline_literal(value) {
96 writeln!(writer, r#" {{"#)?;
97 writeln!(writer, r#" "type": "literal","#)?;
98 writeln!(writer, r#" "value": "{}","#, lit)?;
99 writeln!(writer, r#" "datatype": "{}""#, lit.datatype_iri())?;
100 return write!(writer, r#" }}"#);
101 }
102
103 let uri = if let Some(bytes) = crate::resolver::resolve_hash(value) {
107 String::from_utf8_lossy(bytes).into_owned()
108 } else if (value & crate::resolver::MSB_FLAG) != 0 {
109 format!("did:q42:ptr/{:016x}", value & !crate::resolver::MSB_FLAG)
110 } else {
111 format!("urn:hash:{:016x}", value)
112 };
113 writeln!(writer, r#" {{"#)?;
114 writeln!(writer, r#" "type": "uri","#)?;
115 writeln!(writer, r#" "value": "{}""#, uri)?;
116 write!(writer, r#" }}"#)
117 }
118
119 fn format_value_tsv<W: Write>(
120 writer: &mut W,
121 value: u64,
122 lexicon: Option<&crate::q42_lex::Q42LexMmap>,
123 ) -> std::io::Result<()> {
124 if (value & crate::resolver::MSB_FLAG) == 0
128 && (value & crate::resolver::INLINE_TAG_MASK) == crate::resolver::TAG_EMBEDDED
129 {
130 if let Some(lex) = lexicon {
131 if let Some([s, p, o]) = lex.lookup_embedded_triple(value) {
132 write!(writer, "<<")?;
133 Self::format_value_tsv(writer, s, lexicon)?;
134 write!(writer, " ")?;
135 Self::format_value_tsv(writer, p, lexicon)?;
136 write!(writer, " ")?;
137 Self::format_value_tsv(writer, o, lexicon)?;
138 return write!(writer, ">>");
139 }
140 }
141 }
142
143 if let Some(lit) = crate::resolver::classify_inline_literal(value) {
146 return write!(writer, "\"{}\"^^<{}>", lit, lit.datatype_iri());
147 }
148
149 let uri = if let Some(bytes) = crate::resolver::resolve_hash(value) {
151 String::from_utf8_lossy(bytes).into_owned()
152 } else if (value & crate::resolver::MSB_FLAG) != 0 {
153 format!("did:q42:ptr/{:016x}", value & !crate::resolver::MSB_FLAG)
154 } else {
155 format!("urn:hash:{:016x}", value)
156 };
157 write!(writer, "<{}>", uri)
158 }
159
160 pub fn format_xml<W: Write>(
162 writer: &mut W,
163 variables: &[VariableId],
164 results: &[BindingRow],
165 ctx: &SparqlQueryContext,
166 lexicon: Option<&crate::q42_lex::Q42LexMmap>,
167 ) -> std::io::Result<()> {
168 writeln!(writer, r#"<?xml version="1.0"?>"#)?;
169 writeln!(
170 writer,
171 r#"<sparql xmlns="http://www.w3.org/2005/sparql-results#">"#
172 )?;
173 writeln!(writer, r#" <head>"#)?;
174 writeln!(writer, r#" <variables>"#)?;
175
176 for var in variables {
177 let var_name = ctx.variable_hashes[*var as usize];
178 writeln!(writer, r#" <variable name="{}"/>"#, var_name)?;
179 }
180
181 writeln!(writer, r#" </variables>"#)?;
182 writeln!(writer, r#" </head>"#)?;
183 writeln!(writer, r#" <results>"#)?;
184
185 for row in results {
186 writeln!(writer, r#" <result>"#)?;
187 for var in variables {
188 let var_id = *var;
189 if let Some(value) = row.get(var_id) {
190 let var_name = ctx.variable_hashes[var_id as usize];
191 writeln!(writer, r#" <binding name="{}">"#, var_name)?;
192 Self::format_value_xml(writer, value, lexicon)?;
193 writeln!(writer, r#" </binding>"#)?;
194 }
195 }
196 writeln!(writer, r#" </result>"#)?;
197 }
198
199 writeln!(writer, r#" </results>"#)?;
200 writeln!(writer, r#"</sparql>"#)?;
201
202 Ok(())
203 }
204
205 pub fn format_json<W: Write>(
207 writer: &mut W,
208 variables: &[VariableId],
209 results: &[BindingRow],
210 ctx: &SparqlQueryContext,
211 lexicon: Option<&crate::q42_lex::Q42LexMmap>,
212 ) -> std::io::Result<()> {
213 writeln!(writer, r#"{{"#)?;
214 writeln!(writer, r#" "head": {{"vars": ["#)?;
215
216 for (i, var) in variables.iter().enumerate() {
217 let var_name = ctx.variable_hashes[*var as usize];
218 if i > 0 {
219 write!(writer, r#", "#)?;
220 }
221 write!(writer, r#""{}""#, var_name)?;
222 }
223
224 writeln!(writer, r#"]}},"#)?;
225 writeln!(writer, r#" "results": {{"#)?;
226 writeln!(writer, r#" "bindings": ["#)?;
227
228 for (i, row) in results.iter().enumerate() {
229 if i > 0 {
230 writeln!(writer, r#","#)?;
231 }
232 writeln!(writer, r#" {{"#)?;
233
234 let mut first = true;
235 for var in variables {
236 let var_id = *var;
237 if let Some(value) = row.get(var_id) {
238 if !first {
239 writeln!(writer, r#","#)?;
240 }
241 first = false;
242 let var_name = ctx.variable_hashes[var_id as usize];
243 writeln!(writer, r#" "{}": "#, var_name)?;
244 Self::format_value_json(writer, value, lexicon)?;
245 }
246 }
247
248 if !first {
249 writeln!(writer)?
250 };
251 write!(writer, r#" }}"#)?;
252 }
253 writeln!(writer)?;
254
255 writeln!(writer, r#" ]"#)?;
256 writeln!(writer, r#" }}"#)?;
257 writeln!(writer, r#"}}"#)?;
258
259 Ok(())
260 }
261
262 pub fn format_tsv<W: Write>(
264 writer: &mut W,
265 variables: &[VariableId],
266 results: &[BindingRow],
267 ctx: &SparqlQueryContext,
268 lexicon: Option<&crate::q42_lex::Q42LexMmap>,
269 ) -> std::io::Result<()> {
270 for (i, var) in variables.iter().enumerate() {
271 if i > 0 {
272 write!(writer, "\t")?;
273 }
274 write!(writer, "?{}", ctx.variable_hashes[*var as usize])?;
275 }
276 writeln!(writer)?;
277
278 for row in results {
279 for (i, var) in variables.iter().enumerate() {
280 if i > 0 {
281 write!(writer, "\t")?;
282 }
283 if let Some(value) = row.get(*var) {
284 Self::format_value_tsv(writer, value, lexicon)?;
285 }
286 }
287 writeln!(writer)?;
288 }
289 Ok(())
290 }
291
292 pub fn format_csv<W: Write>(
294 writer: &mut W,
295 variables: &[VariableId],
296 results: &[BindingRow],
297 ctx: &SparqlQueryContext,
298 lexicon: Option<&crate::q42_lex::Q42LexMmap>,
299 ) -> std::io::Result<()> {
300 for (i, var) in variables.iter().enumerate() {
301 if i > 0 {
302 write!(writer, ",")?;
303 }
304 write!(writer, "{}", ctx.variable_hashes[*var as usize])?;
305 }
306 writeln!(writer)?;
307
308 for row in results {
309 for (i, var) in variables.iter().enumerate() {
310 if i > 0 {
311 write!(writer, ",")?;
312 }
313 if let Some(value) = row.get(*var) {
314 let mut temp = Vec::new();
315 Self::format_value_tsv(&mut temp, value, lexicon)?;
316 let s = String::from_utf8_lossy(&temp);
317 if s.contains(',') || s.contains('"') || s.contains('\n') {
318 write!(writer, "\"{}\"", s.replace('"', "\"\""))?;
319 } else {
320 write!(writer, "{}", s)?;
321 }
322 }
323 }
324 writeln!(writer)?;
325 }
326 Ok(())
327 }
328
329 pub fn format_ntriples<W: Write>(
330 writer: &mut W,
331 results: &[BindingRow],
332 ) -> std::io::Result<()> {
333 for row in results {
334 let s = row.get(0).unwrap_or(0);
335 let p = row.get(1).unwrap_or(0);
336 let o = row.get(2).unwrap_or(0);
337 let quin = crate::NQuin {
338 subject: s,
339 predicate: p,
340 object: o,
341 context: 0,
342 metadata: 0,
343 parity: 0,
344 };
345 crate::resolver::format_ntriples_to(&[quin], writer)?;
346 }
347 Ok(())
348 }
349
350 pub fn format_ask_xml<W: Write>(writer: &mut W, result: bool) -> std::io::Result<()> {
351 writeln!(writer, r#"<?xml version="1.0"?>"#)?;
352 writeln!(
353 writer,
354 r#"<sparql xmlns="http://www.w3.org/2005/sparql-results#">"#
355 )?;
356 writeln!(writer, r#" <head></head>"#)?;
357 writeln!(writer, r#" <boolean>{}</boolean>"#, result)?;
358 writeln!(writer, r#"</sparql>"#)?;
359 Ok(())
360 }
361
362 #[cfg(test)]
363 fn value_json_string(value: u64) -> String {
364 let mut buf = Vec::new();
365 Self::format_value_json(&mut buf, value, None).unwrap();
366 String::from_utf8(buf).unwrap()
367 }
368
369 #[cfg(test)]
370 fn value_xml_string(value: u64) -> String {
371 let mut buf = Vec::new();
372 Self::format_value_xml(&mut buf, value, None).unwrap();
373 String::from_utf8(buf).unwrap()
374 }
375
376 #[cfg(test)]
377 fn value_tsv_string(value: u64) -> String {
378 let mut buf = Vec::new();
379 Self::format_value_tsv(&mut buf, value, None).unwrap();
380 String::from_utf8(buf).unwrap()
381 }
382
383 pub fn format_ask_json<W: Write>(writer: &mut W, result: bool) -> std::io::Result<()> {
384 writeln!(writer, r#"{{"#)?;
385 writeln!(writer, r#" "head": {{}},"#)?;
386 writeln!(writer, r#" "boolean": {}"#, result)?;
387 writeln!(writer, r#"}}"#)?;
388 Ok(())
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::ResultFormatter;
395 use crate::resolver::{
396 INLINE_TAG_BOOLEAN, INLINE_TAG_DECIMAL, INLINE_TAG_FLOAT, INLINE_TAG_INTEGER,
397 };
398
399 #[test]
403 fn inline_integer_serialises_as_typed_literal_json() {
404 let json = ResultFormatter::value_json_string(INLINE_TAG_INTEGER | 42);
405 assert!(json.contains(r#""type": "literal""#), "got: {json}");
406 assert!(json.contains(r#""value": "42""#), "got: {json}");
407 assert!(
408 json.contains("XMLSchema#integer"),
409 "expected xsd:integer datatype, got: {json}"
410 );
411 assert!(
412 !json.contains(r#""type": "uri""#),
413 "must not be a uri: {json}"
414 );
415 }
416
417 #[test]
418 fn inline_boolean_serialises_as_typed_literal_json() {
419 let json = ResultFormatter::value_json_string(INLINE_TAG_BOOLEAN | 1);
420 assert!(json.contains(r#""type": "literal""#), "got: {json}");
421 assert!(json.contains(r#""value": "true""#), "got: {json}");
422 assert!(json.contains("XMLSchema#boolean"), "got: {json}");
423 }
424
425 #[test]
426 fn inline_decimal_serialises_as_typed_literal_json() {
427 let json = ResultFormatter::value_json_string(INLINE_TAG_DECIMAL | 3_500_000);
429 assert!(json.contains("XMLSchema#decimal"), "got: {json}");
430 assert!(json.contains(r#""value": "3.500000""#), "got: {json}");
431 }
432
433 #[test]
434 fn inline_float_serialises_as_typed_literal_json() {
435 let val = INLINE_TAG_FLOAT | (0.5f32.to_bits() as u64);
436 let json = ResultFormatter::value_json_string(val);
437 assert!(json.contains("XMLSchema#float"), "got: {json}");
438 assert!(json.contains(r#""value": "0.5""#), "got: {json}");
439 }
440
441 #[test]
442 fn inline_integer_serialises_as_typed_literal_xml() {
443 let xml = ResultFormatter::value_xml_string(INLINE_TAG_INTEGER | 7);
444 assert!(xml.contains("<literal"), "expected <literal>, got: {xml}");
445 assert!(xml.contains("XMLSchema#integer"), "got: {xml}");
446 assert!(xml.contains(">7</literal>"), "got: {xml}");
447 assert!(!xml.contains("<uri>"), "must not be a <uri>: {xml}");
448 }
449
450 #[test]
451 fn unresolved_hash_still_serialises_as_uri_json() {
452 let json = ResultFormatter::value_json_string(0x0123_4567_89ab_cdef);
454 assert!(json.contains(r#""type": "uri""#), "got: {json}");
455 }
456
457 #[test]
461 fn inline_integer_serialises_as_typed_literal_tsv() {
462 let tsv = ResultFormatter::value_tsv_string(INLINE_TAG_INTEGER | 42);
463 assert!(tsv.contains(r#""42"^^<"#), "got: {tsv}");
464 assert!(tsv.contains("XMLSchema#integer"), "got: {tsv}");
465 assert!(!tsv.contains("<<"), "must not be an embedded triple: {tsv}");
466 }
467}