This commit is contained in:
Alex Wied
2026-07-13 14:05:40 -04:00
parent 54f417c96d
commit fc970084d7
12 changed files with 253 additions and 218 deletions
+30
View File
@@ -0,0 +1,30 @@
use std::collections::BTreeMap;
#[derive(Clone)]
pub struct CurieHelper {
prefixes: BTreeMap<String, String>,
}
impl CurieHelper {
pub fn new(prefixes: BTreeMap<String, String>) -> Self {
Self {
prefixes,
}
}
pub fn abbreviate(&self, iri: &str) -> Option<String> {
for (name, base) in &self.prefixes {
if let Some(local_name) = iri.strip_prefix(base) {
return Some(format!("{name}:{local_name}"));
}
}
None
}
pub fn expand(&self, abbreviated_iri: &str) -> Option<String> {
let (prefix, name) = abbreviated_iri.split_once(':')?;
self.prefixes
.get(prefix)
.map(|base| format!("{base}{name}"))
}
}
+2 -1
View File
@@ -3,4 +3,5 @@ pub(crate) mod term_helper;
pub mod vocab;
pub(crate) mod materialize;
pub(crate) mod conversion;
pub(crate) mod language;
pub(crate) mod language;
pub(crate) mod curie;
+53 -43
View File
@@ -1,18 +1,20 @@
use crate::error;
use crate::rdf::vocab::gl;
use oxigraph::model::vocab::{rdf, rdfs, xsd};
use oxigraph::model::{Dataset, Graph, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef};
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use oxigraph::model::{Dataset, Graph, GraphName, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef};
use oxigraph::sparql::{PreparedSparqlQuery, QueryResults, SparqlEvaluator};
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use oxigraph::store::Store;
use tracing::{debug_span, info};
use tracing::debug_span;
use crate::rdf::{conversion, materialize};
use crate::rdf::conversion::{quad_into_term, term_into_named_node, term_into_string, term_to_named_node};
use crate::rdf::language::LanguageCondition;
const ONTOLOGY_PREFIX: &str = "https://graphofliberty.org/2026/04/ont/";
const ONTOLOGY_GRAPH_NAME: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont"));
static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
BTreeMap::from_iter([
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
@@ -44,12 +46,13 @@ static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
("rdax", "http://rdaregistry.info/Elements/x/"),
("schema", "https://schema.org/"),
("quill", "http://fedora.quill.lan/rest/"),
("gl", "https://graphofliberty.org/2026/04/ont/"),
("gl", "ONTOLOGY_PREFIX"),
].map(|(k, v)| (k.to_string(), v.to_string())))
});
pub struct OntologyBuilder {
path: Option<PathBuf>,
materialize_inferences: bool,
}
impl OntologyBuilder {
@@ -59,9 +62,18 @@ impl OntologyBuilder {
self
}
pub fn materialize_inferences(mut self) -> Self {
self.materialize_inferences = true;
self
}
pub fn build(self) -> error::Result<Ontology> {
let mut store = if let Some(path) = self.path {
Store::open_read_only(path)
if self.materialize_inferences {
Store::open(path)
} else {
Store::open_read_only(path)
}
} else {
Store::new()
}?;
@@ -71,10 +83,12 @@ impl OntologyBuilder {
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect::<HashMap<String, String>>();
/*materialize::same_as(&mut store)?;
materialize::super_properties(&mut store)?;
materialize::super_classes(&mut store)?;
debug_span!("Optimize Ontology").in_scope(|| store.optimize())?;*/
if self.materialize_inferences {
materialize::same_as(&mut store)?;
materialize::super_properties(&mut store)?;
materialize::super_classes(&mut store)?;
debug_span!("Optimize Ontology").in_scope(|| store.optimize())?;
}
let mut indexed_by = HashMap::new();
for quad in store.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None)
@@ -92,6 +106,7 @@ impl OntologyBuilder {
}
}
#[derive(Clone, Debug)]
pub struct IndexEntry {
pub catalog_id: u64,
pub fields: HashMap<String, String>,
@@ -142,14 +157,22 @@ impl Ontology {
pub fn builder() -> OntologyBuilder {
OntologyBuilder {
path: None,
materialize_inferences: false,
}
}
pub fn prefixes(&self) -> &BTreeMap<String, String> {
pub fn prefixes() -> &'static BTreeMap<String, String> {
&*PREFIXES
}
pub fn index(&self, language: &LanguageCondition) -> error::Result<HashMap<NamedNode, IndexEntry>> {
pub fn to_dataset(&self) -> Dataset {
self.store
.quads_for_pattern(None, None, None, Some(ONTOLOGY_GRAPH_NAME))
.filter_map(Result::ok)
.collect::<Dataset>()
}
pub fn index_query(language: &LanguageCondition) -> PreparedSparqlQuery {
let language_filter = language.to_filter_expression("fieldValue");
let query = format!(r#"SELECT ?individual ?catalogId ?fieldName ?fieldValue
WHERE {{
@@ -165,12 +188,20 @@ WHERE {{
{language_filter}
}}"#);
let mut sparql = SparqlEvaluator::new()
.with_prefix("gl", "https://graphofliberty.org/2026/04/ont/")?
.parse_query(&query)?;
.with_prefix("gl", ONTOLOGY_PREFIX).unwrap()
.parse_query(&query)
.unwrap();
sparql.dataset_mut().set_default_graph_as_union();
sparql
}
pub fn execute_query(&self, query: PreparedSparqlQuery) -> error::Result<QueryResults<'_>> {
Ok(query.on_store(&self.store).execute()?)
}
pub fn transform_index_results(query_results: QueryResults) -> HashMap<NamedNode, IndexEntry> {
let mut results = HashMap::new();
if let QueryResults::Solutions(solutions) = sparql.on_store(&self.store).execute()? {
if let QueryResults::Solutions(solutions) = query_results {
for solution in solutions.filter_map(Result::ok) {
let individual = solution.get("individual").and_then(conversion::term_to_named_node);
let catalog_id = solution.get("catalogId").and_then(conversion::term_to_u64);
@@ -187,8 +218,7 @@ WHERE {{
}
}
}
Ok(results)
results
}
pub fn catalog_id(&self, class: &NamedNode) -> Option<u64> {
@@ -211,7 +241,7 @@ WHERE {{
}}"#);
let mut sparql = SparqlEvaluator::new()
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
.with_prefix("gl", "https://graphofliberty.org/2026/04/ont/")?
.with_prefix("gl", ONTOLOGY_PREFIX)?
.parse_query(&query)?;
sparql.dataset_mut().set_default_graph_as_union();
@@ -232,32 +262,12 @@ WHERE {{
Ok(results)
}
pub fn abbreviate(&self, node: NamedNodeRef<'_>) -> String {
for (prefix_name, prefix_iri) in &self.prefixes {
if let Some(local_name) = node.as_str().strip_prefix(prefix_iri) {
return if local_name.is_empty() {
format!("{prefix_name}:")
} else {
format!("{prefix_name}:{local_name}")
};
}
}
node.as_str().to_string()
}
pub fn expand(&self, prefixed_iri: &str) -> Option<NamedNode> {
let (prefix, name) = prefixed_iri.split_once(':')?;
self.prefixes
.get(prefix)
.map(|base| NamedNode::new_unchecked(format!("{base}{name}")))
}
pub fn info(&self, iri: &NamedNode, language: &LanguageCondition) -> LabeledIri {
let label = self.store.quads_for_pattern(Some(iri.as_ref().into()), Some(rdfs::LABEL), None, None)
.filter_map(Result::ok)
.map(quad_into_term)
.map(conversion::quad_into_term)
.filter(|term| language.primary_matches_term(term))
.filter_map(term_into_string)
.filter_map(conversion::term_into_string)
.next()
.unwrap_or_default();
@@ -273,9 +283,9 @@ WHERE {{
.filter_map(|quad| {
let label = self.store.quads_for_pattern(Some(quad.subject.as_ref().into()), Some(rdfs::LABEL), None, None)
.filter_map(Result::ok)
.map(quad_into_term)
.map(conversion::quad_into_term)
.filter(|term| language.primary_matches_term(term))
.filter_map(term_into_string)
.filter_map(conversion::term_into_string)
.next();
if let Some(label) = label && let NamedOrBlankNode::NamedNode(subject) = quad.subject {
Some(LabeledIri {