use crate::{Entity, OntologyDocument};
pub fn normalize_iri(iri: &str) -> String {
iri.trim_end_matches('#').trim_end_matches('/').to_string()
}
pub fn file_uri_for_path(path: &std::path::Path) -> String {
let display = path.display().to_string();
if display.starts_with('/') {
format!("file://{display}")
} else {
format!("file:///{display}")
}
}
pub fn document_matches_entity(entity: &Entity, doc: &OntologyDocument) -> bool {
if entity.ontology_id == doc.id {
return true;
}
if let Some(base) = &doc.base_iri {
if normalize_iri(base) == normalize_iri(&entity.ontology_id) {
return true;
}
}
false
}
fn base_iri_prefix_len(entity: &Entity, doc: &OntologyDocument) -> Option<usize> {
let base = doc.base_iri.as_ref()?;
if entity.iri.starts_with(base.as_str()) {
Some(base.len())
} else {
None
}
}
pub fn document_matches_ontology_id(ontology_id: &str, doc: &OntologyDocument) -> bool {
if ontology_id == doc.id {
return true;
}
if let Some(base) = &doc.base_iri {
if normalize_iri(base) == normalize_iri(ontology_id) {
return true;
}
}
false
}
pub fn document_for_entity<'a>(
documents: &'a [OntologyDocument],
entity: &Entity,
) -> Option<&'a OntologyDocument> {
if let Some(doc) = documents.iter().find(|d| entity.ontology_id == d.id) {
return Some(doc);
}
if let Some(doc) = documents.iter().find(|d| {
d.base_iri
.as_ref()
.is_some_and(|base| normalize_iri(base) == normalize_iri(&entity.ontology_id))
}) {
return Some(doc);
}
documents
.iter()
.filter_map(|d| base_iri_prefix_len(entity, d).map(|len| (d, len)))
.max_by_key(|(_, len)| *len)
.map(|(d, _)| d)
}
pub fn document_for_ontology_id<'a>(
documents: &'a [OntologyDocument],
ontology_id: &str,
) -> Option<&'a OntologyDocument> {
documents.iter().find(|d| document_matches_ontology_id(ontology_id, d))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{EntityKind, OntologyFormat, ParseStatus};
use std::collections::BTreeMap;
use std::path::Path;
fn doc(id: &str, base_iri: Option<&str>) -> OntologyDocument {
OntologyDocument {
id: id.to_string(),
path: Path::new(&format!("{id}.ttl")).to_path_buf(),
format: OntologyFormat::Turtle,
base_iri: base_iri.map(str::to_string),
imports: vec![],
namespaces: BTreeMap::new(),
parse_status: ParseStatus::Ok,
content_hash: "h".to_string(),
modified_time: 0,
parse_message: None,
parse_error_location: None,
}
}
fn entity(iri: &str, ontology_id: &str) -> Entity {
Entity {
iri: iri.to_string(),
short_name: iri.rsplit(['#', '/']).next().unwrap_or(iri).to_string(),
kind: EntityKind::Class,
ontology_id: ontology_id.to_string(),
source_location: Default::default(),
labels: vec![],
comments: vec![],
deprecated: false,
obo_id: None,
characteristics: Default::default(),
}
}
#[test]
fn matches_entity_by_ontology_iri() {
let document = doc("doc-1", Some("http://example.org/people"));
let ent = entity("http://example.org/people#Person", "http://example.org/people");
assert!(document_matches_entity(&ent, &document));
}
#[test]
fn broader_base_iri_does_not_match_when_ontology_id_differs() {
let broad = doc("doc-A", Some("http://example.org/"));
let ent = entity("http://example.org/people#Person", "doc-B");
assert!(!document_matches_entity(&ent, &broad));
}
#[test]
fn document_for_entity_prefers_exact_ontology_id_over_broader_base() {
let broad = doc("doc-A", Some("http://example.org/"));
let specific = doc("doc-B", Some("http://example.org/people#"));
let ent = entity("http://example.org/people#Person", "doc-B");
let documents = vec![broad, specific];
let found = document_for_entity(&documents, &ent).expect("owner");
assert_eq!(found.id, "doc-B");
}
#[test]
fn document_for_entity_longest_base_iri_wins_without_ontology_id_match() {
let broad = doc("doc-A", Some("http://example.org/"));
let specific = doc("doc-B", Some("http://example.org/people#"));
let ent = entity("http://example.org/people#Person", "unknown");
let documents = vec![broad, specific];
let found = document_for_entity(&documents, &ent).expect("owner");
assert_eq!(found.id, "doc-B");
}
#[test]
fn document_for_entity_longest_base_iri_wins_when_broad_is_second() {
let specific = doc("doc-B", Some("http://example.org/people#"));
let broad = doc("doc-A", Some("http://example.org/"));
let ent = entity("http://example.org/people#Person", "unknown");
let documents = vec![specific, broad];
let found = document_for_entity(&documents, &ent).expect("owner");
assert_eq!(found.id, "doc-B");
}
}