This commit is contained in:
2026-08-23 14:20:48 -04:00
parent e28cfd1c53
commit 8f6e2b3ac6
8 changed files with 82 additions and 132 deletions
+2 -1
View File
@@ -12,4 +12,5 @@ oxilangtag.workspace = true
rayon.workspace = true
spargebra.workspace = true
thiserror.workspace = true
tracing.workspace = true
tracing.workspace = true
url = "2.5.8"
+9 -1
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use std::sync::LazyLock;
use url::Url;
const ONTOLOGY_PREFIX: &str = "https://graphofliberty.org/2026/04/ont/";
@@ -19,7 +20,9 @@ pub static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
),
("prov", "http://www.w3.org/ns/prov#"),
("locid", "http://id.loc.gov/vocabulary/identifiers/"),
("locname", "http://id.loc.gov/authorities/names/"),
("loclang", "http://id.loc.gov/vocabulary/languages/"),
("viaf", "http://viaf.org/viaf/"),
("premis", "http://www.loc.gov/premis/rdf/v1#"),
("premis3", "http://www.loc.gov/premis/rdf/v3/"),
("dcterms", "http://purl.org/dc/terms/"),
@@ -69,7 +72,12 @@ impl CurieHelper {
return Some(format!("{name}:{local_name}"));
}
}
None
if let Some(base) = base &&
let Some(parsed_iri) = Url::parse(iri).ok() &&
let Some(parsed_base) = Url::parse(base).ok() {
parsed_base.make_relative(&parsed_iri)
} else { None }
}
pub fn expand(&self, base: Option<&str>, abbreviated_iri: &str) -> Option<String> {
+5 -3
View File
@@ -118,15 +118,17 @@ impl<'a> Indexer<'a> {
results
}
pub fn ontology(&self, entities: HashMap<NamedNode, (NamedNode, ResourceDescription)>) -> Vec<HashMap<Field, OwnedValue>> {
pub fn ontology(&self, entities: HashMap<NamedNode, ResourceDescription>) -> Vec<HashMap<Field, OwnedValue>> {
let label_field = Schema::field("label", self.language.primary_language());
let definition_field = Schema::field("definition", self.language.primary_language());
entities.iter()
.map(|(subject, (class, description))| {
.map(|(subject, description)| {
let mut document = HashMap::with_capacity(4);
let discriminant = Class::try_from_named_node(class)
let discriminant = description.class
.as_ref()
.and_then(|node| Class::try_from_named_node(node.as_ref()))
.map(|doc_type| doc_type as u64)
.map(OwnedValue::U64)
.unwrap_or(OwnedValue::Null);
+46 -119
View File
@@ -5,18 +5,24 @@ use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
use oxigraph::model::{Graph, NamedNode, NamedNodeRef, Triple};
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput};
use spargebra::algebra::GraphPattern;
use spargebra::term::{GroundTerm, Variable};
use spargebra::term::{GroundTerm, NamedNodePattern, TermPattern, TriplePattern, Variable};
use gl_inference::proto::ontology_client::OntologyClient;
use gl_inference::proto::OntologyQueryRequest;
use gl_inference::tonic::transport::{Channel, Endpoint};
use crate::helpers::{term_as_str, term_to_named_node};
use crate::language::LanguageCondition;
use crate::vocab::{rdf, rdfs};
use crate::vocab;
#[derive(Clone, Debug, Default)]
pub struct ResourceDescription {
pub label: Option<String>,
pub description: Option<String>,
pub class: Option<NamedNode>,
}
pub enum ResourceSelector {
Classes,
Subjects,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
@@ -127,8 +133,8 @@ SELECT ?subject ?class WHERE {{
if let Some(subject) = subject &&
let Some(class) = class {
let entry = match class.as_ref() {
rdf::PROPERTY => ReadOnlyEntity::Property(subject),
rdfs::CLASS => ReadOnlyEntity::Class(subject),
vocab::rdf::PROPERTY => ReadOnlyEntity::Property(subject),
vocab::rdfs::CLASS => ReadOnlyEntity::Class(subject),
_ => continue,
};
results.insert(entry);
@@ -138,125 +144,44 @@ SELECT ?subject ?class WHERE {{
Ok(results)
}
pub async fn datatypes(&mut self) -> crate::Result<HashMap<NamedNode, ResourceDescription>> {
let label_filter = self.language.filter("label");
let comment_filter = self.language.filter("comment");
let mut request = OntologyQueryRequest::default();
request.sparql_query = Some(format!(r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?subject ?label ?comment WHERE {{
OPTIONAL {{
?subject a rdfs:Datatype
}} OPTIONAL {{
?subject rdfs:label ?label
{label_filter}
}} OPTIONAL {{
?subject rdfs:comment ?comment
{comment_filter}
}}
}}"#));
let response = self.client.query(request).await?;
let parser_output = QueryResultsParser::from_format(QueryResultsFormat::Json)
.for_slice(&response.get_ref().results)?;
let mut results = HashMap::new();
if let SliceQueryResultsParserOutput::Solutions(solutions) = parser_output {
for solution in solutions.filter_map(Result::ok) {
let subject = solution
.get("subject")
.and_then(term_to_named_node)
.map(|node| node.clone());
if let Some(subject) = subject {
let label = solution
.get("label")
.and_then(term_as_str)
.map(String::from);
let description = solution
.get("description")
.and_then(term_as_str)
.map(String::from);
results.insert(subject, ResourceDescription { label, description });
}
}
}
Ok(results)
}
pub async fn resource_description_from_subject(&mut self, subject: NamedNode) -> crate::Result<ResourceDescription> {
let label_filter = self.language.filter("label");
let comment_filter = self.language.filter("comment");
let definition_filter = self.language.filter("definition");
let mut request = OntologyQueryRequest::default();
request.sparql_query = Some(format!(r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?label ?description WHERE {{
OPTIONAL {{
{subject} rdfs:label ?label
{label_filter}
}} OPTIONAL {{
{subject} rdfs:comment ?comment
{comment_filter}
}} OPTIONAL {{
{subject} skos:definition ?definition
{definition_filter}
}}
BIND(COALESCE(?definition, ?comment) AS ?description)
}}"#));
let foo = request.sparql_query.clone().unwrap();
let response = self.client.query(request).await?;
let parser_output = QueryResultsParser::from_format(QueryResultsFormat::Json)
.for_slice(&response.get_ref().results)?;
if let SliceQueryResultsParserOutput::Solutions(solutions) = parser_output {
Ok(solutions.filter_map(Result::ok)
.next()
.map(|solution| {
let label = solution
.get("label")
.and_then(term_as_str)
.map(String::from);
let description = solution
.get("description")
.and_then(term_as_str)
.map(String::from);
ResourceDescription {
label,
description,
}
}).unwrap_or(ResourceDescription::default())
)
} else { unreachable!() }
}
fn named_nodes_to_values_expression<'a>(variable: &str, nodes: impl IntoIterator<Item = NamedNodeRef<'a>>) -> String {
fn selector_to_sparql_pattern<'a>(selector: ResourceSelector, nodes: impl IntoIterator<Item = NamedNodeRef<'a>>) -> String {
let bindings = nodes.into_iter()
.map(|node| GroundTerm::NamedNode(node.into_owned()))
.map(Some)
.map(|item| vec![item])
.collect();
GraphPattern::Values {
variables: vec![Variable::new_unchecked(variable)],
let variable = match selector {
ResourceSelector::Classes => Variable::new_unchecked("class"),
ResourceSelector::Subjects => Variable::new_unchecked("subject"),
};
let values_pattern = GraphPattern::Values {
variables: vec![variable.clone()],
bindings,
};
let triple_pattern = match selector {
ResourceSelector::Classes => vec![TriplePattern {
subject: TermPattern::Variable(Variable::new_unchecked("subject")),
predicate: NamedNodePattern::NamedNode(vocab::rdf::TYPE.into_owned()),
object: TermPattern::Variable(variable),
}],
ResourceSelector::Subjects => vec![],
};
let basic_pattern = GraphPattern::Bgp {
patterns: triple_pattern,
};
GraphPattern::Join {
left: Box::new(basic_pattern),
right: Box::new(values_pattern),
}.to_string()
}
pub async fn resource_descriptions_from_classes<'a>(&mut self, classes: impl IntoIterator<Item = NamedNodeRef<'a>>) -> crate::Result<HashMap<NamedNode, (NamedNode, ResourceDescription)>> {
let values = Self::named_nodes_to_values_expression("class", classes);
pub async fn resource_descriptions<'a>(&mut self, selector: ResourceSelector, nodes: impl IntoIterator<Item = NamedNodeRef<'a>>) -> crate::Result<HashMap<NamedNode, ResourceDescription>> {
let pattern = Self::selector_to_sparql_pattern(selector, nodes);
let label_filter = self.language.filter("label");
let comment_filter = self.language.filter("comment");
let definition_filter = self.language.filter("definition");
@@ -266,9 +191,8 @@ SELECT ?label ?description WHERE {{
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?subject ?label ?description WHERE {{
{values}
?subject a ?class .
SELECT ?subject ?label ?description ?class WHERE {{
{pattern}
OPTIONAL {{
?subject rdfs:label ?label .
{label_filter}
@@ -298,8 +222,7 @@ SELECT ?class ?subject ?label ?description WHERE {{
.and_then(term_to_named_node)
.map(|node| node.clone());
if let Some(subject) = subject &&
let Some(class) = class {
if let Some(subject) = subject {
let label = solution
.get("label")
.and_then(term_as_str)
@@ -310,7 +233,11 @@ SELECT ?class ?subject ?label ?description WHERE {{
.and_then(term_as_str)
.map(String::from);
results.insert(subject, (class.clone(), ResourceDescription { label, description }));
results.insert(subject, ResourceDescription {
label,
description,
class: class.cloned(),
});
}
}
}