.
This commit is contained in:
@@ -10,5 +10,6 @@ gl-search.workspace = true
|
||||
oxigraph.workspace = true
|
||||
oxilangtag.workspace = true
|
||||
rayon.workspace = true
|
||||
spargebra.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
+74
-1
@@ -1,6 +1,12 @@
|
||||
use oxigraph::model::NamedNodeRef;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use oxigraph::model::{NamedNode, NamedNodeRef};
|
||||
use oxilangtag::LanguageTag;
|
||||
use gl_search::Schema;
|
||||
use crate::language::LanguageCondition;
|
||||
use crate::vocab;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Class {
|
||||
RdfProperty = 0,
|
||||
RdfsClass = 1,
|
||||
@@ -12,7 +18,33 @@ pub enum Class {
|
||||
Manifestation = 10007,
|
||||
}
|
||||
|
||||
impl Display for Class {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Class::RdfProperty => f.write_str("RDF Property"),
|
||||
Class::RdfsClass => f.write_str("RDFS Class"),
|
||||
Class::SkosConcept => f.write_str("SKOS Concept"),
|
||||
Class::Work => f.write_str("Work"),
|
||||
Class::Person => f.write_str("Person"),
|
||||
Class::CorporateBody => f.write_str("Corporate Body"),
|
||||
Class::Expression => f.write_str("Expression"),
|
||||
Class::Manifestation => f.write_str("Manifestation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Class {
|
||||
pub const ALL: &[Class] = &[
|
||||
Class::RdfProperty,
|
||||
Class::RdfsClass,
|
||||
Class::SkosConcept,
|
||||
Class::Work,
|
||||
Class::Person,
|
||||
Class::CorporateBody,
|
||||
Class::Expression,
|
||||
Class::Manifestation,
|
||||
];
|
||||
|
||||
pub fn to_named_node(&self) -> NamedNodeRef<'_> {
|
||||
match self {
|
||||
Class::RdfProperty => vocab::rdf::PROPERTY,
|
||||
@@ -40,4 +72,45 @@ impl Class {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fields(&self, language: LanguageTag<&str>) -> Vec<(gl_search::Field, String)> {
|
||||
match self {
|
||||
Class::RdfProperty => vec![
|
||||
(Schema::curie_field(), String::from("CURIE")),
|
||||
(Schema::field("label", Some(language.primary_language())), String::from("Label")),
|
||||
(Schema::field("definition", Some(language.primary_language())), String::from("Definition")),
|
||||
],
|
||||
Class::RdfsClass => vec![
|
||||
(Schema::curie_field(), String::from("CURIE")),
|
||||
(Schema::field("label", Some(language.primary_language())), String::from("Label")),
|
||||
(Schema::field("definition", Some(language.primary_language())), String::from("Definition")),
|
||||
],
|
||||
Class::SkosConcept => vec![
|
||||
(Schema::curie_field(), String::from("CURIE")),
|
||||
(Schema::field("label", Some(language.primary_language())), String::from("Label")),
|
||||
(Schema::field("definition", Some(language.primary_language())), String::from("Definition")),
|
||||
],
|
||||
Class::Work => vec![
|
||||
(Schema::curie_field(), String::from("CURIE")),
|
||||
(Schema::field("title", Some(language.primary_language())), String::from("Title")),
|
||||
],
|
||||
Class::Person => vec![
|
||||
(Schema::curie_field(), String::from("CURIE")),
|
||||
(Schema::field("given_name", Some(language.primary_language())), String::from("Given Name")),
|
||||
(Schema::field("surname", Some(language.primary_language())), String::from("Surname")),
|
||||
],
|
||||
Class::CorporateBody => vec![
|
||||
(Schema::curie_field(), String::from("CURIE")),
|
||||
(Schema::field("corporate_body", Some(language.primary_language())), String::from("Corporate Body")),
|
||||
],
|
||||
Class::Expression => vec![
|
||||
(Schema::curie_field(), String::from("CURIE")),
|
||||
(Schema::field("title", Some(language.primary_language())), String::from("Title")),
|
||||
],
|
||||
Class::Manifestation => vec![
|
||||
(Schema::curie_field(), String::from("CURIE")),
|
||||
(Schema::field("title", Some(language.primary_language())), String::from("Title")),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ pub type Result<R> = std::result::Result<R, Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
IriParse(#[from] oxigraph::model::IriParseError),
|
||||
|
||||
@@ -22,6 +25,9 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError),
|
||||
|
||||
#[error(transparent)]
|
||||
TonicTransport(#[from] gl_inference::tonic::transport::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
TonicStatus(#[from] gl_inference::tonic::Status),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use oxigraph::model::{NamedNode, NamedNodeRef, Term, TermRef};
|
||||
use oxigraph::model::{NamedNode, Term, TermRef};
|
||||
use oxigraph::model::vocab::xsd;
|
||||
use crate::vocab::rdf;
|
||||
|
||||
|
||||
+10
-1
@@ -74,6 +74,15 @@ impl<'a> Indexer<'a> {
|
||||
])
|
||||
}
|
||||
|
||||
fn manifestation(&self, graph: &Graph, subject: NamedNodeRef) -> HashMap<Field, OwnedValue> {
|
||||
HashMap::from_iter([
|
||||
(
|
||||
Schema::field("title", self.language.primary_language()),
|
||||
self.extract_string(graph, subject, vocab::rdamd::TITLE_OF_MANIFESTATION),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn graph(&self, graph: &Graph) -> crate::Result<Vec<HashMap<Field, OwnedValue>>> {
|
||||
let mut entities_and_types = HashSet::new();
|
||||
let triples = graph.triples_for_predicate(vocab::rdf::TYPE);
|
||||
@@ -95,7 +104,7 @@ impl<'a> Indexer<'a> {
|
||||
Class::Person => Some(self.person(graph, *subject)),
|
||||
Class::CorporateBody => Some(self.corporate_body(graph, *subject)),
|
||||
Class::Expression => Some(self.expression(graph, *subject)),
|
||||
Class::Manifestation => Some(self.person(graph, *subject)),
|
||||
Class::Manifestation => Some(self.manifestation(graph, *subject)),
|
||||
}.map(|mut document| {
|
||||
document.insert(Schema::discriminant_field(), OwnedValue::from(class as u64));
|
||||
document.insert(Schema::iri_field(), OwnedValue::Str(subject.as_str().to_string()));
|
||||
|
||||
+50
-3
@@ -1,6 +1,9 @@
|
||||
use oxigraph::model::TermRef;
|
||||
use oxigraph::model::{Literal, TermRef};
|
||||
use oxilangtag::LanguageTag;
|
||||
use std::sync::LazyLock;
|
||||
use spargebra::algebra::{Expression, Function, GraphPattern};
|
||||
use spargebra::term::{NamedNode, NamedNodePattern, TermPattern, TriplePattern, Variable};
|
||||
use tracing::debug;
|
||||
|
||||
pub const ENGLISH_PRIMARY: &str = "en";
|
||||
|
||||
@@ -10,7 +13,7 @@ pub static ENGLISH_OR_UNTAGGED: LazyLock<LanguageCondition> = LazyLock::new(|| {
|
||||
)
|
||||
});
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum LanguageCondition {
|
||||
ExactMatchOnly(LanguageTag<String>),
|
||||
ExactMatchOrUntagged(LanguageTag<String>),
|
||||
@@ -49,7 +52,51 @@ impl LanguageCondition {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_filter_expression(&self, var: &str) -> String {
|
||||
pub fn filter(conditions: impl IntoIterator<Item = (String, Self)>) -> String {
|
||||
let exact_expression = |language, variable| Expression::FunctionCall(
|
||||
Function::LangMatches, vec![
|
||||
Expression::FunctionCall(Function::Lang, vec![
|
||||
Expression::Variable(Variable::new_unchecked(variable))
|
||||
]),
|
||||
Expression::Literal(Literal::new_simple_literal(language))
|
||||
],
|
||||
);
|
||||
|
||||
let untagged_expression = |variable| Expression::Not(
|
||||
Box::new(Expression::FunctionCall(Function::HasLang, vec![
|
||||
Expression::Variable(Variable::new_unchecked(variable))
|
||||
]))
|
||||
);
|
||||
|
||||
let condition_to_expression = |variable, condition| match condition {
|
||||
LanguageCondition::ExactMatchOnly(language) =>
|
||||
Some(exact_expression(language.to_string(), variable)),
|
||||
LanguageCondition::ExactMatchOrUntagged(language) => {
|
||||
Some(Expression::Or(
|
||||
Box::new(exact_expression(language.to_string(), variable.clone())),
|
||||
Box::new(untagged_expression(variable)),
|
||||
))
|
||||
}
|
||||
LanguageCondition::UntaggedOnly => Some(untagged_expression(variable)),
|
||||
LanguageCondition::AnyOrNone => None,
|
||||
};
|
||||
|
||||
let mut iter = conditions.into_iter();
|
||||
let first_expression = iter.next()
|
||||
.and_then(|(variable, condition)| condition_to_expression(variable, condition));
|
||||
let remaining_expressions = iter.filter_map(|(variable, condition)| condition_to_expression(variable, condition))
|
||||
.collect::<Vec<_>>();
|
||||
let concatenated_expressions = if let Some(expression) = first_expression {
|
||||
if remaining_expressions.is_empty() {
|
||||
//Expression::
|
||||
}
|
||||
} else { String::default() }
|
||||
}
|
||||
|
||||
pub fn to_sparql_expression(&self, var: &str) -> String {
|
||||
let foo =
|
||||
debug!("Test: {foo}");
|
||||
|
||||
match self {
|
||||
LanguageCondition::ExactMatchOnly(language) => {
|
||||
format!(r#"FILTER(langMATCHES(LANG(?{var}), "{language}"))"#)
|
||||
|
||||
@@ -7,6 +7,7 @@ pub mod category;
|
||||
pub mod language;
|
||||
mod helpers;
|
||||
pub mod indexer;
|
||||
pub mod ontology;
|
||||
|
||||
pub use curie::{CurieHelper, PREFIXES};
|
||||
pub use error::{Error, Result};
|
||||
@@ -0,0 +1,214 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
|
||||
use oxigraph::model::{Graph, NamedNode, Triple, TripleRef};
|
||||
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput};
|
||||
use gl_inference::proto::ontology_client::OntologyClient;
|
||||
use gl_inference::proto::OntologyQueryRequest;
|
||||
use gl_inference::tonic::codegen::StdError;
|
||||
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};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ResourceDescription {
|
||||
pub label: Option<String>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum ReadOnlyEntity {
|
||||
Property(NamedNode),
|
||||
Class(NamedNode),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ontology {
|
||||
client: OntologyClient<Channel>,
|
||||
language: LanguageCondition,
|
||||
}
|
||||
|
||||
impl Debug for Ontology {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("ontology")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ontology {
|
||||
pub async fn new<D>(endpoint: D, language: LanguageCondition) -> crate::Result<Self>
|
||||
where
|
||||
D: TryInto<Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let client = OntologyClient::connect(endpoint).await?;
|
||||
Ok(Self {
|
||||
client,
|
||||
language,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run_inference(&mut self, graph: &Graph) -> crate::Result<Graph> {
|
||||
let mut output_buffer = Vec::new();
|
||||
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
||||
.for_writer(output_buffer);
|
||||
for triple in graph {
|
||||
serializer.serialize_triple(triple)?;
|
||||
}
|
||||
output_buffer = serializer.finish()?;
|
||||
let turtle = String::from_utf8_lossy(&output_buffer).to_string();
|
||||
|
||||
let mut request = OntologyQueryRequest::default();
|
||||
request.sparql_query = None;
|
||||
request.turtle = Some(turtle);
|
||||
request.base = None;
|
||||
request.inferences_only = true;
|
||||
|
||||
let response = self.client.query(request).await?;
|
||||
Ok(RdfParser::from_format(RdfFormat::Turtle)
|
||||
.for_slice(&response.get_ref().results)
|
||||
.filter_map(Result::ok)
|
||||
.map(Triple::from)
|
||||
.collect::<Graph>())
|
||||
}
|
||||
|
||||
pub async fn list_read_only(&mut self) -> crate::Result<HashSet<ReadOnlyEntity>> {
|
||||
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 xsd: <http://www.w3.org/2001/XMLSchema#>
|
||||
PREFIX gl: <https://graphofliberty.org/2026/04/ont/>
|
||||
|
||||
SELECT ?subject ?class WHERE {{
|
||||
VALUES ?class {{ rdf:Property rdfs:Class }}
|
||||
?subject a ?class ;
|
||||
gl:readOnly "true"^^xsd:boolean .
|
||||
}}"#));
|
||||
|
||||
let response = self.client.query(request).await?;
|
||||
let parser_output = QueryResultsParser::from_format(QueryResultsFormat::Json)
|
||||
.for_slice(&response.get_ref().results)?;
|
||||
|
||||
let mut results = HashSet::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());
|
||||
|
||||
let class = solution
|
||||
.get("class")
|
||||
.and_then(term_to_named_node)
|
||||
.map(|node| node.clone());
|
||||
|
||||
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),
|
||||
_ => continue,
|
||||
};
|
||||
results.insert(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn datatypes(&mut self) -> crate::Result<HashMap<NamedNode, ResourceDescription>> {
|
||||
let label_filter = self.language.to_filter_expression("label");
|
||||
let description_filter = self.language.to_filter_expression("description");
|
||||
|
||||
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 ?description WHERE {{
|
||||
?subject a rdfs:Datatype
|
||||
OPTIONAL {{ ?subject rdfs:label ?label }}
|
||||
{label_filter}
|
||||
|
||||
OPTIONAL {{ ?subject rdfs:comment ?comment }}
|
||||
OPTIONAL {{ ?subject skos:definition ?definition }}
|
||||
BIND(COALESCE(?definition, ?comment) AS ?description)
|
||||
{description_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(&mut self, subject: NamedNode) -> crate::Result<ResourceDescription> {
|
||||
let label_filter = self.language.to_filter_expression("label");
|
||||
let description_filter = self.language.to_filter_expression("description");
|
||||
|
||||
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 }}
|
||||
OPTIONAL {{ {subject} skos:definition ?definition }}
|
||||
BIND(COALESCE(?definition, ?comment) AS ?description)
|
||||
{description_filter}
|
||||
}}"#));
|
||||
|
||||
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!() }
|
||||
}
|
||||
}
|
||||
@@ -69,4 +69,11 @@ pub mod rdaed {
|
||||
|
||||
pub const TITLE_OF_EXPRESSION: NamedNodeRef =
|
||||
NamedNodeRef::new_unchecked("http://rdaregistry.info/Elements/e/datatype/P20312");
|
||||
}
|
||||
|
||||
pub mod rdamd {
|
||||
use oxigraph::model::NamedNodeRef;
|
||||
|
||||
pub const TITLE_OF_MANIFESTATION: NamedNodeRef =
|
||||
NamedNodeRef::new_unchecked("http://rdaregistry.info/Elements/m/datatype/P30335");
|
||||
}
|
||||
Reference in New Issue
Block a user