pub mod error;
#[allow(clippy::module_inception)]
pub mod format;
pub mod jsonld;
pub mod n3;
pub mod n3_lexer;
pub mod nquads;
pub mod ntriples;
pub mod parser;
pub mod rdfxml;
pub mod serializer;
pub mod toolkit;
pub mod trig;
pub mod turtle;
pub mod turtle_grammar;
pub mod w3c_tests;
pub use error::{FormatError, RdfParseError, RdfSyntaxError, TextPosition};
pub use format::RdfFormat;
pub use parser::{QuadParseResult, RdfParser, ReaderQuadParser, SliceQuadParser};
pub use serializer::{QuadSerializeResult, RdfSerializer, WriterQuadSerializer};
pub use format::{JsonLdProfile, JsonLdProfileSet};
pub use jsonld::{JsonLdParser, JsonLdSerializer};
pub use n3::N3Serializer;
pub use nquads::NQuadsSerializer;
pub use ntriples::{NTriplesParser, NTriplesSerializer};
pub use rdfxml::{RdfXmlParser, RdfXmlSerializer};
pub use trig::TriGSerializer;
pub use turtle::{TurtleParser, TurtleSerializer};
pub use w3c_tests::{
run_w3c_compliance_tests, RdfComplianceStats, RdfTestResult, RdfTestStatus, RdfTestType,
W3cRdfTestConfig, W3cRdfTestSuiteRunner,
};
use crate::model::{Quad, Triple};
use crate::OxirsError;
use std::io::{Read, Write};
pub type FormatResult<T> = Result<T, FormatError>;
pub trait FormatDetection {
fn from_extension(extension: &str) -> Option<RdfFormat>;
fn from_media_type(media_type: &str) -> Option<RdfFormat>;
fn from_content(content: &[u8]) -> Option<RdfFormat>;
fn from_filename(filename: &str) -> Option<RdfFormat> {
std::path::Path::new(filename)
.extension()
.and_then(|ext| ext.to_str())
.and_then(Self::from_extension)
}
}
pub struct FormatHandler {
format: RdfFormat,
}
impl FormatHandler {
pub fn new(format: RdfFormat) -> Self {
Self { format }
}
pub fn parse_quads<R: Read + Send + 'static>(&self, reader: R) -> FormatResult<Vec<Quad>> {
let parser = RdfParser::new(self.format.clone());
let mut quads = Vec::new();
for quad_result in parser.for_reader(reader) {
quads.push(quad_result?);
}
Ok(quads)
}
pub fn parse_triples<R: Read + Send + 'static>(&self, reader: R) -> FormatResult<Vec<Triple>> {
let quads = self.parse_quads(reader)?;
Ok(quads
.into_iter()
.filter_map(|quad| quad.triple_in_default_graph())
.collect())
}
pub fn serialize_quads<W: Write + 'static>(
&self,
writer: W,
quads: &[Quad],
) -> FormatResult<()> {
let mut serializer = RdfSerializer::new(self.format.clone()).for_writer(writer);
for quad in quads {
serializer.serialize_quad(quad.as_ref())?;
}
serializer.finish()?;
Ok(())
}
pub fn serialize_triples<W: Write + 'static>(
&self,
writer: W,
triples: &[Triple],
) -> FormatResult<()> {
let quads: Vec<Quad> = triples.iter().map(|triple| triple.clone().into()).collect();
self.serialize_quads(writer, &quads)
}
pub fn format(&self) -> RdfFormat {
self.format.clone()
}
}
impl FormatDetection for FormatHandler {
fn from_extension(extension: &str) -> Option<RdfFormat> {
RdfFormat::from_extension(extension)
}
fn from_media_type(media_type: &str) -> Option<RdfFormat> {
RdfFormat::from_media_type(media_type)
}
fn from_content(content: &[u8]) -> Option<RdfFormat> {
let content_str = std::str::from_utf8(content).ok()?;
let content_lower = content_str.to_lowercase();
if content_lower.contains("<?xml") || content_lower.contains("<rdf:") {
return Some(RdfFormat::RdfXml);
}
if content_lower.trim_start().starts_with('{')
&& (content_lower.contains("@context") || content_lower.contains("@type"))
{
return Some(RdfFormat::JsonLd {
profile: JsonLdProfileSet::empty(),
});
}
if content_lower.contains("@prefix") || content_lower.contains("@base") {
if content_lower.contains("graph") {
return Some(RdfFormat::TriG);
}
return Some(RdfFormat::Turtle);
}
let lines: Vec<&str> = content_str.lines().take(10).collect();
if lines.iter().any(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
parts.len() >= 4 && line.ends_with(" .")
}) {
return Some(RdfFormat::NQuads);
}
if lines.iter().any(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
parts.len() >= 3 && line.ends_with(" .")
}) {
return Some(RdfFormat::NTriples);
}
None
}
}
impl From<OxirsError> for FormatError {
fn from(err: OxirsError) -> Self {
FormatError::InvalidData(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_detection_from_extension() {
assert_eq!(
FormatHandler::from_extension("ttl"),
Some(RdfFormat::Turtle)
);
assert_eq!(
FormatHandler::from_extension("nt"),
Some(RdfFormat::NTriples)
);
assert_eq!(
FormatHandler::from_extension("jsonld"),
Some(RdfFormat::JsonLd {
profile: JsonLdProfileSet::empty()
})
);
assert_eq!(FormatHandler::from_extension("unknown"), None);
}
#[test]
fn test_format_detection_from_content() {
let turtle_content = b"@prefix ex: <http://example.org/> .\nex:foo ex:bar ex:baz .";
assert_eq!(
FormatHandler::from_content(turtle_content),
Some(RdfFormat::Turtle)
);
let jsonld_content = br#"{"@context": "http://example.org/", "@type": "Person"}"#;
assert_eq!(
FormatHandler::from_content(jsonld_content),
Some(RdfFormat::JsonLd {
profile: JsonLdProfileSet::empty()
})
);
let rdfxml_content = b"<?xml version=\"1.0\"?>\n<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">";
assert_eq!(
FormatHandler::from_content(rdfxml_content),
Some(RdfFormat::RdfXml)
);
}
}