.
This commit is contained in:
+88
-45
@@ -1,17 +1,16 @@
|
|||||||
use crate::rdf::ontology::Ontology;
|
use std::collections::HashSet;
|
||||||
|
use crate::rdf::ontology::{LabeledIri, Ontology};
|
||||||
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
|
||||||
use crate::rdf::vocab::{gl, rda};
|
use crate::rdf::vocab::{gl, rda};
|
||||||
use gl_search::{Schema, SearchDocument, SearchIndex, doc};
|
use gl_search::{Schema, SearchDocument, SearchIndex, doc, NamedFieldDocument, OwnedValue};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use iced::alignment::Horizontal;
|
use iced::alignment::Horizontal;
|
||||||
use iced::widget::button::Style;
|
use iced::widget::button::Style;
|
||||||
use iced::widget::grid::Sizing;
|
use iced::widget::grid::Sizing;
|
||||||
use iced::widget::{
|
use iced::widget::{button, center, column, combo_box, container, grid, mouse_area, opaque, pick_list, row, scrollable, space, stack, table, text, text_input, toggler};
|
||||||
button, center, column, combo_box, container, grid, mouse_area, opaque, 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};
|
||||||
@@ -20,7 +19,7 @@ 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, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -44,6 +43,7 @@ pub(crate) enum Message {
|
|||||||
HoverRow(QuadKey),
|
HoverRow(QuadKey),
|
||||||
UnhoverRow(QuadKey),
|
UnhoverRow(QuadKey),
|
||||||
QueryUpdated(String),
|
QueryUpdated(String),
|
||||||
|
QueryTypeUpdated(NamedNode),
|
||||||
SearchResultClicked(NamedNode),
|
SearchResultClicked(NamedNode),
|
||||||
DatatypeUpdated(QuadKey, Option<String>),
|
DatatypeUpdated(QuadKey, Option<String>),
|
||||||
LanguageUpdated(QuadKey, Option<String>),
|
LanguageUpdated(QuadKey, Option<String>),
|
||||||
@@ -74,7 +74,8 @@ struct SearchState {
|
|||||||
window_id: window::Id,
|
window_id: window::Id,
|
||||||
action: SearchResultClickAction,
|
action: SearchResultClickAction,
|
||||||
query: String,
|
query: String,
|
||||||
results: Vec<NamedNode>,
|
type_: LabeledIri,
|
||||||
|
results: Vec<NamedFieldDocument>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Publisher {
|
pub(crate) struct Publisher {
|
||||||
@@ -113,12 +114,14 @@ impl Publisher {
|
|||||||
Schema::iri_field() => iri.as_str(),
|
Schema::iri_field() => iri.as_str(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(label) = &info.label {
|
if let Some(label) = &info.label &&
|
||||||
document.add_text(Schema::field("label"), label.to_lowercase());
|
let Some(field) = ontology.field_for_property(&rdfs::LABEL.into_owned()) {
|
||||||
|
document.add_text(Schema::field(&field.name), label);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(comment) = &info.comment {
|
if let Some(comment) = &info.comment &&
|
||||||
document.add_text(Schema::field("comment"), comment.to_lowercase());
|
let Some(field) = ontology.field_for_property(&rdfs::COMMENT.into_owned()) {
|
||||||
|
document.add_text(Schema::field(&field.name), comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
index.add(document).expect("Unable to add annotated IRI to search index");
|
index.add(document).expect("Unable to add annotated IRI to search index");
|
||||||
@@ -192,9 +195,9 @@ impl Publisher {
|
|||||||
&& let TermRef::Literal(literal) = quad.object
|
&& let TermRef::Literal(literal) = quad.object
|
||||||
{
|
{
|
||||||
let field = Schema::schema()
|
let field = Schema::schema()
|
||||||
.get_field(&field.key)
|
.get_field(field.name.as_str())
|
||||||
.expect("Field not found in schema");
|
.expect("Field not found in schema");
|
||||||
document.add_text(field, literal.value().to_lowercase());
|
document.add_text(field, literal.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,19 +309,29 @@ 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 {
|
||||||
let (id, window_task) = window::open(Settings::default());
|
if let Some(type_) = self.ontology.labeled_entities().next() {
|
||||||
self.search_state = Some(SearchState {
|
let (id, window_task) = window::open(Settings::default());
|
||||||
window_id: id,
|
self.search_state = Some(SearchState {
|
||||||
action,
|
window_id: id,
|
||||||
query: String::new(),
|
action,
|
||||||
results: Vec::new(),
|
query: String::new(),
|
||||||
});
|
type_,
|
||||||
task = window_task.map(|_| Message::None)
|
results: Vec::new(),
|
||||||
|
});
|
||||||
|
task = window_task.map(|_| Message::None)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::QueryTypeUpdated(type_) => {
|
||||||
|
if let Some(search_state) = &mut self.search_state {
|
||||||
|
if let Some(type_) = self.ontology.labeled_entity(&type_) {
|
||||||
|
search_state.type_ = type_;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 type_ = 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) => {
|
||||||
self.document
|
self.document
|
||||||
@@ -334,11 +347,8 @@ impl Publisher {
|
|||||||
|
|
||||||
search_state.results = self
|
search_state.results = self
|
||||||
.index
|
.index
|
||||||
.query(type_, new_query.as_str(), Schema::all_fields())
|
.query(catalog_id, new_query.as_str(), Schema::all_fields())
|
||||||
.expect("Error encountered while querying index")
|
.expect("Error encountered while querying index");
|
||||||
.iter()
|
|
||||||
.map(NamedNode::new_unchecked)
|
|
||||||
.collect();
|
|
||||||
search_state.query = new_query;
|
search_state.query = new_query;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -670,7 +680,41 @@ impl Publisher {
|
|||||||
let search_input =
|
let search_input =
|
||||||
text_input("Query", &search_state.query).on_input(Message::QueryUpdated);
|
text_input("Query", &search_state.query).on_input(Message::QueryUpdated);
|
||||||
|
|
||||||
let label_column = table::column("Label", |result: &NamedNode| {
|
let mut entities = self.ontology
|
||||||
|
.labeled_entities()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
entities.sort_by(|a, b| Ord::cmp(&a.label, &b.label));
|
||||||
|
|
||||||
|
let type_selector = pick_list(Some(&search_state.type_), entities, ToString::to_string)
|
||||||
|
.on_select(|selection| Message::QueryTypeUpdated(selection.iri));
|
||||||
|
|
||||||
|
let mut columns = vec![];
|
||||||
|
for field in self.ontology.fields_for_class(&search_state.type_.iri) {
|
||||||
|
let header_text = field.label.as_ref().unwrap_or(&field.name);
|
||||||
|
columns.push(table::column(text(header_text), |document: &NamedFieldDocument| {
|
||||||
|
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.0
|
||||||
|
.get("iri")
|
||||||
|
.and_then(|values| values.first())
|
||||||
|
.map(|value| match value {
|
||||||
|
OwnedValue::Str(string) => string,
|
||||||
|
_ => "???",
|
||||||
|
}).unwrap_or("");
|
||||||
|
|
||||||
|
button(text(cell_value).wrapping(Wrapping::Word))
|
||||||
|
.on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri)))
|
||||||
|
.style(button::text)
|
||||||
|
}).width(Length::Fixed(256.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*let label_column = table::column("Label", |result: &NamedNode| {
|
||||||
let header_text = self
|
let header_text = self
|
||||||
.ontology
|
.ontology
|
||||||
.info(result.as_ref())
|
.info(result.as_ref())
|
||||||
@@ -695,7 +739,7 @@ impl Publisher {
|
|||||||
.style(button::text)
|
.style(button::text)
|
||||||
});
|
});
|
||||||
|
|
||||||
/*let description_column = table::column("Description", |result: &NamedNode| {
|
let description_column = table::column("Description", |result: &NamedNode| {
|
||||||
let header_text = self.ontology.info(result.as_ref())
|
let header_text = self.ontology.info(result.as_ref())
|
||||||
.and_then(|info| info.comment.clone())
|
.and_then(|info| info.comment.clone())
|
||||||
.unwrap_or("Unknown".to_string());
|
.unwrap_or("Unknown".to_string());
|
||||||
@@ -703,25 +747,24 @@ impl Publisher {
|
|||||||
button(text(header_text))
|
button(text(header_text))
|
||||||
.on_press(Message::SearchResultClicked(result.clone()))
|
.on_press(Message::SearchResultClicked(result.clone()))
|
||||||
.style(button::text)
|
.style(button::text)
|
||||||
});*/
|
});
|
||||||
|
|
||||||
let iri_column = table::column("IRI", |result: &NamedNode| {
|
let iri_column = table::column("IRI", |result: &NamedNode| {
|
||||||
button(text(result.as_str()))
|
button(text(result.as_str()))
|
||||||
.on_press(Message::SearchResultClicked(result.clone()))
|
.on_press(Message::SearchResultClicked(result.clone()))
|
||||||
.style(button::text)
|
.style(button::text)
|
||||||
});
|
});*/
|
||||||
|
|
||||||
let results_table = scrollable(table(
|
let results_table = if columns.is_empty() {
|
||||||
[
|
None
|
||||||
label_column,
|
} else {
|
||||||
//description_column,
|
Some(scrollable(table(columns, &search_state.results)))
|
||||||
type_column,
|
};
|
||||||
iri_column,
|
|
||||||
],
|
|
||||||
&search_state.results,
|
|
||||||
));
|
|
||||||
|
|
||||||
return column![search_input, results_table].into();
|
return column![
|
||||||
|
row![search_input, type_selector],
|
||||||
|
results_table
|
||||||
|
].into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let add_row_button = button("Add row").on_press(Message::AddRow(None));
|
let add_row_button = button("Add row").on_press(Message::AddRow(None));
|
||||||
@@ -742,8 +785,8 @@ impl Publisher {
|
|||||||
|
|
||||||
let body: Element<Message> = if self.show_new_document_buttons {
|
let body: Element<Message> = if self.show_new_document_buttons {
|
||||||
column![
|
column![
|
||||||
self.view_new_entity_buttons(self.ontology.subclass_of(gl::ENTITY)),
|
//self.view_new_entity_buttons(self.ontology.subclass_of(gl::ENTITY)),
|
||||||
self.view_new_entity_buttons(self.ontology.subclass_of(rda::ENTITY)),
|
//self.view_new_entity_buttons(self.ontology.subclass_of(rda::ENTITY)),
|
||||||
]
|
]
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
@@ -818,4 +861,4 @@ where
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
@@ -99,6 +99,11 @@ rdam:P30154 rdf:type owl:ObjectProperty .
|
|||||||
rdam:uniformResourceLocator.en rdf:type owl:ObjectProperty .
|
rdam:uniformResourceLocator.en rdf:type owl:ObjectProperty .
|
||||||
|
|
||||||
|
|
||||||
|
### https://graphofliberty.org/2026/04/ont/associatedProperty
|
||||||
|
:associatedProperty rdf:type owl:ObjectProperty ;
|
||||||
|
rdfs:label "associated property"@en .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/indexedByField
|
### https://graphofliberty.org/2026/04/ont/indexedByField
|
||||||
:indexedByField rdf:type owl:ObjectProperty ;
|
:indexedByField rdf:type owl:ObjectProperty ;
|
||||||
rdfs:domain owl:DatatypeProperty ;
|
rdfs:domain owl:DatatypeProperty ;
|
||||||
@@ -163,6 +168,18 @@ ldp:contains rdf:type owl:DatatypeProperty ;
|
|||||||
rdfs:label "catalog id" .
|
rdfs:label "catalog id" .
|
||||||
|
|
||||||
|
|
||||||
|
### https://graphofliberty.org/2026/04/ont/fieldLabel
|
||||||
|
:fieldLabel rdf:type owl:DatatypeProperty ;
|
||||||
|
rdfs:comment "The label of a field, which shall be displayed to the user."@en ;
|
||||||
|
rdfs:label "field label"@en .
|
||||||
|
|
||||||
|
|
||||||
|
### https://graphofliberty.org/2026/04/ont/fieldName
|
||||||
|
:fieldName rdf:type owl:DatatypeProperty ;
|
||||||
|
rdfs:comment "The name of the field, as defined in the full-text search document schema."@en ;
|
||||||
|
rdfs:label "field name"@en .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/readOnly
|
### https://graphofliberty.org/2026/04/ont/readOnly
|
||||||
:readOnly rdf:type owl:DatatypeProperty ;
|
:readOnly rdf:type owl:DatatypeProperty ;
|
||||||
rdfs:comment "Indicates that the property or class is read only (server managed) and should not be made editable in user-facing applications."@en ;
|
rdfs:comment "Indicates that the property or class is read only (server managed) and should not be made editable in user-facing applications."@en ;
|
||||||
@@ -195,8 +212,7 @@ rdac:C10002 rdf:type owl:Class ;
|
|||||||
|
|
||||||
### http://rdaregistry.info/Elements/c/C10004
|
### http://rdaregistry.info/Elements/c/C10004
|
||||||
rdac:C10004 rdf:type owl:Class ;
|
rdac:C10004 rdf:type owl:Class ;
|
||||||
rdfs:subClassOf rdac:C10002 ;
|
rdfs:subClassOf rdac:C10002 .
|
||||||
rdfs:label "Person"@en .
|
|
||||||
|
|
||||||
|
|
||||||
### http://rdaregistry.info/Elements/c/C10007
|
### http://rdaregistry.info/Elements/c/C10007
|
||||||
@@ -282,11 +298,6 @@ ldp:Resource rdf:type owl:Class .
|
|||||||
rdfs:subClassOf :Entity .
|
rdfs:subClassOf :Entity .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/Person
|
|
||||||
:Person rdf:type owl:Class ;
|
|
||||||
rdfs:subClassOf :Entity .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/Podcast
|
### https://graphofliberty.org/2026/04/ont/Podcast
|
||||||
:Podcast rdf:type owl:Class ;
|
:Podcast rdf:type owl:Class ;
|
||||||
rdfs:subClassOf :Entity .
|
rdfs:subClassOf :Entity .
|
||||||
@@ -366,6 +377,13 @@ rdaa:identifierForPerson.en rdf:type owl:NamedIndividual .
|
|||||||
rdaa:surname.en rdf:type owl:NamedIndividual .
|
rdaa:surname.en rdf:type owl:NamedIndividual .
|
||||||
|
|
||||||
|
|
||||||
|
### http://rdaregistry.info/Elements/c/C10004
|
||||||
|
rdac:C10004 rdf:type owl:NamedIndividual ;
|
||||||
|
:associatedProperty rdaa:P50291 ,
|
||||||
|
rdaa:P50292 ;
|
||||||
|
:catalogId "8"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
### http://rdaregistry.info/Elements/c/C10007
|
### http://rdaregistry.info/Elements/c/C10007
|
||||||
rdac:C10007 rdf:type owl:NamedIndividual ;
|
rdac:C10007 rdf:type owl:NamedIndividual ;
|
||||||
:template [ rdf:type rdac:C10007
|
:template [ rdf:type rdac:C10007
|
||||||
@@ -385,14 +403,28 @@ rdam:uniformResourceLocator.en 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 ;
|
||||||
|
:associatedProperty rdfs:comment ,
|
||||||
|
rdfs:label ;
|
||||||
: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 ;
|
||||||
|
:associatedProperty rdfs:comment ,
|
||||||
|
rdfs:label ;
|
||||||
:catalogId "1"^^xsd:nonNegativeInteger .
|
:catalogId "1"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
|
### http://www.w3.org/2000/01/rdf-schema#comment
|
||||||
|
rdfs:comment rdf:type owl:NamedIndividual ;
|
||||||
|
:indexedByField :comment .
|
||||||
|
|
||||||
|
|
||||||
|
### http://www.w3.org/2000/01/rdf-schema#label
|
||||||
|
rdfs:label rdf:type owl:NamedIndividual ;
|
||||||
|
:indexedByField :label .
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/ns/ldp#BasicContainer
|
### http://www.w3.org/ns/ldp#BasicContainer
|
||||||
ldp:BasicContainer rdf:type owl:NamedIndividual ;
|
ldp:BasicContainer rdf:type owl:NamedIndividual ;
|
||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
@@ -448,11 +480,6 @@ ldp:contains rdf:type owl:NamedIndividual ;
|
|||||||
:catalogId "7"^^xsd:nonNegativeInteger .
|
:catalogId "7"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/Person
|
|
||||||
:Person rdf:type owl:NamedIndividual ;
|
|
||||||
:catalogId "8"^^xsd:nonNegativeInteger .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/Podcast
|
### https://graphofliberty.org/2026/04/ont/Podcast
|
||||||
:Podcast rdf:type owl:NamedIndividual ;
|
:Podcast rdf:type owl:NamedIndividual ;
|
||||||
:catalogId "9"^^xsd:nonNegativeInteger .
|
:catalogId "9"^^xsd:nonNegativeInteger .
|
||||||
@@ -463,18 +490,36 @@ ldp:contains rdf:type owl:NamedIndividual ;
|
|||||||
:catalogId "10"^^xsd:nonNegativeInteger .
|
:catalogId "10"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
|
### https://graphofliberty.org/2026/04/ont/comment
|
||||||
|
:comment rdf:type owl:NamedIndividual ,
|
||||||
|
:IndexField ;
|
||||||
|
:fieldLabel "Comment"@en ;
|
||||||
|
:fieldName "comment" ;
|
||||||
|
rdfs:label "Comment Field"@en .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/givenName
|
### https://graphofliberty.org/2026/04/ont/givenName
|
||||||
:givenName rdf:type owl:NamedIndividual ,
|
:givenName rdf:type owl:NamedIndividual ,
|
||||||
:IndexField ;
|
:IndexField ;
|
||||||
rdfs:label "Given Name"@en ;
|
:fieldLabel "Given Name"@en ;
|
||||||
rdfs:value "given name" .
|
:fieldName "givenName" ;
|
||||||
|
rdfs:label "Given Name Field"@en .
|
||||||
|
|
||||||
|
|
||||||
|
### https://graphofliberty.org/2026/04/ont/label
|
||||||
|
:label rdf:type owl:NamedIndividual ,
|
||||||
|
:IndexField ;
|
||||||
|
:fieldLabel "Label"@en ;
|
||||||
|
:fieldName "label" ;
|
||||||
|
rdfs:label "Label Field"@en .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/surname
|
### https://graphofliberty.org/2026/04/ont/surname
|
||||||
:surname rdf:type owl:NamedIndividual ,
|
:surname rdf:type owl:NamedIndividual ,
|
||||||
:IndexField ;
|
:IndexField ;
|
||||||
rdfs:label "Surname"@en ;
|
:fieldLabel "Surname"@en ;
|
||||||
rdfs:value "surname" .
|
:fieldName "surname" ;
|
||||||
|
rdfs:label "Surname Field"@en .
|
||||||
|
|
||||||
|
|
||||||
#################################################################
|
#################################################################
|
||||||
@@ -490,12 +535,27 @@ rdaa:P50291 rdfs:label "has surname"@en .
|
|||||||
rdaa:P50292 rdfs:label "has given name"@en .
|
rdaa:P50292 rdfs:label "has given name"@en .
|
||||||
|
|
||||||
|
|
||||||
|
rdac:C10004 rdfs:label "Person"@en .
|
||||||
|
|
||||||
|
|
||||||
rdac:C10007 rdfs:label "Manifestation"@en .
|
rdac:C10007 rdfs:label "Manifestation"@en .
|
||||||
|
|
||||||
|
|
||||||
rdam:P30154 rdfs:label "has uniform resource locator"@en .
|
rdam:P30154 rdfs:label "has uniform resource locator"@en .
|
||||||
|
|
||||||
|
|
||||||
|
rdf:Property rdfs:label "Property"@en .
|
||||||
|
|
||||||
|
|
||||||
|
rdfs:Class rdfs:label "Class"@en .
|
||||||
|
|
||||||
|
|
||||||
|
rdfs:comment rdfs:label "Comment Property"@en .
|
||||||
|
|
||||||
|
|
||||||
|
rdfs:label rdfs:label "Label Property"@en .
|
||||||
|
|
||||||
|
|
||||||
:AudioBook rdfs:label "Audio Book"@en .
|
:AudioBook rdfs:label "Audio Book"@en .
|
||||||
|
|
||||||
|
|
||||||
@@ -514,9 +574,6 @@ rdam:P30154 rdfs:label "has uniform resource locator"@en .
|
|||||||
:Music rdfs:label "Music"@en .
|
:Music rdfs:label "Music"@en .
|
||||||
|
|
||||||
|
|
||||||
:Person rdfs:label "Person"@en .
|
|
||||||
|
|
||||||
|
|
||||||
:Podcast rdfs:label "Podcast"@en .
|
:Podcast rdfs:label "Podcast"@en .
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+152
-61
@@ -1,5 +1,5 @@
|
|||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::rdf::vocab::{gl, owl};
|
use crate::rdf::vocab::{gl, owl, rda};
|
||||||
use oxigraph::io::{RdfFormat, RdfParser};
|
use oxigraph::io::{RdfFormat, RdfParser};
|
||||||
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
||||||
use oxigraph::model::{
|
use oxigraph::model::{
|
||||||
@@ -7,9 +7,10 @@ use oxigraph::model::{
|
|||||||
TermRef, Triple, TripleRef,
|
TermRef, Triple, TripleRef,
|
||||||
};
|
};
|
||||||
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fmt::Display;
|
||||||
use iced::widget::sensor::Key;
|
use iced::widget::sensor::Key;
|
||||||
use tracing::debug;
|
use tracing::{debug, info};
|
||||||
|
|
||||||
const PREFIXES: &[(&str, &str)] = &[
|
const PREFIXES: &[(&str, &str)] = &[
|
||||||
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
|
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
|
||||||
@@ -155,11 +156,11 @@ SELECT DISTINCT ?class ?subject ?label ?comment ?read_only {
|
|||||||
?subject a ?class .
|
?subject a ?class .
|
||||||
OPTIONAL {
|
OPTIONAL {
|
||||||
?subject rdfs:label ?label
|
?subject rdfs:label ?label
|
||||||
FILTER (LANG(?label) = 'en' || LANG(?label) = '')
|
FILTER (langMATCHES(LANG(?label), "en") || !hasLANG(?label))
|
||||||
}
|
}
|
||||||
OPTIONAL {
|
OPTIONAL {
|
||||||
?subject rdfs:comment ?comment
|
?subject rdfs:comment ?comment
|
||||||
FILTER (LANG(?comment) = 'en' || LANG(?comment) = '')
|
FILTER (langMATCHES(LANG(?label), "en") || !hasLANG(?comment))
|
||||||
}
|
}
|
||||||
OPTIONAL { ?subject gl:readOnly ?read_only }
|
OPTIONAL { ?subject gl:readOnly ?read_only }
|
||||||
}"#,
|
}"#,
|
||||||
@@ -200,11 +201,11 @@ SELECT DISTINCT ?class ?subject ?label ?comment ?read_only {
|
|||||||
r#"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
r#"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||||
PREFIX gl: <https://graphofliberty.org/2026/04/ont/>
|
PREFIX gl: <https://graphofliberty.org/2026/04/ont/>
|
||||||
|
|
||||||
SELECT DISTINCT ?subject ?key ?label {
|
SELECT DISTINCT ?subject ?name ?label {
|
||||||
?subject a gl:IndexField ;
|
?subject a gl:IndexField ;
|
||||||
rdfs:value ?key ;
|
gl:fieldName ?name ;
|
||||||
rdfs:label ?label .
|
gl:fieldLabel ?label .
|
||||||
FILTER(langMATCHES(LANG(?label), "en") || !hasLANG(?label))
|
FILTER (langMATCHES(LANG(?label), "en") || !hasLANG(?label))
|
||||||
}"#,
|
}"#,
|
||||||
)
|
)
|
||||||
.expect("Unable to parse field query");
|
.expect("Unable to parse field query");
|
||||||
@@ -215,13 +216,13 @@ SELECT DISTINCT ?subject ?key ?label {
|
|||||||
{
|
{
|
||||||
for solution in solutions.filter_map(Result::ok) {
|
for solution in solutions.filter_map(Result::ok) {
|
||||||
let subject = solution.get("subject").and_then(term_to_named_node);
|
let subject = solution.get("subject").and_then(term_to_named_node);
|
||||||
let key = solution.get("key").and_then(term_to_string);
|
let name = solution.get("name").and_then(term_to_string);
|
||||||
let label = solution.get("label").and_then(term_to_string);
|
let label = solution.get("label").and_then(term_to_string);
|
||||||
|
|
||||||
if let Some(subject) = subject && let Some(key) = key {
|
if let Some(subject) = subject && let Some(name) = name {
|
||||||
let field = IndexField {
|
let field = IndexField {
|
||||||
key: key.to_owned(),
|
name: name.to_string(),
|
||||||
label: label.map(String::from),
|
label: label.map(|l| l.to_string()),
|
||||||
};
|
};
|
||||||
results.insert(subject.to_owned(), field);
|
results.insert(subject.to_owned(), field);
|
||||||
}
|
}
|
||||||
@@ -230,6 +231,34 @@ SELECT DISTINCT ?subject ?key ?label {
|
|||||||
results
|
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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(&mut self) -> error::Result<Ontology> {
|
pub fn build(&mut self) -> error::Result<Ontology> {
|
||||||
let prefixes = PREFIXES
|
let prefixes = PREFIXES
|
||||||
.iter()
|
.iter()
|
||||||
@@ -251,6 +280,41 @@ SELECT DISTINCT ?subject ?key ?label {
|
|||||||
// Full-text search index field names
|
// Full-text search index field names
|
||||||
let fields = Self::fields(&dataset);
|
let fields = Self::fields(&dataset);
|
||||||
|
|
||||||
|
let mut entities: HashMap<NamedNode, Entity> = HashMap::new();
|
||||||
|
let entity_iris = Self::subclass_of(&dataset, gl::ENTITY)
|
||||||
|
.chain(Self::subclass_of(&dataset, rda::ENTITY))
|
||||||
|
.chain([
|
||||||
|
rdf::PROPERTY.into_owned(),
|
||||||
|
rdfs::CLASS.into_owned(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for iri in entity_iris {
|
||||||
|
let subject = iri.as_ref().into();
|
||||||
|
|
||||||
|
let label = dataset.quads_for_pattern(Some(subject), Some(rdfs::LABEL), None, None)
|
||||||
|
.filter_map(|quad| term_to_string(&quad.object.into_owned()).map(String::from))
|
||||||
|
.next();
|
||||||
|
|
||||||
|
let catalog_id = dataset.quads_for_pattern(Some(subject), Some(gl::CATALOG_ID), None, None)
|
||||||
|
.filter_map(|quad| term_to_u64(&quad.object.into_owned()))
|
||||||
|
.next();
|
||||||
|
|
||||||
|
if let Some(catalog_id) = catalog_id {
|
||||||
|
let mut properties = HashSet::new();
|
||||||
|
for quad in dataset.quads_for_pattern(Some(subject), Some(gl::ASSOCIATED_PROPERTY), None, None) {
|
||||||
|
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject
|
||||||
|
&& let TermRef::NamedNode(property) = quad.object {
|
||||||
|
properties.insert(property.into_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entities.insert(iri, Entity {
|
||||||
|
label: label.unwrap_or(catalog_id.to_string()),
|
||||||
|
catalog_id,
|
||||||
|
properties,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut indexed_by = HashMap::new();
|
let mut indexed_by = HashMap::new();
|
||||||
for quad in dataset.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None) {
|
for quad in dataset.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None) {
|
||||||
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject
|
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject
|
||||||
@@ -260,30 +324,35 @@ SELECT DISTINCT ?subject ?key ?label {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catalog IDs (used to quickly filter full-text search results)
|
|
||||||
let mut catalog_ids = HashMap::new();
|
|
||||||
for quad in dataset.quads_for_pattern(None, Some(gl::CATALOG_ID), None, None) {
|
|
||||||
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject
|
|
||||||
&& let TermRef::Literal(literal) = quad.object
|
|
||||||
{
|
|
||||||
if literal.datatype() == xsd::NON_NEGATIVE_INTEGER {
|
|
||||||
let value: u64 = literal.value().parse().expect("Failed to parse catalog ID from ontology. It ought to be a non-negative integer.");
|
|
||||||
catalog_ids.insert(subject.into_owned(), value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Ontology {
|
Ok(Ontology {
|
||||||
dataset,
|
dataset,
|
||||||
prefixes,
|
prefixes,
|
||||||
iri_info,
|
iri_info,
|
||||||
fields,
|
fields,
|
||||||
|
entities,
|
||||||
indexed_by,
|
indexed_by,
|
||||||
catalog_ids,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LabeledIri {
|
||||||
|
pub iri: NamedNode,
|
||||||
|
pub label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for LabeledIri {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.iri == other.iri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LabeledIri {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.label.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct IriInformation {
|
pub struct IriInformation {
|
||||||
pub type_: NamedNode,
|
pub type_: NamedNode,
|
||||||
@@ -292,19 +361,34 @@ pub struct IriInformation {
|
|||||||
pub read_only: bool,
|
pub read_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Entity {
|
||||||
|
pub label: String,
|
||||||
|
pub catalog_id: u64,
|
||||||
|
pub properties: HashSet<NamedNode>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct IndexField {
|
pub struct IndexField {
|
||||||
pub key: String,
|
pub name: String,
|
||||||
pub label: Option<String>,
|
pub label: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ontology {
|
pub struct Ontology {
|
||||||
dataset: Dataset,
|
dataset: Dataset,
|
||||||
prefixes: HashMap<String, String>,
|
prefixes: HashMap<String, String>,
|
||||||
|
|
||||||
|
// Resource (Property or Class) -> Rust Type
|
||||||
iri_info: HashMap<NamedNode, IriInformation>,
|
iri_info: HashMap<NamedNode, IriInformation>,
|
||||||
|
|
||||||
|
// NamedIndividual of class IndexField -> Rust Type
|
||||||
fields: HashMap<NamedNode, IndexField>,
|
fields: HashMap<NamedNode, IndexField>,
|
||||||
|
|
||||||
|
// NamedIndividual of class Entity -> Rust Type
|
||||||
|
entities: HashMap<NamedNode, Entity>,
|
||||||
|
|
||||||
|
// Property -> NamedIndividual of class IndexField
|
||||||
indexed_by: HashMap<NamedNode, NamedNode>,
|
indexed_by: HashMap<NamedNode, NamedNode>,
|
||||||
catalog_ids: HashMap<NamedNode, u64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn term_to_named_node(term: &Term) -> Option<&NamedNode> {
|
fn term_to_named_node(term: &Term) -> Option<&NamedNode> {
|
||||||
@@ -340,6 +424,14 @@ fn term_to_boolean(term: &Term) -> Option<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn term_to_u64(term: &Term) -> Option<u64> {
|
||||||
|
if let Term::Literal(literal) = term &&
|
||||||
|
literal.datatype() == xsd::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)
|
||||||
|
} else { None }
|
||||||
|
}
|
||||||
|
|
||||||
impl Ontology {
|
impl Ontology {
|
||||||
pub fn builder<'a>() -> OntologyBuilder<'a> {
|
pub fn builder<'a>() -> OntologyBuilder<'a> {
|
||||||
OntologyBuilder {
|
OntologyBuilder {
|
||||||
@@ -377,12 +469,39 @@ impl Ontology {
|
|||||||
.and_then(|node| self.fields.get(node))
|
.and_then(|node| self.fields.get(node))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn catalog_id(&self, class: &NamedNode) -> Option<u64> {
|
pub fn fields_for_class(&self, class: &NamedNode) -> Vec<&IndexField> {
|
||||||
self.catalog_ids.get(class).copied()
|
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 iri_info(&self) -> &HashMap<NamedNode, IriInformation>
|
pub fn catalog_id(&self, class: &NamedNode) -> Option<u64> {
|
||||||
{
|
self.entities
|
||||||
|
.get(class)
|
||||||
|
.map(|entity| entity.catalog_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn labeled_entity(&self, class: &NamedNode) -> Option<LabeledIri> {
|
||||||
|
self.entities.get(class)
|
||||||
|
.map(|entity| LabeledIri {
|
||||||
|
iri: class.to_owned(),
|
||||||
|
label: entity.label.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn labeled_entities(&self) -> impl Iterator<Item = LabeledIri> {
|
||||||
|
self.entities.iter()
|
||||||
|
.map(|(iri, entity)| LabeledIri {
|
||||||
|
iri: iri.to_owned(),
|
||||||
|
label: entity.label.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iri_info(&self) -> &HashMap<NamedNode, IriInformation> {
|
||||||
&self.iri_info
|
&self.iri_info
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,34 +542,6 @@ impl Ontology {
|
|||||||
Self::is_read_only_impl(&self.iri_info, triple)
|
Self::is_read_only_impl(&self.iri_info, triple)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subclass_of(&self, 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(&self.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!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn template_triples<'a>(
|
pub fn template_triples<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
class: NamedNodeRef<'a>,
|
class: NamedNodeRef<'a>,
|
||||||
|
|||||||
@@ -3,16 +3,21 @@ pub mod gl {
|
|||||||
|
|
||||||
pub const TEMPLATE: NamedNodeRef =
|
pub const TEMPLATE: NamedNodeRef =
|
||||||
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/template");
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/template");
|
||||||
|
|
||||||
pub const INDEXED_BY_FIELD: NamedNodeRef =
|
pub const INDEXED_BY_FIELD: NamedNodeRef =
|
||||||
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/indexedByField");
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/indexedByField");
|
||||||
|
|
||||||
pub const CATALOG_ID: NamedNodeRef =
|
pub const CATALOG_ID: NamedNodeRef =
|
||||||
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/catalogId");
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/catalogId");
|
||||||
|
|
||||||
pub const ENTITY: NamedNodeRef =
|
pub const ENTITY: NamedNodeRef =
|
||||||
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/Entity");
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/Entity");
|
||||||
|
|
||||||
pub const INDEX_FIELD: NamedNodeRef =
|
pub const ASSOCIATED_PROPERTY: NamedNodeRef =
|
||||||
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/IndexField");
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/associatedProperty");
|
||||||
|
|
||||||
|
/*pub const INDEX_FIELD: NamedNodeRef =
|
||||||
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/IndexField");*/
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod owl {
|
pub mod owl {
|
||||||
|
|||||||
+17
-17
@@ -1,13 +1,13 @@
|
|||||||
use crate::error;
|
use crate::{error, SearchDocument};
|
||||||
use crate::error::SearchError;
|
use crate::error::SearchError;
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
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};
|
||||||
use tantivy::query::{BooleanQuery, Occur, QueryParser, TermQuery};
|
use tantivy::query::{BooleanQuery, Occur, QueryParser, TermQuery};
|
||||||
use tantivy::schema::{Field, IndexRecordOption, Value};
|
use tantivy::schema::{Field, IndexRecordOption, NamedFieldDocument, Value};
|
||||||
use tantivy::tokenizer::{NgramTokenizer, TokenizerManager};
|
use tantivy::tokenizer::{LowerCaser, NgramTokenizer, TextAnalyzer, TokenizerManager};
|
||||||
use tantivy::{Index, IndexReader, IndexWriter, ReloadPolicy, TantivyDocument, Term};
|
use tantivy::{Document, Index, IndexReader, IndexWriter, ReloadPolicy, Score, TantivyDocument, Term};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SearchIndexBuilder {
|
pub struct SearchIndexBuilder {
|
||||||
@@ -23,8 +23,12 @@ impl SearchIndexBuilder {
|
|||||||
pub fn build(self) -> error::Result<SearchIndex> {
|
pub fn build(self) -> error::Result<SearchIndex> {
|
||||||
if let Some(path) = self.path {
|
if let Some(path) = self.path {
|
||||||
let ngram_32 = NgramTokenizer::new(1, 32, false)?;
|
let ngram_32 = NgramTokenizer::new(1, 32, false)?;
|
||||||
|
let ngram_32_lowercase = TextAnalyzer::builder(ngram_32)
|
||||||
|
.filter(LowerCaser)
|
||||||
|
.build();
|
||||||
|
|
||||||
let tokenizer_manager = TokenizerManager::default();
|
let tokenizer_manager = TokenizerManager::default();
|
||||||
tokenizer_manager.register("ngram_32", ngram_32);
|
tokenizer_manager.register("ngram_32", ngram_32_lowercase);
|
||||||
|
|
||||||
let mmap_directory = MmapDirectory::open(path)?;
|
let mmap_directory = MmapDirectory::open(path)?;
|
||||||
let managed_directory = ManagedDirectory::wrap(Box::new(mmap_directory))?;
|
let managed_directory = ManagedDirectory::wrap(Box::new(mmap_directory))?;
|
||||||
@@ -82,7 +86,7 @@ impl SearchIndex {
|
|||||||
type_: Option<u64>,
|
type_: Option<u64>,
|
||||||
user_query: &str,
|
user_query: &str,
|
||||||
default_fields: Vec<Field>,
|
default_fields: Vec<Field>,
|
||||||
) -> error::Result<Vec<String>> {
|
) -> error::Result<Vec<NamedFieldDocument>> {
|
||||||
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);
|
||||||
|
|
||||||
@@ -98,16 +102,12 @@ impl SearchIndex {
|
|||||||
|
|
||||||
let query = BooleanQuery::new(subqueries);
|
let query = BooleanQuery::new(subqueries);
|
||||||
let searcher = self.reader.searcher();
|
let searcher = self.reader.searcher();
|
||||||
let results = searcher.search(&query, &TopDocs::with_limit(10000).order_by_score())?;
|
let results = searcher.search(&query, &TopDocs::with_limit(10000).order_by_score())?
|
||||||
let mut iris = vec![];
|
.iter()
|
||||||
for (_score, address) in results.iter() {
|
.map(|(_, address)| searcher.doc(*address))
|
||||||
let doc: TantivyDocument = searcher.doc(*address)?;
|
.filter_map(Result::ok)
|
||||||
if let Some(doc_iri) = doc.get_first(Schema::iri_field()) {
|
.map(|doc: SearchDocument| doc.to_named_doc(Schema::schema()))
|
||||||
let doc_iri_string = doc_iri.as_str().unwrap_or("???").to_string();
|
.collect();
|
||||||
iris.push(doc_iri_string);
|
Ok(results)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(iris)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod schema;
|
|||||||
|
|
||||||
pub use tantivy::TantivyDocument as SearchDocument;
|
pub use tantivy::TantivyDocument as SearchDocument;
|
||||||
pub use tantivy::doc;
|
pub use tantivy::doc;
|
||||||
|
pub use tantivy::schema::{NamedFieldDocument, OwnedValue};
|
||||||
|
|
||||||
pub use error::{Result, SearchError};
|
pub use error::{Result, SearchError};
|
||||||
pub use index::{SearchIndex, SearchIndexBuilder};
|
pub use index::{SearchIndex, SearchIndexBuilder};
|
||||||
|
|||||||
+11
-16
@@ -11,36 +11,31 @@ 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 ngram_32 = 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();
|
||||||
|
|
||||||
let 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();
|
||||||
|
|
||||||
let mut schema_builder = TantivySchema::builder();
|
let mut schema_builder = TantivySchema::builder();
|
||||||
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", ngram_32.clone());
|
|
||||||
schema_builder.add_text_field("comment", en_stem.clone());
|
|
||||||
|
|
||||||
schema_builder.add_text_field("given name", ngram_32.clone());
|
schema_builder.add_text_field("label", stored_ngram32.clone());
|
||||||
schema_builder.add_text_field("surname", ngram_32);
|
schema_builder.add_text_field("comment", stored_en_stem.clone());
|
||||||
|
|
||||||
schema_builder.add_text_field("title", en_stem.clone());
|
schema_builder.add_text_field("givenName", stored_ngram32.clone());
|
||||||
|
schema_builder.add_text_field("surname", stored_ngram32);
|
||||||
|
|
||||||
|
/*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());
|
||||||
schema_builder.add_text_field("content", en_stem);
|
schema_builder.add_text_field("content", en_stem);*/
|
||||||
|
|
||||||
schema_builder.add_u64_field("page", schema::STORED);
|
|
||||||
schema_builder.add_u64_field("book", schema::STORED);
|
|
||||||
schema_builder.add_u64_field("chapter", schema::STORED);
|
|
||||||
schema_builder.add_u64_field("verse", schema::STORED);
|
|
||||||
|
|
||||||
schema_builder.build()
|
schema_builder.build()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user