1use super::error::FormatError;
9use crate::model::{
10 GraphName, Literal, NamedNode, ObjectRef, PredicateRef, Quad, QuadRef, SubjectRef,
11};
12use std::collections::HashMap;
13use std::io::Write;
14
15#[derive(Debug, Clone)]
17pub struct N3Serializer {
18 base_iri: Option<String>,
20 prefixes: HashMap<String, String>,
22 pretty: bool,
24}
25
26impl N3Serializer {
27 pub fn new() -> Self {
29 let mut prefixes = HashMap::new();
30
31 prefixes.insert(
33 "rdf".to_string(),
34 "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(),
35 );
36 prefixes.insert(
37 "rdfs".to_string(),
38 "http://www.w3.org/2000/01/rdf-schema#".to_string(),
39 );
40 prefixes.insert(
41 "xsd".to_string(),
42 "http://www.w3.org/2001/XMLSchema#".to_string(),
43 );
44 prefixes.insert(
45 "owl".to_string(),
46 "http://www.w3.org/2002/07/owl#".to_string(),
47 );
48
49 Self {
50 base_iri: None,
51 prefixes,
52 pretty: false,
53 }
54 }
55
56 pub fn with_base_iri(mut self, base: &str) -> Self {
58 self.base_iri = Some(base.to_string());
59 self
60 }
61
62 pub fn with_prefix(mut self, prefix: &str, iri: &str) -> Self {
64 self.prefixes.insert(prefix.to_string(), iri.to_string());
65 self
66 }
67
68 pub fn pretty(mut self) -> Self {
70 self.pretty = true;
71 self
72 }
73
74 pub fn for_writer<W: Write + 'static>(self, writer: W) -> N3Writer<W> {
76 N3Writer {
77 writer,
78 serializer: self,
79 buffer: Vec::new(),
80 }
81 }
82
83 fn serialize_quads<W: Write>(&self, quads: &[Quad], writer: &mut W) -> Result<(), FormatError> {
85 for (prefix, namespace) in &self.prefixes {
87 writeln!(writer, "@prefix {}: <{}> .", prefix, namespace).map_err(FormatError::from)?;
88 }
89
90 if !self.prefixes.is_empty() {
91 writeln!(writer).map_err(FormatError::from)?;
92 }
93
94 if let Some(base) = &self.base_iri {
96 writeln!(writer, "@base <{}> .", base).map_err(FormatError::from)?;
97 writeln!(writer).map_err(FormatError::from)?;
98 }
99
100 for quad in quads {
102 if matches!(quad.graph_name(), GraphName::DefaultGraph) {
104 self.serialize_triple(quad.as_ref(), writer)?;
105 writeln!(writer, " .").map_err(FormatError::from)?;
106 }
107 }
108
109 Ok(())
110 }
111
112 fn serialize_triple<W: Write>(
113 &self,
114 quad: QuadRef<'_>,
115 writer: &mut W,
116 ) -> Result<(), FormatError> {
117 self.write_subject(quad.subject(), writer)?;
118 write!(writer, " ").map_err(FormatError::from)?;
119
120 self.write_predicate(quad.predicate(), writer)?;
121 write!(writer, " ").map_err(FormatError::from)?;
122
123 self.write_object(quad.object(), writer)?;
124
125 Ok(())
126 }
127
128 fn write_subject<W: Write>(
129 &self,
130 subject: SubjectRef<'_>,
131 writer: &mut W,
132 ) -> Result<(), FormatError> {
133 match subject {
134 SubjectRef::NamedNode(node) => self.write_named_node(node, writer)?,
135 SubjectRef::BlankNode(node) => {
136 let id = node.as_str();
137 let id = id.strip_prefix("_:").unwrap_or(id);
138 write!(writer, "_:{}", id).map_err(FormatError::from)?;
139 }
140 SubjectRef::Variable(var) => {
141 write!(writer, "?{}", var.name()).map_err(FormatError::from)?;
143 }
144 }
145 Ok(())
146 }
147
148 fn write_predicate<W: Write>(
149 &self,
150 predicate: PredicateRef<'_>,
151 writer: &mut W,
152 ) -> Result<(), FormatError> {
153 match predicate {
154 PredicateRef::NamedNode(node) => {
155 if node.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
157 write!(writer, "a").map_err(FormatError::from)?;
158 } else if node.as_str() == "http://www.w3.org/2002/07/owl#sameAs" {
159 write!(writer, "=").map_err(FormatError::from)?;
161 } else {
162 self.write_named_node(node, writer)?;
163 }
164 }
165 PredicateRef::Variable(var) => {
166 write!(writer, "?{}", var.name()).map_err(FormatError::from)?;
167 }
168 }
169 Ok(())
170 }
171
172 fn write_object<W: Write>(
173 &self,
174 object: ObjectRef<'_>,
175 writer: &mut W,
176 ) -> Result<(), FormatError> {
177 match object {
178 ObjectRef::NamedNode(node) => self.write_named_node(node, writer)?,
179 ObjectRef::BlankNode(node) => {
180 let id = node.as_str();
181 let id = id.strip_prefix("_:").unwrap_or(id);
182 write!(writer, "_:{}", id).map_err(FormatError::from)?;
183 }
184 ObjectRef::Literal(literal) => self.write_literal(literal, writer)?,
185 ObjectRef::Variable(var) => {
186 write!(writer, "?{}", var.name()).map_err(FormatError::from)?;
187 }
188 }
189 Ok(())
190 }
191
192 fn write_named_node<W: Write>(
193 &self,
194 node: &NamedNode,
195 writer: &mut W,
196 ) -> Result<(), FormatError> {
197 let iri = node.as_str();
198
199 for (prefix, namespace) in &self.prefixes {
201 if let Some(local) = iri.strip_prefix(namespace) {
202 write!(writer, "{}:{}", prefix, local).map_err(FormatError::from)?;
203 return Ok(());
204 }
205 }
206
207 write!(writer, "<{}>", iri).map_err(FormatError::from)?;
209 Ok(())
210 }
211
212 fn write_literal<W: Write>(
213 &self,
214 literal: &Literal,
215 writer: &mut W,
216 ) -> Result<(), FormatError> {
217 let value = literal.value();
218
219 if let Some(_datatype) = self.check_numeric_shortcut(literal) {
221 write!(writer, "{}", value).map_err(FormatError::from)?;
222 return Ok(());
223 }
224
225 let escaped = self.escape_string(value);
227 write!(writer, "\"{}\"", escaped).map_err(FormatError::from)?;
228
229 if let Some(lang) = literal.language() {
230 write!(writer, "@{}", lang).map_err(FormatError::from)?;
231 } else {
232 let datatype = literal.datatype();
233 if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
234 write!(writer, "^^").map_err(FormatError::from)?;
235 self.write_named_node(&datatype.into_owned(), writer)?;
236 }
237 }
238
239 Ok(())
240 }
241
242 fn check_numeric_shortcut(&self, literal: &Literal) -> Option<String> {
243 let datatype = literal.datatype();
244 let value = literal.value();
245
246 match datatype.as_str() {
247 "http://www.w3.org/2001/XMLSchema#integer" if value.parse::<i64>().is_ok() => {
248 Some("integer".to_string())
249 }
250 "http://www.w3.org/2001/XMLSchema#decimal" if value.parse::<f64>().is_ok() => {
251 Some("decimal".to_string())
252 }
253 "http://www.w3.org/2001/XMLSchema#double" if value.parse::<f64>().is_ok() => {
254 Some("double".to_string())
255 }
256 "http://www.w3.org/2001/XMLSchema#boolean" if value == "true" || value == "false" => {
257 Some("boolean".to_string())
258 }
259 _ => None,
260 }
261 }
262
263 fn escape_string(&self, s: &str) -> String {
264 let mut result = String::with_capacity(s.len());
265 for ch in s.chars() {
266 match ch {
267 '\\' => result.push_str("\\\\"),
268 '\"' => result.push_str("\\\""),
269 '\n' => result.push_str("\\n"),
270 '\r' => result.push_str("\\r"),
271 '\t' => result.push_str("\\t"),
272 c if c.is_control() => {
273 result.push_str(&format!("\\u{:04X}", c as u32));
274 }
275 c => result.push(c),
276 }
277 }
278 result
279 }
280}
281
282impl Default for N3Serializer {
283 fn default() -> Self {
284 Self::new()
285 }
286}
287
288pub struct N3Writer<W: Write> {
290 writer: W,
291 serializer: N3Serializer,
292 buffer: Vec<Quad>,
293}
294
295impl<W: Write> N3Writer<W> {
296 pub fn serialize_quad(&mut self, quad: QuadRef<'_>) -> Result<(), FormatError> {
298 self.buffer.push(quad.into());
299 Ok(())
300 }
301
302 pub fn finish(mut self) -> Result<W, FormatError> {
304 self.serializer
305 .serialize_quads(&self.buffer, &mut self.writer)?;
306 Ok(self.writer)
307 }
308}
309
310impl<W: Write> super::serializer::QuadSerializer<W> for N3Writer<W> {
312 fn serialize_quad(&mut self, quad: QuadRef<'_>) -> super::serializer::QuadSerializeResult {
313 N3Writer::serialize_quad(self, quad)
314 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
315 }
316
317 fn finish(self: Box<Self>) -> super::error::SerializeResult<W> {
318 N3Writer::finish(*self).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325 use crate::model::{NamedNode, Object, Subject, Triple};
326
327 #[test]
328 fn test_n3_serialize_triple() {
329 let serializer = N3Serializer::new();
330 let mut writer = Vec::new();
331
332 let triple = Triple::new(
333 Subject::NamedNode(NamedNode::new("http://example.org/subject").expect("valid IRI")),
334 NamedNode::new("http://example.org/predicate").expect("valid IRI"),
335 Object::NamedNode(NamedNode::new("http://example.org/object").expect("valid IRI")),
336 );
337
338 let quads = vec![Quad::from(triple)];
339 serializer
340 .serialize_quads(&quads, &mut writer)
341 .expect("operation should succeed");
342
343 let output = String::from_utf8(writer).expect("bytes should be valid UTF-8");
344 assert!(output.contains("@prefix"));
345 assert!(output.contains("<http://example.org/subject>"));
346 assert!(output.contains("<http://example.org/predicate>"));
347 assert!(output.contains("<http://example.org/object>"));
348 }
349
350 #[test]
351 fn test_n3_rdf_type_abbreviation() {
352 let serializer = N3Serializer::new();
353 let mut writer = Vec::new();
354
355 let triple = Triple::new(
356 Subject::NamedNode(NamedNode::new("http://example.org/subject").expect("valid IRI")),
357 NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type").expect("valid IRI"),
358 Object::NamedNode(NamedNode::new("http://example.org/Type").expect("valid IRI")),
359 );
360
361 let quads = vec![Quad::from(triple)];
362 serializer
363 .serialize_quads(&quads, &mut writer)
364 .expect("operation should succeed");
365
366 let output = String::from_utf8(writer).expect("bytes should be valid UTF-8");
367 assert!(output.contains(" a "));
368 }
369}