Skip to main content

oxirs_core/format/
trig.rs

1//! TriG format serializer and parser
2//!
3//! TriG extends Turtle with support for named graphs, allowing multiple RDF graphs
4//! to be serialized in a single document with graph-level organization.
5//!
6//! W3C Specification: <https://www.w3.org/TR/trig/>
7
8use super::error::FormatError;
9use crate::model::{
10    GraphName, Literal, NamedNode, ObjectRef, PredicateRef, Quad, QuadRef, SubjectRef,
11};
12use std::collections::{BTreeMap, HashMap};
13use std::io::Write;
14
15/// TriG serializer for writing RDF quads with named graph support
16#[derive(Debug, Clone)]
17pub struct TriGSerializer {
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 TriGSerializer {
27    /// Create a new TriG serializer
28    pub fn new() -> Self {
29        let mut prefixes = HashMap::new();
30
31        // Add common 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
45        Self {
46            base_iri: None,
47            prefixes,
48            pretty: false,
49        }
50    }
51
52    /// Set the base IRI
53    pub fn with_base_iri(mut self, base: &str) -> Self {
54        self.base_iri = Some(base.to_string());
55        self
56    }
57
58    /// Add a prefix mapping
59    pub fn with_prefix(mut self, prefix: &str, iri: &str) -> Self {
60        self.prefixes.insert(prefix.to_string(), iri.to_string());
61        self
62    }
63
64    /// Enable pretty printing
65    pub fn pretty(mut self) -> Self {
66        self.pretty = true;
67        self
68    }
69
70    /// Wrap this serializer for a specific writer
71    pub fn for_writer<W: Write + 'static>(self, writer: W) -> TriGWriter<W> {
72        TriGWriter {
73            writer,
74            serializer: self,
75            buffer: Vec::new(),
76        }
77    }
78
79    /// Serialize quads grouped by graph
80    fn serialize_quads<W: Write>(&self, quads: &[Quad], writer: &mut W) -> Result<(), FormatError> {
81        // Write prefix declarations
82        for (prefix, namespace) in &self.prefixes {
83            writeln!(writer, "@prefix {}: <{}> .", prefix, namespace).map_err(FormatError::from)?;
84        }
85
86        if !self.prefixes.is_empty() {
87            writeln!(writer).map_err(FormatError::from)?;
88        }
89
90        // Group quads by graph
91        let grouped = self.group_quads_by_graph(quads);
92
93        for (graph_name, graph_quads) in grouped {
94            match graph_name {
95                GraphName::DefaultGraph => {
96                    // Serialize default graph triples directly
97                    for quad in graph_quads {
98                        self.serialize_triple(quad.as_ref(), writer)?;
99                        writeln!(writer, " .").map_err(FormatError::from)?;
100                    }
101                }
102                GraphName::NamedNode(node) => {
103                    // Named graph
104                    self.write_named_node(&node, writer)?;
105                    writeln!(writer, " {{").map_err(FormatError::from)?;
106
107                    for quad in graph_quads {
108                        if self.pretty {
109                            write!(writer, "    ").map_err(FormatError::from)?;
110                        }
111                        self.serialize_triple(quad.as_ref(), writer)?;
112                        writeln!(writer, " .").map_err(FormatError::from)?;
113                    }
114
115                    writeln!(writer, "}}").map_err(FormatError::from)?;
116                }
117                GraphName::BlankNode(node) => {
118                    // Blank node graph
119                    let id = node.as_str();
120                    let id = id.strip_prefix("_:").unwrap_or(id);
121                    writeln!(writer, "_:{} {{", id).map_err(FormatError::from)?;
122
123                    for quad in graph_quads {
124                        if self.pretty {
125                            write!(writer, "    ").map_err(FormatError::from)?;
126                        }
127                        self.serialize_triple(quad.as_ref(), writer)?;
128                        writeln!(writer, " .").map_err(FormatError::from)?;
129                    }
130
131                    writeln!(writer, "}}").map_err(FormatError::from)?;
132                }
133                GraphName::Variable(_) => {
134                    return Err(FormatError::InvalidData(
135                        "Cannot serialize variable graph names".to_string(),
136                    ));
137                }
138            }
139
140            if self.pretty {
141                writeln!(writer).map_err(FormatError::from)?;
142            }
143        }
144
145        Ok(())
146    }
147
148    fn serialize_triple<W: Write>(
149        &self,
150        quad: QuadRef<'_>,
151        writer: &mut W,
152    ) -> Result<(), FormatError> {
153        self.write_subject(quad.subject(), writer)?;
154        write!(writer, " ").map_err(FormatError::from)?;
155
156        self.write_predicate(quad.predicate(), writer)?;
157        write!(writer, " ").map_err(FormatError::from)?;
158
159        self.write_object(quad.object(), writer)?;
160
161        Ok(())
162    }
163
164    fn write_subject<W: Write>(
165        &self,
166        subject: SubjectRef<'_>,
167        writer: &mut W,
168    ) -> Result<(), FormatError> {
169        match subject {
170            SubjectRef::NamedNode(node) => self.write_named_node(node, writer)?,
171            SubjectRef::BlankNode(node) => {
172                let id = node.as_str();
173                let id = id.strip_prefix("_:").unwrap_or(id);
174                write!(writer, "_:{}", id).map_err(FormatError::from)?;
175            }
176            SubjectRef::Variable(var) => {
177                write!(writer, "?{}", var.name()).map_err(FormatError::from)?;
178            }
179            SubjectRef::QuotedTriple(qt) => self.write_quoted_triple(qt.inner(), writer)?,
180        }
181        Ok(())
182    }
183
184    fn write_quoted_triple<W: Write>(
185        &self,
186        inner: &crate::model::Triple,
187        writer: &mut W,
188    ) -> Result<(), FormatError> {
189        write!(writer, "<< ").map_err(FormatError::from)?;
190        self.write_subject(inner.subject().into(), writer)?;
191        write!(writer, " ").map_err(FormatError::from)?;
192        self.write_predicate(inner.predicate().into(), writer)?;
193        write!(writer, " ").map_err(FormatError::from)?;
194        self.write_object(inner.object().into(), writer)?;
195        write!(writer, " >>").map_err(FormatError::from)?;
196        Ok(())
197    }
198
199    fn write_predicate<W: Write>(
200        &self,
201        predicate: PredicateRef<'_>,
202        writer: &mut W,
203    ) -> Result<(), FormatError> {
204        match predicate {
205            PredicateRef::NamedNode(node) => {
206                // Check for rdf:type abbreviation
207                if node.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
208                    write!(writer, "a").map_err(FormatError::from)?;
209                } else {
210                    self.write_named_node(node, writer)?;
211                }
212            }
213            PredicateRef::Variable(var) => {
214                write!(writer, "?{}", var.name()).map_err(FormatError::from)?;
215            }
216        }
217        Ok(())
218    }
219
220    fn write_object<W: Write>(
221        &self,
222        object: ObjectRef<'_>,
223        writer: &mut W,
224    ) -> Result<(), FormatError> {
225        match object {
226            ObjectRef::NamedNode(node) => self.write_named_node(node, writer)?,
227            ObjectRef::BlankNode(node) => {
228                let id = node.as_str();
229                let id = id.strip_prefix("_:").unwrap_or(id);
230                write!(writer, "_:{}", id).map_err(FormatError::from)?;
231            }
232            ObjectRef::Literal(literal) => self.write_literal(literal, writer)?,
233            ObjectRef::Variable(var) => {
234                write!(writer, "?{}", var.name()).map_err(FormatError::from)?;
235            }
236            ObjectRef::QuotedTriple(qt) => self.write_quoted_triple(qt.inner(), writer)?,
237        }
238        Ok(())
239    }
240
241    fn write_named_node<W: Write>(
242        &self,
243        node: &NamedNode,
244        writer: &mut W,
245    ) -> Result<(), FormatError> {
246        let iri = node.as_str();
247
248        // Try to use a prefix
249        for (prefix, namespace) in &self.prefixes {
250            if let Some(local) = iri.strip_prefix(namespace) {
251                write!(writer, "{}:{}", prefix, local).map_err(FormatError::from)?;
252                return Ok(());
253            }
254        }
255
256        // Use full IRI
257        write!(writer, "<{}>", iri).map_err(FormatError::from)?;
258        Ok(())
259    }
260
261    fn write_literal<W: Write>(
262        &self,
263        literal: &Literal,
264        writer: &mut W,
265    ) -> Result<(), FormatError> {
266        let value = literal.value();
267        let escaped = self.escape_string(value);
268
269        write!(writer, "\"{}\"", escaped).map_err(FormatError::from)?;
270
271        if let Some(lang) = literal.language() {
272            write!(writer, "@{}", lang).map_err(FormatError::from)?;
273        } else {
274            let datatype = literal.datatype();
275            if datatype.as_str() != "http://www.w3.org/2001/XMLSchema#string" {
276                write!(writer, "^^").map_err(FormatError::from)?;
277                self.write_named_node(&datatype.into_owned(), writer)?;
278            }
279        }
280
281        Ok(())
282    }
283
284    fn escape_string(&self, s: &str) -> String {
285        let mut result = String::with_capacity(s.len());
286        for ch in s.chars() {
287            match ch {
288                '\\' => result.push_str("\\\\"),
289                '\"' => result.push_str("\\\""),
290                '\n' => result.push_str("\\n"),
291                '\r' => result.push_str("\\r"),
292                '\t' => result.push_str("\\t"),
293                c if c.is_control() => {
294                    result.push_str(&format!("\\u{:04X}", c as u32));
295                }
296                c => result.push(c),
297            }
298        }
299        result
300    }
301
302    fn group_quads_by_graph<'a>(&self, quads: &'a [Quad]) -> BTreeMap<GraphName, Vec<&'a Quad>> {
303        let mut grouped = BTreeMap::new();
304
305        for quad in quads {
306            grouped
307                .entry(quad.graph_name().clone())
308                .or_insert_with(Vec::new)
309                .push(quad);
310        }
311
312        grouped
313    }
314}
315
316impl Default for TriGSerializer {
317    fn default() -> Self {
318        Self::new()
319    }
320}
321
322/// Writer wrapper for TriG serialization
323pub struct TriGWriter<W: Write> {
324    writer: W,
325    serializer: TriGSerializer,
326    buffer: Vec<Quad>,
327}
328
329impl<W: Write> TriGWriter<W> {
330    /// Serialize a single quad (buffered until finish)
331    pub fn serialize_quad(&mut self, quad: QuadRef<'_>) -> Result<(), FormatError> {
332        self.buffer.push(quad.into());
333        Ok(())
334    }
335
336    /// Finish serialization and return the writer
337    pub fn finish(mut self) -> Result<W, FormatError> {
338        self.serializer
339            .serialize_quads(&self.buffer, &mut self.writer)?;
340        Ok(self.writer)
341    }
342}
343
344/// Implement the QuadSerializer trait for integration with the format system
345impl<W: Write> super::serializer::QuadSerializer<W> for TriGWriter<W> {
346    fn serialize_quad(&mut self, quad: QuadRef<'_>) -> super::serializer::QuadSerializeResult {
347        TriGWriter::serialize_quad(self, quad)
348            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
349    }
350
351    fn finish(self: Box<Self>) -> super::error::SerializeResult<W> {
352        TriGWriter::finish(*self).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359    use crate::model::{NamedNode, Object, Quad, Subject, Triple};
360
361    #[test]
362    fn test_trig_serialize_default_graph() {
363        let serializer = TriGSerializer::new();
364        let mut writer = Vec::new();
365
366        let triple = Triple::new(
367            Subject::NamedNode(NamedNode::new("http://example.org/subject").expect("valid IRI")),
368            NamedNode::new("http://example.org/predicate").expect("valid IRI"),
369            Object::NamedNode(NamedNode::new("http://example.org/object").expect("valid IRI")),
370        );
371
372        let quads = vec![Quad::from(triple)];
373        serializer
374            .serialize_quads(&quads, &mut writer)
375            .expect("operation should succeed");
376
377        let output = String::from_utf8(writer).expect("bytes should be valid UTF-8");
378        assert!(output.contains("<http://example.org/subject>"));
379        assert!(output.contains("<http://example.org/predicate>"));
380        assert!(output.contains("<http://example.org/object>"));
381    }
382
383    #[test]
384    fn test_trig_serialize_named_graph() {
385        let serializer = TriGSerializer::new();
386        let mut writer = Vec::new();
387
388        let quad = Quad::new(
389            Subject::NamedNode(NamedNode::new("http://example.org/subject").expect("valid IRI")),
390            NamedNode::new("http://example.org/predicate").expect("valid IRI"),
391            Object::NamedNode(NamedNode::new("http://example.org/object").expect("valid IRI")),
392            GraphName::NamedNode(NamedNode::new("http://example.org/graph").expect("valid IRI")),
393        );
394
395        let quads = vec![quad];
396        serializer
397            .serialize_quads(&quads, &mut writer)
398            .expect("operation should succeed");
399
400        let output = String::from_utf8(writer).expect("bytes should be valid UTF-8");
401        assert!(output.contains("<http://example.org/graph>"));
402        assert!(output.contains("{"));
403        assert!(output.contains("}"));
404    }
405}