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