diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index c5010d9c..a58e710c 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -36,6 +36,8 @@ jobs:
working-directory: ./lib/oxrdfxml
- run: cargo clippy
working-directory: ./lib/oxttl
+ - run: cargo clippy
+ working-directory: ./lib/oxrdfio
- run: cargo clippy
working-directory: ./lib/sparesults
- run: cargo clippy
@@ -102,6 +104,8 @@ jobs:
working-directory: ./lib/oxrdfxml
- run: cargo clippy -- -D warnings -D clippy::all
working-directory: ./lib/oxttl
+ - run: cargo clippy -- -D warnings -D clippy::all
+ working-directory: ./lib/oxrdfio
- run: cargo clippy -- -D warnings -D clippy::all
working-directory: ./lib/sparesults
- run: cargo clippy -- -D warnings -D clippy::all
@@ -159,7 +163,7 @@ jobs:
- run: rustup update
- uses: Swatinem/rust-cache@v2
- run: cargo install cargo-semver-checks || true
- - run: cargo semver-checks check-release --exclude oxrocksdb-sys --exclude oxigraph_js --exclude pyoxigraph --exclude oxigraph_testsuite --exclude oxigraph_server --exclude oxrdfxml --exclude oxttl --exclude sparopt
+ - run: cargo semver-checks check-release --exclude oxrocksdb-sys --exclude oxigraph_js --exclude pyoxigraph --exclude oxigraph_testsuite --exclude oxigraph_server --exclude oxrdfxml --exclude oxttl --exclude oxrdfio --exclude sparopt
test_linux:
runs-on: ubuntu-latest
diff --git a/Cargo.lock b/Cargo.lock
index ab40b360..1e3337d5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -952,10 +952,9 @@ dependencies = [
"oxilangtag",
"oxiri",
"oxrdf",
- "oxrdfxml",
+ "oxrdfio",
"oxrocksdb-sys",
"oxsdatatypes",
- "oxttl",
"rand",
"regex",
"sha-1",
@@ -1036,6 +1035,16 @@ dependencies = [
"rand",
]
+[[package]]
+name = "oxrdfio"
+version = "0.1.0-alpha.1-dev"
+dependencies = [
+ "oxrdf",
+ "oxrdfxml",
+ "oxttl",
+ "tokio",
+]
+
[[package]]
name = "oxrdfxml"
version = "0.1.0-alpha.1-dev"
diff --git a/Cargo.toml b/Cargo.toml
index 0ad8536d..8b6c3e48 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@ members = [
"js",
"lib",
"lib/oxrdf",
+ "lib/oxrdfio",
"lib/oxrdfxml",
"lib/oxsdatatypes",
"lib/oxttl",
diff --git a/lib/Cargo.toml b/lib/Cargo.toml
index aef5f012..0cf839de 100644
--- a/lib/Cargo.toml
+++ b/lib/Cargo.toml
@@ -35,9 +35,8 @@ siphasher = "0.3"
lazy_static = "1"
json-event-parser = "0.1"
oxrdf = { version = "0.2.0-alpha.1-dev", path = "oxrdf", features = ["rdf-star", "oxsdatatypes"] }
-oxrdfxml = { version = "0.1.0-alpha.1-dev", path = "oxrdfxml" }
oxsdatatypes = { version = "0.2.0-alpha.1-dev", path="oxsdatatypes" }
-oxttl = { version = "0.1.0-alpha.1-dev" , path = "oxttl", features = ["rdf-star"] }
+oxrdfio = { version = "0.1.0-alpha.1-dev" , path = "oxrdfio", features = ["rdf-star"] }
spargebra = { version = "0.3.0-alpha.1-dev", path = "spargebra", features = ["rdf-star", "sep-0002", "sep-0006"] }
sparopt = { version = "0.1.0-alpha.1-dev", path="sparopt", features = ["rdf-star", "sep-0002", "sep-0006"] }
sparesults = { version = "0.2.0-alpha.1-dev", path = "sparesults", features = ["rdf-star"] }
diff --git a/lib/oxrdfio/Cargo.toml b/lib/oxrdfio/Cargo.toml
new file mode 100644
index 00000000..70f266b0
--- /dev/null
+++ b/lib/oxrdfio/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "oxrdfio"
+version = "0.1.0-alpha.1-dev"
+authors = ["Tpt "]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+keywords = ["RDF"]
+repository = "https://github.com/oxigraph/oxigraph/tree/master/lib/oxrdfxml"
+homepage = "https://oxigraph.org/"
+documentation = "https://docs.rs/oxrdfio"
+description = """
+Parser for various RDF serializations
+"""
+edition = "2021"
+rust-version = "1.65"
+
+[features]
+default = []
+async-tokio = ["dep:tokio", "oxrdfxml/async-tokio", "oxttl/async-tokio"]
+rdf-star = ["oxrdf/rdf-star", "oxttl/rdf-star"]
+
+[dependencies]
+oxrdf = { version = "0.2.0-alpha.1-dev", path = "../oxrdf" }
+oxrdfxml = { version = "0.1.0-alpha.1-dev", path = "../oxrdfxml" }
+oxttl = { version = "0.1.0-alpha.1-dev" , path = "../oxttl" }
+tokio = { version = "1", optional = true, features = ["io-util"] }
+
+[dev-dependencies]
+tokio = { version = "1", features = ["rt", "macros"] }
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
diff --git a/lib/oxrdfio/README.md b/lib/oxrdfio/README.md
new file mode 100644
index 00000000..1712b8ae
--- /dev/null
+++ b/lib/oxrdfio/README.md
@@ -0,0 +1,61 @@
+OxRDF I/O
+=========
+
+[![Latest Version](https://img.shields.io/crates/v/oxrdfio.svg)](https://crates.io/crates/oxrdfio)
+[![Released API docs](https://docs.rs/oxrdfio/badge.svg)](https://docs.rs/oxrdfio)
+[![Crates.io downloads](https://img.shields.io/crates/d/oxrdfio)](https://crates.io/crates/oxrdfio)
+[![actions status](https://github.com/oxigraph/oxigraph/workflows/build/badge.svg)](https://github.com/oxigraph/oxigraph/actions)
+[![Gitter](https://badges.gitter.im/oxigraph/community.svg)](https://gitter.im/oxigraph/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+
+OxRDF I/O is a set of parsers and serializers for RDF.
+
+It supports:
+* [N3](https://w3c.github.io/N3/spec/) using [`oxttl`](https://crates.io/crates/oxttl)
+* [N-Quads](https://www.w3.org/TR/n-quads/) using [`oxttl`](https://crates.io/crates/oxttl)
+* [N-Triples](https://www.w3.org/TR/n-triples/) using [`oxttl`](https://crates.io/crates/oxttl)
+* [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) using [`oxrdfxml`](https://crates.io/crates/oxrdfxml)
+* [TriG](https://www.w3.org/TR/trig/) using [`oxttl`](https://crates.io/crates/oxttl)
+* [Turtle](https://www.w3.org/TR/turtle/) using [`oxttl`](https://crates.io/crates/oxttl)
+
+Support for [SPARQL-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html) is also available behind the `rdf-star`feature for [Turtle-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#turtle-star), [TriG-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#trig-star), [N-Triples-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-triples-star) and [N-Quads-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-quads-star).
+
+It is designed as a low level parser compatible with both synchronous and asynchronous I/O (behind the `async-tokio` feature).
+
+Usage example counting the number of people in a Turtle file:
+```rust
+use oxrdf::{NamedNodeRef, vocab::rdf};
+use oxrdfio::{RdfFormat, RdfParser};
+
+let file = b"@base .
+@prefix schema: .
+ a schema:Person ;
+ schema:name \"Foo\" .
+ a schema:Person ;
+ schema:name \"Bar\" .";
+
+let schema_person = NamedNodeRef::new("http://schema.org/Person").unwrap();
+let mut count = 0;
+for quad in RdfParser::from_format(RdfFormat::Turtle).parse_read(file.as_ref()) {
+ let quad = quad.unwrap();
+ if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
+ count += 1;
+ }
+}
+assert_eq!(2, count);
+```
+
+## License
+
+This project is licensed under either of
+
+* Apache License, Version 2.0, ([LICENSE-APACHE](../LICENSE-APACHE) or
+ ``)
+* MIT license ([LICENSE-MIT](../LICENSE-MIT) or
+ ``)
+
+at your option.
+
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Oxigraph by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
diff --git a/lib/oxrdfio/src/error.rs b/lib/oxrdfio/src/error.rs
new file mode 100644
index 00000000..ac8173a7
--- /dev/null
+++ b/lib/oxrdfio/src/error.rs
@@ -0,0 +1,148 @@
+use std::error::Error;
+use std::{fmt, io};
+
+/// Error returned during RDF format parsing.
+#[derive(Debug)]
+pub enum ParseError {
+ /// I/O error during parsing (file not found...).
+ Io(io::Error),
+ /// An error in the file syntax.
+ Syntax(SyntaxError),
+}
+
+impl ParseError {
+ pub(crate) fn msg(msg: &'static str) -> Self {
+ Self::Syntax(SyntaxError {
+ inner: SyntaxErrorKind::Msg { msg },
+ })
+ }
+}
+
+impl fmt::Display for ParseError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Io(e) => e.fmt(f),
+ Self::Syntax(e) => e.fmt(f),
+ }
+ }
+}
+
+impl Error for ParseError {
+ #[inline]
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::Io(e) => Some(e),
+ Self::Syntax(e) => Some(e),
+ }
+ }
+}
+
+impl From for SyntaxError {
+ #[inline]
+ fn from(error: oxttl::SyntaxError) -> Self {
+ SyntaxError {
+ inner: SyntaxErrorKind::Turtle(error),
+ }
+ }
+}
+
+impl From for ParseError {
+ #[inline]
+ fn from(error: oxttl::ParseError) -> Self {
+ match error {
+ oxttl::ParseError::Syntax(e) => Self::Syntax(e.into()),
+ oxttl::ParseError::Io(e) => Self::Io(e),
+ }
+ }
+}
+
+impl From for SyntaxError {
+ #[inline]
+ fn from(error: oxrdfxml::SyntaxError) -> Self {
+ SyntaxError {
+ inner: SyntaxErrorKind::RdfXml(error),
+ }
+ }
+}
+
+impl From for ParseError {
+ #[inline]
+ fn from(error: oxrdfxml::ParseError) -> Self {
+ match error {
+ oxrdfxml::ParseError::Syntax(e) => Self::Syntax(e.into()),
+ oxrdfxml::ParseError::Io(e) => Self::Io(e),
+ }
+ }
+}
+
+impl From for ParseError {
+ #[inline]
+ fn from(error: io::Error) -> Self {
+ Self::Io(error)
+ }
+}
+
+impl From for ParseError {
+ #[inline]
+ fn from(error: SyntaxError) -> Self {
+ Self::Syntax(error)
+ }
+}
+
+impl From for io::Error {
+ #[inline]
+ fn from(error: ParseError) -> Self {
+ match error {
+ ParseError::Io(error) => error,
+ ParseError::Syntax(error) => error.into(),
+ }
+ }
+}
+
+/// An error in the syntax of the parsed file.
+#[derive(Debug)]
+pub struct SyntaxError {
+ inner: SyntaxErrorKind,
+}
+
+#[derive(Debug)]
+enum SyntaxErrorKind {
+ Turtle(oxttl::SyntaxError),
+ RdfXml(oxrdfxml::SyntaxError),
+
+ Msg { msg: &'static str },
+}
+
+impl fmt::Display for SyntaxError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match &self.inner {
+ SyntaxErrorKind::Turtle(e) => e.fmt(f),
+ SyntaxErrorKind::RdfXml(e) => e.fmt(f),
+ SyntaxErrorKind::Msg { msg } => write!(f, "{msg}"),
+ }
+ }
+}
+
+impl Error for SyntaxError {
+ #[inline]
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match &self.inner {
+ SyntaxErrorKind::Turtle(e) => Some(e),
+ SyntaxErrorKind::RdfXml(e) => Some(e),
+ SyntaxErrorKind::Msg { .. } => None,
+ }
+ }
+}
+
+impl From for io::Error {
+ #[inline]
+ fn from(error: SyntaxError) -> Self {
+ match error.inner {
+ SyntaxErrorKind::Turtle(error) => error.into(),
+ SyntaxErrorKind::RdfXml(error) => error.into(),
+ SyntaxErrorKind::Msg { msg } => io::Error::new(io::ErrorKind::InvalidData, msg),
+ }
+ }
+}
diff --git a/lib/oxrdfio/src/format.rs b/lib/oxrdfio/src/format.rs
new file mode 100644
index 00000000..8c4ce230
--- /dev/null
+++ b/lib/oxrdfio/src/format.rs
@@ -0,0 +1,203 @@
+use std::fmt;
+
+/// RDF serialization formats.
+///
+/// This enumeration is non exhaustive. New formats like JSON-LD might be added in the future.
+#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
+#[non_exhaustive]
+pub enum RdfFormat {
+ /// [N3](https://w3c.github.io/N3/spec/)
+ N3,
+ /// [N-Quads](https://www.w3.org/TR/n-quads/)
+ NQuads,
+ /// [N-Triples](https://www.w3.org/TR/n-triples/)
+ NTriples,
+ /// [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/)
+ RdfXml,
+ /// [TriG](https://www.w3.org/TR/trig/)
+ TriG,
+ /// [Turtle](https://www.w3.org/TR/turtle/)
+ Turtle,
+}
+
+impl RdfFormat {
+ /// The format canonical IRI according to the [Unique URIs for file formats registry](https://www.w3.org/ns/formats/).
+ ///
+ /// ```
+ /// use oxrdfio::RdfFormat;
+ ///
+ /// assert_eq!(RdfFormat::NTriples.iri(), "http://www.w3.org/ns/formats/N-Triples")
+ /// ```
+ #[inline]
+ pub const fn iri(self) -> &'static str {
+ match self {
+ Self::N3 => "http://www.w3.org/ns/formats/N3",
+ Self::NQuads => "http://www.w3.org/ns/formats/N-Quads",
+ Self::NTriples => "http://www.w3.org/ns/formats/N-Triples",
+ Self::RdfXml => "http://www.w3.org/ns/formats/RDF_XML",
+ Self::TriG => "http://www.w3.org/ns/formats/TriG",
+ Self::Turtle => "http://www.w3.org/ns/formats/Turtle",
+ }
+ }
+
+ /// The format [IANA media type](https://tools.ietf.org/html/rfc2046).
+ ///
+ /// ```
+ /// use oxrdfio::RdfFormat;
+ ///
+ /// assert_eq!(RdfFormat::NTriples.media_type(), "application/n-triples")
+ /// ```
+ #[inline]
+ pub const fn media_type(self) -> &'static str {
+ match self {
+ Self::N3 => "text/n3",
+ Self::NQuads => "application/n-quads",
+ Self::NTriples => "application/n-triples",
+ Self::RdfXml => "application/rdf+xml",
+ Self::TriG => "application/trig",
+ Self::Turtle => "text/turtle",
+ }
+ }
+
+ /// The format [IANA-registered](https://tools.ietf.org/html/rfc2046) file extension.
+ ///
+ /// ```
+ /// use oxrdfio::RdfFormat;
+ ///
+ /// assert_eq!(RdfFormat::NTriples.file_extension(), "nt")
+ /// ```
+ #[inline]
+ pub const fn file_extension(self) -> &'static str {
+ match self {
+ Self::N3 => "n3",
+ Self::NQuads => "nq",
+ Self::NTriples => "nt",
+ Self::RdfXml => "rdf",
+ Self::TriG => "trig",
+ Self::Turtle => "ttl",
+ }
+ }
+
+ /// The format name.
+ ///
+ /// ```
+ /// use oxrdfio::RdfFormat;
+ ///
+ /// assert_eq!(RdfFormat::NTriples.name(), "N-Triples")
+ /// ```
+ #[inline]
+ pub const fn name(self) -> &'static str {
+ match self {
+ Self::N3 => "N3",
+ Self::NQuads => "N-Quads",
+ Self::NTriples => "N-Triples",
+ Self::RdfXml => "RDF/XML",
+ Self::TriG => "TriG",
+ Self::Turtle => "Turtle",
+ }
+ }
+
+ /// Checks if the formats supports [RDF datasets](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) and not only [RDF graphs](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-graph).
+ ///
+ /// ```
+ /// use oxrdfio::RdfFormat;
+ ///
+ /// assert_eq!(RdfFormat::NTriples.supports_datasets(), false);
+ /// assert_eq!(RdfFormat::NQuads.supports_datasets(), true);
+ /// ```
+ #[inline]
+ pub const fn supports_datasets(self) -> bool {
+ matches!(self, Self::NQuads | Self::TriG)
+ }
+
+ /// Checks if the formats supports [RDF-star quoted triples](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#dfn-quoted).
+ ///
+ /// ```
+ /// use oxrdfio::RdfFormat;
+ ///
+ /// assert_eq!(RdfFormat::NTriples.supports_rdf_star(), true);
+ /// assert_eq!(RdfFormat::RdfXml.supports_rdf_star(), false);
+ /// ```
+ #[inline]
+ #[cfg(feature = "rdf-star")]
+ pub const fn supports_rdf_star(self) -> bool {
+ matches!(
+ self,
+ Self::NTriples | Self::NQuads | Self::Turtle | Self::TriG
+ )
+ }
+
+ /// Looks for a known format from a media type.
+ ///
+ /// It supports some media type aliases.
+ /// For example, "application/xml" is going to return `RdfFormat::RdfXml` even if it is not its canonical media type.
+ ///
+ /// Example:
+ /// ```
+ /// use oxrdfio::RdfFormat;
+ ///
+ /// assert_eq!(RdfFormat::from_media_type("text/turtle; charset=utf-8"), Some(RdfFormat::Turtle))
+ /// ```
+ #[inline]
+ pub fn from_media_type(media_type: &str) -> Option {
+ const MEDIA_TYPES: [(&str, RdfFormat); 14] = [
+ ("application/n-quads", RdfFormat::NQuads),
+ ("application/n-triples", RdfFormat::NTriples),
+ ("application/rdf+xml", RdfFormat::RdfXml),
+ ("application/trig", RdfFormat::TriG),
+ ("application/turtle", RdfFormat::Turtle),
+ ("application/xml", RdfFormat::RdfXml),
+ ("application/x-trig", RdfFormat::TriG),
+ ("application/x-turtle", RdfFormat::Turtle),
+ ("text/n3", RdfFormat::N3),
+ ("text/nquads", RdfFormat::NQuads),
+ ("text/plain", RdfFormat::NTriples),
+ ("text/turtle", RdfFormat::Turtle),
+ ("text/xml", RdfFormat::RdfXml),
+ ("text/x-nquads", RdfFormat::NQuads),
+ ];
+ let media_type = media_type.split(';').next()?.trim();
+ for (candidate_media_type, candidate_id) in MEDIA_TYPES {
+ if candidate_media_type.eq_ignore_ascii_case(media_type) {
+ return Some(candidate_id);
+ }
+ }
+ None
+ }
+
+ /// Looks for a known format from an extension.
+ ///
+ /// It supports some aliases.
+ ///
+ /// Example:
+ /// ```
+ /// use oxrdfio::RdfFormat;
+ ///
+ /// assert_eq!(RdfFormat::from_extension("nt"), Some(RdfFormat::NTriples))
+ /// ```
+ #[inline]
+ pub fn from_extension(extension: &str) -> Option {
+ const MEDIA_TYPES: [(&str, RdfFormat); 8] = [
+ ("n3", RdfFormat::N3),
+ ("nq", RdfFormat::NQuads),
+ ("nt", RdfFormat::NTriples),
+ ("rdf", RdfFormat::RdfXml),
+ ("trig", RdfFormat::TriG),
+ ("ttl", RdfFormat::Turtle),
+ ("txt", RdfFormat::NTriples),
+ ("xml", RdfFormat::RdfXml),
+ ];
+ for (candidate_extension, candidate_id) in MEDIA_TYPES {
+ if candidate_extension.eq_ignore_ascii_case(extension) {
+ return Some(candidate_id);
+ }
+ }
+ None
+ }
+}
+
+impl fmt::Display for RdfFormat {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(self.name())
+ }
+}
diff --git a/lib/oxrdfio/src/lib.rs b/lib/oxrdfio/src/lib.rs
new file mode 100644
index 00000000..d31ec656
--- /dev/null
+++ b/lib/oxrdfio/src/lib.rs
@@ -0,0 +1,19 @@
+#![doc = include_str!("../README.md")]
+#![doc(test(attr(deny(warnings))))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![doc(html_favicon_url = "https://raw.githubusercontent.com/oxigraph/oxigraph/main/logo.svg")]
+#![doc(html_logo_url = "https://raw.githubusercontent.com/oxigraph/oxigraph/main/logo.svg")]
+
+mod error;
+mod format;
+mod parser;
+mod serializer;
+
+pub use error::{ParseError, SyntaxError};
+pub use format::RdfFormat;
+#[cfg(feature = "async-tokio")]
+pub use parser::FromTokioAsyncReadQuadReader;
+pub use parser::{FromReadQuadReader, RdfParser};
+#[cfg(feature = "async-tokio")]
+pub use serializer::ToTokioAsyncWriteQuadWriter;
+pub use serializer::{RdfSerializer, ToWriteQuadWriter};
diff --git a/lib/oxrdfio/src/parser.rs b/lib/oxrdfio/src/parser.rs
new file mode 100644
index 00000000..b0e4a419
--- /dev/null
+++ b/lib/oxrdfio/src/parser.rs
@@ -0,0 +1,577 @@
+//! Utilities to read RDF graphs and datasets.
+
+pub use crate::error::{ParseError, SyntaxError};
+use crate::format::RdfFormat;
+use oxrdf::{BlankNode, GraphName, IriParseError, Quad, Subject, Term, Triple};
+#[cfg(feature = "async-tokio")]
+use oxrdfxml::FromTokioAsyncReadRdfXmlReader;
+use oxrdfxml::{FromReadRdfXmlReader, RdfXmlParser};
+#[cfg(feature = "async-tokio")]
+use oxttl::n3::FromTokioAsyncReadN3Reader;
+use oxttl::n3::{FromReadN3Reader, N3Parser, N3Quad, N3Term};
+#[cfg(feature = "async-tokio")]
+use oxttl::nquads::FromTokioAsyncReadNQuadsReader;
+use oxttl::nquads::{FromReadNQuadsReader, NQuadsParser};
+#[cfg(feature = "async-tokio")]
+use oxttl::ntriples::FromTokioAsyncReadNTriplesReader;
+use oxttl::ntriples::{FromReadNTriplesReader, NTriplesParser};
+#[cfg(feature = "async-tokio")]
+use oxttl::trig::FromTokioAsyncReadTriGReader;
+use oxttl::trig::{FromReadTriGReader, TriGParser};
+#[cfg(feature = "async-tokio")]
+use oxttl::turtle::FromTokioAsyncReadTurtleReader;
+use oxttl::turtle::{FromReadTurtleReader, TurtleParser};
+use std::collections::HashMap;
+use std::io::Read;
+#[cfg(feature = "async-tokio")]
+use tokio::io::AsyncRead;
+
+/// Parsers for RDF serialization formats.
+///
+/// It currently supports the following formats:
+/// * [N3](https://w3c.github.io/N3/spec/) ([`RdfFormat::N3`])
+/// * [N-Quads](https://www.w3.org/TR/n-quads/) ([`RdfFormat::NQuads`])
+/// * [N-Triples](https://www.w3.org/TR/n-triples/) ([`RdfFormat::NTriples`])
+/// * [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) ([`RdfFormat::RdfXml`])
+/// * [TriG](https://www.w3.org/TR/trig/) ([`RdfFormat::TriG`])
+/// * [Turtle](https://www.w3.org/TR/turtle/) ([`RdfFormat::Turtle`])
+///
+/// Note the useful options:
+/// - [`with_base_iri`](RdfParser::with_base_iri) to resolve the relative IRIs.
+/// - [`rename_blank_nodes`](RdfParser::rename_blank_nodes) to rename the blank nodes to auto-generated numbers to avoid conflicts when merging RDF graphs together.
+/// - [`without_named_graphs`](RdfParser::without_named_graphs) to parse a single graph.
+///
+/// ```
+/// use oxrdfio::{RdfFormat, RdfParser};
+///
+/// let file = " .";
+///
+/// let parser = RdfParser::from_format(RdfFormat::NTriples);
+/// let quads = parser.parse_read(file.as_bytes()).collect::,_>>()?;
+///
+/// assert_eq!(quads.len(), 1);
+/// assert_eq!(quads[0].subject.to_string(), "");
+/// # std::io::Result::Ok(())
+/// ```
+pub struct RdfParser {
+ inner: RdfParserKind,
+ default_graph: GraphName,
+ without_named_graphs: bool,
+ rename_blank_nodes: bool,
+}
+
+enum RdfParserKind {
+ N3(N3Parser),
+ NQuads(NQuadsParser),
+ NTriples(NTriplesParser),
+ RdfXml(RdfXmlParser),
+ TriG(TriGParser),
+ Turtle(TurtleParser),
+}
+
+impl RdfParser {
+ /// Builds a parser for the given format.
+ #[inline]
+ #[must_use]
+ pub fn from_format(format: RdfFormat) -> Self {
+ Self {
+ inner: match format {
+ RdfFormat::N3 => RdfParserKind::N3(N3Parser::new()),
+ RdfFormat::NQuads => RdfParserKind::NQuads({
+ #[cfg(feature = "rdf-star")]
+ {
+ NQuadsParser::new().with_quoted_triples()
+ }
+ #[cfg(not(feature = "rdf-star"))]
+ {
+ NQuadsParser::new()
+ }
+ }),
+ RdfFormat::NTriples => RdfParserKind::NTriples({
+ #[cfg(feature = "rdf-star")]
+ {
+ NTriplesParser::new().with_quoted_triples()
+ }
+ #[cfg(not(feature = "rdf-star"))]
+ {
+ NTriplesParser::new()
+ }
+ }),
+ RdfFormat::RdfXml => RdfParserKind::RdfXml(RdfXmlParser::new()),
+ RdfFormat::TriG => RdfParserKind::TriG({
+ #[cfg(feature = "rdf-star")]
+ {
+ TriGParser::new().with_quoted_triples()
+ }
+ #[cfg(not(feature = "rdf-star"))]
+ {
+ TriGParser::new()
+ }
+ }),
+ RdfFormat::Turtle => RdfParserKind::Turtle({
+ #[cfg(feature = "rdf-star")]
+ {
+ TurtleParser::new().with_quoted_triples()
+ }
+ #[cfg(not(feature = "rdf-star"))]
+ {
+ TurtleParser::new()
+ }
+ }),
+ },
+ default_graph: GraphName::DefaultGraph,
+ without_named_graphs: false,
+ rename_blank_nodes: false,
+ }
+ }
+
+ /// Provides an IRI that could be used to resolve the file relative IRIs.
+ ///
+ /// ```
+ /// use oxrdfio::{RdfFormat, RdfParser};
+ ///
+ /// let file = "
.";
+ ///
+ /// let parser = RdfParser::from_format(RdfFormat::Turtle).with_base_iri("http://example.com")?;
+ /// let quads = parser.parse_read(file.as_bytes()).collect::,_>>()?;
+ ///
+ /// assert_eq!(quads.len(), 1);
+ /// assert_eq!(quads[0].subject.to_string(), "");
+ /// # Result::<_,Box>::Ok(())
+ /// ```
+ #[inline]
+ pub fn with_base_iri(self, base_iri: impl Into) -> Result {
+ Ok(Self {
+ inner: match self.inner {
+ RdfParserKind::N3(p) => RdfParserKind::N3(p),
+ RdfParserKind::NTriples(p) => RdfParserKind::NTriples(p),
+ RdfParserKind::NQuads(p) => RdfParserKind::NQuads(p),
+ RdfParserKind::RdfXml(p) => RdfParserKind::RdfXml(p.with_base_iri(base_iri)?),
+ RdfParserKind::TriG(p) => RdfParserKind::TriG(p.with_base_iri(base_iri)?),
+ RdfParserKind::Turtle(p) => RdfParserKind::Turtle(p.with_base_iri(base_iri)?),
+ },
+ default_graph: self.default_graph,
+ without_named_graphs: self.without_named_graphs,
+ rename_blank_nodes: self.rename_blank_nodes,
+ })
+ }
+
+ /// Provides the name graph name that should replace the default graph in the returned quads.
+ ///
+ /// ```
+ /// use oxrdf::NamedNode;
+ /// use oxrdfio::{RdfFormat, RdfParser};
+ ///
+ /// let file = " .";
+ ///
+ /// let parser = RdfParser::from_format(RdfFormat::Turtle).with_default_graph(NamedNode::new("http://example.com/g")?);
+ /// let quads = parser.parse_read(file.as_bytes()).collect::,_>>()?;
+ ///
+ /// assert_eq!(quads.len(), 1);
+ /// assert_eq!(quads[0].graph_name.to_string(), "");
+ /// # Result::<_,Box>::Ok(())
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn with_default_graph(self, default_graph: impl Into) -> Self {
+ Self {
+ inner: self.inner,
+ default_graph: default_graph.into(),
+ without_named_graphs: self.without_named_graphs,
+ rename_blank_nodes: self.rename_blank_nodes,
+ }
+ }
+
+ /// Sets that the parser must fail if parsing a named graph.
+ ///
+ /// This function restricts the parser to only parse a single [RDF graph](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-graph) and not an [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset).
+ ///
+ /// ```
+ /// use oxrdfio::{RdfFormat, RdfParser};
+ ///
+ /// let file = " .";
+ ///
+ /// let parser = RdfParser::from_format(RdfFormat::NQuads).without_named_graphs();
+ /// assert!(parser.parse_read(file.as_bytes()).next().unwrap().is_err());
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn without_named_graphs(self) -> Self {
+ Self {
+ inner: self.inner,
+ default_graph: self.default_graph,
+ without_named_graphs: true,
+ rename_blank_nodes: self.rename_blank_nodes,
+ }
+ }
+
+ /// Renames the blank nodes ids from the ones set in the serialization to random ids.
+ ///
+ /// This allows to avoid id conflicts when merging graphs together.
+ ///
+ /// ```
+ /// use oxrdfio::{RdfFormat, RdfParser};
+ ///
+ /// let file = "_:a .";
+ ///
+ /// let parser = RdfParser::from_format(RdfFormat::NQuads).rename_blank_nodes();
+ /// let result1 = parser.parse_read(file.as_bytes()).collect::,_>>()?;
+ /// let result2 = parser.parse_read(file.as_bytes()).collect::,_>>()?;
+ /// assert_ne!(result1, result2);
+ /// # Result::<_,Box>::Ok(())
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn rename_blank_nodes(self) -> Self {
+ Self {
+ inner: self.inner,
+ default_graph: self.default_graph,
+ without_named_graphs: self.without_named_graphs,
+ rename_blank_nodes: true,
+ }
+ }
+
+ /// Parses from a [`Read`] implementation and returns an iterator of quads.
+ ///
+ /// Reads are buffered.
+ ///
+ /// ```
+ /// use oxrdfio::{RdfFormat, RdfParser};
+ ///
+ /// let file = " .";
+ ///
+ /// let parser = RdfParser::from_format(RdfFormat::NTriples);
+ /// let quads = parser.parse_read(file.as_bytes()).collect::,_>>()?;
+ ///
+ /// assert_eq!(quads.len(), 1);
+ /// assert_eq!(quads[0].subject.to_string(), "");
+ /// # std::io::Result::Ok(())
+ /// ```
+ pub fn parse_read(&self, reader: R) -> FromReadQuadReader {
+ FromReadQuadReader {
+ parser: match &self.inner {
+ RdfParserKind::N3(p) => FromReadQuadReaderKind::N3(p.parse_read(reader)),
+ RdfParserKind::NQuads(p) => FromReadQuadReaderKind::NQuads(p.parse_read(reader)),
+ RdfParserKind::NTriples(p) => {
+ FromReadQuadReaderKind::NTriples(p.parse_read(reader))
+ }
+ RdfParserKind::RdfXml(p) => FromReadQuadReaderKind::RdfXml(p.parse_read(reader)),
+ RdfParserKind::TriG(p) => FromReadQuadReaderKind::TriG(p.parse_read(reader)),
+ RdfParserKind::Turtle(p) => FromReadQuadReaderKind::Turtle(p.parse_read(reader)),
+ },
+ mapper: QuadMapper {
+ default_graph: self.default_graph.clone(),
+ without_named_graphs: self.without_named_graphs,
+ blank_node_map: self.rename_blank_nodes.then(HashMap::new),
+ },
+ }
+ }
+
+ /// Parses from a Tokio [`AsyncRead`] implementation and returns an async iterator of quads.
+ ///
+ /// Reads are buffered.
+ ///
+ /// ```
+ /// use oxrdfio::{RdfFormat, RdfParser, ParseError};
+ ///
+ /// #[tokio::main(flavor = "current_thread")]
+ /// async fn main() -> Result<(), ParseError> {
+ /// let file = " .";
+ ///
+ /// let parser = RdfParser::from_format(RdfFormat::NTriples);
+ /// let mut reader = parser.parse_tokio_async_read(file.as_bytes());
+ /// if let Some(quad) = reader.next().await {
+ /// assert_eq!(quad?.subject.to_string(), "");
+ /// }
+ /// Ok(())
+ /// }
+ /// ```
+ #[cfg(feature = "async-tokio")]
+ pub fn parse_tokio_async_read(
+ &self,
+ reader: R,
+ ) -> FromTokioAsyncReadQuadReader {
+ FromTokioAsyncReadQuadReader {
+ parser: match &self.inner {
+ RdfParserKind::N3(p) => {
+ FromTokioAsyncReadQuadReaderKind::N3(p.parse_tokio_async_read(reader))
+ }
+ RdfParserKind::NQuads(p) => {
+ FromTokioAsyncReadQuadReaderKind::NQuads(p.parse_tokio_async_read(reader))
+ }
+ RdfParserKind::NTriples(p) => {
+ FromTokioAsyncReadQuadReaderKind::NTriples(p.parse_tokio_async_read(reader))
+ }
+ RdfParserKind::RdfXml(p) => {
+ FromTokioAsyncReadQuadReaderKind::RdfXml(p.parse_tokio_async_read(reader))
+ }
+ RdfParserKind::TriG(p) => {
+ FromTokioAsyncReadQuadReaderKind::TriG(p.parse_tokio_async_read(reader))
+ }
+ RdfParserKind::Turtle(p) => {
+ FromTokioAsyncReadQuadReaderKind::Turtle(p.parse_tokio_async_read(reader))
+ }
+ },
+ mapper: QuadMapper {
+ default_graph: self.default_graph.clone(),
+ without_named_graphs: self.without_named_graphs,
+ blank_node_map: self.rename_blank_nodes.then(HashMap::new),
+ },
+ }
+ }
+}
+
+/// Parses a RDF file from a [`Read`] implementation. Can be built using [`RdfParser::parse_read`].
+///
+/// Reads are buffered.
+///
+/// ```
+/// use oxrdfio::{RdfFormat, RdfParser};
+///
+/// let file = " .";
+///
+/// let parser = RdfParser::from_format(RdfFormat::NTriples);
+/// let quads = parser.parse_read(file.as_bytes()).collect::,_>>()?;
+///
+/// assert_eq!(quads.len(), 1);
+/// assert_eq!(quads[0].subject.to_string(), "");
+/// # std::io::Result::Ok(())
+/// ```
+#[must_use]
+pub struct FromReadQuadReader {
+ parser: FromReadQuadReaderKind,
+ mapper: QuadMapper,
+}
+
+enum FromReadQuadReaderKind {
+ N3(FromReadN3Reader),
+ NQuads(FromReadNQuadsReader),
+ NTriples(FromReadNTriplesReader),
+ RdfXml(FromReadRdfXmlReader),
+ TriG(FromReadTriGReader),
+ Turtle(FromReadTurtleReader),
+}
+
+impl Iterator for FromReadQuadReader {
+ type Item = Result;
+
+ fn next(&mut self) -> Option> {
+ Some(match &mut self.parser {
+ FromReadQuadReaderKind::N3(parser) => match parser.next()? {
+ Ok(quad) => self.mapper.map_n3_quad(quad),
+ Err(e) => Err(e.into()),
+ },
+ FromReadQuadReaderKind::NQuads(parser) => match parser.next()? {
+ Ok(quad) => self.mapper.map_quad(quad),
+ Err(e) => Err(e.into()),
+ },
+ FromReadQuadReaderKind::NTriples(parser) => match parser.next()? {
+ Ok(triple) => Ok(self.mapper.map_triple_to_quad(triple)),
+ Err(e) => Err(e.into()),
+ },
+ FromReadQuadReaderKind::RdfXml(parser) => match parser.next()? {
+ Ok(triple) => Ok(self.mapper.map_triple_to_quad(triple)),
+ Err(e) => Err(e.into()),
+ },
+ FromReadQuadReaderKind::TriG(parser) => match parser.next()? {
+ Ok(quad) => self.mapper.map_quad(quad),
+ Err(e) => Err(e.into()),
+ },
+ FromReadQuadReaderKind::Turtle(parser) => match parser.next()? {
+ Ok(triple) => Ok(self.mapper.map_triple_to_quad(triple)),
+ Err(e) => Err(e.into()),
+ },
+ })
+ }
+}
+
+/// Parses a RDF file from a Tokio [`AsyncRead`] implementation. Can be built using [`RdfParser::parse_tokio_async_read`].
+///
+/// Reads are buffered.
+///
+/// ```
+/// use oxrdfio::{RdfFormat, RdfParser, ParseError};
+///
+/// #[tokio::main(flavor = "current_thread")]
+/// async fn main() -> Result<(), ParseError> {
+/// let file = " .";
+///
+/// let parser = RdfParser::from_format(RdfFormat::NTriples);
+/// let mut reader = parser.parse_tokio_async_read(file.as_bytes());
+/// if let Some(quad) = reader.next().await {
+/// assert_eq!(quad?.subject.to_string(), "");
+/// }
+/// Ok(())
+/// }
+/// ```
+#[must_use]
+#[cfg(feature = "async-tokio")]
+pub struct FromTokioAsyncReadQuadReader {
+ parser: FromTokioAsyncReadQuadReaderKind,
+ mapper: QuadMapper,
+}
+
+#[cfg(feature = "async-tokio")]
+enum FromTokioAsyncReadQuadReaderKind {
+ N3(FromTokioAsyncReadN3Reader),
+ NQuads(FromTokioAsyncReadNQuadsReader),
+ NTriples(FromTokioAsyncReadNTriplesReader),
+ RdfXml(FromTokioAsyncReadRdfXmlReader),
+ TriG(FromTokioAsyncReadTriGReader),
+ Turtle(FromTokioAsyncReadTurtleReader),
+}
+
+#[cfg(feature = "async-tokio")]
+impl FromTokioAsyncReadQuadReader {
+ pub async fn next(&mut self) -> Option> {
+ Some(match &mut self.parser {
+ FromTokioAsyncReadQuadReaderKind::N3(parser) => match parser.next().await? {
+ Ok(quad) => self.mapper.map_n3_quad(quad),
+ Err(e) => Err(e.into()),
+ },
+ FromTokioAsyncReadQuadReaderKind::NQuads(parser) => match parser.next().await? {
+ Ok(quad) => self.mapper.map_quad(quad),
+ Err(e) => Err(e.into()),
+ },
+ FromTokioAsyncReadQuadReaderKind::NTriples(parser) => match parser.next().await? {
+ Ok(triple) => Ok(self.mapper.map_triple_to_quad(triple)),
+ Err(e) => Err(e.into()),
+ },
+ FromTokioAsyncReadQuadReaderKind::RdfXml(parser) => match parser.next().await? {
+ Ok(triple) => Ok(self.mapper.map_triple_to_quad(triple)),
+ Err(e) => Err(e.into()),
+ },
+ FromTokioAsyncReadQuadReaderKind::TriG(parser) => match parser.next().await? {
+ Ok(quad) => self.mapper.map_quad(quad),
+ Err(e) => Err(e.into()),
+ },
+ FromTokioAsyncReadQuadReaderKind::Turtle(parser) => match parser.next().await? {
+ Ok(triple) => Ok(self.mapper.map_triple_to_quad(triple)),
+ Err(e) => Err(e.into()),
+ },
+ })
+ }
+}
+
+struct QuadMapper {
+ default_graph: GraphName,
+ without_named_graphs: bool,
+ blank_node_map: Option>,
+}
+
+impl QuadMapper {
+ fn map_blank_node(&mut self, node: BlankNode) -> BlankNode {
+ if let Some(blank_node_map) = &mut self.blank_node_map {
+ blank_node_map
+ .entry(node)
+ .or_insert_with(BlankNode::default)
+ .clone()
+ } else {
+ node
+ }
+ }
+
+ fn map_subject(&mut self, node: Subject) -> Subject {
+ match node {
+ Subject::NamedNode(node) => node.into(),
+ Subject::BlankNode(node) => self.map_blank_node(node).into(),
+ #[cfg(feature = "rdf-star")]
+ Subject::Triple(triple) => self.map_triple(*triple).into(),
+ }
+ }
+
+ fn map_term(&mut self, node: Term) -> Term {
+ match node {
+ Term::NamedNode(node) => node.into(),
+ Term::BlankNode(node) => self.map_blank_node(node).into(),
+ Term::Literal(literal) => literal.into(),
+ #[cfg(feature = "rdf-star")]
+ Term::Triple(triple) => self.map_triple(*triple).into(),
+ }
+ }
+
+ fn map_triple(&mut self, triple: Triple) -> Triple {
+ Triple {
+ subject: self.map_subject(triple.subject),
+ predicate: triple.predicate,
+ object: self.map_term(triple.object),
+ }
+ }
+
+ fn map_graph_name(&mut self, graph_name: GraphName) -> Result {
+ match graph_name {
+ GraphName::NamedNode(node) => {
+ if self.without_named_graphs {
+ Err(ParseError::msg("Named graphs are not allowed"))
+ } else {
+ Ok(node.into())
+ }
+ }
+ GraphName::BlankNode(node) => {
+ if self.without_named_graphs {
+ Err(ParseError::msg("Named graphs are not allowed"))
+ } else {
+ Ok(self.map_blank_node(node).into())
+ }
+ }
+ GraphName::DefaultGraph => Ok(self.default_graph.clone()),
+ }
+ }
+
+ fn map_quad(&mut self, quad: Quad) -> Result {
+ Ok(Quad {
+ subject: self.map_subject(quad.subject),
+ predicate: quad.predicate,
+ object: self.map_term(quad.object),
+ graph_name: self.map_graph_name(quad.graph_name)?,
+ })
+ }
+
+ fn map_triple_to_quad(&mut self, triple: Triple) -> Quad {
+ self.map_triple(triple).in_graph(self.default_graph.clone())
+ }
+
+ fn map_n3_quad(&mut self, quad: N3Quad) -> Result {
+ Ok(Quad {
+ subject: match quad.subject {
+ N3Term::NamedNode(s) => Ok(s.into()),
+ N3Term::BlankNode(s) => Ok(self.map_blank_node(s).into()),
+ N3Term::Literal(_) => Err(ParseError::msg(
+ "literals are not allowed in regular RDF subjects",
+ )),
+ #[cfg(feature = "rdf-star")]
+ N3Term::Triple(s) => Ok(self.map_triple(*s).into()),
+ N3Term::Variable(_) => Err(ParseError::msg(
+ "variables are not allowed in regular RDF subjects",
+ )),
+ }?,
+ predicate: match quad.predicate {
+ N3Term::NamedNode(p) => Ok(p),
+ N3Term::BlankNode(_) => Err(ParseError::msg(
+ "blank nodes are not allowed in regular RDF predicates",
+ )),
+ N3Term::Literal(_) => Err(ParseError::msg(
+ "literals are not allowed in regular RDF predicates",
+ )),
+ #[cfg(feature = "rdf-star")]
+ N3Term::Triple(_) => Err(ParseError::msg(
+ "quoted triples are not allowed in regular RDF predicates",
+ )),
+ N3Term::Variable(_) => Err(ParseError::msg(
+ "variables are not allowed in regular RDF predicates",
+ )),
+ }?,
+ object: match quad.object {
+ N3Term::NamedNode(o) => Ok(o.into()),
+ N3Term::BlankNode(o) => Ok(self.map_blank_node(o).into()),
+ N3Term::Literal(o) => Ok(o.into()),
+ #[cfg(feature = "rdf-star")]
+ N3Term::Triple(o) => Ok(self.map_triple(*o).into()),
+ N3Term::Variable(_) => Err(ParseError::msg(
+ "variables are not allowed in regular RDF objects",
+ )),
+ }?,
+ graph_name: self.map_graph_name(quad.graph_name)?,
+ })
+ }
+}
diff --git a/lib/oxrdfio/src/serializer.rs b/lib/oxrdfio/src/serializer.rs
new file mode 100644
index 00000000..35a9f29e
--- /dev/null
+++ b/lib/oxrdfio/src/serializer.rs
@@ -0,0 +1,322 @@
+//! Utilities to write RDF graphs and datasets.
+
+use crate::format::RdfFormat;
+use oxrdf::{GraphNameRef, QuadRef, TripleRef};
+#[cfg(feature = "async-tokio")]
+use oxrdfxml::ToTokioAsyncWriteRdfXmlWriter;
+use oxrdfxml::{RdfXmlSerializer, ToWriteRdfXmlWriter};
+#[cfg(feature = "async-tokio")]
+use oxttl::nquads::ToTokioAsyncWriteNQuadsWriter;
+use oxttl::nquads::{NQuadsSerializer, ToWriteNQuadsWriter};
+#[cfg(feature = "async-tokio")]
+use oxttl::ntriples::ToTokioAsyncWriteNTriplesWriter;
+use oxttl::ntriples::{NTriplesSerializer, ToWriteNTriplesWriter};
+#[cfg(feature = "async-tokio")]
+use oxttl::trig::ToTokioAsyncWriteTriGWriter;
+use oxttl::trig::{ToWriteTriGWriter, TriGSerializer};
+#[cfg(feature = "async-tokio")]
+use oxttl::turtle::ToTokioAsyncWriteTurtleWriter;
+use oxttl::turtle::{ToWriteTurtleWriter, TurtleSerializer};
+use std::io::{self, Write};
+#[cfg(feature = "async-tokio")]
+use tokio::io::{AsyncWrite, AsyncWriteExt};
+
+/// A serializer for RDF serialization formats.
+///
+/// It currently supports the following formats:
+/// * [N3](https://w3c.github.io/N3/spec/) ([`RdfFormat::N3`])
+/// * [N-Quads](https://www.w3.org/TR/n-quads/) ([`RdfFormat::NQuads`])
+/// * [N-Triples](https://www.w3.org/TR/n-triples/) ([`RdfFormat::NTriples`])
+/// * [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) ([`RdfFormat::RdfXml`])
+/// * [TriG](https://www.w3.org/TR/trig/) ([`RdfFormat::TriG`])
+/// * [Turtle](https://www.w3.org/TR/turtle/) ([`RdfFormat::Turtle`])
+///
+/// ```
+/// use oxrdfio::{RdfFormat, RdfSerializer};
+/// use oxrdf::{Quad, NamedNode};
+///
+/// let mut buffer = Vec::new();
+/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_write(&mut buffer);
+/// writer.write_quad(&Quad {
+/// subject: NamedNode::new("http://example.com/s")?.into(),
+/// predicate: NamedNode::new("http://example.com/p")?,
+/// object: NamedNode::new("http://example.com/o")?.into(),
+/// graph_name: NamedNode::new("http://example.com/g")?.into()
+/// })?;
+/// writer.finish()?;
+///
+/// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+/// # Result::<_,Box>::Ok(())
+/// ```
+pub struct RdfSerializer {
+ format: RdfFormat,
+}
+
+impl RdfSerializer {
+ /// Builds a serializer for the given format
+ #[inline]
+ pub fn from_format(format: RdfFormat) -> Self {
+ Self { format }
+ }
+
+ /// Writes to a [`Write`] implementation.
+ ///
+ /// Warning: Do not forget to run the [`finish`](ToWriteQuadWriter::finish()) method to properly write the last bytes of the file.
+ ///
+ /// Warning: This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.
+ ///
+ /// ```
+ /// use oxrdfio::{RdfFormat, RdfSerializer};
+ /// use oxrdf::{Quad, NamedNode};
+ ///
+ /// let mut buffer = Vec::new();
+ /// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_write(&mut buffer);
+ /// writer.write_quad(&Quad {
+ /// subject: NamedNode::new("http://example.com/s")?.into(),
+ /// predicate: NamedNode::new("http://example.com/p")?,
+ /// object: NamedNode::new("http://example.com/o")?.into(),
+ /// graph_name: NamedNode::new("http://example.com/g")?.into()
+ /// })?;
+ /// writer.finish()?;
+ ///
+ /// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+ /// # Result::<_,Box>::Ok(())
+ /// ```
+ pub fn serialize_to_write(&self, writer: W) -> ToWriteQuadWriter {
+ ToWriteQuadWriter {
+ formatter: match self.format {
+ RdfFormat::NQuads => ToWriteQuadWriterKind::NQuads(
+ NQuadsSerializer::new().serialize_to_write(writer),
+ ),
+ RdfFormat::NTriples => ToWriteQuadWriterKind::NTriples(
+ NTriplesSerializer::new().serialize_to_write(writer),
+ ),
+ RdfFormat::RdfXml => ToWriteQuadWriterKind::RdfXml(
+ RdfXmlSerializer::new().serialize_to_write(writer),
+ ),
+ RdfFormat::TriG => {
+ ToWriteQuadWriterKind::TriG(TriGSerializer::new().serialize_to_write(writer))
+ }
+ RdfFormat::Turtle | RdfFormat::N3 => ToWriteQuadWriterKind::Turtle(
+ TurtleSerializer::new().serialize_to_write(writer),
+ ),
+ },
+ }
+ }
+
+ /// Writes to a Tokio [`AsyncWrite`] implementation.
+ ///
+ /// Warning: Do not forget to run the [`finish`](ToTokioAsyncWriteQuadWriter::finish()) method to properly write the last bytes of the file.
+ ///
+ /// Warning: This writer does unbuffered writes. You might want to use [`BufWriter`](tokio::io::BufWriter) to avoid that.
+ ///
+ /// ```
+ /// use oxrdfio::{RdfFormat, RdfSerializer};
+ /// use oxrdf::{Quad, NamedNode};
+ /// use std::io;
+ ///
+ /// #[tokio::main(flavor = "current_thread")]
+ /// async fn main() -> io::Result<()> {
+ /// let mut buffer = Vec::new();
+ /// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_tokio_async_write(&mut buffer);
+ /// writer.write_quad(&Quad {
+ /// subject: NamedNode::new_unchecked("http://example.com/s").into(),
+ /// predicate: NamedNode::new_unchecked("http://example.com/p"),
+ /// object: NamedNode::new_unchecked("http://example.com/o").into(),
+ /// graph_name: NamedNode::new_unchecked("http://example.com/g").into()
+ /// }).await?;
+ /// writer.finish().await?;
+ ///
+ /// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+ /// Ok(())
+ /// }
+ /// ```
+ #[cfg(feature = "async-tokio")]
+ pub fn serialize_to_tokio_async_write(
+ &self,
+ writer: W,
+ ) -> ToTokioAsyncWriteQuadWriter {
+ ToTokioAsyncWriteQuadWriter {
+ formatter: match self.format {
+ RdfFormat::NQuads => ToTokioAsyncWriteQuadWriterKind::NQuads(
+ NQuadsSerializer::new().serialize_to_tokio_async_write(writer),
+ ),
+ RdfFormat::NTriples => ToTokioAsyncWriteQuadWriterKind::NTriples(
+ NTriplesSerializer::new().serialize_to_tokio_async_write(writer),
+ ),
+ RdfFormat::RdfXml => ToTokioAsyncWriteQuadWriterKind::RdfXml(
+ RdfXmlSerializer::new().serialize_to_tokio_async_write(writer),
+ ),
+ RdfFormat::TriG => ToTokioAsyncWriteQuadWriterKind::TriG(
+ TriGSerializer::new().serialize_to_tokio_async_write(writer),
+ ),
+ RdfFormat::Turtle | RdfFormat::N3 => ToTokioAsyncWriteQuadWriterKind::Turtle(
+ TurtleSerializer::new().serialize_to_tokio_async_write(writer),
+ ),
+ },
+ }
+ }
+}
+
+/// Writes quads or triples to a [`Write`] implementation.
+///
+/// Can be built using [`RdfSerializer::serialize_to_write`].
+///
+/// Warning: Do not forget to run the [`finish`](ToWriteQuadWriter::finish()) method to properly write the last bytes of the file.
+///
+/// Warning: This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.
+///
+/// ```
+/// use oxrdfio::{RdfFormat, RdfSerializer};
+/// use oxrdf::{Quad, NamedNode};
+///
+/// let mut buffer = Vec::new();
+/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_write(&mut buffer);
+/// writer.write_quad(&Quad {
+/// subject: NamedNode::new("http://example.com/s")?.into(),
+/// predicate: NamedNode::new("http://example.com/p")?,
+/// object: NamedNode::new("http://example.com/o")?.into(),
+/// graph_name: NamedNode::new("http://example.com/g")?.into(),
+/// })?;
+/// writer.finish()?;
+///
+/// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+/// # Result::<_,Box>::Ok(())
+/// ```
+#[must_use]
+pub struct ToWriteQuadWriter {
+ formatter: ToWriteQuadWriterKind,
+}
+
+enum ToWriteQuadWriterKind {
+ NQuads(ToWriteNQuadsWriter),
+ NTriples(ToWriteNTriplesWriter),
+ RdfXml(ToWriteRdfXmlWriter),
+ TriG(ToWriteTriGWriter),
+ Turtle(ToWriteTurtleWriter),
+}
+
+impl ToWriteQuadWriter {
+ /// Writes a [`QuadRef`]
+ pub fn write_quad<'a>(&mut self, quad: impl Into>) -> io::Result<()> {
+ match &mut self.formatter {
+ ToWriteQuadWriterKind::NQuads(writer) => writer.write_quad(quad),
+ ToWriteQuadWriterKind::NTriples(writer) => writer.write_triple(to_triple(quad)?),
+ ToWriteQuadWriterKind::RdfXml(writer) => writer.write_triple(to_triple(quad)?),
+ ToWriteQuadWriterKind::TriG(writer) => writer.write_quad(quad),
+ ToWriteQuadWriterKind::Turtle(writer) => writer.write_triple(to_triple(quad)?),
+ }
+ }
+
+ /// Writes a [`TripleRef`]
+ pub fn write_triple<'a>(&mut self, triple: impl Into>) -> io::Result<()> {
+ self.write_quad(triple.into().in_graph(GraphNameRef::DefaultGraph))
+ }
+
+ /// Writes the last bytes of the file
+ pub fn finish(self) -> io::Result<()> {
+ match self.formatter {
+ ToWriteQuadWriterKind::NQuads(writer) => writer.finish(),
+ ToWriteQuadWriterKind::NTriples(writer) => writer.finish(),
+ ToWriteQuadWriterKind::RdfXml(writer) => writer.finish()?,
+ ToWriteQuadWriterKind::TriG(writer) => writer.finish()?,
+ ToWriteQuadWriterKind::Turtle(writer) => writer.finish()?,
+ }
+ .flush()
+ }
+}
+
+/// Writes quads or triples to a [`Write`] implementation.
+///
+/// Can be built using [`RdfSerializer::serialize_to_write`].
+///
+/// Warning: Do not forget to run the [`finish`](ToWriteQuadWriter::finish()) method to properly write the last bytes of the file.
+///
+/// Warning: This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.
+///
+/// ```
+/// use oxrdfio::{RdfFormat, RdfSerializer};
+/// use oxrdf::{Quad, NamedNode};
+/// use std::io;
+///
+/// #[tokio::main(flavor = "current_thread")]
+/// async fn main() -> io::Result<()> {
+/// let mut buffer = Vec::new();
+/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_tokio_async_write(&mut buffer);
+/// writer.write_quad(&Quad {
+/// subject: NamedNode::new_unchecked("http://example.com/s").into(),
+/// predicate: NamedNode::new_unchecked("http://example.com/p"),
+/// object: NamedNode::new_unchecked("http://example.com/o").into(),
+/// graph_name: NamedNode::new_unchecked("http://example.com/g").into()
+/// }).await?;
+/// writer.finish().await?;
+///
+/// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+/// Ok(())
+/// }
+/// ```
+#[must_use]
+#[cfg(feature = "async-tokio")]
+pub struct ToTokioAsyncWriteQuadWriter {
+ formatter: ToTokioAsyncWriteQuadWriterKind,
+}
+
+#[cfg(feature = "async-tokio")]
+enum ToTokioAsyncWriteQuadWriterKind {
+ NQuads(ToTokioAsyncWriteNQuadsWriter),
+ NTriples(ToTokioAsyncWriteNTriplesWriter),
+ RdfXml(ToTokioAsyncWriteRdfXmlWriter),
+ TriG(ToTokioAsyncWriteTriGWriter),
+ Turtle(ToTokioAsyncWriteTurtleWriter),
+}
+
+#[cfg(feature = "async-tokio")]
+impl ToTokioAsyncWriteQuadWriter {
+ /// Writes a [`QuadRef`]
+ pub async fn write_quad<'a>(&mut self, quad: impl Into>) -> io::Result<()> {
+ match &mut self.formatter {
+ ToTokioAsyncWriteQuadWriterKind::NQuads(writer) => writer.write_quad(quad).await,
+ ToTokioAsyncWriteQuadWriterKind::NTriples(writer) => {
+ writer.write_triple(to_triple(quad)?).await
+ }
+ ToTokioAsyncWriteQuadWriterKind::RdfXml(writer) => {
+ writer.write_triple(to_triple(quad)?).await
+ }
+ ToTokioAsyncWriteQuadWriterKind::TriG(writer) => writer.write_quad(quad).await,
+ ToTokioAsyncWriteQuadWriterKind::Turtle(writer) => {
+ writer.write_triple(to_triple(quad)?).await
+ }
+ }
+ }
+
+ /// Writes a [`TripleRef`]
+ pub async fn write_triple<'a>(&mut self, triple: impl Into>) -> io::Result<()> {
+ self.write_quad(triple.into().in_graph(GraphNameRef::DefaultGraph))
+ .await
+ }
+
+ /// Writes the last bytes of the file
+ pub async fn finish(self) -> io::Result<()> {
+ match self.formatter {
+ ToTokioAsyncWriteQuadWriterKind::NQuads(writer) => writer.finish(),
+ ToTokioAsyncWriteQuadWriterKind::NTriples(writer) => writer.finish(),
+ ToTokioAsyncWriteQuadWriterKind::RdfXml(writer) => writer.finish().await?,
+ ToTokioAsyncWriteQuadWriterKind::TriG(writer) => writer.finish().await?,
+ ToTokioAsyncWriteQuadWriterKind::Turtle(writer) => writer.finish().await?,
+ }
+ .flush()
+ .await
+ }
+}
+
+fn to_triple<'a>(quad: impl Into>) -> io::Result> {
+ let quad = quad.into();
+ if quad.graph_name.is_default_graph() {
+ Ok(quad.into())
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Only quads in the default graph can be serialized to a RDF graph format",
+ ))
+ }
+}
diff --git a/lib/src/io/error.rs b/lib/src/io/error.rs
index bea22d8e..7cbdc8ac 100644
--- a/lib/src/io/error.rs
+++ b/lib/src/io/error.rs
@@ -43,40 +43,21 @@ impl Error for ParseError {
}
}
-impl From for SyntaxError {
+impl From for SyntaxError {
#[inline]
- fn from(error: oxttl::SyntaxError) -> Self {
+ fn from(error: oxrdfio::SyntaxError) -> Self {
SyntaxError {
- inner: SyntaxErrorKind::Turtle(error),
+ inner: SyntaxErrorKind::IO(error),
}
}
}
-impl From for ParseError {
+impl From for ParseError {
#[inline]
- fn from(error: oxttl::ParseError) -> Self {
+ fn from(error: oxrdfio::ParseError) -> Self {
match error {
- oxttl::ParseError::Syntax(e) => Self::Syntax(e.into()),
- oxttl::ParseError::Io(e) => Self::Io(e),
- }
- }
-}
-
-impl From for SyntaxError {
- #[inline]
- fn from(error: oxrdfxml::SyntaxError) -> Self {
- SyntaxError {
- inner: SyntaxErrorKind::RdfXml(error),
- }
- }
-}
-
-impl From for ParseError {
- #[inline]
- fn from(error: oxrdfxml::ParseError) -> Self {
- match error {
- oxrdfxml::ParseError::Syntax(e) => Self::Syntax(e.into()),
- oxrdfxml::ParseError::Io(e) => Self::Io(e),
+ oxrdfio::ParseError::Syntax(e) => Self::Syntax(e.into()),
+ oxrdfio::ParseError::Io(e) => Self::Io(e),
}
}
}
@@ -113,8 +94,7 @@ pub struct SyntaxError {
#[derive(Debug)]
enum SyntaxErrorKind {
- Turtle(oxttl::SyntaxError),
- RdfXml(oxrdfxml::SyntaxError),
+ IO(oxrdfio::SyntaxError),
InvalidBaseIri { iri: String, error: IriParseError },
}
@@ -122,8 +102,7 @@ impl fmt::Display for SyntaxError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
- SyntaxErrorKind::Turtle(e) => e.fmt(f),
- SyntaxErrorKind::RdfXml(e) => e.fmt(f),
+ SyntaxErrorKind::IO(e) => e.fmt(f),
SyntaxErrorKind::InvalidBaseIri { iri, error } => {
write!(f, "Invalid base IRI '{iri}': {error}")
}
@@ -135,8 +114,7 @@ impl Error for SyntaxError {
#[inline]
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.inner {
- SyntaxErrorKind::Turtle(e) => Some(e),
- SyntaxErrorKind::RdfXml(e) => Some(e),
+ SyntaxErrorKind::IO(e) => Some(e),
SyntaxErrorKind::InvalidBaseIri { .. } => None,
}
}
@@ -146,8 +124,7 @@ impl From for io::Error {
#[inline]
fn from(error: SyntaxError) -> Self {
match error.inner {
- SyntaxErrorKind::Turtle(error) => error.into(),
- SyntaxErrorKind::RdfXml(error) => error.into(),
+ SyntaxErrorKind::IO(error) => error.into(),
SyntaxErrorKind::InvalidBaseIri { iri, error } => Self::new(
io::ErrorKind::InvalidInput,
format!("Invalid IRI '{iri}': {error}"),
diff --git a/lib/src/io/format.rs b/lib/src/io/format.rs
index 7c95ddcb..01e112ac 100644
--- a/lib/src/io/format.rs
+++ b/lib/src/io/format.rs
@@ -1,3 +1,5 @@
+use oxrdfio::RdfFormat;
+
/// [RDF graph](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-graph) serialization formats.
///
/// This enumeration is non exhaustive. New formats like JSON-LD will be added in the future.
@@ -102,6 +104,16 @@ impl GraphFormat {
}
}
+impl From for RdfFormat {
+ fn from(format: GraphFormat) -> Self {
+ match format {
+ GraphFormat::NTriples => Self::NTriples,
+ GraphFormat::Turtle => Self::Turtle,
+ GraphFormat::RdfXml => Self::RdfXml,
+ }
+ }
+}
+
/// [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) serialization formats.
///
/// This enumeration is non exhaustive. New formats like JSON-LD will be added in the future.
@@ -198,6 +210,15 @@ impl DatasetFormat {
}
}
+impl From for RdfFormat {
+ fn from(format: DatasetFormat) -> Self {
+ match format {
+ DatasetFormat::NQuads => Self::NQuads,
+ DatasetFormat::TriG => Self::TriG,
+ }
+ }
+}
+
impl TryFrom for GraphFormat {
type Error = ();
diff --git a/lib/src/io/mod.rs b/lib/src/io/mod.rs
index 2e5269de..a8185918 100644
--- a/lib/src/io/mod.rs
+++ b/lib/src/io/mod.rs
@@ -8,3 +8,7 @@ pub mod write;
pub use self::format::{DatasetFormat, GraphFormat};
pub use self::read::{DatasetParser, GraphParser};
pub use self::write::{DatasetSerializer, GraphSerializer};
+pub use oxrdfio::{
+ FromReadQuadReader, ParseError, RdfFormat, RdfParser, RdfSerializer, SyntaxError,
+ ToWriteQuadWriter,
+};
diff --git a/lib/src/io/read.rs b/lib/src/io/read.rs
index eddaabed..fe414aa1 100644
--- a/lib/src/io/read.rs
+++ b/lib/src/io/read.rs
@@ -4,12 +4,7 @@ pub use crate::io::error::{ParseError, SyntaxError};
use crate::io::{DatasetFormat, GraphFormat};
use crate::model::*;
use oxiri::IriParseError;
-use oxrdfxml::{FromReadRdfXmlReader, RdfXmlParser};
-use oxttl::nquads::{FromReadNQuadsReader, NQuadsParser};
-use oxttl::ntriples::{FromReadNTriplesReader, NTriplesParser};
-use oxttl::trig::{FromReadTriGReader, TriGParser};
-use oxttl::turtle::{FromReadTurtleReader, TurtleParser};
-use std::collections::HashMap;
+use oxrdfio::{FromReadQuadReader, RdfParser};
use std::io::Read;
/// Parsers for RDF graph serialization formats.
@@ -27,18 +22,12 @@ use std::io::Read;
/// let parser = GraphParser::from_format(GraphFormat::NTriples);
/// let triples = parser.read_triples(file.as_bytes()).collect::,_>>()?;
///
-///assert_eq!(triples.len(), 1);
-///assert_eq!(triples[0].subject.to_string(), "");
+/// assert_eq!(triples.len(), 1);
+/// assert_eq!(triples[0].subject.to_string(), "");
/// # std::io::Result::Ok(())
/// ```
pub struct GraphParser {
- inner: GraphParserKind,
-}
-
-enum GraphParserKind {
- NTriples(NTriplesParser),
- Turtle(TurtleParser),
- RdfXml(RdfXmlParser),
+ inner: RdfParser,
}
impl GraphParser {
@@ -46,15 +35,9 @@ impl GraphParser {
#[inline]
pub fn from_format(format: GraphFormat) -> Self {
Self {
- inner: match format {
- GraphFormat::NTriples => {
- GraphParserKind::NTriples(NTriplesParser::new().with_quoted_triples())
- }
- GraphFormat::Turtle => {
- GraphParserKind::Turtle(TurtleParser::new().with_quoted_triples())
- }
- GraphFormat::RdfXml => GraphParserKind::RdfXml(RdfXmlParser::new()),
- },
+ inner: RdfParser::from_format(format.into())
+ .without_named_graphs()
+ .rename_blank_nodes(),
}
}
@@ -68,30 +51,21 @@ impl GraphParser {
/// let parser = GraphParser::from_format(GraphFormat::Turtle).with_base_iri("http://example.com")?;
/// let triples = parser.read_triples(file.as_bytes()).collect::,_>>()?;
///
- ///assert_eq!(triples.len(), 1);
- ///assert_eq!(triples[0].subject.to_string(), "");
+ /// assert_eq!(triples.len(), 1);
+ /// assert_eq!(triples[0].subject.to_string(), "");
/// # Result::<_,Box>::Ok(())
/// ```
#[inline]
pub fn with_base_iri(self, base_iri: impl Into) -> Result {
Ok(Self {
- inner: match self.inner {
- GraphParserKind::NTriples(p) => GraphParserKind::NTriples(p),
- GraphParserKind::Turtle(p) => GraphParserKind::Turtle(p.with_base_iri(base_iri)?),
- GraphParserKind::RdfXml(p) => GraphParserKind::RdfXml(p.with_base_iri(base_iri)?),
- },
+ inner: self.inner.with_base_iri(base_iri)?,
})
}
/// Executes the parsing itself on a [`Read`] implementation and returns an iterator of triples.
pub fn read_triples(&self, reader: R) -> TripleReader {
TripleReader {
- mapper: BlankNodeMapper::default(),
- parser: match &self.inner {
- GraphParserKind::NTriples(p) => TripleReaderKind::NTriples(p.parse_read(reader)),
- GraphParserKind::Turtle(p) => TripleReaderKind::Turtle(p.parse_read(reader)),
- GraphParserKind::RdfXml(p) => TripleReaderKind::RdfXml(p.parse_read(reader)),
- },
+ parser: self.inner.parse_read(reader),
}
}
}
@@ -107,41 +81,20 @@ impl GraphParser {
/// let parser = GraphParser::from_format(GraphFormat::NTriples);
/// let triples = parser.read_triples(file.as_bytes()).collect::,_>>()?;
///
-///assert_eq!(triples.len(), 1);
-///assert_eq!(triples[0].subject.to_string(), "");
+/// assert_eq!(triples.len(), 1);
+/// assert_eq!(triples[0].subject.to_string(), "");
/// # std::io::Result::Ok(())
/// ```
#[must_use]
pub struct TripleReader {
- mapper: BlankNodeMapper,
- parser: TripleReaderKind,
-}
-
-#[allow(clippy::large_enum_variant)]
-enum TripleReaderKind {
- NTriples(FromReadNTriplesReader),
- Turtle(FromReadTurtleReader),
- RdfXml(FromReadRdfXmlReader),
+ parser: FromReadQuadReader,
}
impl Iterator for TripleReader {
type Item = Result;
fn next(&mut self) -> Option> {
- Some(match &mut self.parser {
- TripleReaderKind::NTriples(parser) => match parser.next()? {
- Ok(triple) => Ok(self.mapper.triple(triple)),
- Err(e) => Err(e.into()),
- },
- TripleReaderKind::Turtle(parser) => match parser.next()? {
- Ok(triple) => Ok(self.mapper.triple(triple)),
- Err(e) => Err(e.into()),
- },
- TripleReaderKind::RdfXml(parser) => match parser.next()? {
- Ok(triple) => Ok(self.mapper.triple(triple)),
- Err(e) => Err(e.into()),
- },
- })
+ Some(self.parser.next()?.map(Into::into).map_err(Into::into))
}
}
@@ -159,17 +112,12 @@ impl Iterator for TripleReader {
/// let parser = DatasetParser::from_format(DatasetFormat::NQuads);
/// let quads = parser.read_quads(file.as_bytes()).collect::,_>>()?;
///
-///assert_eq!(quads.len(), 1);
-///assert_eq!(quads[0].subject.to_string(), "");
+/// assert_eq!(quads.len(), 1);
+/// assert_eq!(quads[0].subject.to_string(), "");
/// # std::io::Result::Ok(())
/// ```
pub struct DatasetParser {
- inner: DatasetParserKind,
-}
-
-enum DatasetParserKind {
- NQuads(NQuadsParser),
- TriG(TriGParser),
+ inner: RdfParser,
}
impl DatasetParser {
@@ -177,14 +125,7 @@ impl DatasetParser {
#[inline]
pub fn from_format(format: DatasetFormat) -> Self {
Self {
- inner: match format {
- DatasetFormat::NQuads => {
- DatasetParserKind::NQuads(NQuadsParser::new().with_quoted_triples())
- }
- DatasetFormat::TriG => {
- DatasetParserKind::TriG(TriGParser::new().with_quoted_triples())
- }
- },
+ inner: RdfParser::from_format(format.into()).rename_blank_nodes(),
}
}
@@ -198,28 +139,21 @@ impl DatasetParser {
/// let parser = DatasetParser::from_format(DatasetFormat::TriG).with_base_iri("http://example.com")?;
/// let triples = parser.read_quads(file.as_bytes()).collect::,_>>()?;
///
- ///assert_eq!(triples.len(), 1);
- ///assert_eq!(triples[0].subject.to_string(), "");
+ /// assert_eq!(triples.len(), 1);
+ /// assert_eq!(triples[0].subject.to_string(), "");
/// # Result::<_,Box>::Ok(())
/// ```
#[inline]
pub fn with_base_iri(self, base_iri: impl Into) -> Result {
Ok(Self {
- inner: match self.inner {
- DatasetParserKind::NQuads(p) => DatasetParserKind::NQuads(p),
- DatasetParserKind::TriG(p) => DatasetParserKind::TriG(p.with_base_iri(base_iri)?),
- },
+ inner: self.inner.with_base_iri(base_iri)?,
})
}
/// Executes the parsing itself on a [`Read`] implementation and returns an iterator of quads.
pub fn read_quads(&self, reader: R) -> QuadReader {
QuadReader {
- mapper: BlankNodeMapper::default(),
- parser: match &self.inner {
- DatasetParserKind::NQuads(p) => QuadReaderKind::NQuads(p.parse_read(reader)),
- DatasetParserKind::TriG(p) => QuadReaderKind::TriG(p.parse_read(reader)),
- },
+ parser: self.inner.parse_read(reader),
}
}
}
@@ -235,90 +169,19 @@ impl DatasetParser {
/// let parser = DatasetParser::from_format(DatasetFormat::NQuads);
/// let quads = parser.read_quads(file.as_bytes()).collect::,_>>()?;
///
-///assert_eq!(quads.len(), 1);
-///assert_eq!(quads[0].subject.to_string(), "");
+/// assert_eq!(quads.len(), 1);
+/// assert_eq!(quads[0].subject.to_string(), "");
/// # std::io::Result::Ok(())
/// ```
#[must_use]
pub struct QuadReader {
- mapper: BlankNodeMapper,
- parser: QuadReaderKind,
-}
-
-enum QuadReaderKind {
- NQuads(FromReadNQuadsReader),
- TriG(FromReadTriGReader),
+ parser: FromReadQuadReader,
}
impl Iterator for QuadReader {
type Item = Result;
fn next(&mut self) -> Option> {
- Some(match &mut self.parser {
- QuadReaderKind::NQuads(parser) => match parser.next()? {
- Ok(quad) => Ok(self.mapper.quad(quad)),
- Err(e) => Err(e.into()),
- },
- QuadReaderKind::TriG(parser) => match parser.next()? {
- Ok(quad) => Ok(self.mapper.quad(quad)),
- Err(e) => Err(e.into()),
- },
- })
- }
-}
-
-#[derive(Default)]
-struct BlankNodeMapper {
- bnode_map: HashMap,
-}
-
-impl BlankNodeMapper {
- fn blank_node(&mut self, node: BlankNode) -> BlankNode {
- self.bnode_map
- .entry(node)
- .or_insert_with(BlankNode::default)
- .clone()
- }
-
- fn subject(&mut self, node: Subject) -> Subject {
- match node {
- Subject::NamedNode(node) => node.into(),
- Subject::BlankNode(node) => self.blank_node(node).into(),
- Subject::Triple(triple) => self.triple(*triple).into(),
- }
- }
-
- fn term(&mut self, node: Term) -> Term {
- match node {
- Term::NamedNode(node) => node.into(),
- Term::BlankNode(node) => self.blank_node(node).into(),
- Term::Literal(literal) => literal.into(),
- Term::Triple(triple) => self.triple(*triple).into(),
- }
- }
-
- fn triple(&mut self, triple: Triple) -> Triple {
- Triple {
- subject: self.subject(triple.subject),
- predicate: triple.predicate,
- object: self.term(triple.object),
- }
- }
-
- fn graph_name(&mut self, graph_name: GraphName) -> GraphName {
- match graph_name {
- GraphName::NamedNode(node) => node.into(),
- GraphName::BlankNode(node) => self.blank_node(node).into(),
- GraphName::DefaultGraph => GraphName::DefaultGraph,
- }
- }
-
- fn quad(&mut self, quad: Quad) -> Quad {
- Quad {
- subject: self.subject(quad.subject),
- predicate: quad.predicate,
- object: self.term(quad.object),
- graph_name: self.graph_name(quad.graph_name),
- }
+ Some(self.parser.next()?.map_err(Into::into))
}
}
diff --git a/lib/src/io/write.rs b/lib/src/io/write.rs
index 1661343f..b9373afc 100644
--- a/lib/src/io/write.rs
+++ b/lib/src/io/write.rs
@@ -2,11 +2,7 @@
use crate::io::{DatasetFormat, GraphFormat};
use crate::model::*;
-use oxrdfxml::{RdfXmlSerializer, ToWriteRdfXmlWriter};
-use oxttl::nquads::{NQuadsSerializer, ToWriteNQuadsWriter};
-use oxttl::ntriples::{NTriplesSerializer, ToWriteNTriplesWriter};
-use oxttl::trig::{ToWriteTriGWriter, TriGSerializer};
-use oxttl::turtle::{ToWriteTurtleWriter, TurtleSerializer};
+use oxrdfio::{RdfSerializer, ToWriteQuadWriter};
use std::io::{self, Write};
/// A serializer for RDF graph serialization formats.
@@ -29,34 +25,26 @@ use std::io::{self, Write};
/// })?;
/// writer.finish()?;
///
-///assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+/// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
/// # Result::<_,Box>::Ok(())
/// ```
pub struct GraphSerializer {
- format: GraphFormat,
+ inner: RdfSerializer,
}
impl GraphSerializer {
/// Builds a serializer for the given format
#[inline]
pub fn from_format(format: GraphFormat) -> Self {
- Self { format }
+ Self {
+ inner: RdfSerializer::from_format(format.into()),
+ }
}
/// Returns a [`TripleWriter`] allowing writing triples into the given [`Write`] implementation
pub fn triple_writer(&self, writer: W) -> TripleWriter {
TripleWriter {
- formatter: match self.format {
- GraphFormat::NTriples => {
- TripleWriterKind::NTriples(NTriplesSerializer::new().serialize_to_write(writer))
- }
- GraphFormat::Turtle => {
- TripleWriterKind::Turtle(TurtleSerializer::new().serialize_to_write(writer))
- }
- GraphFormat::RdfXml => {
- TripleWriterKind::RdfXml(RdfXmlSerializer::new().serialize_to_write(writer))
- }
- },
+ writer: self.inner.serialize_to_write(writer),
}
}
}
@@ -79,37 +67,23 @@ impl GraphSerializer {
/// })?;
/// writer.finish()?;
///
-///assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+/// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
/// # Result::<_,Box>::Ok(())
/// ```
#[must_use]
pub struct TripleWriter {
- formatter: TripleWriterKind,
-}
-
-enum TripleWriterKind {
- NTriples(ToWriteNTriplesWriter),
- Turtle(ToWriteTurtleWriter),
- RdfXml(ToWriteRdfXmlWriter),
+ writer: ToWriteQuadWriter,
}
impl TripleWriter {
/// Writes a triple
pub fn write<'a>(&mut self, triple: impl Into>) -> io::Result<()> {
- match &mut self.formatter {
- TripleWriterKind::NTriples(writer) => writer.write_triple(triple),
- TripleWriterKind::Turtle(writer) => writer.write_triple(triple),
- TripleWriterKind::RdfXml(writer) => writer.write_triple(triple),
- }
+ self.writer.write_triple(triple)
}
/// Writes the last bytes of the file
pub fn finish(self) -> io::Result<()> {
- match self.formatter {
- TripleWriterKind::NTriples(writer) => writer.finish().flush(),
- TripleWriterKind::Turtle(writer) => writer.finish()?.flush(),
- TripleWriterKind::RdfXml(formatter) => formatter.finish()?.flush(),
- }
+ self.writer.finish()
}
}
@@ -133,31 +107,26 @@ impl TripleWriter {
/// })?;
/// writer.finish()?;
///
-///assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+/// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
/// # Result::<_,Box>::Ok(())
/// ```
pub struct DatasetSerializer {
- format: DatasetFormat,
+ inner: RdfSerializer,
}
impl DatasetSerializer {
/// Builds a serializer for the given format
#[inline]
pub fn from_format(format: DatasetFormat) -> Self {
- Self { format }
+ Self {
+ inner: RdfSerializer::from_format(format.into()),
+ }
}
/// Returns a [`QuadWriter`] allowing writing triples into the given [`Write`] implementation
pub fn quad_writer(&self, writer: W) -> QuadWriter {
QuadWriter {
- formatter: match self.format {
- DatasetFormat::NQuads => {
- QuadWriterKind::NQuads(NQuadsSerializer::new().serialize_to_write(writer))
- }
- DatasetFormat::TriG => {
- QuadWriterKind::TriG(TriGSerializer::new().serialize_to_write(writer))
- }
- },
+ writer: self.inner.serialize_to_write(writer),
}
}
}
@@ -181,33 +150,22 @@ impl DatasetSerializer {
/// })?;
/// writer.finish()?;
///
-///assert_eq!(buffer.as_slice(), " .\n".as_bytes());
+/// assert_eq!(buffer.as_slice(), " .\n".as_bytes());
/// # Result::<_,Box>::Ok(())
/// ```
#[must_use]
pub struct QuadWriter {
- formatter: QuadWriterKind,
-}
-
-enum QuadWriterKind {
- NQuads(ToWriteNQuadsWriter),
- TriG(ToWriteTriGWriter),
+ writer: ToWriteQuadWriter,
}
impl QuadWriter {
/// Writes a quad
pub fn write<'a>(&mut self, quad: impl Into>) -> io::Result<()> {
- match &mut self.formatter {
- QuadWriterKind::NQuads(writer) => writer.write_quad(quad),
- QuadWriterKind::TriG(writer) => writer.write_quad(quad),
- }
+ self.writer.write_quad(quad)
}
/// Writes the last bytes of the file
pub fn finish(self) -> io::Result<()> {
- match self.formatter {
- QuadWriterKind::NQuads(writer) => writer.finish().flush(),
- QuadWriterKind::TriG(writer) => writer.finish()?.flush(),
- }
+ self.writer.finish()
}
}