oxrdfio/serializer.rs
1//! Utilities to write RDF graphs and datasets.
2
3use crate::format::RdfFormat;
4#[cfg(feature = "async-tokio")]
5use oxjsonld::TokioAsyncWriterJsonLdSerializer;
6use oxjsonld::{JsonLdProfile, JsonLdSerializer, WriterJsonLdSerializer};
7use oxrdf::{GraphNameRef, IriParseError, QuadRef, TripleRef};
8#[cfg(feature = "async-tokio")]
9use oxrdfxml::TokioAsyncWriterRdfXmlSerializer;
10use oxrdfxml::{RdfXmlSerializer, WriterRdfXmlSerializer};
11#[cfg(feature = "async-tokio")]
12use oxttl::nquads::TokioAsyncWriterNQuadsSerializer;
13use oxttl::nquads::{NQuadsSerializer, WriterNQuadsSerializer};
14#[cfg(feature = "async-tokio")]
15use oxttl::ntriples::TokioAsyncWriterNTriplesSerializer;
16use oxttl::ntriples::{NTriplesSerializer, WriterNTriplesSerializer};
17#[cfg(feature = "async-tokio")]
18use oxttl::trig::TokioAsyncWriterTriGSerializer;
19use oxttl::trig::{TriGSerializer, WriterTriGSerializer};
20#[cfg(feature = "async-tokio")]
21use oxttl::turtle::TokioAsyncWriterTurtleSerializer;
22use oxttl::turtle::{TurtleSerializer, WriterTurtleSerializer};
23use std::io::{self, Write};
24#[cfg(feature = "async-tokio")]
25use tokio::io::AsyncWrite;
26
27/// A serializer for RDF serialization formats.
28///
29/// It currently supports the following formats:
30/// * [JSON-LD](https://www.w3.org/TR/json-ld/) ([`RdfFormat::JsonLd`])
31/// * [N3](https://w3c.github.io/N3/spec/) ([`RdfFormat::N3`])
32/// * [N-Quads](https://www.w3.org/TR/n-quads/) ([`RdfFormat::NQuads`])
33/// * [canonical](https://www.w3.org/TR/n-triples/#canonical-ntriples) [N-Triples](https://www.w3.org/TR/n-triples/) ([`RdfFormat::NTriples`])
34/// * [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) ([`RdfFormat::RdfXml`])
35/// * [TriG](https://www.w3.org/TR/trig/) ([`RdfFormat::TriG`])
36/// * [Turtle](https://www.w3.org/TR/turtle/) ([`RdfFormat::Turtle`])
37///
38/// ```
39/// use oxrdfio::{RdfFormat, RdfSerializer};
40/// use oxrdf::{Quad, NamedNode};
41///
42/// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_writer(Vec::new());
43/// serializer.serialize_quad(&Quad {
44/// subject: NamedNode::new("http://example.com/s")?.into(),
45/// predicate: NamedNode::new("http://example.com/p")?,
46/// object: NamedNode::new("http://example.com/o")?.into(),
47/// graph_name: NamedNode::new("http://example.com/g")?.into()
48/// })?;
49/// assert_eq!(serializer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
50/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
51/// ```
52#[must_use]
53#[derive(Clone)]
54pub struct RdfSerializer {
55 inner: RdfSerializerKind,
56}
57
58#[derive(Clone)]
59enum RdfSerializerKind {
60 JsonLd(JsonLdSerializer),
61 NQuads(NQuadsSerializer),
62 NTriples(NTriplesSerializer),
63 RdfXml(RdfXmlSerializer),
64 TriG(TriGSerializer),
65 Turtle(TurtleSerializer),
66}
67
68impl RdfSerializer {
69 /// Builds a serializer for the given format
70 #[inline]
71 pub fn from_format(format: RdfFormat) -> Self {
72 Self {
73 inner: match format {
74 RdfFormat::JsonLd { .. } => RdfSerializerKind::JsonLd(JsonLdSerializer::new()),
75 RdfFormat::NQuads => RdfSerializerKind::NQuads(NQuadsSerializer::new()),
76 RdfFormat::NTriples => RdfSerializerKind::NTriples(NTriplesSerializer::new()),
77 RdfFormat::RdfXml => RdfSerializerKind::RdfXml(RdfXmlSerializer::new()),
78 RdfFormat::TriG => RdfSerializerKind::TriG(TriGSerializer::new()),
79 RdfFormat::Turtle | RdfFormat::N3 => {
80 RdfSerializerKind::Turtle(TurtleSerializer::new())
81 }
82 },
83 }
84 }
85
86 /// The format the serializer serializes to.
87 ///
88 /// ```
89 /// use oxrdfio::{RdfFormat, RdfSerializer};
90 ///
91 /// assert_eq!(
92 /// RdfSerializer::from_format(RdfFormat::Turtle).format(),
93 /// RdfFormat::Turtle
94 /// );
95 /// ```
96 pub fn format(&self) -> RdfFormat {
97 match &self.inner {
98 RdfSerializerKind::JsonLd(_) => RdfFormat::JsonLd {
99 profile: JsonLdProfile::Streaming.into(), // TODO: also expanded?
100 },
101 RdfSerializerKind::NQuads(_) => RdfFormat::NQuads,
102 RdfSerializerKind::NTriples(_) => RdfFormat::NTriples,
103 RdfSerializerKind::RdfXml(_) => RdfFormat::RdfXml,
104 RdfSerializerKind::TriG(_) => RdfFormat::TriG,
105 RdfSerializerKind::Turtle(_) => RdfFormat::Turtle,
106 }
107 }
108
109 /// If the format supports it, sets a prefix.
110 ///
111 /// ```
112 /// use oxrdf::vocab::rdf;
113 /// use oxrdf::{NamedNodeRef, TripleRef};
114 /// use oxrdfio::{RdfFormat, RdfSerializer};
115 ///
116 /// let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
117 /// .with_prefix("schema", "http://schema.org/")?
118 /// .for_writer(Vec::new());
119 /// serializer.serialize_triple(TripleRef {
120 /// subject: NamedNodeRef::new("http://example.com/s")?.into(),
121 /// predicate: rdf::TYPE.into(),
122 /// object: NamedNodeRef::new("http://schema.org/Person")?.into(),
123 /// })?;
124 /// assert_eq!(
125 /// serializer.finish()?,
126 /// b"@prefix schema: <http://schema.org/> .\n<http://example.com/s> a schema:Person .\n"
127 /// );
128 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
129 /// ```
130 #[inline]
131 pub fn with_prefix(
132 mut self,
133 prefix_name: impl Into<String>,
134 prefix_iri: impl Into<String>,
135 ) -> Result<Self, IriParseError> {
136 self.inner = match self.inner {
137 RdfSerializerKind::JsonLd(s) => RdfSerializerKind::JsonLd(s),
138 RdfSerializerKind::NQuads(s) => RdfSerializerKind::NQuads(s),
139 RdfSerializerKind::NTriples(s) => RdfSerializerKind::NTriples(s),
140 RdfSerializerKind::RdfXml(s) => {
141 RdfSerializerKind::RdfXml(s.with_prefix(prefix_name, prefix_iri)?)
142 }
143 RdfSerializerKind::TriG(s) => {
144 RdfSerializerKind::TriG(s.with_prefix(prefix_name, prefix_iri)?)
145 }
146 RdfSerializerKind::Turtle(s) => {
147 RdfSerializerKind::Turtle(s.with_prefix(prefix_name, prefix_iri)?)
148 }
149 };
150 Ok(self)
151 }
152
153 /// If the format supports it, sets a base IRI.
154 ///
155 /// ```
156 /// use oxrdf::vocab::rdf;
157 /// use oxrdf::{NamedNodeRef, TripleRef};
158 /// use oxrdfio::{RdfFormat, RdfSerializer};
159 ///
160 /// let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
161 /// .with_base_iri("http://example.com")?
162 /// .with_prefix("ex", "http://example.com/ns#")?
163 /// .for_writer(Vec::new());
164 /// serializer.serialize_triple(TripleRef::new(
165 /// NamedNodeRef::new("http://example.com/me")?,
166 /// rdf::TYPE,
167 /// NamedNodeRef::new("http://example.com/ns#Person")?,
168 /// ))?;
169 /// assert_eq!(
170 /// serializer.finish()?,
171 /// b"@base <http://example.com> .\n@prefix ex: </ns#> .\n</me> a ex:Person .\n",
172 /// );
173 /// # Result::<_,Box<dyn std::error::Error>>::Ok(())
174 /// ```
175 #[inline]
176 pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
177 self.inner = match self.inner {
178 RdfSerializerKind::JsonLd(s) => RdfSerializerKind::JsonLd(s),
179 RdfSerializerKind::NQuads(s) => RdfSerializerKind::NQuads(s),
180 RdfSerializerKind::NTriples(s) => RdfSerializerKind::NTriples(s),
181 RdfSerializerKind::RdfXml(s) => RdfSerializerKind::RdfXml(s.with_base_iri(base_iri)?),
182 RdfSerializerKind::TriG(s) => RdfSerializerKind::TriG(s.with_base_iri(base_iri)?),
183 RdfSerializerKind::Turtle(s) => RdfSerializerKind::Turtle(s.with_base_iri(base_iri)?),
184 };
185 Ok(self)
186 }
187
188 /// Serializes to a [`Write`] implementation.
189 ///
190 /// <div class="warning">
191 ///
192 /// Do not forget to run the [`finish`](WriterQuadSerializer::finish()) method to properly write the last bytes of the file.</div>
193 ///
194 /// <div class="warning">
195 ///
196 /// This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.</div>
197 ///
198 /// ```
199 /// use oxrdfio::{RdfFormat, RdfSerializer};
200 /// use oxrdf::{Quad, NamedNode};
201 ///
202 /// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_writer(Vec::new());
203 /// serializer.serialize_quad(&Quad {
204 /// subject: NamedNode::new("http://example.com/s")?.into(),
205 /// predicate: NamedNode::new("http://example.com/p")?,
206 /// object: NamedNode::new("http://example.com/o")?.into(),
207 /// graph_name: NamedNode::new("http://example.com/g")?.into()
208 /// })?;
209 /// assert_eq!(serializer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
210 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
211 /// ```
212 pub fn for_writer<W: Write>(self, writer: W) -> WriterQuadSerializer<W> {
213 WriterQuadSerializer {
214 inner: match self.inner {
215 RdfSerializerKind::JsonLd(s) => {
216 WriterQuadSerializerKind::JsonLd(s.for_writer(writer))
217 }
218 RdfSerializerKind::NQuads(s) => {
219 WriterQuadSerializerKind::NQuads(s.for_writer(writer))
220 }
221 RdfSerializerKind::NTriples(s) => {
222 WriterQuadSerializerKind::NTriples(s.for_writer(writer))
223 }
224 RdfSerializerKind::RdfXml(s) => {
225 WriterQuadSerializerKind::RdfXml(s.for_writer(writer))
226 }
227 RdfSerializerKind::TriG(s) => WriterQuadSerializerKind::TriG(s.for_writer(writer)),
228 RdfSerializerKind::Turtle(s) => {
229 WriterQuadSerializerKind::Turtle(s.for_writer(writer))
230 }
231 },
232 }
233 }
234
235 /// Serializes to a Tokio [`AsyncWrite`] implementation.
236 ///
237 /// <div class="warning">
238 ///
239 /// Do not forget to run the [`finish`](TokioAsyncWriterQuadSerializer::finish()) method to properly write the last bytes of the file.</div>
240 ///
241 /// <div class="warning">
242 ///
243 /// This writer does unbuffered writes. You might want to use [`BufWriter`](tokio::io::BufWriter) to avoid that.</div>
244 ///
245 /// ```
246 /// # #[tokio::main(flavor = "current_thread")]
247 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
248 /// use oxrdfio::{RdfFormat, RdfSerializer};
249 /// use oxrdf::{Quad, NamedNode};
250 ///
251 /// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_tokio_async_writer(Vec::new());
252 /// serializer.serialize_quad(&Quad {
253 /// subject: NamedNode::new("http://example.com/s")?.into(),
254 /// predicate: NamedNode::new("http://example.com/p")?,
255 /// object: NamedNode::new("http://example.com/o")?.into(),
256 /// graph_name: NamedNode::new("http://example.com/g")?.into()
257 /// }).await?;
258 /// assert_eq!(serializer.finish().await?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
259 /// # Ok(())
260 /// # }
261 /// ```
262 #[cfg(feature = "async-tokio")]
263 pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
264 self,
265 writer: W,
266 ) -> TokioAsyncWriterQuadSerializer<W> {
267 TokioAsyncWriterQuadSerializer {
268 inner: match self.inner {
269 RdfSerializerKind::JsonLd(s) => {
270 TokioAsyncWriterQuadSerializerKind::JsonLd(s.for_tokio_async_writer(writer))
271 }
272 RdfSerializerKind::NQuads(s) => {
273 TokioAsyncWriterQuadSerializerKind::NQuads(s.for_tokio_async_writer(writer))
274 }
275 RdfSerializerKind::NTriples(s) => {
276 TokioAsyncWriterQuadSerializerKind::NTriples(s.for_tokio_async_writer(writer))
277 }
278 RdfSerializerKind::RdfXml(s) => {
279 TokioAsyncWriterQuadSerializerKind::RdfXml(s.for_tokio_async_writer(writer))
280 }
281 RdfSerializerKind::TriG(s) => {
282 TokioAsyncWriterQuadSerializerKind::TriG(s.for_tokio_async_writer(writer))
283 }
284 RdfSerializerKind::Turtle(s) => {
285 TokioAsyncWriterQuadSerializerKind::Turtle(s.for_tokio_async_writer(writer))
286 }
287 },
288 }
289 }
290}
291
292impl From<RdfFormat> for RdfSerializer {
293 fn from(format: RdfFormat) -> Self {
294 Self::from_format(format)
295 }
296}
297
298/// Serializes quads or triples to a [`Write`] implementation.
299///
300/// Can be built using [`RdfSerializer::for_writer`].
301///
302/// <div class="warning">
303///
304/// Do not forget to run the [`finish`](WriterQuadSerializer::finish()) method to properly write the last bytes of the file.</div>
305///
306/// <div class="warning">
307///
308/// This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.</div>
309///
310/// ```
311/// use oxrdfio::{RdfFormat, RdfSerializer};
312/// use oxrdf::{Quad, NamedNode};
313///
314/// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_writer(Vec::new());
315/// serializer.serialize_quad(&Quad {
316/// subject: NamedNode::new("http://example.com/s")?.into(),
317/// predicate: NamedNode::new("http://example.com/p")?,
318/// object: NamedNode::new("http://example.com/o")?.into(),
319/// graph_name: NamedNode::new("http://example.com/g")?.into(),
320/// })?;
321/// assert_eq!(serializer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
322/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
323/// ```
324#[must_use]
325pub struct WriterQuadSerializer<W: Write> {
326 inner: WriterQuadSerializerKind<W>,
327}
328
329enum WriterQuadSerializerKind<W: Write> {
330 JsonLd(WriterJsonLdSerializer<W>),
331 NQuads(WriterNQuadsSerializer<W>),
332 NTriples(WriterNTriplesSerializer<W>),
333 RdfXml(WriterRdfXmlSerializer<W>),
334 TriG(WriterTriGSerializer<W>),
335 Turtle(WriterTurtleSerializer<W>),
336}
337
338impl<W: Write> WriterQuadSerializer<W> {
339 /// Serializes a [`QuadRef`]
340 pub fn serialize_quad<'a>(&mut self, quad: impl Into<QuadRef<'a>>) -> io::Result<()> {
341 match &mut self.inner {
342 WriterQuadSerializerKind::JsonLd(serializer) => serializer.serialize_quad(quad),
343 WriterQuadSerializerKind::NQuads(serializer) => serializer.serialize_quad(quad),
344 WriterQuadSerializerKind::NTriples(serializer) => {
345 serializer.serialize_triple(to_triple(quad)?)
346 }
347 WriterQuadSerializerKind::RdfXml(serializer) => {
348 serializer.serialize_triple(to_triple(quad)?)
349 }
350 WriterQuadSerializerKind::TriG(serializer) => serializer.serialize_quad(quad),
351 WriterQuadSerializerKind::Turtle(serializer) => {
352 serializer.serialize_triple(to_triple(quad)?)
353 }
354 }
355 }
356
357 /// Serializes a [`TripleRef`]
358 pub fn serialize_triple<'a>(&mut self, triple: impl Into<TripleRef<'a>>) -> io::Result<()> {
359 self.serialize_quad(triple.into().in_graph(GraphNameRef::DefaultGraph))
360 }
361
362 /// Writes the last bytes of the file
363 ///
364 /// Note that this function does not flush the writer. You need to do that if you are using a [`BufWriter`](io::BufWriter).
365 pub fn finish(self) -> io::Result<W> {
366 Ok(match self.inner {
367 WriterQuadSerializerKind::JsonLd(serializer) => serializer.finish()?,
368 WriterQuadSerializerKind::NQuads(serializer) => serializer.finish(),
369 WriterQuadSerializerKind::NTriples(serializer) => serializer.finish(),
370 WriterQuadSerializerKind::RdfXml(serializer) => serializer.finish()?,
371 WriterQuadSerializerKind::TriG(serializer) => serializer.finish()?,
372 WriterQuadSerializerKind::Turtle(serializer) => serializer.finish()?,
373 })
374 }
375}
376
377/// Serializes quads or triples to a [`AsyncWrite`] implementation.
378///
379/// Can be built using [`RdfSerializer::for_tokio_async_writer`].
380///
381/// <div class="warning">
382///
383/// Do not forget to run the [`finish`](WriterQuadSerializer::finish()) method to properly write the last bytes of the file.</div>
384///
385/// <div class="warning">
386///
387/// This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.</div>
388///
389/// ```
390/// # #[tokio::main(flavor = "current_thread")]
391/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
392/// use oxrdfio::{RdfFormat, RdfSerializer};
393/// use oxrdf::{Quad, NamedNode};
394///
395/// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_tokio_async_writer(Vec::new());
396/// serializer.serialize_quad(&Quad {
397/// subject: NamedNode::new("http://example.com/s")?.into(),
398/// predicate: NamedNode::new("http://example.com/p")?,
399/// object: NamedNode::new("http://example.com/o")?.into(),
400/// graph_name: NamedNode::new("http://example.com/g")?.into()
401/// }).await?;
402/// assert_eq!(serializer.finish().await?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
403/// # Ok(())
404/// # }
405/// ```
406#[must_use]
407#[cfg(feature = "async-tokio")]
408pub struct TokioAsyncWriterQuadSerializer<W: AsyncWrite + Unpin> {
409 inner: TokioAsyncWriterQuadSerializerKind<W>,
410}
411
412#[cfg(feature = "async-tokio")]
413enum TokioAsyncWriterQuadSerializerKind<W: AsyncWrite + Unpin> {
414 JsonLd(TokioAsyncWriterJsonLdSerializer<W>),
415 NQuads(TokioAsyncWriterNQuadsSerializer<W>),
416 NTriples(TokioAsyncWriterNTriplesSerializer<W>),
417 RdfXml(TokioAsyncWriterRdfXmlSerializer<W>),
418 TriG(TokioAsyncWriterTriGSerializer<W>),
419 Turtle(TokioAsyncWriterTurtleSerializer<W>),
420}
421
422#[cfg(feature = "async-tokio")]
423impl<W: AsyncWrite + Unpin> TokioAsyncWriterQuadSerializer<W> {
424 /// Serializes a [`QuadRef`]
425 pub async fn serialize_quad<'a>(&mut self, quad: impl Into<QuadRef<'a>>) -> io::Result<()> {
426 match &mut self.inner {
427 TokioAsyncWriterQuadSerializerKind::JsonLd(serializer) => {
428 serializer.serialize_quad(quad).await
429 }
430 TokioAsyncWriterQuadSerializerKind::NQuads(serializer) => {
431 serializer.serialize_quad(quad).await
432 }
433 TokioAsyncWriterQuadSerializerKind::NTriples(serializer) => {
434 serializer.serialize_triple(to_triple(quad)?).await
435 }
436 TokioAsyncWriterQuadSerializerKind::RdfXml(serializer) => {
437 serializer.serialize_triple(to_triple(quad)?).await
438 }
439 TokioAsyncWriterQuadSerializerKind::TriG(serializer) => {
440 serializer.serialize_quad(quad).await
441 }
442 TokioAsyncWriterQuadSerializerKind::Turtle(serializer) => {
443 serializer.serialize_triple(to_triple(quad)?).await
444 }
445 }
446 }
447
448 /// Serializes a [`TripleRef`]
449 pub async fn serialize_triple<'a>(
450 &mut self,
451 triple: impl Into<TripleRef<'a>>,
452 ) -> io::Result<()> {
453 self.serialize_quad(triple.into().in_graph(GraphNameRef::DefaultGraph))
454 .await
455 }
456
457 /// Writes the last bytes of the file
458 ///
459 /// Note that this function does not flush the writer. You need to do that if you are using a [`BufWriter`](io::BufWriter).
460 pub async fn finish(self) -> io::Result<W> {
461 Ok(match self.inner {
462 TokioAsyncWriterQuadSerializerKind::JsonLd(serializer) => serializer.finish().await?,
463 TokioAsyncWriterQuadSerializerKind::NQuads(serializer) => serializer.finish(),
464 TokioAsyncWriterQuadSerializerKind::NTriples(serializer) => serializer.finish(),
465 TokioAsyncWriterQuadSerializerKind::RdfXml(serializer) => serializer.finish().await?,
466 TokioAsyncWriterQuadSerializerKind::TriG(serializer) => serializer.finish().await?,
467 TokioAsyncWriterQuadSerializerKind::Turtle(serializer) => serializer.finish().await?,
468 })
469 }
470}
471
472fn to_triple<'a>(quad: impl Into<QuadRef<'a>>) -> io::Result<TripleRef<'a>> {
473 let quad = quad.into();
474 if quad.graph_name.is_default_graph() {
475 Ok(quad.into())
476 } else {
477 Err(io::Error::new(
478 io::ErrorKind::InvalidInput,
479 "Only quads in the default graph can be serialized to a RDF graph format",
480 ))
481 }
482}