Skip to main content

oxirs_core/format/
serializer.rs

1//! Unified RDF Serializer Interface
2//!
3//! Provides a consistent API for serializing to all supported RDF formats.
4//! Extracted and adapted from OxiGraph with OxiRS enhancements.
5
6use super::error::FormatError;
7pub use super::error::SerializeResult;
8use super::format::RdfFormat;
9use crate::model::{Quad, QuadRef, Triple, TripleRef};
10use std::collections::HashMap;
11use std::io::Write;
12
13/// Result type for quad serialization operations
14pub type QuadSerializeResult = SerializeResult<()>;
15
16/// Writer-based quad serializer
17pub struct WriterQuadSerializer<W: Write> {
18    inner: Box<dyn QuadSerializer<W>>,
19}
20
21impl<W: Write> WriterQuadSerializer<W> {
22    /// Create a new writer serializer
23    pub fn new(serializer: Box<dyn QuadSerializer<W>>) -> Self {
24        Self { inner: serializer }
25    }
26
27    /// Serialize a quad
28    pub fn serialize_quad<'a>(&mut self, quad: impl Into<QuadRef<'a>>) -> QuadSerializeResult {
29        self.inner.serialize_quad(quad.into())
30    }
31
32    /// Serialize a triple (placed in default graph)
33    pub fn serialize_triple<'a>(
34        &mut self,
35        triple: impl Into<TripleRef<'a>>,
36    ) -> QuadSerializeResult {
37        let quad = triple.into().in_graph(None);
38        self.serialize_quad(quad)
39    }
40
41    /// Serialize multiple quads
42    pub fn serialize_quads<I>(&mut self, quads: I) -> QuadSerializeResult
43    where
44        I: IntoIterator,
45        I::Item: Into<QuadRef<'static>>,
46    {
47        for quad in quads {
48            self.inner.serialize_quad(quad.into())?;
49        }
50        Ok(())
51    }
52
53    /// Finish serialization and return the writer
54    pub fn finish(self) -> SerializeResult<W> {
55        self.inner.finish()
56    }
57}
58
59/// Trait for serializing quads to a writer
60pub trait QuadSerializer<W: Write> {
61    /// Serialize a quad
62    fn serialize_quad(&mut self, quad: QuadRef<'_>) -> QuadSerializeResult;
63
64    /// Finish serialization and return the writer
65    fn finish(self: Box<Self>) -> SerializeResult<W>;
66}
67
68/// Extension trait for bulk serialization operations
69pub trait QuadSerializerExt<W: Write>: QuadSerializer<W> {
70    /// Serialize multiple quads
71    fn serialize_quads<I>(&mut self, quads: I) -> QuadSerializeResult
72    where
73        I: IntoIterator,
74        I::Item: Into<QuadRef<'static>>,
75    {
76        for quad in quads {
77            self.serialize_quad(quad.into())?;
78        }
79        Ok(())
80    }
81}
82
83/// Blanket implementation for all QuadSerializer types
84impl<W: Write, T: QuadSerializer<W>> QuadSerializerExt<W> for T {}
85
86/// Unified RDF serializer supporting all formats
87pub struct RdfSerializer {
88    format: RdfFormat,
89    base_iri: Option<String>,
90    prefixes: HashMap<String, String>,
91    pretty: bool,
92}
93
94impl RdfSerializer {
95    /// Create a new serializer for the specified format
96    pub fn new(format: RdfFormat) -> Self {
97        Self {
98            format,
99            base_iri: None,
100            prefixes: HashMap::new(),
101            pretty: false,
102        }
103    }
104
105    /// Set the base IRI for relative IRI generation
106    pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Self {
107        self.base_iri = Some(base_iri.into());
108        self
109    }
110
111    /// Add a namespace prefix
112    pub fn with_prefix(mut self, prefix: impl Into<String>, iri: impl Into<String>) -> Self {
113        self.prefixes.insert(prefix.into(), iri.into());
114        self
115    }
116
117    /// Enable pretty formatting (indentation, line breaks)
118    pub fn pretty(mut self) -> Self {
119        self.pretty = true;
120        self
121    }
122
123    /// Create a writer-based serializer
124    pub fn for_writer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
125        match self.format {
126            RdfFormat::Turtle => self.create_turtle_serializer(writer),
127            RdfFormat::NTriples => self.create_ntriples_serializer(writer),
128            RdfFormat::NQuads => self.create_nquads_serializer(writer),
129            RdfFormat::TriG => self.create_trig_serializer(writer),
130            RdfFormat::RdfXml => self.create_rdfxml_serializer(writer),
131            RdfFormat::JsonLd { .. } => self.create_jsonld_serializer(writer),
132            RdfFormat::N3 => self.create_n3_serializer(writer),
133        }
134    }
135
136    /// Get the format being serialized
137    pub fn format(&self) -> RdfFormat {
138        self.format.clone()
139    }
140
141    /// Get the base IRI
142    pub fn base_iri(&self) -> Option<&str> {
143        self.base_iri.as_deref()
144    }
145
146    /// Get the prefixes
147    pub fn prefixes(&self) -> &HashMap<String, String> {
148        &self.prefixes
149    }
150
151    /// Check if pretty formatting is enabled
152    pub fn is_pretty(&self) -> bool {
153        self.pretty
154    }
155
156    // Format-specific serializer implementations
157
158    fn create_turtle_serializer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
159        // Use existing Turtle serializer implementation
160        let mut turtle_serializer = super::turtle::TurtleSerializer::new();
161
162        // Apply configuration
163        if let Some(base) = self.base_iri {
164            turtle_serializer = turtle_serializer.with_base_iri(&base);
165        }
166        for (prefix, iri) in self.prefixes {
167            turtle_serializer = turtle_serializer.with_prefix(&prefix, &iri);
168        }
169        if self.pretty {
170            turtle_serializer = turtle_serializer.pretty();
171        }
172
173        WriterQuadSerializer::new(Box::new(turtle_serializer.for_writer(writer)))
174    }
175
176    fn create_ntriples_serializer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
177        // Use existing N-Triples serializer implementation
178        let ntriples_serializer = super::ntriples::NTriplesSerializer::new().for_writer(writer);
179        WriterQuadSerializer::new(Box::new(ntriples_serializer))
180    }
181
182    fn create_nquads_serializer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
183        // Use N-Quads serializer implementation
184        let nquads_serializer = super::nquads::NQuadsSerializer::new().for_writer(writer);
185        WriterQuadSerializer::new(Box::new(nquads_serializer))
186    }
187
188    fn create_trig_serializer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
189        // Use TriG serializer implementation
190        let mut trig_serializer = super::trig::TriGSerializer::new();
191
192        // Apply configuration
193        if let Some(base) = self.base_iri {
194            trig_serializer = trig_serializer.with_base_iri(&base);
195        }
196        for (prefix, iri) in self.prefixes {
197            trig_serializer = trig_serializer.with_prefix(&prefix, &iri);
198        }
199        if self.pretty {
200            trig_serializer = trig_serializer.pretty();
201        }
202
203        WriterQuadSerializer::new(Box::new(trig_serializer.for_writer(writer)))
204    }
205
206    fn create_rdfxml_serializer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
207        // Use existing RDF/XML serializer implementation
208        let mut rdfxml_serializer = super::rdfxml::RdfXmlSerializer::new();
209
210        // Apply configuration
211        if self.pretty {
212            rdfxml_serializer = rdfxml_serializer.pretty();
213        }
214
215        WriterQuadSerializer::new(Box::new(rdfxml_serializer.for_writer(writer)))
216    }
217
218    fn create_jsonld_serializer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
219        // Use existing JSON-LD serializer implementation
220        let mut jsonld_serializer = super::jsonld::JsonLdSerializer::new();
221
222        // Apply configuration
223        if self.pretty {
224            jsonld_serializer = jsonld_serializer.pretty();
225        }
226
227        WriterQuadSerializer::new(Box::new(jsonld_serializer.for_writer(writer)))
228    }
229
230    fn create_n3_serializer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
231        // Use N3 serializer implementation
232        let mut n3_serializer = super::n3::N3Serializer::new();
233
234        // Apply configuration
235        if let Some(base) = self.base_iri {
236            n3_serializer = n3_serializer.with_base_iri(&base);
237        }
238        for (prefix, iri) in self.prefixes {
239            n3_serializer = n3_serializer.with_prefix(&prefix, &iri);
240        }
241        if self.pretty {
242            n3_serializer = n3_serializer.pretty();
243        }
244
245        WriterQuadSerializer::new(Box::new(n3_serializer.for_writer(writer)))
246    }
247}
248
249impl Default for RdfSerializer {
250    fn default() -> Self {
251        Self::new(RdfFormat::default())
252    }
253}
254
255/// Serialization configuration for fine-grained control
256#[derive(Debug, Clone)]
257pub struct SerializeConfig {
258    /// Use compact formatting
259    pub compact: bool,
260    /// Indentation string for pretty formatting
261    pub indent: String,
262    /// Line ending style
263    pub line_ending: LineEnding,
264    /// Maximum line length for wrapping
265    pub max_line_length: Option<usize>,
266    /// Sort output by subject/predicate
267    pub sort_output: bool,
268    /// Include comments in output
269    pub include_comments: bool,
270    /// Validate output during serialization
271    pub validate_output: bool,
272}
273
274/// Line ending styles
275#[derive(Debug, Clone, PartialEq, Eq)]
276pub enum LineEnding {
277    /// Unix-style line endings (\n)
278    Unix,
279    /// Windows-style line endings (\r\n)
280    Windows,
281    /// Mac-style line endings (\r)
282    Mac,
283    /// Platform-default line endings
284    Platform,
285}
286
287impl Default for SerializeConfig {
288    fn default() -> Self {
289        Self {
290            compact: false,
291            indent: "  ".to_string(),
292            line_ending: LineEnding::Platform,
293            max_line_length: None,
294            sort_output: false,
295            include_comments: false,
296            validate_output: true,
297        }
298    }
299}
300
301impl LineEnding {
302    /// Get the line ending string
303    pub fn as_str(&self) -> &'static str {
304        match self {
305            Self::Unix => "\n",
306            Self::Windows => "\r\n",
307            Self::Mac => "\r",
308            Self::Platform => {
309                #[cfg(windows)]
310                return "\r\n";
311                #[cfg(not(windows))]
312                return "\n";
313            }
314        }
315    }
316}
317
318/// Advanced serializer with configuration support
319pub struct ConfigurableSerializer {
320    serializer: RdfSerializer,
321    config: SerializeConfig,
322}
323
324impl ConfigurableSerializer {
325    /// Create a new configurable serializer
326    pub fn new(format: RdfFormat, config: SerializeConfig) -> Self {
327        Self {
328            serializer: RdfSerializer::new(format),
329            config,
330        }
331    }
332
333    /// Set base IRI
334    pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Self {
335        self.serializer = self.serializer.with_base_iri(base_iri);
336        self
337    }
338
339    /// Add prefix
340    pub fn with_prefix(mut self, prefix: impl Into<String>, iri: impl Into<String>) -> Self {
341        self.serializer = self.serializer.with_prefix(prefix, iri);
342        self
343    }
344
345    /// Create a writer with configuration
346    pub fn for_writer<W: Write + 'static>(self, writer: W) -> WriterQuadSerializer<W> {
347        // Apply configuration settings and create serializer
348        let mut serializer = self.serializer;
349
350        // Apply pretty formatting (negation of compact)
351        if !self.config.compact {
352            serializer = serializer.pretty();
353        }
354
355        // Note: The following configuration options are defined in SerializeConfig
356        // but not yet fully supported by all underlying format serializers:
357        // - indent: Custom indentation strings (currently using format defaults)
358        // - line_ending: Custom line endings (currently using platform defaults)
359        // - max_line_length: Line wrapping (reserved for future implementation)
360        // - sort_output: Output ordering (reserved for future implementation)
361        // - include_comments: Comment generation (reserved for future implementation)
362        // - validate_output: Currently handled by individual serializers
363        //
364        // These options are preserved for backward compatibility and future enhancement.
365        // For now, compact/pretty formatting is the primary configurable option.
366
367        serializer.for_writer(writer)
368    }
369
370    /// Get the configuration
371    pub fn config(&self) -> &SerializeConfig {
372        &self.config
373    }
374
375    /// Get the serializer
376    pub fn serializer(&self) -> &RdfSerializer {
377        &self.serializer
378    }
379}
380
381/// Simple serialization functions for common use cases
382pub mod simple {
383    use super::*;
384
385    /// Serialize triples to a string in the specified format
386    pub fn serialize_triples_to_string(
387        triples: &[Triple],
388        format: RdfFormat,
389    ) -> Result<String, FormatError> {
390        let buffer = Vec::new();
391        let mut serializer = RdfSerializer::new(format).for_writer(buffer);
392        for triple in triples {
393            serializer.serialize_triple(triple.as_ref())?;
394        }
395        let buffer = serializer.finish()?;
396        String::from_utf8(buffer).map_err(|e| FormatError::invalid_data(e.to_string()))
397    }
398
399    /// Serialize quads to a string in the specified format
400    pub fn serialize_quads_to_string(
401        quads: &[Quad],
402        format: RdfFormat,
403    ) -> Result<String, FormatError> {
404        let buffer = Vec::new();
405        let mut serializer = RdfSerializer::new(format).for_writer(buffer);
406        for quad in quads {
407            serializer.serialize_quad(quad.as_ref())?;
408        }
409        let buffer = serializer.finish()?;
410        String::from_utf8(buffer).map_err(|e| FormatError::invalid_data(e.to_string()))
411    }
412
413    /// Serialize triples to Turtle string
414    pub fn serialize_turtle(triples: &[Triple]) -> Result<String, FormatError> {
415        serialize_triples_to_string(triples, RdfFormat::Turtle)
416    }
417
418    /// Serialize triples to N-Triples string
419    pub fn serialize_ntriples(triples: &[Triple]) -> Result<String, FormatError> {
420        serialize_triples_to_string(triples, RdfFormat::NTriples)
421    }
422
423    /// Serialize quads to N-Quads string
424    pub fn serialize_nquads(quads: &[Quad]) -> Result<String, FormatError> {
425        serialize_quads_to_string(quads, RdfFormat::NQuads)
426    }
427
428    /// Serialize quads to TriG string
429    pub fn serialize_trig(quads: &[Quad]) -> Result<String, FormatError> {
430        serialize_quads_to_string(quads, RdfFormat::TriG)
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn test_serializer_creation() {
440        let serializer = RdfSerializer::new(RdfFormat::Turtle);
441        assert_eq!(serializer.format(), RdfFormat::Turtle);
442        assert!(serializer.base_iri().is_none());
443        assert!(serializer.prefixes().is_empty());
444        assert!(!serializer.is_pretty());
445    }
446
447    #[test]
448    fn test_serializer_configuration() {
449        let serializer = RdfSerializer::new(RdfFormat::Turtle)
450            .with_base_iri("http://example.org/")
451            .with_prefix("ex", "http://example.org/ns#")
452            .pretty();
453
454        assert_eq!(serializer.base_iri(), Some("http://example.org/"));
455        assert_eq!(
456            serializer.prefixes().get("ex"),
457            Some(&"http://example.org/ns#".to_string())
458        );
459        assert!(serializer.is_pretty());
460    }
461
462    #[test]
463    fn test_configurable_serializer() {
464        let config = SerializeConfig {
465            compact: true,
466            sort_output: true,
467            ..Default::default()
468        };
469
470        let serializer = ConfigurableSerializer::new(RdfFormat::NQuads, config);
471        assert!(serializer.config().compact);
472        assert!(serializer.config().sort_output);
473    }
474
475    #[test]
476    fn test_serialize_config_default() {
477        let config = SerializeConfig::default();
478        assert!(!config.compact);
479        assert_eq!(config.indent, "  ");
480        assert_eq!(config.line_ending, LineEnding::Platform);
481        assert_eq!(config.max_line_length, None);
482        assert!(!config.sort_output);
483        assert!(!config.include_comments);
484        assert!(config.validate_output);
485    }
486
487    #[test]
488    fn test_line_ending() {
489        assert_eq!(LineEnding::Unix.as_str(), "\n");
490        assert_eq!(LineEnding::Windows.as_str(), "\r\n");
491        assert_eq!(LineEnding::Mac.as_str(), "\r");
492        // Platform depends on the compilation target
493    }
494}