.
This commit is contained in:
@@ -30,4 +30,7 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
TonicStatus(#[from] gl_inference::tonic::Status),
|
TonicStatus(#[from] gl_inference::tonic::Status),
|
||||||
|
|
||||||
|
#[error("Ontology client is not connected")]
|
||||||
|
NotConnected,
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-62
@@ -1,16 +1,12 @@
|
|||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use oxigraph::model::{Graph, NamedNode, TermRef, NamedOrBlankNodeRef, NamedNodeRef};
|
use oxigraph::model::{Graph, NamedNode, TermRef, NamedOrBlankNodeRef, NamedNodeRef};
|
||||||
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput};
|
|
||||||
use rayon::iter::IntoParallelRefIterator;
|
use rayon::iter::IntoParallelRefIterator;
|
||||||
use gl_inference::tonic::transport::Channel;
|
|
||||||
use gl_inference::proto::ontology_client::OntologyClient;
|
|
||||||
use gl_inference::proto::OntologyQueryRequest;
|
|
||||||
use gl_search::{Field, OwnedValue, Schema};
|
use gl_search::{Field, OwnedValue, Schema};
|
||||||
use crate::class::Class;
|
use crate::class::Class;
|
||||||
use crate::{helpers, vocab, CurieHelper};
|
use crate::{helpers, vocab, CurieHelper};
|
||||||
use crate::helpers::{term_as_str, term_to_named_node};
|
|
||||||
use crate::language::LanguageCondition;
|
use crate::language::LanguageCondition;
|
||||||
|
use crate::ontology::ResourceDescription;
|
||||||
|
|
||||||
pub struct Indexer<'a> {
|
pub struct Indexer<'a> {
|
||||||
language: LanguageCondition,
|
language: LanguageCondition,
|
||||||
@@ -83,7 +79,7 @@ impl<'a> Indexer<'a> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn graph(&self, graph: &Graph) -> crate::Result<Vec<HashMap<Field, OwnedValue>>> {
|
pub fn graph(&self, graph: &Graph) -> Vec<HashMap<Field, OwnedValue>> {
|
||||||
let mut entities_and_types = HashSet::new();
|
let mut entities_and_types = HashSet::new();
|
||||||
let triples = graph.triples_for_predicate(vocab::rdf::TYPE);
|
let triples = graph.triples_for_predicate(vocab::rdf::TYPE);
|
||||||
for triple in triples {
|
for triple in triples {
|
||||||
@@ -119,85 +115,42 @@ impl<'a> Indexer<'a> {
|
|||||||
})
|
})
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
Ok(results)
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ontology(&self, client: &mut OntologyClient<Channel>) -> crate::Result<Vec<HashMap<Field, OwnedValue>>> {
|
pub fn ontology(&self, entities: HashMap<NamedNode, (NamedNode, ResourceDescription)>) -> Vec<HashMap<Field, OwnedValue>> {
|
||||||
let filter = LanguageCondition::filter([
|
let label_field = Schema::field("label", self.language.primary_language());
|
||||||
(String::from("label"), self.language.clone()),
|
let definition_field = Schema::field("definition", self.language.primary_language());
|
||||||
(String::from("description"), self.language.clone()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let mut request = OntologyQueryRequest::default();
|
entities.iter()
|
||||||
request.sparql_query = Some(format!(r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
.map(|(subject, (class, description))| {
|
||||||
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 ?class {{ rdf:Property rdfs:Class skos:Concept }}
|
|
||||||
?subject a ?class ;
|
|
||||||
rdfs:label ?label .
|
|
||||||
|
|
||||||
OPTIONAL {{ ?subject rdfs:comment ?comment }}
|
|
||||||
OPTIONAL {{ ?subject skos:definition ?definition }}
|
|
||||||
BIND(COALESCE(?definition, ?comment) AS ?description)
|
|
||||||
{filter}
|
|
||||||
}}"#));
|
|
||||||
|
|
||||||
let response = client.query(request).await?;
|
|
||||||
let parser_output = QueryResultsParser::from_format(QueryResultsFormat::Json)
|
|
||||||
.for_slice(&response.get_ref().results)?;
|
|
||||||
|
|
||||||
let mut results = Vec::new();
|
|
||||||
if let SliceQueryResultsParserOutput::Solutions(solutions) = parser_output {
|
|
||||||
let primary_language = self.language.primary_language();
|
|
||||||
let label_field = Schema::field("label", primary_language);
|
|
||||||
let definition_field = Schema::field("definition", primary_language);
|
|
||||||
for solution in solutions.filter_map(Result::ok) {
|
|
||||||
let mut document = HashMap::with_capacity(4);
|
let mut document = HashMap::with_capacity(4);
|
||||||
|
|
||||||
let discriminant = solution
|
let discriminant = Class::try_from_named_node(class)
|
||||||
.get("class")
|
|
||||||
.and_then(term_to_named_node)
|
|
||||||
.and_then(Class::try_from_named_node)
|
|
||||||
.map(|doc_type| doc_type as u64)
|
.map(|doc_type| doc_type as u64)
|
||||||
.map(OwnedValue::U64)
|
.map(OwnedValue::U64)
|
||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
document.insert(Schema::discriminant_field(), discriminant);
|
document.insert(Schema::discriminant_field(), discriminant);
|
||||||
|
|
||||||
let subject_str = solution
|
let curie = self.curie_helper.abbreviate(None, subject.as_str())
|
||||||
.get("subject")
|
|
||||||
.and_then(term_to_named_node)
|
|
||||||
.map(NamedNode::as_str);
|
|
||||||
|
|
||||||
let curie = subject_str
|
|
||||||
.and_then(|subject| self.curie_helper.abbreviate(None, subject))
|
|
||||||
.map(OwnedValue::Str)
|
.map(OwnedValue::Str)
|
||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
document.insert(Schema::curie_field(), curie);
|
document.insert(Schema::curie_field(), curie);
|
||||||
|
|
||||||
let subject = subject_str
|
let subject = OwnedValue::from(subject.as_str());
|
||||||
.map(OwnedValue::from)
|
|
||||||
.unwrap_or(OwnedValue::Null);
|
|
||||||
document.insert(Schema::iri_field(), subject);
|
document.insert(Schema::iri_field(), subject);
|
||||||
|
|
||||||
let label = solution
|
let label = description.label.clone()
|
||||||
.get("label")
|
|
||||||
.and_then(term_as_str)
|
|
||||||
.map(OwnedValue::from)
|
.map(OwnedValue::from)
|
||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
document.insert(label_field, label);
|
document.insert(label_field, label);
|
||||||
|
|
||||||
let definition = solution
|
let definition = description.description.clone()
|
||||||
.get("description")
|
|
||||||
.and_then(term_as_str)
|
|
||||||
.map(OwnedValue::from)
|
.map(OwnedValue::from)
|
||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
document.insert(definition_field, definition);
|
document.insert(definition_field, definition);
|
||||||
|
|
||||||
results.push(document);
|
document
|
||||||
}
|
}).collect()
|
||||||
}
|
|
||||||
Ok(results)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+18
-30
@@ -53,47 +53,35 @@ impl LanguageCondition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter(conditions: impl IntoIterator<Item = (String, Self)>) -> String {
|
pub fn filter(&self, variable: impl Into<String>) -> String {
|
||||||
let exact_expression = |variable: String, language| Expression::FunctionCall(
|
let variable = variable.into();
|
||||||
|
|
||||||
|
let exact = |language: &LanguageTag<String>| Expression::FunctionCall(
|
||||||
Function::LangMatches, vec![
|
Function::LangMatches, vec![
|
||||||
Expression::FunctionCall(Function::Lang, vec![
|
Expression::FunctionCall(Function::Lang, vec![
|
||||||
Expression::Variable(Variable::new_unchecked(variable))
|
Expression::Variable(Variable::new_unchecked(&variable))
|
||||||
]),
|
]),
|
||||||
Expression::Literal(Literal::new_simple_literal(language))
|
Expression::Literal(Literal::new_simple_literal(language.to_string()))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
let untagged_expression = |variable| Expression::Not(
|
let untagged = Expression::Not(
|
||||||
Box::new(Expression::FunctionCall(Function::HasLang, vec![
|
Box::new(Expression::FunctionCall(Function::HasLang, vec![
|
||||||
Expression::Variable(Variable::new_unchecked(variable))
|
Expression::Variable(Variable::new_unchecked(&variable))
|
||||||
]))
|
]))
|
||||||
);
|
);
|
||||||
|
|
||||||
let condition_to_expression = |variable, condition| match condition {
|
match self {
|
||||||
LanguageCondition::ExactMatchOnly(language) =>
|
Self::ExactMatchOnly(language) => Some(exact(language)),
|
||||||
Some(exact_expression(variable, language.to_string())),
|
Self::ExactMatchOrUntagged(language) => Some(Expression::Or(
|
||||||
LanguageCondition::ExactMatchOrUntagged(language) => {
|
Box::new(exact(language)),
|
||||||
Some(Expression::Or(
|
Box::new(untagged),
|
||||||
Box::new(exact_expression(variable.clone(), language.to_string())),
|
)),
|
||||||
Box::new(untagged_expression(variable)),
|
Self::UntaggedOnly => Some(untagged),
|
||||||
))
|
Self::AnyOrNone => None,
|
||||||
}
|
}.map(|expr| GraphPattern::Filter {
|
||||||
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));
|
|
||||||
if let Some(expression) = first_expression {
|
|
||||||
let expr = iter.filter_map(|(variable, condition)| condition_to_expression(variable, condition))
|
|
||||||
.fold(expression, |acc, next_exp| Expression::Or(Box::new(acc), Box::new(next_exp)));
|
|
||||||
GraphPattern::Filter {
|
|
||||||
expr,
|
expr,
|
||||||
inner: Box::new(GraphPattern::Bgp { patterns: vec![] }),
|
inner: Box::new(GraphPattern::Bgp { patterns: vec![] }),
|
||||||
}.to_string()
|
}.to_string()).unwrap_or_default()
|
||||||
} else {
|
|
||||||
String::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+141
-38
@@ -1,12 +1,13 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::str::FromStr;
|
||||||
use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
|
use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
|
||||||
use oxigraph::model::{Graph, NamedNode, Triple, TripleRef};
|
use oxigraph::model::{Graph, NamedNode, NamedNodeRef, Triple};
|
||||||
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput};
|
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput};
|
||||||
use tracing::debug;
|
use spargebra::algebra::GraphPattern;
|
||||||
|
use spargebra::term::{GroundTerm, Variable};
|
||||||
use gl_inference::proto::ontology_client::OntologyClient;
|
use gl_inference::proto::ontology_client::OntologyClient;
|
||||||
use gl_inference::proto::OntologyQueryRequest;
|
use gl_inference::proto::OntologyQueryRequest;
|
||||||
use gl_inference::tonic::codegen::StdError;
|
|
||||||
use gl_inference::tonic::transport::{Channel, Endpoint};
|
use gl_inference::tonic::transport::{Channel, Endpoint};
|
||||||
use crate::helpers::{term_as_str, term_to_named_node};
|
use crate::helpers::{term_as_str, term_to_named_node};
|
||||||
use crate::language::LanguageCondition;
|
use crate::language::LanguageCondition;
|
||||||
@@ -24,31 +25,51 @@ pub enum ReadOnlyEntity {
|
|||||||
Class(NamedNode),
|
Class(NamedNode),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct OntologyBuilder {
|
||||||
|
endpoint: Endpoint,
|
||||||
|
language: LanguageCondition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OntologyBuilder {
|
||||||
|
pub fn from_string(endpoint: &str, language: LanguageCondition) -> crate::Result<Self> {
|
||||||
|
let endpoint = Endpoint::from_str(endpoint)?
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(10))
|
||||||
|
.tcp_keepalive(Some(std::time::Duration::from_secs(30)))
|
||||||
|
.http2_keep_alive_interval(std::time::Duration::from_secs(15))
|
||||||
|
.keep_alive_timeout(std::time::Duration::from_secs(20))
|
||||||
|
.keep_alive_while_idle(true);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
endpoint,
|
||||||
|
language,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect(self) -> crate::Result<ConnectedOntology> {
|
||||||
|
let client = OntologyClient::connect(self.endpoint)
|
||||||
|
.await?
|
||||||
|
.max_decoding_message_size(1024 * 1024 * 1024);
|
||||||
|
|
||||||
|
Ok(ConnectedOntology {
|
||||||
|
client,
|
||||||
|
language: self.language,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Ontology {
|
pub struct ConnectedOntology {
|
||||||
client: OntologyClient<Channel>,
|
client: OntologyClient<Channel>,
|
||||||
language: LanguageCondition,
|
language: LanguageCondition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Ontology {
|
impl Debug for ConnectedOntology {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str("ontology")
|
f.write_str("ontology")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ontology {
|
impl ConnectedOntology {
|
||||||
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> {
|
pub async fn run_inference(&mut self, graph: &Graph) -> crate::Result<Graph> {
|
||||||
let mut output_buffer = Vec::new();
|
let mut output_buffer = Vec::new();
|
||||||
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
||||||
@@ -118,23 +139,24 @@ SELECT ?subject ?class WHERE {{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn datatypes(&mut self) -> crate::Result<HashMap<NamedNode, ResourceDescription>> {
|
pub async fn datatypes(&mut self) -> crate::Result<HashMap<NamedNode, ResourceDescription>> {
|
||||||
let filter = LanguageCondition::filter([
|
let label_filter = self.language.filter("label");
|
||||||
(String::from("label"), self.language.clone()),
|
let comment_filter = self.language.filter("comment");
|
||||||
(String::from("description"), self.language.clone()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let mut request = OntologyQueryRequest::default();
|
let mut request = OntologyQueryRequest::default();
|
||||||
request.sparql_query = Some(format!(r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
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 rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||||
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
|
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
|
||||||
|
|
||||||
SELECT ?subject ?label ?description WHERE {{
|
SELECT ?subject ?label ?comment WHERE {{
|
||||||
|
OPTIONAL {{
|
||||||
?subject a rdfs:Datatype
|
?subject a rdfs:Datatype
|
||||||
OPTIONAL {{ ?subject rdfs:label ?label }}
|
}} OPTIONAL {{
|
||||||
OPTIONAL {{ ?subject rdfs:comment ?comment }}
|
?subject rdfs:label ?label
|
||||||
OPTIONAL {{ ?subject skos:definition ?definition }}
|
{label_filter}
|
||||||
BIND(COALESCE(?definition, ?comment) AS ?description)
|
}} OPTIONAL {{
|
||||||
{filter}
|
?subject rdfs:comment ?comment
|
||||||
|
{comment_filter}
|
||||||
|
}}
|
||||||
}}"#));
|
}}"#));
|
||||||
|
|
||||||
let response = self.client.query(request).await?;
|
let response = self.client.query(request).await?;
|
||||||
@@ -167,12 +189,10 @@ SELECT ?subject ?label ?description WHERE {{
|
|||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resource_description(&mut self, subject: NamedNode) -> crate::Result<ResourceDescription> {
|
pub async fn resource_description_from_subject(&mut self, subject: NamedNode) -> crate::Result<ResourceDescription> {
|
||||||
let filter = LanguageCondition::filter([
|
let label_filter = self.language.filter("label");
|
||||||
(String::from("label"), self.language.clone()),
|
let comment_filter = self.language.filter("comment");
|
||||||
(String::from("description"), self.language.clone()),
|
let definition_filter = self.language.filter("definition");
|
||||||
]);
|
|
||||||
debug!("Filter: {filter}");
|
|
||||||
|
|
||||||
let mut request = OntologyQueryRequest::default();
|
let mut request = OntologyQueryRequest::default();
|
||||||
request.sparql_query = Some(format!(r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
request.sparql_query = Some(format!(r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
||||||
@@ -180,13 +200,21 @@ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|||||||
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
|
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
|
||||||
|
|
||||||
SELECT ?label ?description WHERE {{
|
SELECT ?label ?description WHERE {{
|
||||||
OPTIONAL {{ {subject} rdfs:label ?label }}
|
OPTIONAL {{
|
||||||
OPTIONAL {{ {subject} rdfs:comment ?comment }}
|
{subject} rdfs:label ?label
|
||||||
OPTIONAL {{ {subject} skos:definition ?definition }}
|
{label_filter}
|
||||||
|
}} OPTIONAL {{
|
||||||
|
{subject} rdfs:comment ?comment
|
||||||
|
{comment_filter}
|
||||||
|
}} OPTIONAL {{
|
||||||
|
{subject} skos:definition ?definition
|
||||||
|
{definition_filter}
|
||||||
|
}}
|
||||||
BIND(COALESCE(?definition, ?comment) AS ?description)
|
BIND(COALESCE(?definition, ?comment) AS ?description)
|
||||||
{filter}
|
|
||||||
}}"#));
|
}}"#));
|
||||||
|
|
||||||
|
let foo = request.sparql_query.clone().unwrap();
|
||||||
|
|
||||||
let response = self.client.query(request).await?;
|
let response = self.client.query(request).await?;
|
||||||
let parser_output = QueryResultsParser::from_format(QueryResultsFormat::Json)
|
let parser_output = QueryResultsParser::from_format(QueryResultsFormat::Json)
|
||||||
.for_slice(&response.get_ref().results)?;
|
.for_slice(&response.get_ref().results)?;
|
||||||
@@ -213,4 +241,79 @@ SELECT ?label ?description WHERE {{
|
|||||||
)
|
)
|
||||||
} else { unreachable!() }
|
} else { unreachable!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn named_nodes_to_values_expression<'a>(variable: &str, 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)],
|
||||||
|
bindings,
|
||||||
|
}.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);
|
||||||
|
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 ?class ?subject ?label ?description WHERE {{
|
||||||
|
{values}
|
||||||
|
?subject a ?class .
|
||||||
|
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 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 class = solution
|
||||||
|
.get("class")
|
||||||
|
.and_then(term_to_named_node);
|
||||||
|
|
||||||
|
let subject = solution
|
||||||
|
.get("subject")
|
||||||
|
.and_then(term_to_named_node)
|
||||||
|
.map(|node| node.clone());
|
||||||
|
|
||||||
|
if let Some(subject) = subject &&
|
||||||
|
let Some(class) = class {
|
||||||
|
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, (class.clone(), ResourceDescription { label, description }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+12
-7
@@ -3,14 +3,14 @@ use crate::navigator::Navigator;
|
|||||||
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
||||||
use crate::widget::iri_input::iri_input;
|
use crate::widget::iri_input::iri_input;
|
||||||
use crate::widget::navigation_area::navigation_area;
|
use crate::widget::navigation_area::navigation_area;
|
||||||
use gl_graph::CurieHelper;
|
use gl_graph::{language, CurieHelper};
|
||||||
use gl_search::{Schema, SearchIndex};
|
use gl_search::{Schema, SearchIndex};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use iced::alignment::Horizontal;
|
use iced::alignment::Horizontal;
|
||||||
use iced::keyboard::{Event, key};
|
use iced::keyboard::{Event, key};
|
||||||
use iced::widget::button::Style;
|
use iced::widget::button::Style;
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, center, column, combo_box, container, grid, mouse_area, opaque, operation, pick_list,
|
button, center, column, combo_box, container, mouse_area, opaque, operation, pick_list,
|
||||||
row, scrollable, space, stack, table, text, text_input, toggler,
|
row, scrollable, space, stack, table, text, text_input, toggler,
|
||||||
};
|
};
|
||||||
use iced::window::Settings;
|
use iced::window::Settings;
|
||||||
@@ -23,10 +23,10 @@ use ldp::reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
|||||||
use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions};
|
use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions};
|
||||||
use oxigraph::io::RdfFormat;
|
use oxigraph::io::RdfFormat;
|
||||||
use oxigraph::model::vocab::{rdf, rdfs};
|
use oxigraph::model::vocab::{rdf, rdfs};
|
||||||
use oxigraph::model::{BaseDirection, Dataset, Graph, NamedNode, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef};
|
use oxigraph::model::{BaseDirection, Graph, NamedNode, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
use gl_graph::class::Class;
|
use gl_graph::class::Class;
|
||||||
use gl_graph::ontology::{Ontology, ReadOnlyEntity, ResourceDescription};
|
use gl_graph::ontology::{ConnectedOntology, OntologyBuilder, ReadOnlyEntity, ResourceDescription};
|
||||||
use gl_search::tantivy::schema::Value;
|
use gl_search::tantivy::schema::Value;
|
||||||
use crate::tasks;
|
use crate::tasks;
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ use crate::tasks;
|
|||||||
pub(crate) enum Message {
|
pub(crate) enum Message {
|
||||||
None,
|
None,
|
||||||
ConnectToOntologyService(String),
|
ConnectToOntologyService(String),
|
||||||
ConnectedToOntologyService(Ontology),
|
ConnectedToOntologyService(ConnectedOntology),
|
||||||
CacheDatatypes(HashMap<NamedNode, ResourceDescription>),
|
CacheDatatypes(HashMap<NamedNode, ResourceDescription>),
|
||||||
CacheReadOnlyEntities(HashSet<ReadOnlyEntity>),
|
CacheReadOnlyEntities(HashSet<ReadOnlyEntity>),
|
||||||
PopulateCaches,
|
PopulateCaches,
|
||||||
@@ -106,7 +106,7 @@ struct SearchState {
|
|||||||
pub(crate) struct Publisher {
|
pub(crate) struct Publisher {
|
||||||
http_client: ClientWithMiddleware,
|
http_client: ClientWithMiddleware,
|
||||||
curie_helper: CurieHelper,
|
curie_helper: CurieHelper,
|
||||||
ontology: Option<Ontology>,
|
ontology: Option<ConnectedOntology>,
|
||||||
read_only_entities: HashSet<ReadOnlyEntity>,
|
read_only_entities: HashSet<ReadOnlyEntity>,
|
||||||
abbreviated_datatypes: Vec<String>,
|
abbreviated_datatypes: Vec<String>,
|
||||||
resource_descriptions: HashMap<NamedNode, ResourceDescription>,
|
resource_descriptions: HashMap<NamedNode, ResourceDescription>,
|
||||||
@@ -192,7 +192,10 @@ impl Publisher {
|
|||||||
|
|
||||||
match message {
|
match message {
|
||||||
Message::ConnectToOntologyService(endpoint) => {
|
Message::ConnectToOntologyService(endpoint) => {
|
||||||
task = tasks::connect_to_ontology_service(endpoint)
|
let builder = OntologyBuilder::from_string(&endpoint, language::ENGLISH_OR_UNTAGGED.clone())
|
||||||
|
.expect("Unable to parse endpoint of ontology service");
|
||||||
|
|
||||||
|
task = tasks::connect_to_ontology_service(builder)
|
||||||
.chain(Task::done(Message::PopulateCaches));
|
.chain(Task::done(Message::PopulateCaches));
|
||||||
}
|
}
|
||||||
Message::ConnectedToOntologyService(ontology) => {
|
Message::ConnectedToOntologyService(ontology) => {
|
||||||
@@ -217,10 +220,12 @@ impl Publisher {
|
|||||||
self.read_only_entities = entities;
|
self.read_only_entities = entities;
|
||||||
}
|
}
|
||||||
Message::LookupResourceDescription(node) => {
|
Message::LookupResourceDescription(node) => {
|
||||||
|
if !self.resource_descriptions.contains_key(&node) {
|
||||||
if let Some(ontology) = &mut self.ontology {
|
if let Some(ontology) = &mut self.ontology {
|
||||||
task = tasks::lookup_resource_description(ontology.clone(), node)
|
task = tasks::lookup_resource_description(ontology.clone(), node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Message::CacheResourceDescription(node, description) => {
|
Message::CacheResourceDescription(node, description) => {
|
||||||
self.resource_descriptions.insert(node, description);
|
self.resource_descriptions.insert(node, description);
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-31
@@ -26,7 +26,8 @@ use tracing_subscriber::util::SubscriberInitExt;
|
|||||||
use tracing_subscriber::{EnvFilter, fmt};
|
use tracing_subscriber::{EnvFilter, fmt};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use gl_graph::indexer::Indexer;
|
use gl_graph::indexer::Indexer;
|
||||||
use gl_graph::{language, CurieHelper};
|
use gl_graph::{language, CurieHelper, vocab};
|
||||||
|
use gl_graph::ontology::OntologyBuilder;
|
||||||
use gl_inference::proto::ontology_client::OntologyClient;
|
use gl_inference::proto::ontology_client::OntologyClient;
|
||||||
use gl_inference::proto::OntologyQueryRequest;
|
use gl_inference::proto::OntologyQueryRequest;
|
||||||
|
|
||||||
@@ -66,7 +67,11 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
runtime.block_on(async {
|
runtime.block_on(async {
|
||||||
let mut client = OntologyClient::connect("http://[::1]:3000").await.unwrap();
|
let mut client = OntologyClient::connect("http://[::1]:3000")
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.max_decoding_message_size(1024 * 1024 * 1024);
|
||||||
|
|
||||||
let response = client.query(request).await.unwrap();
|
let response = client.query(request).await.unwrap();
|
||||||
println!("{}", response.get_ref().results);
|
println!("{}", response.get_ref().results);
|
||||||
});
|
});
|
||||||
@@ -103,8 +108,15 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
let curie_helper = CurieHelper::new(gl_graph::PREFIXES.clone());
|
let curie_helper = CurieHelper::new(gl_graph::PREFIXES.clone());
|
||||||
let indexer = Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper);
|
let indexer = Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper);
|
||||||
|
|
||||||
let mut client = OntologyClient::connect("http://[::1]:3000").await?;
|
let mut client = OntologyBuilder::from_string("http://[::1]:3000", language::ENGLISH_OR_UNTAGGED.clone())?
|
||||||
let documents = indexer.ontology(&mut client).await?;
|
.connect()
|
||||||
|
.await?;
|
||||||
|
let resource_descriptions = client.resource_descriptions_from_classes([
|
||||||
|
vocab::rdf::PROPERTY,
|
||||||
|
vocab::rdfs::CLASS,
|
||||||
|
vocab::skos::CONCEPT,
|
||||||
|
]).await?;
|
||||||
|
let documents = indexer.ontology(resource_descriptions);
|
||||||
debug_span!("Index Ontology", documents = field::Empty).in_scope(|| {
|
debug_span!("Index Ontology", documents = field::Empty).in_scope(|| {
|
||||||
for document in documents {
|
for document in documents {
|
||||||
writer.add_document(document).unwrap();
|
writer.add_document(document).unwrap();
|
||||||
@@ -122,13 +134,16 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
let starting_url = Url::parse("http://fedora.quill.lan/rest/")?;
|
let starting_url = Url::parse("http://fedora.quill.lan/rest/")?;
|
||||||
let mut dataset = Dataset::new();
|
let mut graph = Graph::new();
|
||||||
let mut traversal = Traverse::new(http_client, starting_url, None);
|
let mut traversal = Traverse::new(http_client, starting_url, None);
|
||||||
let mut rdf_source_count = 0usize;
|
let mut rdf_source_count = 0usize;
|
||||||
while let Some(result) = traversal.next().await {
|
while let Some(result) = traversal.next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(rdf_source) => {
|
Ok(rdf_source) => {
|
||||||
dataset.extend(rdf_source.dataset());
|
let triples = rdf_source.dataset()
|
||||||
|
.iter()
|
||||||
|
.map(|quad| TripleRef::from(quad));
|
||||||
|
graph.extend(triples);
|
||||||
rdf_source_count += 1;
|
rdf_source_count += 1;
|
||||||
},
|
},
|
||||||
Err(err) => error!(?err),
|
Err(err) => error!(?err),
|
||||||
@@ -140,32 +155,12 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
let curie_helper = CurieHelper::new(gl_graph::PREFIXES.clone());
|
let curie_helper = CurieHelper::new(gl_graph::PREFIXES.clone());
|
||||||
let indexer = Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper);
|
let indexer = Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper);
|
||||||
|
|
||||||
let mut client = OntologyClient::connect("http://[::1]:3000").await?;
|
let mut client = OntologyBuilder::from_string("http://[::1]:3000", language::ENGLISH_OR_UNTAGGED.clone())?
|
||||||
let mut request = OntologyQueryRequest::default();
|
.connect()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut output_buffer = Vec::new();
|
let graph_with_inferences = client.run_inference(&graph).await?;
|
||||||
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
for document in indexer.graph(&graph_with_inferences) {
|
||||||
.for_writer(output_buffer);
|
|
||||||
for quad in &dataset {
|
|
||||||
serializer.serialize_triple(TripleRef::from(quad))?;
|
|
||||||
}
|
|
||||||
output_buffer = serializer.finish()?;
|
|
||||||
let turtle = String::from_utf8_lossy(&output_buffer).to_string();
|
|
||||||
|
|
||||||
request.sparql_query = None;
|
|
||||||
request.turtle = Some(turtle);
|
|
||||||
request.prefixes = HashMap::new();
|
|
||||||
request.base = None;
|
|
||||||
request.inferences_only = false;
|
|
||||||
|
|
||||||
let response = client.query(request).await?;
|
|
||||||
let graph_with_inferences = RdfParser::from_format(RdfFormat::Turtle)
|
|
||||||
.for_slice(&response.get_ref().results)
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.map(Triple::from)
|
|
||||||
.collect::<Graph>();
|
|
||||||
|
|
||||||
for document in indexer.graph(&graph_with_inferences)? {
|
|
||||||
writer.add_document(document)?;
|
writer.add_document(document)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-12
@@ -1,22 +1,20 @@
|
|||||||
use iced::Task;
|
use iced::Task;
|
||||||
use oxigraph::model::{Graph, NamedNode};
|
use oxigraph::model::{Graph, NamedNode};
|
||||||
use gl_graph::language;
|
use gl_graph::ontology::{ConnectedOntology, OntologyBuilder};
|
||||||
use gl_graph::ontology::Ontology;
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
|
|
||||||
pub(crate) fn connect_to_ontology_service(endpoint: String) -> Task<Message> {
|
pub(crate) fn connect_to_ontology_service(builder: OntologyBuilder) -> Task<Message> {
|
||||||
Task::future(Ontology::new(endpoint, (&*language::ENGLISH_OR_UNTAGGED).clone()))
|
Task::perform(builder.connect(), |result| {
|
||||||
.then(|result| {
|
|
||||||
match result {
|
match result {
|
||||||
Ok(ontology) => Task::done(Message::ConnectedToOntologyService(ontology)),
|
Ok(ontology) => Message::ConnectedToOntologyService(ontology),
|
||||||
Err(err) => Task::done(Message::ShowError(err.to_string()))
|
Err(err) => Message::ShowError(err.to_string())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lookup_resource_description(mut ontology: Ontology, subject: NamedNode) -> Task<Message> {
|
pub(crate) fn lookup_resource_description(mut ontology: ConnectedOntology, subject: NamedNode) -> Task<Message> {
|
||||||
let subject_clone = subject.clone();
|
let subject_clone = subject.clone();
|
||||||
Task::perform(async move { ontology.resource_description(subject_clone).await }, |result| {
|
Task::perform(async move { ontology.resource_description_from_subject(subject_clone).await }, |result| {
|
||||||
match result {
|
match result {
|
||||||
Ok(description) => Message::CacheResourceDescription(subject, description),
|
Ok(description) => Message::CacheResourceDescription(subject, description),
|
||||||
Err(err) => Message::ShowError(err.to_string()),
|
Err(err) => Message::ShowError(err.to_string()),
|
||||||
@@ -24,7 +22,7 @@ pub(crate) fn lookup_resource_description(mut ontology: Ontology, subject: Named
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn list_datatypes(mut ontology: Ontology) -> Task<Message> {
|
pub(crate) fn list_datatypes(mut ontology: ConnectedOntology) -> Task<Message> {
|
||||||
Task::perform(async move { ontology.datatypes().await }, |result| {
|
Task::perform(async move { ontology.datatypes().await }, |result| {
|
||||||
match result {
|
match result {
|
||||||
Ok(datatypes) => Message::CacheDatatypes(datatypes),
|
Ok(datatypes) => Message::CacheDatatypes(datatypes),
|
||||||
@@ -33,7 +31,7 @@ pub(crate) fn list_datatypes(mut ontology: Ontology) -> Task<Message> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn list_read_only_entities(mut ontology: Ontology) -> Task<Message> {
|
pub(crate) fn list_read_only_entities(mut ontology: ConnectedOntology) -> Task<Message> {
|
||||||
Task::perform(async move { ontology.list_read_only().await }, |result| {
|
Task::perform(async move { ontology.list_read_only().await }, |result| {
|
||||||
match result {
|
match result {
|
||||||
Ok(entities) => Message::CacheReadOnlyEntities(entities),
|
Ok(entities) => Message::CacheReadOnlyEntities(entities),
|
||||||
@@ -42,7 +40,7 @@ pub(crate) fn list_read_only_entities(mut ontology: Ontology) -> Task<Message> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run_inference(mut ontology: Ontology, graph: Graph) -> Task<Message> {
|
pub(crate) fn run_inference(mut ontology: ConnectedOntology, graph: Graph) -> Task<Message> {
|
||||||
Task::perform(async move { ontology.run_inference(&graph).await }, |result| {
|
Task::perform(async move { ontology.run_inference(&graph).await }, |result| {
|
||||||
match result {
|
match result {
|
||||||
Ok(inferences) => Message::SetInferredTriples(inferences),
|
Ok(inferences) => Message::SetInferredTriples(inferences),
|
||||||
|
|||||||
Reference in New Issue
Block a user