oxttl/
trig.rs

1//! A [TriG](https://www.w3.org/TR/trig/) streaming parser implemented by [`TriGParser`]
2//! and a serializer implemented by [`TriGSerializer`].
3
4use crate::lexer::N3Lexer;
5use crate::terse::TriGRecognizer;
6#[cfg(feature = "async-tokio")]
7use crate::toolkit::TokioAsyncReaderIterator;
8use crate::toolkit::{Parser, ReaderIterator, SliceIterator, TurtleParseError, TurtleSyntaxError};
9use oxiri::{Iri, IriParseError};
10use oxrdf::vocab::{rdf, xsd};
11use oxrdf::{
12    GraphName, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, Quad, QuadRef, Subject, TermRef,
13};
14use std::borrow::Cow;
15use std::collections::hash_map::Iter;
16use std::collections::{BTreeMap, HashMap};
17use std::fmt;
18use std::io::{self, Read, Write};
19#[cfg(feature = "async-tokio")]
20use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
21
22/// A [TriG](https://www.w3.org/TR/trig/) streaming parser.
23///
24/// Support for [TriG-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#trig-star) is available behind the `rdf-star` feature and the [`TriGParser::with_quoted_triples`] option.
25///
26/// Count the number of people:
27/// ```
28/// use oxrdf::vocab::rdf;
29/// use oxrdf::NamedNodeRef;
30/// use oxttl::TriGParser;
31///
32/// let file = br#"@base <http://example.com/> .
33/// @prefix schema: <http://schema.org/> .
34/// <foo> a schema:Person ;
35///     schema:name "Foo" .
36/// <bar> a schema:Person ;
37///     schema:name "Bar" ."#;
38///
39/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
40/// let mut count = 0;
41/// for quad in TriGParser::new().for_reader(file.as_ref()) {
42///     let quad = quad?;
43///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
44///         count += 1;
45///     }
46/// }
47/// assert_eq!(2, count);
48/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
49/// ```
50#[derive(Default, Clone)]
51#[must_use]
52pub struct TriGParser {
53    unchecked: bool,
54    base: Option<Iri<String>>,
55    prefixes: HashMap<String, Iri<String>>,
56    #[cfg(feature = "rdf-star")]
57    with_quoted_triples: bool,
58}
59
60impl TriGParser {
61    /// Builds a new [`TriGParser`].
62    #[inline]
63    pub fn new() -> Self {
64        Self::default()
65    }
66
67    /// Assumes the file is valid to make parsing faster.
68    ///
69    /// It will skip some validations.
70    ///
71    /// Note that if the file is actually not valid, broken RDF might be emitted by the parser.
72    #[inline]
73    pub fn unchecked(mut self) -> Self {
74        self.unchecked = true;
75        self
76    }
77
78    #[inline]
79    pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
80        self.base = Some(Iri::parse(base_iri.into())?);
81        Ok(self)
82    }
83
84    #[inline]
85    pub fn with_prefix(
86        mut self,
87        prefix_name: impl Into<String>,
88        prefix_iri: impl Into<String>,
89    ) -> Result<Self, IriParseError> {
90        self.prefixes
91            .insert(prefix_name.into(), Iri::parse(prefix_iri.into())?);
92        Ok(self)
93    }
94
95    /// Enables [TriG-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#trig-star).
96    #[cfg(feature = "rdf-star")]
97    #[inline]
98    pub fn with_quoted_triples(mut self) -> Self {
99        self.with_quoted_triples = true;
100        self
101    }
102
103    /// Parses a TriG file from a [`Read`] implementation.
104    ///
105    /// Count the number of people:
106    /// ```
107    /// use oxrdf::vocab::rdf;
108    /// use oxrdf::NamedNodeRef;
109    /// use oxttl::TriGParser;
110    ///
111    /// let file = br#"@base <http://example.com/> .
112    /// @prefix schema: <http://schema.org/> .
113    /// <foo> a schema:Person ;
114    ///     schema:name "Foo" .
115    /// <bar> a schema:Person ;
116    ///     schema:name "Bar" ."#;
117    ///
118    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
119    /// let mut count = 0;
120    /// for quad in TriGParser::new().for_reader(file.as_ref()) {
121    ///     let quad = quad?;
122    ///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
123    ///         count += 1;
124    ///     }
125    /// }
126    /// assert_eq!(2, count);
127    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
128    /// ```
129    pub fn for_reader<R: Read>(self, reader: R) -> ReaderTriGParser<R> {
130        ReaderTriGParser {
131            inner: self.low_level().parser.for_reader(reader),
132        }
133    }
134
135    /// Parses a TriG file from a [`AsyncRead`] implementation.
136    ///
137    /// Count the number of people:
138    /// ```
139    /// # #[tokio::main(flavor = "current_thread")]
140    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
141    /// use oxrdf::vocab::rdf;
142    /// use oxrdf::NamedNodeRef;
143    /// use oxttl::TriGParser;
144    ///
145    /// let file = br#"@base <http://example.com/> .
146    /// @prefix schema: <http://schema.org/> .
147    /// <foo> a schema:Person ;
148    ///     schema:name "Foo" .
149    /// <bar> a schema:Person ;
150    ///     schema:name "Bar" ."#;
151    ///
152    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
153    /// let mut count = 0;
154    /// let mut parser = TriGParser::new().for_tokio_async_reader(file.as_ref());
155    /// while let Some(triple) = parser.next().await {
156    ///     let triple = triple?;
157    ///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
158    ///         count += 1;
159    ///     }
160    /// }
161    /// assert_eq!(2, count);
162    /// # Ok(())
163    /// # }
164    /// ```
165    #[cfg(feature = "async-tokio")]
166    pub fn for_tokio_async_reader<R: AsyncRead + Unpin>(
167        self,
168        reader: R,
169    ) -> TokioAsyncReaderTriGParser<R> {
170        TokioAsyncReaderTriGParser {
171            inner: self.low_level().parser.for_tokio_async_reader(reader),
172        }
173    }
174
175    /// Parses a TriG file from a byte slice.
176    ///
177    /// Count the number of people:
178    /// ```
179    /// use oxrdf::vocab::rdf;
180    /// use oxrdf::NamedNodeRef;
181    /// use oxttl::TriGParser;
182    ///
183    /// let file = br#"@base <http://example.com/> .
184    /// @prefix schema: <http://schema.org/> .
185    /// <foo> a schema:Person ;
186    ///     schema:name "Foo" .
187    /// <bar> a schema:Person ;
188    ///     schema:name "Bar" ."#;
189    ///
190    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
191    /// let mut count = 0;
192    /// for quad in TriGParser::new().for_slice(file) {
193    ///     let quad = quad?;
194    ///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
195    ///         count += 1;
196    ///     }
197    /// }
198    /// assert_eq!(2, count);
199    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
200    /// ```
201    pub fn for_slice(self, slice: &[u8]) -> SliceTriGParser<'_> {
202        SliceTriGParser {
203            inner: TriGRecognizer::new_parser(
204                slice,
205                true,
206                true,
207                #[cfg(feature = "rdf-star")]
208                self.with_quoted_triples,
209                self.unchecked,
210                self.base,
211                self.prefixes,
212            )
213            .into_iter(),
214        }
215    }
216
217    /// Allows to parse a TriG file by using a low-level API.
218    ///
219    /// Count the number of people:
220    /// ```
221    /// use oxrdf::vocab::rdf;
222    /// use oxrdf::NamedNodeRef;
223    /// use oxttl::TriGParser;
224    ///
225    /// let file: [&[u8]; 5] = [
226    ///     b"@base <http://example.com/>",
227    ///     b". @prefix schema: <http://schema.org/> .",
228    ///     b"<foo> a schema:Person",
229    ///     b" ; schema:name \"Foo\" . <bar>",
230    ///     b" a schema:Person ; schema:name \"Bar\" .",
231    /// ];
232    ///
233    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
234    /// let mut count = 0;
235    /// let mut parser = TriGParser::new().low_level();
236    /// let mut file_chunks = file.iter();
237    /// while !parser.is_end() {
238    ///     // We feed more data to the parser
239    ///     if let Some(chunk) = file_chunks.next() {
240    ///         parser.extend_from_slice(chunk);
241    ///     } else {
242    ///         parser.end(); // It's finished
243    ///     }
244    ///     // We read as many quads from the parser as possible
245    ///     while let Some(quad) = parser.parse_next() {
246    ///         let quad = quad?;
247    ///         if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
248    ///             count += 1;
249    ///         }
250    ///     }
251    /// }
252    /// assert_eq!(2, count);
253    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
254    /// ```
255    pub fn low_level(self) -> LowLevelTriGParser {
256        LowLevelTriGParser {
257            parser: TriGRecognizer::new_parser(
258                Vec::new(),
259                false,
260                true,
261                #[cfg(feature = "rdf-star")]
262                self.with_quoted_triples,
263                self.unchecked,
264                self.base,
265                self.prefixes,
266            ),
267        }
268    }
269}
270
271/// Parses a TriG file from a [`Read`] implementation.
272///
273/// Can be built using [`TriGParser::for_reader`].
274///
275/// Count the number of people:
276/// ```
277/// use oxrdf::vocab::rdf;
278/// use oxrdf::NamedNodeRef;
279/// use oxttl::TriGParser;
280///
281/// let file = br#"@base <http://example.com/> .
282/// @prefix schema: <http://schema.org/> .
283/// <foo> a schema:Person ;
284///     schema:name "Foo" .
285/// <bar> a schema:Person ;
286///     schema:name "Bar" ."#;
287///
288/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
289/// let mut count = 0;
290/// for quad in TriGParser::new().for_reader(file.as_ref()) {
291///     let quad = quad?;
292///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
293///         count += 1;
294///     }
295/// }
296/// assert_eq!(2, count);
297/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
298/// ```
299#[must_use]
300pub struct ReaderTriGParser<R: Read> {
301    inner: ReaderIterator<R, TriGRecognizer>,
302}
303
304impl<R: Read> ReaderTriGParser<R> {
305    /// The list of IRI prefixes considered at the current step of the parsing.
306    ///
307    /// This method returns (prefix name, prefix value) tuples.
308    /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
309    /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
310    ///
311    /// ```
312    /// use oxttl::TriGParser;
313    ///
314    /// let file = br#"@base <http://example.com/> .
315    /// @prefix schema: <http://schema.org/> .
316    /// <foo> a schema:Person ;
317    ///     schema:name "Foo" ."#;
318    ///
319    /// let mut parser = TriGParser::new().for_reader(file.as_ref());
320    /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
321    ///
322    /// parser.next().unwrap()?; // We read the first triple
323    /// assert_eq!(
324    ///     parser.prefixes().collect::<Vec<_>>(),
325    ///     [("schema", "http://schema.org/")]
326    /// ); // There are now prefixes
327    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
328    /// ```
329    pub fn prefixes(&self) -> TriGPrefixesIter<'_> {
330        TriGPrefixesIter {
331            inner: self.inner.parser.context.prefixes(),
332        }
333    }
334
335    /// The base IRI considered at the current step of the parsing.
336    ///
337    /// ```
338    /// use oxttl::TriGParser;
339    ///
340    /// let file = br#"@base <http://example.com/> .
341    /// @prefix schema: <http://schema.org/> .
342    /// <foo> a schema:Person ;
343    ///     schema:name "Foo" ."#;
344    ///
345    /// let mut parser = TriGParser::new().for_reader(file.as_ref());
346    /// assert!(parser.base_iri().is_none()); // No base at the beginning because none has been given to the parser.
347    ///
348    /// parser.next().unwrap()?; // We read the first triple
349    /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI.
350    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
351    /// ```
352    pub fn base_iri(&self) -> Option<&str> {
353        self.inner
354            .parser
355            .context
356            .lexer_options
357            .base_iri
358            .as_ref()
359            .map(Iri::as_str)
360    }
361}
362
363impl<R: Read> Iterator for ReaderTriGParser<R> {
364    type Item = Result<Quad, TurtleParseError>;
365
366    fn next(&mut self) -> Option<Self::Item> {
367        self.inner.next()
368    }
369}
370
371/// Parses a TriG file from a [`AsyncRead`] implementation.
372///
373/// Can be built using [`TriGParser::for_tokio_async_reader`].
374///
375/// Count the number of people:
376/// ```
377/// # #[tokio::main(flavor = "current_thread")]
378/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
379/// use oxrdf::vocab::rdf;
380/// use oxrdf::NamedNodeRef;
381/// use oxttl::TriGParser;
382///
383/// let file = br#"@base <http://example.com/> .
384/// @prefix schema: <http://schema.org/> .
385/// <foo> a schema:Person ;
386///     schema:name "Foo" .
387/// <bar> a schema:Person ;
388///     schema:name "Bar" ."#;
389///
390/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
391/// let mut count = 0;
392/// let mut parser = TriGParser::new().for_tokio_async_reader(file.as_ref());
393/// while let Some(triple) = parser.next().await {
394///     let triple = triple?;
395///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
396///         count += 1;
397///     }
398/// }
399/// assert_eq!(2, count);
400/// # Ok(())
401/// # }
402/// ```
403#[cfg(feature = "async-tokio")]
404#[must_use]
405pub struct TokioAsyncReaderTriGParser<R: AsyncRead + Unpin> {
406    inner: TokioAsyncReaderIterator<R, TriGRecognizer>,
407}
408
409#[cfg(feature = "async-tokio")]
410impl<R: AsyncRead + Unpin> TokioAsyncReaderTriGParser<R> {
411    /// Reads the next triple or returns `None` if the file is finished.
412    pub async fn next(&mut self) -> Option<Result<Quad, TurtleParseError>> {
413        self.inner.next().await
414    }
415
416    /// The list of IRI prefixes considered at the current step of the parsing.
417    ///
418    /// This method returns (prefix name, prefix value) tuples.
419    /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
420    /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
421    ///
422    /// ```
423    /// # #[tokio::main(flavor = "current_thread")]
424    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
425    /// use oxttl::TriGParser;
426    ///
427    /// let file = br#"@base <http://example.com/> .
428    /// @prefix schema: <http://schema.org/> .
429    /// <foo> a schema:Person ;
430    ///     schema:name "Foo" ."#;
431    ///
432    /// let mut parser = TriGParser::new().for_tokio_async_reader(file.as_ref());
433    /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
434    ///
435    /// parser.next().await.unwrap()?; // We read the first triple
436    /// assert_eq!(
437    ///     parser.prefixes().collect::<Vec<_>>(),
438    ///     [("schema", "http://schema.org/")]
439    /// ); // There are now prefixes
440    /// # Ok(())
441    /// # }
442    /// ```
443    pub fn prefixes(&self) -> TriGPrefixesIter<'_> {
444        TriGPrefixesIter {
445            inner: self.inner.parser.context.prefixes(),
446        }
447    }
448
449    /// The base IRI considered at the current step of the parsing.
450    ///
451    /// ```
452    /// # #[tokio::main(flavor = "current_thread")]
453    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
454    /// use oxttl::TriGParser;
455    ///
456    /// let file = br#"@base <http://example.com/> .
457    /// @prefix schema: <http://schema.org/> .
458    /// <foo> a schema:Person ;
459    ///     schema:name "Foo" ."#;
460    ///
461    /// let mut parser = TriGParser::new().for_tokio_async_reader(file.as_ref());
462    /// assert!(parser.base_iri().is_none()); // No base IRI at the beginning
463    ///
464    /// parser.next().await.unwrap()?; // We read the first triple
465    /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI
466    /// # Ok(())
467    /// # }
468    /// ```
469    pub fn base_iri(&self) -> Option<&str> {
470        self.inner
471            .parser
472            .context
473            .lexer_options
474            .base_iri
475            .as_ref()
476            .map(Iri::as_str)
477    }
478}
479
480/// Parses a TriG file from a byte slice.
481///
482/// Can be built using [`TriGParser::for_slice`].
483///
484/// Count the number of people:
485/// ```
486/// use oxrdf::vocab::rdf;
487/// use oxrdf::NamedNodeRef;
488/// use oxttl::TriGParser;
489///
490/// let file = br#"@base <http://example.com/> .
491/// @prefix schema: <http://schema.org/> .
492/// <foo> a schema:Person ;
493///     schema:name "Foo" .
494/// <bar> a schema:Person ;
495///     schema:name "Bar" ."#;
496///
497/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
498/// let mut count = 0;
499/// for quad in TriGParser::new().for_slice(file) {
500///     let quad = quad?;
501///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
502///         count += 1;
503///     }
504/// }
505/// assert_eq!(2, count);
506/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
507/// ```
508#[must_use]
509pub struct SliceTriGParser<'a> {
510    inner: SliceIterator<'a, TriGRecognizer>,
511}
512
513impl SliceTriGParser<'_> {
514    /// The list of IRI prefixes considered at the current step of the parsing.
515    ///
516    /// This method returns (prefix name, prefix value) tuples.
517    /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
518    /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
519    ///
520    /// ```
521    /// use oxttl::TriGParser;
522    ///
523    /// let file = br#"@base <http://example.com/> .
524    /// @prefix schema: <http://schema.org/> .
525    /// <foo> a schema:Person ;
526    ///     schema:name "Foo" ."#;
527    ///
528    /// let mut parser = TriGParser::new().for_slice(file);
529    /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
530    ///
531    /// parser.next().unwrap()?; // We read the first triple
532    /// assert_eq!(
533    ///     parser.prefixes().collect::<Vec<_>>(),
534    ///     [("schema", "http://schema.org/")]
535    /// ); // There are now prefixes
536    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
537    /// ```
538    pub fn prefixes(&self) -> TriGPrefixesIter<'_> {
539        TriGPrefixesIter {
540            inner: self.inner.parser.context.prefixes(),
541        }
542    }
543
544    /// The base IRI considered at the current step of the parsing.
545    ///
546    /// ```
547    /// use oxttl::TriGParser;
548    ///
549    /// let file = br#"@base <http://example.com/> .
550    /// @prefix schema: <http://schema.org/> .
551    /// <foo> a schema:Person ;
552    ///     schema:name "Foo" ."#;
553    ///
554    /// let mut parser = TriGParser::new().for_slice(file);
555    /// assert!(parser.base_iri().is_none()); // No base at the beginning because none has been given to the parser.
556    ///
557    /// parser.next().unwrap()?; // We read the first triple
558    /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI.
559    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
560    /// ```
561    pub fn base_iri(&self) -> Option<&str> {
562        self.inner
563            .parser
564            .context
565            .lexer_options
566            .base_iri
567            .as_ref()
568            .map(Iri::as_str)
569    }
570}
571
572impl Iterator for SliceTriGParser<'_> {
573    type Item = Result<Quad, TurtleSyntaxError>;
574
575    fn next(&mut self) -> Option<Self::Item> {
576        self.inner.next()
577    }
578}
579
580/// Parses a TriG file by using a low-level API.
581///
582/// Can be built using [`TriGParser::low_level`].
583///
584/// Count the number of people:
585/// ```
586/// use oxrdf::vocab::rdf;
587/// use oxrdf::NamedNodeRef;
588/// use oxttl::TriGParser;
589///
590/// let file: [&[u8]; 5] = [
591///     b"@base <http://example.com/>",
592///     b". @prefix schema: <http://schema.org/> .",
593///     b"<foo> a schema:Person",
594///     b" ; schema:name \"Foo\" . <bar>",
595///     b" a schema:Person ; schema:name \"Bar\" .",
596/// ];
597///
598/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
599/// let mut count = 0;
600/// let mut parser = TriGParser::new().low_level();
601/// let mut file_chunks = file.iter();
602/// while !parser.is_end() {
603///     // We feed more data to the parser
604///     if let Some(chunk) = file_chunks.next() {
605///         parser.extend_from_slice(chunk);
606///     } else {
607///         parser.end(); // It's finished
608///     }
609///     // We read as many quads from the parser as possible
610///     while let Some(quad) = parser.parse_next() {
611///         let quad = quad?;
612///         if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
613///             count += 1;
614///         }
615///     }
616/// }
617/// assert_eq!(2, count);
618/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
619/// ```
620pub struct LowLevelTriGParser {
621    parser: Parser<Vec<u8>, TriGRecognizer>,
622}
623
624impl LowLevelTriGParser {
625    /// Adds some extra bytes to the parser. Should be called when [`parse_next`](Self::parse_next) returns [`None`] and there is still unread data.
626    pub fn extend_from_slice(&mut self, other: &[u8]) {
627        self.parser.extend_from_slice(other)
628    }
629
630    /// Tell the parser that the file is finished.
631    ///
632    /// This triggers the parsing of the final bytes and might lead [`parse_next`](Self::parse_next) to return some extra values.
633    pub fn end(&mut self) {
634        self.parser.end()
635    }
636
637    /// 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`.
638    pub fn is_end(&self) -> bool {
639        self.parser.is_end()
640    }
641
642    /// Attempt to parse a new quad from the already provided data.
643    ///
644    /// Returns [`None`] if the parsing is finished or more data is required.
645    /// If it is the case more data should be fed using [`extend_from_slice`](Self::extend_from_slice).
646    pub fn parse_next(&mut self) -> Option<Result<Quad, TurtleSyntaxError>> {
647        self.parser.parse_next()
648    }
649
650    /// The list of IRI prefixes considered at the current step of the parsing.
651    ///
652    /// This method returns (prefix name, prefix value) tuples.
653    /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered.
654    /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned).
655    ///
656    /// ```
657    /// use oxttl::TriGParser;
658    ///
659    /// let file = br#"@base <http://example.com/> .
660    /// @prefix schema: <http://schema.org/> .
661    /// <foo> a schema:Person ;
662    ///     schema:name "Foo" ."#;
663    ///
664    /// let mut parser = TriGParser::new().low_level();
665    /// parser.extend_from_slice(file);
666    /// assert_eq!(parser.prefixes().collect::<Vec<_>>(), []); // No prefix at the beginning
667    ///
668    /// parser.parse_next().unwrap()?; // We read the first triple
669    /// assert_eq!(
670    ///     parser.prefixes().collect::<Vec<_>>(),
671    ///     [("schema", "http://schema.org/")]
672    /// ); // There are now prefixes
673    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
674    /// ```
675    pub fn prefixes(&self) -> TriGPrefixesIter<'_> {
676        TriGPrefixesIter {
677            inner: self.parser.context.prefixes(),
678        }
679    }
680
681    /// The base IRI considered at the current step of the parsing.
682    ///
683    /// ```
684    /// use oxttl::TriGParser;
685    ///
686    /// let file = br#"@base <http://example.com/> .
687    /// @prefix schema: <http://schema.org/> .
688    /// <foo> a schema:Person ;
689    ///     schema:name "Foo" ."#;
690    ///
691    /// let mut parser = TriGParser::new().low_level();
692    /// parser.extend_from_slice(file);
693    /// assert!(parser.base_iri().is_none()); // No base IRI at the beginning
694    ///
695    /// parser.parse_next().unwrap()?; // We read the first triple
696    /// assert_eq!(parser.base_iri(), Some("http://example.com/")); // There is now a base IRI
697    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
698    /// ```
699    pub fn base_iri(&self) -> Option<&str> {
700        self.parser
701            .context
702            .lexer_options
703            .base_iri
704            .as_ref()
705            .map(Iri::as_str)
706    }
707}
708
709/// Iterator on the file prefixes.
710///
711/// See [`LowLevelTriGParser::prefixes`].
712pub struct TriGPrefixesIter<'a> {
713    inner: Iter<'a, String, Iri<String>>,
714}
715
716impl<'a> Iterator for TriGPrefixesIter<'a> {
717    type Item = (&'a str, &'a str);
718
719    #[inline]
720    fn next(&mut self) -> Option<Self::Item> {
721        let (key, value) = self.inner.next()?;
722        Some((key.as_str(), value.as_str()))
723    }
724
725    #[inline]
726    fn size_hint(&self) -> (usize, Option<usize>) {
727        self.inner.size_hint()
728    }
729}
730
731/// A [TriG](https://www.w3.org/TR/trig/) serializer.
732///
733/// Support for [TriG-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#trig-star) is available behind the `rdf-star` feature.
734///
735/// ```
736/// use oxrdf::{NamedNodeRef, QuadRef};
737/// use oxrdf::vocab::rdf;
738/// use oxttl::TriGSerializer;
739///
740/// let mut serializer = TriGSerializer::new()
741///     .with_prefix("schema", "http://schema.org/")?
742///     .for_writer(Vec::new());
743/// serializer.serialize_quad(QuadRef::new(
744///     NamedNodeRef::new("http://example.com#me")?,
745///     rdf::TYPE,
746///     NamedNodeRef::new("http://schema.org/Person")?,
747///     NamedNodeRef::new("http://example.com")?,
748/// ))?;
749/// assert_eq!(
750///     b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
751///     serializer.finish()?.as_slice()
752/// );
753/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
754/// ```
755#[derive(Default, Clone)]
756#[must_use]
757pub struct TriGSerializer {
758    base_iri: Option<Iri<String>>,
759    prefixes: BTreeMap<String, String>,
760}
761
762impl TriGSerializer {
763    /// Builds a new [`TriGSerializer`].
764    #[inline]
765    pub fn new() -> Self {
766        Self {
767            base_iri: None,
768            prefixes: BTreeMap::new(),
769        }
770    }
771
772    #[inline]
773    pub fn with_prefix(
774        mut self,
775        prefix_name: impl Into<String>,
776        prefix_iri: impl Into<String>,
777    ) -> Result<Self, IriParseError> {
778        self.prefixes.insert(
779            Iri::parse(prefix_iri.into())?.into_inner(),
780            prefix_name.into(),
781        );
782        Ok(self)
783    }
784
785    /// Adds a base IRI to the serialization.
786    ///
787    /// ```
788    /// use oxrdf::vocab::rdf;
789    /// use oxrdf::{NamedNodeRef, QuadRef};
790    /// use oxttl::TriGSerializer;
791    ///
792    /// let mut serializer = TriGSerializer::new()
793    ///     .with_base_iri("http://example.com")?
794    ///     .with_prefix("ex", "http://example.com/ns#")?
795    ///     .for_writer(Vec::new());
796    /// serializer.serialize_quad(QuadRef::new(
797    ///     NamedNodeRef::new("http://example.com/me")?,
798    ///     rdf::TYPE,
799    ///     NamedNodeRef::new("http://example.com/ns#Person")?,
800    ///     NamedNodeRef::new("http://example.com")?,
801    /// ))?;
802    /// assert_eq!(
803    ///     b"@base <http://example.com> .\n@prefix ex: </ns#> .\n<> {\n\t</me> a ex:Person .\n}\n",
804    ///     serializer.finish()?.as_slice()
805    /// );
806    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
807    /// ```
808    #[inline]
809    pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
810        self.base_iri = Some(Iri::parse(base_iri.into())?);
811        Ok(self)
812    }
813
814    /// Writes a TriG file to a [`Write`] implementation.
815    ///
816    /// ```
817    /// use oxrdf::{NamedNodeRef, QuadRef};
818    /// use oxrdf::vocab::rdf;
819    /// use oxttl::TriGSerializer;
820    ///
821    /// let mut serializer = TriGSerializer::new()
822    ///     .with_prefix("schema", "http://schema.org/")?
823    ///     .for_writer(Vec::new());
824    /// serializer.serialize_quad(QuadRef::new(
825    ///     NamedNodeRef::new("http://example.com#me")?,
826    ///     rdf::TYPE,
827    ///     NamedNodeRef::new("http://schema.org/Person")?,
828    ///     NamedNodeRef::new("http://example.com")?,
829    /// ))?;
830    /// assert_eq!(
831    ///     b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
832    ///     serializer.finish()?.as_slice()
833    /// );
834    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
835    /// ```
836    pub fn for_writer<W: Write>(self, writer: W) -> WriterTriGSerializer<W> {
837        WriterTriGSerializer {
838            writer,
839            low_level_writer: self.low_level(),
840        }
841    }
842
843    /// Writes a TriG file to a [`AsyncWrite`] implementation.
844    ///
845    /// ```
846    /// # #[tokio::main(flavor = "current_thread")]
847    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
848    /// use oxrdf::{NamedNodeRef, QuadRef};
849    /// use oxrdf::vocab::rdf;
850    /// use oxttl::TriGSerializer;
851    ///
852    /// let mut serializer = TriGSerializer::new()
853    ///     .with_prefix("schema", "http://schema.org/")?
854    ///     .for_tokio_async_writer(Vec::new());
855    /// serializer
856    ///     .serialize_quad(QuadRef::new(
857    ///         NamedNodeRef::new("http://example.com#me")?,
858    ///         rdf::TYPE,
859    ///         NamedNodeRef::new("http://schema.org/Person")?,
860    ///         NamedNodeRef::new("http://example.com")?,
861    ///     ))
862    ///     .await?;
863    /// assert_eq!(
864    ///     b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
865    ///     serializer.finish().await?.as_slice()
866    /// );
867    /// # Ok(())
868    /// # }
869    /// ```
870    #[cfg(feature = "async-tokio")]
871    pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
872        self,
873        writer: W,
874    ) -> TokioAsyncWriterTriGSerializer<W> {
875        TokioAsyncWriterTriGSerializer {
876            writer,
877            low_level_writer: self.low_level(),
878            buffer: Vec::new(),
879        }
880    }
881
882    /// Builds a low-level TriG writer.
883    ///
884    /// ```
885    /// use oxrdf::{NamedNodeRef, QuadRef};
886    /// use oxrdf::vocab::rdf;
887    /// use oxttl::TriGSerializer;
888    ///
889    /// let mut buf = Vec::new();
890    /// let mut serializer = TriGSerializer::new()
891    ///     .with_prefix("schema", "http://schema.org/")?
892    ///     .low_level();
893    /// serializer.serialize_quad(
894    ///     QuadRef::new(
895    ///         NamedNodeRef::new("http://example.com#me")?,
896    ///         rdf::TYPE,
897    ///         NamedNodeRef::new("http://schema.org/Person")?,
898    ///         NamedNodeRef::new("http://example.com")?,
899    ///     ),
900    ///     &mut buf,
901    /// )?;
902    /// serializer.finish(&mut buf)?;
903    /// assert_eq!(
904    ///     b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
905    ///     buf.as_slice()
906    /// );
907    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
908    /// ```
909    pub fn low_level(self) -> LowLevelTriGSerializer {
910        LowLevelTriGSerializer {
911            prefixes: self.prefixes,
912            base_iri: self.base_iri,
913            prelude_written: false,
914            current_graph_name: GraphName::DefaultGraph,
915            current_subject_predicate: None,
916        }
917    }
918}
919
920/// Writes a TriG file to a [`Write`] implementation.
921///
922/// Can be built using [`TriGSerializer::for_writer`].
923///
924/// ```
925/// use oxrdf::{NamedNodeRef, QuadRef};
926/// use oxrdf::vocab::rdf;
927/// use oxttl::TriGSerializer;
928///
929/// let mut serializer = TriGSerializer::new()
930///     .with_prefix("schema", "http://schema.org/")?
931///     .for_writer(Vec::new());
932/// serializer.serialize_quad(QuadRef::new(
933///     NamedNodeRef::new("http://example.com#me")?,
934///     rdf::TYPE,
935///     NamedNodeRef::new("http://schema.org/Person")?,
936///     NamedNodeRef::new("http://example.com")?,
937/// ))?;
938/// assert_eq!(
939///     b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
940///     serializer.finish()?.as_slice()
941/// );
942/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
943/// ```
944#[must_use]
945pub struct WriterTriGSerializer<W: Write> {
946    writer: W,
947    low_level_writer: LowLevelTriGSerializer,
948}
949
950impl<W: Write> WriterTriGSerializer<W> {
951    /// Writes an extra quad.
952    pub fn serialize_quad<'a>(&mut self, q: impl Into<QuadRef<'a>>) -> io::Result<()> {
953        self.low_level_writer.serialize_quad(q, &mut self.writer)
954    }
955
956    /// Ends the write process and returns the underlying [`Write`].
957    pub fn finish(mut self) -> io::Result<W> {
958        self.low_level_writer.finish(&mut self.writer)?;
959        Ok(self.writer)
960    }
961}
962
963/// Writes a TriG file to a [`AsyncWrite`] implementation.
964///
965/// Can be built using [`TriGSerializer::for_tokio_async_writer`].
966///
967/// ```
968/// # #[tokio::main(flavor = "current_thread")]
969/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
970/// use oxrdf::{NamedNodeRef, QuadRef};
971/// use oxrdf::vocab::rdf;
972/// use oxttl::TriGSerializer;
973///
974/// let mut serializer = TriGSerializer::new()
975///     .with_prefix("schema", "http://schema.org/")?
976///     .for_tokio_async_writer(Vec::new());
977/// serializer
978///     .serialize_quad(QuadRef::new(
979///         NamedNodeRef::new("http://example.com#me")?,
980///         rdf::TYPE,
981///         NamedNodeRef::new("http://schema.org/Person")?,
982///         NamedNodeRef::new("http://example.com")?,
983///     ))
984///     .await?;
985/// assert_eq!(
986///     b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
987///     serializer.finish().await?.as_slice()
988/// );
989/// # Ok(())
990/// # }
991/// ```
992#[cfg(feature = "async-tokio")]
993#[must_use]
994pub struct TokioAsyncWriterTriGSerializer<W: AsyncWrite + Unpin> {
995    writer: W,
996    low_level_writer: LowLevelTriGSerializer,
997    buffer: Vec<u8>,
998}
999
1000#[cfg(feature = "async-tokio")]
1001impl<W: AsyncWrite + Unpin> TokioAsyncWriterTriGSerializer<W> {
1002    /// Writes an extra quad.
1003    pub async fn serialize_quad<'a>(&mut self, q: impl Into<QuadRef<'a>>) -> io::Result<()> {
1004        self.low_level_writer.serialize_quad(q, &mut self.buffer)?;
1005        self.writer.write_all(&self.buffer).await?;
1006        self.buffer.clear();
1007        Ok(())
1008    }
1009
1010    /// Ends the write process and returns the underlying [`Write`].
1011    pub async fn finish(mut self) -> io::Result<W> {
1012        self.low_level_writer.finish(&mut self.buffer)?;
1013        self.writer.write_all(&self.buffer).await?;
1014        self.buffer.clear();
1015        Ok(self.writer)
1016    }
1017}
1018
1019/// Writes a TriG file by using a low-level API.
1020///
1021/// Can be built using [`TriGSerializer::low_level`].
1022///
1023/// ```
1024/// use oxrdf::{NamedNodeRef, QuadRef};
1025/// use oxrdf::vocab::rdf;
1026/// use oxttl::TriGSerializer;
1027///
1028/// let mut buf = Vec::new();
1029/// let mut serializer = TriGSerializer::new()
1030///     .with_prefix("schema", "http://schema.org/")?
1031///     .low_level();
1032/// serializer.serialize_quad(
1033///     QuadRef::new(
1034///         NamedNodeRef::new("http://example.com#me")?,
1035///         rdf::TYPE,
1036///         NamedNodeRef::new("http://schema.org/Person")?,
1037///         NamedNodeRef::new("http://example.com")?,
1038///     ),
1039///     &mut buf,
1040/// )?;
1041/// serializer.finish(&mut buf)?;
1042/// assert_eq!(
1043///     b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
1044///     buf.as_slice()
1045/// );
1046/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
1047/// ```
1048pub struct LowLevelTriGSerializer {
1049    prefixes: BTreeMap<String, String>,
1050    base_iri: Option<Iri<String>>,
1051    prelude_written: bool,
1052    current_graph_name: GraphName,
1053    current_subject_predicate: Option<(Subject, NamedNode)>,
1054}
1055
1056impl LowLevelTriGSerializer {
1057    /// Writes an extra quad.
1058    pub fn serialize_quad<'a>(
1059        &mut self,
1060        q: impl Into<QuadRef<'a>>,
1061        mut writer: impl Write,
1062    ) -> io::Result<()> {
1063        if !self.prelude_written {
1064            self.prelude_written = true;
1065            if let Some(base_iri) = &self.base_iri {
1066                writeln!(writer, "@base <{base_iri}> .")?;
1067            }
1068            for (prefix_iri, prefix_name) in &self.prefixes {
1069                writeln!(
1070                    writer,
1071                    "@prefix {prefix_name}: <{}> .",
1072                    relative_iri(prefix_iri, &self.base_iri)
1073                )?;
1074            }
1075        }
1076        let q = q.into();
1077        if q.graph_name == self.current_graph_name.as_ref() {
1078            if let Some((current_subject, current_predicate)) =
1079                self.current_subject_predicate.take()
1080            {
1081                if q.subject == current_subject.as_ref() {
1082                    if q.predicate == current_predicate {
1083                        self.current_subject_predicate = Some((current_subject, current_predicate));
1084                        write!(writer, " , {}", self.term(q.object))
1085                    } else {
1086                        self.current_subject_predicate =
1087                            Some((current_subject, q.predicate.into_owned()));
1088                        writeln!(writer, " ;")?;
1089                        if !self.current_graph_name.is_default_graph() {
1090                            write!(writer, "\t")?;
1091                        }
1092                        write!(
1093                            writer,
1094                            "\t{} {}",
1095                            self.predicate(q.predicate),
1096                            self.term(q.object)
1097                        )
1098                    }
1099                } else {
1100                    self.current_subject_predicate =
1101                        Some((q.subject.into_owned(), q.predicate.into_owned()));
1102                    writeln!(writer, " .")?;
1103                    if !self.current_graph_name.is_default_graph() {
1104                        write!(writer, "\t")?;
1105                    }
1106                    write!(
1107                        writer,
1108                        "{} {} {}",
1109                        self.term(q.subject),
1110                        self.predicate(q.predicate),
1111                        self.term(q.object)
1112                    )
1113                }
1114            } else {
1115                self.current_subject_predicate =
1116                    Some((q.subject.into_owned(), q.predicate.into_owned()));
1117                if !self.current_graph_name.is_default_graph() {
1118                    write!(writer, "\t")?;
1119                }
1120                write!(
1121                    writer,
1122                    "{} {} {}",
1123                    self.term(q.subject),
1124                    self.predicate(q.predicate),
1125                    self.term(q.object)
1126                )
1127            }
1128        } else {
1129            if self.current_subject_predicate.is_some() {
1130                writeln!(writer, " .")?;
1131            }
1132            if !self.current_graph_name.is_default_graph() {
1133                writeln!(writer, "}}")?;
1134            }
1135            self.current_graph_name = q.graph_name.into_owned();
1136            self.current_subject_predicate =
1137                Some((q.subject.into_owned(), q.predicate.into_owned()));
1138            match self.current_graph_name.as_ref() {
1139                GraphNameRef::NamedNode(g) => {
1140                    writeln!(writer, "{} {{", self.term(g))?;
1141                    write!(writer, "\t")?;
1142                }
1143                GraphNameRef::BlankNode(g) => {
1144                    writeln!(writer, "{} {{", self.term(g))?;
1145                    write!(writer, "\t")?;
1146                }
1147                GraphNameRef::DefaultGraph => (),
1148            }
1149
1150            write!(
1151                writer,
1152                "{} {} {}",
1153                self.term(q.subject),
1154                self.predicate(q.predicate),
1155                self.term(q.object)
1156            )
1157        }
1158    }
1159
1160    fn predicate<'a>(&'a self, named_node: impl Into<NamedNodeRef<'a>>) -> TurtlePredicate<'a> {
1161        TurtlePredicate {
1162            named_node: named_node.into(),
1163            prefixes: &self.prefixes,
1164            base_iri: &self.base_iri,
1165        }
1166    }
1167
1168    fn term<'a>(&'a self, term: impl Into<TermRef<'a>>) -> TurtleTerm<'a> {
1169        TurtleTerm {
1170            term: term.into(),
1171            prefixes: &self.prefixes,
1172            base_iri: &self.base_iri,
1173        }
1174    }
1175
1176    /// Finishes to write the file.
1177    pub fn finish(&mut self, mut writer: impl Write) -> io::Result<()> {
1178        if self.current_subject_predicate.is_some() {
1179            writeln!(writer, " .")?;
1180        }
1181        if !self.current_graph_name.is_default_graph() {
1182            writeln!(writer, "}}")?;
1183        }
1184        Ok(())
1185    }
1186}
1187
1188struct TurtlePredicate<'a> {
1189    named_node: NamedNodeRef<'a>,
1190    prefixes: &'a BTreeMap<String, String>,
1191    base_iri: &'a Option<Iri<String>>,
1192}
1193
1194impl fmt::Display for TurtlePredicate<'_> {
1195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1196        if self.named_node == rdf::TYPE {
1197            f.write_str("a")
1198        } else {
1199            TurtleTerm {
1200                term: self.named_node.into(),
1201                prefixes: self.prefixes,
1202                base_iri: self.base_iri,
1203            }
1204            .fmt(f)
1205        }
1206    }
1207}
1208
1209struct TurtleTerm<'a> {
1210    term: TermRef<'a>,
1211    prefixes: &'a BTreeMap<String, String>,
1212    base_iri: &'a Option<Iri<String>>,
1213}
1214
1215impl fmt::Display for TurtleTerm<'_> {
1216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1217        match self.term {
1218            TermRef::NamedNode(v) => {
1219                for (prefix_iri, prefix_name) in self.prefixes {
1220                    if let Some(local_name) = v.as_str().strip_prefix(prefix_iri) {
1221                        if local_name.is_empty() {
1222                            return write!(f, "{prefix_name}:");
1223                        } else if let Some(escaped_local_name) = escape_local_name(local_name) {
1224                            return write!(f, "{prefix_name}:{escaped_local_name}");
1225                        }
1226                    }
1227                }
1228                write!(f, "<{}>", relative_iri(v.as_str(), self.base_iri))
1229            }
1230            TermRef::BlankNode(v) => write!(f, "{v}"),
1231            TermRef::Literal(v) => {
1232                let value = v.value();
1233                let inline = match v.datatype() {
1234                    xsd::BOOLEAN => is_turtle_boolean(value),
1235                    xsd::INTEGER => is_turtle_integer(value),
1236                    xsd::DECIMAL => is_turtle_decimal(value),
1237                    xsd::DOUBLE => is_turtle_double(value),
1238                    _ => false,
1239                };
1240                if inline {
1241                    f.write_str(value)
1242                } else if v.is_plain() {
1243                    write!(f, "{v}")
1244                } else {
1245                    write!(
1246                        f,
1247                        "{}^^{}",
1248                        LiteralRef::new_simple_literal(v.value()),
1249                        TurtleTerm {
1250                            term: v.datatype().into(),
1251                            prefixes: self.prefixes,
1252                            base_iri: self.base_iri,
1253                        }
1254                    )
1255                }
1256            }
1257            #[cfg(feature = "rdf-star")]
1258            TermRef::Triple(t) => {
1259                write!(
1260                    f,
1261                    "<< {} {} {} >>",
1262                    TurtleTerm {
1263                        term: t.subject.as_ref().into(),
1264                        prefixes: self.prefixes,
1265                        base_iri: self.base_iri,
1266                    },
1267                    TurtleTerm {
1268                        term: t.predicate.as_ref().into(),
1269                        prefixes: self.prefixes,
1270                        base_iri: self.base_iri,
1271                    },
1272                    TurtleTerm {
1273                        term: t.object.as_ref(),
1274                        prefixes: self.prefixes,
1275                        base_iri: self.base_iri,
1276                    }
1277                )
1278            }
1279        }
1280    }
1281}
1282
1283fn relative_iri<'a>(iri: &'a str, base_iri: &Option<Iri<String>>) -> Cow<'a, str> {
1284    if let Some(base_iri) = base_iri {
1285        if let Ok(relative) = base_iri.relativize(&Iri::parse_unchecked(iri)) {
1286            return relative.into_inner().into();
1287        }
1288    }
1289    iri.into()
1290}
1291
1292fn is_turtle_boolean(value: &str) -> bool {
1293    matches!(value, "true" | "false")
1294}
1295
1296fn is_turtle_integer(value: &str) -> bool {
1297    // [19]  INTEGER  ::=  [+-]? [0-9]+
1298    let mut value = value.as_bytes();
1299    if let Some(v) = value.strip_prefix(b"+") {
1300        value = v;
1301    } else if let Some(v) = value.strip_prefix(b"-") {
1302        value = v;
1303    }
1304    !value.is_empty() && value.iter().all(u8::is_ascii_digit)
1305}
1306
1307fn is_turtle_decimal(value: &str) -> bool {
1308    // [20]  DECIMAL  ::=  [+-]? [0-9]* '.' [0-9]+
1309    let mut value = value.as_bytes();
1310    if let Some(v) = value.strip_prefix(b"+") {
1311        value = v;
1312    } else if let Some(v) = value.strip_prefix(b"-") {
1313        value = v;
1314    }
1315    while value.first().is_some_and(u8::is_ascii_digit) {
1316        value = &value[1..];
1317    }
1318    let Some(value) = value.strip_prefix(b".") else {
1319        return false;
1320    };
1321    !value.is_empty() && value.iter().all(u8::is_ascii_digit)
1322}
1323
1324fn is_turtle_double(value: &str) -> bool {
1325    // [21]    DOUBLE    ::=  [+-]? ([0-9]+ '.' [0-9]* EXPONENT | '.' [0-9]+ EXPONENT | [0-9]+ EXPONENT)
1326    // [154s]  EXPONENT  ::=  [eE] [+-]? [0-9]+
1327    let mut value = value.as_bytes();
1328    if let Some(v) = value.strip_prefix(b"+") {
1329        value = v;
1330    } else if let Some(v) = value.strip_prefix(b"-") {
1331        value = v;
1332    }
1333    let mut with_before = false;
1334    while value.first().is_some_and(u8::is_ascii_digit) {
1335        value = &value[1..];
1336        with_before = true;
1337    }
1338    let mut with_after = false;
1339    if let Some(v) = value.strip_prefix(b".") {
1340        value = v;
1341        while value.first().is_some_and(u8::is_ascii_digit) {
1342            value = &value[1..];
1343            with_after = true;
1344        }
1345    }
1346    if let Some(v) = value.strip_prefix(b"e") {
1347        value = v;
1348    } else if let Some(v) = value.strip_prefix(b"E") {
1349        value = v;
1350    } else {
1351        return false;
1352    }
1353    if let Some(v) = value.strip_prefix(b"+") {
1354        value = v;
1355    } else if let Some(v) = value.strip_prefix(b"-") {
1356        value = v;
1357    }
1358    (with_before || with_after) && !value.is_empty() && value.iter().all(u8::is_ascii_digit)
1359}
1360
1361fn escape_local_name(value: &str) -> Option<String> {
1362    // TODO: PLX
1363    // [168s] 	PN_LOCAL 	::= 	(PN_CHARS_U | ':' | [0-9] | PLX) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX))?
1364    let mut output = String::with_capacity(value.len());
1365    let mut chars = value.chars();
1366    let first = chars.next()?;
1367    if N3Lexer::is_possible_pn_chars_u(first) || first == ':' || first.is_ascii_digit() {
1368        output.push(first);
1369    } else if can_be_escaped_in_local_name(first) {
1370        output.push('\\');
1371        output.push(first);
1372    } else {
1373        return None;
1374    }
1375
1376    while let Some(c) = chars.next() {
1377        if N3Lexer::is_possible_pn_chars(c) || c == ':' || (c == '.' && !chars.as_str().is_empty())
1378        {
1379            output.push(c);
1380        } else if can_be_escaped_in_local_name(c) {
1381            output.push('\\');
1382            output.push(c);
1383        } else {
1384            return None;
1385        }
1386    }
1387
1388    Some(output)
1389}
1390
1391fn can_be_escaped_in_local_name(c: char) -> bool {
1392    matches!(
1393        c,
1394        '_' | '~'
1395            | '.'
1396            | '-'
1397            | '!'
1398            | '$'
1399            | '&'
1400            | '\''
1401            | '('
1402            | ')'
1403            | '*'
1404            | '+'
1405            | ','
1406            | ';'
1407            | '='
1408            | '/'
1409            | '?'
1410            | '#'
1411            | '@'
1412            | '%'
1413    )
1414}
1415
1416#[cfg(test)]
1417#[allow(clippy::panic_in_result_fn)]
1418mod tests {
1419    use super::*;
1420    use oxrdf::BlankNodeRef;
1421
1422    #[test]
1423    fn test_write() -> io::Result<()> {
1424        let mut serializer = TriGSerializer::new()
1425            .with_prefix("ex", "http://example.com/")
1426            .unwrap()
1427            .for_writer(Vec::new());
1428        serializer.serialize_quad(QuadRef::new(
1429            NamedNodeRef::new_unchecked("http://example.com/s"),
1430            NamedNodeRef::new_unchecked("http://example.com/p"),
1431            NamedNodeRef::new_unchecked("http://example.com/o."),
1432            NamedNodeRef::new_unchecked("http://example.com/g"),
1433        ))?;
1434        serializer.serialize_quad(QuadRef::new(
1435            NamedNodeRef::new_unchecked("http://example.com/s"),
1436            NamedNodeRef::new_unchecked("http://example.com/p"),
1437            NamedNodeRef::new_unchecked("http://example.com/o{o}"),
1438            NamedNodeRef::new_unchecked("http://example.com/g"),
1439        ))?;
1440        serializer.serialize_quad(QuadRef::new(
1441            NamedNodeRef::new_unchecked("http://example.com/s"),
1442            NamedNodeRef::new_unchecked("http://example.com/p"),
1443            NamedNodeRef::new_unchecked("http://example.com/"),
1444            NamedNodeRef::new_unchecked("http://example.com/g"),
1445        ))?;
1446        serializer.serialize_quad(QuadRef::new(
1447            NamedNodeRef::new_unchecked("http://example.com/s"),
1448            NamedNodeRef::new_unchecked("http://example.com/p"),
1449            LiteralRef::new_simple_literal("foo"),
1450            NamedNodeRef::new_unchecked("http://example.com/g"),
1451        ))?;
1452        serializer.serialize_quad(QuadRef::new(
1453            NamedNodeRef::new_unchecked("http://example.com/s"),
1454            NamedNodeRef::new_unchecked("http://example.com/p2"),
1455            LiteralRef::new_language_tagged_literal_unchecked("foo", "en"),
1456            NamedNodeRef::new_unchecked("http://example.com/g"),
1457        ))?;
1458        serializer.serialize_quad(QuadRef::new(
1459            BlankNodeRef::new_unchecked("b"),
1460            NamedNodeRef::new_unchecked("http://example.com/p2"),
1461            BlankNodeRef::new_unchecked("b2"),
1462            NamedNodeRef::new_unchecked("http://example.com/g"),
1463        ))?;
1464        serializer.serialize_quad(QuadRef::new(
1465            BlankNodeRef::new_unchecked("b"),
1466            NamedNodeRef::new_unchecked("http://example.com/p2"),
1467            LiteralRef::new_typed_literal("true", xsd::BOOLEAN),
1468            GraphNameRef::DefaultGraph,
1469        ))?;
1470        serializer.serialize_quad(QuadRef::new(
1471            BlankNodeRef::new_unchecked("b"),
1472            NamedNodeRef::new_unchecked("http://example.org/p2"),
1473            LiteralRef::new_typed_literal("false", xsd::BOOLEAN),
1474            NamedNodeRef::new_unchecked("http://example.com/g2"),
1475        ))?;
1476        assert_eq!(
1477            String::from_utf8(serializer.finish()?).unwrap(),
1478            "@prefix ex: <http://example.com/> .\nex:g {\n\tex:s ex:p ex:o\\. , <http://example.com/o{o}> , ex: , \"foo\" ;\n\t\tex:p2 \"foo\"@en .\n\t_:b ex:p2 _:b2 .\n}\n_:b ex:p2 true .\nex:g2 {\n\t_:b <http://example.org/p2> false .\n}\n"
1479        );
1480        Ok(())
1481    }
1482}