.
This commit is contained in:
+98
-27
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user