.
This commit is contained in:
+13
-8
@@ -1,11 +1,12 @@
|
|||||||
|
use oxigraph::model::{GraphNameRef, NamedNodeRef};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use oxigraph::model::{GraphNameRef, NamedNodeRef};
|
|
||||||
|
|
||||||
const ONTOLOGY_PREFIX: &str = "https://graphofliberty.org/2026/04/ont/";
|
const ONTOLOGY_PREFIX: &str = "https://graphofliberty.org/2026/04/ont/";
|
||||||
|
|
||||||
pub static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
pub static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
||||||
BTreeMap::from_iter([
|
BTreeMap::from_iter(
|
||||||
|
[
|
||||||
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
|
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
|
||||||
("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
|
("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
|
||||||
("owl", "http://www.w3.org/2002/07/owl#"),
|
("owl", "http://www.w3.org/2002/07/owl#"),
|
||||||
@@ -43,7 +44,9 @@ pub static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
|||||||
("schema", "https://schema.org/"),
|
("schema", "https://schema.org/"),
|
||||||
("gl", "http://fedora.quill.lan/rest/"),
|
("gl", "http://fedora.quill.lan/rest/"),
|
||||||
("glo", ONTOLOGY_PREFIX),
|
("glo", ONTOLOGY_PREFIX),
|
||||||
].map(|(k, v)| (k.to_string(), v.to_string())))
|
]
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string())),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -53,13 +56,13 @@ pub struct CurieHelper {
|
|||||||
|
|
||||||
impl CurieHelper {
|
impl CurieHelper {
|
||||||
pub fn new(prefixes: BTreeMap<String, String>) -> Self {
|
pub fn new(prefixes: BTreeMap<String, String>) -> Self {
|
||||||
Self {
|
Self { prefixes }
|
||||||
prefixes,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abbreviate(&self, base: Option<&str>, iri: &str) -> Option<String> {
|
pub fn abbreviate(&self, base: Option<&str>, iri: &str) -> Option<String> {
|
||||||
if let Some(base) = base && let Some(local_name) = iri.strip_prefix(base) {
|
if let Some(base) = base
|
||||||
|
&& let Some(local_name) = iri.strip_prefix(base)
|
||||||
|
{
|
||||||
return Some(format!(":{local_name}"));
|
return Some(format!(":{local_name}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +77,9 @@ impl CurieHelper {
|
|||||||
pub fn expand(&self, base: Option<&str>, abbreviated_iri: &str) -> Option<String> {
|
pub fn expand(&self, base: Option<&str>, abbreviated_iri: &str) -> Option<String> {
|
||||||
let (prefix, name) = abbreviated_iri.split_once(':')?;
|
let (prefix, name) = abbreviated_iri.split_once(':')?;
|
||||||
|
|
||||||
if prefix == "" && let Some(base) = base {
|
if prefix == ""
|
||||||
|
&& let Some(base) = base
|
||||||
|
{
|
||||||
Some(format!("{base}{name}"))
|
Some(format!("{base}{name}"))
|
||||||
} else {
|
} else {
|
||||||
self.prefixes
|
self.prefixes
|
||||||
|
|||||||
+1
-1
@@ -17,5 +17,5 @@ pub enum Error {
|
|||||||
QueryEvaluation(#[from] oxigraph::sparql::QueryEvaluationError),
|
QueryEvaluation(#[from] oxigraph::sparql::QueryEvaluationError),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError)
|
UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError),
|
||||||
}
|
}
|
||||||
+25
-11
@@ -1,13 +1,14 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use oxigraph::model::{Dataset, GraphNameRef, NamedNodeRef, Quad, QuadRef};
|
use oxigraph::model::{Dataset, GraphNameRef, NamedNodeRef, Quad, QuadRef};
|
||||||
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||||
use oxigraph::store::Store;
|
use oxigraph::store::Store;
|
||||||
use tracing::{debug, debug_span, field};
|
use tracing::{debug_span, field};
|
||||||
|
|
||||||
const RDF_SCHEMA_PREFIX: &str = "http://www.w3.org/2000/01/rdf-schema#";
|
const RDF_SCHEMA_PREFIX: &str = "http://www.w3.org/2000/01/rdf-schema#";
|
||||||
const GL_PREFIX: &str = "https://graphofliberty.org/";
|
const GL_PREFIX: &str = "https://graphofliberty.org/";
|
||||||
|
|
||||||
const INFERENCE_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/inference"));
|
const INFERENCE_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked(
|
||||||
|
"https://graphofliberty.org/inference",
|
||||||
|
));
|
||||||
|
|
||||||
/// `prp-spo1`
|
/// `prp-spo1`
|
||||||
const SUB_PROPERTY_OF_QUERY: &str = r#"CONSTRUCT {
|
const SUB_PROPERTY_OF_QUERY: &str = r#"CONSTRUCT {
|
||||||
@@ -49,15 +50,23 @@ fn run_query(name: &str, query: &str, dataset: &Dataset) -> crate::Result<Datase
|
|||||||
|
|
||||||
query.dataset_mut().set_default_graph_as_union();
|
query.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
let inferences = if let QueryResults::Graph(result) = query.on_queryable_dataset(dataset).execute()? {
|
let inferences =
|
||||||
result.filter_map(Result::ok)
|
if let QueryResults::Graph(result) = query.on_queryable_dataset(dataset).execute()? {
|
||||||
.map(|triple| Quad::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH))
|
result
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.map(|triple| {
|
||||||
|
Quad::new(
|
||||||
|
triple.subject,
|
||||||
|
triple.predicate,
|
||||||
|
triple.object,
|
||||||
|
INFERENCE_GRAPH,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
Dataset::new()
|
Dataset::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Ok(inferences)
|
Ok(inferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,15 +95,20 @@ impl InferenceEngine {
|
|||||||
ontology.extend(store.iter().filter_map(Result::ok));
|
ontology.extend(store.iter().filter_map(Result::ok));
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self { ontology }
|
||||||
ontology,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self, dataset: &Dataset) -> crate::Result<Dataset> {
|
pub fn run(&self, dataset: &Dataset) -> crate::Result<Dataset> {
|
||||||
let span = debug_span!("Inference", inferences = field::Empty).entered();
|
let span = debug_span!("Inference", inferences = field::Empty).entered();
|
||||||
|
|
||||||
let input = dataset.iter().map(|triple| QuadRef::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH));
|
let input = dataset.iter().map(|triple| {
|
||||||
|
QuadRef::new(
|
||||||
|
triple.subject,
|
||||||
|
triple.predicate,
|
||||||
|
triple.object,
|
||||||
|
INFERENCE_GRAPH,
|
||||||
|
)
|
||||||
|
});
|
||||||
let mut ontology = self.ontology.clone();
|
let mut ontology = self.ontology.clone();
|
||||||
ontology.extend(input);
|
ontology.extend(input);
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@ mod curie;
|
|||||||
//mod materialize;
|
//mod materialize;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
||||||
pub mod vocab;
|
|
||||||
pub mod inference;
|
pub mod inference;
|
||||||
|
pub mod vocab;
|
||||||
|
|
||||||
pub use curie::{PREFIXES, CurieHelper};
|
pub use curie::{CurieHelper, PREFIXES};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
+123
-72
@@ -3,6 +3,8 @@ use crate::rdf::ontology::{LabeledIri, Ontology};
|
|||||||
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::inference::InferenceEngine;
|
||||||
use gl_search::language;
|
use gl_search::language;
|
||||||
use gl_search::{Schema, SearchDocument, SearchIndex, Value};
|
use gl_search::{Schema, SearchDocument, SearchIndex, Value};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
@@ -11,7 +13,10 @@ use iced::keyboard::{Event, key};
|
|||||||
use iced::widget::button::Style;
|
use iced::widget::button::Style;
|
||||||
use iced::widget::grid::Sizing;
|
use iced::widget::grid::Sizing;
|
||||||
use iced::widget::text::Wrapping;
|
use iced::widget::text::Wrapping;
|
||||||
use iced::widget::{button, center, column, combo_box, container, grid, mouse_area, opaque, operation, pick_list, row, scrollable, space, stack, table, text, text_input, toggler};
|
use iced::widget::{
|
||||||
|
button, center, column, combo_box, container, grid, mouse_area, opaque, operation, pick_list,
|
||||||
|
row, scrollable, space, stack, table, text, text_input, toggler,
|
||||||
|
};
|
||||||
use iced::window::Settings;
|
use iced::window::Settings;
|
||||||
use iced::{Background, Color, Element, Length, Subscription, Task, color, keyboard, window};
|
use iced::{Background, Color, Element, Length, Subscription, Task, color, keyboard, window};
|
||||||
use ldp::middleware::BasicAuthMiddleware;
|
use ldp::middleware::BasicAuthMiddleware;
|
||||||
@@ -21,11 +26,12 @@ 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, TripleRef};
|
use oxigraph::model::{
|
||||||
|
BaseDirection, Dataset, Graph, NamedNode, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term,
|
||||||
|
TripleRef,
|
||||||
|
};
|
||||||
use oxigraph::store::Store;
|
use oxigraph::store::Store;
|
||||||
use tracing::{debug_span, error, trace};
|
use tracing::{debug_span, error, trace};
|
||||||
use gl_graph::CurieHelper;
|
|
||||||
use gl_graph::inference::InferenceEngine;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum Message {
|
pub(crate) enum Message {
|
||||||
@@ -139,9 +145,12 @@ impl Publisher {
|
|||||||
let mut abbreviated_datatypes = ontology
|
let mut abbreviated_datatypes = ontology
|
||||||
.datatypes()
|
.datatypes()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|node| curie_helper.abbreviate(None, node.as_str())
|
.map(|node| {
|
||||||
|
curie_helper
|
||||||
|
.abbreviate(None, node.as_str())
|
||||||
.unwrap_or(node.as_str().to_string())
|
.unwrap_or(node.as_str().to_string())
|
||||||
).collect::<Vec<_>>();
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
abbreviated_datatypes.sort();
|
abbreviated_datatypes.sort();
|
||||||
|
|
||||||
let (id, task) = window::open(Settings::default());
|
let (id, task) = window::open(Settings::default());
|
||||||
@@ -246,14 +255,8 @@ impl Publisher {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Message::LoadDocument(document) => {
|
Message::LoadDocument(document) => {
|
||||||
let messages = document
|
let messages = document.dataset().quads.iter().map(|(key, quad)| {
|
||||||
.dataset()
|
let datatype_state = combo_box::State::new(self.abbreviated_datatypes.clone());
|
||||||
.quads
|
|
||||||
.iter()
|
|
||||||
.map(|(key, quad)| {
|
|
||||||
let datatype_state = combo_box::State::new(
|
|
||||||
self.abbreviated_datatypes.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let state = RowState {
|
let state = RowState {
|
||||||
read_only: self.ontology.is_read_only(quad.as_ref()),
|
read_only: self.ontology.is_read_only(quad.as_ref()),
|
||||||
@@ -277,9 +280,7 @@ impl Publisher {
|
|||||||
quad.object = Term::NamedNode(empty_node);
|
quad.object = Term::NamedNode(empty_node);
|
||||||
|
|
||||||
let key = self.document.dataset_mut().quads.insert(quad);
|
let key = self.document.dataset_mut().quads.insert(quad);
|
||||||
let datatype_state = combo_box::State::new(
|
let datatype_state = combo_box::State::new(self.abbreviated_datatypes.clone());
|
||||||
self.abbreviated_datatypes.clone(),
|
|
||||||
);
|
|
||||||
let state = RowState {
|
let state = RowState {
|
||||||
read_only: false,
|
read_only: false,
|
||||||
datatype_state,
|
datatype_state,
|
||||||
@@ -306,14 +307,14 @@ impl Publisher {
|
|||||||
let selected_entity_class = match action {
|
let selected_entity_class = match action {
|
||||||
SearchResultClickAction::Predicate(_) => Some(rdf::PROPERTY),
|
SearchResultClickAction::Predicate(_) => Some(rdf::PROPERTY),
|
||||||
SearchResultClickAction::Object(key) => {
|
SearchResultClickAction::Object(key) => {
|
||||||
self.document
|
self.document.dataset().quads.get(key).and_then(|quad| {
|
||||||
.dataset()
|
if quad.predicate == rdf::TYPE {
|
||||||
.quads
|
|
||||||
.get(key)
|
|
||||||
.and_then(|quad| if quad.predicate == rdf::TYPE {
|
|
||||||
Some(rdfs::CLASS)
|
Some(rdfs::CLASS)
|
||||||
} else { None })
|
} else {
|
||||||
},
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
SearchResultClickAction::URLInput => None,
|
SearchResultClickAction::URLInput => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -332,7 +333,10 @@ impl Publisher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(selected_entity_class) = selected_entity_class {
|
if let Some(selected_entity_class) = selected_entity_class {
|
||||||
let selection = self.ontology.info(&selected_entity_class.into_owned(), &*language::ENGLISH_OR_UNTAGGED);
|
let selection = self.ontology.info(
|
||||||
|
&selected_entity_class.into_owned(),
|
||||||
|
&*language::ENGLISH_OR_UNTAGGED,
|
||||||
|
);
|
||||||
task = task.chain(Task::done(Message::QueryTypeUpdated(selection)));
|
task = task.chain(Task::done(Message::QueryTypeUpdated(selection)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,13 +348,16 @@ impl Publisher {
|
|||||||
}
|
}
|
||||||
Message::QueryUpdated(new_query) => {
|
Message::QueryUpdated(new_query) => {
|
||||||
if let Some(search_state) = &mut self.search_state {
|
if let Some(search_state) = &mut self.search_state {
|
||||||
let category_id = search_state.entity_selection
|
let category_id = search_state
|
||||||
|
.entity_selection
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|info| self.ontology.category_id(&info.iri));
|
.and_then(|info| self.ontology.category_id(&info.iri));
|
||||||
|
|
||||||
let query = new_query.clone();
|
let query = new_query.clone();
|
||||||
search_state.query = new_query;
|
search_state.query = new_query;
|
||||||
let results = self.index.query(category_id, query.as_str(), Schema::all_fields(), 25)
|
let results = self
|
||||||
|
.index
|
||||||
|
.query(category_id, query.as_str(), Schema::all_fields(), 25)
|
||||||
.expect("Unable to complete search");
|
.expect("Unable to complete search");
|
||||||
task = Task::done(Message::SetSearchResults(results));
|
task = Task::done(Message::SetSearchResults(results));
|
||||||
};
|
};
|
||||||
@@ -548,7 +555,8 @@ impl Publisher {
|
|||||||
self.show_inferred_properties = value;
|
self.show_inferred_properties = value;
|
||||||
}
|
}
|
||||||
Message::RunInference => {
|
Message::RunInference => {
|
||||||
let dataset = self.document
|
let dataset = self
|
||||||
|
.document
|
||||||
.dataset()
|
.dataset()
|
||||||
.quads
|
.quads
|
||||||
.iter()
|
.iter()
|
||||||
@@ -556,9 +564,10 @@ impl Publisher {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let engine = self.inference_engine.clone();
|
let engine = self.inference_engine.clone();
|
||||||
task = Task::perform(tokio::task::spawn_blocking(move || {
|
task = Task::perform(
|
||||||
engine.run(&dataset).unwrap()
|
tokio::task::spawn_blocking(move || engine.run(&dataset).unwrap()),
|
||||||
}), |dataset| Message::SetInferredTriples(dataset.unwrap()));
|
|dataset| Message::SetInferredTriples(dataset.unwrap()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Message::SetInferredTriples(dataset) => {
|
Message::SetInferredTriples(dataset) => {
|
||||||
self.inferred_triples = Graph::from_iter(&dataset);
|
self.inferred_triples = Graph::from_iter(&dataset);
|
||||||
@@ -621,22 +630,34 @@ impl Publisher {
|
|||||||
subject_input_base.on_input(move |value| Message::SubjectUpdated(key, value))
|
subject_input_base.on_input(move |value| Message::SubjectUpdated(key, value))
|
||||||
};
|
};
|
||||||
|
|
||||||
let predicate_input_base = iri_input(&self.curie_helper, "Predicate", Some(&base), triple.predicate.as_str());
|
let predicate_input_base = iri_input(
|
||||||
|
&self.curie_helper,
|
||||||
|
"Predicate",
|
||||||
|
Some(&base),
|
||||||
|
triple.predicate.as_str(),
|
||||||
|
);
|
||||||
let predicate_input = if self.ontology.is_read_only(triple.as_ref()) {
|
let predicate_input = if self.ontology.is_read_only(triple.as_ref()) {
|
||||||
predicate_input_base
|
predicate_input_base
|
||||||
} else {
|
} else {
|
||||||
predicate_input_base.on_input(move |value| Message::PredicateUpdated(key, value))
|
predicate_input_base
|
||||||
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Predicate(key)))
|
.on_input(move |value| Message::PredicateUpdated(key, value))
|
||||||
|
.on_control_click(Message::OpenQueryWindow(
|
||||||
|
SearchResultClickAction::Predicate(key),
|
||||||
|
))
|
||||||
.on_shift_click(Message::NavigateToPredicate(key))
|
.on_shift_click(Message::NavigateToPredicate(key))
|
||||||
};
|
};
|
||||||
|
|
||||||
let predicate_info = self.ontology.info(&triple.predicate, &*language::ENGLISH_OR_UNTAGGED);
|
let predicate_info = self
|
||||||
|
.ontology
|
||||||
|
.info(&triple.predicate, &*language::ENGLISH_OR_UNTAGGED);
|
||||||
let predicate_label = container(text(predicate_info.label));
|
let predicate_label = container(text(predicate_info.label));
|
||||||
|
|
||||||
let term = TermHelper::new(&triple.object);
|
let term = TermHelper::new(&triple.object);
|
||||||
|
|
||||||
let value_label = term.value_as_named_node().and_then(|node| {
|
let value_label = term.value_as_named_node().and_then(|node| {
|
||||||
let info = self.ontology.info(&node.into_owned(), &*language::ENGLISH_OR_UNTAGGED);
|
let info = self
|
||||||
|
.ontology
|
||||||
|
.info(&node.into_owned(), &*language::ENGLISH_OR_UNTAGGED);
|
||||||
Some(container(text(info.label)))
|
Some(container(text(info.label)))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -660,7 +681,8 @@ impl Publisher {
|
|||||||
if self.ontology.is_read_only(triple.as_ref()) {
|
if self.ontology.is_read_only(triple.as_ref()) {
|
||||||
base.into()
|
base.into()
|
||||||
} else {
|
} else {
|
||||||
base.on_input(move |value| Message::ObjectUpdated(key, value)).into()
|
base.on_input(move |value| Message::ObjectUpdated(key, value))
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let base = iri_input(&self.curie_helper, "Object", Some(&base), value.as_str())
|
let base = iri_input(&self.curie_helper, "Object", Some(&base), value.as_str())
|
||||||
@@ -670,12 +692,15 @@ impl Publisher {
|
|||||||
} else {
|
} else {
|
||||||
base.align_x(value_alignment)
|
base.align_x(value_alignment)
|
||||||
.on_input(move |value| Message::ObjectUpdated(key, value))
|
.on_input(move |value| Message::ObjectUpdated(key, value))
|
||||||
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object(key)))
|
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object(
|
||||||
|
key,
|
||||||
|
)))
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let selected_datatype = term.datatype()
|
let selected_datatype = term
|
||||||
|
.datatype()
|
||||||
.and_then(|node| self.curie_helper.abbreviate(None, node.as_str()));
|
.and_then(|node| self.curie_helper.abbreviate(None, node.as_str()));
|
||||||
|
|
||||||
let datatype_selector: Element<Message> = if state.read_only {
|
let datatype_selector: Element<Message> = if state.read_only {
|
||||||
@@ -690,7 +715,8 @@ impl Publisher {
|
|||||||
.on_input(move |input| {
|
.on_input(move |input| {
|
||||||
let value = if input.is_empty() { None } else { Some(input) };
|
let value = if input.is_empty() { None } else { Some(input) };
|
||||||
Message::DatatypeUpdated(key, value)
|
Message::DatatypeUpdated(key, value)
|
||||||
}).into()
|
})
|
||||||
|
.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
let language = term.language().unwrap_or("en").to_owned();
|
let language = term.language().unwrap_or("en").to_owned();
|
||||||
@@ -748,7 +774,9 @@ impl Publisher {
|
|||||||
entities: impl IntoIterator<Item = LabeledIri>,
|
entities: impl IntoIterator<Item = LabeledIri>,
|
||||||
) -> Element<'_, Message> {
|
) -> Element<'_, Message> {
|
||||||
let buttons = entities.into_iter().map(|entity| {
|
let buttons = entities.into_iter().map(|entity| {
|
||||||
let abbreviation = self.curie_helper.abbreviate(None, entity.iri.as_str())
|
let abbreviation = self
|
||||||
|
.curie_helper
|
||||||
|
.abbreviate(None, entity.iri.as_str())
|
||||||
.unwrap_or_else(|| entity.iri.as_str().to_string());
|
.unwrap_or_else(|| entity.iri.as_str().to_string());
|
||||||
|
|
||||||
let label = format!("{} ({})", entity.label, abbreviation);
|
let label = format!("{} ({})", entity.label, abbreviation);
|
||||||
@@ -766,8 +794,8 @@ impl Publisher {
|
|||||||
if let Some(search_state) = &self.search_state
|
if let Some(search_state) = &self.search_state
|
||||||
&& search_state.window_id == window
|
&& search_state.window_id == window
|
||||||
{
|
{
|
||||||
let search_input =
|
let search_input = text_input("Query", &search_state.query)
|
||||||
text_input("Query", &search_state.query).on_input(Message::QueryUpdated)
|
.on_input(Message::QueryUpdated)
|
||||||
.id("query");
|
.id("query");
|
||||||
|
|
||||||
let entities = Vec::new(); /*self.ontology
|
let entities = Vec::new(); /*self.ontology
|
||||||
@@ -775,26 +803,32 @@ impl Publisher {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<_>>();*/
|
.collect::<Vec<_>>();*/
|
||||||
|
|
||||||
let type_selector = pick_list(search_state.entity_selection.as_ref(), entities, ToString::to_string)
|
let type_selector = pick_list(
|
||||||
|
search_state.entity_selection.as_ref(),
|
||||||
|
entities,
|
||||||
|
ToString::to_string,
|
||||||
|
)
|
||||||
.on_select(|selection| Message::QueryTypeUpdated(selection));
|
.on_select(|selection| Message::QueryTypeUpdated(selection));
|
||||||
|
|
||||||
let mut columns = vec![
|
let mut columns = vec![table::column(text("CURIE"), |document: &SearchDocument| {
|
||||||
table::column(text("CURIE"), |document: &SearchDocument| {
|
let iri = document
|
||||||
let iri = document.get_first(Schema::iri_field())
|
.get_first(Schema::iri_field())
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let abbreviated_iri = self.curie_helper.abbreviate(None, iri)
|
let abbreviated_iri = self
|
||||||
|
.curie_helper
|
||||||
|
.abbreviate(None, iri)
|
||||||
.unwrap_or_else(|| iri.to_string());
|
.unwrap_or_else(|| iri.to_string());
|
||||||
|
|
||||||
button(text(abbreviated_iri).wrapping(Wrapping::Word))
|
button(text(abbreviated_iri).wrapping(Wrapping::Word))
|
||||||
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||||
.style(button::text)
|
.style(button::text)
|
||||||
})
|
})];
|
||||||
];
|
|
||||||
|
|
||||||
let fields = if let Some(selection) = search_state.entity_selection.as_ref() {
|
let fields = if let Some(selection) = search_state.entity_selection.as_ref() {
|
||||||
self.ontology.fields_for_class(&selection.iri, &*language::ENGLISH_OR_UNTAGGED)
|
self.ontology
|
||||||
|
.fields_for_class(&selection.iri, &*language::ENGLISH_OR_UNTAGGED)
|
||||||
.expect("Unable to load fields for class")
|
.expect("Unable to load fields for class")
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
@@ -804,18 +838,21 @@ impl Publisher {
|
|||||||
let field_name = field.name.clone();
|
let field_name = field.name.clone();
|
||||||
columns.push(
|
columns.push(
|
||||||
table::column(text(field.label), move |document: &SearchDocument| {
|
table::column(text(field.label), move |document: &SearchDocument| {
|
||||||
let iri = document.get_first(Schema::iri_field())
|
let iri = document
|
||||||
|
.get_first(Schema::iri_field())
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let value = document.get_first(Schema::field(&field_name, Some(language::ENGLISH_PRIMARY)))
|
let value = document
|
||||||
|
.get_first(Schema::field(&field_name, Some(language::ENGLISH_PRIMARY)))
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
button(text(value).wrapping(Wrapping::Word))
|
button(text(value).wrapping(Wrapping::Word))
|
||||||
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||||
.style(button::text)
|
.style(button::text)
|
||||||
}).width(Length::Fixed(256.0))
|
})
|
||||||
|
.width(Length::Fixed(256.0)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,10 +862,7 @@ impl Publisher {
|
|||||||
Some(scrollable(table(columns, &search_state.results)))
|
Some(scrollable(table(columns, &search_state.results)))
|
||||||
};
|
};
|
||||||
|
|
||||||
return column![
|
return column![row![search_input, type_selector], results_table].into();
|
||||||
row![search_input, type_selector],
|
|
||||||
results_table
|
|
||||||
].into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let back_button = button("\u{1f870}").on_press(Message::NavigateBack);
|
let back_button = button("\u{1f870}").on_press(Message::NavigateBack);
|
||||||
@@ -841,7 +875,9 @@ impl Publisher {
|
|||||||
.on_input(Message::URLInputChanged)
|
.on_input(Message::URLInputChanged)
|
||||||
.on_submit(Message::URLInputSubmitted)
|
.on_submit(Message::URLInputSubmitted)
|
||||||
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::URLInput))
|
.on_control_click(Message::OpenQueryWindow(SearchResultClickAction::URLInput))
|
||||||
.style(crate::theme::styles::text_input::required(!self.url_input_valid));
|
.style(crate::theme::styles::text_input::required(
|
||||||
|
!self.url_input_valid,
|
||||||
|
));
|
||||||
|
|
||||||
let mut rows: Vec<Element<Message>> = vec![];
|
let mut rows: Vec<Element<Message>> = vec![];
|
||||||
rows = self
|
rows = self
|
||||||
@@ -854,7 +890,9 @@ impl Publisher {
|
|||||||
|
|
||||||
let body: Element<Message> = if self.show_new_document_buttons {
|
let body: Element<Message> = if self.show_new_document_buttons {
|
||||||
let subclasses = Vec::new(); // TODO self.ontology.subclasses_of(vocab::rda::ENTITY.into_owned());
|
let subclasses = Vec::new(); // TODO self.ontology.subclasses_of(vocab::rda::ENTITY.into_owned());
|
||||||
let labeled_subclasses = subclasses.iter().map(|iri| self.ontology.info(iri, &*language::ENGLISH_OR_UNTAGGED));
|
let labeled_subclasses = subclasses
|
||||||
|
.iter()
|
||||||
|
.map(|iri| self.ontology.info(iri, &*language::ENGLISH_OR_UNTAGGED));
|
||||||
column![self.view_new_entity_buttons(labeled_subclasses)].into()
|
column![self.view_new_entity_buttons(labeled_subclasses)].into()
|
||||||
} else {
|
} else {
|
||||||
column(rows).into()
|
column(rows).into()
|
||||||
@@ -864,24 +902,38 @@ impl Publisher {
|
|||||||
let subject_column = table::column("Subject", |triple: TripleRef| {
|
let subject_column = table::column("Subject", |triple: TripleRef| {
|
||||||
let curie = if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject {
|
let curie = if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject {
|
||||||
self.curie_helper.abbreviate(None, subject.as_str())
|
self.curie_helper.abbreviate(None, subject.as_str())
|
||||||
} else { None };
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
curie.map(text)
|
curie.map(text)
|
||||||
});
|
});
|
||||||
|
|
||||||
let predicate_column = table::column("Predicate", |triple: TripleRef| {
|
let predicate_column = table::column("Predicate", |triple: TripleRef| {
|
||||||
let predicate_info = self.ontology.info(&triple.predicate.into_owned(), &*language::ENGLISH_OR_UNTAGGED);
|
let predicate_info = self.ontology.info(
|
||||||
|
&triple.predicate.into_owned(),
|
||||||
|
&*language::ENGLISH_OR_UNTAGGED,
|
||||||
|
);
|
||||||
let label = if predicate_info.label.is_empty() {
|
let label = if predicate_info.label.is_empty() {
|
||||||
self.curie_helper
|
self.curie_helper
|
||||||
.abbreviate(None, triple.predicate.as_str())
|
.abbreviate(None, triple.predicate.as_str())
|
||||||
.unwrap_or(triple.predicate.as_str().to_string())
|
.unwrap_or(triple.predicate.as_str().to_string())
|
||||||
} else { predicate_info.label };
|
} else {
|
||||||
|
predicate_info.label
|
||||||
|
};
|
||||||
text(label)
|
text(label)
|
||||||
});
|
});
|
||||||
|
|
||||||
let object_column = table::column("Object", |triple: TripleRef| text(triple.object.to_string()));
|
let object_column = table::column("Object", |triple: TripleRef| {
|
||||||
Some(table([subject_column, predicate_column, object_column], self.inferred_triples.iter()))
|
text(triple.object.to_string())
|
||||||
} else { None };
|
});
|
||||||
|
Some(table(
|
||||||
|
[subject_column, predicate_column, object_column],
|
||||||
|
self.inferred_triples.iter(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let save_button_base = button(text("\u{1f4be}"));
|
let save_button_base = button(text("\u{1f4be}"));
|
||||||
let save_button = if self.modified {
|
let save_button = if self.modified {
|
||||||
@@ -890,14 +942,13 @@ impl Publisher {
|
|||||||
save_button_base
|
save_button_base
|
||||||
};
|
};
|
||||||
|
|
||||||
let inferred_type_toggle = toggler(self.show_inferred_types)
|
let inferred_type_toggle =
|
||||||
.on_toggle(Message::SetInferredTypes);
|
toggler(self.show_inferred_types).on_toggle(Message::SetInferredTypes);
|
||||||
|
|
||||||
let inferred_property_toggle = toggler(self.show_inferred_properties)
|
let inferred_property_toggle =
|
||||||
.on_toggle(Message::SetInferredProperties);
|
toggler(self.show_inferred_properties).on_toggle(Message::SetInferredProperties);
|
||||||
|
|
||||||
let read_only_toggle = toggler(self.show_read_only)
|
let read_only_toggle = toggler(self.show_read_only).on_toggle(Message::SetReadOnly);
|
||||||
.on_toggle(Message::SetReadOnly);
|
|
||||||
|
|
||||||
let footer = row![
|
let footer = row![
|
||||||
space::horizontal(),
|
space::horizontal(),
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub(crate) struct QueryArgs {
|
pub(crate) struct QueryArgs {
|
||||||
|
|||||||
+52
-27
@@ -1,14 +1,17 @@
|
|||||||
mod app;
|
mod app;
|
||||||
mod error;
|
|
||||||
mod rdf;
|
|
||||||
mod windows;
|
|
||||||
mod widget;
|
|
||||||
mod navigator;
|
|
||||||
mod theme;
|
|
||||||
mod args;
|
mod args;
|
||||||
|
mod error;
|
||||||
|
mod navigator;
|
||||||
|
mod rdf;
|
||||||
|
mod theme;
|
||||||
|
mod widget;
|
||||||
|
mod windows;
|
||||||
|
|
||||||
use std::fs::File;
|
use crate::app::Publisher;
|
||||||
|
use crate::args::{AppArgs, Command};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use gl_graph::inference::InferenceEngine;
|
||||||
|
use gl_search::SearchIndex;
|
||||||
use iced::futures::StreamExt;
|
use iced::futures::StreamExt;
|
||||||
use ldp::middleware::BasicAuthMiddleware;
|
use ldp::middleware::BasicAuthMiddleware;
|
||||||
use ldp::reqwest::{Client, Url};
|
use ldp::reqwest::{Client, Url};
|
||||||
@@ -16,23 +19,24 @@ use ldp::reqwest_middleware::ClientBuilder;
|
|||||||
use ldp::traverse::Traverse;
|
use ldp::traverse::Traverse;
|
||||||
use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
|
use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
|
||||||
use oxigraph::model::{Dataset, GraphName, NamedNode, Quad, Triple};
|
use oxigraph::model::{Dataset, GraphName, NamedNode, Quad, Triple};
|
||||||
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
|
||||||
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer};
|
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer};
|
||||||
|
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||||
use oxigraph::store::Store;
|
use oxigraph::store::Store;
|
||||||
|
use std::fs::File;
|
||||||
use tracing::{debug_span, error, field};
|
use tracing::{debug_span, error, field};
|
||||||
use crate::app::Publisher;
|
use tracing_subscriber::fmt::format::FmtSpan;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use tracing_subscriber::{EnvFilter, fmt};
|
use tracing_subscriber::{EnvFilter, fmt};
|
||||||
use tracing_subscriber::fmt::format::FmtSpan;
|
|
||||||
use gl_graph::inference::InferenceEngine;
|
|
||||||
use gl_search::SearchIndex;
|
|
||||||
use crate::args::{AppArgs, Command};
|
|
||||||
|
|
||||||
fn main() -> color_eyre::Result<()> {
|
fn main() -> color_eyre::Result<()> {
|
||||||
let appender = tracing_appender::rolling::never("/tmp", "publisher-log");
|
let appender = tracing_appender::rolling::never("/tmp", "publisher-log");
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(fmt::layer().with_span_events(FmtSpan::CLOSE).with_writer(appender))
|
.with(
|
||||||
|
fmt::layer()
|
||||||
|
.with_span_events(FmtSpan::CLOSE)
|
||||||
|
.with_writer(appender),
|
||||||
|
)
|
||||||
.with(EnvFilter::from_default_env())
|
.with(EnvFilter::from_default_env())
|
||||||
.init();
|
.init();
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
@@ -48,7 +52,10 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
let store = Store::open("/home/alex/.local/share/org.graphofliberty.desktop/ontology")?;
|
let store = Store::open("/home/alex/.local/share/org.graphofliberty.desktop/ontology")?;
|
||||||
let inference_engine = InferenceEngine::new(store.clone());
|
let inference_engine = InferenceEngine::new(store.clone());
|
||||||
|
|
||||||
let graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!("file://{}", args.dataset_path.to_string_lossy())));
|
let graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!(
|
||||||
|
"file://{}",
|
||||||
|
args.dataset_path.to_string_lossy()
|
||||||
|
)));
|
||||||
let dataset = RdfParser::from_format(RdfFormat::Turtle)
|
let dataset = RdfParser::from_format(RdfFormat::Turtle)
|
||||||
.with_default_graph(graph_name)
|
.with_default_graph(graph_name)
|
||||||
.for_reader(File::open(&args.dataset_path)?)
|
.for_reader(File::open(&args.dataset_path)?)
|
||||||
@@ -57,11 +64,13 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
let dataset_with_inferences = inference_engine.run(&dataset)?;
|
let dataset_with_inferences = inference_engine.run(&dataset)?;
|
||||||
|
|
||||||
let raw_query = String::from_utf8(std::fs::read(&args.query_path)?)?;
|
let raw_query = String::from_utf8(std::fs::read(&args.query_path)?)?;
|
||||||
let mut query = SparqlEvaluator::new()
|
let mut query = SparqlEvaluator::new().parse_query(&raw_query)?;
|
||||||
.parse_query(&raw_query)?;
|
|
||||||
query.dataset_mut().set_default_graph_as_union();
|
query.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
match query.on_queryable_dataset(&dataset_with_inferences).execute()? {
|
match query
|
||||||
|
.on_queryable_dataset(&dataset_with_inferences)
|
||||||
|
.execute()?
|
||||||
|
{
|
||||||
QueryResults::Graph(graph) => {
|
QueryResults::Graph(graph) => {
|
||||||
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
||||||
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
|
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
|
||||||
@@ -78,15 +87,20 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
serializer.finish()?;
|
serializer.finish()?;
|
||||||
}
|
}
|
||||||
QueryResults::Solutions(solutions) => {
|
QueryResults::Solutions(solutions) => {
|
||||||
let json_serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Json);
|
let json_serializer =
|
||||||
let mut writer = json_serializer.serialize_solutions_to_writer(std::io::stdout(), Vec::from_iter(solutions.variables().iter().cloned()))?;
|
QueryResultsSerializer::from_format(QueryResultsFormat::Json);
|
||||||
|
let mut writer = json_serializer.serialize_solutions_to_writer(
|
||||||
|
std::io::stdout(),
|
||||||
|
Vec::from_iter(solutions.variables().iter().cloned()),
|
||||||
|
)?;
|
||||||
for solution in solutions.filter_map(Result::ok) {
|
for solution in solutions.filter_map(Result::ok) {
|
||||||
writer.serialize(&solution)?;
|
writer.serialize(&solution)?;
|
||||||
}
|
}
|
||||||
writer.finish()?;
|
writer.finish()?;
|
||||||
}
|
}
|
||||||
QueryResults::Boolean(result) => {
|
QueryResults::Boolean(result) => {
|
||||||
let json_serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Json);
|
let json_serializer =
|
||||||
|
QueryResultsSerializer::from_format(QueryResultsFormat::Json);
|
||||||
json_serializer.serialize_boolean_to_writer(std::io::stdout(), result)?;
|
json_serializer.serialize_boolean_to_writer(std::io::stdout(), result)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +122,11 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let span = debug_span!("Index Schema", documents = field::Empty).entered();
|
let span = debug_span!("Index Schema", documents = field::Empty).entered();
|
||||||
let count = gl_search::rdf::index_schema(store, &*gl_search::language::ENGLISH_OR_UNTAGGED, &writer)?;
|
let count = gl_search::rdf::index_schema(
|
||||||
|
store,
|
||||||
|
&*gl_search::language::ENGLISH_OR_UNTAGGED,
|
||||||
|
&writer,
|
||||||
|
)?;
|
||||||
span.record("documents", count);
|
span.record("documents", count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +158,17 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
let inferences = inference_engine.run(&dataset).unwrap();
|
let inferences = inference_engine.run(&dataset).unwrap();
|
||||||
dataset.extend(&inferences);
|
dataset.extend(&inferences);
|
||||||
|
|
||||||
let span = debug_span!("Index Repository", rdf_sources = field::Empty, documents = field::Empty).entered();
|
let span = debug_span!(
|
||||||
let document_count = gl_search::rdf::index_entity(&dataset, &*gl_search::language::ENGLISH_OR_UNTAGGED, &writer)?;
|
"Index Repository",
|
||||||
|
rdf_sources = field::Empty,
|
||||||
|
documents = field::Empty
|
||||||
|
)
|
||||||
|
.entered();
|
||||||
|
let document_count = gl_search::rdf::index_entity(
|
||||||
|
&dataset,
|
||||||
|
&*gl_search::language::ENGLISH_OR_UNTAGGED,
|
||||||
|
&writer,
|
||||||
|
)?;
|
||||||
span.record("rdf_sources", rdf_source_count);
|
span.record("rdf_sources", rdf_source_count);
|
||||||
span.record("documents", document_count);
|
span.record("documents", document_count);
|
||||||
|
|
||||||
@@ -149,9 +176,7 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut writer = runtime.block_on(traversal_task)??;
|
let mut writer = runtime.block_on(traversal_task)??;
|
||||||
debug_span!("Commit").in_scope(|| {
|
debug_span!("Commit").in_scope(|| writer.commit())?;
|
||||||
writer.commit()
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
iced::daemon(Publisher::new, Publisher::update, Publisher::view)
|
iced::daemon(Publisher::new, Publisher::update, Publisher::view)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use oxigraph::model::{NamedNode, Quad, Term};
|
|
||||||
use oxigraph::model::vocab::{rdf, xsd};
|
use oxigraph::model::vocab::{rdf, xsd};
|
||||||
|
use oxigraph::model::{NamedNode, Quad, Term};
|
||||||
|
|
||||||
pub fn quad_into_term(quad: Quad) -> Term {
|
pub fn quad_into_term(quad: Quad) -> Term {
|
||||||
quad.object
|
quad.object
|
||||||
@@ -51,11 +51,17 @@ pub fn term_to_boolean(term: &Term) -> Option<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn term_to_u64(term: &Term) -> Option<u64> {
|
pub fn term_to_u64(term: &Term) -> Option<u64> {
|
||||||
if let Term::Literal(literal) = term &&
|
if let Term::Literal(literal) = term
|
||||||
(literal.datatype() == xsd::NON_NEGATIVE_INTEGER || literal.datatype() == xsd::INTEGER) {
|
&& (literal.datatype() == xsd::NON_NEGATIVE_INTEGER || literal.datatype() == xsd::INTEGER)
|
||||||
let value: u64 = literal.value().parse().expect("Failed to parse u64 from ontology. It ought to be a non-negative integer.");
|
{
|
||||||
|
let value: u64 = literal
|
||||||
|
.value()
|
||||||
|
.parse()
|
||||||
|
.expect("Failed to parse u64 from ontology. It ought to be a non-negative integer.");
|
||||||
Some(value)
|
Some(value)
|
||||||
} else { None }
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn term_into_u64(term: Term) -> Option<u64> {
|
pub fn term_into_u64(term: Term) -> Option<u64> {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
pub(crate) mod conversion;
|
||||||
pub(crate) mod ontology;
|
pub(crate) mod ontology;
|
||||||
pub(crate) mod term_helper;
|
pub(crate) mod term_helper;
|
||||||
pub(crate) mod conversion;
|
|
||||||
+106
-42
@@ -4,7 +4,10 @@ use gl_graph::vocab::gl;
|
|||||||
use gl_search::language::LanguageCondition;
|
use gl_search::language::LanguageCondition;
|
||||||
use iced::futures::TryFutureExt;
|
use iced::futures::TryFutureExt;
|
||||||
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
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::sparql::{QueryResults, SparqlEvaluator};
|
||||||
use oxigraph::store::Store;
|
use oxigraph::store::Store;
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||||
@@ -14,10 +17,13 @@ use std::sync::LazyLock;
|
|||||||
use tracing::{debug_span, field};
|
use tracing::{debug_span, field};
|
||||||
|
|
||||||
const ONTOLOGY_PREFIX: &str = "https://graphofliberty.org/2026/04/ont/";
|
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(|| {
|
static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
||||||
BTreeMap::from_iter([
|
BTreeMap::from_iter(
|
||||||
|
[
|
||||||
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
|
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
|
||||||
("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
|
("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
|
||||||
("owl", "http://www.w3.org/2002/07/owl#"),
|
("owl", "http://www.w3.org/2002/07/owl#"),
|
||||||
@@ -55,7 +61,9 @@ static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
|||||||
("schema", "https://schema.org/"),
|
("schema", "https://schema.org/"),
|
||||||
("gl", "http://fedora.quill.lan/rest/"),
|
("gl", "http://fedora.quill.lan/rest/"),
|
||||||
("glo", ONTOLOGY_PREFIX),
|
("glo", ONTOLOGY_PREFIX),
|
||||||
].map(|(k, v)| (k.to_string(), v.to_string())))
|
]
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string())),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct OntologyBuilder {
|
pub struct OntologyBuilder {
|
||||||
@@ -82,18 +90,18 @@ impl OntologyBuilder {
|
|||||||
.collect::<HashMap<String, String>>();
|
.collect::<HashMap<String, String>>();
|
||||||
|
|
||||||
let mut indexed_by = HashMap::new();
|
let mut indexed_by = HashMap::new();
|
||||||
for quad in store.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None)
|
for quad in store
|
||||||
.filter_map(Result::ok) {
|
.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
{
|
||||||
if let NamedOrBlankNode::NamedNode(subject) = quad.subject
|
if let NamedOrBlankNode::NamedNode(subject) = quad.subject
|
||||||
&& let Term::NamedNode(field) = quad.object {
|
&& let Term::NamedNode(field) = quad.object
|
||||||
|
{
|
||||||
indexed_by.insert(subject, field);
|
indexed_by.insert(subject, field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Ontology {
|
Ok(Ontology { store, prefixes })
|
||||||
store,
|
|
||||||
prefixes,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,9 +154,7 @@ pub struct Ontology {
|
|||||||
|
|
||||||
impl Ontology {
|
impl Ontology {
|
||||||
pub fn builder() -> OntologyBuilder {
|
pub fn builder() -> OntologyBuilder {
|
||||||
OntologyBuilder {
|
OntologyBuilder { path: None }
|
||||||
path: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prefixes() -> &'static BTreeMap<String, String> {
|
pub fn prefixes() -> &'static BTreeMap<String, String> {
|
||||||
@@ -166,9 +172,14 @@ impl Ontology {
|
|||||||
self.store.clone()
|
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 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 {{
|
WHERE {{
|
||||||
?class a gl:SearchableClass ;
|
?class a gl:SearchableClass ;
|
||||||
gl:categoryId ?categoryId ;
|
gl:categoryId ?categoryId ;
|
||||||
@@ -180,9 +191,11 @@ WHERE {{
|
|||||||
?property ?fieldValue .
|
?property ?fieldValue .
|
||||||
|
|
||||||
{language_filter}
|
{language_filter}
|
||||||
}}"#);
|
}}"#
|
||||||
|
);
|
||||||
let mut sparql = SparqlEvaluator::new()
|
let mut sparql = SparqlEvaluator::new()
|
||||||
.with_prefix("gl", ONTOLOGY_PREFIX).unwrap()
|
.with_prefix("gl", ONTOLOGY_PREFIX)
|
||||||
|
.unwrap()
|
||||||
.parse_query(&query)
|
.parse_query(&query)
|
||||||
.expect("Unable to parse query");
|
.expect("Unable to parse query");
|
||||||
sparql.dataset_mut().set_default_graph_as_union();
|
sparql.dataset_mut().set_default_graph_as_union();
|
||||||
@@ -194,23 +207,39 @@ WHERE {{
|
|||||||
sparql.on_queryable_dataset(source).execute()
|
sparql.on_queryable_dataset(source).execute()
|
||||||
} else {
|
} else {
|
||||||
sparql.on_store(&store).execute()
|
sparql.on_store(&store).execute()
|
||||||
}.expect("Unable to execute indexing query");
|
}
|
||||||
|
.expect("Unable to execute indexing query");
|
||||||
|
|
||||||
let mut results = HashMap::new();
|
let mut results = HashMap::new();
|
||||||
if let QueryResults::Solutions(solutions) = query_results {
|
if let QueryResults::Solutions(solutions) = query_results {
|
||||||
let mut counter = 0usize;
|
let mut counter = 0usize;
|
||||||
for solution in solutions.filter_map(Result::ok) {
|
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 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_name = solution.get("fieldName").and_then(conversion::term_as_str);
|
||||||
let field_value = solution.get("fieldValue").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) {
|
if let (
|
||||||
results.entry(individual.to_owned())
|
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| {
|
.and_modify(|entry: &mut IndexEntry| {
|
||||||
entry.fields.insert(field_name.to_owned(), field_value.to_owned());
|
entry
|
||||||
}).or_insert(IndexEntry {
|
.fields
|
||||||
|
.insert(field_name.to_owned(), field_value.to_owned());
|
||||||
|
})
|
||||||
|
.or_insert(IndexEntry {
|
||||||
category_id: catalog_id,
|
category_id: catalog_id,
|
||||||
fields: HashMap::from_iter([(field_name.to_owned(), field_value.to_owned())]),
|
fields: HashMap::from_iter([(
|
||||||
|
field_name.to_owned(),
|
||||||
|
field_value.to_owned(),
|
||||||
|
)]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
counter += 1;
|
counter += 1;
|
||||||
@@ -220,27 +249,35 @@ WHERE {{
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
results
|
results
|
||||||
}).map_err(error::Error::from)
|
})
|
||||||
|
.map_err(error::Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn category_id(&self, class: &NamedNode) -> Option<u64> {
|
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)
|
.filter_map(Result::ok)
|
||||||
.map(conversion::quad_into_term)
|
.map(conversion::quad_into_term)
|
||||||
.filter_map(conversion::term_into_u64)
|
.filter_map(conversion::term_into_u64)
|
||||||
.next()
|
.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 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 .
|
{class} gl:associatedProperty/gl:indexedByField ?field .
|
||||||
|
|
||||||
?field gl:fieldName ?name ;
|
?field gl:fieldName ?name ;
|
||||||
gl:fieldLabel ?label .
|
gl:fieldLabel ?label .
|
||||||
|
|
||||||
{language_filter}
|
{language_filter}
|
||||||
}}"#);
|
}}"#
|
||||||
|
);
|
||||||
let mut sparql = SparqlEvaluator::new()
|
let mut sparql = SparqlEvaluator::new()
|
||||||
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
|
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
|
||||||
.with_prefix("gl", ONTOLOGY_PREFIX)?
|
.with_prefix("gl", ONTOLOGY_PREFIX)?
|
||||||
@@ -253,7 +290,9 @@ WHERE {{
|
|||||||
let name = solution.get("name").and_then(conversion::term_as_str);
|
let name = solution.get("name").and_then(conversion::term_as_str);
|
||||||
let label = solution.get("label").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 {
|
results.push(IndexField {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
label: label.to_string(),
|
label: label.to_string(),
|
||||||
@@ -265,7 +304,9 @@ WHERE {{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn info(&self, iri: &NamedNode, language: &LanguageCondition) -> LabeledIri {
|
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)
|
.filter_map(Result::ok)
|
||||||
.map(conversion::quad_into_term)
|
.map(conversion::quad_into_term)
|
||||||
.filter(|term| language.primary_matches_term(term))
|
.filter(|term| language.primary_matches_term(term))
|
||||||
@@ -280,11 +321,16 @@ WHERE {{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn subclasses_of(&self, class: &NamedNode) -> BTreeSet<NamedNode> {
|
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(Result::ok)
|
||||||
.filter_map(|quad| if let NamedOrBlankNode::NamedNode(subject) = quad.subject {
|
.filter_map(|quad| {
|
||||||
|
if let NamedOrBlankNode::NamedNode(subject) = quad.subject {
|
||||||
Some(subject)
|
Some(subject)
|
||||||
} else { None })
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +346,8 @@ WHERE {{
|
|||||||
.filter_map(|quad| match quad.subject {
|
.filter_map(|quad| match quad.subject {
|
||||||
NamedOrBlankNode::NamedNode(subject) => Some(subject),
|
NamedOrBlankNode::NamedNode(subject) => Some(subject),
|
||||||
_ => None,
|
_ => None,
|
||||||
}).collect()
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_read_only<'a>(&self, triple: impl Into<TripleRef<'a>>) -> bool {
|
pub fn is_read_only<'a>(&self, triple: impl Into<TripleRef<'a>>) -> bool {
|
||||||
@@ -313,23 +360,35 @@ WHERE {{
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.store
|
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)
|
.filter_map(Result::ok)
|
||||||
.count() >= 1
|
.count()
|
||||||
|
>= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exclude_read_only(&self) -> impl Fn(TripleRef<'_>) -> bool + 'static {
|
pub fn exclude_read_only(&self) -> impl Fn(TripleRef<'_>) -> bool + 'static {
|
||||||
let true_term = TermRef::Literal(LiteralRef::new_typed_literal("true", xsd::BOOLEAN));
|
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)
|
.quads_for_pattern(None, Some(gl::READ_ONLY), Some(true_term), None)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.map(Triple::from)
|
.map(Triple::from)
|
||||||
.collect::<Graph>();
|
.collect::<Graph>();
|
||||||
move |triple| {
|
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
|
read_only_graph.triples_for_subject(class).count() == 0
|
||||||
} else {
|
} 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 {
|
if let Term::BlankNode(blank_node) = quad.object {
|
||||||
let iter = self
|
let iter = self
|
||||||
.store
|
.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)
|
.filter_map(Result::ok)
|
||||||
.map(Triple::from)
|
.map(Triple::from)
|
||||||
.map(move |mut triple| {
|
.map(move |mut triple| {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
pub mod text_input {
|
pub mod text_input {
|
||||||
use iced::{color, Theme};
|
|
||||||
use iced::widget::text_input;
|
use iced::widget::text_input;
|
||||||
use iced::widget::text_input::{Status, Style};
|
use iced::widget::text_input::{Status, Style};
|
||||||
|
use iced::{Theme, color};
|
||||||
|
|
||||||
pub fn required(invalid: bool) -> impl Fn(&Theme, Status) -> Style {
|
pub fn required(invalid: bool) -> impl Fn(&Theme, Status) -> Style {
|
||||||
move |theme: &Theme, status| {
|
move |theme: &Theme, status| {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use iced::{keyboard, widget, Element, Event, Length, Rectangle, Size};
|
use gl_graph::CurieHelper;
|
||||||
use iced::advanced::{mouse, text, Shell, renderer};
|
|
||||||
use iced::advanced::layout::{Limits, Node};
|
use iced::advanced::layout::{Limits, Node};
|
||||||
|
use iced::advanced::widget::{Operation, Tree, tree};
|
||||||
use iced::advanced::{Layout, Widget};
|
use iced::advanced::{Layout, Widget};
|
||||||
use iced::advanced::widget::{tree, Tree, Operation};
|
use iced::advanced::{Shell, mouse, renderer, text};
|
||||||
use iced::clipboard::Content;
|
use iced::clipboard::Content;
|
||||||
use iced::mouse::{Cursor, Interaction};
|
use iced::mouse::{Cursor, Interaction};
|
||||||
use iced::widget::text_input::{Catalog, Status, Style, StyleFn};
|
use iced::widget::text_input::{Catalog, Status, Style, StyleFn};
|
||||||
use gl_graph::CurieHelper;
|
use iced::{Element, Event, Length, Rectangle, Size, keyboard, widget};
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
control: bool,
|
control: bool,
|
||||||
@@ -31,8 +31,15 @@ where
|
|||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
pub fn new(curie_helper: &'a CurieHelper, placeholder: &'a str, base: Option<&str>, iri: &str) -> Self {
|
pub fn new(
|
||||||
let display_value = curie_helper.abbreviate(base, iri).unwrap_or_else(|| iri.to_string());
|
curie_helper: &'a CurieHelper,
|
||||||
|
placeholder: &'a str,
|
||||||
|
base: Option<&str>,
|
||||||
|
iri: &str,
|
||||||
|
) -> Self {
|
||||||
|
let display_value = curie_helper
|
||||||
|
.abbreviate(base, iri)
|
||||||
|
.unwrap_or_else(|| iri.to_string());
|
||||||
let text_input = widget::TextInput::new(placeholder, display_value);
|
let text_input = widget::TextInput::new(placeholder, display_value);
|
||||||
Self {
|
Self {
|
||||||
curie_helper,
|
curie_helper,
|
||||||
@@ -96,7 +103,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> From<IriInput<'a, Message, Theme, Renderer>>
|
impl<'a, Message, Theme, Renderer> From<IriInput<'a, Message, Theme, Renderer>>
|
||||||
for Element<'a, Message, Theme, Renderer>
|
for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone + 'a,
|
Message: Clone + 'a,
|
||||||
Theme: Catalog + 'a,
|
Theme: Catalog + 'a,
|
||||||
@@ -107,7 +114,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iri_input<'a, Message, Theme, Renderer>(curie_helper: &'a CurieHelper, placeholder: &'a str, base: Option<&str>, iri: &str) -> IriInput<'a, Message, Theme, Renderer>
|
pub fn iri_input<'a, Message, Theme, Renderer>(
|
||||||
|
curie_helper: &'a CurieHelper,
|
||||||
|
placeholder: &'a str,
|
||||||
|
base: Option<&str>,
|
||||||
|
iri: &str,
|
||||||
|
) -> IriInput<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
@@ -116,7 +128,8 @@ where
|
|||||||
IriInput::new(curie_helper, placeholder, base, iri)
|
IriInput::new(curie_helper, placeholder, base, iri)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <Message, Theme, Renderer> Widget<Message, Theme, Renderer> for IriInput<'_, Message, Theme, Renderer>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
for IriInput<'_, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
@@ -127,11 +140,34 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
|
fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
|
||||||
Widget::layout(&mut self.text_input, &mut tree.children[0], renderer, limits)
|
Widget::layout(
|
||||||
|
&mut self.text_input,
|
||||||
|
&mut tree.children[0],
|
||||||
|
renderer,
|
||||||
|
limits,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: Cursor, viewport: &Rectangle) {
|
fn draw(
|
||||||
Widget::draw(&self.text_input, &tree.children[0], renderer, theme, style, layout, cursor, viewport)
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
Widget::draw(
|
||||||
|
&self.text_input,
|
||||||
|
&tree.children[0],
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout,
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
@@ -156,10 +192,20 @@ where
|
|||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
operation: &mut dyn Operation,
|
operation: &mut dyn Operation,
|
||||||
) {
|
) {
|
||||||
self.text_input.operate(&mut tree.children[0], layout, renderer, operation);
|
self.text_input
|
||||||
|
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: Cursor, renderer: &Renderer, shell: &mut Shell<'_, Message>, viewport: &Rectangle) {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: &Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: Cursor,
|
||||||
|
renderer: &Renderer,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
@@ -169,12 +215,16 @@ where
|
|||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds()) {
|
||||||
if state.control && let Some(on_control_click) = &self.on_control_click {
|
if state.control
|
||||||
|
&& let Some(on_control_click) = &self.on_control_click
|
||||||
|
{
|
||||||
shell.publish(on_control_click.clone());
|
shell.publish(on_control_click.clone());
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.shift && let Some(on_shift_click) = &self.on_shift_click {
|
if state.shift
|
||||||
|
&& let Some(on_shift_click) = &self.on_shift_click
|
||||||
|
{
|
||||||
shell.publish(on_shift_click.clone());
|
shell.publish(on_shift_click.clone());
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
@@ -183,7 +233,16 @@ where
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget::update(&mut self.text_input, &mut tree.children[0], event, layout, cursor, renderer, shell, viewport);
|
Widget::update(
|
||||||
|
&mut self.text_input,
|
||||||
|
&mut tree.children[0],
|
||||||
|
event,
|
||||||
|
layout,
|
||||||
|
cursor,
|
||||||
|
renderer,
|
||||||
|
shell,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
let clipboard = shell.clipboard_mut();
|
let clipboard = shell.clipboard_mut();
|
||||||
if let Some(Content::Text(content)) = &clipboard.write {
|
if let Some(Content::Text(content)) = &clipboard.write {
|
||||||
@@ -193,15 +252,29 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(&self, tree: &Tree, layout: Layout<'_>, cursor: Cursor, viewport: &Rectangle, renderer: &Renderer) -> Interaction {
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> Interaction {
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
let interaction = if cursor.is_over(layout.bounds()) {
|
let interaction = if cursor.is_over(layout.bounds()) {
|
||||||
if (state.control && self.on_control_click.is_some()) ||
|
if (state.control && self.on_control_click.is_some())
|
||||||
(state.shift && self.on_shift_click.is_some()) {
|
|| (state.shift && self.on_shift_click.is_some())
|
||||||
|
{
|
||||||
Some(Interaction::Pointer)
|
Some(Interaction::Pointer)
|
||||||
} else { None }
|
} else {
|
||||||
} else { None };
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
interaction.unwrap_or_else(|| Widget::mouse_interaction(&self.text_input, tree, layout, cursor, viewport, renderer))
|
interaction.unwrap_or_else(|| {
|
||||||
|
Widget::mouse_interaction(&self.text_input, tree, layout, cursor, viewport, renderer)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use iced::advanced::{mouse, text, Layout, Shell, Widget, overlay};
|
|
||||||
use iced::{Element, Event, Length, Rectangle, Size, Vector};
|
|
||||||
use iced::advanced::layout::{Limits, Node};
|
use iced::advanced::layout::{Limits, Node};
|
||||||
use iced::advanced::renderer::Style;
|
use iced::advanced::renderer::Style;
|
||||||
use iced::advanced::widget::{Operation, Tree};
|
use iced::advanced::widget::{Operation, Tree};
|
||||||
|
use iced::advanced::{Layout, Shell, Widget, mouse, overlay, text};
|
||||||
use iced::mouse::Cursor;
|
use iced::mouse::Cursor;
|
||||||
use iced::widget::text_input::Catalog;
|
use iced::widget::text_input::Catalog;
|
||||||
|
use iced::{Element, Event, Length, Rectangle, Size, Vector};
|
||||||
|
|
||||||
pub struct NavigationArea<'a, Message, Theme, Renderer>
|
pub struct NavigationArea<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
@@ -45,7 +45,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> From<NavigationArea<'a, Message, Theme, Renderer>>
|
impl<'a, Message, Theme, Renderer> From<NavigationArea<'a, Message, Theme, Renderer>>
|
||||||
for Element<'a, Message, Theme, Renderer>
|
for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone + 'a,
|
Message: Clone + 'a,
|
||||||
Theme: Catalog + 'a,
|
Theme: Catalog + 'a,
|
||||||
@@ -56,7 +56,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn navigation_area<'a, Message, Theme, Renderer>(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> NavigationArea<'a, Message, Theme, Renderer>
|
pub fn navigation_area<'a, Message, Theme, Renderer>(
|
||||||
|
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
) -> NavigationArea<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
@@ -65,7 +67,8 @@ where
|
|||||||
NavigationArea::new(content)
|
NavigationArea::new(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <Message, Theme, Renderer> Widget<Message, Theme, Renderer> for NavigationArea<'_, Message, Theme, Renderer>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
for NavigationArea<'_, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
@@ -81,7 +84,16 @@ where
|
|||||||
.layout(&mut tree.children[0], renderer, limits)
|
.layout(&mut tree.children[0], renderer, limits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &Style, layout: Layout<'_>, cursor: Cursor, viewport: &Rectangle) {
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
style: &Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
self.content.as_widget().draw(
|
self.content.as_widget().draw(
|
||||||
&tree.children[0],
|
&tree.children[0],
|
||||||
renderer,
|
renderer,
|
||||||
@@ -97,13 +109,28 @@ where
|
|||||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(&mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation) {
|
fn operate(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
operation: &mut dyn Operation,
|
||||||
|
) {
|
||||||
self.content
|
self.content
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: Cursor, renderer: &Renderer, shell: &mut Shell<'_, Message>, viewport: &Rectangle) {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: &Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: Cursor,
|
||||||
|
renderer: &Renderer,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
self.content.as_widget_mut().update(
|
self.content.as_widget_mut().update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
event,
|
event,
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
+7
-13
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tantivy::collector::TopDocs;
|
use tantivy::collector::TopDocs;
|
||||||
use tantivy::directory::{ManagedDirectory, MmapDirectory};
|
use tantivy::directory::{ManagedDirectory, MmapDirectory};
|
||||||
@@ -24,9 +24,7 @@ impl SearchIndexBuilder {
|
|||||||
|
|
||||||
pub fn build(self) -> error::Result<SearchIndex> {
|
pub fn build(self) -> error::Result<SearchIndex> {
|
||||||
let ngram_32 = NgramTokenizer::new(1, 32, false)?;
|
let ngram_32 = NgramTokenizer::new(1, 32, false)?;
|
||||||
let ngram_32_lowercase = TextAnalyzer::builder(ngram_32)
|
let ngram_32_lowercase = TextAnalyzer::builder(ngram_32).filter(LowerCaser).build();
|
||||||
.filter(LowerCaser)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let tokenizer_manager = TokenizerManager::default();
|
let tokenizer_manager = TokenizerManager::default();
|
||||||
tokenizer_manager.register("ngram_32", ngram_32_lowercase);
|
tokenizer_manager.register("ngram_32", ngram_32_lowercase);
|
||||||
@@ -49,10 +47,7 @@ impl SearchIndexBuilder {
|
|||||||
.reload_policy(ReloadPolicy::OnCommitWithDelay)
|
.reload_policy(ReloadPolicy::OnCommitWithDelay)
|
||||||
.try_into()?;
|
.try_into()?;
|
||||||
|
|
||||||
Ok(SearchIndex {
|
Ok(SearchIndex { index, reader })
|
||||||
index,
|
|
||||||
reader,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +77,7 @@ impl SearchIndex {
|
|||||||
let parser = QueryParser::for_index(&self.index, default_fields);
|
let parser = QueryParser::for_index(&self.index, default_fields);
|
||||||
let (user_query, _) = parser.parse_query_lenient(user_query);
|
let (user_query, _) = parser.parse_query_lenient(user_query);
|
||||||
|
|
||||||
let mut subqueries = vec![
|
let mut subqueries = vec![(Occur::Must, user_query)];
|
||||||
(Occur::Must, user_query)
|
|
||||||
];
|
|
||||||
|
|
||||||
if let Some(type_) = type_ {
|
if let Some(type_) = type_ {
|
||||||
let doc_type_term = Term::from_field_u64(Schema::discriminant_field(), type_);
|
let doc_type_term = Term::from_field_u64(Schema::discriminant_field(), type_);
|
||||||
@@ -94,7 +87,8 @@ impl SearchIndex {
|
|||||||
|
|
||||||
let query = BooleanQuery::new(subqueries);
|
let query = BooleanQuery::new(subqueries);
|
||||||
let searcher = self.reader.searcher();
|
let searcher = self.reader.searcher();
|
||||||
let results: Vec<TantivyDocument> = searcher.search(&query, &TopDocs::with_limit(limit).order_by_score())?
|
let results: Vec<TantivyDocument> = searcher
|
||||||
|
.search(&query, &TopDocs::with_limit(limit).order_by_score())?
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, address)| searcher.doc(*address))
|
.map(|(_, address)| searcher.doc(*address))
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
|
|||||||
+16
-7
@@ -1,11 +1,13 @@
|
|||||||
use std::sync::LazyLock;
|
|
||||||
use oxigraph::model::{Term, TermRef};
|
use oxigraph::model::{Term, TermRef};
|
||||||
use oxilangtag::LanguageTag;
|
use oxilangtag::LanguageTag;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
pub const ENGLISH_PRIMARY: &str = "en";
|
pub const ENGLISH_PRIMARY: &str = "en";
|
||||||
|
|
||||||
pub static ENGLISH_OR_UNTAGGED: LazyLock<LanguageCondition> = LazyLock::new(|| {
|
pub static ENGLISH_OR_UNTAGGED: LazyLock<LanguageCondition> = LazyLock::new(|| {
|
||||||
LanguageCondition::ExactMatchOrUntagged(LanguageTag::parse(ENGLISH_PRIMARY.to_string()).unwrap())
|
LanguageCondition::ExactMatchOrUntagged(
|
||||||
|
LanguageTag::parse(ENGLISH_PRIMARY.to_string()).unwrap(),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
pub enum LanguageCondition {
|
pub enum LanguageCondition {
|
||||||
@@ -18,13 +20,16 @@ pub enum LanguageCondition {
|
|||||||
impl LanguageCondition {
|
impl LanguageCondition {
|
||||||
pub fn primary_matches_term<'a>(&self, term: impl Into<TermRef<'a>>) -> bool {
|
pub fn primary_matches_term<'a>(&self, term: impl Into<TermRef<'a>>) -> bool {
|
||||||
if let TermRef::Literal(literal) = term.into() {
|
if let TermRef::Literal(literal) = term.into() {
|
||||||
let tag = literal.language()
|
let tag = literal
|
||||||
|
.language()
|
||||||
.map(LanguageTag::parse_and_normalize)
|
.map(LanguageTag::parse_and_normalize)
|
||||||
.and_then(Result::ok);
|
.and_then(Result::ok);
|
||||||
|
|
||||||
match (tag, self) {
|
match (tag, self) {
|
||||||
(Some(language), LanguageCondition::ExactMatchOnly(expectation)) |
|
(Some(language), LanguageCondition::ExactMatchOnly(expectation))
|
||||||
(Some(language), LanguageCondition::ExactMatchOrUntagged(expectation)) => language.primary_language() == expectation.primary_language(),
|
| (Some(language), LanguageCondition::ExactMatchOrUntagged(expectation)) => {
|
||||||
|
language.primary_language() == expectation.primary_language()
|
||||||
|
}
|
||||||
(None, LanguageCondition::ExactMatchOrUntagged(_)) => true,
|
(None, LanguageCondition::ExactMatchOrUntagged(_)) => true,
|
||||||
(None, LanguageCondition::UntaggedOnly) => true,
|
(None, LanguageCondition::UntaggedOnly) => true,
|
||||||
(_, LanguageCondition::AnyOrNone) => true,
|
(_, LanguageCondition::AnyOrNone) => true,
|
||||||
@@ -45,8 +50,12 @@ impl LanguageCondition {
|
|||||||
|
|
||||||
pub fn to_filter_expression(&self, var: &str) -> String {
|
pub fn to_filter_expression(&self, var: &str) -> String {
|
||||||
match self {
|
match self {
|
||||||
LanguageCondition::ExactMatchOnly(language) => format!(r#"FILTER(langMATCHES(LANG(?{var}), "{language}"))"#),
|
LanguageCondition::ExactMatchOnly(language) => {
|
||||||
LanguageCondition::ExactMatchOrUntagged(language) => format!(r#"FILTER(langMATCHES(LANG(?{var}), "{language}") || !hasLANG(?{var}))"#),
|
format!(r#"FILTER(langMATCHES(LANG(?{var}), "{language}"))"#)
|
||||||
|
}
|
||||||
|
LanguageCondition::ExactMatchOrUntagged(language) => {
|
||||||
|
format!(r#"FILTER(langMATCHES(LANG(?{var}), "{language}") || !hasLANG(?{var}))"#)
|
||||||
|
}
|
||||||
LanguageCondition::UntaggedOnly => format!("FILTER(!hasLANG(?{var}))"),
|
LanguageCondition::UntaggedOnly => format!("FILTER(!hasLANG(?{var}))"),
|
||||||
LanguageCondition::AnyOrNone => "".to_string(),
|
LanguageCondition::AnyOrNone => "".to_string(),
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,15 +1,15 @@
|
|||||||
mod error;
|
mod error;
|
||||||
mod index;
|
mod index;
|
||||||
|
pub mod language;
|
||||||
|
pub mod rdf;
|
||||||
mod schema;
|
mod schema;
|
||||||
pub mod subtitles;
|
pub mod subtitles;
|
||||||
pub mod rdf;
|
|
||||||
pub mod language;
|
|
||||||
|
|
||||||
use oxigraph::model::NamedNodeRef;
|
use oxigraph::model::NamedNodeRef;
|
||||||
pub use tantivy::TantivyDocument as SearchDocument;
|
pub use tantivy::TantivyDocument as SearchDocument;
|
||||||
pub use tantivy::doc;
|
pub use tantivy::doc;
|
||||||
pub use tantivy::schema::document::{Document, Value};
|
|
||||||
pub use tantivy::indexer::IndexWriter;
|
pub use tantivy::indexer::IndexWriter;
|
||||||
|
pub use tantivy::schema::document::{Document, Value};
|
||||||
|
|
||||||
pub use error::{Result, SearchError};
|
pub use error::{Result, SearchError};
|
||||||
use gl_graph::vocab;
|
use gl_graph::vocab;
|
||||||
|
|||||||
+97
-36
@@ -1,14 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use oxigraph::model::{Dataset, NamedNode, NamedNodeRef, NamedOrBlankNodeRef, Term, TermRef};
|
|
||||||
use oxigraph::model::vocab::{rdf, xsd};
|
|
||||||
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
|
||||||
use oxigraph::store::Store;
|
|
||||||
use tantivy::IndexWriter;
|
|
||||||
use tantivy::schema::{Field, OwnedValue};
|
|
||||||
use gl_graph::{vocab, CurieHelper};
|
|
||||||
use crate::DocType;
|
use crate::DocType;
|
||||||
use crate::language::LanguageCondition;
|
use crate::language::LanguageCondition;
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
|
use gl_graph::{CurieHelper, vocab};
|
||||||
|
use oxigraph::model::vocab::{rdf, xsd};
|
||||||
|
use oxigraph::model::{Dataset, NamedNode, NamedNodeRef, NamedOrBlankNodeRef, Term, TermRef};
|
||||||
|
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||||
|
use oxigraph::store::Store;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tantivy::IndexWriter;
|
||||||
|
use tantivy::schema::{Field, OwnedValue};
|
||||||
|
|
||||||
fn term_to_named_node(term: &Term) -> Option<&NamedNode> {
|
fn term_to_named_node(term: &Term) -> Option<&NamedNode> {
|
||||||
if let Term::NamedNode(node) = term {
|
if let Term::NamedNode(node) = term {
|
||||||
@@ -52,13 +52,18 @@ fn term_ref_as_str(term: TermRef<'_>) -> Option<&str> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index_entity(dataset: &Dataset, language: &LanguageCondition, writer: &IndexWriter<HashMap<Field, OwnedValue>>) -> crate::Result<usize> {
|
pub fn index_entity(
|
||||||
|
dataset: &Dataset,
|
||||||
|
language: &LanguageCondition,
|
||||||
|
writer: &IndexWriter<HashMap<Field, OwnedValue>>,
|
||||||
|
) -> crate::Result<usize> {
|
||||||
let curie_helper = CurieHelper::new((&*gl_graph::PREFIXES).clone());
|
let curie_helper = CurieHelper::new((&*gl_graph::PREFIXES).clone());
|
||||||
|
|
||||||
let mut subject_type_map = HashMap::new();
|
let mut subject_type_map = HashMap::new();
|
||||||
for quad in dataset.quads_for_pattern(None, Some(rdf::TYPE), None, None) {
|
for quad in dataset.quads_for_pattern(None, Some(rdf::TYPE), None, None) {
|
||||||
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject {
|
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject {
|
||||||
subject_type_map.entry(subject)
|
subject_type_map
|
||||||
|
.entry(subject)
|
||||||
.and_modify(|types: &mut Vec<_>| types.push(quad.object))
|
.and_modify(|types: &mut Vec<_>| types.push(quad.object))
|
||||||
.or_insert_with(|| vec![quad.object]);
|
.or_insert_with(|| vec![quad.object]);
|
||||||
}
|
}
|
||||||
@@ -67,19 +72,28 @@ pub fn index_entity(dataset: &Dataset, language: &LanguageCondition, writer: &In
|
|||||||
let mut counter = 0usize;
|
let mut counter = 0usize;
|
||||||
for (subject, types) in subject_type_map {
|
for (subject, types) in subject_type_map {
|
||||||
for type_ in types {
|
for type_ in types {
|
||||||
if let Some(doc_type) = term_ref_as_named_node(type_).and_then(DocType::try_from_named_node) {
|
if let Some(doc_type) =
|
||||||
|
term_ref_as_named_node(type_).and_then(DocType::try_from_named_node)
|
||||||
|
{
|
||||||
let mut doc = match doc_type {
|
let mut doc = match doc_type {
|
||||||
DocType::Person => index_person(dataset, subject, language),
|
DocType::Person => index_person(dataset, subject, language),
|
||||||
DocType::CorporateBody => index_corporate_body(dataset, subject, language),
|
DocType::CorporateBody => index_corporate_body(dataset, subject, language),
|
||||||
_ => continue
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let subject_str = subject.as_str();
|
let subject_str = subject.as_str();
|
||||||
|
|
||||||
doc.insert(Schema::discriminant_field(), OwnedValue::U64(doc_type as u64));
|
doc.insert(
|
||||||
doc.insert(Schema::iri_field(), OwnedValue::Str(subject_str.to_string()));
|
Schema::discriminant_field(),
|
||||||
|
OwnedValue::U64(doc_type as u64),
|
||||||
|
);
|
||||||
|
doc.insert(
|
||||||
|
Schema::iri_field(),
|
||||||
|
OwnedValue::Str(subject_str.to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
let curie = curie_helper.abbreviate(None, subject_str)
|
let curie = curie_helper
|
||||||
|
.abbreviate(None, subject_str)
|
||||||
.map(OwnedValue::Str)
|
.map(OwnedValue::Str)
|
||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
doc.insert(Schema::curie_field(), curie);
|
doc.insert(Schema::curie_field(), curie);
|
||||||
@@ -93,8 +107,18 @@ pub fn index_entity(dataset: &Dataset, language: &LanguageCondition, writer: &In
|
|||||||
Ok(counter)
|
Ok(counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_person(dataset: &Dataset, subject: NamedNodeRef<'_>, language: &LanguageCondition) -> HashMap<Field, OwnedValue> {
|
fn index_person(
|
||||||
let given_name = dataset.quads_for_pattern(Some(subject.into()), Some(vocab::rdaad::GIVEN_NAME), None, None)
|
dataset: &Dataset,
|
||||||
|
subject: NamedNodeRef<'_>,
|
||||||
|
language: &LanguageCondition,
|
||||||
|
) -> HashMap<Field, OwnedValue> {
|
||||||
|
let given_name = dataset
|
||||||
|
.quads_for_pattern(
|
||||||
|
Some(subject.into()),
|
||||||
|
Some(vocab::rdaad::GIVEN_NAME),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
.map(|q| q.object)
|
.map(|q| q.object)
|
||||||
.filter(|term| language.primary_matches_term(*term))
|
.filter(|term| language.primary_matches_term(*term))
|
||||||
.filter_map(term_ref_as_str)
|
.filter_map(term_ref_as_str)
|
||||||
@@ -103,7 +127,13 @@ fn index_person(dataset: &Dataset, subject: NamedNodeRef<'_>, language: &Languag
|
|||||||
.next()
|
.next()
|
||||||
.unwrap_or_else(|| OwnedValue::Null);
|
.unwrap_or_else(|| OwnedValue::Null);
|
||||||
|
|
||||||
let surname = dataset.quads_for_pattern(Some(subject.into()), Some(vocab::rdaad::SURNAME), None, None)
|
let surname = dataset
|
||||||
|
.quads_for_pattern(
|
||||||
|
Some(subject.into()),
|
||||||
|
Some(vocab::rdaad::SURNAME),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
.map(|q| q.object)
|
.map(|q| q.object)
|
||||||
.filter(|term| language.primary_matches_term(*term))
|
.filter(|term| language.primary_matches_term(*term))
|
||||||
.filter_map(term_ref_as_str)
|
.filter_map(term_ref_as_str)
|
||||||
@@ -113,13 +143,29 @@ fn index_person(dataset: &Dataset, subject: NamedNodeRef<'_>, language: &Languag
|
|||||||
.unwrap_or_else(|| OwnedValue::Null);
|
.unwrap_or_else(|| OwnedValue::Null);
|
||||||
|
|
||||||
HashMap::from_iter([
|
HashMap::from_iter([
|
||||||
(Schema::field("given_name", language.primary_language()), given_name),
|
(
|
||||||
(Schema::field("surname", language.primary_language()), surname),
|
Schema::field("given_name", language.primary_language()),
|
||||||
|
given_name,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Schema::field("surname", language.primary_language()),
|
||||||
|
surname,
|
||||||
|
),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_corporate_body(dataset: &Dataset, subject: NamedNodeRef<'_>, language: &LanguageCondition) -> HashMap<Field, OwnedValue> {
|
fn index_corporate_body(
|
||||||
let name = dataset.quads_for_pattern(Some(subject.into()), Some(vocab::rdaad::NAME_OF_CORPORATE_BODY), None, None)
|
dataset: &Dataset,
|
||||||
|
subject: NamedNodeRef<'_>,
|
||||||
|
language: &LanguageCondition,
|
||||||
|
) -> HashMap<Field, OwnedValue> {
|
||||||
|
let name = dataset
|
||||||
|
.quads_for_pattern(
|
||||||
|
Some(subject.into()),
|
||||||
|
Some(vocab::rdaad::NAME_OF_CORPORATE_BODY),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
.map(|q| q.object)
|
.map(|q| q.object)
|
||||||
.filter(|term| language.primary_matches_term(*term))
|
.filter(|term| language.primary_matches_term(*term))
|
||||||
.filter_map(term_ref_as_str)
|
.filter_map(term_ref_as_str)
|
||||||
@@ -128,18 +174,24 @@ fn index_corporate_body(dataset: &Dataset, subject: NamedNodeRef<'_>, language:
|
|||||||
.next()
|
.next()
|
||||||
.unwrap_or_else(|| OwnedValue::Null);
|
.unwrap_or_else(|| OwnedValue::Null);
|
||||||
|
|
||||||
HashMap::from_iter([
|
HashMap::from_iter([(
|
||||||
(Schema::field("corporate_name", language.primary_language()), name),
|
Schema::field("corporate_name", language.primary_language()),
|
||||||
])
|
name,
|
||||||
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index_schema(store: Store, language: &LanguageCondition, writer: &IndexWriter<HashMap<Field, OwnedValue>>) -> crate::Result<usize> {
|
pub fn index_schema(
|
||||||
|
store: Store,
|
||||||
|
language: &LanguageCondition,
|
||||||
|
writer: &IndexWriter<HashMap<Field, OwnedValue>>,
|
||||||
|
) -> crate::Result<usize> {
|
||||||
let curie_helper = CurieHelper::new((&*gl_graph::PREFIXES).clone());
|
let curie_helper = CurieHelper::new((&*gl_graph::PREFIXES).clone());
|
||||||
|
|
||||||
let label_filter = language.to_filter_expression("label");
|
let label_filter = language.to_filter_expression("label");
|
||||||
let description_filter = language.to_filter_expression("description");
|
let description_filter = language.to_filter_expression("description");
|
||||||
|
|
||||||
let query = format!(r#"SELECT ?class ?subject ?label ?description WHERE {{
|
let query = format!(
|
||||||
|
r#"SELECT ?class ?subject ?label ?description WHERE {{
|
||||||
VALUES ?class {{ rdf:Property rdfs:Class skos:Concept }}
|
VALUES ?class {{ rdf:Property rdfs:Class skos:Concept }}
|
||||||
?subject a ?class ;
|
?subject a ?class ;
|
||||||
rdfs:label ?label .
|
rdfs:label ?label .
|
||||||
@@ -149,11 +201,15 @@ pub fn index_schema(store: Store, language: &LanguageCondition, writer: &IndexWr
|
|||||||
OPTIONAL {{ ?subject skos:definition ?definition }}
|
OPTIONAL {{ ?subject skos:definition ?definition }}
|
||||||
BIND(COALESCE(?definition, ?comment) AS ?description)
|
BIND(COALESCE(?definition, ?comment) AS ?description)
|
||||||
{description_filter}
|
{description_filter}
|
||||||
}}"#);
|
}}"#
|
||||||
|
);
|
||||||
let mut sparql = SparqlEvaluator::new()
|
let mut sparql = SparqlEvaluator::new()
|
||||||
.with_prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#").unwrap()
|
.with_prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
|
||||||
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#").unwrap()
|
.unwrap()
|
||||||
.with_prefix("skos", "http://www.w3.org/2004/02/skos/core#").unwrap()
|
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")
|
||||||
|
.unwrap()
|
||||||
|
.with_prefix("skos", "http://www.w3.org/2004/02/skos/core#")
|
||||||
|
.unwrap()
|
||||||
.parse_query(&query)?;
|
.parse_query(&query)?;
|
||||||
sparql.dataset_mut().set_default_graph_as_union();
|
sparql.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
@@ -167,7 +223,8 @@ pub fn index_schema(store: Store, language: &LanguageCondition, writer: &IndexWr
|
|||||||
for solution in solutions.filter_map(Result::ok) {
|
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.get("class")
|
let discriminant = solution
|
||||||
|
.get("class")
|
||||||
.and_then(term_to_named_node)
|
.and_then(term_to_named_node)
|
||||||
.and_then(DocType::try_from_named_node)
|
.and_then(DocType::try_from_named_node)
|
||||||
.map(|doc_type| doc_type as u64)
|
.map(|doc_type| doc_type as u64)
|
||||||
@@ -175,7 +232,8 @@ pub fn index_schema(store: Store, language: &LanguageCondition, writer: &IndexWr
|
|||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
document.insert(Schema::discriminant_field(), discriminant);
|
document.insert(Schema::discriminant_field(), discriminant);
|
||||||
|
|
||||||
let subject_str = solution.get("subject")
|
let subject_str = solution
|
||||||
|
.get("subject")
|
||||||
.and_then(term_to_named_node)
|
.and_then(term_to_named_node)
|
||||||
.map(NamedNode::as_str);
|
.map(NamedNode::as_str);
|
||||||
|
|
||||||
@@ -185,17 +243,20 @@ pub fn index_schema(store: Store, language: &LanguageCondition, writer: &IndexWr
|
|||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
document.insert(Schema::curie_field(), curie);
|
document.insert(Schema::curie_field(), curie);
|
||||||
|
|
||||||
let subject = subject_str.map(OwnedValue::from)
|
let subject = subject_str
|
||||||
|
.map(OwnedValue::from)
|
||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
document.insert(Schema::iri_field(), subject);
|
document.insert(Schema::iri_field(), subject);
|
||||||
|
|
||||||
let label = solution.get("label")
|
let label = solution
|
||||||
|
.get("label")
|
||||||
.and_then(term_as_str)
|
.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.get("description")
|
let definition = solution
|
||||||
|
.get("description")
|
||||||
.and_then(term_as_str)
|
.and_then(term_as_str)
|
||||||
.map(OwnedValue::from)
|
.map(OwnedValue::from)
|
||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
|
|||||||
@@ -11,17 +11,21 @@ pub struct Schema;
|
|||||||
impl Schema {
|
impl Schema {
|
||||||
pub fn schema() -> &'static TantivySchema {
|
pub fn schema() -> &'static TantivySchema {
|
||||||
SCHEMA.get_or_init(|| {
|
SCHEMA.get_or_init(|| {
|
||||||
let stored_ngram32 = TextOptions::default().set_indexing_options(
|
let stored_ngram32 = TextOptions::default()
|
||||||
|
.set_indexing_options(
|
||||||
TextFieldIndexing::default()
|
TextFieldIndexing::default()
|
||||||
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
|
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
|
||||||
.set_tokenizer("ngram_32"),
|
.set_tokenizer("ngram_32"),
|
||||||
).set_stored();
|
)
|
||||||
|
.set_stored();
|
||||||
|
|
||||||
let stored_en_stem = TextOptions::default().set_indexing_options(
|
let stored_en_stem = TextOptions::default()
|
||||||
|
.set_indexing_options(
|
||||||
TextFieldIndexing::default()
|
TextFieldIndexing::default()
|
||||||
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
|
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
|
||||||
.set_tokenizer("en_stem"),
|
.set_tokenizer("en_stem"),
|
||||||
).set_stored();
|
)
|
||||||
|
.set_stored();
|
||||||
|
|
||||||
let mut schema_builder = TantivySchema::builder();
|
let mut schema_builder = TantivySchema::builder();
|
||||||
schema_builder.add_u64_field("discriminant", schema::FAST | schema::INDEXED);
|
schema_builder.add_u64_field("discriminant", schema::FAST | schema::INDEXED);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use crate::{Schema, SearchDocument};
|
||||||
use subtitler;
|
use subtitler;
|
||||||
use subtitler::SubtitleFormat;
|
use subtitler::SubtitleFormat;
|
||||||
use tantivy::doc;
|
use tantivy::doc;
|
||||||
use crate::{Schema, SearchDocument};
|
|
||||||
|
|
||||||
pub fn bytes_to_documents(data: &[u8]) -> crate::Result<Vec<SearchDocument>> {
|
pub fn bytes_to_documents(data: &[u8]) -> crate::Result<Vec<SearchDocument>> {
|
||||||
let subtitle_start = Schema::field("subtitle_start", None);
|
let subtitle_start = Schema::field("subtitle_start", None);
|
||||||
@@ -10,12 +10,15 @@ pub fn bytes_to_documents(data: &[u8]) -> crate::Result<Vec<SearchDocument>> {
|
|||||||
|
|
||||||
let subtitle_file = subtitler::parse_bytes(data)?;
|
let subtitle_file = subtitler::parse_bytes(data)?;
|
||||||
let subtitles = subtitle_file.subtitles();
|
let subtitles = subtitle_file.subtitles();
|
||||||
let documents = subtitles.iter().map(|subtitle| {
|
let documents = subtitles
|
||||||
|
.iter()
|
||||||
|
.map(|subtitle| {
|
||||||
doc!(
|
doc!(
|
||||||
subtitle_start => subtitle.start,
|
subtitle_start => subtitle.start,
|
||||||
subtitle_end => subtitle.end,
|
subtitle_end => subtitle.end,
|
||||||
subtitle_content => subtitle.text,
|
subtitle_content => subtitle.text,
|
||||||
)
|
)
|
||||||
}).collect::<Vec<_>>();
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
Ok(documents)
|
Ok(documents)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user