use crate::Result;
use crate::terms::{self, NamedNode};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Namespace {
prefix: String,
base: NamedNode,
}
impl Namespace {
pub fn new(prefix: impl Into<String>, base_iri: impl AsRef<str>) -> Result<Self> {
let prefix = prefix.into();
if prefix.is_empty()
|| !prefix
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
return Err(crate::Error::InvalidRdf(format!(
"namespace prefix '{prefix}' is invalid"
)));
}
let base = terms::named_node(base_iri.as_ref())?;
let base_str = base.as_str();
if !(base_str.ends_with('/') || base_str.ends_with('#') || base_str.ends_with(':')) {
return Err(crate::Error::InvalidRdf(format!(
"namespace base '{base_str}' must end with '/', '#', or ':' so local names do not concatenate ambiguously"
)));
}
Ok(Self { prefix, base })
}
#[must_use]
pub fn prefix(&self) -> &str {
&self.prefix
}
#[must_use]
pub fn base(&self) -> &NamedNode {
&self.base
}
pub fn expand(&self, local: &str) -> Result<NamedNode> {
let local = local
.strip_prefix(&format!("{}:", self.prefix))
.unwrap_or(local);
if local.is_empty() || local.contains(':') {
return Err(crate::Error::InvalidRdf(format!(
"local name '{local}' is invalid for namespace '{}'",
self.prefix
)));
}
terms::named_node(format!("{}{local}", self.base.as_str()))
}
}