oxttl/turtle.rs
1//! A [Turtle](https://www.w3.org/TR/turtle/) streaming parser implemented by [`TurtleParser`]
2//! and a serializer implemented by [`TurtleSerializer`].
3
4use crate::MIN_PARALLEL_CHUNK_SIZE;
5use crate::chunker::get_turtle_slice_chunks;
6use crate::terse::TriGRecognizer;
7#[cfg(feature = "async-tokio")]
8use crate::toolkit::TokioAsyncReaderIterator;
9use crate::toolkit::{Parser, ReaderIterator, SliceIterator, TurtleParseError, TurtleSyntaxError};
10#[cfg(feature = "async-tokio")]
11use crate::trig::TokioAsyncWriterTriGSerializer;
12use crate::trig::{LowLevelTriGSerializer, TriGSerializer, WriterTriGSerializer};
13use oxiri::{Iri, IriParseError};
14use oxrdf::{GraphNameRef, Triple, TripleRef};
15use std::collections::HashMap;
16use std::collections::hash_map::Iter;
17use std::io::{self, Read, Write};
18#[cfg(feature = "async-tokio")]
19use tokio::io::{AsyncRead, AsyncWrite};
20
21/// A [Turtle](https://www.w3.org/TR/turtle/) streaming parser.
22///
23/// Count the number of people:
24/// ```
25/// use oxrdf::NamedNodeRef;
26/// use oxrdf::vocab::rdf;
27/// use oxttl::TurtleParser;
28///
29/// let file = r#"@base <http://example.com/> .
30/// @prefix schema: <http://schema.org/> .
31/// <foo> a schema:Person ;
32/// schema:name "Foo" .
33/// <bar> a schema:Person ;
34/// schema:name "Bar" ."#;
35///
36/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
37/// let mut count = 0;
38/// for triple in TurtleParser::new().for_reader(file.as_bytes()) {
39/// let triple = triple?;
40/// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
41/// count += 1;
42/// }
43/// }
44/// assert_eq!(2, count);
45/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
46/// ```
47#[derive(Default, Clone)]
48#[must_use]
49pub struct TurtleParser {
50 lenient: bool,
51 base: Option<Iri<String>>,
52 prefixes: HashMap<String, Iri<String>>,
53}
54
55impl TurtleParser {
56 /// Builds a new [`TurtleParser`].
57 #[inline]
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 /// Assumes the file is valid to make parsing faster.
63 ///
64 /// It will skip some validations.
65 ///
66 /// Note that if the file is actually not valid, the parser might emit broken RDF.
67 #[inline]
68 pub fn lenient(mut self) -> Self {
69 self.lenient = true;
70 self
71 }
72
73 #[deprecated(note = "Use `lenient()` instead", since = "0.2.0")]
74 #[inline]
75 pub fn unchecked(self) -> Self {
76 self.lenient()
77 }
78
79 #[inline]
80 pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
81 self.base = Some(Iri::parse(base_iri.into())?);
82 Ok(self)
83 }
84
85 #[inline]
86 pub fn with_prefix(
87 mut self,
88 prefix_name: impl Into<String>,
89 prefix_iri: impl Into<String>,
90 ) -> Result<Self, IriParseError> {
91 self.prefixes
92 .insert(prefix_name.into(), Iri::parse(prefix_iri.into())?);
93 Ok(self)
94 }
95
96 /// Parses a Turtle file from a [`Read`] implementation.
97 ///
98 /// Count the number of people:
99 /// ```
100 /// use oxrdf::NamedNodeRef;
101 /// use oxrdf::vocab::rdf;
102 /// use oxttl::TurtleParser;
103 ///
104 /// let file = r#"@base <http://example.com/> .
105 /// @prefix schema: <http://schema.org/> .
106 /// <foo> a schema:Person ;
107 /// schema:name "Foo" .
108 /// <bar> a schema:Person ;
109 /// schema:name "Bar" ."#;
110 ///
111 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
112 /// let mut count = 0;
113 /// for triple in TurtleParser::new().for_reader(file.as_bytes()) {
114 /// let triple = triple?;
115 /// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
116 /// count += 1;
117 /// }
118 /// }
119 /// assert_eq!(2, count);
120 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
121 /// ```
122 pub fn for_reader<R: Read>(self, reader: R) -> ReaderTurtleParser<R> {
123 ReaderTurtleParser {
124 inner: self.low_level().parser.for_reader(reader),
125 }
126 }
127
128 /// Parses a Turtle file from a [`AsyncRead`] implementation.
129 ///
130 /// Count the number of people:
131 /// ```
132 /// # #[tokio::main(flavor = "current_thread")]
133 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
134 /// use oxrdf::NamedNodeRef;
135 /// use oxrdf::vocab::rdf;
136 /// use oxttl::TurtleParser;
137 ///
138 /// let file = r#"@base <http://example.com/> .
139 /// @prefix schema: <http://schema.org/> .
140 /// <foo> a schema:Person ;
141 /// schema:name "Foo" .
142 /// <bar> a schema:Person ;
143 /// schema:name "Bar" ."#;
144 ///
145 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
146 /// let mut count = 0;
147 /// let mut parser = TurtleParser::new().for_tokio_async_reader(file.as_bytes());
148 /// while let Some(triple) = parser.next().await {
149 /// let triple = triple?;
150 /// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
151 /// count += 1;
152 /// }
153 /// }
154 /// assert_eq!(2, count);
155 /// # Ok(())
156 /// # }
157 /// ```
158 #[cfg(feature = "async-tokio")]
159 pub fn for_tokio_async_reader<R: AsyncRead + Unpin>(
160 self,
161 reader: R,
162 ) -> TokioAsyncReaderTurtleParser<R> {
163 TokioAsyncReaderTurtleParser {
164 inner: self.low_level().parser.for_tokio_async_reader(reader),
165 }
166 }
167
168 /// Parses Turtle file from a byte slice.
169 ///
170 /// Count the number of people:
171 /// ```
172 /// use oxrdf::NamedNodeRef;
173 /// use oxrdf::vocab::rdf;
174 /// use oxttl::TurtleParser;
175 ///
176 /// let file = r#"@base <http://example.com/> .
177 /// @prefix schema: <http://schema.org/> .
178 /// <foo> a schema:Person ;
179 /// schema:name "Foo" .
180 /// <bar> a schema:Person ;
181 /// schema:name "Bar" ."#;
182 ///
183 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
184 /// let mut count = 0;
185 /// for triple in TurtleParser::new().for_slice(file) {
186 /// let triple = triple?;
187 /// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
188 /// count += 1;
189 /// }
190 /// }
191 /// assert_eq!(2, count);
192 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
193 /// ```
194 pub fn for_slice(self, slice: &(impl AsRef<[u8]> + ?Sized)) -> SliceTurtleParser<'_> {
195 SliceTurtleParser {
196 inner: TriGRecognizer::new_parser(
197 slice.as_ref(),
198 true,
199 false,
200 self.lenient,
201 self.base,
202 self.prefixes,
203 )
204 .into_iter(),
205 }
206 }
207
208 /// Creates a vector of parsers that may be used to parse a Turtle document slice in parallel.
209 /// To dynamically specify target_parallelism, use e.g. [`std::thread::available_parallelism`].
210 /// Intended to work on large documents.
211 /// Can fail or return wrong results if there are prefixes or base iris that are not defined
212 /// at the top of the document, or valid turtle syntax inside literal values.
213 ///
214 /// Count the number of people:
215 /// ```
216 /// use oxrdf::NamedNodeRef;
217 /// use oxrdf::vocab::rdf;
218 /// use oxttl::TurtleParser;
219 /// use rayon::iter::{IntoParallelIterator, ParallelIterator};
220 ///
221 /// let file = r#"@base <http://example.com/> .
222 /// @prefix schema: <http://schema.org/> .
223 /// <foo> a schema:Person ;
224 /// schema:name "Foo" .
225 /// <bar> a schema:Person ;
226 /// schema:name "Bar" ."#;
227 ///
228 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
229 /// let readers = TurtleParser::new().split_slice_for_parallel_parsing(file, 2);
230 /// let count = readers
231 /// .into_par_iter()
232 /// .map(|reader| {
233 /// let mut count = 0;
234 /// for triple in reader {
235 /// let triple = triple.unwrap();
236 /// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
237 /// count += 1;
238 /// }
239 /// }
240 /// count
241 /// })
242 /// .sum();
243 /// assert_eq!(2, count);
244 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
245 /// ```
246 pub fn split_slice_for_parallel_parsing(
247 mut self,
248 slice: &(impl AsRef<[u8]> + ?Sized),
249 target_parallelism: usize,
250 ) -> Vec<SliceTurtleParser<'_>> {
251 let slice = slice.as_ref();
252 let n_chunks = (slice.len() / MIN_PARALLEL_CHUNK_SIZE).clamp(1, target_parallelism);
253
254 if n_chunks > 1 {
255 // Prefixes must be determined before chunks, since determining chunks relies on parser with prefixes determined.
256 let mut from_slice_parser = self.clone().for_slice(slice);
257 // We don't care about errors: they will be raised when parsing the first chunk anyway
258 from_slice_parser.next();
259 for (p, iri) in from_slice_parser.prefixes() {
260 // Already know this is a valid IRI
261 self = self.with_prefix(p, iri).unwrap();
262 }
263 }
264
265 get_turtle_slice_chunks(slice, n_chunks, &self)
266 .into_iter()
267 .map(|(start, end)| self.clone().for_slice(&slice[start..end]))
268 .collect()
269 }
270
271 /// Allows to parse a Turtle file by using a low-level API.
272 ///
273 /// Count the number of people:
274 /// ```
275 /// use oxrdf::NamedNodeRef;
276 /// use oxrdf::vocab::rdf;
277 /// use oxttl::TurtleParser;
278 ///
279 /// let file: [&[u8]; 5] = [
280 /// b"@base <http://example.com/>",
281 /// b". @prefix schema: <http://schema.org/> .",
282 /// b"<foo> a schema:Person",
283 /// b" ; schema:name \"Foo\" . <bar>",
284 /// b" a schema:Person ; schema:name \"Bar\" .",
285 /// ];
286 ///
287 /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
288 /// let mut count = 0;
289 /// let mut parser = TurtleParser::new().low_level();
290 /// let mut file_chunks = file.iter();
291 /// while !parser.is_end() {
292 /// // We feed more data to the parser
293 /// if let Some(chunk) = file_chunks.next() {
294 /// parser.extend_from_slice(chunk);
295 /// } else {
296 /// parser.end(); // It's finished
297 /// }
298 /// // We read as many triples from the parser as possible
299 /// while let Some(triple) = parser.parse_next() {
300 /// let triple = triple?;
301 /// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
302 /// count += 1;
303 /// }
304 /// }
305 /// }
306 /// assert_eq!(2, count);
307 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
308 /// ```
309 pub fn low_level(self) -> LowLevelTurtleParser {
310 LowLevelTurtleParser {
311 parser: TriGRecognizer::new_parser(
312 Vec::new(),
313 false,
314 false,
315 self.lenient,
316 self.base,
317 self.prefixes,
318 ),
319 }
320 }
321}
322
323/// Parses a Turtle file from a [`Read`] implementation.
324///
325/// Can be built using [`TurtleParser::for_reader`].
326///
327/// Count the number of people:
328/// ```
329/// use oxrdf::NamedNodeRef;
330/// use oxrdf::vocab::rdf;
331/// use oxttl::TurtleParser;
332///
333/// let file = r#"@base <http://example.com/> .
334/// @prefix schema: <http://schema.org/> .
335/// <foo> a schema:Person ;
336/// schema:name "Foo" .
337/// <bar> a schema:Person ;
338/// schema:name "Bar" ."#;
339///
340/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
341/// let mut count = 0;
342/// for triple in TurtleParser::new().for_reader(file.as_bytes()) {
343/// let triple = triple?;
344/// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
345/// count += 1;
346/// }
347/// }
348/// assert_eq!(2, count);
349/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
350/// ```
351#[must_use]
352pub struct ReaderTurtleParser<R: Read> {
353 inner: ReaderIterator<R, TriGRecognizer>,
354}
355
356impl<R: Read> ReaderTurtleParser<R> {
357 /// The list of IRI prefixes considered at the current step of the parsing.
358 ///
359 /// This method returns (prefix name, prefix value) tuples.
360 /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
361 /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
362 ///
363 /// ```
364 /// use oxttl::TurtleParser;
365 ///
366 /// let file = r#"@base <http://example.com/> .
367 /// @prefix schema: <http://schema.org/> .
368 /// <foo> a schema:Person ;
369 /// schema:name "Foo" ."#;
370 ///
371 /// let mut parser = TurtleParser::new().for_reader(file.as_bytes());
372 /// assert!(parser.prefixes().collect::<Vec<_>>().is_empty()); // No prefix at the beginning
373 ///
374 /// parser.next().unwrap()?; // We read the first triple
375 /// assert_eq!(
376 /// parser.prefixes().collect::<Vec<_>>(),
377 /// [("schema", "http://schema.org/")]
378 /// ); // There are now prefixes
379 /// //
380 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
381 /// ```
382 pub fn prefixes(&self) -> TurtlePrefixesIter<'_> {
383 TurtlePrefixesIter {
384 inner: self.inner.parser.context.prefixes(),
385 }
386 }
387
388 /// The base IRI considered at the current step of the parsing.
389 ///
390 /// ```
391 /// use oxttl::TurtleParser;
392 ///
393 /// let file = r#"@base <http://example.com/> .
394 /// @prefix schema: <http://schema.org/> .
395 /// <foo> a schema:Person ;
396 /// schema:name "Foo" ."#;
397 ///
398 /// let mut parser = TurtleParser::new().for_reader(file.as_bytes());
399 /// assert!(parser.base_iri().is_none()); // No base at the beginning because none has been given to the parser.
400 ///
401 /// parser.next().unwrap()?; // We read the first triple
402 /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI.
403 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
404 /// ```
405 pub fn base_iri(&self) -> Option<&str> {
406 self.inner
407 .parser
408 .context
409 .lexer_options
410 .base_iri
411 .as_ref()
412 .map(Iri::as_str)
413 }
414}
415
416impl<R: Read> Iterator for ReaderTurtleParser<R> {
417 type Item = Result<Triple, TurtleParseError>;
418
419 fn next(&mut self) -> Option<Self::Item> {
420 Some(self.inner.next()?.map(Into::into))
421 }
422}
423
424/// Parses a Turtle file from a [`AsyncRead`] implementation.
425///
426/// Can be built using [`TurtleParser::for_tokio_async_reader`].
427///
428/// Count the number of people:
429/// ```
430/// # #[tokio::main(flavor = "current_thread")]
431/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
432/// use oxrdf::NamedNodeRef;
433/// use oxrdf::vocab::rdf;
434/// use oxttl::TurtleParser;
435///
436/// let file = r#"@base <http://example.com/> .
437/// @prefix schema: <http://schema.org/> .
438/// <foo> a schema:Person ;
439/// schema:name "Foo" .
440/// <bar> a schema:Person ;
441/// schema:name "Bar" ."#;
442///
443/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
444/// let mut count = 0;
445/// let mut parser = TurtleParser::new().for_tokio_async_reader(file.as_bytes());
446/// while let Some(triple) = parser.next().await {
447/// let triple = triple?;
448/// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
449/// count += 1;
450/// }
451/// }
452/// assert_eq!(2, count);
453/// # Ok(())
454/// # }
455/// ```
456#[cfg(feature = "async-tokio")]
457#[must_use]
458pub struct TokioAsyncReaderTurtleParser<R: AsyncRead + Unpin> {
459 inner: TokioAsyncReaderIterator<R, TriGRecognizer>,
460}
461
462#[cfg(feature = "async-tokio")]
463impl<R: AsyncRead + Unpin> TokioAsyncReaderTurtleParser<R> {
464 /// Reads the next triple or returns `None` if the file is finished.
465 pub async fn next(&mut self) -> Option<Result<Triple, TurtleParseError>> {
466 Some(self.inner.next().await?.map(Into::into))
467 }
468
469 /// The list of IRI prefixes considered at the current step of the parsing.
470 ///
471 /// This method returns (prefix name, prefix value) tuples.
472 /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
473 /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
474 ///
475 /// ```
476 /// # #[tokio::main(flavor = "current_thread")]
477 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
478 /// use oxttl::TurtleParser;
479 ///
480 /// let file = r#"@base <http://example.com/> .
481 /// @prefix schema: <http://schema.org/> .
482 /// <foo> a schema:Person ;
483 /// schema:name "Foo" ."#;
484 ///
485 /// let mut parser = TurtleParser::new().for_tokio_async_reader(file.as_bytes());
486 /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
487 ///
488 /// parser.next().await.unwrap()?; // We read the first triple
489 /// assert_eq!(
490 /// parser.prefixes().collect::<Vec<_>>(),
491 /// [("schema", "http://schema.org/")]
492 /// ); // There are now prefixes
493 /// //
494 /// # Ok(())
495 /// # }
496 /// ```
497 pub fn prefixes(&self) -> TurtlePrefixesIter<'_> {
498 TurtlePrefixesIter {
499 inner: self.inner.parser.context.prefixes(),
500 }
501 }
502
503 /// The base IRI considered at the current step of the parsing.
504 ///
505 /// ```
506 /// # #[tokio::main(flavor = "current_thread")]
507 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
508 /// use oxttl::TurtleParser;
509 ///
510 /// let file = r#"@base <http://example.com/> .
511 /// @prefix schema: <http://schema.org/> .
512 /// <foo> a schema:Person ;
513 /// schema:name "Foo" ."#;
514 ///
515 /// let mut parser = TurtleParser::new().for_tokio_async_reader(file.as_bytes());
516 /// assert!(parser.base_iri().is_none()); // No base IRI at the beginning
517 ///
518 /// parser.next().await.unwrap()?; // We read the first triple
519 /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI
520 /// //
521 /// # Ok(())
522 /// # }
523 /// ```
524 pub fn base_iri(&self) -> Option<&str> {
525 self.inner
526 .parser
527 .context
528 .lexer_options
529 .base_iri
530 .as_ref()
531 .map(Iri::as_str)
532 }
533}
534
535/// Parses a Turtle file from a byte slice.
536///
537/// Can be built using [`TurtleParser::for_slice`].
538///
539/// Count the number of people:
540/// ```
541/// use oxrdf::NamedNodeRef;
542/// use oxrdf::vocab::rdf;
543/// use oxttl::TurtleParser;
544///
545/// let file = r#"@base <http://example.com/> .
546/// @prefix schema: <http://schema.org/> .
547/// <foo> a schema:Person ;
548/// schema:name "Foo" .
549/// <bar> a schema:Person ;
550/// schema:name "Bar" ."#;
551///
552/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
553/// let mut count = 0;
554/// for triple in TurtleParser::new().for_slice(file) {
555/// let triple = triple?;
556/// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
557/// count += 1;
558/// }
559/// }
560/// assert_eq!(2, count);
561/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
562/// ```
563#[must_use]
564pub struct SliceTurtleParser<'a> {
565 inner: SliceIterator<'a, TriGRecognizer>,
566}
567
568impl SliceTurtleParser<'_> {
569 /// The list of IRI prefixes considered at the current step of the parsing.
570 ///
571 /// This method returns (prefix name, prefix value) tuples.
572 /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
573 /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
574 ///
575 /// ```
576 /// use oxttl::TurtleParser;
577 ///
578 /// let file = r#"@base <http://example.com/> .
579 /// @prefix schema: <http://schema.org/> .
580 /// <foo> a schema:Person ;
581 /// schema:name "Foo" ."#;
582 ///
583 /// let mut parser = TurtleParser::new().for_slice(file);
584 /// assert!(parser.prefixes().collect::<Vec<_>>().is_empty()); // No prefix at the beginning
585 ///
586 /// parser.next().unwrap()?; // We read the first triple
587 /// assert_eq!(
588 /// parser.prefixes().collect::<Vec<_>>(),
589 /// [("schema", "http://schema.org/")]
590 /// ); // There are now prefixes
591 /// //
592 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
593 /// ```
594 pub fn prefixes(&self) -> TurtlePrefixesIter<'_> {
595 TurtlePrefixesIter {
596 inner: self.inner.parser.context.prefixes(),
597 }
598 }
599
600 /// The base IRI considered at the current step of the parsing.
601 ///
602 /// ```
603 /// use oxttl::TurtleParser;
604 ///
605 /// let file = r#"@base <http://example.com/> .
606 /// @prefix schema: <http://schema.org/> .
607 /// <foo> a schema:Person ;
608 /// schema:name "Foo" ."#;
609 ///
610 /// let mut parser = TurtleParser::new().for_slice(file);
611 /// assert!(parser.base_iri().is_none()); // No base at the beginning because none has been given to the parser.
612 ///
613 /// parser.next().unwrap()?; // We read the first triple
614 /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI.
615 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
616 /// ```
617 pub fn base_iri(&self) -> Option<&str> {
618 self.inner
619 .parser
620 .context
621 .lexer_options
622 .base_iri
623 .as_ref()
624 .map(Iri::as_str)
625 }
626}
627
628impl Iterator for SliceTurtleParser<'_> {
629 type Item = Result<Triple, TurtleSyntaxError>;
630
631 fn next(&mut self) -> Option<Self::Item> {
632 Some(self.inner.next()?.map(Into::into))
633 }
634}
635
636/// Parses a Turtle file by using a low-level API.
637///
638/// Can be built using [`TurtleParser::low_level`].
639///
640/// Count the number of people:
641/// ```
642/// use oxrdf::NamedNodeRef;
643/// use oxrdf::vocab::rdf;
644/// use oxttl::TurtleParser;
645///
646/// let file: [&[u8]; 5] = [
647/// b"@base <http://example.com/>",
648/// b". @prefix schema: <http://schema.org/> .",
649/// b"<foo> a schema:Person",
650/// b" ; schema:name \"Foo\" . <bar>",
651/// b" a schema:Person ; schema:name \"Bar\" .",
652/// ];
653///
654/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
655/// let mut count = 0;
656/// let mut parser = TurtleParser::new().low_level();
657/// let mut file_chunks = file.iter();
658/// while !parser.is_end() {
659/// // We feed more data to the parser
660/// if let Some(chunk) = file_chunks.next() {
661/// parser.extend_from_slice(chunk);
662/// } else {
663/// parser.end(); // It's finished
664/// }
665/// // We read as many triples from the parser as possible
666/// while let Some(triple) = parser.parse_next() {
667/// let triple = triple?;
668/// if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
669/// count += 1;
670/// }
671/// }
672/// }
673/// assert_eq!(2, count);
674/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
675/// ```
676pub struct LowLevelTurtleParser {
677 parser: Parser<Vec<u8>, TriGRecognizer>,
678}
679
680impl LowLevelTurtleParser {
681 /// Adds some extra bytes to the parser. Should be called when [`parse_next`](Self::parse_next) returns [`None`] and there is still unread data.
682 pub fn extend_from_slice(&mut self, other: &[u8]) {
683 self.parser.extend_from_slice(other)
684 }
685
686 /// Tell the parser that the file is finished.
687 ///
688 /// This triggers the parsing of the final bytes and might lead [`parse_next`](Self::parse_next) to return some extra values.
689 pub fn end(&mut self) {
690 self.parser.end()
691 }
692
693 /// 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`.
694 pub fn is_end(&self) -> bool {
695 self.parser.is_end()
696 }
697
698 /// Attempt to parse a new triple from the already provided data.
699 ///
700 /// Returns [`None`] if the parsing is finished or more data is required.
701 /// If it is the case more data should be fed using [`extend_from_slice`](Self::extend_from_slice).
702 pub fn parse_next(&mut self) -> Option<Result<Triple, TurtleSyntaxError>> {
703 Some(self.parser.parse_next()?.map(Into::into))
704 }
705
706 /// The list of IRI prefixes considered at the current step of the parsing.
707 ///
708 /// This method returns (prefix name, prefix value) tuples.
709 /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
710 /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
711 ///
712 /// ```
713 /// use oxttl::TurtleParser;
714 ///
715 /// let file = r#"@base <http://example.com/> .
716 /// @prefix schema: <http://schema.org/> .
717 /// <foo> a schema:Person ;
718 /// schema:name "Foo" ."#;
719 ///
720 /// let mut parser = TurtleParser::new().low_level();
721 /// parser.extend_from_slice(file.as_bytes());
722 /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
723 ///
724 /// parser.parse_next().unwrap()?; // We read the first triple
725 /// assert_eq!(
726 /// parser.prefixes().collect::<Vec<_>>(),
727 /// [("schema", "http://schema.org/")]
728 /// ); // There are now prefixes
729 /// //
730 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
731 /// ```
732 pub fn prefixes(&self) -> TurtlePrefixesIter<'_> {
733 TurtlePrefixesIter {
734 inner: self.parser.context.prefixes(),
735 }
736 }
737
738 /// The base IRI considered at the current step of the parsing.
739 ///
740 /// ```
741 /// use oxttl::TurtleParser;
742 ///
743 /// let file = r#"@base <http://example.com/> .
744 /// @prefix schema: <http://schema.org/> .
745 /// <foo> a schema:Person ;
746 /// schema:name "Foo" ."#;
747 ///
748 /// let mut parser = TurtleParser::new().low_level();
749 /// parser.extend_from_slice(file.as_bytes());
750 /// assert!(parser.base_iri().is_none()); // No base IRI at the beginning
751 ///
752 /// parser.parse_next().unwrap()?; // We read the first triple
753 /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI
754 /// //
755 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
756 /// ```
757 pub fn base_iri(&self) -> Option<&str> {
758 self.parser
759 .context
760 .lexer_options
761 .base_iri
762 .as_ref()
763 .map(Iri::as_str)
764 }
765}
766
767/// Iterator on the file prefixes.
768///
769/// See [`LowLevelTurtleParser::prefixes`].
770pub struct TurtlePrefixesIter<'a> {
771 inner: Iter<'a, String, Iri<String>>,
772}
773
774impl<'a> Iterator for TurtlePrefixesIter<'a> {
775 type Item = (&'a str, &'a str);
776
777 #[inline]
778 fn next(&mut self) -> Option<Self::Item> {
779 let (key, value) = self.inner.next()?;
780 Some((key.as_str(), value.as_str()))
781 }
782
783 #[inline]
784 fn size_hint(&self) -> (usize, Option<usize>) {
785 self.inner.size_hint()
786 }
787}
788
789/// A [Turtle](https://www.w3.org/TR/turtle/) serializer.
790///
791/// ```
792/// use oxrdf::vocab::rdf;
793/// use oxrdf::{NamedNodeRef, TripleRef};
794/// use oxttl::TurtleSerializer;
795///
796/// let mut serializer = TurtleSerializer::new()
797/// .with_prefix("schema", "http://schema.org/")?
798/// .for_writer(Vec::new());
799/// serializer.serialize_triple(TripleRef::new(
800/// NamedNodeRef::new("http://example.com#me")?,
801/// rdf::TYPE,
802/// NamedNodeRef::new("http://schema.org/Person")?,
803/// ))?;
804/// assert_eq!(
805/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
806/// serializer.finish()?.as_slice()
807/// );
808/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
809/// ```
810#[derive(Default, Clone)]
811#[must_use]
812pub struct TurtleSerializer {
813 inner: TriGSerializer,
814}
815
816impl TurtleSerializer {
817 /// Builds a new [`TurtleSerializer`].
818 #[inline]
819 pub fn new() -> Self {
820 Self::default()
821 }
822
823 #[inline]
824 pub fn with_prefix(
825 mut self,
826 prefix_name: impl Into<String>,
827 prefix_iri: impl Into<String>,
828 ) -> Result<Self, IriParseError> {
829 self.inner = self.inner.with_prefix(prefix_name, prefix_iri)?;
830 Ok(self)
831 }
832
833 /// Adds a base IRI to the serialization.
834 ///
835 /// ```
836 /// use oxrdf::vocab::rdf;
837 /// use oxrdf::{NamedNodeRef, TripleRef};
838 /// use oxttl::TurtleSerializer;
839 ///
840 /// let mut serializer = TurtleSerializer::new()
841 /// .with_base_iri("http://example.com")?
842 /// .with_prefix("ex", "http://example.com/ns#")?
843 /// .for_writer(Vec::new());
844 /// serializer.serialize_triple(TripleRef::new(
845 /// NamedNodeRef::new("http://example.com/me")?,
846 /// rdf::TYPE,
847 /// NamedNodeRef::new("http://example.com/ns#Person")?,
848 /// ))?;
849 /// assert_eq!(
850 /// b"@base <http://example.com> .\n@prefix ex: </ns#> .\n</me> a ex:Person .\n",
851 /// serializer.finish()?.as_slice()
852 /// );
853 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
854 /// ```
855 #[inline]
856 pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
857 self.inner = self.inner.with_base_iri(base_iri)?;
858 Ok(self)
859 }
860
861 /// Writes a Turtle file to a [`Write`] implementation.
862 ///
863 /// ```
864 /// use oxrdf::vocab::rdf;
865 /// use oxrdf::{NamedNodeRef, TripleRef};
866 /// use oxttl::TurtleSerializer;
867 ///
868 /// let mut serializer = TurtleSerializer::new()
869 /// .with_prefix("schema", "http://schema.org/")?
870 /// .for_writer(Vec::new());
871 /// serializer.serialize_triple(TripleRef::new(
872 /// NamedNodeRef::new("http://example.com#me")?,
873 /// rdf::TYPE,
874 /// NamedNodeRef::new("http://schema.org/Person")?,
875 /// ))?;
876 /// assert_eq!(
877 /// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
878 /// serializer.finish()?.as_slice()
879 /// );
880 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
881 /// ```
882 pub fn for_writer<W: Write>(self, writer: W) -> WriterTurtleSerializer<W> {
883 WriterTurtleSerializer {
884 inner: self.inner.for_writer(writer),
885 }
886 }
887
888 /// Writes a Turtle file to a [`AsyncWrite`] implementation.
889 ///
890 /// ```
891 /// # #[tokio::main(flavor = "current_thread")]
892 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
893 /// use oxrdf::vocab::rdf;
894 /// use oxrdf::{NamedNodeRef, TripleRef};
895 /// use oxttl::TurtleSerializer;
896 ///
897 /// let mut serializer = TurtleSerializer::new()
898 /// .with_prefix("schema", "http://schema.org/")?
899 /// .for_tokio_async_writer(Vec::new());
900 /// serializer
901 /// .serialize_triple(TripleRef::new(
902 /// NamedNodeRef::new("http://example.com#me")?,
903 /// rdf::TYPE,
904 /// NamedNodeRef::new("http://schema.org/Person")?,
905 /// ))
906 /// .await?;
907 /// assert_eq!(
908 /// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
909 /// serializer.finish().await?.as_slice()
910 /// );
911 /// # Ok(())
912 /// # }
913 /// ```
914 #[cfg(feature = "async-tokio")]
915 pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
916 self,
917 writer: W,
918 ) -> TokioAsyncWriterTurtleSerializer<W> {
919 TokioAsyncWriterTurtleSerializer {
920 inner: self.inner.for_tokio_async_writer(writer),
921 }
922 }
923
924 /// Builds a low-level Turtle writer.
925 ///
926 /// ```
927 /// use oxrdf::vocab::rdf;
928 /// use oxrdf::{NamedNodeRef, TripleRef};
929 /// use oxttl::TurtleSerializer;
930 ///
931 /// let mut buf = Vec::new();
932 /// let mut serializer = TurtleSerializer::new()
933 /// .with_prefix("schema", "http://schema.org/")?
934 /// .low_level();
935 /// serializer.serialize_triple(
936 /// TripleRef::new(
937 /// NamedNodeRef::new("http://example.com#me")?,
938 /// rdf::TYPE,
939 /// NamedNodeRef::new("http://schema.org/Person")?,
940 /// ),
941 /// &mut buf,
942 /// )?;
943 /// serializer.finish(&mut buf)?;
944 /// assert_eq!(
945 /// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
946 /// buf.as_slice()
947 /// );
948 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
949 /// ```
950 pub fn low_level(self) -> LowLevelTurtleSerializer {
951 LowLevelTurtleSerializer {
952 inner: self.inner.low_level(),
953 }
954 }
955}
956
957/// Writes a Turtle file to a [`Write`] implementation.
958///
959/// Can be built using [`TurtleSerializer::for_writer`].
960///
961/// ```
962/// use oxrdf::vocab::rdf;
963/// use oxrdf::{NamedNodeRef, TripleRef};
964/// use oxttl::TurtleSerializer;
965///
966/// let mut serializer = TurtleSerializer::new()
967/// .with_prefix("schema", "http://schema.org/")?
968/// .for_writer(Vec::new());
969/// serializer.serialize_triple(TripleRef::new(
970/// NamedNodeRef::new("http://example.com#me")?,
971/// rdf::TYPE,
972/// NamedNodeRef::new("http://schema.org/Person")?,
973/// ))?;
974/// assert_eq!(
975/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
976/// serializer.finish()?.as_slice()
977/// );
978/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
979/// ```
980#[must_use]
981pub struct WriterTurtleSerializer<W: Write> {
982 inner: WriterTriGSerializer<W>,
983}
984
985impl<W: Write> WriterTurtleSerializer<W> {
986 /// Writes an extra triple.
987 pub fn serialize_triple<'a>(&mut self, t: impl Into<TripleRef<'a>>) -> io::Result<()> {
988 self.inner
989 .serialize_quad(t.into().in_graph(GraphNameRef::DefaultGraph))
990 }
991
992 /// Ends the write process and returns the underlying [`Write`].
993 pub fn finish(self) -> io::Result<W> {
994 self.inner.finish()
995 }
996}
997
998/// Writes a Turtle file to a [`AsyncWrite`] implementation.
999///
1000/// Can be built using [`TurtleSerializer::for_tokio_async_writer`].
1001///
1002/// ```
1003/// # #[tokio::main(flavor = "current_thread")]
1004/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1005/// use oxrdf::vocab::rdf;
1006/// use oxrdf::{NamedNodeRef, TripleRef};
1007/// use oxttl::TurtleSerializer;
1008///
1009/// let mut serializer = TurtleSerializer::new()
1010/// .with_prefix("schema", "http://schema.org/")?
1011/// .for_tokio_async_writer(Vec::new());
1012/// serializer
1013/// .serialize_triple(TripleRef::new(
1014/// NamedNodeRef::new("http://example.com#me")?,
1015/// rdf::TYPE,
1016/// NamedNodeRef::new("http://schema.org/Person")?,
1017/// ))
1018/// .await?;
1019/// assert_eq!(
1020/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
1021/// serializer.finish().await?.as_slice()
1022/// );
1023/// # Ok(())
1024/// # }
1025/// ```
1026#[cfg(feature = "async-tokio")]
1027#[must_use]
1028pub struct TokioAsyncWriterTurtleSerializer<W: AsyncWrite + Unpin> {
1029 inner: TokioAsyncWriterTriGSerializer<W>,
1030}
1031
1032#[cfg(feature = "async-tokio")]
1033impl<W: AsyncWrite + Unpin> TokioAsyncWriterTurtleSerializer<W> {
1034 /// Writes an extra triple.
1035 pub async fn serialize_triple<'a>(&mut self, t: impl Into<TripleRef<'a>>) -> io::Result<()> {
1036 self.inner
1037 .serialize_quad(t.into().in_graph(GraphNameRef::DefaultGraph))
1038 .await
1039 }
1040
1041 /// Ends the write process and returns the underlying [`Write`].
1042 pub async fn finish(self) -> io::Result<W> {
1043 self.inner.finish().await
1044 }
1045}
1046
1047/// Writes a Turtle file by using a low-level API.
1048///
1049/// Can be built using [`TurtleSerializer::low_level`].
1050///
1051/// ```
1052/// use oxrdf::vocab::rdf;
1053/// use oxrdf::{NamedNodeRef, TripleRef};
1054/// use oxttl::TurtleSerializer;
1055///
1056/// let mut buf = Vec::new();
1057/// let mut serializer = TurtleSerializer::new()
1058/// .with_prefix("schema", "http://schema.org/")?
1059/// .low_level();
1060/// serializer.serialize_triple(
1061/// TripleRef::new(
1062/// NamedNodeRef::new("http://example.com#me")?,
1063/// rdf::TYPE,
1064/// NamedNodeRef::new("http://schema.org/Person")?,
1065/// ),
1066/// &mut buf,
1067/// )?;
1068/// serializer.finish(&mut buf)?;
1069/// assert_eq!(
1070/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
1071/// buf.as_slice()
1072/// );
1073/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
1074/// ```
1075pub struct LowLevelTurtleSerializer {
1076 inner: LowLevelTriGSerializer,
1077}
1078
1079impl LowLevelTurtleSerializer {
1080 /// Writes an extra triple.
1081 pub fn serialize_triple<'a>(
1082 &mut self,
1083 t: impl Into<TripleRef<'a>>,
1084 writer: impl Write,
1085 ) -> io::Result<()> {
1086 self.inner
1087 .serialize_quad(t.into().in_graph(GraphNameRef::DefaultGraph), writer)
1088 }
1089
1090 /// Finishes to write the file.
1091 pub fn finish(&mut self, writer: impl Write) -> io::Result<()> {
1092 self.inner.finish(writer)
1093 }
1094}
1095
1096#[cfg(test)]
1097#[expect(clippy::panic_in_result_fn)]
1098mod tests {
1099 use super::*;
1100 use oxrdf::{BlankNodeRef, LiteralRef, NamedNodeRef};
1101
1102 #[test]
1103 fn test_write() -> io::Result<()> {
1104 let mut serializer = TurtleSerializer::new().for_writer(Vec::new());
1105 serializer.serialize_triple(TripleRef::new(
1106 NamedNodeRef::new_unchecked("http://example.com/s"),
1107 NamedNodeRef::new_unchecked("http://example.com/p"),
1108 NamedNodeRef::new_unchecked("http://example.com/o"),
1109 ))?;
1110 serializer.serialize_triple(TripleRef::new(
1111 NamedNodeRef::new_unchecked("http://example.com/s"),
1112 NamedNodeRef::new_unchecked("http://example.com/p"),
1113 LiteralRef::new_simple_literal("foo"),
1114 ))?;
1115 serializer.serialize_triple(TripleRef::new(
1116 NamedNodeRef::new_unchecked("http://example.com/s"),
1117 NamedNodeRef::new_unchecked("http://example.com/p2"),
1118 LiteralRef::new_language_tagged_literal_unchecked("foo", "en"),
1119 ))?;
1120 serializer.serialize_triple(TripleRef::new(
1121 BlankNodeRef::new_unchecked("b"),
1122 NamedNodeRef::new_unchecked("http://example.com/p2"),
1123 BlankNodeRef::new_unchecked("b2"),
1124 ))?;
1125 assert_eq!(
1126 String::from_utf8(serializer.finish()?).map_err(io::Error::other)?,
1127 "<http://example.com/s> <http://example.com/p> <http://example.com/o> , \"foo\" ;\n\t<http://example.com/p2> \"foo\"@en .\n_:b <http://example.com/p2> _:b2 .\n"
1128 );
1129 Ok(())
1130 }
1131}