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