oxttl/
ntriples.rs

1//! A [N-Triples](https://www.w3.org/TR/n-triples/) streaming parser implemented by [`NTriplesParser`]
2//! and a serializer implemented by [`NTriplesSerializer`].
3
4use crate::chunker::get_ntriples_file_chunks;
5use crate::line_formats::NQuadsRecognizer;
6#[cfg(feature = "async-tokio")]
7use crate::toolkit::TokioAsyncReaderIterator;
8use crate::toolkit::{Parser, ReaderIterator, SliceIterator, TurtleParseError, TurtleSyntaxError};
9use crate::MIN_PARALLEL_CHUNK_SIZE;
10use oxrdf::{Triple, TripleRef};
11use std::io::{self, Read, Write};
12#[cfg(feature = "async-tokio")]
13use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
14
15/// A [N-Triples](https://www.w3.org/TR/n-triples/) streaming parser.
16///
17/// Support for [N-Triples-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-triples-star) is available behind the `rdf-star` feature and the [`NTriplesParser::with_quoted_triples`] option.
18///
19/// Count the number of people:
20/// ```
21/// use oxrdf::{NamedNodeRef, vocab::rdf};
22/// use oxttl::NTriplesParser;
23///
24/// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
25/// <http://example.com/foo> <http://schema.org/name> "Foo" .
26/// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
27/// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
28///
29/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
30/// let mut count = 0;
31/// for triple in NTriplesParser::new().for_reader(file.as_ref()) {
32///     let triple = triple?;
33///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
34///         count += 1;
35///     }
36/// }
37/// assert_eq!(2, count);
38/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
39/// ```
40#[derive(Default, Clone)]
41#[must_use]
42pub struct NTriplesParser {
43    unchecked: bool,
44    #[cfg(feature = "rdf-star")]
45    with_quoted_triples: bool,
46}
47
48impl NTriplesParser {
49    /// Builds a new [`NTriplesParser`].
50    #[inline]
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Assumes the file is valid to make parsing faster.
56    ///
57    /// It will skip some validations.
58    ///
59    /// Note that if the file is actually not valid, broken RDF might be emitted by the parser.    ///
60    #[inline]
61    pub fn unchecked(mut self) -> Self {
62        self.unchecked = true;
63        self
64    }
65
66    /// Enables [N-Triples-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-triples-star).
67    #[cfg(feature = "rdf-star")]
68    #[inline]
69    pub fn with_quoted_triples(mut self) -> Self {
70        self.with_quoted_triples = true;
71        self
72    }
73
74    /// Parses a N-Triples file from a [`Read`] implementation.
75    ///
76    /// Count the number of people:
77    /// ```
78    /// use oxrdf::{NamedNodeRef, vocab::rdf};
79    /// use oxttl::NTriplesParser;
80    ///
81    /// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
82    /// <http://example.com/foo> <http://schema.org/name> "Foo" .
83    /// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
84    /// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
85    ///
86    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
87    /// let mut count = 0;
88    /// for triple in NTriplesParser::new().for_reader(file.as_ref()) {
89    ///     let triple = triple?;
90    ///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
91    ///         count += 1;
92    ///     }
93    /// }
94    /// assert_eq!(2, count);
95    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
96    /// ```
97    pub fn for_reader<R: Read>(self, reader: R) -> ReaderNTriplesParser<R> {
98        ReaderNTriplesParser {
99            inner: self.low_level().parser.for_reader(reader),
100        }
101    }
102
103    /// Parses a N-Triples file from a [`AsyncRead`] implementation.
104    ///
105    /// Count the number of people:
106    /// ```
107    /// # #[tokio::main(flavor = "current_thread")]
108    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
109    /// use oxrdf::{NamedNodeRef, vocab::rdf};
110    /// use oxttl::NTriplesParser;
111    ///
112    /// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
113    /// <http://example.com/foo> <http://schema.org/name> "Foo" .
114    /// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
115    /// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
116    ///
117    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
118    /// let mut count = 0;
119    /// let mut parser = NTriplesParser::new().for_tokio_async_reader(file.as_ref());
120    /// while let Some(triple) = parser.next().await {
121    ///     let triple = triple?;
122    ///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
123    ///         count += 1;
124    ///     }
125    /// }
126    /// assert_eq!(2, count);
127    /// # Ok(())
128    /// # }
129    /// ```
130    #[cfg(feature = "async-tokio")]
131    pub fn for_tokio_async_reader<R: AsyncRead + Unpin>(
132        self,
133        reader: R,
134    ) -> TokioAsyncReaderNTriplesParser<R> {
135        TokioAsyncReaderNTriplesParser {
136            inner: self.low_level().parser.for_tokio_async_reader(reader),
137        }
138    }
139
140    /// Parses a N-Triples file from a byte slice.
141    ///
142    /// Count the number of people:
143    /// ```
144    /// use oxrdf::{NamedNodeRef, vocab::rdf};
145    /// use oxttl::NTriplesParser;
146    ///
147    /// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
148    /// <http://example.com/foo> <http://schema.org/name> "Foo" .
149    /// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
150    /// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
151    ///
152    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
153    /// let mut count = 0;
154    /// for triple in NTriplesParser::new().for_slice(file) {
155    ///     let triple = triple?;
156    ///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
157    ///         count += 1;
158    ///     }
159    /// }
160    /// assert_eq!(2, count);
161    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
162    /// ```
163    pub fn for_slice(self, slice: &[u8]) -> SliceNTriplesParser<'_> {
164        SliceNTriplesParser {
165            inner: NQuadsRecognizer::new_parser(
166                slice,
167                true,
168                false,
169                #[cfg(feature = "rdf-star")]
170                self.with_quoted_triples,
171                self.unchecked,
172            )
173            .into_iter(),
174        }
175    }
176
177    /// Creates a vector of iterators that may be used to parse an NTriples document slice in parallel.
178    /// To dynamically specify target_parallelism, use e.g. [`std::thread::available_parallelism`].
179    /// Intended to work on large documents.
180    ///
181    /// Count the number of people:
182    /// ```
183    /// use oxrdf::vocab::rdf;
184    /// use oxrdf::NamedNodeRef;
185    /// use oxttl::{NTriplesParser};
186    /// use rayon::iter::{IntoParallelIterator, ParallelIterator};
187    ///
188    /// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
189    /// <http://example.com/foo> <http://schema.org/name> "Foo" .
190    /// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
191    /// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
192    ///
193    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
194    /// let readers = NTriplesParser::new().split_slice_for_parallel_parsing(file.as_ref(), 2);
195    /// let count = readers
196    ///     .into_par_iter()
197    ///     .map(|reader| {
198    ///         let mut count = 0;
199    ///         for triple in reader {
200    ///             let triple = triple.unwrap();
201    ///             if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
202    ///                 count += 1;
203    ///             }
204    ///         }
205    ///         count
206    ///     })
207    ///     .sum();
208    /// assert_eq!(2, count);
209    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
210    /// ```
211    pub fn split_slice_for_parallel_parsing<'a>(
212        &self,
213        slice: &'a [u8],
214        target_parallelism: usize,
215    ) -> Vec<SliceNTriplesParser<'a>> {
216        let n_chunks = (slice.len() / MIN_PARALLEL_CHUNK_SIZE).clamp(1, target_parallelism);
217        get_ntriples_file_chunks(slice, n_chunks)
218            .into_iter()
219            .map(|(start, end)| self.clone().for_slice(&slice[start..end]))
220            .collect()
221    }
222
223    /// Allows to parse a N-Triples file by using a low-level API.
224    ///
225    /// Count the number of people:
226    /// ```
227    /// use oxrdf::{NamedNodeRef, vocab::rdf};
228    /// use oxttl::NTriplesParser;
229    ///
230    /// let file: [&[u8]; 4] = [
231    ///     b"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
232    ///     b"<http://example.com/foo> <http://schema.org/name> \"Foo\" .\n",
233    ///     b"<http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
234    ///     b"<http://example.com/bar> <http://schema.org/name> \"Bar\" .\n"
235    /// ];
236    ///
237    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
238    /// let mut count = 0;
239    /// let mut parser = NTriplesParser::new().low_level();
240    /// let mut file_chunks = file.iter();
241    /// while !parser.is_end() {
242    ///     // We feed more data to the parser
243    ///     if let Some(chunk) = file_chunks.next() {
244    ///         parser.extend_from_slice(chunk);    
245    ///     } else {
246    ///         parser.end(); // It's finished
247    ///     }
248    ///     // We read as many triples from the parser as possible
249    ///     while let Some(triple) = parser.parse_next() {
250    ///         let triple = triple?;
251    ///         if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
252    ///             count += 1;
253    ///         }
254    ///     }
255    /// }
256    /// assert_eq!(2, count);
257    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
258    /// ```
259    #[allow(clippy::unused_self)]
260    pub fn low_level(self) -> LowLevelNTriplesParser {
261        LowLevelNTriplesParser {
262            parser: NQuadsRecognizer::new_parser(
263                Vec::new(),
264                false,
265                false,
266                #[cfg(feature = "rdf-star")]
267                self.with_quoted_triples,
268                self.unchecked,
269            ),
270        }
271    }
272}
273
274/// Parses a N-Triples file from a [`Read`] implementation.
275///
276/// Can be built using [`NTriplesParser::for_reader`].
277///
278/// Count the number of people:
279/// ```
280/// use oxrdf::{NamedNodeRef, vocab::rdf};
281/// use oxttl::NTriplesParser;
282///
283/// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
284/// <http://example.com/foo> <http://schema.org/name> "Foo" .
285/// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
286/// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
287///
288/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
289/// let mut count = 0;
290/// for triple in NTriplesParser::new().for_reader(file.as_ref()) {
291///     let triple = triple?;
292///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
293///         count += 1;
294///     }
295/// }
296/// assert_eq!(2, count);
297/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
298/// ```
299#[must_use]
300pub struct ReaderNTriplesParser<R: Read> {
301    inner: ReaderIterator<R, NQuadsRecognizer>,
302}
303
304impl<R: Read> Iterator for ReaderNTriplesParser<R> {
305    type Item = Result<Triple, TurtleParseError>;
306
307    fn next(&mut self) -> Option<Self::Item> {
308        Some(self.inner.next()?.map(Into::into))
309    }
310}
311
312/// Parses a N-Triples file from a [`AsyncRead`] implementation.
313///
314/// Can be built using [`NTriplesParser::for_tokio_async_reader`].
315///
316/// Count the number of people:
317/// ```
318/// # #[tokio::main(flavor = "current_thread")]
319/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
320/// use oxrdf::{NamedNodeRef, vocab::rdf};
321/// use oxttl::NTriplesParser;
322///
323/// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
324/// <http://example.com/foo> <http://schema.org/name> "Foo" .
325/// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
326/// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
327///
328/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
329/// let mut count = 0;
330/// let mut parser = NTriplesParser::new().for_tokio_async_reader(file.as_ref());
331/// while let Some(triple) = parser.next().await {
332///     let triple = triple?;
333///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
334///         count += 1;
335///     }
336/// }
337/// assert_eq!(2, count);
338/// # Ok(())
339/// # }
340/// ```
341#[cfg(feature = "async-tokio")]
342#[must_use]
343pub struct TokioAsyncReaderNTriplesParser<R: AsyncRead + Unpin> {
344    inner: TokioAsyncReaderIterator<R, NQuadsRecognizer>,
345}
346
347#[cfg(feature = "async-tokio")]
348impl<R: AsyncRead + Unpin> TokioAsyncReaderNTriplesParser<R> {
349    /// Reads the next triple or returns `None` if the file is finished.
350    pub async fn next(&mut self) -> Option<Result<Triple, TurtleParseError>> {
351        Some(self.inner.next().await?.map(Into::into))
352    }
353}
354
355/// Parses a N-Triples file from a byte slice.
356///
357/// Can be built using [`NTriplesParser::for_slice`].
358///
359/// Count the number of people:
360/// ```
361/// use oxrdf::{NamedNodeRef, vocab::rdf};
362/// use oxttl::NTriplesParser;
363///
364/// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
365/// <http://example.com/foo> <http://schema.org/name> "Foo" .
366/// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
367/// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
368///
369/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
370/// let mut count = 0;
371/// for triple in NTriplesParser::new().for_slice(file) {
372///     let triple = triple?;
373///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
374///         count += 1;
375///     }
376/// }
377/// assert_eq!(2, count);
378/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
379/// ```
380#[must_use]
381pub struct SliceNTriplesParser<'a> {
382    inner: SliceIterator<'a, NQuadsRecognizer>,
383}
384
385impl Iterator for SliceNTriplesParser<'_> {
386    type Item = Result<Triple, TurtleSyntaxError>;
387
388    fn next(&mut self) -> Option<Self::Item> {
389        Some(self.inner.next()?.map(Into::into))
390    }
391}
392
393/// Parses a N-Triples file by using a low-level API.
394///
395/// Can be built using [`NTriplesParser::low_level`].
396///
397/// Count the number of people:
398/// ```
399/// use oxrdf::{NamedNodeRef, vocab::rdf};
400/// use oxttl::NTriplesParser;
401///
402/// let file: [&[u8]; 4] = [
403///     b"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
404///     b"<http://example.com/foo> <http://schema.org/name> \"Foo\" .\n",
405///     b"<http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
406///     b"<http://example.com/bar> <http://schema.org/name> \"Bar\" .\n"
407/// ];
408///
409/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
410/// let mut count = 0;
411/// let mut parser = NTriplesParser::new().low_level();
412/// let mut file_chunks = file.iter();
413/// while !parser.is_end() {
414///     // We feed more data to the parser
415///     if let Some(chunk) = file_chunks.next() {
416///         parser.extend_from_slice(chunk);    
417///     } else {
418///         parser.end(); // It's finished
419///     }
420///     // We read as many triples from the parser as possible
421///     while let Some(triple) = parser.parse_next() {
422///         let triple = triple?;
423///         if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
424///             count += 1;
425///         }
426///     }
427/// }
428/// assert_eq!(2, count);
429/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
430/// ```
431pub struct LowLevelNTriplesParser {
432    parser: Parser<Vec<u8>, NQuadsRecognizer>,
433}
434
435impl LowLevelNTriplesParser {
436    /// Adds some extra bytes to the parser. Should be called when [`parse_next`](Self::parse_next) returns [`None`] and there is still unread data.
437    pub fn extend_from_slice(&mut self, other: &[u8]) {
438        self.parser.extend_from_slice(other)
439    }
440
441    /// Tell the parser that the file is finished.
442    ///
443    /// This triggers the parsing of the final bytes and might lead [`parse_next`](Self::parse_next) to return some extra values.
444    pub fn end(&mut self) {
445        self.parser.end()
446    }
447
448    /// Returns if the parsing is finished i.e. [`end`](Self::end) has been called and [`parse_next`](Self::parse_next) is always going to return `None`.
449    pub fn is_end(&self) -> bool {
450        self.parser.is_end()
451    }
452
453    /// Attempt to parse a new triple from the already provided data.
454    ///
455    /// Returns [`None`] if the parsing is finished or more data is required.
456    /// If it is the case more data should be fed using [`extend_from_slice`](Self::extend_from_slice).
457    pub fn parse_next(&mut self) -> Option<Result<Triple, TurtleSyntaxError>> {
458        Some(self.parser.parse_next()?.map(Into::into))
459    }
460}
461
462/// A [canonical](https://www.w3.org/TR/n-triples/#canonical-ntriples) [N-Triples](https://www.w3.org/TR/n-triples/) serializer.
463///
464/// Support for [N-Triples-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-triples-star) is available behind the `rdf-star` feature.
465///
466/// ```
467/// use oxrdf::{NamedNodeRef, TripleRef};
468/// use oxrdf::vocab::rdf;
469/// use oxttl::NTriplesSerializer;
470///
471/// let mut serializer = NTriplesSerializer::new().for_writer(Vec::new());
472/// serializer.serialize_triple(TripleRef::new(
473///     NamedNodeRef::new("http://example.com#me")?,
474///     rdf::TYPE,
475///     NamedNodeRef::new("http://schema.org/Person")?,
476/// ))?;
477/// assert_eq!(
478///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
479///     serializer.finish().as_slice()
480/// );
481/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
482/// ```
483#[derive(Default, Clone)]
484#[must_use]
485#[allow(clippy::empty_structs_with_brackets)]
486pub struct NTriplesSerializer {}
487
488impl NTriplesSerializer {
489    /// Builds a new [`NTriplesSerializer`].
490    #[inline]
491    pub fn new() -> Self {
492        Self {}
493    }
494
495    /// Writes a N-Triples file to a [`Write`] implementation.
496    ///
497    /// ```
498    /// use oxrdf::{NamedNodeRef, TripleRef};
499    /// use oxrdf::vocab::rdf;
500    /// use oxttl::NTriplesSerializer;
501    ///
502    /// let mut serializer = NTriplesSerializer::new().for_writer(Vec::new());
503    /// serializer.serialize_triple(TripleRef::new(
504    ///     NamedNodeRef::new("http://example.com#me")?,
505    ///     rdf::TYPE,
506    ///     NamedNodeRef::new("http://schema.org/Person")?,
507    /// ))?;
508    /// assert_eq!(
509    ///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
510    ///     serializer.finish().as_slice()
511    /// );
512    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
513    /// ```
514    pub fn for_writer<W: Write>(self, writer: W) -> WriterNTriplesSerializer<W> {
515        WriterNTriplesSerializer {
516            writer,
517            low_level_writer: self.low_level(),
518        }
519    }
520
521    /// Writes a N-Triples file to a [`AsyncWrite`] implementation.
522    ///
523    /// ```
524    /// # #[tokio::main(flavor = "current_thread")]
525    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
526    /// use oxrdf::{NamedNodeRef, TripleRef};
527    /// use oxrdf::vocab::rdf;
528    /// use oxttl::NTriplesSerializer;
529    ///
530    /// let mut serializer = NTriplesSerializer::new().for_tokio_async_writer(Vec::new());
531    /// serializer.serialize_triple(TripleRef::new(
532    ///     NamedNodeRef::new("http://example.com#me")?,
533    ///     rdf::TYPE,
534    ///     NamedNodeRef::new("http://schema.org/Person")?,
535    /// )).await?;
536    /// assert_eq!(
537    ///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
538    ///     serializer.finish().as_slice()
539    /// );
540    /// # Ok(())
541    /// # }
542    /// ```
543    #[cfg(feature = "async-tokio")]
544    pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
545        self,
546        writer: W,
547    ) -> TokioAsyncWriterNTriplesSerializer<W> {
548        TokioAsyncWriterNTriplesSerializer {
549            writer,
550            low_level_writer: self.low_level(),
551            buffer: Vec::new(),
552        }
553    }
554
555    /// Builds a low-level N-Triples writer.
556    ///
557    /// ```
558    /// use oxrdf::{NamedNodeRef, TripleRef};
559    /// use oxrdf::vocab::rdf;
560    /// use oxttl::NTriplesSerializer;
561    ///
562    /// let mut buf = Vec::new();
563    /// let mut serializer = NTriplesSerializer::new().low_level();
564    /// serializer.serialize_triple(TripleRef::new(
565    ///     NamedNodeRef::new("http://example.com#me")?,
566    ///     rdf::TYPE,
567    ///     NamedNodeRef::new("http://schema.org/Person")?,
568    /// ), &mut buf)?;
569    /// assert_eq!(
570    ///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
571    ///     buf.as_slice()
572    /// );
573    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
574    /// ```
575    #[allow(clippy::unused_self)]
576    pub fn low_level(self) -> LowLevelNTriplesSerializer {
577        LowLevelNTriplesSerializer {}
578    }
579}
580
581/// Writes a N-Triples file to a [`Write`] implementation.
582///
583/// Can be built using [`NTriplesSerializer::for_writer`].
584///
585/// ```
586/// use oxrdf::{NamedNodeRef, TripleRef};
587/// use oxrdf::vocab::rdf;
588/// use oxttl::NTriplesSerializer;
589///
590/// let mut serializer = NTriplesSerializer::new().for_writer(Vec::new());
591/// serializer.serialize_triple(TripleRef::new(
592///     NamedNodeRef::new("http://example.com#me")?,
593///     rdf::TYPE,
594///     NamedNodeRef::new("http://schema.org/Person")?,
595/// ))?;
596/// assert_eq!(
597///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
598///     serializer.finish().as_slice()
599/// );
600/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
601/// ```
602#[must_use]
603pub struct WriterNTriplesSerializer<W: Write> {
604    writer: W,
605    low_level_writer: LowLevelNTriplesSerializer,
606}
607
608impl<W: Write> WriterNTriplesSerializer<W> {
609    /// Writes an extra triple.
610    pub fn serialize_triple<'a>(&mut self, t: impl Into<TripleRef<'a>>) -> io::Result<()> {
611        self.low_level_writer.serialize_triple(t, &mut self.writer)
612    }
613
614    /// Ends the write process and returns the underlying [`Write`].
615    pub fn finish(self) -> W {
616        self.writer
617    }
618}
619
620/// Writes a N-Triples file to a [`AsyncWrite`] implementation.
621///
622/// Can be built using [`NTriplesSerializer::for_tokio_async_writer`].
623///
624/// ```
625/// # #[tokio::main(flavor = "current_thread")]
626/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
627/// use oxrdf::{NamedNodeRef, TripleRef};
628/// use oxrdf::vocab::rdf;
629/// use oxttl::NTriplesSerializer;
630///
631/// let mut serializer = NTriplesSerializer::new().for_tokio_async_writer(Vec::new());
632/// serializer.serialize_triple(TripleRef::new(
633///     NamedNodeRef::new("http://example.com#me")?,
634///     rdf::TYPE,
635///     NamedNodeRef::new("http://schema.org/Person")?
636/// )).await?;
637/// assert_eq!(
638///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
639///     serializer.finish().as_slice()
640/// );
641/// # Ok(())
642/// # }
643/// ```
644#[cfg(feature = "async-tokio")]
645#[must_use]
646pub struct TokioAsyncWriterNTriplesSerializer<W: AsyncWrite + Unpin> {
647    writer: W,
648    low_level_writer: LowLevelNTriplesSerializer,
649    buffer: Vec<u8>,
650}
651
652#[cfg(feature = "async-tokio")]
653impl<W: AsyncWrite + Unpin> TokioAsyncWriterNTriplesSerializer<W> {
654    /// Writes an extra triple.
655    pub async fn serialize_triple<'a>(&mut self, t: impl Into<TripleRef<'a>>) -> io::Result<()> {
656        self.low_level_writer
657            .serialize_triple(t, &mut self.buffer)?;
658        self.writer.write_all(&self.buffer).await?;
659        self.buffer.clear();
660        Ok(())
661    }
662
663    /// Ends the write process and returns the underlying [`Write`].
664    pub fn finish(self) -> W {
665        self.writer
666    }
667}
668
669/// Writes a N-Triples file by using a low-level API.
670///
671/// Can be built using [`NTriplesSerializer::low_level`].
672///
673/// ```
674/// use oxrdf::{NamedNodeRef, TripleRef};
675/// use oxrdf::vocab::rdf;
676/// use oxttl::NTriplesSerializer;
677///
678/// let mut buf = Vec::new();
679/// let mut serializer = NTriplesSerializer::new().low_level();
680/// serializer.serialize_triple(TripleRef::new(
681///     NamedNodeRef::new("http://example.com#me")?,
682///     rdf::TYPE,
683///     NamedNodeRef::new("http://schema.org/Person")?,
684/// ), &mut buf)?;
685/// assert_eq!(
686///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
687///     buf.as_slice()
688/// );
689/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
690/// ```
691#[allow(clippy::empty_structs_with_brackets)]
692pub struct LowLevelNTriplesSerializer {}
693
694impl LowLevelNTriplesSerializer {
695    /// Writes an extra triple.
696    #[allow(clippy::unused_self)]
697    pub fn serialize_triple<'a>(
698        &mut self,
699        t: impl Into<TripleRef<'a>>,
700        mut writer: impl Write,
701    ) -> io::Result<()> {
702        writeln!(writer, "{} .", t.into())
703    }
704}
705
706#[cfg(test)]
707mod tests {
708    use super::*;
709    use oxrdf::{Literal, NamedNode};
710
711    #[test]
712    fn unchecked_parsing() {
713        let triples = NTriplesParser::new()
714            .unchecked()
715            .for_reader(r#"<foo> <bar> "baz"@toolonglangtag ."#.as_bytes())
716            .collect::<Result<Vec<_>, _>>()
717            .unwrap();
718        assert_eq!(
719            triples,
720            [Triple::new(
721                NamedNode::new_unchecked("foo"),
722                NamedNode::new_unchecked("bar"),
723                Literal::new_language_tagged_literal_unchecked("baz", "toolonglangtag"),
724            )]
725        )
726    }
727}