Skip to main content

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}