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}