use crate::error::{TextPosition, TurtleParseError, TurtleResult, TurtleSyntaxError};
use crate::formats::n3_parser::AdvancedN3Parser;
use crate::toolkit::Parser;
use oxirs_core::model::Triple;
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read};
#[derive(Debug, Clone)]
pub struct N3Parser {
pub lenient: bool,
pub base_iri: Option<String>,
pub prefixes: HashMap<String, String>,
}
impl Default for N3Parser {
fn default() -> Self {
Self::new()
}
}
impl N3Parser {
pub fn new() -> Self {
let mut prefixes = HashMap::new();
prefixes.insert(
"rdf".to_string(),
"http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(),
);
prefixes.insert(
"rdfs".to_string(),
"http://www.w3.org/2000/01/rdf-schema#".to_string(),
);
prefixes.insert(
"xsd".to_string(),
"http://www.w3.org/2001/XMLSchema#".to_string(),
);
prefixes.insert(
"owl".to_string(),
"http://www.w3.org/2002/07/owl#".to_string(),
);
Self {
lenient: false,
base_iri: None,
prefixes,
}
}
pub fn new_lenient() -> Self {
Self {
lenient: true,
..Self::new()
}
}
fn parse_n3_content<R: BufRead>(&self, mut reader: R) -> TurtleResult<Vec<Triple>> {
let mut content = String::new();
reader
.read_to_string(&mut content)
.map_err(TurtleParseError::io)?;
let mut advanced = AdvancedN3Parser::new(&content)?;
advanced.lenient = self.lenient;
for (prefix, iri) in &self.prefixes {
advanced.prefixes.insert(prefix.clone(), iri.clone());
}
if let Some(base) = &self.base_iri {
advanced.base_iri = Some(base.clone());
}
let doc = advanced.parse_document()?;
let mut triples = Vec::with_capacity(doc.statements.len());
for statement in doc.statements {
match statement.as_rdf_triple() {
Some(triple) => triples.push(triple),
None => {
if !self.lenient {
return Err(TurtleParseError::syntax(TurtleSyntaxError::Generic {
message:
"N3 statement uses a variable or formula and cannot be represented \
as a plain RDF triple; use AdvancedN3Parser for full N3 support"
.to_string(),
position: TextPosition::default(),
}));
}
}
}
}
Ok(triples)
}
}
impl Parser<Triple> for N3Parser {
fn parse<R: Read>(&self, reader: R) -> TurtleResult<Vec<Triple>> {
let buf_reader = BufReader::new(reader);
self.parse_n3_content(buf_reader)
}
fn for_reader<R: BufRead>(&self, reader: R) -> Box<dyn Iterator<Item = TurtleResult<Triple>>> {
match self.parse_n3_content(reader) {
Ok(triples) => Box::new(triples.into_iter().map(Ok)),
Err(e) => Box::new(std::iter::once(Err(e))),
}
}
}
pub struct N3Iterator {
triples: std::vec::IntoIter<Triple>,
}
impl N3Iterator {
pub fn new<R: BufRead>(reader: R, parser: &N3Parser) -> TurtleResult<Self> {
let triples = parser.parse_n3_content(reader)?;
Ok(Self {
triples: triples.into_iter(),
})
}
}
impl Iterator for N3Iterator {
type Item = TurtleResult<Triple>;
fn next(&mut self) -> Option<Self::Item> {
self.triples.next().map(Ok)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn parse(input: &str) -> TurtleResult<Vec<Triple>> {
N3Parser::new().parse(Cursor::new(input.as_bytes()))
}
#[test]
fn test_basic_triple() {
let triples = parse("@prefix ex: <http://example.org/> .\nex:alice ex:knows ex:bob .\n")
.expect("parsing should succeed");
assert_eq!(triples.len(), 1);
}
#[test]
fn test_iri_with_equals_sign_is_not_dropped() {
let triples = parse(
"@prefix ex: <http://example.org/> .\n\
ex:alice ex:seeAlso <http://example.org/search?a=b&c=d> .\n",
)
.expect("parsing should succeed");
assert_eq!(triples.len(), 1);
assert_eq!(
triples[0].object().to_string(),
"<http://example.org/search?a=b&c=d>"
);
}
#[test]
fn test_multiline_semicolon_statement_is_not_lost() {
let triples = parse(
"@prefix ex: <http://example.org/> .\n\
ex:alice\n ex:name \"Alice\" ;\n ex:knows ex:bob .\n",
)
.expect("parsing should succeed");
assert_eq!(triples.len(), 2);
}
#[test]
fn test_implication_does_not_break_surrounding_triples() {
let triples = parse(
"@prefix ex: <http://example.org/> .\n\
ex:alice ex:knows ex:bob .\n\
{ ?x ex:knows ?y } => { ?y ex:knows ?x } .\n\
ex:carol ex:knows ex:dave .\n",
)
.expect("parsing should succeed");
assert_eq!(triples.len(), 2);
}
#[test]
fn test_implication_is_error_in_strict_mode_without_variables() {
let strict = N3Parser::new();
let result = strict.parse(Cursor::new(
b"@prefix ex: <http://example.org/> .\n?x ex:knows ex:bob .\n".as_slice(),
));
assert!(result.is_err());
}
#[test]
fn test_variable_statement_skipped_in_lenient_mode() {
let lenient = N3Parser::new_lenient();
let triples = lenient
.parse(Cursor::new(
b"@prefix ex: <http://example.org/> .\n?x ex:knows ex:bob .\nex:alice ex:knows ex:bob .\n"
.as_slice(),
))
.expect("lenient parsing should succeed");
assert_eq!(triples.len(), 1);
}
}