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 SubjectRef::QuotedTriple(qt) => self.write_quoted_triple(qt.inner(), writer)?,
145 }
146 Ok(())
147 }
148
149 fn write_quoted_triple<W: Write>(
150 &self,
151 inner: &crate::model::Triple,
152 writer: &mut W,
153 ) -> Result<(), FormatError> {
154 write!(writer, "<< ").map_err(FormatError::from)?;
155 self.write_subject(inner.subject().into(), writer)?;
156 write!(writer, " ").map_err(FormatError::from)?;
157 self.write_predicate(inner.predicate().into(), writer)?;
158 write!(writer, " ").map_err(FormatError::from)?;
159 self.write_object(inner.object().into(), writer)?;
160 write!(writer, " >>").map_err(FormatError::from)?;
161 Ok(())
162 }
163
164 fn write_predicate<W: Write>(
165 &self,
166 predicate: PredicateRef<'_>,
167 writer: &mut W,
168 ) -> Result<(), FormatError> {
169 match predicate {
170 PredicateRef::NamedNode(node) => {
171 if node.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
173 write!(writer, "a").map_err(FormatError::from)?;
174 } else if node.as_str() == "http://www.w3.org/2002/07/owl#sameAs" {
175 write!(writer, "=").map_err(FormatError::from)?;
177 } else {
178 self.write_named_node(node, writer)?;
179 }
180 }
181 PredicateRef::Variable(var) => {
182 write!(writer, "?{}", var.name()).map_err(FormatError::from)?;
183 }
184 }
185 Ok(())
186 }
187
188 fn write_object<W: Write>(
189 &self,
190 object: ObjectRef<'_>,
191 writer: &mut W,
192 ) -> Result<(), FormatError> {
193 match object {
194 ObjectRef::NamedNode(node) => self.write_named_node(node, writer)?,
195 ObjectRef::BlankNode(node) => {
196 let id = node.as_str();
197 let id = id.strip_prefix("_:").unwrap_or(id);
198 write!(writer, "_:{}", id).map_err(FormatError::from)?;
199 }
200 ObjectRef::Literal(literal) => self.write_literal(literal, writer)?,
201 ObjectRef::Variable(var) => {
202 write!(writer, "?{}", var.name()).map_err(FormatError::from)?;
203 }
204 ObjectRef::QuotedTriple(qt) => self.write_quoted_triple(qt.inner(), writer)?,
205 }
206 Ok(())
207 }
208
209 fn write_named_node<W: Write>(
210 &self,
211 node: &NamedNode,
212 writer: &mut W,
213 ) -> Result<(), FormatError> {
214 let iri = node.as_str();
215
216 for (prefix, namespace) in &self.prefixes {
218 if let Some(local) = iri.strip_prefix(namespace) {
219 write!(writer, "{}:{}", prefix, local).map_err(FormatError::from)?;
220 return Ok(());
221 }
222 }
223
224 write!(writer, "<{}>", iri).map_err(FormatError::from)?;
226 Ok(())
227 }
228
229 fn write_literal<W: Write>(
230 &self,
231 literal: &Literal,
232 writer: &mut W,
233 ) -> Result<(), FormatError> {
234 let value = literal.value();
235
236 if let Some(_datatype) = self.check_numeric_shortcut(literal) {
238 write!(writer, "{}", value).map_err(FormatError::from)?;
239 return Ok(());
240 }
241
242 let escaped = self.escape_string(value);
244 write!(writer, "\"{}\"", escaped).map_err(FormatError::from)?;
245
246 if let Some(lang) = literal.language() {
247 write!(writer, "@{}", lang).map_err(FormatError::from)?;
248 } else {
249 let datatype = literal.datatype();
250 if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
251 write!(writer, "^^").map_err(FormatError::from)?;
252 self.write_named_node(&datatype.into_owned(), writer)?;
253 }
254 }
255
256 Ok(())
257 }
258
259 fn check_numeric_shortcut(&self, literal: &Literal) -> Option<String> {
260 let datatype = literal.datatype();
261 let value = literal.value();
262
263 match datatype.as_str() {
264 "http://www.w3.org/2001/XMLSchema#integer" if value.parse::<i64>().is_ok() => {
265 Some("integer".to_string())
266 }
267 "http://www.w3.org/2001/XMLSchema#decimal" if value.parse::<f64>().is_ok() => {
268 Some("decimal".to_string())
269 }
270 "http://www.w3.org/2001/XMLSchema#double" if value.parse::<f64>().is_ok() => {
271 Some("double".to_string())
272 }
273 "http://www.w3.org/2001/XMLSchema#boolean" if value == "true" || value == "false" => {
274 Some("boolean".to_string())
275 }
276 _ => None,
277 }
278 }
279
280 fn escape_string(&self, s: &str) -> String {
281 let mut result = String::with_capacity(s.len());
282 for ch in s.chars() {
283 match ch {
284 '\\' => result.push_str("\\\\"),
285 '\"' => result.push_str("\\\""),
286 '\n' => result.push_str("\\n"),
287 '\r' => result.push_str("\\r"),
288 '\t' => result.push_str("\\t"),
289 c if c.is_control() => {
290 result.push_str(&format!("\\u{:04X}", c as u32));
291 }
292 c => result.push(c),
293 }
294 }
295 result
296 }
297}
298
299impl Default for N3Serializer {
300 fn default() -> Self {
301 Self::new()
302 }
303}
304
305pub struct N3Writer<W: Write> {
307 writer: W,
308 serializer: N3Serializer,
309 buffer: Vec<Quad>,
310}
311
312impl<W: Write> N3Writer<W> {
313 pub fn serialize_quad(&mut self, quad: QuadRef<'_>) -> Result<(), FormatError> {
315 self.buffer.push(quad.into());
316 Ok(())
317 }
318
319 pub fn finish(mut self) -> Result<W, FormatError> {
321 self.serializer
322 .serialize_quads(&self.buffer, &mut self.writer)?;
323 Ok(self.writer)
324 }
325}
326
327impl<W: Write> super::serializer::QuadSerializer<W> for N3Writer<W> {
329 fn serialize_quad(&mut self, quad: QuadRef<'_>) -> super::serializer::QuadSerializeResult {
330 N3Writer::serialize_quad(self, quad)
331 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
332 }
333
334 fn finish(self: Box<Self>) -> super::error::SerializeResult<W> {
335 N3Writer::finish(*self).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use crate::model::{NamedNode, Object, Subject, Triple};
343
344 #[test]
345 fn test_n3_serialize_triple() {
346 let serializer = N3Serializer::new();
347 let mut writer = Vec::new();
348
349 let triple = Triple::new(
350 Subject::NamedNode(NamedNode::new("http://example.org/subject").expect("valid IRI")),
351 NamedNode::new("http://example.org/predicate").expect("valid IRI"),
352 Object::NamedNode(NamedNode::new("http://example.org/object").expect("valid IRI")),
353 );
354
355 let quads = vec![Quad::from(triple)];
356 serializer
357 .serialize_quads(&quads, &mut writer)
358 .expect("operation should succeed");
359
360 let output = String::from_utf8(writer).expect("bytes should be valid UTF-8");
361 assert!(output.contains("@prefix"));
362 assert!(output.contains("<http://example.org/subject>"));
363 assert!(output.contains("<http://example.org/predicate>"));
364 assert!(output.contains("<http://example.org/object>"));
365 }
366
367 #[test]
368 fn test_n3_rdf_type_abbreviation() {
369 let serializer = N3Serializer::new();
370 let mut writer = Vec::new();
371
372 let triple = Triple::new(
373 Subject::NamedNode(NamedNode::new("http://example.org/subject").expect("valid IRI")),
374 NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type").expect("valid IRI"),
375 Object::NamedNode(NamedNode::new("http://example.org/Type").expect("valid IRI")),
376 );
377
378 let quads = vec![Quad::from(triple)];
379 serializer
380 .serialize_quads(&quads, &mut writer)
381 .expect("operation should succeed");
382
383 let output = String::from_utf8(writer).expect("bytes should be valid UTF-8");
384 assert!(output.contains(" a "));
385 }
386}