use oxigraph::io::{RdfFormat, RdfParser};
use oxigraph::model::Quad;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseDiagnostic {
pub line: u64,
pub column: u64,
pub end_line: u64,
pub end_column: u64,
pub offset: u64,
pub message: String,
}
#[derive(Debug, Clone, Default)]
pub struct LocatedParse {
pub quads: Vec<Quad>,
pub diagnostics: Vec<ParseDiagnostic>,
}
fn parse_located(format: RdfFormat, content: &str) -> LocatedParse {
let mut quads = Vec::new();
let mut diagnostics = Vec::new();
let mut parser = RdfParser::from_format(format).for_slice(content.as_bytes());
loop {
match parser.next() {
Some(Ok(quad)) => quads.push(quad),
Some(Err(err)) => {
let (line, column, end_line, end_column, offset) = match err.location() {
Some(range) => (
range.start.line + 1,
range.start.column + 1,
range.end.line + 1,
range.end.column + 1,
range.start.offset,
),
None => (1, 1, 1, 1, 0),
};
diagnostics.push(ParseDiagnostic {
line,
column,
end_line,
end_column,
offset,
message: err.to_string(),
});
break;
}
None => break,
}
}
LocatedParse { quads, diagnostics }
}
pub fn parse_turtle_located(content: &str) -> LocatedParse {
parse_located(RdfFormat::Turtle, content)
}
pub fn parse_ntriples_located(content: &str) -> LocatedParse {
parse_located(RdfFormat::NTriples, content)
}
pub fn parse_nquads_located(content: &str) -> LocatedParse {
parse_located(RdfFormat::NQuads, content)
}
pub fn extract_prefixes(content: &str) -> Vec<(String, String)> {
let mut parser = RdfParser::from_format(RdfFormat::Turtle).for_slice(content.as_bytes());
for item in parser.by_ref() {
if item.is_err() {
break;
}
}
parser
.prefixes()
.map(|(prefix, iri)| (prefix.to_string(), iri.to_string()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clean_turtle_has_no_diagnostics() {
let ttl = "@prefix ex: <http://example.org/> .\nex:a ex:b ex:c .\n";
let result = parse_turtle_located(ttl);
assert!(result.diagnostics.is_empty(), "expected clean parse");
assert_eq!(result.quads.len(), 1);
}
#[test]
fn malformed_turtle_reports_located_diagnostic() {
let ttl = "@prefix ex: <http://example.org/> .\nex:a ex:b \"unterminated\n";
let result = parse_turtle_located(ttl);
assert!(
!result.diagnostics.is_empty(),
"expected a syntax diagnostic"
);
let diag = &result.diagnostics[0];
assert!(diag.line >= 1);
assert!(diag.column >= 1);
assert!(!diag.message.is_empty());
}
#[test]
fn extract_prefixes_returns_declared_prefixes() {
let ttl = "@prefix ex: <http://example.org/> .\n@prefix sh: <http://www.w3.org/ns/shacl#> .\nex:a ex:b ex:c .\n";
let prefixes = extract_prefixes(ttl);
assert!(prefixes
.iter()
.any(|(p, i)| p == "ex" && i == "http://example.org/"));
assert!(prefixes
.iter()
.any(|(p, i)| p == "sh" && i == "http://www.w3.org/ns/shacl#"));
}
}