Serialization: allows to set prefixes

pull/759/head
Tpt 8 months ago committed by Thomas Tanon
parent 0a7cea5e25
commit ef765666be
  1. 4
      .github/workflows/tests.yml
  2. 10
      Cargo.lock
  3. 19
      cli/src/main.rs
  4. 43
      fuzz/fuzz_targets/trig.rs
  5. 8
      lib/Cargo.toml
  6. 2
      lib/oxrdf/Cargo.toml
  7. 4
      lib/oxrdfio/Cargo.toml
  8. 26
      lib/oxrdfio/src/parser.rs
  9. 164
      lib/oxrdfio/src/serializer.rs
  10. 4
      lib/oxrdfxml/Cargo.toml
  11. 209
      lib/oxrdfxml/src/serializer.rs
  12. 2
      lib/oxttl/Cargo.toml
  13. 8
      lib/oxttl/src/lexer.rs
  14. 4
      lib/oxttl/src/line_formats.rs
  15. 4
      lib/oxttl/src/n3.rs
  16. 2
      lib/oxttl/src/nquads.rs
  17. 2
      lib/oxttl/src/ntriples.rs
  18. 4
      lib/oxttl/src/terse.rs
  19. 341
      lib/oxttl/src/trig.rs
  20. 108
      lib/oxttl/src/turtle.rs
  21. 4
      lib/sparesults/Cargo.toml
  22. 7
      lib/sparesults/src/xml.rs
  23. 4
      lib/spargebra/Cargo.toml
  24. 6
      lib/sparopt/Cargo.toml

@ -42,8 +42,12 @@ jobs:
working-directory: ./lib/oxttl
- run: cargo clippy --all-targets -- -D warnings -D clippy::all
working-directory: ./lib/oxrdfio
- run: cargo clippy --all-targets --features async-tokio -- -D warnings -D clippy::all
working-directory: ./lib/oxrdfio
- run: cargo clippy --all-targets -- -D warnings -D clippy::all
working-directory: ./lib/sparesults
- run: cargo clippy --all-targets --features async-tokio -- -D warnings -D clippy::all
working-directory: ./lib/sparesults
- run: cargo clippy --all-targets -- -D warnings -D clippy::all
working-directory: ./lib/spargebra
- run: cargo clippy --all-targets -- -D warnings -D clippy::all

10
Cargo.lock generated

@ -1104,7 +1104,7 @@ checksum = "b225dad32cfaa43a960b93f01fa7f87528ac07e794b80f6d9a0153e0222557e2"
[[package]]
name = "oxrdf"
version = "0.2.0-alpha.1"
version = "0.2.0-alpha.2-dev"
dependencies = [
"oxilangtag",
"oxiri",
@ -1124,7 +1124,7 @@ dependencies = [
[[package]]
name = "oxrdfxml"
version = "0.1.0-alpha.1"
version = "0.1.0-alpha.2-dev"
dependencies = [
"oxilangtag",
"oxiri",
@ -1719,7 +1719,7 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "sparesults"
version = "0.2.0-alpha.1"
version = "0.2.0-alpha.2-dev"
dependencies = [
"json-event-parser",
"memchr",
@ -1730,7 +1730,7 @@ dependencies = [
[[package]]
name = "spargebra"
version = "0.3.0-alpha.1"
version = "0.3.0-alpha.2-dev"
dependencies = [
"oxilangtag",
"oxiri",
@ -1741,7 +1741,7 @@ dependencies = [
[[package]]
name = "sparopt"
version = "0.1.0-alpha.1"
version = "0.1.0-alpha.2-dev"
dependencies = [
"oxrdf",
"rand",

@ -714,7 +714,7 @@ pub fn main() -> anyhow::Result<()> {
let to_format = if let Some(format) = to_format {
rdf_format_from_name(&format)?
} else if let Some(file) = &from_file {
} else if let Some(file) = &to_file {
rdf_format_from_path(file)?
} else {
bail!("The --to-format option must be set when writing to stdout")
@ -826,14 +826,21 @@ fn dump<W: Write>(
fn do_convert<R: Read, W: Write>(
parser: RdfParser,
read: R,
serializer: RdfSerializer,
mut serializer: RdfSerializer,
write: W,
lenient: bool,
from_graph: &Option<GraphName>,
default_graph: &GraphName,
) -> anyhow::Result<W> {
let mut parser = parser.parse_read(read);
let first = parser.next(); // We read the first element to get prefixes
for (prefix_name, prefix_iri) in parser.prefixes() {
serializer = serializer
.with_prefix(prefix_name, prefix_iri)
.with_context(|| format!("Invalid IRI for prefix {prefix_name}: {prefix_iri}"))?;
}
let mut writer = serializer.serialize_to_write(write);
for quad_result in parser.parse_read(read) {
for quad_result in first.into_iter().chain(parser) {
match quad_result {
Ok(mut quad) => {
if let Some(from_graph) = from_graph {
@ -2239,8 +2246,8 @@ mod tests {
#[test]
fn cli_convert_file() -> Result<()> {
let input_file = NamedTempFile::new("input.ttl")?;
input_file.write_str("<s> <p> <o> .")?;
let output_file = NamedTempFile::new("output.nt")?;
input_file.write_str("@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person ;\n\tschema:name \"Foo Bar\"@en .\n")?;
let output_file = NamedTempFile::new("output.rdf")?;
cli_command()?
.arg("convert")
.arg("--from-file")
@ -2252,7 +2259,7 @@ mod tests {
.assert()
.success();
output_file
.assert("<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n");
.assert("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:schema=\"http://schema.org/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<schema:Person rdf:about=\"http://example.com#me\">\n\t\t<schema:name xml:lang=\"en\">Foo Bar</schema:name>\n\t</schema:Person>\n</rdf:RDF>");
Ok(())
}

@ -7,7 +7,7 @@ use oxttl::{TriGParser, TriGSerializer};
fn parse<'a>(
chunks: impl IntoIterator<Item = &'a [u8]>,
unchecked: bool,
) -> (Vec<Quad>, Vec<String>) {
) -> (Vec<Quad>, Vec<String>, Vec<(String, String)>) {
let mut quads = Vec::new();
let mut errors = Vec::new();
let mut parser = TriGParser::new()
@ -35,7 +35,14 @@ fn parse<'a>(
}
}
assert!(reader.is_end());
(quads, errors)
(
quads,
errors,
reader
.prefixes()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect(),
)
}
fn count_triple_blank_nodes(triple: &Triple) -> usize {
@ -62,8 +69,12 @@ fn count_quad_blank_nodes(quad: &Quad) -> usize {
}) + usize::from(matches!(quad.graph_name, GraphName::BlankNode(_)))
}
fn serialize_quads(quads: &[Quad]) -> Vec<u8> {
let mut writer = TriGSerializer::new().serialize_to_write(Vec::new());
fn serialize_quads(quads: &[Quad], prefixes: Vec<(String, String)>) -> Vec<u8> {
let mut serializer = TriGSerializer::new();
for (prefix_name, prefix_iri) in prefixes {
serializer = serializer.with_prefix(prefix_name, prefix_iri).unwrap();
}
let mut writer = serializer.serialize_to_write(Vec::new());
for quad in quads {
writer.write_quad(quad).unwrap();
}
@ -72,9 +83,9 @@ fn serialize_quads(quads: &[Quad]) -> Vec<u8> {
fuzz_target!(|data: &[u8]| {
// We parse with splitting
let (quads, errors) = parse(data.split(|c| *c == 0xFF), false);
let (quads, errors, prefixes) = parse(data.split(|c| *c == 0xFF), false);
// We parse without splitting
let (quads_without_split, errors_without_split) = parse(
let (quads_without_split, errors_without_split, _) = parse(
[data
.iter()
.copied()
@ -83,7 +94,7 @@ fuzz_target!(|data: &[u8]| {
.as_slice()],
false,
);
let (quads_unchecked, errors_unchecked) = parse(data.split(|c| *c == 0xFF), true);
let (quads_unchecked, errors_unchecked, _) = parse(data.split(|c| *c == 0xFF), true);
if errors.is_empty() {
assert!(errors_unchecked.is_empty());
}
@ -94,16 +105,16 @@ fuzz_target!(|data: &[u8]| {
quads,
quads_without_split,
"With split:\n{}\nWithout split:\n{}",
String::from_utf8_lossy(&serialize_quads(&quads)),
String::from_utf8_lossy(&serialize_quads(&quads_without_split))
String::from_utf8_lossy(&serialize_quads(&quads, Vec::new())),
String::from_utf8_lossy(&serialize_quads(&quads_without_split, Vec::new()))
);
if errors.is_empty() {
assert_eq!(
quads,
quads_unchecked,
"Validating:\n{}\nUnchecked:\n{}",
String::from_utf8_lossy(&serialize_quads(&quads)),
String::from_utf8_lossy(&serialize_quads(&quads_unchecked))
String::from_utf8_lossy(&serialize_quads(&quads, Vec::new())),
String::from_utf8_lossy(&serialize_quads(&quads_unchecked, Vec::new()))
);
}
} else if bnodes_count <= 4 {
@ -115,8 +126,8 @@ fuzz_target!(|data: &[u8]| {
dataset_with_split,
dataset_without_split,
"With split:\n{}\nWithout split:\n{}",
String::from_utf8_lossy(&serialize_quads(&quads)),
String::from_utf8_lossy(&serialize_quads(&quads_without_split))
String::from_utf8_lossy(&serialize_quads(&quads, Vec::new())),
String::from_utf8_lossy(&serialize_quads(&quads_without_split, Vec::new()))
);
if errors.is_empty() {
if errors.is_empty() {
@ -126,8 +137,8 @@ fuzz_target!(|data: &[u8]| {
dataset_with_split,
dataset_unchecked,
"Validating:\n{}\nUnchecked:\n{}",
String::from_utf8_lossy(&serialize_quads(&quads)),
String::from_utf8_lossy(&serialize_quads(&quads_unchecked))
String::from_utf8_lossy(&serialize_quads(&quads, Vec::new())),
String::from_utf8_lossy(&serialize_quads(&quads_unchecked, Vec::new()))
);
}
}
@ -135,7 +146,7 @@ fuzz_target!(|data: &[u8]| {
assert_eq!(errors, errors_without_split);
// We serialize
let new_serialization = serialize_quads(&quads);
let new_serialization = serialize_quads(&quads, prefixes);
// We parse the serialization
let new_quads = TriGParser::new()

@ -32,7 +32,7 @@ json-event-parser = "0.2.0-alpha.2"
md-5 = "0.10"
oxilangtag = "0.1"
oxiri = "0.2.3-alpha.1"
oxrdf = { version = "0.2.0-alpha.1", path = "oxrdf", features = ["rdf-star", "oxsdatatypes"] }
oxrdf = { version = "0.2.0-alpha.2-dev", path = "oxrdf", features = ["rdf-star", "oxsdatatypes"] }
oxrdfio = { version = "0.1.0-alpha.2-dev", path = "oxrdfio", features = ["rdf-star"] }
oxsdatatypes = { version = "0.2.0-alpha.1", path = "oxsdatatypes" }
rand = "0.8"
@ -40,9 +40,9 @@ regex = "1.7"
sha1 = "0.10"
sha2 = "0.10"
siphasher = ">=0.3, <2.0"
sparesults = { version = "0.2.0-alpha.1", path = "sparesults", features = ["rdf-star"] }
spargebra = { version = "0.3.0-alpha.1", path = "spargebra", features = ["rdf-star", "sep-0002", "sep-0006"] }
sparopt = { version = "0.1.0-alpha.1", path = "sparopt", features = ["rdf-star", "sep-0002", "sep-0006"] }
sparesults = { version = "0.2.0-alpha.2-dev", path = "sparesults", features = ["rdf-star"] }
spargebra = { version = "0.3.0-alpha.2-dev", path = "spargebra", features = ["rdf-star", "sep-0002", "sep-0006"] }
sparopt = { version = "0.1.0-alpha.2-dev", path = "sparopt", features = ["rdf-star", "sep-0002", "sep-0006"] }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
libc = "0.2.147"

@ -1,6 +1,6 @@
[package]
name = "oxrdf"
version = "0.2.0-alpha.1"
version = "0.2.0-alpha.2-dev"
authors = ["Tpt <thomas@pellissier-tanon.fr>"]
license = "MIT OR Apache-2.0"
readme = "README.md"

@ -19,8 +19,8 @@ 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", path = "../oxrdf" }
oxrdfxml = { version = "0.1.0-alpha.1", path = "../oxrdfxml" }
oxrdf = { version = "0.2.0-alpha.2-dev", path = "../oxrdf" }
oxrdfxml = { version = "0.1.0-alpha.2-dev", path = "../oxrdfxml" }
oxttl = { version = "0.1.0-alpha.2-dev", path = "../oxttl" }
tokio = { version = "1.29", optional = true, features = ["io-util"] }

@ -598,12 +598,14 @@ impl<R: AsyncRead + Unpin> FromTokioAsyncReadQuadReader<R> {
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 */
FromTokioAsyncReadQuadReaderKind::N3(p) => PrefixesIterKind::N3(p.prefixes()),
FromTokioAsyncReadQuadReaderKind::TriG(p) => PrefixesIterKind::TriG(p.prefixes()),
FromTokioAsyncReadQuadReaderKind::Turtle(p) => {
PrefixesIterKind::Turtle(p.prefixes())
}
FromTokioAsyncReadQuadReaderKind::NQuads(_)
| FromTokioAsyncReadQuadReaderKind::NTriples(_)
| FromTokioAsyncReadQuadReaderKind::RdfXml(_) => PrefixesIterKind::None, /* TODO: implement for RDF/XML */
},
}
}
@ -633,12 +635,12 @@ impl<R: AsyncRead + Unpin> FromTokioAsyncReadQuadReader<R> {
/// ```
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
FromTokioAsyncReadQuadReaderKind::N3(p) => p.base_iri(),
FromTokioAsyncReadQuadReaderKind::TriG(p) => p.base_iri(),
FromTokioAsyncReadQuadReaderKind::Turtle(p) => p.base_iri(),
FromTokioAsyncReadQuadReaderKind::NQuads(_)
| FromTokioAsyncReadQuadReaderKind::NTriples(_)
| FromTokioAsyncReadQuadReaderKind::RdfXml(_) => None, // TODO: implement for RDF/XML
}
}
}

@ -1,7 +1,7 @@
//! Utilities to write RDF graphs and datasets.
use crate::format::RdfFormat;
use oxrdf::{GraphNameRef, QuadRef, TripleRef};
use oxrdf::{GraphNameRef, IriParseError, QuadRef, TripleRef};
#[cfg(feature = "async-tokio")]
use oxrdfxml::ToTokioAsyncWriteRdfXmlWriter;
use oxrdfxml::{RdfXmlSerializer, ToWriteRdfXmlWriter};
@ -35,29 +35,44 @@ use tokio::io::AsyncWrite;
/// 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);
/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_write(Vec::new());
/// 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(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes());
/// assert_eq!(writer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[must_use]
pub struct RdfSerializer {
format: RdfFormat,
inner: RdfSerializerKind,
}
enum RdfSerializerKind {
NQuads(NQuadsSerializer),
NTriples(NTriplesSerializer),
RdfXml(RdfXmlSerializer),
TriG(TriGSerializer),
Turtle(TurtleSerializer),
}
impl RdfSerializer {
/// Builds a serializer for the given format
#[inline]
pub fn from_format(format: RdfFormat) -> Self {
Self { format }
Self {
inner: match format {
RdfFormat::NQuads => RdfSerializerKind::NQuads(NQuadsSerializer::new()),
RdfFormat::NTriples => RdfSerializerKind::NTriples(NTriplesSerializer::new()),
RdfFormat::RdfXml => RdfSerializerKind::RdfXml(RdfXmlSerializer::new()),
RdfFormat::TriG => RdfSerializerKind::TriG(TriGSerializer::new()),
RdfFormat::Turtle | RdfFormat::N3 => {
RdfSerializerKind::Turtle(TurtleSerializer::new())
}
},
}
}
/// The format the serializer serializes to.
@ -71,7 +86,56 @@ impl RdfSerializer {
/// );
/// ```
pub fn format(&self) -> RdfFormat {
self.format
match &self.inner {
RdfSerializerKind::NQuads(_) => RdfFormat::NQuads,
RdfSerializerKind::NTriples(_) => RdfFormat::NTriples,
RdfSerializerKind::RdfXml(_) => RdfFormat::RdfXml,
RdfSerializerKind::TriG(_) => RdfFormat::TriG,
RdfSerializerKind::Turtle(_) => RdfFormat::Turtle,
}
}
/// If the format supports it, sets a prefix.
///
/// ```
/// use oxrdf::vocab::rdf;
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxrdfio::{RdfFormat, RdfSerializer};
///
/// let mut writer = RdfSerializer::from_format(RdfFormat::Turtle)
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_write(Vec::new());
/// writer.write_triple(TripleRef {
/// subject: NamedNodeRef::new("http://example.com/s")?.into(),
/// predicate: rdf::TYPE.into(),
/// object: NamedNodeRef::new("http://schema.org/Person")?.into(),
/// })?;
/// assert_eq!(
/// writer.finish()?,
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com/s> a schema:Person .\n"
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[inline]
pub fn with_prefix(
mut self,
prefix_name: impl Into<String>,
prefix_iri: impl Into<String>,
) -> Result<Self, IriParseError> {
self.inner = match self.inner {
RdfSerializerKind::NQuads(s) => RdfSerializerKind::NQuads(s),
RdfSerializerKind::NTriples(s) => RdfSerializerKind::NTriples(s),
RdfSerializerKind::RdfXml(s) => {
RdfSerializerKind::RdfXml(s.with_prefix(prefix_name, prefix_iri)?)
}
RdfSerializerKind::TriG(s) => {
RdfSerializerKind::TriG(s.with_prefix(prefix_name, prefix_iri)?)
}
RdfSerializerKind::Turtle(s) => {
RdfSerializerKind::Turtle(s.with_prefix(prefix_name, prefix_iri)?)
}
};
Ok(self)
}
/// Writes to a [`Write`] implementation.
@ -88,36 +152,33 @@ impl RdfSerializer {
/// 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);
/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_write(Vec::new());
/// 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(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes());
/// assert_eq!(writer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn serialize_to_write<W: Write>(self, write: W) -> ToWriteQuadWriter<W> {
ToWriteQuadWriter {
formatter: match self.format {
RdfFormat::NQuads => {
ToWriteQuadWriterKind::NQuads(NQuadsSerializer::new().serialize_to_write(write))
formatter: match self.inner {
RdfSerializerKind::NQuads(s) => {
ToWriteQuadWriterKind::NQuads(s.serialize_to_write(write))
}
RdfFormat::NTriples => ToWriteQuadWriterKind::NTriples(
NTriplesSerializer::new().serialize_to_write(write),
),
RdfFormat::RdfXml => {
ToWriteQuadWriterKind::RdfXml(RdfXmlSerializer::new().serialize_to_write(write))
RdfSerializerKind::NTriples(s) => {
ToWriteQuadWriterKind::NTriples(s.serialize_to_write(write))
}
RdfFormat::TriG => {
ToWriteQuadWriterKind::TriG(TriGSerializer::new().serialize_to_write(write))
RdfSerializerKind::RdfXml(s) => {
ToWriteQuadWriterKind::RdfXml(s.serialize_to_write(write))
}
RdfFormat::Turtle | RdfFormat::N3 => {
ToWriteQuadWriterKind::Turtle(TurtleSerializer::new().serialize_to_write(write))
RdfSerializerKind::TriG(s) => {
ToWriteQuadWriterKind::TriG(s.serialize_to_write(write))
}
RdfSerializerKind::Turtle(s) => {
ToWriteQuadWriterKind::Turtle(s.serialize_to_write(write))
}
},
}
@ -139,17 +200,14 @@ impl RdfSerializer {
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let mut buffer = Vec::new();
/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_tokio_async_write(&mut buffer);
/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_tokio_async_write(Vec::new());
/// 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(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes());
/// assert_eq!(writer.finish().await?, "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
/// # Ok(())
/// # }
/// ```
@ -159,22 +217,22 @@ impl RdfSerializer {
write: W,
) -> ToTokioAsyncWriteQuadWriter<W> {
ToTokioAsyncWriteQuadWriter {
formatter: match self.format {
RdfFormat::NQuads => ToTokioAsyncWriteQuadWriterKind::NQuads(
NQuadsSerializer::new().serialize_to_tokio_async_write(write),
),
RdfFormat::NTriples => ToTokioAsyncWriteQuadWriterKind::NTriples(
NTriplesSerializer::new().serialize_to_tokio_async_write(write),
),
RdfFormat::RdfXml => ToTokioAsyncWriteQuadWriterKind::RdfXml(
RdfXmlSerializer::new().serialize_to_tokio_async_write(write),
),
RdfFormat::TriG => ToTokioAsyncWriteQuadWriterKind::TriG(
TriGSerializer::new().serialize_to_tokio_async_write(write),
),
RdfFormat::Turtle | RdfFormat::N3 => ToTokioAsyncWriteQuadWriterKind::Turtle(
TurtleSerializer::new().serialize_to_tokio_async_write(write),
formatter: match self.inner {
RdfSerializerKind::NQuads(s) => {
ToTokioAsyncWriteQuadWriterKind::NQuads(s.serialize_to_tokio_async_write(write))
}
RdfSerializerKind::NTriples(s) => ToTokioAsyncWriteQuadWriterKind::NTriples(
s.serialize_to_tokio_async_write(write),
),
RdfSerializerKind::RdfXml(s) => {
ToTokioAsyncWriteQuadWriterKind::RdfXml(s.serialize_to_tokio_async_write(write))
}
RdfSerializerKind::TriG(s) => {
ToTokioAsyncWriteQuadWriterKind::TriG(s.serialize_to_tokio_async_write(write))
}
RdfSerializerKind::Turtle(s) => {
ToTokioAsyncWriteQuadWriterKind::Turtle(s.serialize_to_tokio_async_write(write))
}
},
}
}
@ -202,17 +260,14 @@ impl From<RdfFormat> for RdfSerializer {
/// 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);
/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_write(Vec::new());
/// 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(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes());
/// assert_eq!(writer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[must_use]
@ -277,17 +332,14 @@ impl<W: Write> ToWriteQuadWriter<W> {
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let mut buffer = Vec::new();
/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_tokio_async_write(&mut buffer);
/// let mut writer = RdfSerializer::from_format(RdfFormat::NQuads).serialize_to_tokio_async_write(Vec::new());
/// 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(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes());
/// assert_eq!(writer.finish().await?, "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
/// # Ok(())
/// # }
/// ```

@ -1,6 +1,6 @@
[package]
name = "oxrdfxml"
version = "0.1.0-alpha.1"
version = "0.1.0-alpha.2-dev"
authors = ["Tpt <thomas@pellissier-tanon.fr>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
@ -18,7 +18,7 @@ default = []
async-tokio = ["dep:tokio", "quick-xml/async-tokio"]
[dependencies]
oxrdf = { version = "0.2.0-alpha.1", path = "../oxrdf" }
oxrdf = { version = "0.2.0-alpha.2-dev", path = "../oxrdf" }
oxilangtag = "0.1"
oxiri = "0.2.3-alpha.1"
quick-xml = ">=0.29, <0.32"

@ -1,8 +1,11 @@
use crate::utils::*;
use oxrdf::{Subject, SubjectRef, TermRef, TripleRef};
use quick_xml::events::*;
use oxiri::{Iri, IriParseError};
use oxrdf::vocab::rdf;
use oxrdf::{NamedNodeRef, Subject, SubjectRef, TermRef, TripleRef};
use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
use quick_xml::Writer;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::io;
use std::io::Write;
use std::sync::Arc;
@ -12,30 +15,52 @@ use tokio::io::AsyncWrite;
/// A [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) serializer.
///
/// ```
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxrdf::{LiteralRef, NamedNodeRef, TripleRef};
/// use oxrdfxml::RdfXmlSerializer;
///
/// let mut writer = RdfXmlSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = RdfXmlSerializer::new().with_prefix("schema", "http://schema.org/")?.serialize_to_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ))?;
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://schema.org/name")?,
/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
/// ))?;
/// assert_eq!(
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<rdf:Description rdf:about=\"http://example.com#me\">\n\t\t<rdf:type rdf:resource=\"http://schema.org/Person\"/>\n\t</rdf:Description>\n</rdf:RDF>",
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:schema=\"http://schema.org/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<schema:Person rdf:about=\"http://example.com#me\">\n\t\t<schema:name xml:lang=\"en\">Foo Bar</schema:name>\n\t</schema:Person>\n</rdf:RDF>",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[derive(Default)]
#[must_use]
pub struct RdfXmlSerializer;
pub struct RdfXmlSerializer {
prefixes: BTreeMap<String, String>,
}
impl RdfXmlSerializer {
/// Builds a new [`RdfXmlSerializer`].
#[inline]
pub fn new() -> Self {
Self
Self {
prefixes: BTreeMap::new(),
}
}
#[inline]
pub fn with_prefix(
mut self,
prefix_name: impl Into<String>,
prefix_iri: impl Into<String>,
) -> Result<Self, IriParseError> {
self.prefixes.insert(
Iri::parse(prefix_iri.into())?.into_inner(),
prefix_name.into(),
);
Ok(self)
}
/// Writes a RDF/XML file to a [`Write`] implementation.
@ -43,17 +68,22 @@ impl RdfXmlSerializer {
/// This writer does unbuffered writes.
///
/// ```
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxrdf::{LiteralRef, NamedNodeRef, TripleRef};
/// use oxrdfxml::RdfXmlSerializer;
///
/// let mut writer = RdfXmlSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = RdfXmlSerializer::new().with_prefix("schema", "http://schema.org/")?.serialize_to_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ))?;
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://schema.org/name")?,
/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
/// ))?;
/// assert_eq!(
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<rdf:Description rdf:about=\"http://example.com#me\">\n\t\t<rdf:type rdf:resource=\"http://schema.org/Person\"/>\n\t</rdf:Description>\n</rdf:RDF>",
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:schema=\"http://schema.org/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<schema:Person rdf:about=\"http://example.com#me\">\n\t\t<schema:name xml:lang=\"en\">Foo Bar</schema:name>\n\t</schema:Person>\n</rdf:RDF>",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
@ -62,9 +92,7 @@ impl RdfXmlSerializer {
pub fn serialize_to_write<W: Write>(self, write: W) -> ToWriteRdfXmlWriter<W> {
ToWriteRdfXmlWriter {
writer: Writer::new_with_indent(write, b'\t', 1),
inner: InnerRdfXmlWriter {
current_subject: None,
},
inner: self.inner_writer(),
}
}
@ -73,19 +101,24 @@ impl RdfXmlSerializer {
/// This writer does unbuffered writes.
///
/// ```
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxrdf::{NamedNodeRef, TripleRef, LiteralRef};
/// use oxrdfxml::RdfXmlSerializer;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let mut writer = RdfXmlSerializer::new().serialize_to_tokio_async_write(Vec::new());
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut writer = RdfXmlSerializer::new().with_prefix("schema", "http://schema.org/")?.serialize_to_tokio_async_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// )).await?;
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://schema.org/name")?,
/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
/// )).await?;
/// assert_eq!(
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<rdf:Description rdf:about=\"http://example.com#me\">\n\t\t<rdf:type rdf:resource=\"http://schema.org/Person\"/>\n\t</rdf:Description>\n</rdf:RDF>",
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:schema=\"http://schema.org/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<schema:Person rdf:about=\"http://example.com#me\">\n\t\t<schema:name xml:lang=\"en\">Foo Bar</schema:name>\n\t</schema:Person>\n</rdf:RDF>",
/// writer.finish().await?.as_slice()
/// );
/// # Ok(())
@ -99,9 +132,19 @@ impl RdfXmlSerializer {
) -> ToTokioAsyncWriteRdfXmlWriter<W> {
ToTokioAsyncWriteRdfXmlWriter {
writer: Writer::new_with_indent(write, b'\t', 1),
inner: InnerRdfXmlWriter {
current_subject: None,
},
inner: self.inner_writer(),
}
}
fn inner_writer(mut self) -> InnerRdfXmlWriter {
self.prefixes.insert(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#".into(),
"rdf".into(),
);
InnerRdfXmlWriter {
current_subject: None,
current_resource_tag: None,
prefixes: self.prefixes,
}
}
}
@ -109,17 +152,22 @@ impl RdfXmlSerializer {
/// Writes a RDF/XML file to a [`Write`] implementation. Can be built using [`RdfXmlSerializer::serialize_to_write`].
///
/// ```
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxrdf::{LiteralRef, NamedNodeRef, TripleRef};
/// use oxrdfxml::RdfXmlSerializer;
///
/// let mut writer = RdfXmlSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = RdfXmlSerializer::new().with_prefix("schema", "http://schema.org/")?.serialize_to_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ))?;
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://schema.org/name")?,
/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
/// ))?;
/// assert_eq!(
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<rdf:Description rdf:about=\"http://example.com#me\">\n\t\t<rdf:type rdf:resource=\"http://schema.org/Person\"/>\n\t</rdf:Description>\n</rdf:RDF>",
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:schema=\"http://schema.org/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<schema:Person rdf:about=\"http://example.com#me\">\n\t\t<schema:name xml:lang=\"en\">Foo Bar</schema:name>\n\t</schema:Person>\n</rdf:RDF>",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
@ -158,19 +206,24 @@ impl<W: Write> ToWriteRdfXmlWriter<W> {
/// Writes a RDF/XML file to a [`AsyncWrite`] implementation. Can be built using [`RdfXmlSerializer::serialize_to_tokio_async_write`].
///
/// ```
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxrdf::{NamedNodeRef, TripleRef, LiteralRef};
/// use oxrdfxml::RdfXmlSerializer;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let mut writer = RdfXmlSerializer::new().serialize_to_tokio_async_write(Vec::new());
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut writer = RdfXmlSerializer::new().with_prefix("schema", "http://schema.org/")?.serialize_to_tokio_async_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// )).await?;
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://schema.org/name")?,
/// LiteralRef::new_language_tagged_literal_unchecked("Foo Bar", "en"),
/// )).await?;
/// assert_eq!(
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<rdf:Description rdf:about=\"http://example.com#me\">\n\t\t<type xmlns=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" rdf:resource=\"http://schema.org/Person\"/>\n\t</rdf:Description>\n</rdf:RDF>",
/// b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xmlns:schema=\"http://schema.org/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n\t<schema:Person rdf:about=\"http://example.com#me\">\n\t\t<schema:name xml:lang=\"en\">Foo Bar</schema:name>\n\t</schema:Person>\n</rdf:RDF>",
/// writer.finish().await?.as_slice()
/// );
/// # Ok(())
@ -214,6 +267,8 @@ impl<W: AsyncWrite + Unpin> ToTokioAsyncWriteRdfXmlWriter<W> {
pub struct InnerRdfXmlWriter {
current_subject: Option<Subject>,
current_resource_tag: Option<String>,
prefixes: BTreeMap<String, String>,
}
impl InnerRdfXmlWriter {
@ -224,17 +279,36 @@ impl InnerRdfXmlWriter {
output: &mut Vec<Event<'a>>,
) -> io::Result<()> {
if self.current_subject.is_none() {
Self::write_start(output);
self.write_start(output);
}
let triple = t.into();
// We open a new rdf:Description if useful
if self.current_subject.as_ref().map(Subject::as_ref) != Some(triple.subject) {
if self.current_subject.is_some() {
output.push(Event::End(BytesEnd::new("rdf:Description")));
output.push(Event::End(
self.current_resource_tag
.take()
.map_or_else(|| BytesEnd::new("rdf:Description"), BytesEnd::new),
));
}
self.current_subject = Some(triple.subject.into_owned());
let mut description_open = BytesStart::new("rdf:Description");
let (mut description_open, with_type_tag) = if triple.predicate == rdf::TYPE {
if let TermRef::NamedNode(t) = triple.object {
let (prop_qname, prop_xmlns) = self.uri_to_qname_and_xmlns(t);
let mut description_open = BytesStart::new(prop_qname.clone());
if let Some(prop_xmlns) = prop_xmlns {
description_open.push_attribute(prop_xmlns);
}
self.current_resource_tag = Some(prop_qname.into_owned());
(description_open, true)
} else {
(BytesStart::new("rdf:Description"), false)
}
} else {
(BytesStart::new("rdf:Description"), false)
};
match triple.subject {
SubjectRef::NamedNode(node) => {
description_open.push_attribute(("rdf:about", node.as_str()))
@ -250,20 +324,12 @@ impl InnerRdfXmlWriter {
}
}
output.push(Event::Start(description_open));
if with_type_tag {
return Ok(()); // No need for a value
}
}
self.current_subject = Some(triple.subject.into_owned());
let (prop_prefix, prop_value) = split_iri(triple.predicate.as_str());
let (prop_qname, prop_xmlns) =
if prop_prefix == "http://www.w3.org/1999/02/22-rdf-syntax-ns#" {
(Cow::Owned(format!("rdf:{prop_value}")), None)
} else if prop_prefix == "http://www.w3.org/2000/xmlns/" {
(Cow::Owned(format!("xmlns:{prop_value}")), None)
} else if prop_value.is_empty() {
(Cow::Borrowed("p:"), Some(("xmlns:p", prop_prefix)))
} else {
(Cow::Borrowed(prop_value), Some(("xmlns", prop_prefix)))
};
let (prop_qname, prop_xmlns) = self.uri_to_qname_and_xmlns(triple.predicate);
let mut property_open = BytesStart::new(prop_qname.clone());
if let Some(prop_xmlns) = prop_xmlns {
property_open.push_attribute(prop_xmlns);
@ -302,29 +368,58 @@ impl InnerRdfXmlWriter {
Ok(())
}
fn write_start(output: &mut Vec<Event<'_>>) {
fn write_start(&self, output: &mut Vec<Event<'_>>) {
output.push(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)));
let mut rdf_open = BytesStart::new("rdf:RDF");
rdf_open.push_attribute(("xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"));
for (prefix_value, prefix_name) in &self.prefixes {
rdf_open.push_attribute((
format!("xmlns:{prefix_name}").as_str(),
prefix_value.as_str(),
));
}
output.push(Event::Start(rdf_open))
}
fn finish(&self, output: &mut Vec<Event<'static>>) {
fn finish(&mut self, output: &mut Vec<Event<'static>>) {
if self.current_subject.is_some() {
output.push(Event::End(BytesEnd::new("rdf:Description")));
output.push(Event::End(
self.current_resource_tag
.take()
.map_or_else(|| BytesEnd::new("rdf:Description"), BytesEnd::new),
));
} else {
Self::write_start(output);
self.write_start(output);
}
output.push(Event::End(BytesEnd::new("rdf:RDF")));
}
fn uri_to_qname_and_xmlns<'a>(
&self,
uri: NamedNodeRef<'a>,
) -> (Cow<'a, str>, Option<(&'a str, &'a str)>) {
let (prop_prefix, prop_value) = split_iri(uri.as_str());
if let Some(prop_prefix) = self.prefixes.get(prop_prefix) {
(
if prop_prefix.is_empty() {
Cow::Borrowed(prop_value)
} else {
Cow::Owned(format!("{prop_prefix}:{prop_value}"))
},
None,
)
} else if prop_prefix == "http://www.w3.org/2000/xmlns/" {
(Cow::Owned(format!("xmlns:{prop_value}")), None)
} else if prop_value.is_empty() {
(Cow::Borrowed("p:"), Some(("xmlns:p", prop_prefix)))
} else {
(Cow::Borrowed(prop_value), Some(("xmlns", prop_prefix)))
}
}
}
fn map_err(error: quick_xml::Error) -> io::Error {
if let quick_xml::Error::Io(error) = error {
match Arc::try_unwrap(error) {
Ok(error) => error,
Err(error) => io::Error::new(error.kind(), error),
}
Arc::try_unwrap(error).unwrap_or_else(|error| io::Error::new(error.kind(), error))
} else {
io::Error::new(io::ErrorKind::Other, error)
}

@ -20,7 +20,7 @@ async-tokio = ["dep:tokio"]
[dependencies]
memchr = "2.5"
oxrdf = { version = "0.2.0-alpha.1", path = "../oxrdf" }
oxrdf = { version = "0.2.0-alpha.2-dev", path = "../oxrdf" }
oxiri = "0.2.3-alpha.1"
oxilangtag = "0.1"
tokio = { version = "1.29", optional = true, features = ["io-util"] }

@ -49,14 +49,14 @@ pub struct N3Lexer {
// TODO: simplify by not giving is_end and fail with an "unexpected eof" is none is returned when is_end=true?
impl TokenRecognizer for N3Lexer {
type Token<'a> = N3Token<'a>;
type Options = N3LexerOptions;
type Token<'a> = N3Token<'a>;
fn recognize_next_token<'a>(
&mut self,
data: &'a [u8],
is_ending: bool,
options: &Self::Options,
options: &N3LexerOptions,
) -> Option<(usize, Result<N3Token<'a>, TokenRecognizerError>)> {
match *data.first()? {
b'<' => match *data.get(1)? {
@ -914,12 +914,12 @@ impl N3Lexer {
}
// [158s] PN_CHARS_U ::= PN_CHARS_BASE | '_' | ':'
fn is_possible_pn_chars_u(c: char) -> bool {
pub(super) fn is_possible_pn_chars_u(c: char) -> bool {
Self::is_possible_pn_chars_base(c) || c == '_'
}
// [160s] PN_CHARS ::= PN_CHARS_U | '-' | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040]
fn is_possible_pn_chars(c: char) -> bool {
pub(crate) fn is_possible_pn_chars(c: char) -> bool {
Self::is_possible_pn_chars_u(c)
|| matches!(c,
'-' | '0'..='9' | '\u{00B7}' | '\u{0300}'..='\u{036F}' | '\u{203F}'..='\u{2040}')

@ -39,9 +39,9 @@ enum NQuadsState {
}
impl RuleRecognizer for NQuadsRecognizer {
type TokenRecognizer = N3Lexer;
type Output = Quad;
type Context = NQuadsRecognizerContext;
type Output = Quad;
type TokenRecognizer = N3Lexer;
fn error_recovery_state(mut self) -> Self {
self.stack.clear();

@ -723,9 +723,9 @@ struct N3RecognizerContext {
}
impl RuleRecognizer for N3Recognizer {
type TokenRecognizer = N3Lexer;
type Output = N3Quad;
type Context = N3RecognizerContext;
type Output = N3Quad;
type TokenRecognizer = N3Lexer;
fn error_recovery_state(mut self) -> Self {
self.stack.clear();

@ -441,7 +441,7 @@ impl NQuadsSerializer {
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[allow(clippy::unused_self)]
pub fn serialize(&self) -> LowLevelNQuadsWriter {
pub fn serialize(self) -> LowLevelNQuadsWriter {
LowLevelNQuadsWriter
}
}

@ -437,7 +437,7 @@ impl NTriplesSerializer {
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[allow(clippy::unused_self)]
pub fn serialize(&self) -> LowLevelNTriplesWriter {
pub fn serialize(self) -> LowLevelNTriplesWriter {
LowLevelNTriplesWriter
}
}

@ -35,9 +35,9 @@ impl TriGRecognizerContext {
}
impl RuleRecognizer for TriGRecognizer {
type TokenRecognizer = N3Lexer;
type Output = Quad;
type Context = TriGRecognizerContext;
type Output = Quad;
type TokenRecognizer = N3Lexer;
fn error_recovery_state(mut self) -> Self {
self.stack.clear();

@ -1,15 +1,18 @@
//! A [TriG](https://www.w3.org/TR/trig/) streaming parser implemented by [`TriGParser`]
//! and a serializer implemented by [`TriGSerializer`].
use crate::lexer::N3Lexer;
use crate::terse::TriGRecognizer;
#[cfg(feature = "async-tokio")]
use crate::toolkit::FromTokioAsyncReadIterator;
use crate::toolkit::{FromReadIterator, ParseError, Parser, SyntaxError};
use oxiri::{Iri, IriParseError};
use oxrdf::vocab::xsd;
use oxrdf::{GraphName, NamedNode, Quad, QuadRef, Subject, TermRef};
use oxrdf::vocab::{rdf, xsd};
use oxrdf::{
GraphName, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, Quad, QuadRef, Subject, TermRef,
};
use std::collections::hash_map::Iter;
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::io::{self, Read, Write};
#[cfg(feature = "async-tokio")]
@ -582,7 +585,9 @@ impl<'a> Iterator for TriGPrefixesIter<'a> {
/// use oxrdf::{NamedNodeRef, QuadRef};
/// use oxttl::TriGSerializer;
///
/// let mut writer = TriGSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = TriGSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_write(Vec::new());
/// writer.write_quad(QuadRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
@ -590,20 +595,37 @@ impl<'a> Iterator for TriGPrefixesIter<'a> {
/// NamedNodeRef::new("http://example.com")?,
/// ))?;
/// assert_eq!(
/// b"<http://example.com> {\n\t<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n}\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[derive(Default)]
#[must_use]
pub struct TriGSerializer;
pub struct TriGSerializer {
prefixes: BTreeMap<String, String>,
}
impl TriGSerializer {
/// Builds a new [`TriGSerializer`].
#[inline]
pub fn new() -> Self {
Self
Self {
prefixes: BTreeMap::new(),
}
}
#[inline]
pub fn with_prefix(
mut self,
prefix_name: impl Into<String>,
prefix_iri: impl Into<String>,
) -> Result<Self, IriParseError> {
self.prefixes.insert(
Iri::parse(prefix_iri.into())?.into_inner(),
prefix_name.into(),
);
Ok(self)
}
/// Writes a TriG file to a [`Write`] implementation.
@ -612,7 +634,9 @@ impl TriGSerializer {
/// use oxrdf::{NamedNodeRef, QuadRef};
/// use oxttl::TriGSerializer;
///
/// let mut writer = TriGSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = TriGSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_write(Vec::new());
/// writer.write_quad(QuadRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
@ -620,7 +644,7 @@ impl TriGSerializer {
/// NamedNodeRef::new("http://example.com")?,
/// ))?;
/// assert_eq!(
/// b"<http://example.com> {\n\t<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n}\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
@ -639,16 +663,20 @@ impl TriGSerializer {
/// use oxttl::TriGSerializer;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let mut writer = TriGSerializer::new().serialize_to_tokio_async_write(Vec::new());
/// writer.write_quad(QuadRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// NamedNodeRef::new_unchecked("http://example.com"),
/// )).await?;
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut writer = TriGSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_tokio_async_write(Vec::new());
/// writer
/// .write_quad(QuadRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// NamedNodeRef::new_unchecked("http://example.com"),
/// ))
/// .await?;
/// assert_eq!(
/// b"<http://example.com> {\n\t<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n}\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
/// writer.finish().await?.as_slice()
/// );
/// # Ok(())
@ -673,23 +701,29 @@ impl TriGSerializer {
/// use oxttl::TriGSerializer;
///
/// let mut buf = Vec::new();
/// let mut writer = TriGSerializer::new().serialize();
/// writer.write_quad(QuadRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// NamedNodeRef::new("http://example.com")?,
/// ), &mut buf)?;
/// let mut writer = TriGSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize();
/// writer.write_quad(
/// QuadRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// NamedNodeRef::new("http://example.com")?,
/// ),
/// &mut buf,
/// )?;
/// writer.finish(&mut buf)?;
/// assert_eq!(
/// b"<http://example.com> {\n\t<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n}\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
/// buf.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
#[allow(clippy::unused_self)]
pub fn serialize(&self) -> LowLevelTriGWriter {
pub fn serialize(self) -> LowLevelTriGWriter {
LowLevelTriGWriter {
prefixes: self.prefixes,
prelude_written: false,
current_graph_name: GraphName::DefaultGraph,
current_subject_predicate: None,
}
@ -702,7 +736,9 @@ impl TriGSerializer {
/// use oxrdf::{NamedNodeRef, QuadRef};
/// use oxttl::TriGSerializer;
///
/// let mut writer = TriGSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = TriGSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_write(Vec::new());
/// writer.write_quad(QuadRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
@ -710,7 +746,7 @@ impl TriGSerializer {
/// NamedNodeRef::new("http://example.com")?,
/// ))?;
/// assert_eq!(
/// b"<http://example.com> {\n\t<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n}\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
@ -741,16 +777,20 @@ impl<W: Write> ToWriteTriGWriter<W> {
/// use oxttl::TriGSerializer;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let mut writer = TriGSerializer::new().serialize_to_tokio_async_write(Vec::new());
/// writer.write_quad(QuadRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// NamedNodeRef::new_unchecked("http://example.com"),
/// )).await?;
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut writer = TriGSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_tokio_async_write(Vec::new());
/// writer
/// .write_quad(QuadRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// NamedNodeRef::new_unchecked("http://example.com"),
/// ))
/// .await?;
/// assert_eq!(
/// b"<http://example.com> {\n\t<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n}\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
/// writer.finish().await?.as_slice()
/// );
/// # Ok(())
@ -790,21 +830,28 @@ impl<W: AsyncWrite + Unpin> ToTokioAsyncWriteTriGWriter<W> {
/// use oxttl::TriGSerializer;
///
/// let mut buf = Vec::new();
/// let mut writer = TriGSerializer::new().serialize();
/// writer.write_quad(QuadRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// NamedNodeRef::new("http://example.com")?,
/// ), &mut buf)?;
/// let mut writer = TriGSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize();
/// writer.write_quad(
/// QuadRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// NamedNodeRef::new("http://example.com")?,
/// ),
/// &mut buf,
/// )?;
/// writer.finish(&mut buf)?;
/// assert_eq!(
/// b"<http://example.com> {\n\t<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n}\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com> {\n\t<http://example.com#me> a schema:Person .\n}\n",
/// buf.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
pub struct LowLevelTriGWriter {
prefixes: BTreeMap<String, String>,
prelude_written: bool,
current_graph_name: GraphName,
current_subject_predicate: Option<(Subject, NamedNode)>,
}
@ -816,6 +863,12 @@ impl LowLevelTriGWriter {
q: impl Into<QuadRef<'a>>,
mut write: impl Write,
) -> io::Result<()> {
if !self.prelude_written {
self.prelude_written = true;
for (prefix_iri, prefix_name) in &self.prefixes {
writeln!(write, "@prefix {prefix_name}: <{prefix_iri}> .")?;
}
}
let q = q.into();
if q.graph_name == self.current_graph_name.as_ref() {
if let Some((current_subject, current_predicate)) =
@ -824,7 +877,7 @@ impl LowLevelTriGWriter {
if q.subject == current_subject.as_ref() {
if q.predicate == current_predicate {
self.current_subject_predicate = Some((current_subject, current_predicate));
write!(write, " , {}", TurtleTerm(q.object))
write!(write, " , {}", self.term(q.object))
} else {
self.current_subject_predicate =
Some((current_subject, q.predicate.into_owned()));
@ -832,7 +885,12 @@ impl LowLevelTriGWriter {
if !self.current_graph_name.is_default_graph() {
write!(write, "\t")?;
}
write!(write, "\t{} {}", q.predicate, TurtleTerm(q.object))
write!(
write,
"\t{} {}",
self.predicate(q.predicate),
self.term(q.object)
)
}
} else {
self.current_subject_predicate =
@ -844,9 +902,9 @@ impl LowLevelTriGWriter {
write!(
write,
"{} {} {}",
TurtleTerm(q.subject.into()),
q.predicate,
TurtleTerm(q.object)
self.term(q.subject),
self.predicate(q.predicate),
self.term(q.object)
)
}
} else {
@ -858,9 +916,9 @@ impl LowLevelTriGWriter {
write!(
write,
"{} {} {}",
TurtleTerm(q.subject.into()),
q.predicate,
TurtleTerm(q.object)
self.term(q.subject),
self.predicate(q.predicate),
self.term(q.object)
)
}
} else {
@ -873,20 +931,42 @@ impl LowLevelTriGWriter {
self.current_graph_name = q.graph_name.into_owned();
self.current_subject_predicate =
Some((q.subject.into_owned(), q.predicate.into_owned()));
if !self.current_graph_name.is_default_graph() {
writeln!(write, "{} {{", q.graph_name)?;
write!(write, "\t")?;
match self.current_graph_name.as_ref() {
GraphNameRef::NamedNode(g) => {
writeln!(write, "{} {{", self.term(g))?;
write!(write, "\t")?;
}
GraphNameRef::BlankNode(g) => {
writeln!(write, "{} {{", self.term(g))?;
write!(write, "\t")?;
}
GraphNameRef::DefaultGraph => (),
}
write!(
write,
"{} {} {}",
TurtleTerm(q.subject.into()),
q.predicate,
TurtleTerm(q.object)
self.term(q.subject),
self.predicate(q.predicate),
self.term(q.object)
)
}
}
fn predicate<'a>(&'a self, named_node: impl Into<NamedNodeRef<'a>>) -> TurtlePredicate<'a> {
TurtlePredicate {
named_node: named_node.into(),
prefixes: &self.prefixes,
}
}
fn term<'a>(&'a self, term: impl Into<TermRef<'a>>) -> TurtleTerm<'a> {
TurtleTerm {
term: term.into(),
prefixes: &self.prefixes,
}
}
/// Finishes to write the file.
pub fn finish(&mut self, mut write: impl Write) -> io::Result<()> {
if self.current_subject_predicate.is_some() {
@ -899,12 +979,43 @@ impl LowLevelTriGWriter {
}
}
struct TurtleTerm<'a>(TermRef<'a>);
struct TurtlePredicate<'a> {
named_node: NamedNodeRef<'a>,
prefixes: &'a BTreeMap<String, String>,
}
impl<'a> fmt::Display for TurtlePredicate<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.named_node == rdf::TYPE {
write!(f, "a")
} else {
TurtleTerm {
term: self.named_node.into(),
prefixes: self.prefixes,
}
.fmt(f)
}
}
}
struct TurtleTerm<'a> {
term: TermRef<'a>,
prefixes: &'a BTreeMap<String, String>,
}
impl<'a> fmt::Display for TurtleTerm<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
TermRef::NamedNode(v) => write!(f, "{v}"),
match self.term {
TermRef::NamedNode(v) => {
for (prefix_iri, prefix_name) in self.prefixes {
if let Some(local_name) = v.as_str().strip_prefix(prefix_iri) {
if let Some(escaped_local_name) = escape_local_name(local_name) {
return write!(f, "{prefix_name}:{escaped_local_name}");
}
}
}
write!(f, "{v}")
}
TermRef::BlankNode(v) => write!(f, "{v}"),
TermRef::Literal(v) => {
let value = v.value();
@ -917,8 +1028,18 @@ impl<'a> fmt::Display for TurtleTerm<'a> {
};
if inline {
write!(f, "{value}")
} else {
} else if v.is_plain() {
write!(f, "{v}")
} else {
write!(
f,
"{}^^{}",
LiteralRef::new_simple_literal(v.value()),
TurtleTerm {
term: v.datatype().into(),
prefixes: self.prefixes
}
)
}
}
#[cfg(feature = "rdf-star")]
@ -926,9 +1047,18 @@ impl<'a> fmt::Display for TurtleTerm<'a> {
write!(
f,
"<< {} {} {} >>",
TurtleTerm(t.subject.as_ref().into()),
t.predicate,
TurtleTerm(t.object.as_ref())
TurtleTerm {
term: t.subject.as_ref().into(),
prefixes: self.prefixes
},
TurtleTerm {
term: t.predicate.as_ref().into(),
prefixes: self.prefixes
},
TurtleTerm {
term: t.object.as_ref(),
prefixes: self.prefixes
}
)
}
}
@ -1004,6 +1134,61 @@ fn is_turtle_double(value: &str) -> bool {
(with_before || with_after) && !value.is_empty() && value.iter().all(u8::is_ascii_digit)
}
fn escape_local_name(value: &str) -> Option<String> {
// TODO: PLX
// [168s] PN_LOCAL ::= (PN_CHARS_U | ':' | [0-9] | PLX) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX))?
let mut output = String::with_capacity(value.len());
let mut chars = value.chars();
let first = chars.next()?;
if N3Lexer::is_possible_pn_chars_u(first) || first == ':' || first.is_ascii_digit() {
output.push(first);
} else if can_be_escaped_in_local_name(first) {
output.push('\\');
output.push(first);
} else {
return None;
}
while let Some(c) = chars.next() {
if N3Lexer::is_possible_pn_chars(c) || c == ':' || (c == '.' && !chars.as_str().is_empty())
{
output.push(c);
} else if can_be_escaped_in_local_name(c) {
output.push('\\');
output.push(c);
} else {
return None;
}
}
Some(output)
}
fn can_be_escaped_in_local_name(c: char) -> bool {
matches!(
c,
'_' | '~'
| '.'
| '-'
| '!'
| '$'
| '&'
| '\''
| '('
| ')'
| '*'
| '+'
| ','
| ';'
| '='
| '/'
| '?'
| '#'
| '@'
| '%'
)
}
#[cfg(test)]
mod tests {
#![allow(clippy::panic_in_result_fn)]
@ -1014,11 +1199,20 @@ mod tests {
#[test]
fn test_write() -> io::Result<()> {
let mut writer = TriGSerializer::new().serialize_to_write(Vec::new());
let mut writer = TriGSerializer::new()
.with_prefix("ex", "http://example.com/")
.unwrap()
.serialize_to_write(Vec::new());
writer.write_quad(QuadRef::new(
NamedNodeRef::new_unchecked("http://example.com/s"),
NamedNodeRef::new_unchecked("http://example.com/p"),
NamedNodeRef::new_unchecked("http://example.com/o"),
NamedNodeRef::new_unchecked("http://example.com/o."),
NamedNodeRef::new_unchecked("http://example.com/g"),
))?;
writer.write_quad(QuadRef::new(
NamedNodeRef::new_unchecked("http://example.com/s"),
NamedNodeRef::new_unchecked("http://example.com/p"),
NamedNodeRef::new_unchecked("http://example.com/o{o}"),
NamedNodeRef::new_unchecked("http://example.com/g"),
))?;
writer.write_quad(QuadRef::new(
@ -1047,11 +1241,14 @@ mod tests {
))?;
writer.write_quad(QuadRef::new(
BlankNodeRef::new_unchecked("b"),
NamedNodeRef::new_unchecked("http://example.com/p2"),
NamedNodeRef::new_unchecked("http://example.org/p2"),
LiteralRef::new_typed_literal("false", xsd::BOOLEAN),
NamedNodeRef::new_unchecked("http://example.com/g2"),
))?;
assert_eq!(String::from_utf8(writer.finish()?).unwrap(), "<http://example.com/g> {\n\t<http://example.com/s> <http://example.com/p> <http://example.com/o> , \"foo\" ;\n\t\t<http://example.com/p2> \"foo\"@en .\n\t_:b <http://example.com/p2> _:b2 .\n}\n_:b <http://example.com/p2> true .\n<http://example.com/g2> {\n\t_:b <http://example.com/p2> false .\n}\n");
assert_eq!(
String::from_utf8(writer.finish()?).unwrap(),
"@prefix ex: <http://example.com/> .\nex:g {\n\tex:s ex:p ex:o\\. , <http://example.com/o{o}> , \"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"
);
Ok(())
}
}

@ -583,14 +583,16 @@ impl<'a> Iterator for TurtlePrefixesIter<'a> {
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxttl::TurtleSerializer;
///
/// let mut writer = TurtleSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = TurtleSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ))?;
/// assert_eq!(
/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
@ -608,20 +610,32 @@ impl TurtleSerializer {
Self::default()
}
#[inline]
pub fn with_prefix(
mut self,
prefix_name: impl Into<String>,
prefix_iri: impl Into<String>,
) -> Result<Self, IriParseError> {
self.inner = self.inner.with_prefix(prefix_name, prefix_iri)?;
Ok(self)
}
/// Writes a Turtle file to a [`Write`] implementation.
///
/// ```
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxttl::TurtleSerializer;
///
/// let mut writer = TurtleSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = TurtleSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ))?;
/// assert_eq!(
/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
@ -639,15 +653,19 @@ impl TurtleSerializer {
/// use oxttl::TurtleSerializer;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let mut writer = TurtleSerializer::new().serialize_to_tokio_async_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// )).await?;
/// # async fn main() -> Result<(),Box<dyn std::error::Error>> {
/// let mut writer = TurtleSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_tokio_async_write(Vec::new());
/// writer
/// .write_triple(TripleRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// ))
/// .await?;
/// assert_eq!(
/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
/// writer.finish().await?.as_slice()
/// );
/// # Ok(())
@ -670,20 +688,25 @@ impl TurtleSerializer {
/// use oxttl::TurtleSerializer;
///
/// let mut buf = Vec::new();
/// let mut writer = TurtleSerializer::new().serialize();
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ), &mut buf)?;
/// let mut writer = TurtleSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize();
/// writer.write_triple(
/// TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ),
/// &mut buf,
/// )?;
/// writer.finish(&mut buf)?;
/// assert_eq!(
/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
/// buf.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn serialize(&self) -> LowLevelTurtleWriter {
pub fn serialize(self) -> LowLevelTurtleWriter {
LowLevelTurtleWriter {
inner: self.inner.serialize(),
}
@ -696,14 +719,16 @@ impl TurtleSerializer {
/// use oxrdf::{NamedNodeRef, TripleRef};
/// use oxttl::TurtleSerializer;
///
/// let mut writer = TurtleSerializer::new().serialize_to_write(Vec::new());
/// let mut writer = TurtleSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ))?;
/// assert_eq!(
/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
/// writer.finish()?.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
@ -733,15 +758,19 @@ impl<W: Write> ToWriteTurtleWriter<W> {
/// use oxttl::TurtleSerializer;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let mut writer = TurtleSerializer::new().serialize_to_tokio_async_write(Vec::new());
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person")
/// )).await?;
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut writer = TurtleSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize_to_tokio_async_write(Vec::new());
/// writer
/// .write_triple(TripleRef::new(
/// NamedNodeRef::new_unchecked("http://example.com#me"),
/// NamedNodeRef::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
/// NamedNodeRef::new_unchecked("http://schema.org/Person"),
/// ))
/// .await?;
/// assert_eq!(
/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
/// writer.finish().await?.as_slice()
/// );
/// # Ok(())
@ -775,15 +804,20 @@ impl<W: AsyncWrite + Unpin> ToTokioAsyncWriteTurtleWriter<W> {
/// use oxttl::TurtleSerializer;
///
/// let mut buf = Vec::new();
/// let mut writer = TurtleSerializer::new().serialize();
/// writer.write_triple(TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ), &mut buf)?;
/// let mut writer = TurtleSerializer::new()
/// .with_prefix("schema", "http://schema.org/")?
/// .serialize();
/// writer.write_triple(
/// TripleRef::new(
/// NamedNodeRef::new("http://example.com#me")?,
/// NamedNodeRef::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
/// NamedNodeRef::new("http://schema.org/Person")?,
/// ),
/// &mut buf,
/// )?;
/// writer.finish(&mut buf)?;
/// assert_eq!(
/// b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
/// b"@prefix schema: <http://schema.org/> .\n<http://example.com#me> a schema:Person .\n",
/// buf.as_slice()
/// );
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())

@ -1,6 +1,6 @@
[package]
name = "sparesults"
version = "0.2.0-alpha.1"
version = "0.2.0-alpha.2-dev"
authors = ["Tpt <thomas@pellissier-tanon.fr>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
@ -21,7 +21,7 @@ async-tokio = ["dep:tokio", "quick-xml/async-tokio", "json-event-parser/async-to
[dependencies]
json-event-parser = "0.2.0-alpha.2"
memchr = "2.5"
oxrdf = { version = "0.2.0-alpha.1", path = "../oxrdf" }
oxrdf = { version = "0.2.0-alpha.2-dev", path = "../oxrdf" }
quick-xml = ">=0.29, <0.32"
tokio = { version = "1.29", optional = true, features = ["io-util"] }

@ -665,10 +665,9 @@ fn decode<'a, T>(
fn map_xml_error(error: quick_xml::Error) -> io::Error {
match error {
quick_xml::Error::Io(error) => match Arc::try_unwrap(error) {
Ok(error) => error,
Err(error) => io::Error::new(error.kind(), error),
},
quick_xml::Error::Io(error) => {
Arc::try_unwrap(error).unwrap_or_else(|error| io::Error::new(error.kind(), error))
}
quick_xml::Error::UnexpectedEof(_) => io::Error::new(io::ErrorKind::UnexpectedEof, error),
_ => io::Error::new(io::ErrorKind::InvalidData, error),
}

@ -1,6 +1,6 @@
[package]
name = "spargebra"
version = "0.3.0-alpha.1"
version = "0.3.0-alpha.2-dev"
authors = ["Tpt <thomas@pellissier-tanon.fr>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
@ -24,7 +24,7 @@ peg = "0.8"
rand = "0.8"
oxiri = "0.2.3-alpha.1"
oxilangtag = "0.1"
oxrdf = { version = "0.2.0-alpha.1", path = "../oxrdf" }
oxrdf = { version = "0.2.0-alpha.2-dev", path = "../oxrdf" }
[lints]
workspace = true

@ -1,6 +1,6 @@
[package]
name = "sparopt"
version = "0.1.0-alpha.1"
version = "0.1.0-alpha.2-dev"
authors = ["Tpt <thomas@pellissier-tanon.fr>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
@ -20,9 +20,9 @@ sep-0002 = ["spargebra/sep-0002"]
sep-0006 = ["spargebra/sep-0006"]
[dependencies]
oxrdf = { version = "0.2.0-alpha.1", path = "../oxrdf" }
oxrdf = { version = "0.2.0-alpha.2-dev", path = "../oxrdf" }
rand = "0.8"
spargebra = { version = "0.3.0-alpha.1", path = "../spargebra" }
spargebra = { version = "0.3.0-alpha.2-dev", path = "../spargebra" }
[lints]
workspace = true

Loading…
Cancel
Save