oxttl/nquads.rs
1//! A [N-Quads](https://www.w3.org/TR/n-quads/) streaming parser implemented by [`NQuadsParser`]
2//! and a serializer implemented by [`NQuadsSerializer`].
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::{Quad, QuadRef};
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-Quads](https://www.w3.org/TR/n-quads/) streaming parser.
18///
19/// Count the number of people:
20/// ```
21/// use oxrdf::{NamedNodeRef, vocab::rdf};
22/// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_reader(file.as_bytes()) {
32/// let quad = quad?;
33/// if quad.predicate == rdf::TYPE && quad.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 NQuadsParser {
43 lenient: bool,
44}
45
46impl NQuadsParser {
47 /// Builds a new [`NQuadsParser`].
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-Quads file from a [`Read`] implementation.
71 ///
72 /// Count the number of people:
73 /// ```
74 /// use oxrdf::{NamedNodeRef, vocab::rdf};
75 /// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_reader(file.as_bytes()) {
85 /// let quad = quad?;
86 /// if quad.predicate == rdf::TYPE && quad.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) -> ReaderNQuadsParser<R> {
94 ReaderNQuadsParser {
95 inner: self.low_level().parser.for_reader(reader),
96 }
97 }
98
99 /// Parses a N-Quads 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::NQuadsParser;
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 = NQuadsParser::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 ) -> TokioAsyncReaderNQuadsParser<R> {
131 TokioAsyncReaderNQuadsParser {
132 inner: self.low_level().parser.for_tokio_async_reader(reader),
133 }
134 }
135
136 /// Parses a N-Quads file from a byte slice.
137 ///
138 /// Count the number of people:
139 /// ```
140 /// use oxrdf::{NamedNodeRef, vocab::rdf};
141 /// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_slice(file) {
151 /// let quad = quad?;
152 /// if quad.predicate == rdf::TYPE && quad.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)) -> SliceNQuadsParser<'_> {
160 SliceNQuadsParser {
161 inner: NQuadsRecognizer::new_parser(slice.as_ref(), true, true, self.lenient)
162 .into_iter(),
163 }
164 }
165
166 /// Creates a vector of parsers that may be used to parse an NQuads 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::NQuadsParser;
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 = NQuadsParser::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 quad in reader {
189 /// let quad = quad.unwrap();
190 /// if quad.predicate == rdf::TYPE && quad.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<SliceNQuadsParser<'_>> {
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 NQuads 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<ReaderNQuadsParser<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-Quads file by using a low-level API.
271 ///
272 /// Count the number of people:
273 /// ```
274 /// use oxrdf::{NamedNodeRef, vocab::rdf};
275 /// use oxttl::NQuadsParser;
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 = NQuadsParser::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 quads from the parser as possible
296 /// while let Some(quad) = parser.parse_next() {
297 /// let quad = quad?;
298 /// if quad.predicate == rdf::TYPE && quad.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) -> LowLevelNQuadsParser {
307 LowLevelNQuadsParser {
308 parser: NQuadsRecognizer::new_parser(Vec::new(), false, true, self.lenient),
309 }
310 }
311}
312
313/// Parses a N-Quads file from a [`Read`] implementation.
314///
315/// Can be built using [`NQuadsParser::for_reader`].
316///
317/// Count the number of people:
318/// ```
319/// use oxrdf::{NamedNodeRef, vocab::rdf};
320/// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_reader(file.as_bytes()) {
330/// let quad = quad?;
331/// if quad.predicate == rdf::TYPE && quad.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 ReaderNQuadsParser<R: Read> {
340 inner: ReaderIterator<R, NQuadsRecognizer>,
341}
342
343impl<R: Read> Iterator for ReaderNQuadsParser<R> {
344 type Item = Result<Quad, TurtleParseError>;
345
346 fn next(&mut self) -> Option<Self::Item> {
347 self.inner.next()
348 }
349}
350
351/// Parses a N-Quads file from a [`AsyncRead`] implementation.
352///
353/// Can be built using [`NQuadsParser::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::NQuadsParser;
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 = NQuadsParser::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 TokioAsyncReaderNQuadsParser<R: AsyncRead + Unpin> {
383 inner: TokioAsyncReaderIterator<R, NQuadsRecognizer>,
384}
385
386#[cfg(feature = "async-tokio")]
387impl<R: AsyncRead + Unpin> TokioAsyncReaderNQuadsParser<R> {
388 /// Reads the next triple or returns `None` if the file is finished.
389 pub async fn next(&mut self) -> Option<Result<Quad, TurtleParseError>> {
390 self.inner.next().await
391 }
392}
393
394/// Parses an N-Quads file from a byte slice.
395///
396/// Can be built using [`NQuadsParser::for_slice`].
397///
398/// Count the number of people:
399/// ```
400/// use oxrdf::{NamedNodeRef, vocab::rdf};
401/// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_slice(file) {
411/// let quad = quad?;
412/// if quad.predicate == rdf::TYPE && quad.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 SliceNQuadsParser<'a> {
421 inner: SliceIterator<'a, NQuadsRecognizer>,
422}
423
424impl Iterator for SliceNQuadsParser<'_> {
425 type Item = Result<Quad, TurtleSyntaxError>;
426
427 fn next(&mut self) -> Option<Self::Item> {
428 self.inner.next()
429 }
430}
431
432/// Parses a N-Quads file by using a low-level API.
433///
434/// Can be built using [`NQuadsParser::low_level`].
435///
436/// Count the number of people:
437/// ```
438/// use oxrdf::{NamedNodeRef, vocab::rdf};
439/// use oxttl::NQuadsParser;
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 = NQuadsParser::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 quads from the parser as possible
460/// while let Some(quad) = parser.parse_next() {
461/// let quad = quad?;
462/// if quad.predicate == rdf::TYPE && quad.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 LowLevelNQuadsParser {
471 parser: Parser<Vec<u8>, NQuadsRecognizer>,
472}
473
474impl LowLevelNQuadsParser {
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 quad 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<Quad, TurtleSyntaxError>> {
497 self.parser.parse_next()
498 }
499}
500
501/// A [N-Quads](https://www.w3.org/TR/n-quads/) serializer.
502///
503/// ```
504/// use oxrdf::{NamedNodeRef, QuadRef};
505/// use oxrdf::vocab::rdf;
506/// use oxttl::NQuadsSerializer;
507///
508/// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
509/// serializer.serialize_quad(QuadRef::new(
510/// NamedNodeRef::new("http://example.com#me")?,
511/// rdf::TYPE,
512/// NamedNodeRef::new("http://schema.org/Person")?,
513/// NamedNodeRef::new("http://example.com")?,
514/// ))?;
515/// assert_eq!(
516/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
517/// serializer.finish().as_slice()
518/// );
519/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
520/// ```
521#[derive(Default, Clone)]
522#[must_use]
523#[expect(clippy::empty_structs_with_brackets)]
524pub struct NQuadsSerializer {}
525
526impl NQuadsSerializer {
527 /// Builds a new [`NQuadsSerializer`].
528 #[inline]
529 pub fn new() -> Self {
530 Self {}
531 }
532
533 /// Writes a N-Quads file to a [`Write`] implementation.
534 ///
535 /// ```
536 /// use oxrdf::{NamedNodeRef, QuadRef};
537 /// use oxrdf::vocab::rdf;
538 /// use oxttl::NQuadsSerializer;
539 ///
540 /// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
541 /// serializer.serialize_quad(QuadRef::new(
542 /// NamedNodeRef::new("http://example.com#me")?,
543 /// rdf::TYPE,
544 /// NamedNodeRef::new("http://schema.org/Person")?,
545 /// NamedNodeRef::new("http://example.com")?,
546 /// ))?;
547 /// assert_eq!(
548 /// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
549 /// serializer.finish().as_slice()
550 /// );
551 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
552 /// ```
553 pub fn for_writer<W: Write>(self, writer: W) -> WriterNQuadsSerializer<W> {
554 WriterNQuadsSerializer {
555 writer,
556 low_level_writer: self.low_level(),
557 }
558 }
559
560 /// Writes a N-Quads file to a [`AsyncWrite`] implementation.
561 ///
562 /// ```
563 /// # #[tokio::main(flavor = "current_thread")]
564 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
565 /// use oxrdf::{NamedNodeRef, QuadRef};
566 /// use oxttl::NQuadsSerializer;
567 /// use oxrdf::vocab::rdf;
568 ///
569 /// let mut serializer = NQuadsSerializer::new().for_tokio_async_writer(Vec::new());
570 /// serializer.serialize_quad(QuadRef::new(
571 /// NamedNodeRef::new("http://example.com#me")?,
572 /// rdf::TYPE,
573 /// NamedNodeRef::new("http://schema.org/Person")?,
574 /// NamedNodeRef::new("http://example.com")?,
575 /// )).await?;
576 /// assert_eq!(
577 /// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
578 /// serializer.finish().as_slice()
579 /// );
580 /// # Ok(())
581 /// # }
582 /// ```
583 #[cfg(feature = "async-tokio")]
584 pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
585 self,
586 writer: W,
587 ) -> TokioAsyncWriterNQuadsSerializer<W> {
588 TokioAsyncWriterNQuadsSerializer {
589 writer,
590 low_level_writer: self.low_level(),
591 buffer: Vec::new(),
592 }
593 }
594
595 /// Builds a low-level N-Quads writer.
596 ///
597 /// ```
598 /// use oxrdf::{NamedNodeRef, QuadRef};
599 /// use oxrdf::vocab::rdf;
600 /// use oxttl::NQuadsSerializer;
601 ///
602 /// let mut buf = Vec::new();
603 /// let mut serializer = NQuadsSerializer::new().low_level();
604 /// serializer.serialize_quad(QuadRef::new(
605 /// NamedNodeRef::new("http://example.com#me")?,
606 /// rdf::TYPE,
607 /// NamedNodeRef::new("http://schema.org/Person")?,
608 /// NamedNodeRef::new("http://example.com")?,
609 /// ), &mut buf)?;
610 /// assert_eq!(
611 /// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
612 /// buf.as_slice()
613 /// );
614 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
615 /// ```
616 #[expect(clippy::unused_self)]
617 pub fn low_level(self) -> LowLevelNQuadsSerializer {
618 LowLevelNQuadsSerializer {}
619 }
620}
621
622/// Writes a N-Quads file to a [`Write`] implementation.
623///
624/// Can be built using [`NQuadsSerializer::for_writer`].
625///
626/// ```
627/// use oxrdf::{NamedNodeRef, QuadRef};
628/// use oxrdf::vocab::rdf;
629/// use oxttl::NQuadsSerializer;
630///
631/// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
632/// serializer.serialize_quad(QuadRef::new(
633/// NamedNodeRef::new("http://example.com#me")?,
634/// rdf::TYPE,
635/// NamedNodeRef::new("http://schema.org/Person")?,
636/// NamedNodeRef::new("http://example.com")?,
637/// ))?;
638/// assert_eq!(
639/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
640/// serializer.finish().as_slice()
641/// );
642/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
643/// ```
644#[must_use]
645pub struct WriterNQuadsSerializer<W: Write> {
646 writer: W,
647 low_level_writer: LowLevelNQuadsSerializer,
648}
649
650impl<W: Write> WriterNQuadsSerializer<W> {
651 /// Writes an extra quad.
652 pub fn serialize_quad<'a>(&mut self, q: impl Into<QuadRef<'a>>) -> io::Result<()> {
653 self.low_level_writer.serialize_quad(q, &mut self.writer)
654 }
655
656 /// Ends the write process and returns the underlying [`Write`].
657 pub fn finish(self) -> W {
658 self.writer
659 }
660}
661
662/// Writes a N-Quads file to a [`AsyncWrite`] implementation.
663///
664/// Can be built using [`NQuadsSerializer::for_tokio_async_writer`].
665///
666/// ```
667/// # #[tokio::main(flavor = "current_thread")]
668/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
669/// use oxrdf::{NamedNodeRef, QuadRef};
670/// use oxrdf::vocab::rdf;
671/// use oxttl::NQuadsSerializer;
672///
673/// let mut serializer = NQuadsSerializer::new().for_tokio_async_writer(Vec::new());
674/// serializer.serialize_quad(QuadRef::new(
675/// NamedNodeRef::new("http://example.com#me")?,
676/// rdf::TYPE,
677/// NamedNodeRef::new("http://schema.org/Person")?,
678/// NamedNodeRef::new("http://example.com")?,
679/// )).await?;
680/// assert_eq!(
681/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
682/// serializer.finish().as_slice()
683/// );
684/// # Ok(())
685/// # }
686/// ```
687#[cfg(feature = "async-tokio")]
688#[must_use]
689pub struct TokioAsyncWriterNQuadsSerializer<W: AsyncWrite + Unpin> {
690 writer: W,
691 low_level_writer: LowLevelNQuadsSerializer,
692 buffer: Vec<u8>,
693}
694
695#[cfg(feature = "async-tokio")]
696impl<W: AsyncWrite + Unpin> TokioAsyncWriterNQuadsSerializer<W> {
697 /// Writes an extra quad.
698 pub async fn serialize_quad<'a>(&mut self, q: impl Into<QuadRef<'a>>) -> io::Result<()> {
699 self.low_level_writer.serialize_quad(q, &mut self.buffer)?;
700 self.writer.write_all(&self.buffer).await?;
701 self.buffer.clear();
702 Ok(())
703 }
704
705 /// Ends the write process and returns the underlying [`Write`].
706 pub fn finish(self) -> W {
707 self.writer
708 }
709}
710
711/// Writes a N-Quads file by using a low-level API.
712///
713/// Can be built using [`NQuadsSerializer::low_level`].
714///
715/// ```
716/// use oxrdf::{NamedNodeRef, QuadRef};
717/// use oxrdf::vocab::rdf;
718/// use oxttl::NQuadsSerializer;
719///
720/// let mut buf = Vec::new();
721/// let mut serializer = NQuadsSerializer::new().low_level();
722/// serializer.serialize_quad(QuadRef::new(
723/// NamedNodeRef::new("http://example.com#me")?,
724/// rdf::TYPE,
725/// NamedNodeRef::new("http://schema.org/Person")?,
726/// NamedNodeRef::new("http://example.com")?,
727/// ), &mut buf)?;
728/// assert_eq!(
729/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
730/// buf.as_slice()
731/// );
732/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
733/// ```
734#[expect(clippy::empty_structs_with_brackets)]
735pub struct LowLevelNQuadsSerializer {}
736
737impl LowLevelNQuadsSerializer {
738 /// Writes an extra quad.
739 #[expect(clippy::unused_self)]
740 pub fn serialize_quad<'a>(
741 &mut self,
742 q: impl Into<QuadRef<'a>>,
743 mut writer: impl Write,
744 ) -> io::Result<()> {
745 writeln!(writer, "{} .", q.into())
746 }
747}