.
This commit is contained in:
+147
-83
@@ -4,7 +4,10 @@ use gl_graph::vocab::gl;
|
||||
use gl_search::language::LanguageCondition;
|
||||
use iced::futures::TryFutureExt;
|
||||
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
||||
use oxigraph::model::{Dataset, Graph, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef};
|
||||
use oxigraph::model::{
|
||||
Dataset, Graph, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode,
|
||||
NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef,
|
||||
};
|
||||
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||
use oxigraph::store::Store;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
@@ -14,48 +17,53 @@ use std::sync::LazyLock;
|
||||
use tracing::{debug_span, field};
|
||||
|
||||
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"));
|
||||
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#"),
|
||||
("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
|
||||
("owl", "http://www.w3.org/2002/07/owl#"),
|
||||
("xsd", "http://www.w3.org/2001/XMLSchema#"),
|
||||
("ldp", "http://www.w3.org/ns/ldp#"),
|
||||
("dc", "http://purl.org/dc/elements/1.1/"),
|
||||
("posix", "http://www.w3.org/ns/posix/stat#"),
|
||||
(
|
||||
"ebucore",
|
||||
"http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#",
|
||||
),
|
||||
("prov", "http://www.w3.org/ns/prov#"),
|
||||
("locid", "http://id.loc.gov/vocabulary/identifiers/"),
|
||||
("loclang", "http://id.loc.gov/vocabulary/languages/"),
|
||||
("premis", "http://www.loc.gov/premis/rdf/v1#"),
|
||||
("premis3", "http://www.loc.gov/premis/rdf/v3/"),
|
||||
("dcterms", "http://purl.org/dc/terms/"),
|
||||
("fedora", "http://fedora.info/definitions/v4/repository#"),
|
||||
("rdaa", "http://rdaregistry.info/Elements/a/"),
|
||||
("rdac", "http://rdaregistry.info/Elements/c/"),
|
||||
("rdae", "http://rdaregistry.info/Elements/e/"),
|
||||
("rdai", "http://rdaregistry.info/Elements/i/"),
|
||||
("rdam", "http://rdaregistry.info/Elements/m/"),
|
||||
("rdan", "http://rdaregistry.info/Elements/n/"),
|
||||
("rdap", "http://rdaregistry.info/Elements/p/"),
|
||||
("rdaf", "http://rdaregistry.info/Elements/rof/"),
|
||||
("rdat", "http://rdaregistry.info/Elements/t/"),
|
||||
("rdau", "http://rdaregistry.info/Elements/u/"),
|
||||
("rdaw", "http://rdaregistry.info/Elements/w/"),
|
||||
("rdax", "http://rdaregistry.info/Elements/x/"),
|
||||
("rdaco", "http://rdaregistry.info/termList/RDAContentType/"),
|
||||
("rdact", "http://rdaregistry.info/termList/RDACarrierType/"),
|
||||
("rdamt", "http://rdaregistry.info/termList/RDAMediaType/"),
|
||||
("rdaft", "http://rdaregistry.info/termList/fileType/"),
|
||||
("schema", "https://schema.org/"),
|
||||
("gl", "http://fedora.quill.lan/rest/"),
|
||||
("glo", ONTOLOGY_PREFIX),
|
||||
].map(|(k, v)| (k.to_string(), v.to_string())))
|
||||
BTreeMap::from_iter(
|
||||
[
|
||||
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
|
||||
("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
|
||||
("owl", "http://www.w3.org/2002/07/owl#"),
|
||||
("xsd", "http://www.w3.org/2001/XMLSchema#"),
|
||||
("ldp", "http://www.w3.org/ns/ldp#"),
|
||||
("dc", "http://purl.org/dc/elements/1.1/"),
|
||||
("posix", "http://www.w3.org/ns/posix/stat#"),
|
||||
(
|
||||
"ebucore",
|
||||
"http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#",
|
||||
),
|
||||
("prov", "http://www.w3.org/ns/prov#"),
|
||||
("locid", "http://id.loc.gov/vocabulary/identifiers/"),
|
||||
("loclang", "http://id.loc.gov/vocabulary/languages/"),
|
||||
("premis", "http://www.loc.gov/premis/rdf/v1#"),
|
||||
("premis3", "http://www.loc.gov/premis/rdf/v3/"),
|
||||
("dcterms", "http://purl.org/dc/terms/"),
|
||||
("fedora", "http://fedora.info/definitions/v4/repository#"),
|
||||
("rdaa", "http://rdaregistry.info/Elements/a/"),
|
||||
("rdac", "http://rdaregistry.info/Elements/c/"),
|
||||
("rdae", "http://rdaregistry.info/Elements/e/"),
|
||||
("rdai", "http://rdaregistry.info/Elements/i/"),
|
||||
("rdam", "http://rdaregistry.info/Elements/m/"),
|
||||
("rdan", "http://rdaregistry.info/Elements/n/"),
|
||||
("rdap", "http://rdaregistry.info/Elements/p/"),
|
||||
("rdaf", "http://rdaregistry.info/Elements/rof/"),
|
||||
("rdat", "http://rdaregistry.info/Elements/t/"),
|
||||
("rdau", "http://rdaregistry.info/Elements/u/"),
|
||||
("rdaw", "http://rdaregistry.info/Elements/w/"),
|
||||
("rdax", "http://rdaregistry.info/Elements/x/"),
|
||||
("rdaco", "http://rdaregistry.info/termList/RDAContentType/"),
|
||||
("rdact", "http://rdaregistry.info/termList/RDACarrierType/"),
|
||||
("rdamt", "http://rdaregistry.info/termList/RDAMediaType/"),
|
||||
("rdaft", "http://rdaregistry.info/termList/fileType/"),
|
||||
("schema", "https://schema.org/"),
|
||||
("gl", "http://fedora.quill.lan/rest/"),
|
||||
("glo", ONTOLOGY_PREFIX),
|
||||
]
|
||||
.map(|(k, v)| (k.to_string(), v.to_string())),
|
||||
)
|
||||
});
|
||||
|
||||
pub struct OntologyBuilder {
|
||||
@@ -82,18 +90,18 @@ impl OntologyBuilder {
|
||||
.collect::<HashMap<String, String>>();
|
||||
|
||||
let mut indexed_by = HashMap::new();
|
||||
for quad in store.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None)
|
||||
.filter_map(Result::ok) {
|
||||
for quad in store
|
||||
.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None)
|
||||
.filter_map(Result::ok)
|
||||
{
|
||||
if let NamedOrBlankNode::NamedNode(subject) = quad.subject
|
||||
&& let Term::NamedNode(field) = quad.object {
|
||||
&& let Term::NamedNode(field) = quad.object
|
||||
{
|
||||
indexed_by.insert(subject, field);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Ontology {
|
||||
store,
|
||||
prefixes,
|
||||
})
|
||||
Ok(Ontology { store, prefixes })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +154,7 @@ pub struct Ontology {
|
||||
|
||||
impl Ontology {
|
||||
pub fn builder() -> OntologyBuilder {
|
||||
OntologyBuilder {
|
||||
path: None,
|
||||
}
|
||||
OntologyBuilder { path: None }
|
||||
}
|
||||
|
||||
pub fn prefixes() -> &'static BTreeMap<String, String> {
|
||||
@@ -166,9 +172,14 @@ impl Ontology {
|
||||
self.store.clone()
|
||||
}
|
||||
|
||||
pub fn query_for_indexable_triples(&self, language: &LanguageCondition, source: Option<Dataset>) -> impl Future<Output = error::Result<HashMap<NamedNode, IndexEntry>>> + 'static {
|
||||
pub fn query_for_indexable_triples(
|
||||
&self,
|
||||
language: &LanguageCondition,
|
||||
source: Option<Dataset>,
|
||||
) -> impl Future<Output = error::Result<HashMap<NamedNode, IndexEntry>>> + 'static {
|
||||
let language_filter = language.to_filter_expression("fieldValue");
|
||||
let query = format!(r#"SELECT ?individual ?categoryId ?fieldName ?fieldValue
|
||||
let query = format!(
|
||||
r#"SELECT ?individual ?categoryId ?fieldName ?fieldValue
|
||||
WHERE {{
|
||||
?class a gl:SearchableClass ;
|
||||
gl:categoryId ?categoryId ;
|
||||
@@ -180,9 +191,11 @@ WHERE {{
|
||||
?property ?fieldValue .
|
||||
|
||||
{language_filter}
|
||||
}}"#);
|
||||
}}"#
|
||||
);
|
||||
let mut sparql = SparqlEvaluator::new()
|
||||
.with_prefix("gl", ONTOLOGY_PREFIX).unwrap()
|
||||
.with_prefix("gl", ONTOLOGY_PREFIX)
|
||||
.unwrap()
|
||||
.parse_query(&query)
|
||||
.expect("Unable to parse query");
|
||||
sparql.dataset_mut().set_default_graph_as_union();
|
||||
@@ -194,24 +207,40 @@ WHERE {{
|
||||
sparql.on_queryable_dataset(source).execute()
|
||||
} else {
|
||||
sparql.on_store(&store).execute()
|
||||
}.expect("Unable to execute indexing query");
|
||||
}
|
||||
.expect("Unable to execute indexing query");
|
||||
|
||||
let mut results = HashMap::new();
|
||||
if let QueryResults::Solutions(solutions) = query_results {
|
||||
let mut counter = 0usize;
|
||||
for solution in solutions.filter_map(Result::ok) {
|
||||
let individual = solution.get("individual").and_then(conversion::term_to_named_node);
|
||||
let individual = solution
|
||||
.get("individual")
|
||||
.and_then(conversion::term_to_named_node);
|
||||
let catalog_id = solution.get("categoryId").and_then(conversion::term_to_u64);
|
||||
let field_name = solution.get("fieldName").and_then(conversion::term_as_str);
|
||||
let field_value = solution.get("fieldValue").and_then(conversion::term_as_str);
|
||||
if let (Some(individual), Some(catalog_id), Some(field_name), Some(field_value)) = (individual, catalog_id, field_name, field_value) {
|
||||
results.entry(individual.to_owned())
|
||||
if let (
|
||||
Some(individual),
|
||||
Some(catalog_id),
|
||||
Some(field_name),
|
||||
Some(field_value),
|
||||
) = (individual, catalog_id, field_name, field_value)
|
||||
{
|
||||
results
|
||||
.entry(individual.to_owned())
|
||||
.and_modify(|entry: &mut IndexEntry| {
|
||||
entry.fields.insert(field_name.to_owned(), field_value.to_owned());
|
||||
}).or_insert(IndexEntry {
|
||||
category_id: catalog_id,
|
||||
fields: HashMap::from_iter([(field_name.to_owned(), field_value.to_owned())]),
|
||||
});
|
||||
entry
|
||||
.fields
|
||||
.insert(field_name.to_owned(), field_value.to_owned());
|
||||
})
|
||||
.or_insert(IndexEntry {
|
||||
category_id: catalog_id,
|
||||
fields: HashMap::from_iter([(
|
||||
field_name.to_owned(),
|
||||
field_value.to_owned(),
|
||||
)]),
|
||||
});
|
||||
}
|
||||
counter += 1;
|
||||
}
|
||||
@@ -220,27 +249,35 @@ WHERE {{
|
||||
unreachable!()
|
||||
}
|
||||
results
|
||||
}).map_err(error::Error::from)
|
||||
})
|
||||
.map_err(error::Error::from)
|
||||
}
|
||||
|
||||
pub fn category_id(&self, class: &NamedNode) -> Option<u64> {
|
||||
self.store.quads_for_pattern(Some(class.into()), Some(gl::CATEGORY_ID), None, None)
|
||||
self.store
|
||||
.quads_for_pattern(Some(class.into()), Some(gl::CATEGORY_ID), None, None)
|
||||
.filter_map(Result::ok)
|
||||
.map(conversion::quad_into_term)
|
||||
.filter_map(conversion::term_into_u64)
|
||||
.next()
|
||||
}
|
||||
|
||||
pub fn fields_for_class(&self, class: &NamedNode, language: &LanguageCondition) -> error::Result<Vec<IndexField>> {
|
||||
pub fn fields_for_class(
|
||||
&self,
|
||||
class: &NamedNode,
|
||||
language: &LanguageCondition,
|
||||
) -> error::Result<Vec<IndexField>> {
|
||||
let language_filter = language.to_filter_expression("label");
|
||||
let query = format!(r#"SELECT DISTINCT ?name ?label {{
|
||||
let query = format!(
|
||||
r#"SELECT DISTINCT ?name ?label {{
|
||||
{class} gl:associatedProperty/gl:indexedByField ?field .
|
||||
|
||||
?field gl:fieldName ?name ;
|
||||
gl:fieldLabel ?label .
|
||||
|
||||
{language_filter}
|
||||
}}"#);
|
||||
}}"#
|
||||
);
|
||||
let mut sparql = SparqlEvaluator::new()
|
||||
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
|
||||
.with_prefix("gl", ONTOLOGY_PREFIX)?
|
||||
@@ -253,7 +290,9 @@ WHERE {{
|
||||
let name = solution.get("name").and_then(conversion::term_as_str);
|
||||
let label = solution.get("label").and_then(conversion::term_as_str);
|
||||
|
||||
if let Some(name) = name && let Some(label) = label {
|
||||
if let Some(name) = name
|
||||
&& let Some(label) = label
|
||||
{
|
||||
results.push(IndexField {
|
||||
name: name.to_string(),
|
||||
label: label.to_string(),
|
||||
@@ -265,7 +304,9 @@ WHERE {{
|
||||
}
|
||||
|
||||
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)
|
||||
let label = self
|
||||
.store
|
||||
.quads_for_pattern(Some(iri.as_ref().into()), Some(rdfs::LABEL), None, None)
|
||||
.filter_map(Result::ok)
|
||||
.map(conversion::quad_into_term)
|
||||
.filter(|term| language.primary_matches_term(term))
|
||||
@@ -280,11 +321,16 @@ WHERE {{
|
||||
}
|
||||
|
||||
pub fn subclasses_of(&self, class: &NamedNode) -> BTreeSet<NamedNode> {
|
||||
self.store.quads_for_pattern(None, Some(rdfs::SUB_CLASS_OF), Some(class.into()), None)
|
||||
self.store
|
||||
.quads_for_pattern(None, Some(rdfs::SUB_CLASS_OF), Some(class.into()), None)
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|quad| if let NamedOrBlankNode::NamedNode(subject) = quad.subject {
|
||||
Some(subject)
|
||||
} else { None })
|
||||
.filter_map(|quad| {
|
||||
if let NamedOrBlankNode::NamedNode(subject) = quad.subject {
|
||||
Some(subject)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -300,7 +346,8 @@ WHERE {{
|
||||
.filter_map(|quad| match quad.subject {
|
||||
NamedOrBlankNode::NamedNode(subject) => Some(subject),
|
||||
_ => None,
|
||||
}).collect()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn is_read_only<'a>(&self, triple: impl Into<TripleRef<'a>>) -> bool {
|
||||
@@ -313,23 +360,35 @@ WHERE {{
|
||||
};
|
||||
|
||||
self.store
|
||||
.quads_for_pattern(Some(NamedOrBlankNodeRef::NamedNode(subject)), Some(gl::READ_ONLY), Some(true_term), None)
|
||||
.quads_for_pattern(
|
||||
Some(NamedOrBlankNodeRef::NamedNode(subject)),
|
||||
Some(gl::READ_ONLY),
|
||||
Some(true_term),
|
||||
None,
|
||||
)
|
||||
.filter_map(Result::ok)
|
||||
.count() >= 1
|
||||
.count()
|
||||
>= 1
|
||||
}
|
||||
|
||||
pub fn exclude_read_only(&self) -> impl Fn(TripleRef<'_>) -> bool + 'static {
|
||||
let true_term = TermRef::Literal(LiteralRef::new_typed_literal("true", xsd::BOOLEAN));
|
||||
let read_only_graph = self.store
|
||||
let read_only_graph = self
|
||||
.store
|
||||
.quads_for_pattern(None, Some(gl::READ_ONLY), Some(true_term), None)
|
||||
.filter_map(Result::ok)
|
||||
.map(Triple::from)
|
||||
.collect::<Graph>();
|
||||
move |triple| {
|
||||
if triple.predicate == rdf::TYPE && let TermRef::NamedNode(class) = triple.object {
|
||||
if triple.predicate == rdf::TYPE
|
||||
&& let TermRef::NamedNode(class) = triple.object
|
||||
{
|
||||
read_only_graph.triples_for_subject(class).count() == 0
|
||||
} else {
|
||||
read_only_graph.triples_for_subject(triple.predicate).count() == 0
|
||||
read_only_graph
|
||||
.triples_for_subject(triple.predicate)
|
||||
.count()
|
||||
== 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,7 +412,12 @@ WHERE {{
|
||||
if let Term::BlankNode(blank_node) = quad.object {
|
||||
let iter = self
|
||||
.store
|
||||
.quads_for_pattern(Some(NamedOrBlankNodeRef::BlankNode(blank_node.as_ref())), None, None, None)
|
||||
.quads_for_pattern(
|
||||
Some(NamedOrBlankNodeRef::BlankNode(blank_node.as_ref())),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.filter_map(Result::ok)
|
||||
.map(Triple::from)
|
||||
.map(move |mut triple| {
|
||||
@@ -368,4 +432,4 @@ WHERE {{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user