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::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::{Quad, QuadRef};
11use std::io::{self, Read, Write};
12#[cfg(feature = "async-tokio")]
13use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
14
15/// A [N-Quads](https://www.w3.org/TR/n-quads/) streaming parser.
16///
17/// Support for [N-Quads-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-quads-star) is available behind the `rdf-star` feature and the [`NQuadsParser::with_quoted_triples`] option.
18///
19/// Count the number of people:
20/// ```
21/// use oxrdf::{NamedNodeRef, vocab::rdf};
22/// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_reader(file.as_ref()) {
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 unchecked: bool,
44 #[cfg(feature = "rdf-star")]
45 with_quoted_triples: bool,
46}
47
48impl NQuadsParser {
49 /// Builds a new [`NQuadsParser`].
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-Quads-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-quads-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-Quads file from a [`Read`] implementation.
75 ///
76 /// Count the number of people:
77 /// ```
78 /// use oxrdf::{NamedNodeRef, vocab::rdf};
79 /// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_reader(file.as_ref()) {
89 /// let quad = quad?;
90 /// if quad.predicate == rdf::TYPE && quad.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) -> ReaderNQuadsParser<R> {
98 ReaderNQuadsParser {
99 inner: self.low_level().parser.for_reader(reader),
100 }
101 }
102
103 /// Parses a N-Quads 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::NQuadsParser;
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 = NQuadsParser::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 ) -> TokioAsyncReaderNQuadsParser<R> {
135 TokioAsyncReaderNQuadsParser {
136 inner: self.low_level().parser.for_tokio_async_reader(reader),
137 }
138 }
139
140 /// Parses a N-Quads file from a byte slice.
141 ///
142 /// Count the number of people:
143 /// ```
144 /// use oxrdf::{NamedNodeRef, vocab::rdf};
145 /// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_slice(file) {
155 /// let quad = quad?;
156 /// if quad.predicate == rdf::TYPE && quad.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]) -> SliceNQuadsParser<'_> {
164 SliceNQuadsParser {
165 inner: NQuadsRecognizer::new_parser(
166 slice,
167 true,
168 true,
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 NQuads 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::NQuadsParser;
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 = NQuadsParser::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 quad in reader {
200 /// let quad = quad.unwrap();
201 /// if quad.predicate == rdf::TYPE && quad.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<SliceNQuadsParser<'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-Quads file by using a low-level API.
224 ///
225 /// Count the number of people:
226 /// ```
227 /// use oxrdf::{NamedNodeRef, vocab::rdf};
228 /// use oxttl::NQuadsParser;
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 = NQuadsParser::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 quads from the parser as possible
249 /// while let Some(quad) = parser.parse_next() {
250 /// let quad = quad?;
251 /// if quad.predicate == rdf::TYPE && quad.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) -> LowLevelNQuadsParser {
261 LowLevelNQuadsParser {
262 parser: NQuadsRecognizer::new_parser(
263 Vec::new(),
264 false,
265 true,
266 #[cfg(feature = "rdf-star")]
267 self.with_quoted_triples,
268 self.unchecked,
269 ),
270 }
271 }
272}
273
274/// Parses a N-Quads file from a [`Read`] implementation.
275///
276/// Can be built using [`NQuadsParser::for_reader`].
277///
278/// Count the number of people:
279/// ```
280/// use oxrdf::{NamedNodeRef, vocab::rdf};
281/// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_reader(file.as_ref()) {
291/// let quad = quad?;
292/// if quad.predicate == rdf::TYPE && quad.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 ReaderNQuadsParser<R: Read> {
301 inner: ReaderIterator<R, NQuadsRecognizer>,
302}
303
304impl<R: Read> Iterator for ReaderNQuadsParser<R> {
305 type Item = Result<Quad, TurtleParseError>;
306
307 fn next(&mut self) -> Option<Self::Item> {
308 self.inner.next()
309 }
310}
311
312/// Parses a N-Quads file from a [`AsyncRead`] implementation.
313///
314/// Can be built using [`NQuadsParser::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::NQuadsParser;
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 = NQuadsParser::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 TokioAsyncReaderNQuadsParser<R: AsyncRead + Unpin> {
344 inner: TokioAsyncReaderIterator<R, NQuadsRecognizer>,
345}
346
347#[cfg(feature = "async-tokio")]
348impl<R: AsyncRead + Unpin> TokioAsyncReaderNQuadsParser<R> {
349 /// Reads the next triple or returns `None` if the file is finished.
350 pub async fn next(&mut self) -> Option<Result<Quad, TurtleParseError>> {
351 self.inner.next().await
352 }
353}
354
355/// Parses a N-Quads file from a byte slice.
356///
357/// Can be built using [`NQuadsParser::for_slice`].
358///
359/// Count the number of people:
360/// ```
361/// use oxrdf::{NamedNodeRef, vocab::rdf};
362/// use oxttl::NQuadsParser;
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 quad in NQuadsParser::new().for_slice(file) {
372/// let quad = quad?;
373/// if quad.predicate == rdf::TYPE && quad.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 SliceNQuadsParser<'a> {
382 inner: SliceIterator<'a, NQuadsRecognizer>,
383}
384
385impl Iterator for SliceNQuadsParser<'_> {
386 type Item = Result<Quad, TurtleSyntaxError>;
387
388 fn next(&mut self) -> Option<Self::Item> {
389 self.inner.next()
390 }
391}
392
393/// Parses a N-Quads file by using a low-level API.
394///
395/// Can be built using [`NQuadsParser::low_level`].
396///
397/// Count the number of people:
398/// ```
399/// use oxrdf::{NamedNodeRef, vocab::rdf};
400/// use oxttl::NQuadsParser;
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 = NQuadsParser::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 quads from the parser as possible
421/// while let Some(quad) = parser.parse_next() {
422/// let quad = quad?;
423/// if quad.predicate == rdf::TYPE && quad.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 LowLevelNQuadsParser {
432 parser: Parser<Vec<u8>, NQuadsRecognizer>,
433}
434
435impl LowLevelNQuadsParser {
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 quad 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<Quad, TurtleSyntaxError>> {
458 self.parser.parse_next()
459 }
460}
461
462/// A [N-Quads](https://www.w3.org/TR/n-quads/) serializer.
463///
464/// Support for [N-Quads-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-quads-star) is available behind the `rdf-star` feature.
465///
466/// ```
467/// use oxrdf::{NamedNodeRef, QuadRef};
468/// use oxrdf::vocab::rdf;
469/// use oxttl::NQuadsSerializer;
470///
471/// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
472/// serializer.serialize_quad(QuadRef::new(
473/// NamedNodeRef::new("http://example.com#me")?,
474/// rdf::TYPE,
475/// NamedNodeRef::new("http://schema.org/Person")?,
476/// NamedNodeRef::new("http://example.com")?,
477/// ))?;
478/// assert_eq!(
479/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
480/// serializer.finish().as_slice()
481/// );
482/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
483/// ```
484#[derive(Default, Clone)]
485#[must_use]
486#[allow(clippy::empty_structs_with_brackets)]
487pub struct NQuadsSerializer {}
488
489impl NQuadsSerializer {
490 /// Builds a new [`NQuadsSerializer`].
491 #[inline]
492 pub fn new() -> Self {
493 Self {}
494 }
495
496 /// Writes a N-Quads file to a [`Write`] implementation.
497 ///
498 /// ```
499 /// use oxrdf::{NamedNodeRef, QuadRef};
500 /// use oxrdf::vocab::rdf;
501 /// use oxttl::NQuadsSerializer;
502 ///
503 /// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
504 /// serializer.serialize_quad(QuadRef::new(
505 /// NamedNodeRef::new("http://example.com#me")?,
506 /// rdf::TYPE,
507 /// NamedNodeRef::new("http://schema.org/Person")?,
508 /// NamedNodeRef::new("http://example.com")?,
509 /// ))?;
510 /// assert_eq!(
511 /// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
512 /// serializer.finish().as_slice()
513 /// );
514 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
515 /// ```
516 pub fn for_writer<W: Write>(self, writer: W) -> WriterNQuadsSerializer<W> {
517 WriterNQuadsSerializer {
518 writer,
519 low_level_writer: self.low_level(),
520 }
521 }
522
523 /// Writes a N-Quads file to a [`AsyncWrite`] implementation.
524 ///
525 /// ```
526 /// # #[tokio::main(flavor = "current_thread")]
527 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
528 /// use oxrdf::{NamedNodeRef, QuadRef};
529 /// use oxttl::NQuadsSerializer;
530 /// use oxrdf::vocab::rdf;
531 ///
532 /// let mut serializer = NQuadsSerializer::new().for_tokio_async_writer(Vec::new());
533 /// serializer.serialize_quad(QuadRef::new(
534 /// NamedNodeRef::new("http://example.com#me")?,
535 /// rdf::TYPE,
536 /// NamedNodeRef::new("http://schema.org/Person")?,
537 /// NamedNodeRef::new("http://example.com")?,
538 /// )).await?;
539 /// assert_eq!(
540 /// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
541 /// serializer.finish().as_slice()
542 /// );
543 /// # Ok(())
544 /// # }
545 /// ```
546 #[cfg(feature = "async-tokio")]
547 pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
548 self,
549 writer: W,
550 ) -> TokioAsyncWriterNQuadsSerializer<W> {
551 TokioAsyncWriterNQuadsSerializer {
552 writer,
553 low_level_writer: self.low_level(),
554 buffer: Vec::new(),
555 }
556 }
557
558 /// Builds a low-level N-Quads writer.
559 ///
560 /// ```
561 /// use oxrdf::{NamedNodeRef, QuadRef};
562 /// use oxrdf::vocab::rdf;
563 /// use oxttl::NQuadsSerializer;
564 ///
565 /// let mut buf = Vec::new();
566 /// let mut serializer = NQuadsSerializer::new().low_level();
567 /// serializer.serialize_quad(QuadRef::new(
568 /// NamedNodeRef::new("http://example.com#me")?,
569 /// rdf::TYPE,
570 /// NamedNodeRef::new("http://schema.org/Person")?,
571 /// NamedNodeRef::new("http://example.com")?,
572 /// ), &mut buf)?;
573 /// assert_eq!(
574 /// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
575 /// buf.as_slice()
576 /// );
577 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
578 /// ```
579 #[allow(clippy::unused_self)]
580 pub fn low_level(self) -> LowLevelNQuadsSerializer {
581 LowLevelNQuadsSerializer {}
582 }
583}
584
585/// Writes a N-Quads file to a [`Write`] implementation.
586///
587/// Can be built using [`NQuadsSerializer::for_writer`].
588///
589/// ```
590/// use oxrdf::{NamedNodeRef, QuadRef};
591/// use oxrdf::vocab::rdf;
592/// use oxttl::NQuadsSerializer;
593///
594/// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
595/// serializer.serialize_quad(QuadRef::new(
596/// NamedNodeRef::new("http://example.com#me")?,
597/// rdf::TYPE,
598/// NamedNodeRef::new("http://schema.org/Person")?,
599/// NamedNodeRef::new("http://example.com")?,
600/// ))?;
601/// assert_eq!(
602/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
603/// serializer.finish().as_slice()
604/// );
605/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
606/// ```
607#[must_use]
608pub struct WriterNQuadsSerializer<W: Write> {
609 writer: W,
610 low_level_writer: LowLevelNQuadsSerializer,
611}
612
613impl<W: Write> WriterNQuadsSerializer<W> {
614 /// Writes an extra quad.
615 pub fn serialize_quad<'a>(&mut self, q: impl Into<QuadRef<'a>>) -> io::Result<()> {
616 self.low_level_writer.serialize_quad(q, &mut self.writer)
617 }
618
619 /// Ends the write process and returns the underlying [`Write`].
620 pub fn finish(self) -> W {
621 self.writer
622 }
623}
624
625/// Writes a N-Quads file to a [`AsyncWrite`] implementation.
626///
627/// Can be built using [`NQuadsSerializer::for_tokio_async_writer`].
628///
629/// ```
630/// # #[tokio::main(flavor = "current_thread")]
631/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
632/// use oxrdf::{NamedNodeRef, QuadRef};
633/// use oxrdf::vocab::rdf;
634/// use oxttl::NQuadsSerializer;
635///
636/// let mut serializer = NQuadsSerializer::new().for_tokio_async_writer(Vec::new());
637/// serializer.serialize_quad(QuadRef::new(
638/// NamedNodeRef::new("http://example.com#me")?,
639/// rdf::TYPE,
640/// NamedNodeRef::new("http://schema.org/Person")?,
641/// NamedNodeRef::new("http://example.com")?,
642/// )).await?;
643/// assert_eq!(
644/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
645/// serializer.finish().as_slice()
646/// );
647/// # Ok(())
648/// # }
649/// ```
650#[cfg(feature = "async-tokio")]
651#[must_use]
652pub struct TokioAsyncWriterNQuadsSerializer<W: AsyncWrite + Unpin> {
653 writer: W,
654 low_level_writer: LowLevelNQuadsSerializer,
655 buffer: Vec<u8>,
656}
657
658#[cfg(feature = "async-tokio")]
659impl<W: AsyncWrite + Unpin> TokioAsyncWriterNQuadsSerializer<W> {
660 /// Writes an extra quad.
661 pub async fn serialize_quad<'a>(&mut self, q: impl Into<QuadRef<'a>>) -> io::Result<()> {
662 self.low_level_writer.serialize_quad(q, &mut self.buffer)?;
663 self.writer.write_all(&self.buffer).await?;
664 self.buffer.clear();
665 Ok(())
666 }
667
668 /// Ends the write process and returns the underlying [`Write`].
669 pub fn finish(self) -> W {
670 self.writer
671 }
672}
673
674/// Writes a N-Quads file by using a low-level API.
675///
676/// Can be built using [`NQuadsSerializer::low_level`].
677///
678/// ```
679/// use oxrdf::{NamedNodeRef, QuadRef};
680/// use oxrdf::vocab::rdf;
681/// use oxttl::NQuadsSerializer;
682///
683/// let mut buf = Vec::new();
684/// let mut serializer = NQuadsSerializer::new().low_level();
685/// serializer.serialize_quad(QuadRef::new(
686/// NamedNodeRef::new("http://example.com#me")?,
687/// rdf::TYPE,
688/// NamedNodeRef::new("http://schema.org/Person")?,
689/// NamedNodeRef::new("http://example.com")?,
690/// ), &mut buf)?;
691/// assert_eq!(
692/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
693/// buf.as_slice()
694/// );
695/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
696/// ```
697#[allow(clippy::empty_structs_with_brackets)]
698pub struct LowLevelNQuadsSerializer {}
699
700impl LowLevelNQuadsSerializer {
701 /// Writes an extra quad.
702 #[allow(clippy::unused_self)]
703 pub fn serialize_quad<'a>(
704 &mut self,
705 q: impl Into<QuadRef<'a>>,
706 mut writer: impl Write,
707 ) -> io::Result<()> {
708 writeln!(writer, "{} .", q.into())
709 }
710}