This commit is contained in:
Alex Wied
2026-06-09 15:40:28 -04:00
parent d0dc1b277d
commit a5f62602f6
7 changed files with 751 additions and 109 deletions
+98 -27
View File
@@ -7,6 +7,8 @@ use oxigraph::model::{
};
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
use std::collections::{HashMap, HashSet};
use tracing::debug;
use crate::rdf::vocab::{gl, owl};
const PREFIXES: &[(&str, &str)] = &[
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
@@ -41,26 +43,11 @@ const PREFIXES: &[(&str, &str)] = &[
const RDF_ONT: &[u8] = include_bytes!("ontologies/22-rdf-syntax-ns.ttl");
const RDFS_ONT: &[u8] = include_bytes!("ontologies/rdf-schema.ttl");
const OWL_ONT: &[u8] = include_bytes!("ontologies/owl.ttl");
const LDP_ONT: &[u8] = include_bytes!("ontologies/ldp.ttl");
const FEDORA_ONT: &[u8] = include_bytes!("ontologies/fedora.xml");
const GL_ONT: &[u8] = include_bytes!("ontologies/ontology.ttl");
const GL_READ_ONLY: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/ReadOnly");
const GL_TEMPLATE: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/template");
const GL_INDEXED_BY_FIELD: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/indexedByField");
const GL_CATALOG_ID: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/catalogId");
const OWL_SAME_AS: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#sameAs");
pub const RDA_ENTITY: NamedNodeRef =
NamedNodeRef::new_unchecked("http://rdaregistry.info/Elements/c/C10013");
pub const GL_ENTITY: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/Entity");
pub struct OntologyBuilder<'a> {
ontologies: Vec<(RdfFormat, &'a [u8])>,
}
@@ -74,6 +61,7 @@ impl<'a> OntologyBuilder<'a> {
pub fn with_default_ontologies(self) -> Self {
self.with_ontology_bytes(RdfFormat::Turtle, RDF_ONT)
.with_ontology_bytes(RdfFormat::Turtle, RDFS_ONT)
.with_ontology_bytes(RdfFormat::Turtle, OWL_ONT)
.with_ontology_bytes(RdfFormat::Turtle, LDP_ONT)
.with_ontology_bytes(RdfFormat::RdfXml, FEDORA_ONT)
.with_ontology_bytes(RdfFormat::Turtle, GL_ONT)
@@ -81,7 +69,7 @@ impl<'a> OntologyBuilder<'a> {
fn materialize_same_as(dataset: &mut Dataset) {
let additional_quads = dataset
.quads_for_pattern(None, Some(OWL_SAME_AS), None, None)
.quads_for_pattern(None, Some(owl::SAME_AS), None, None)
.fold(Dataset::new(), |mut new_dataset, alias| {
if let NamedOrBlankNodeRef::NamedNode(x) = alias.subject
&& let TermRef::NamedNode(y) = alias.object
@@ -118,8 +106,50 @@ impl<'a> OntologyBuilder<'a> {
}
}
fn lookup_iri_information<'b>(dataset: &Dataset, classes: impl IntoIterator<Item = NamedNodeRef<'b>>) -> HashMap<NamedNode, IriInformation> {
let class_filter = classes
.into_iter()
.map(|node| node.to_string())
.collect::<Vec<_>>()
.join(",");
let query = SparqlEvaluator::new()
.parse_query(format!(r#"PREFIX gl: <https://graphofliberty.org/2026/04/ont/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT DISTINCT ?subject ?label ?comment ?read_only {{
?subject a ?class .
FILTER(?class IN ({class_filter}))
OPTIONAL {{ ?subject rdfs:label ?label }}
OPTIONAL {{ ?subject rdfs:comment ?comment }}
OPTIONAL {{ ?subject gl:readOnly ?read_only }}
}}"#).as_str()).expect("Unable to parse property query");
let mut results = HashMap::new();
if let QueryResults::Solutions(solutions) =
query.on_queryable_dataset(dataset).execute().unwrap()
{
for solution in solutions.filter_map(Result::ok) {
let subject = solution.get("subject").and_then(term_to_named_node);
let label = solution.get("label").and_then(term_to_string);
let comment = solution.get("comment").and_then(term_to_string);
let read_only = solution.get("read_only").and_then(term_to_boolean).unwrap_or(false);
if let Some(subject) = subject {
let info = IriInformation {
label: label.map(String::from),
comment: comment.map(String::from),
read_only,
};
results.insert(subject.to_owned(), info);
}
}
}
results
}
fn memoize(ontology: &mut Ontology) {
// Read-only properties
/*// Read-only properties
for quad in ontology.dataset.quads_for_pattern(
None,
Some(rdf::TYPE),
@@ -165,12 +195,12 @@ impl<'a> OntologyBuilder<'a> {
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject
&& let TermRef::Literal(literal) = quad.object
{
if literal.datatype() == xsd::POSITIVE_INTEGER {
let value: u64 = literal.value().parse().expect("Failed to parse catalog ID from ontology. It ought to be a positive integer.");
if literal.datatype() == xsd::NON_NEGATIVE_INTEGER {
let value: u64 = literal.value().parse().expect("Failed to parse catalog ID from ontology. It ought to be a non-negative integer.");
ontology.catalog_ids.insert(subject.into_owned(), value);
}
}
}
}*/
}
pub fn build(&mut self) -> error::Result<Ontology> {
@@ -188,9 +218,25 @@ impl<'a> OntologyBuilder<'a> {
}
Self::materialize_same_as(&mut dataset);
let properties = Self::lookup_iri_information(&dataset, [
rdf::PROPERTY,
owl::ANNOTATION_PROPERTY,
owl::DATATYPE_PROPERTY,
owl::FUNCTIONAL_PROPERTY,
owl::OBJECT_PROPERTY,
owl::ONTOLOGY_PROPERTY,
]);
let classes = Self::lookup_iri_information(&dataset, [
rdfs::CLASS,
owl::CLASS,
]);
let mut ontology = Ontology {
dataset,
prefixes,
properties,
classes,
read_only_properties: HashSet::new(),
read_only_classes: HashSet::new(),
field_map: HashMap::new(),
@@ -202,20 +248,45 @@ impl<'a> OntologyBuilder<'a> {
}
}
#[derive(Debug)]
struct IriInformation {
label: Option<String>,
comment: Option<String>,
read_only: bool,
}
pub struct Ontology {
dataset: Dataset,
prefixes: HashMap<String, String>,
properties: HashMap<NamedNode, IriInformation>,
classes: HashMap<NamedNode, IriInformation>,
read_only_properties: HashSet<NamedNode>,
read_only_classes: HashSet<NamedNode>,
field_map: HashMap<NamedNode, String>,
catalog_ids: HashMap<NamedNode, u64>,
}
fn term_to_string(term: &Term) -> Option<String> {
match term {
Term::Literal(literal) => Some(literal.value().to_string()),
_ => None,
}
fn term_to_named_node(term: &Term) -> Option<&NamedNode> {
if let Term::NamedNode(node) = term {
Some(node)
} else { None }
}
fn term_to_string(term: &Term) -> Option<&str> {
if let Term::Literal(literal) = term {
match literal.datatype() {
xsd::STRING | xsd::NORMALIZED_STRING => Some(literal.value()),
_ => None,
}
} else { None }
}
fn term_to_boolean(term: &Term) -> Option<bool> {
if let Term::Literal(literal) = term {
if literal.datatype() == xsd::BOOLEAN {
literal.value().parse().ok()
} else { None }
} else { None }
}
impl Ontology {
@@ -384,7 +455,7 @@ SELECT ?class {{
.dataset
.quads_for_pattern(
Some(NamedOrBlankNodeRef::NamedNode(class)),
Some(GL_TEMPLATE),
Some(gl::TEMPLATE),
None,
None,
)