Skip to main content

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