Skip to main content

oxirs_core/format/
n3.rs

1//! N3 (Notation3) format serializer and parser
2//!
3//! N3 is a superset of Turtle that adds support for variables, rules, and formulae.
4//! This implementation focuses on the Turtle-compatible subset for now.
5//!
6//! W3C Specification: <https://w3c.github.io/N3/spec/>
7
8use 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/// N3 serializer for writing RDF with Turtle-compatible syntax
16#[derive(Debug, Clone)]
17pub struct N3Serializer {
18    /// Base IRI for relative IRI resolution
19    base_iri: Option<String>,
20    /// Prefix declarations for compact serialization
21    prefixes: HashMap<String, String>,
22    /// Pretty printing with indentation
23    pretty: bool,
24}
25
26impl N3Serializer {
27    /// Create a new N3 serializer
28    pub fn new() -> Self {
29        let mut prefixes = HashMap::new();
30
31        // Add standard N3 prefixes
32        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    /// Set the base IRI
57    pub fn with_base_iri(mut self, base: &str) -> Self {
58        self.base_iri = Some(base.to_string());
59        self
60    }
61
62    /// Add a prefix mapping
63    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    /// Enable pretty printing
69    pub fn pretty(mut self) -> Self {
70        self.pretty = true;
71        self
72    }
73
74    /// Wrap this serializer for a specific writer
75    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    /// Serialize quads as N3 triples (only default graph)
84    fn serialize_quads<W: Write>(&self, quads: &[Quad], writer: &mut W) -> Result<(), FormatError> {
85        // Write prefix declarations
86        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        // Write base if present
95        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        // Serialize triples (only from default graph)
101        for quad in quads {
102            // N3 typically handles only default graph; named graphs would need special syntax
103            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                // N3 supports variables with ?variable syntax
142                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                // N3 uses 'a' for rdf:type
156                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                    // N3 uses '=' for owl:sameAs
160                    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        // Try to use a prefix
200        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        // Use full IRI
208        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        // N3 supports some numeric shortcuts
220        if let Some(_datatype) = self.check_numeric_shortcut(literal) {
221            write!(writer, "{}", value).map_err(FormatError::from)?;
222            return Ok(());
223        }
224
225        // Regular string literal
226        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
288/// Writer wrapper for N3 serialization
289pub struct N3Writer<W: Write> {
290    writer: W,
291    serializer: N3Serializer,
292    buffer: Vec<Quad>,
293}
294
295impl<W: Write> N3Writer<W> {
296    /// Serialize a single quad (buffered until finish)
297    pub fn serialize_quad(&mut self, quad: QuadRef<'_>) -> Result<(), FormatError> {
298        self.buffer.push(quad.into());
299        Ok(())
300    }
301
302    /// Finish serialization and return the writer
303    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
310/// Implement the QuadSerializer trait for integration with the format system
311impl<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}