.
This commit is contained in:
@@ -135,6 +135,7 @@ ldp:contains rdf:type owl:DatatypeProperty ;
|
|||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/catalogId
|
### https://graphofliberty.org/2026/04/ont/catalogId
|
||||||
:catalogId rdf:type owl:DatatypeProperty ;
|
:catalogId rdf:type owl:DatatypeProperty ;
|
||||||
|
rdfs:domain :Entity ;
|
||||||
rdfs:comment "An integer associated with the class for fast lookup in a database."@en ;
|
rdfs:comment "An integer associated with the class for fast lookup in a database."@en ;
|
||||||
rdfs:label "catalog id" .
|
rdfs:label "catalog id" .
|
||||||
|
|
||||||
@@ -165,6 +166,7 @@ ldp:contains rdf:type owl:DatatypeProperty ;
|
|||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/Entity
|
### https://graphofliberty.org/2026/04/ont/Entity
|
||||||
:Entity rdf:type owl:Class ;
|
:Entity rdf:type owl:Class ;
|
||||||
|
rdfs:comment "An Entity is a first-class citizen of the Graph of Liberty catalog. It is the class of all cultural artifacts which are to be preserved and indexed."@en ;
|
||||||
rdfs:label "Graph of Liberty Entity"@en .
|
rdfs:label "Graph of Liberty Entity"@en .
|
||||||
|
|
||||||
|
|
||||||
@@ -214,16 +216,20 @@ fedora:lastModifiedBy rdf:type owl:NamedIndividual ;
|
|||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
|
### http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
|
||||||
rdf:Property rdf:type owl:NamedIndividual ;
|
rdf:Property rdf:type owl:NamedIndividual ,
|
||||||
|
:Entity ;
|
||||||
:associatedProperty rdfs:comment ,
|
:associatedProperty rdfs:comment ,
|
||||||
rdfs:label ;
|
rdfs:label ,
|
||||||
|
skos:definition ;
|
||||||
:catalogId "0"^^xsd:nonNegativeInteger .
|
:catalogId "0"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/2000/01/rdf-schema#Class
|
### http://www.w3.org/2000/01/rdf-schema#Class
|
||||||
rdfs:Class rdf:type owl:NamedIndividual ;
|
rdfs:Class rdf:type owl:NamedIndividual ,
|
||||||
|
:Entity ;
|
||||||
:associatedProperty rdfs:comment ,
|
:associatedProperty rdfs:comment ,
|
||||||
rdfs:label ;
|
rdfs:label ,
|
||||||
|
skos:definition ;
|
||||||
:catalogId "1"^^xsd:nonNegativeInteger .
|
:catalogId "1"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+61
-68
@@ -1,6 +1,6 @@
|
|||||||
use crate::rdf::ontology::{LabeledIri, Ontology};
|
use crate::rdf::ontology::{LabeledIri, Ontology};
|
||||||
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
||||||
use gl_search::{Schema, SearchDocument, SearchIndex, doc, IndexWriter};
|
use gl_search::{Schema, SearchDocument, SearchIndex, doc, IndexWriter, Value};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use iced::alignment::Horizontal;
|
use iced::alignment::Horizontal;
|
||||||
use iced::widget::button::Style;
|
use iced::widget::button::Style;
|
||||||
@@ -8,6 +8,7 @@ use iced::widget::grid::Sizing;
|
|||||||
use iced::widget::{button, center, column, combo_box, container, grid, mouse_area, opaque, pick_list, row, scrollable, space, stack, table, text, text_input, toggler};
|
use iced::widget::{button, center, column, combo_box, container, grid, mouse_area, opaque, 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, window};
|
use iced::{Background, Color, Element, Length, Subscription, Task, color, window};
|
||||||
|
use iced::widget::text::Wrapping;
|
||||||
use ldp::middleware::BasicAuthMiddleware;
|
use ldp::middleware::BasicAuthMiddleware;
|
||||||
use ldp::model::{KeyedDataset, QuadKey};
|
use ldp::model::{KeyedDataset, QuadKey};
|
||||||
use ldp::reqwest::{Client, Url};
|
use ldp::reqwest::{Client, Url};
|
||||||
@@ -16,9 +17,9 @@ use ldp::traverse::Traverse;
|
|||||||
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, NamedNode, Quad, Term, TermRef};
|
use oxigraph::model::{BaseDirection, Dataset, NamedNode, NamedNodeRef, Quad, Term, TermRef};
|
||||||
use tracing::{debug_span, error, trace};
|
use tracing::{debug, debug_span, error, trace};
|
||||||
use crate::rdf::conversion;
|
use crate::rdf::language;
|
||||||
use crate::widget::iri_input::iri_input;
|
use crate::widget::iri_input::iri_input;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -108,39 +109,23 @@ impl Publisher {
|
|||||||
.build()
|
.build()
|
||||||
.expect("Failed to build search index");
|
.expect("Failed to build search index");
|
||||||
|
|
||||||
let classes_to_index = [
|
|
||||||
rdf::PROPERTY,
|
|
||||||
rdfs::CLASS,
|
|
||||||
];
|
|
||||||
|
|
||||||
debug_span!("Ontology Indexing").in_scope(|| {
|
debug_span!("Ontology Indexing").in_scope(|| {
|
||||||
|
let mut counter = 0;
|
||||||
let mut writer = index.writer().expect("Failed to build index writer");
|
let mut writer = index.writer().expect("Failed to build index writer");
|
||||||
for class in &classes_to_index {
|
let index = ontology.index(&*language::ENGLISH_OR_UNTAGGED).expect("Unable to generate index of entities");
|
||||||
let class = class.into_owned();
|
for (individual, entry) in index {
|
||||||
if let Some(id) = ontology.catalog_id(&class) {
|
let mut document = doc!(
|
||||||
writer.remove_all_of_type(id).expect("Failed to remove documents from search index");
|
Schema::type_field() => entry.catalog_id,
|
||||||
for individual in ontology.individuals(&class) {
|
Schema::iri_field() => individual.as_str(),
|
||||||
let info = ontology.info(&individual, &conversion::english());
|
);
|
||||||
if info.label.is_some() || info.comment.is_some() {
|
for (key, value) in entry.fields {
|
||||||
let mut document = doc!(
|
document.add_text(Schema::field(&key, language::ENGLISH_PRIMARY), value.as_str());
|
||||||
Schema::type_field() => id,
|
|
||||||
Schema::iri_field() => individual.as_str(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(field) = ontology.field_for_property(&rdfs::LABEL.into_owned()) {
|
|
||||||
document.add_text(Schema::field(&field.name), info.label.unwrap_or_default());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(field) = ontology.field_for_property(&rdfs::COMMENT.into_owned()) {
|
|
||||||
document.add_text(Schema::field(&field.name), info.comment.unwrap_or_default());
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.add(document).expect("Failed to add document to search index");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
writer.add(document).expect("Failed to add document to search index");
|
||||||
|
counter += 1;
|
||||||
}
|
}
|
||||||
writer.commit().expect("Failed to commit changes to search index");
|
writer.commit().expect("Failed to commit changes to search index");
|
||||||
|
debug!("Added {counter} documents to search index");
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut abbreviated_datatypes = ontology
|
let mut abbreviated_datatypes = ontology
|
||||||
@@ -207,7 +192,7 @@ impl Publisher {
|
|||||||
document.add_u64(Schema::type_field(), catalog_id);
|
document.add_u64(Schema::type_field(), catalog_id);
|
||||||
document.add_text(Schema::iri_field(), rdf_source.origin());
|
document.add_text(Schema::iri_field(), rdf_source.origin());
|
||||||
|
|
||||||
for quad in rdf_source.dataset() {
|
/*for quad in rdf_source.dataset() {
|
||||||
if let Some(field) =
|
if let Some(field) =
|
||||||
self.ontology.field_for_property(&quad.predicate.into_owned())
|
self.ontology.field_for_property(&quad.predicate.into_owned())
|
||||||
&& let TermRef::Literal(literal) = quad.object
|
&& let TermRef::Literal(literal) = quad.object
|
||||||
@@ -217,7 +202,7 @@ impl Publisher {
|
|||||||
.expect("Field not found in schema");
|
.expect("Field not found in schema");
|
||||||
document.add_text(field, literal.value());
|
document.add_text(field, literal.value());
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
if let Some(writer) = &self.index_writer {
|
if let Some(writer) = &self.index_writer {
|
||||||
writer.add(document).expect("Unable to add document to search index");
|
writer.add(document).expect("Unable to add document to search index");
|
||||||
@@ -328,13 +313,13 @@ impl Publisher {
|
|||||||
if let Some(search_state) = &mut self.search_state {
|
if let Some(search_state) = &mut self.search_state {
|
||||||
search_state.action = action;
|
search_state.action = action;
|
||||||
} else {
|
} else {
|
||||||
if let Some(type_) = self.ontology.labeled_entities().next() {
|
if let Some(entity) = self.ontology.labeled_entities(&*language::ENGLISH_OR_UNTAGGED).next() {
|
||||||
let (id, window_task) = window::open(Settings::default());
|
let (id, window_task) = window::open(Settings::default());
|
||||||
self.search_state = Some(SearchState {
|
self.search_state = Some(SearchState {
|
||||||
window_id: id,
|
window_id: id,
|
||||||
action,
|
action,
|
||||||
query: String::new(),
|
query: String::new(),
|
||||||
type_,
|
type_: entity,
|
||||||
results: Vec::new(),
|
results: Vec::new(),
|
||||||
});
|
});
|
||||||
task = window_task.map(|_| Message::None)
|
task = window_task.map(|_| Message::None)
|
||||||
@@ -350,6 +335,7 @@ 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 selected_entity_class = &search_state.type_.iri;
|
||||||
let catalog_id = match search_state.action {
|
let catalog_id = match search_state.action {
|
||||||
SearchResultClickAction::Predicate(_) => self.ontology.catalog_id(&rdf::PROPERTY.into_owned()),
|
SearchResultClickAction::Predicate(_) => self.ontology.catalog_id(&rdf::PROPERTY.into_owned()),
|
||||||
SearchResultClickAction::Object(key) => {
|
SearchResultClickAction::Object(key) => {
|
||||||
@@ -360,10 +346,10 @@ impl Publisher {
|
|||||||
.and_then(|quad| if quad.predicate == rdf::TYPE {
|
.and_then(|quad| if quad.predicate == rdf::TYPE {
|
||||||
self.ontology.catalog_id(&rdfs::CLASS.into_owned())
|
self.ontology.catalog_id(&rdfs::CLASS.into_owned())
|
||||||
} else {
|
} else {
|
||||||
self.ontology.catalog_id(&search_state.type_.iri)
|
self.ontology.catalog_id(selected_entity_class)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
SearchResultClickAction::URLInput => self.ontology.catalog_id(&search_state.type_.iri),
|
SearchResultClickAction::URLInput => self.ontology.catalog_id(selected_entity_class),
|
||||||
};
|
};
|
||||||
|
|
||||||
let index = self.index.clone();
|
let index = self.index.clone();
|
||||||
@@ -373,7 +359,7 @@ impl Publisher {
|
|||||||
});
|
});
|
||||||
task = Task::future(async {
|
task = Task::future(async {
|
||||||
match search_task.await {
|
match search_task.await {
|
||||||
Ok(Ok(documents)) => Message::SetSearchResults(documents),
|
Ok(Ok(results)) => Message::SetSearchResults(results),
|
||||||
Ok(Err(err)) => Message::ShowError(err.to_string()),
|
Ok(Err(err)) => Message::ShowError(err.to_string()),
|
||||||
Err(err) => Message::ShowError(err.to_string()),
|
Err(err) => Message::ShowError(err.to_string()),
|
||||||
}
|
}
|
||||||
@@ -556,9 +542,8 @@ impl Publisher {
|
|||||||
|
|
||||||
let property_label = self
|
let property_label = self
|
||||||
.ontology
|
.ontology
|
||||||
.info(&triple.predicate, &conversion::english())
|
.info(&triple.predicate, &*language::ENGLISH_OR_UNTAGGED)
|
||||||
.label
|
.label;
|
||||||
.unwrap_or(self.ontology.abbreviate(triple.predicate.as_ref()));
|
|
||||||
|
|
||||||
let property: Element<Message> = if state.read_only {
|
let property: Element<Message> = if state.read_only {
|
||||||
text(property_label).into()
|
text(property_label).into()
|
||||||
@@ -574,10 +559,8 @@ impl Publisher {
|
|||||||
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| {
|
||||||
self.ontology
|
let info = self.ontology.info(&node.into_owned(), &*language::ENGLISH_OR_UNTAGGED);
|
||||||
.info(&node.into_owned(), &conversion::english())
|
Some(container(text(info.label)))
|
||||||
.label
|
|
||||||
.map(|label| container(text(label)))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let value = term
|
let value = term
|
||||||
@@ -668,9 +651,8 @@ impl Publisher {
|
|||||||
let buttons = entities.map(|entity| {
|
let buttons = entities.map(|entity| {
|
||||||
let label = self
|
let label = self
|
||||||
.ontology
|
.ontology
|
||||||
.info(&entity, &conversion::english())
|
.info(&entity, &*language::ENGLISH_OR_UNTAGGED)
|
||||||
.label
|
.label;
|
||||||
.unwrap_or(entity.as_str().to_string());
|
|
||||||
button(text(label))
|
button(text(label))
|
||||||
.on_press(Message::NewDocument(entity))
|
.on_press(Message::NewDocument(entity))
|
||||||
.into()
|
.into()
|
||||||
@@ -689,35 +671,46 @@ impl Publisher {
|
|||||||
text_input("Query", &search_state.query).on_input(Message::QueryUpdated);
|
text_input("Query", &search_state.query).on_input(Message::QueryUpdated);
|
||||||
|
|
||||||
let mut entities = self.ontology
|
let mut entities = self.ontology
|
||||||
.labeled_entities()
|
.labeled_entities(&*language::ENGLISH_OR_UNTAGGED)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
entities.sort_by(|a, b| Ord::cmp(&a.label, &b.label));
|
entities.sort_by(|a, b| Ord::cmp(&a.label, &b.label));
|
||||||
|
|
||||||
let type_selector = pick_list(Some(&search_state.type_), entities, ToString::to_string)
|
let type_selector = pick_list(Some(&search_state.type_), entities, ToString::to_string)
|
||||||
.on_select(|selection| Message::QueryTypeUpdated(selection.iri));
|
.on_select(|selection| Message::QueryTypeUpdated(selection.iri));
|
||||||
|
|
||||||
let mut columns = vec![];
|
let fields = self.ontology.fields_for_class(&search_state.type_.iri, &*language::ENGLISH_OR_UNTAGGED)
|
||||||
for field in self.ontology.fields_for_class(&search_state.type_.iri) {
|
.expect("Unable to load fields for class");
|
||||||
let header_text = field.label.as_ref().unwrap_or(&field.name);
|
|
||||||
/*columns.push(table::column(text(header_text), |document: &QueryResult| {
|
|
||||||
let cell_value = document.0.get(&field.name)
|
|
||||||
.and_then(|values| values.first())
|
|
||||||
.map(|value| match value {
|
|
||||||
OwnedValue::Str(string) => string,
|
|
||||||
_ => "???",
|
|
||||||
}).unwrap_or("");
|
|
||||||
|
|
||||||
let iri = document.get("iri")
|
let mut columns = vec![
|
||||||
.and_then(|values| values.first())
|
table::column(text("CURIE"), |document: &SearchDocument| {
|
||||||
.map(|value| match value {
|
let iri = document.get_first(Schema::iri_field())
|
||||||
OwnedValue::Str(string) => string,
|
.and_then(|value| value.as_str())
|
||||||
_ => "???",
|
.unwrap_or_default();
|
||||||
}).unwrap_or("");
|
|
||||||
|
|
||||||
button(text(cell_value).wrapping(Wrapping::Word))
|
let abbreviated_iri = self.ontology.abbreviate(NamedNodeRef::new_unchecked(iri));
|
||||||
|
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)
|
||||||
}).width(Length::Fixed(256.0)));*/
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
for field in fields.into_iter() {
|
||||||
|
let field_name = field.name.clone();
|
||||||
|
columns.push(
|
||||||
|
table::column(text(field.label), move |document: &SearchDocument| {
|
||||||
|
let iri = document.get_first(Schema::iri_field())
|
||||||
|
.and_then(|value| value.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let value = document.get_first(Schema::field(&field_name, language::ENGLISH_PRIMARY))
|
||||||
|
.and_then(|value| value.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
button(text(value).wrapping(Wrapping::Word))
|
||||||
|
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||||
|
.style(button::text)
|
||||||
|
}).width(Length::Fixed(256.0))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let results_table = if columns.is_empty() {
|
let results_table = if columns.is_empty() {
|
||||||
|
|||||||
@@ -2,38 +2,10 @@ use oxigraph::model::{NamedNode, Quad, Term};
|
|||||||
use oxigraph::model::vocab::{rdf, xsd};
|
use oxigraph::model::vocab::{rdf, xsd};
|
||||||
use oxilangtag::LanguageTag;
|
use oxilangtag::LanguageTag;
|
||||||
|
|
||||||
pub enum LanguageCondition {
|
|
||||||
ExactMatch(LanguageTag<String>),
|
|
||||||
RelaxedMatch(LanguageTag<String>),
|
|
||||||
Untagged,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn quad_into_term(quad: Quad) -> Term {
|
pub fn quad_into_term(quad: Quad) -> Term {
|
||||||
quad.object
|
quad.object
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn english() -> LanguageCondition {
|
|
||||||
LanguageCondition::RelaxedMatch(LanguageTag::parse("en".to_string()).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn language_matches(term: &Term, condition: &LanguageCondition) -> bool {
|
|
||||||
if let Term::Literal(literal) = term {
|
|
||||||
let tag = literal.language()
|
|
||||||
.map(LanguageTag::parse_and_normalize)
|
|
||||||
.and_then(Result::ok);
|
|
||||||
|
|
||||||
match (tag, condition) {
|
|
||||||
(Some(language), LanguageCondition::ExactMatch(expectation)) |
|
|
||||||
(Some(language), LanguageCondition::RelaxedMatch(expectation)) => language == *expectation,
|
|
||||||
(None, LanguageCondition::RelaxedMatch(_)) => true,
|
|
||||||
(None, LanguageCondition::Untagged) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn term_to_named_node(term: &Term) -> Option<&NamedNode> {
|
pub fn term_to_named_node(term: &Term) -> Option<&NamedNode> {
|
||||||
if let Term::NamedNode(node) = term {
|
if let Term::NamedNode(node) = term {
|
||||||
Some(node)
|
Some(node)
|
||||||
@@ -59,6 +31,18 @@ pub fn term_as_str(term: &Term) -> Option<&str> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*pub fn term_to_tagged_string(term: &Term) -> Option<(String, Option<LanguageTag<String>>)> {
|
||||||
|
term_as_str(&term).and_then(|value| {
|
||||||
|
if let Term::Literal(literal) = term {
|
||||||
|
literal.language()
|
||||||
|
.map(|language| {
|
||||||
|
let tag = LanguageTag::parse(language.to_string()).ok();
|
||||||
|
(value.to_string(), tag)
|
||||||
|
})
|
||||||
|
} else { None }
|
||||||
|
})
|
||||||
|
}*/
|
||||||
|
|
||||||
pub fn term_to_boolean(term: &Term) -> Option<bool> {
|
pub fn term_to_boolean(term: &Term) -> Option<bool> {
|
||||||
if let Term::Literal(literal) = term {
|
if let Term::Literal(literal) = term {
|
||||||
if literal.datatype() == xsd::BOOLEAN {
|
if literal.datatype() == xsd::BOOLEAN {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use std::sync::LazyLock;
|
||||||
|
use oxigraph::model::Term;
|
||||||
|
use oxilangtag::LanguageTag;
|
||||||
|
|
||||||
|
pub const ENGLISH_PRIMARY: &str = "en";
|
||||||
|
|
||||||
|
pub static ENGLISH_OR_UNTAGGED: LazyLock<LanguageCondition> = LazyLock::new(|| {
|
||||||
|
LanguageCondition::ExactMatchOrUntagged(LanguageTag::parse(ENGLISH_PRIMARY.to_string()).unwrap())
|
||||||
|
});
|
||||||
|
|
||||||
|
pub enum LanguageCondition {
|
||||||
|
ExactMatchOnly(LanguageTag<String>),
|
||||||
|
ExactMatchOrUntagged(LanguageTag<String>),
|
||||||
|
UntaggedOnly,
|
||||||
|
AnyOrNone,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageCondition {
|
||||||
|
pub fn primary_matches_term(&self, term: &Term) -> bool {
|
||||||
|
if let Term::Literal(literal) = term {
|
||||||
|
let tag = literal.language()
|
||||||
|
.map(LanguageTag::parse_and_normalize)
|
||||||
|
.and_then(Result::ok);
|
||||||
|
|
||||||
|
match (tag, self) {
|
||||||
|
(Some(language), LanguageCondition::ExactMatchOnly(expectation)) |
|
||||||
|
(Some(language), LanguageCondition::ExactMatchOrUntagged(expectation)) => language.primary_language() == expectation.primary_language(),
|
||||||
|
(None, LanguageCondition::ExactMatchOrUntagged(_)) => true,
|
||||||
|
(None, LanguageCondition::UntaggedOnly) => true,
|
||||||
|
(_, LanguageCondition::AnyOrNone) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_filter_expression(&self, var: &str) -> String {
|
||||||
|
match self {
|
||||||
|
LanguageCondition::ExactMatchOnly(language) => 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::AnyOrNone => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,3 +3,4 @@ pub(crate) mod term_helper;
|
|||||||
pub mod vocab;
|
pub mod vocab;
|
||||||
pub(crate) mod materialize;
|
pub(crate) mod materialize;
|
||||||
pub(crate) mod conversion;
|
pub(crate) mod conversion;
|
||||||
|
pub(crate) mod language;
|
||||||
+114
-126
@@ -1,3 +1,4 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::rdf::vocab::gl;
|
use crate::rdf::vocab::gl;
|
||||||
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
||||||
@@ -7,10 +8,12 @@ use std::collections::{BTreeMap, HashMap, HashSet};
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
use iced::widget::sensor::Key;
|
||||||
use oxigraph::store::Store;
|
use oxigraph::store::Store;
|
||||||
use tracing::debug_span;
|
use oxilangtag::LanguageTag;
|
||||||
use crate::rdf::{conversion, materialize};
|
use tracing::{debug, debug_span};
|
||||||
use crate::rdf::conversion::LanguageCondition;
|
use crate::rdf::{conversion, language, materialize};
|
||||||
|
use crate::rdf::language::LanguageCondition;
|
||||||
|
|
||||||
static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
||||||
BTreeMap::from_iter([
|
BTreeMap::from_iter([
|
||||||
@@ -30,14 +33,15 @@ static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
|||||||
("dcterms", "http://purl.org/dc/terms/"),
|
("dcterms", "http://purl.org/dc/terms/"),
|
||||||
("fedora", "http://fedora.info/definitions/v4/repository#"),
|
("fedora", "http://fedora.info/definitions/v4/repository#"),
|
||||||
("rdaa", "http://rdaregistry.info/Elements/a/"),
|
("rdaa", "http://rdaregistry.info/Elements/a/"),
|
||||||
("rdaad", "http://rdaregistry.info/Elements/a/datatype/"),
|
|
||||||
("rdac", "http://rdaregistry.info/Elements/c/"),
|
("rdac", "http://rdaregistry.info/Elements/c/"),
|
||||||
("rdae", "http://rdaregistry.info/Elements/e/"),
|
("rdae", "http://rdaregistry.info/Elements/e/"),
|
||||||
("rdai", "http://rdaregistry.info/Elements/i/"),
|
("rdai", "http://rdaregistry.info/Elements/i/"),
|
||||||
("rdam", "http://rdaregistry.info/Elements/m/"),
|
("rdam", "http://rdaregistry.info/Elements/m/"),
|
||||||
("rdan", "http://rdaregistry.info/Elements/n/"),
|
("rdan", "http://rdaregistry.info/Elements/n/"),
|
||||||
("rdap", "http://rdaregistry.info/Elements/p/"),
|
("rdap", "http://rdaregistry.info/Elements/p/"),
|
||||||
|
("rdaf", "http://rdaregistry.info/Elements/rof/"),
|
||||||
("rdat", "http://rdaregistry.info/Elements/t/"),
|
("rdat", "http://rdaregistry.info/Elements/t/"),
|
||||||
|
("rdau", "http://rdaregistry.info/Elements/u/"),
|
||||||
("rdaw", "http://rdaregistry.info/Elements/w/"),
|
("rdaw", "http://rdaregistry.info/Elements/w/"),
|
||||||
("rdax", "http://rdaregistry.info/Elements/x/"),
|
("rdax", "http://rdaregistry.info/Elements/x/"),
|
||||||
("schema", "https://schema.org/"),
|
("schema", "https://schema.org/"),
|
||||||
@@ -69,17 +73,14 @@ impl OntologyBuilder {
|
|||||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
.collect::<HashMap<String, String>>();
|
.collect::<HashMap<String, String>>();
|
||||||
|
|
||||||
materialize::same_as(&mut store)?;
|
/*materialize::same_as(&mut store)?;
|
||||||
materialize::super_properties(&mut store)?;
|
materialize::super_properties(&mut store)?;
|
||||||
materialize::super_classes(&mut store)?;
|
materialize::super_classes(&mut store)?;
|
||||||
debug_span!("Optimize Ontology").in_scope(|| store.optimize())?;
|
debug_span!("Optimize Ontology").in_scope(|| store.optimize())?;*/
|
||||||
|
|
||||||
// Full-text search index field names
|
|
||||||
let fields = Self::fields(&store)?;
|
|
||||||
|
|
||||||
let mut entities: HashMap<NamedNode, Entity> = HashMap::new();
|
let mut entities: HashMap<NamedNode, Entity> = HashMap::new();
|
||||||
// All Entities have exactly one catalog ID.
|
// All Entities have exactly one catalog ID.
|
||||||
for quad in store.quads_for_pattern(None, Some(gl::CATALOG_ID), None, None)
|
/*for quad in store.quads_for_pattern(None, Some(gl::CATALOG_ID), None, None)
|
||||||
.filter_map(Result::ok) {
|
.filter_map(Result::ok) {
|
||||||
let label = store.quads_for_pattern(Some(quad.subject.as_ref().into()), Some(rdfs::LABEL), None, None)
|
let label = store.quads_for_pattern(Some(quad.subject.as_ref().into()), Some(rdfs::LABEL), None, None)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
@@ -112,7 +113,7 @@ impl OntologyBuilder {
|
|||||||
properties,
|
properties,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
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.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None)
|
||||||
@@ -127,80 +128,21 @@ impl OntologyBuilder {
|
|||||||
Ok(Ontology {
|
Ok(Ontology {
|
||||||
store,
|
store,
|
||||||
prefixes,
|
prefixes,
|
||||||
fields,
|
|
||||||
entities,
|
entities,
|
||||||
indexed_by,
|
indexed_by,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn fields(store: &Store) -> error::Result<HashMap<NamedNode, IndexField>> {
|
pub struct IndexEntry {
|
||||||
let query = SparqlEvaluator::new()
|
pub catalog_id: u64,
|
||||||
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
|
pub fields: HashMap<String, String>,
|
||||||
.with_prefix("gl", "https://graphofliberty.org/2026/04/ont/")?
|
|
||||||
.parse_query(
|
|
||||||
r#"SELECT DISTINCT ?subject ?name ?label {
|
|
||||||
GRAPH ?graph {
|
|
||||||
?subject a gl:IndexDocumentField ;
|
|
||||||
gl:fieldName ?name ;
|
|
||||||
gl:fieldLabel ?label .
|
|
||||||
FILTER (langMATCHES(LANG(?label), "en") || !hasLANG(?label))
|
|
||||||
}
|
|
||||||
}"#)?;
|
|
||||||
|
|
||||||
let mut results = HashMap::new();
|
|
||||||
if let QueryResults::Solutions(solutions) = query.on_store(store).execute()?
|
|
||||||
{
|
|
||||||
for solution in solutions.filter_map(Result::ok) {
|
|
||||||
let subject = solution.get("subject").and_then(conversion::term_to_named_node);
|
|
||||||
let name = solution.get("name").and_then(conversion::term_as_str);
|
|
||||||
let label = solution.get("label").and_then(conversion::term_as_str);
|
|
||||||
|
|
||||||
if let Some(subject) = subject && let Some(name) = name {
|
|
||||||
let field = IndexField {
|
|
||||||
name: name.to_string(),
|
|
||||||
label: label.map(|l| l.to_string()),
|
|
||||||
};
|
|
||||||
results.insert(subject.to_owned(), field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*fn subclass_of(dataset: &Dataset, class: NamedNodeRef<'_>) -> impl Iterator<Item = NamedNode> {
|
|
||||||
let query = SparqlEvaluator::new()
|
|
||||||
.parse_query(
|
|
||||||
format!(
|
|
||||||
"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
||||||
SELECT ?class {{
|
|
||||||
?class (rdfs:subClassOf|^rdfs:subClassOf)* {class} .
|
|
||||||
}}"
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.expect("Unable to parse subclass_of query");
|
|
||||||
|
|
||||||
if let QueryResults::Solutions(solutions) =
|
|
||||||
query.on_queryable_dataset(dataset).execute().unwrap()
|
|
||||||
{
|
|
||||||
solutions.filter_map(Result::ok).filter_map(|solution| {
|
|
||||||
if let Some(Term::NamedNode(class)) = solution.get("class") {
|
|
||||||
Some(class.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LabeledIri {
|
pub struct LabeledIri {
|
||||||
pub iri: NamedNode,
|
pub iri: NamedNode,
|
||||||
pub label: Option<String>,
|
pub label: String,
|
||||||
pub comment: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for LabeledIri {
|
impl PartialEq for LabeledIri {
|
||||||
@@ -211,7 +153,7 @@ impl PartialEq for LabeledIri {
|
|||||||
|
|
||||||
impl Display for LabeledIri {
|
impl Display for LabeledIri {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.label.clone().unwrap_or_default())
|
write!(f, "{}", self.label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,19 +168,13 @@ pub struct Entity {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct IndexField {
|
pub struct IndexField {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub label: Option<String>,
|
pub label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ontology {
|
pub struct Ontology {
|
||||||
store: Store,
|
store: Store,
|
||||||
prefixes: HashMap<String, String>,
|
prefixes: HashMap<String, String>,
|
||||||
|
|
||||||
// Resource (Property or Class) -> Rust Type
|
|
||||||
//iri_info: HashMap<NamedNode, IriInformation>,
|
|
||||||
|
|
||||||
// NamedIndividual of class IndexField -> Rust Type
|
|
||||||
fields: HashMap<NamedNode, IndexField>,
|
|
||||||
|
|
||||||
// NamedIndividual of class Entity -> Rust Type
|
// NamedIndividual of class Entity -> Rust Type
|
||||||
entities: HashMap<NamedNode, Entity>,
|
entities: HashMap<NamedNode, Entity>,
|
||||||
|
|
||||||
@@ -257,6 +193,81 @@ impl Ontology {
|
|||||||
&*PREFIXES
|
&*PREFIXES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn index(&self, language: &LanguageCondition) -> error::Result<HashMap<NamedNode, IndexEntry>> {
|
||||||
|
let language_filter = language.to_filter_expression("fieldValue");
|
||||||
|
let query = format!(r#"SELECT ?individual ?catalogId ?fieldName ?fieldValue
|
||||||
|
WHERE {{
|
||||||
|
?entity a gl:Entity ;
|
||||||
|
gl:catalogId ?catalogId ;
|
||||||
|
gl:associatedProperty ?property .
|
||||||
|
|
||||||
|
?property gl:indexedByField/gl:fieldName ?fieldName .
|
||||||
|
|
||||||
|
?individual a ?entity ;
|
||||||
|
?property ?fieldValue .
|
||||||
|
|
||||||
|
{language_filter}
|
||||||
|
}}"#);
|
||||||
|
let mut sparql = SparqlEvaluator::new()
|
||||||
|
.with_prefix("gl", "https://graphofliberty.org/2026/04/ont/")?
|
||||||
|
.parse_query(&query)?;
|
||||||
|
sparql.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
|
let mut results = HashMap::new();
|
||||||
|
if let QueryResults::Solutions(solutions) = sparql.on_store(&self.store).execute()? {
|
||||||
|
for solution in solutions.filter_map(Result::ok) {
|
||||||
|
let individual = solution.get("individual").and_then(conversion::term_to_named_node);
|
||||||
|
let catalog_id = solution.get("catalogId").and_then(conversion::term_to_u64);
|
||||||
|
let field_name = solution.get("fieldName").and_then(conversion::term_as_str);
|
||||||
|
let field_value = solution.get("fieldValue").and_then(conversion::term_as_str);
|
||||||
|
if let (Some(individual), Some(catalog_id), Some(field_name), Some(field_value)) = (individual, catalog_id, field_name, field_value) {
|
||||||
|
results.entry(individual.to_owned())
|
||||||
|
.and_modify(|entry: &mut IndexEntry| {
|
||||||
|
entry.fields.insert(field_name.to_owned(), field_value.to_owned());
|
||||||
|
}).or_insert(IndexEntry {
|
||||||
|
catalog_id,
|
||||||
|
fields: HashMap::from_iter([(field_name.to_owned(), field_value.to_owned())]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fields_for_class(&self, class: &NamedNode, language: &LanguageCondition) -> error::Result<Vec<IndexField>> {
|
||||||
|
let language_filter = language.to_filter_expression("label");
|
||||||
|
let query = format!(r#"SELECT DISTINCT ?name ?label {{
|
||||||
|
{class} gl:associatedProperty/gl:indexedByField ?field .
|
||||||
|
|
||||||
|
?field gl:fieldName ?name ;
|
||||||
|
gl:fieldLabel ?label .
|
||||||
|
|
||||||
|
{language_filter}
|
||||||
|
}}"#);
|
||||||
|
let mut sparql = SparqlEvaluator::new()
|
||||||
|
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
|
||||||
|
.with_prefix("gl", "https://graphofliberty.org/2026/04/ont/")?
|
||||||
|
.parse_query(&query)?;
|
||||||
|
sparql.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
if let QueryResults::Solutions(solutions) = sparql.on_store(&self.store).execute()? {
|
||||||
|
for solution in solutions.filter_map(Result::ok) {
|
||||||
|
let name = solution.get("name").and_then(conversion::term_as_str);
|
||||||
|
let label = solution.get("label").and_then(conversion::term_as_str);
|
||||||
|
|
||||||
|
if let Some(name) = name && let Some(label) = label {
|
||||||
|
results.push(IndexField {
|
||||||
|
name: name.to_string(),
|
||||||
|
label: label.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn abbreviate(&self, node: NamedNodeRef<'_>) -> String {
|
pub fn abbreviate(&self, node: NamedNodeRef<'_>) -> String {
|
||||||
for (prefix_name, prefix_iri) in &self.prefixes {
|
for (prefix_name, prefix_iri) in &self.prefixes {
|
||||||
if let Some(local_name) = node.as_str().strip_prefix(prefix_iri) {
|
if let Some(local_name) = node.as_str().strip_prefix(prefix_iri) {
|
||||||
@@ -277,56 +288,24 @@ impl Ontology {
|
|||||||
.map(|base| NamedNode::new_unchecked(format!("{base}{name}")))
|
.map(|base| NamedNode::new_unchecked(format!("{base}{name}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_for_property(&self, property: &NamedNode) -> Option<&IndexField> {
|
|
||||||
self.indexed_by.get(property)
|
|
||||||
.and_then(|node| self.fields.get(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fields_for_class(&self, class: &NamedNode) -> Vec<&IndexField> {
|
|
||||||
self.entities.get(class)
|
|
||||||
.and_then(|entity| {
|
|
||||||
entity.properties.iter()
|
|
||||||
.map(|property| self.field_for_property(property))
|
|
||||||
.filter(Option::is_some)
|
|
||||||
.collect()
|
|
||||||
}).unwrap_or(Vec::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn catalog_id(&self, class: &NamedNode) -> Option<u64> {
|
pub fn catalog_id(&self, class: &NamedNode) -> Option<u64> {
|
||||||
self.entities
|
self.entities
|
||||||
.get(class)
|
.get(class)
|
||||||
.map(|entity| entity.catalog_id)
|
.map(|entity| entity.catalog_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn individuals(&self, class: &NamedNode) -> impl Iterator<Item = NamedNode> {
|
|
||||||
self.store
|
|
||||||
.quads_for_pattern(None, Some(rdf::TYPE), Some(class.into()), None)
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.filter_map(|quad|
|
|
||||||
if let NamedOrBlankNode::NamedNode(subject) = quad.subject {
|
|
||||||
Some(subject)
|
|
||||||
} else { None })
|
|
||||||
}
|
|
||||||
|
|
||||||
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| conversion::language_matches(term, language))
|
.filter(|term| language.primary_matches_term(term))
|
||||||
.filter_map(conversion::term_into_string)
|
.filter_map(conversion::term_into_string)
|
||||||
.next();
|
.next()
|
||||||
|
.unwrap_or_default();
|
||||||
let comment = self.store.quads_for_pattern(Some(iri.as_ref().into()), Some(rdfs::COMMENT), None, None)
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.map(conversion::quad_into_term)
|
|
||||||
.filter(|term| conversion::language_matches(term, language))
|
|
||||||
.filter_map(conversion::term_into_string)
|
|
||||||
.next();
|
|
||||||
|
|
||||||
LabeledIri {
|
LabeledIri {
|
||||||
iri: iri.clone(),
|
iri: iri.clone(),
|
||||||
label,
|
label,
|
||||||
comment,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,17 +313,26 @@ impl Ontology {
|
|||||||
self.entities.get(class)
|
self.entities.get(class)
|
||||||
.map(|entity| LabeledIri {
|
.map(|entity| LabeledIri {
|
||||||
iri: class.to_owned(),
|
iri: class.to_owned(),
|
||||||
label: Some(entity.label.to_owned()),
|
label: entity.label.to_owned(),
|
||||||
comment: entity.comment.to_owned(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn labeled_entities(&self) -> impl Iterator<Item = LabeledIri> {
|
pub fn labeled_entities(&self, language: &LanguageCondition) -> impl Iterator<Item = LabeledIri> {
|
||||||
self.entities.iter()
|
self.store.quads_for_pattern(None, Some(rdf::TYPE), Some(gl::ENTITY.into()), None)
|
||||||
.map(|(iri, entity)| LabeledIri {
|
.filter_map(Result::ok)
|
||||||
iri: iri.to_owned(),
|
.filter_map(|quad| {
|
||||||
label: Some(entity.label.to_owned()),
|
let label = self.store.quads_for_pattern(Some(quad.subject.as_ref().into()), Some(rdfs::LABEL), None, None)
|
||||||
comment: entity.comment.to_owned(),
|
.filter_map(Result::ok)
|
||||||
|
.map(conversion::quad_into_term)
|
||||||
|
.filter(|term| language.primary_matches_term(term))
|
||||||
|
.filter_map(conversion::term_into_string)
|
||||||
|
.next();
|
||||||
|
if let Some(label) = label && let NamedOrBlankNode::NamedNode(subject) = quad.subject {
|
||||||
|
Some(LabeledIri {
|
||||||
|
iri: subject,
|
||||||
|
label,
|
||||||
|
})
|
||||||
|
} else { None }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -1,5 +1,3 @@
|
|||||||
use std::cmp::Ordering;
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
use crate::{error, update};
|
use crate::{error, update};
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -9,7 +7,7 @@ use tantivy::query::{BooleanQuery, Occur, QueryParser, TermQuery};
|
|||||||
use tantivy::schema::{Field, IndexRecordOption};
|
use tantivy::schema::{Field, IndexRecordOption};
|
||||||
use tantivy::tokenizer::{LowerCaser, NgramTokenizer, TextAnalyzer, TokenizerManager};
|
use tantivy::tokenizer::{LowerCaser, NgramTokenizer, TextAnalyzer, TokenizerManager};
|
||||||
use tantivy::{Index, IndexReader, ReloadPolicy, TantivyDocument, Term};
|
use tantivy::{Index, IndexReader, ReloadPolicy, TantivyDocument, Term};
|
||||||
use tracing::{span, Level};
|
use tracing::{debug, span, Level};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SearchIndexBuilder {
|
pub struct SearchIndexBuilder {
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ mod update;
|
|||||||
|
|
||||||
pub use tantivy::TantivyDocument as SearchDocument;
|
pub use tantivy::TantivyDocument as SearchDocument;
|
||||||
pub use tantivy::doc;
|
pub use tantivy::doc;
|
||||||
pub use tantivy::schema::OwnedValue;
|
pub use tantivy::schema::document::Value;
|
||||||
|
|
||||||
pub use error::{Result, SearchError};
|
pub use error::{Result, SearchError};
|
||||||
pub use index::{SearchIndex, SearchIndexBuilder};
|
pub use index::{SearchIndex, SearchIndexBuilder};
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ impl Schema {
|
|||||||
schema_builder.add_u64_field("type", schema::FAST | schema::INDEXED);
|
schema_builder.add_u64_field("type", schema::FAST | schema::INDEXED);
|
||||||
schema_builder.add_text_field("iri", schema::STORED | schema::STRING);
|
schema_builder.add_text_field("iri", schema::STORED | schema::STRING);
|
||||||
|
|
||||||
schema_builder.add_text_field("label", stored_ngram32.clone());
|
schema_builder.add_text_field("label:en", stored_ngram32.clone());
|
||||||
schema_builder.add_text_field("definition", stored_en_stem.clone());
|
schema_builder.add_text_field("definition:en", stored_en_stem.clone());
|
||||||
|
|
||||||
/*schema_builder.add_text_field("title", en_stem.clone());
|
/*schema_builder.add_text_field("title", en_stem.clone());
|
||||||
schema_builder.add_text_field("description", en_stem.clone());
|
schema_builder.add_text_field("description", en_stem.clone());
|
||||||
@@ -45,9 +45,9 @@ impl Schema {
|
|||||||
Self::schema().get_field("iri").unwrap()
|
Self::schema().get_field("iri").unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field(name: &str) -> Field {
|
pub fn field(name: &str, language: &str) -> Field {
|
||||||
Schema::schema()
|
Schema::schema()
|
||||||
.get_field(name)
|
.get_field(&format!("{name}:{language}"))
|
||||||
.expect("Field not found in schema")
|
.expect("Field not found in schema")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user