Skip to content

Commit

Permalink
sophia_resource now supports more formats
Browse files Browse the repository at this point in the history
  • Loading branch information
pchampin committed Oct 24, 2023
1 parent 1ca0a5d commit fe9c51e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 7 deletions.
9 changes: 9 additions & 0 deletions resource/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ keywords.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
# This feature enables the JSON-LD parser and serializer
jsonld = ["sophia_jsonld"]
# This feature enables the RDF/XML parser and serializer
xml = ["sophia_xml"]
# This feature enables the HTTP client in dependencies
http_client = []


[dependencies]
sophia_api.workspace = true
sophia_iri.workspace = true
sophia_jsonld = { workspace = true, optional = true }
sophia_turtle.workspace = true
sophia_xml = { workspace = true, optional = true }
thiserror.workspace = true

[dev-dependencies]
18 changes: 18 additions & 0 deletions resource/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,20 @@ mod test {
pub const F2R1: Iri<&str> = Iri::new_unchecked_const("http://example.org/file2.ttl#res1");
pub const F2R2: Iri<&str> = Iri::new_unchecked_const("http://example.org/file2.ttl#res2");
pub const FAIL: Iri<&str> = Iri::new_unchecked_const("http://example.org/not_there");
pub const F3: Iri<&str> = Iri::new_unchecked_const("http://example.org/file3.nt");
#[cfg(feature = "jsonld")]
pub const F4: Iri<&str> = Iri::new_unchecked_const("http://example.org/file4.jsonld");
#[cfg(feature = "xml")]
pub const F5: Iri<&str> = Iri::new_unchecked_const("http://example.org/file5.rdf");
pub const SUBDIR: Iri<&str> = Iri::new_unchecked_const("http://example.org/subdir");
// test with no extension (conneg emulation)
pub const F1X: Iri<&str> = Iri::new_unchecked_const("http://example.org/file1");
pub const F1XR1: Iri<&str> = Iri::new_unchecked_const("http://example.org/file1#res1");
pub const F3X: Iri<&str> = Iri::new_unchecked_const("http://example.org/file3");
#[cfg(feature = "jsonld")]
pub const F4X: Iri<&str> = Iri::new_unchecked_const("http://example.org/file4");
#[cfg(feature = "xml")]
pub const F5X: Iri<&str> = Iri::new_unchecked_const("http://example.org/file5");

pub const EX_ID: Iri<&str> = Iri::new_unchecked_const("http://example.org/ns#id");
pub const EX_LIST: Iri<&str> = Iri::new_unchecked_const("http://example.org/ns#list");
Expand All @@ -52,6 +62,14 @@ mod test {
pub const F1_LEN: usize = 20;
/// Number of triples in F2
pub const F2_LEN: usize = 2;
/// Number of triples in F3
pub const F3_LEN: usize = 20;
/// Number of triples in F4
#[cfg(feature = "jsonld")]
pub const F4_LEN: usize = 20;
/// Number of triples in F5
#[cfg(feature = "xml")]
pub const F5_LEN: usize = 20;

pub type MyGraph = Vec<[SimpleTerm<'static>; 3]>;
pub type TestResult = Result<(), Box<dyn std::error::Error>>;
Expand Down
15 changes: 14 additions & 1 deletion resource/src/loader/_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ impl LocalLoader {
fn ctype(&self, iri: &str) -> String {
if iri.ends_with(".ttl") {
"text/turtle".into()
} else if iri.ends_with(".nt") {
"application/n-triples".into()
} else if cfg!(feature = "jsonld") && iri.ends_with(".jsonld") {
"application/ld+json".into()
} else if cfg!(feature = "xml") && iri.ends_with(".rdf") {
"application/rdf+xml".into()
} else {
"application/octet-stream".into()
}
Expand All @@ -80,7 +86,14 @@ impl Loader for LocalLoader {
// emulate conneg if there is no extension
let no_ext = iri.as_bytes()[iri.rfind(['.', '/']).unwrap_or(0)] != b'.';
if no_ext {
for ext in ["ttl", "nt"] {
for ext in [
"ttl",
"nt",
#[cfg(feature = "jsonld")]
"jsonld",
#[cfg(feature = "xml")]
"rdf",
] {
let alt = Iri::new_unchecked(format!("{}.{}", iri, ext));
if let Ok(res) = self.get(alt) {
return Ok(res);
Expand Down
32 changes: 31 additions & 1 deletion resource/src/loader/_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use sophia_api::parser::TripleParser;
use sophia_api::source::TripleSource;
use sophia_api::term::Term;
use sophia_iri::Iri;
use sophia_turtle::parser::turtle;
use sophia_turtle::parser::{nt, turtle};
use std::borrow::Borrow;
use std::fmt::Debug;
use std::io;
Expand Down Expand Up @@ -40,6 +40,36 @@ pub trait Loader: Sized {
.parse(bufread)
.collect_triples()
.map_err(|err| LoaderError::ParseError(iri_buf(iri_str), Box::new(err))),

"application/n-triples" => nt::NTriplesParser {}
.parse(bufread)
.collect_triples()
.map_err(|err| LoaderError::ParseError(iri_buf(iri_str), Box::new(err))),

#[cfg(feature = "jsonld")]
"application/ld+json" => {
use sophia_api::prelude::{Quad, QuadParser, QuadSource};
use sophia_jsonld::{JsonLdOptions, JsonLdParser};
let options =
JsonLdOptions::new().with_base(iri.as_ref().map_unchecked(|t| t.into()));
// TODO use this loader as the document loader for the JSON-LD parser
// (requires to provide an adaptater)
JsonLdParser::new_with_options(options)
.parse(bufread)
.filter_quads(|q| q.g().is_none())
.map_quads(Quad::into_triple)
.collect_triples()
.map_err(|err| LoaderError::ParseError(iri_buf(iri_str), Box::new(err)))
}

#[cfg(feature = "xml")]
"application/rdf+xml" => sophia_xml::parser::RdfXmlParser {
base: Some(iri.as_ref().map_unchecked(|t| t.borrow().to_string())),
}
.parse(bufread)
.collect_triples()
.map_err(|err| LoaderError::ParseError(iri_buf(iri_str), Box::new(err))),

_ => Err(LoaderError::CantGuessSyntax(iri_buf(iri_str))),
}
}
Expand Down
92 changes: 91 additions & 1 deletion resource/src/loader/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,70 @@ fn get_file1_with_add() -> TestResult {
Ok(())
}

#[test]
fn get_file3() -> TestResult {
let ldr = make_loader();
assert_eq!(
ldr.get(F3)?,
(read("test/file3.nt")?, "application/n-triples".into()),
);
Ok(())
}

#[test]
fn get_file3_no_ext() -> TestResult {
let ldr = make_loader();
assert_eq!(
ldr.get(F3X)?,
(read("test/file3.nt")?, "application/n-triples".into()),
);
Ok(())
}

#[cfg(feature = "jsonld")]
#[test]
fn get_file4() -> TestResult {
let ldr = make_loader();
assert_eq!(
ldr.get(F4)?,
(read("test/file4.jsonld")?, "application/ld+json".into()),
);
Ok(())
}

#[cfg(feature = "jsonld")]
#[test]
fn get_file4_no_ext() -> TestResult {
let ldr = make_loader();
assert_eq!(
ldr.get(F4X)?,
(read("test/file4.jsonld")?, "application/ld+json".into()),
);
Ok(())
}

#[cfg(feature = "xml")]
#[test]
fn get_file5() -> TestResult {
let ldr = make_loader();
assert_eq!(
ldr.get(F5)?,
(read("test/file5.rdf")?, "application/rdf+xml".into()),
);
Ok(())
}

#[cfg(feature = "xml")]
#[test]
fn get_file5_no_ext() -> TestResult {
let ldr = make_loader();
assert_eq!(
ldr.get(F5X)?,
(read("test/file5.rdf")?, "application/rdf+xml".into()),
);
Ok(())
}

#[test]
fn file_not_found() -> TestResult {
let ldr = make_loader();
Expand All @@ -73,13 +137,39 @@ fn io_error() -> TestResult {
}

#[test]
fn graph() -> TestResult {
fn graph_from_ttl() -> TestResult {
let ldr = make_loader();
let g: MyGraph = ldr.get_graph(F1)?;
assert_eq!(g.len(), F1_LEN);
Ok(())
}

#[test]
fn graph_from_nt() -> TestResult {
let ldr = make_loader();
let g: MyGraph = ldr.get_graph(F3)?;
assert_eq!(g.len(), F3_LEN);
Ok(())
}

#[cfg(feature = "jsonld")]
#[test]
fn graph_from_jsonld() -> TestResult {
let ldr = make_loader();
let g: MyGraph = ldr.get_graph(F4)?;
assert_eq!(g.len(), F4_LEN);
Ok(())
}

#[cfg(feature = "xml")]
#[test]
fn graph_from_rdfxml() -> TestResult {
let ldr = make_loader();
let g: MyGraph = ldr.get_graph(F5)?;
assert_eq!(g.len(), F5_LEN);
Ok(())
}

#[test]
fn resource() -> TestResult {
let ldr = make_loader().arced();
Expand Down
8 changes: 4 additions & 4 deletions sophia/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ all-features = true
[features]
default = []
# This feature enables the JSON-LD parser and serializer
jsonld = ["sophia_jsonld"]
jsonld = ["sophia_jsonld", "sophia_resource/jsonld"]
# This feature enables the RDF/XML parser and serializer
xml = ["sophia_xml"]
xml = ["sophia_xml", "sophia_resource/xml"]
# This feature enables to use the graph and dataset test macros in other crates
test_macro = ["sophia_api/test_macro"]
# This feature enables the HTTP client crate in dependencies
http_client = ["sophia_jsonld/http_client"]
# This feature enables the HTTP client in dependencies
http_client = ["sophia_jsonld/http_client", "sophia_resource/http_client"]

[dependencies]
sophia_iri.workspace = true
Expand Down

0 comments on commit fe9c51e

Please sign in to comment.