diff --git a/lib/oxrdfio/src/parser.rs b/lib/oxrdfio/src/parser.rs index 0f6d11ac..766be9ce 100644 --- a/lib/oxrdfio/src/parser.rs +++ b/lib/oxrdfio/src/parser.rs @@ -8,7 +8,7 @@ use oxrdfxml::FromTokioAsyncReadRdfXmlReader; use oxrdfxml::{FromReadRdfXmlReader, RdfXmlParser}; #[cfg(feature = "async-tokio")] use oxttl::n3::FromTokioAsyncReadN3Reader; -use oxttl::n3::{FromReadN3Reader, N3Parser, N3Quad, N3Term}; +use oxttl::n3::{FromReadN3Reader, N3Parser, N3PrefixesIter, N3Quad, N3Term}; #[cfg(feature = "async-tokio")] use oxttl::nquads::FromTokioAsyncReadNQuadsReader; use oxttl::nquads::{FromReadNQuadsReader, NQuadsParser}; @@ -17,10 +17,10 @@ use oxttl::ntriples::FromTokioAsyncReadNTriplesReader; use oxttl::ntriples::{FromReadNTriplesReader, NTriplesParser}; #[cfg(feature = "async-tokio")] use oxttl::trig::FromTokioAsyncReadTriGReader; -use oxttl::trig::{FromReadTriGReader, TriGParser}; +use oxttl::trig::{FromReadTriGReader, TriGParser, TriGPrefixesIter}; #[cfg(feature = "async-tokio")] use oxttl::turtle::FromTokioAsyncReadTurtleReader; -use oxttl::turtle::{FromReadTurtleReader, TurtleParser}; +use oxttl::turtle::{FromReadTurtleReader, TurtleParser, TurtlePrefixesIter}; use std::collections::HashMap; use std::io::Read; #[cfg(feature = "async-tokio")] @@ -428,6 +428,77 @@ impl Iterator for FromReadQuadReader { } } +impl FromReadQuadReader { + /// The list of IRI prefixes considered at the current step of the parsing. + /// + /// This method returns (prefix name, prefix value) tuples. + /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. + /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). + /// + /// An empty iterator is return if the format does not support prefixes. + /// + /// ``` + /// use oxrdfio::{RdfFormat, RdfParser}; + /// + /// let file = br#"@base . + /// @prefix schema: . + /// a schema:Person ; + /// schema:name "Foo" ."#; + /// + /// let mut reader = RdfParser::from_format(RdfFormat::Turtle).parse_read(file.as_slice()); + /// assert!(reader.prefixes().collect::>().is_empty()); // No prefix at the beginning + /// + /// reader.next().unwrap()?; // We read the first triple + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes + /// # Result::<_,Box>::Ok(()) + /// ``` + pub fn prefixes(&self) -> PrefixesIter<'_> { + PrefixesIter { + inner: match &self.parser { + FromReadQuadReaderKind::N3(p) => PrefixesIterKind::N3(p.prefixes()), + FromReadQuadReaderKind::TriG(p) => PrefixesIterKind::TriG(p.prefixes()), + FromReadQuadReaderKind::Turtle(p) => PrefixesIterKind::Turtle(p.prefixes()), + FromReadQuadReaderKind::NQuads(_) + | FromReadQuadReaderKind::NTriples(_) + | FromReadQuadReaderKind::RdfXml(_) => PrefixesIterKind::None, /* TODO: implement for RDF/XML */ + }, + } + } + + /// The base IRI considered at the current step of the parsing. + /// + /// `None` is returned if no base IRI is set or the format does not support base IRIs. + /// + /// ``` + /// use oxrdfio::{RdfFormat, RdfParser}; + /// + /// let file = br#"@base . + /// @prefix schema: . + /// a schema:Person ; + /// schema:name "Foo" ."#; + /// + /// let mut reader = RdfParser::from_format(RdfFormat::Turtle).parse_read(file.as_slice()); + /// assert!(reader.base_iri().is_none()); // No base at the beginning because none has been given to the parser. + /// + /// reader.next().unwrap()?; // We read the first triple + /// assert_eq!(reader.base_iri(), Some("http://example.com/")); // There is now a base IRI. + /// # Result::<_,Box>::Ok(()) + /// ``` + pub fn base_iri(&self) -> Option<&str> { + match &self.parser { + FromReadQuadReaderKind::N3(p) => p.base_iri(), + FromReadQuadReaderKind::TriG(p) => p.base_iri(), + FromReadQuadReaderKind::Turtle(p) => p.base_iri(), + FromReadQuadReaderKind::NQuads(_) + | FromReadQuadReaderKind::NTriples(_) + | FromReadQuadReaderKind::RdfXml(_) => None, // TODO: implement for RDF/XML + } + } +} + /// Parses a RDF file from a Tokio [`AsyncRead`] implementation. Can be built using [`RdfParser::parse_tokio_async_read`]. /// /// Reads are buffered. @@ -494,6 +565,120 @@ impl FromTokioAsyncReadQuadReader { }, }) } + + /// The list of IRI prefixes considered at the current step of the parsing. + /// + /// This method returns (prefix name, prefix value) tuples. + /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. + /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). + /// + /// An empty iterator is return if the format does not support prefixes. + /// + /// ``` + /// use oxrdfio::{RdfFormat, RdfParser}; + /// + /// # #[tokio::main(flavor = "current_thread")] + /// # async fn main() -> Result<(), oxttl::ParseError> { + /// let file = br#"@base . + /// @prefix schema: . + /// a schema:Person ; + /// schema:name "Foo" ."#; + /// + /// let mut reader = RdfParser::from_format(RdfFormat::Turtle).parse_read(file.as_slice()); + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning + /// + /// reader.next().await.unwrap()?; // We read the first triple + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes + /// # Ok(()) + /// # } + /// ``` + pub fn prefixes(&self) -> PrefixesIter<'_> { + PrefixesIter { + inner: match &self.parser { + FromReadQuadReaderKind::N3(p) => PrefixesIterKind::N3(p.prefixes()), + FromReadQuadReaderKind::TriG(p) => PrefixesIterKind::TriG(p.prefixes()), + FromReadQuadReaderKind::Turtle(p) => PrefixesIterKind::Turtle(p.prefixes()), + FromReadQuadReaderKind::NQuads(_) + | FromReadQuadReaderKind::NTriples(_) + | FromReadQuadReaderKind::RdfXml(_) => PrefixesIterKind::None, /* TODO: implement for RDF/XML */ + }, + } + } + + /// The base IRI considered at the current step of the parsing. + /// + /// `None` is returned if no base IRI is set or the format does not support base IRIs. + /// + /// ``` + /// use oxrdfio::{RdfFormat, RdfParser}; + /// + /// # #[tokio::main(flavor = "current_thread")] + /// # async fn main() -> Result<(), oxttl::ParseError> { + /// let file = br#"@base . + /// @prefix schema: . + /// a schema:Person ; + /// schema:name "Foo" ."#; + /// + /// let mut reader = + /// RdfParser::from_format(RdfFormat::Turtle).parse_tokio_async_read(file.as_slice()); + /// assert!(reader.base_iri().is_none()); // No base IRI at the beginning + /// + /// reader.next().await.unwrap()?; // We read the first triple + /// assert_eq!(reader.base_iri(), Some("http://example.com/")); // There is now a base IRI + /// # Ok(()) + /// # } + /// ``` + pub fn base_iri(&self) -> Option<&str> { + match &self.parser { + FromReadQuadReaderKind::N3(p) => p.base_iri(), + FromReadQuadReaderKind::TriG(p) => p.base_iri(), + FromReadQuadReaderKind::Turtle(p) => p.base_iri(), + FromReadQuadReaderKind::NQuads(_) + | FromReadQuadReaderKind::NTriples(_) + | FromReadQuadReaderKind::RdfXml(_) => None, // TODO: implement for RDF/XML + } + } +} + +/// Iterator on the file prefixes. +/// +/// See [`FromReadQuadReader::prefixes`]. +pub struct PrefixesIter<'a> { + inner: PrefixesIterKind<'a>, +} + +enum PrefixesIterKind<'a> { + Turtle(TurtlePrefixesIter<'a>), + TriG(TriGPrefixesIter<'a>), + N3(N3PrefixesIter<'a>), + None, +} + +impl<'a> Iterator for PrefixesIter<'a> { + type Item = (&'a str, &'a str); + + #[inline] + fn next(&mut self) -> Option { + match &mut self.inner { + PrefixesIterKind::Turtle(iter) => iter.next(), + PrefixesIterKind::TriG(iter) => iter.next(), + PrefixesIterKind::N3(iter) => iter.next(), + PrefixesIterKind::None => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match &self.inner { + PrefixesIterKind::Turtle(iter) => iter.size_hint(), + PrefixesIterKind::TriG(iter) => iter.size_hint(), + PrefixesIterKind::N3(iter) => iter.size_hint(), + PrefixesIterKind::None => (0, Some(0)), + } + } } struct QuadMapper { diff --git a/lib/oxttl/src/n3.rs b/lib/oxttl/src/n3.rs index d4241b7d..233edccc 100644 --- a/lib/oxttl/src/n3.rs +++ b/lib/oxttl/src/n3.rs @@ -15,6 +15,7 @@ use oxrdf::{ BlankNode, GraphName, Literal, NamedNode, NamedNodeRef, NamedOrBlankNode, Quad, Subject, Term, Variable, }; +use std::collections::hash_map::Iter; use std::collections::HashMap; use std::fmt; use std::io::Read; @@ -403,7 +404,7 @@ pub struct FromReadN3Reader { impl FromReadN3Reader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -416,14 +417,19 @@ impl FromReadN3Reader { /// schema:name "Foo" ."#; /// /// let mut reader = N3Parser::new().parse_read(file.as_ref()); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning /// /// reader.next().unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Result::<_,Box>::Ok(()) /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.inner.parser.context.prefixes + pub fn prefixes(&self) -> N3PrefixesIter<'_> { + N3PrefixesIter { + inner: self.inner.parser.context.prefixes.iter(), + } } /// The base IRI considered at the current step of the parsing. @@ -508,7 +514,7 @@ impl FromTokioAsyncReadN3Reader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -523,15 +529,20 @@ impl FromTokioAsyncReadN3Reader { /// schema:name "Foo" ."#; /// /// let mut reader = N3Parser::new().parse_tokio_async_read(file.as_ref()); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning /// /// reader.next().await.unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Ok(()) /// # } /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.inner.parser.context.prefixes + pub fn prefixes(&self) -> N3PrefixesIter<'_> { + N3PrefixesIter { + inner: self.inner.parser.context.prefixes.iter(), + } } /// The base IRI considered at the current step of the parsing. @@ -636,7 +647,7 @@ impl LowLevelN3Reader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -650,14 +661,19 @@ impl LowLevelN3Reader { /// /// let mut reader = N3Parser::new().parse(); /// reader.extend_from_slice(file); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning /// /// reader.read_next().unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Result::<_,Box>::Ok(()) /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.parser.context.prefixes + pub fn prefixes(&self) -> N3PrefixesIter<'_> { + N3PrefixesIter { + inner: self.parser.context.prefixes.iter(), + } } /// The base IRI considered at the current step of the parsing. @@ -1299,3 +1315,25 @@ enum N3State { FormulaContent, FormulaContentExpectDot, } + +/// Iterator on the file prefixes. +/// +/// See [`LowLevelN3Reader::prefixes`]. +pub struct N3PrefixesIter<'a> { + inner: Iter<'a, String, Iri>, +} + +impl<'a> Iterator for N3PrefixesIter<'a> { + type Item = (&'a str, &'a str); + + #[inline] + fn next(&mut self) -> Option { + let (key, value) = self.inner.next()?; + Some((key.as_str(), value.as_str())) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/lib/oxttl/src/terse.rs b/lib/oxttl/src/terse.rs index 7aa3e103..a8a72c1a 100644 --- a/lib/oxttl/src/terse.rs +++ b/lib/oxttl/src/terse.rs @@ -8,6 +8,7 @@ use oxrdf::vocab::{rdf, xsd}; #[cfg(feature = "rdf-star")] use oxrdf::Triple; use oxrdf::{BlankNode, GraphName, Literal, NamedNode, NamedOrBlankNode, Quad, Subject, Term}; +use std::collections::hash_map::Iter; use std::collections::HashMap; pub struct TriGRecognizer { @@ -24,7 +25,13 @@ pub struct TriGRecognizerContext { pub with_graph_name: bool, #[cfg(feature = "rdf-star")] pub with_quoted_triples: bool, - pub prefixes: HashMap>, + prefixes: HashMap>, +} + +impl TriGRecognizerContext { + pub fn prefixes(&self) -> Iter<'_, String, Iri> { + self.prefixes.iter() + } } impl RuleRecognizer for TriGRecognizer { diff --git a/lib/oxttl/src/trig.rs b/lib/oxttl/src/trig.rs index 5a7cdb4a..77decffd 100644 --- a/lib/oxttl/src/trig.rs +++ b/lib/oxttl/src/trig.rs @@ -8,6 +8,7 @@ use crate::toolkit::{FromReadIterator, ParseError, Parser, SyntaxError}; use oxiri::{Iri, IriParseError}; use oxrdf::vocab::xsd; use oxrdf::{GraphName, NamedNode, Quad, QuadRef, Subject, TermRef}; +use std::collections::hash_map::Iter; use std::collections::HashMap; use std::fmt; use std::io::{self, Read, Write}; @@ -253,7 +254,7 @@ pub struct FromReadTriGReader { impl FromReadTriGReader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -266,14 +267,19 @@ impl FromReadTriGReader { /// schema:name "Foo" ."#; /// /// let mut reader = TriGParser::new().parse_read(file.as_ref()); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning /// /// reader.next().unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Result::<_,Box>::Ok(()) /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.inner.parser.context.prefixes + pub fn prefixes(&self) -> TriGPrefixesIter<'_> { + TriGPrefixesIter { + inner: self.inner.parser.context.prefixes(), + } } /// The base IRI considered at the current step of the parsing. @@ -357,7 +363,7 @@ impl FromTokioAsyncReadTriGReader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -372,15 +378,20 @@ impl FromTokioAsyncReadTriGReader { /// schema:name "Foo" ."#; /// /// let mut reader = TriGParser::new().parse_tokio_async_read(file.as_ref()); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning /// /// reader.next().await.unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Ok(()) /// # } /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.inner.parser.context.prefixes + pub fn prefixes(&self) -> TriGPrefixesIter<'_> { + TriGPrefixesIter { + inner: self.inner.parser.context.prefixes(), + } } /// The base IRI considered at the current step of the parsing. @@ -484,7 +495,7 @@ impl LowLevelTriGReader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -498,14 +509,19 @@ impl LowLevelTriGReader { /// /// let mut reader = TriGParser::new().parse(); /// reader.extend_from_slice(file); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning /// /// reader.read_next().unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Result::<_,Box>::Ok(()) /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.parser.context.prefixes + pub fn prefixes(&self) -> TriGPrefixesIter<'_> { + TriGPrefixesIter { + inner: self.parser.context.prefixes(), + } } /// The base IRI considered at the current step of the parsing. @@ -536,6 +552,28 @@ impl LowLevelTriGReader { } } +/// Iterator on the file prefixes. +/// +/// See [`LowLevelTriGReader::prefixes`]. +pub struct TriGPrefixesIter<'a> { + inner: Iter<'a, String, Iri>, +} + +impl<'a> Iterator for TriGPrefixesIter<'a> { + type Item = (&'a str, &'a str); + + #[inline] + fn next(&mut self) -> Option { + let (key, value) = self.inner.next()?; + Some((key.as_str(), value.as_str())) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + /// A [TriG](https://www.w3.org/TR/trig/) serializer. /// /// Support for [TriG-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#trig-star) is available behind the `rdf-star` feature. diff --git a/lib/oxttl/src/turtle.rs b/lib/oxttl/src/turtle.rs index 5a2b67a2..f5193059 100644 --- a/lib/oxttl/src/turtle.rs +++ b/lib/oxttl/src/turtle.rs @@ -10,6 +10,7 @@ use crate::trig::ToTokioAsyncWriteTriGWriter; use crate::trig::{LowLevelTriGWriter, ToWriteTriGWriter, TriGSerializer}; use oxiri::{Iri, IriParseError}; use oxrdf::{GraphNameRef, Triple, TripleRef}; +use std::collections::hash_map::Iter; use std::collections::HashMap; use std::io::{self, Read, Write}; #[cfg(feature = "async-tokio")] @@ -254,7 +255,7 @@ pub struct FromReadTurtleReader { impl FromReadTurtleReader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -267,14 +268,19 @@ impl FromReadTurtleReader { /// schema:name "Foo" ."#; /// /// let mut reader = TurtleParser::new().parse_read(file.as_ref()); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert!(reader.prefixes().collect::>().is_empty()); // No prefix at the beginning /// /// reader.next().unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Result::<_,Box>::Ok(()) /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.inner.parser.context.prefixes + pub fn prefixes(&self) -> TurtlePrefixesIter<'_> { + TurtlePrefixesIter { + inner: self.inner.parser.context.prefixes(), + } } /// The base IRI considered at the current step of the parsing. @@ -358,7 +364,7 @@ impl FromTokioAsyncReadTurtleReader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -373,15 +379,20 @@ impl FromTokioAsyncReadTurtleReader { /// schema:name "Foo" ."#; /// /// let mut reader = TurtleParser::new().parse_tokio_async_read(file.as_ref()); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning /// /// reader.next().await.unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Ok(()) /// # } /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.inner.parser.context.prefixes + pub fn prefixes(&self) -> TurtlePrefixesIter<'_> { + TurtlePrefixesIter { + inner: self.inner.parser.context.prefixes(), + } } /// The base IRI considered at the current step of the parsing. @@ -485,7 +496,7 @@ impl LowLevelTurtleReader { /// The list of IRI prefixes considered at the current step of the parsing. /// - /// This method returns the mapping from prefix name to prefix value. + /// This method returns (prefix name, prefix value) tuples. /// It is empty at the beginning of the parsing and gets updated when prefixes are encountered. /// It should be full at the end of the parsing (but if a prefix is overridden, only the latest version will be returned). /// @@ -499,14 +510,19 @@ impl LowLevelTurtleReader { /// /// let mut reader = TurtleParser::new().parse(); /// reader.extend_from_slice(file); - /// assert!(reader.prefixes().is_empty()); // No prefix at the beginning + /// assert_eq!(reader.prefixes().collect::>(), []); // No prefix at the beginning /// /// reader.read_next().unwrap()?; // We read the first triple - /// assert_eq!(reader.prefixes()["schema"], "http://schema.org/"); // There are now prefixes + /// assert_eq!( + /// reader.prefixes().collect::>(), + /// [("schema", "http://schema.org/")] + /// ); // There are now prefixes /// # Result::<_,Box>::Ok(()) /// ``` - pub fn prefixes(&self) -> &HashMap> { - &self.parser.context.prefixes + pub fn prefixes(&self) -> TurtlePrefixesIter<'_> { + TurtlePrefixesIter { + inner: self.parser.context.prefixes(), + } } /// The base IRI considered at the current step of the parsing. @@ -537,6 +553,28 @@ impl LowLevelTurtleReader { } } +/// Iterator on the file prefixes. +/// +/// See [`LowLevelTurtleReader::prefixes`]. +pub struct TurtlePrefixesIter<'a> { + inner: Iter<'a, String, Iri>, +} + +impl<'a> Iterator for TurtlePrefixesIter<'a> { + type Item = (&'a str, &'a str); + + #[inline] + fn next(&mut self) -> Option { + let (key, value) = self.inner.next()?; + Some((key.as_str(), value.as_str())) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + /// A [Turtle](https://www.w3.org/TR/turtle/) serializer. /// /// Support for [Turtle-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#turtle-star) is available behind the `rdf-star` feature.