diff --git a/Cargo.lock b/Cargo.lock index 074cc62..b3b8147 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1368,6 +1368,7 @@ dependencies = [ "iced", "ldp", "oxigraph", + "oxilangtag", "rfd", "tar", "thiserror 2.0.18", diff --git a/publish/src/rdf/ontologies/22-rdf-syntax-ns.ttl b/ontology/22-rdf-syntax-ns.ttl similarity index 100% rename from publish/src/rdf/ontologies/22-rdf-syntax-ns.ttl rename to ontology/22-rdf-syntax-ns.ttl diff --git a/ontology/README.md b/ontology/README.md new file mode 100644 index 0000000..1648991 --- /dev/null +++ b/ontology/README.md @@ -0,0 +1,18 @@ +``` +oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology -f 22-rdf-syntax-ns.ttl --graph http://www.w3.org/1999/02/22-rdf-syntax-ns +oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology -f rdf-schema.ttl --graph http://www.w3.org/2000/01/rdf-schema +oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology -f fedora.xml --graph http://fedora.info/definitions/v4/repository +oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology -f ldp.ttl --graph http://www.w3.org/ns/ldp +oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology -f owl.ttl --graph http://www.w3.org/2002/07/owl +oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology -f skos.rdf --graph https://www.w3.org/TR/skos-reference/skos.rdf + +# https://github.com/RDARegistry/RDA-Vocabularies/archive/v5.4.13.tar.gz +find Elements -type f -name '*.xml' -exec oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology --graph http://rdaregistry.info/Elements -f {} \; +find Maps -type f -name '*.xml' -exec oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology --graph http://rdaregistry.info/Maps -f {} \; +find termList -type f -name '*.xml' -exec oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology --graph http://rdaregistry.info/termList -f {} \; +``` + +``` +oxigraph update -l ~/.local/share/org.graphofliberty.desktop/ontology -u "DROP GRAPH " +oxigraph load -l ~/.local/share/org.graphofliberty.desktop/ontology -f graphofliberty.ttl --graph https://graphofliberty.org/2026/04/ont +``` diff --git a/publish/src/rdf/ontologies/fedora.xml b/ontology/fedora.xml similarity index 100% rename from publish/src/rdf/ontologies/fedora.xml rename to ontology/fedora.xml diff --git a/ontology/graphofliberty.ttl b/ontology/graphofliberty.ttl new file mode 100644 index 0000000..18e557a --- /dev/null +++ b/ontology/graphofliberty.ttl @@ -0,0 +1,286 @@ +@prefix : . +@prefix ldp: . +@prefix owl: . +@prefix rdf: . +@prefix xml: . +@prefix xsd: . +@prefix rdaa: . +@prefix rdac: . +@prefix rdae: . +@prefix rdai: . +@prefix rdam: . +@prefix rdan: . +@prefix rdap: . +@prefix rdat: . +@prefix rdaw: . +@prefix rdax: . +@prefix rdfs: . +@prefix skos: . +@prefix fedora: . +@base . + + rdf:type owl:Ontology . + +################################################################# +# Annotation properties +################################################################# + +### http://www.w3.org/2000/01/rdf-schema#value +rdfs:value rdf:type owl:AnnotationProperty . + + +################################################################# +# Datatypes +################################################################# + +### http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString +rdf:dirLangString rdf:type rdfs:Datatype ; + rdfs:label "Directional language-tagged string"@en . + + +### http://www.w3.org/1999/02/22-rdf-syntax-ns#langString +rdf:langString rdf:type rdfs:Datatype ; + rdfs:label "Language-tagged string"@en . + + +### http://www.w3.org/2001/XMLSchema#boolean +xsd:boolean rdf:type rdfs:Datatype ; + rdfs:label "Boolean"@en . + + +### http://www.w3.org/2001/XMLSchema#dateTime +xsd:dateTime rdf:type rdfs:Datatype ; + rdfs:comment "Date and time with or without timezone"@en . + + +### http://www.w3.org/2001/XMLSchema#decimal +xsd:decimal rdf:type rdfs:Datatype ; + rdfs:comment "Arbitrary-precision decimal numbers"@en ; + rdfs:label "Decimal"@en . + + +### http://www.w3.org/2001/XMLSchema#integer +xsd:integer rdf:type rdfs:Datatype ; + rdfs:comment "Arbitrary-size integer numbers"@en ; + rdfs:label "Integer"@en . + + +### http://www.w3.org/2001/XMLSchema#nonNegativeInteger +xsd:nonNegativeInteger rdf:type rdfs:Datatype ; + rdfs:label "Non-negative Integer"@en . + + +### http://www.w3.org/2001/XMLSchema#string +xsd:string rdf:type rdfs:Datatype ; + rdfs:label "String"@en . + + +### http://www.w3.org/2001/XMLSchema#unsignedShort +xsd:unsignedShort rdf:type rdfs:Datatype ; + rdfs:label "Unsigned short"@en . + + +################################################################# +# Object Properties +################################################################# + +### http://fedora.info/definitions/v4/repository#hasParent +fedora:hasParent 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 +:indexedByField rdf:type owl:ObjectProperty ; + rdfs:range :IndexDocumentField ; + rdfs:comment "The property is associated with the given field in a full-text search database."@en ; + rdfs:label "indexed by field"@en . + + +### https://graphofliberty.org/2026/04/ont/template +:template rdf:type owl:ObjectProperty ; + rdfs:comment "A default set of triples used during the creation of new entities of the associated class."@en ; + rdfs:label "has template"@en . + + +################################################################# +# Data properties +################################################################# + +### http://fedora.info/definitions/v4/repository#created +fedora:created rdf:type owl:DatatypeProperty ; + rdfs:subPropertyOf owl:topDataProperty . + + +### http://fedora.info/definitions/v4/repository#createdBy +fedora:createdBy rdf:type owl:DatatypeProperty ; + rdfs:subPropertyOf owl:topDataProperty . + + +### http://fedora.info/definitions/v4/repository#lastModified +fedora:lastModified rdf:type owl:DatatypeProperty . + + +### http://fedora.info/definitions/v4/repository#lastModifiedBy +fedora:lastModifiedBy rdf:type owl:DatatypeProperty . + + +### http://www.w3.org/ns/ldp#contains +ldp:contains rdf:type owl:DatatypeProperty ; + rdfs:subPropertyOf owl:topDataProperty . + + +### https://graphofliberty.org/2026/04/ont/catalogId +:catalogId rdf:type owl:DatatypeProperty ; + rdfs:comment "An integer associated with the class for fast lookup in a database."@en ; + rdfs:label "catalog id" . + + +### https://graphofliberty.org/2026/04/ont/fieldLabel +:fieldLabel rdf:type owl:DatatypeProperty ; + rdfs:domain :IndexDocumentField ; + 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:domain :IndexDocumentField ; + 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 +: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:label "read only"@en . + + +################################################################# +# Classes +################################################################# + +### https://graphofliberty.org/2026/04/ont/Entity +:Entity rdf:type owl:Class ; + rdfs:label "Graph of Liberty Entity"@en . + + +### https://graphofliberty.org/2026/04/ont/IndexDocumentField +:IndexDocumentField rdf:type owl:Class ; + rdfs:comment "Describes a single field present within a document that is indexed in a full-text search database."@en ; + rdfs:label "Index Document Field"@en . + + +################################################################# +# Individuals +################################################################# + +### http://fedora.info/definitions/v4/repository#Container +fedora:Container rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://fedora.info/definitions/v4/repository#Resource +fedora:Resource rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://fedora.info/definitions/v4/repository#created +fedora:created rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://fedora.info/definitions/v4/repository#createdBy +fedora:createdBy rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://fedora.info/definitions/v4/repository#hasParent +fedora:hasParent rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://fedora.info/definitions/v4/repository#lastModified +fedora:lastModified rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://fedora.info/definitions/v4/repository#lastModifiedBy +fedora:lastModifiedBy rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://www.w3.org/1999/02/22-rdf-syntax-ns#Property +rdf:Property rdf:type owl:NamedIndividual ; + :associatedProperty rdfs:comment , + rdfs:label ; + :catalogId "0"^^xsd:nonNegativeInteger . + + +### http://www.w3.org/2000/01/rdf-schema#Class +rdfs:Class rdf:type owl:NamedIndividual ; + :associatedProperty rdfs:comment , + rdfs:label ; + :catalogId "1"^^xsd:nonNegativeInteger . + + +### http://www.w3.org/2000/01/rdf-schema#comment +rdfs:comment rdf:type owl:NamedIndividual ; + :indexedByField :Definition . + + +### http://www.w3.org/2000/01/rdf-schema#label +rdfs:label rdf:type owl:NamedIndividual ; + :indexedByField :Label . + + +### http://www.w3.org/2004/02/skos/core#definition +skos:definition rdf:type owl:NamedIndividual ; + :indexedByField :Definition . + + +### http://www.w3.org/ns/ldp#BasicContainer +ldp:BasicContainer rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://www.w3.org/ns/ldp#Container +ldp:Container rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://www.w3.org/ns/ldp#RDFSource +ldp:RDFSource rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://www.w3.org/ns/ldp#Resource +ldp:Resource rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### http://www.w3.org/ns/ldp#contains +ldp:contains rdf:type owl:NamedIndividual ; + :readOnly "true"^^xsd:boolean . + + +### https://graphofliberty.org/2026/04/ont/Definition +:Definition rdf:type owl:NamedIndividual , + :IndexDocumentField ; + :fieldLabel "Definition"@en ; + :fieldName "definition" ; + rdfs:label "Definition Field"@en . + + +### https://graphofliberty.org/2026/04/ont/Label +:Label rdf:type owl:NamedIndividual , + :IndexDocumentField ; + :fieldLabel "Label"@en ; + :fieldName "label" ; + rdfs:label "Label Field"@en . + + +### Generated by the OWL API (version 4.5.29.2024-05-13T12:11:03Z) https://github.com/owlcs/owlapi diff --git a/publish/src/rdf/ontologies/ldp.ttl b/ontology/ldp.ttl similarity index 100% rename from publish/src/rdf/ontologies/ldp.ttl rename to ontology/ldp.ttl diff --git a/publish/src/rdf/ontologies/owl.ttl b/ontology/owl.ttl similarity index 100% rename from publish/src/rdf/ontologies/owl.ttl rename to ontology/owl.ttl diff --git a/publish/src/rdf/ontologies/rdf-schema.ttl b/ontology/rdf-schema.ttl similarity index 100% rename from publish/src/rdf/ontologies/rdf-schema.ttl rename to ontology/rdf-schema.ttl diff --git a/ontology/skos.rdf b/ontology/skos.rdf new file mode 100644 index 0000000..af18e98 --- /dev/null +++ b/ontology/skos.rdf @@ -0,0 +1,468 @@ + + + + + SKOS Vocabulary + Dave Beckett + Nikki Rogers + Participants in W3C's Semantic Web Deployment Working Group. + An RDF vocabulary for describing the basic structure and content of concept schemes such as thesauri, classification schemes, subject heading lists, taxonomies, 'folksonomies', other types of controlled vocabulary, and also concept schemes embedded in glossaries and terminologies. + Alistair Miles + Sean Bechhofer + + + + Concept + + An idea or notion; a unit of thought. + + + + + Concept Scheme + + A set of concepts, optionally including statements about semantic relationships between those concepts. + A concept scheme may be defined to include concepts from different sources. + Thesauri, classification schemes, subject heading lists, taxonomies, 'folksonomies', and other types of controlled vocabulary are all examples of concept schemes. Concept schemes are also embedded in glossaries and terminologies. + + + + + + + Collection + + A meaningful collection of concepts. + Labelled collections can be used where you would like a set of concepts to be displayed under a 'node label' in the hierarchy. + + + + + + + + + Ordered Collection + + An ordered collection of concepts, where both the grouping and the ordering are meaningful. + Ordered collections can be used where you would like a set of concepts to be displayed in a specific order, and optionally under a 'node label'. + + + + + + + is in scheme + + Relates a resource (for example a concept) to a concept scheme in which it is included. + A concept may be a member of more than one concept scheme. + + + + + + + + + has top concept + + Relates, by convention, a concept scheme to a concept which is topmost in the broader/narrower concept hierarchies for that scheme, providing an entry point to these hierarchies. + + + + + + + + + + + + + is top concept in scheme + + Relates a concept to the concept scheme that it is a top level concept of. + + + + + + + + + + + + + preferred label + + The preferred lexical label for a resource, in a given language. + + + + + + A resource has no more than one value of skos:prefLabel per language tag, and no more than one value of skos:prefLabel without language tag. + + The range of skos:prefLabel is the class of RDF plain literals. + + skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise + disjoint properties. + + + + + alternative label + + An alternative lexical label for a resource. + Acronyms, abbreviations, spelling variants, and irregular plural/singular forms may be included among the alternative labels for a concept. Mis-spelled terms are normally included as hidden labels (see skos:hiddenLabel). + + + + + + The range of skos:altLabel is the class of RDF plain literals. + + skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise disjoint properties. + + + + + hidden label + + A lexical label for a resource that should be hidden when generating visual displays of the resource, but should still be accessible to free text search operations. + + + + + + The range of skos:hiddenLabel is the class of RDF plain literals. + + skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise disjoint properties. + + + + + notation + + A notation, also known as classification code, is a string of characters such as "T58.5" or "303.4833" used to uniquely identify a concept within the scope of a given concept scheme. + By convention, skos:notation is used with a typed literal in the object position of the triple. + + + + + + + note + + A general note, for any purpose. + This property may be used directly, or as a super-property for more specific note types. + + + + + + + change note + + A note about a modification to a concept. + + + + + + + + + definition + + A statement or formal explanation of the meaning of a concept. + + + + + + + + + editorial note + + A note for an editor, translator or maintainer of the vocabulary. + + + + + + + + + example + + An example of the use of a concept. + + + + + + + + + history note + + A note about the past state/use/meaning of a concept. + + + + + + + + + scope note + + A note that helps to clarify the meaning and/or the use of a concept. + + + + + + + + + is in semantic relation with + + Links a concept to a concept related by meaning. + This property should not be used directly, but as a super-property for all properties denoting a relationship of meaning between concepts. + + + + + + + + + + + has broader + + Relates a concept to a concept that is more general in meaning. + Broader concepts are typically rendered as parents in a concept hierarchy (tree). + By convention, skos:broader is only used to assert an immediate (i.e. direct) hierarchical link between two conceptual resources. + + + + + + + + + + + has narrower + + Relates a concept to a concept that is more specific in meaning. + By convention, skos:broader is only used to assert an immediate (i.e. direct) hierarchical link between two conceptual resources. + Narrower concepts are typically rendered as children in a concept hierarchy (tree). + + + + + + + + + + + has related + + Relates a concept to a concept with which there is an associative semantic relationship. + + + + + + + + skos:related is disjoint with skos:broaderTransitive + + + + + has broader transitive + + skos:broaderTransitive is a transitive superproperty of skos:broader. + By convention, skos:broaderTransitive is not used to make assertions. Rather, the properties can be used to draw inferences about the transitive closure of the hierarchical relation, which is useful e.g. when implementing a simple query expansion algorithm in a search application. + + + + + + + + + + + + + has narrower transitive + + skos:narrowerTransitive is a transitive superproperty of skos:narrower. + By convention, skos:narrowerTransitive is not used to make assertions. Rather, the properties can be used to draw inferences about the transitive closure of the hierarchical relation, which is useful e.g. when implementing a simple query expansion algorithm in a search application. + + + + + + + + + + + + + has member + + Relates a collection to one of its members. + + + + + + + + + + + + + + + + + + has member list + + Relates an ordered collection to the RDF list containing its members. + + + + + + + + + + For any resource, every item in the list given as the value of the + skos:memberList property is also a value of the skos:member property. + + + + + is in mapping relation with + + Relates two concepts coming, by convention, from different schemes, and that have comparable meanings + These concept mapping relations mirror semantic relations, and the data model defined below is similar (with the exception of skos:exactMatch) to the data model defined for semantic relations. A distinct vocabulary is provided for concept mapping relations, to provide a convenient way to differentiate links within a concept scheme from links between concept schemes. However, this pattern of usage is not a formal requirement of the SKOS data model, and relies on informal definitions of best practice. + + + + + + + + + has broader match + + skos:broadMatch is used to state a hierarchical mapping link between two conceptual resources in different concept schemes. + + + + + + + + + + + + + has narrower match + + skos:narrowMatch is used to state a hierarchical mapping link between two conceptual resources in different concept schemes. + + + + + + + + + + + + + has related match + + skos:relatedMatch is used to state an associative mapping link between two conceptual resources in different concept schemes. + + + + + + + + + + + + + has exact match + + skos:exactMatch is used to link two concepts, indicating a high degree of confidence that the concepts can be used interchangeably across a wide range of information retrieval applications. skos:exactMatch is a transitive property, and is a sub-property of skos:closeMatch. + + + + + + + + + + skos:exactMatch is disjoint with each of the properties skos:broadMatch and skos:relatedMatch. + + + + + has close match + + skos:closeMatch is used to link two concepts that are sufficiently similar that they can be used interchangeably in some information retrieval applications. In order to avoid the possibility of "compound errors" when combining mappings across more than two concept schemes, skos:closeMatch is not declared to be a transitive property. + + + + + + + + + + diff --git a/publish/Cargo.toml b/publish/Cargo.toml index a4c9d03..cc2d547 100644 --- a/publish/Cargo.toml +++ b/publish/Cargo.toml @@ -14,6 +14,7 @@ http.workspace = true iced.workspace = true rfd.workspace = true oxigraph.workspace = true +oxilangtag.workspace = true tar.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/publish/src/app.rs b/publish/src/app.rs index 3dbc7ca..3a3559e 100644 --- a/publish/src/app.rs +++ b/publish/src/app.rs @@ -1,7 +1,6 @@ use crate::rdf::ontology::{LabeledIri, Ontology}; use crate::rdf::term_helper::{TermHelper, TermHelperMut}; -use crate::rdf::vocab::{gl, rda}; -use gl_search::{Schema, SearchDocument, SearchIndex, NamedFieldDocument, OwnedValue, doc, IndexWriter}; +use gl_search::{Schema, SearchDocument, SearchIndex, doc, IndexWriter}; use http::StatusCode; use iced::alignment::Horizontal; use iced::widget::button::Style; @@ -9,7 +8,6 @@ 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::window::Settings; use iced::{Background, Color, Element, Length, Subscription, Task, color, window}; -use iced::widget::text::Wrapping; use ldp::middleware::BasicAuthMiddleware; use ldp::model::{KeyedDataset, QuadKey}; use ldp::reqwest::{Client, Url}; @@ -19,10 +17,11 @@ use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, Serializat use oxigraph::io::RdfFormat; use oxigraph::model::vocab::{rdf, rdfs}; use oxigraph::model::{BaseDirection, Dataset, NamedNode, Quad, Term, TermRef}; -use tracing::{debug, error, info, info_span, span, Level}; +use tracing::{debug_span, error, trace}; +use crate::rdf::conversion; use crate::widget::iri_input::iri_input; -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub(crate) enum Message { None, Traverse, @@ -43,7 +42,7 @@ pub(crate) enum Message { HoverRow(QuadKey), UnhoverRow(QuadKey), QueryUpdated(String), - SetSearchResults(Vec), + SetSearchResults(Vec), QueryTypeUpdated(NamedNode), SearchResultClicked(NamedNode), DatatypeUpdated(QuadKey, Option), @@ -76,7 +75,7 @@ struct SearchState { action: SearchResultClickAction, query: String, type_: LabeledIri, - results: Vec, + results: Vec, } pub(crate) struct Publisher { @@ -97,32 +96,31 @@ pub(crate) struct Publisher { impl Publisher { pub(crate) fn new() -> (Self, Task) { - let ontology = info_span!("Ontology Creation").in_scope(|| { + let ontology = debug_span!("Ontology Creation").in_scope(|| { Ontology::builder() .with_path("/home/alex/.local/share/org.graphofliberty.desktop/ontology") .build() .expect("Failed to build ontology") }); - let index = info_span!("Ontology Indexing").in_scope(|| { - let mut index = SearchIndex::builder() - //.with_path("/home/alex/.local/share/org.graphofliberty.desktop/index") - .build() - .expect("Failed to build search index"); + let index = SearchIndex::builder() + //.with_path("/home/alex/.local/share/org.graphofliberty.desktop/index") + .build() + .expect("Failed to build search index"); - let writer = index.writer().expect("Failed to create search index writer"); - - let classes_to_index = [ - rdf::PROPERTY, - rdfs::CLASS, - ]; + let classes_to_index = [ + rdf::PROPERTY, + rdfs::CLASS, + ]; + debug_span!("Ontology Indexing").in_scope(|| { + let mut writer = index.writer().expect("Failed to build index writer"); for class in &classes_to_index { let class = class.into_owned(); if let Some(id) = ontology.catalog_id(&class) { - index.remove_all_of_type(id).expect("Failed to remove documents from search index"); + writer.remove_all_of_type(id).expect("Failed to remove documents from search index"); for individual in ontology.individuals(&class) { - let info = ontology.info(&individual); + let info = ontology.info(&individual, &conversion::english()); if info.label.is_some() || info.comment.is_some() { let mut document = doc!( Schema::type_field() => id, @@ -137,13 +135,12 @@ impl Publisher { document.add_text(Schema::field(&field.name), info.comment.unwrap_or_default()); } - writer.add_document(document).expect("Failed to add document to search index"); + writer.add(document).expect("Failed to add document to search index"); } } } } - - index + writer.commit().expect("Failed to commit changes to search index"); }); let mut abbreviated_datatypes = ontology @@ -187,7 +184,7 @@ impl Publisher { pub(crate) fn update(&mut self, message: Message) -> Task { let mut task = Task::none(); - debug!(?message); + trace!(?message); match message { Message::Traverse => { @@ -223,13 +220,14 @@ impl Publisher { } if let Some(writer) = &self.index_writer { - writer.add_document(document).expect("Unable to add document to search index"); + writer.add(document).expect("Unable to add document to search index"); } } } Message::CommitIndex => { if let Some(writer) = &mut self.index_writer { writer.commit().expect("Unable to commit updates to search index"); + self.index_writer = None; } } Message::WindowClosed(id) => { @@ -368,15 +366,20 @@ impl Publisher { SearchResultClickAction::URLInput => self.ontology.catalog_id(&search_state.type_.iri), }; - search_state.query = new_query.clone(); let index = self.index.clone(); - task = Task::future(async move { - tokio::task::spawn_blocking(move || { - let result = index.query(catalog_id, new_query.as_str(), Schema::all_fields()) - .expect("Error encountered while querying index"); - Message::SetSearchResults(result) - }).await.unwrap() + let query = new_query.clone(); + let search_task = tokio::task::spawn_blocking(move || { + index.query(catalog_id, query.as_str(), Schema::all_fields(), 25) }); + task = Task::future(async { + match search_task.await { + Ok(Ok(documents)) => Message::SetSearchResults(documents), + Ok(Err(err)) => Message::ShowError(err.to_string()), + Err(err) => Message::ShowError(err.to_string()), + } + }); + + search_state.query = new_query; }; } Message::SetSearchResults(results) => { @@ -551,11 +554,11 @@ impl Publisher { container(space()).width(BUTTON_WIDTH) }; - let property_label = "foo"; /*self + let property_label = self .ontology - .info(triple.predicate.as_ref()) - .and_then(|info| info.label.clone()) - .unwrap_or(self.ontology.abbreviate(triple.predicate.as_ref()));*/ + .info(&triple.predicate, &conversion::english()) + .label + .unwrap_or(self.ontology.abbreviate(triple.predicate.as_ref())); let property: Element = if state.read_only { text(property_label).into() @@ -572,14 +575,13 @@ impl Publisher { let value_label = term.value_as_named_node().and_then(|node| { self.ontology - .info(&node.into_owned()) + .info(&node.into_owned(), &conversion::english()) .label .map(|label| container(text(label))) }); let value = term .value_as_named_node() - //.map(|node| self.ontology.abbreviate(node)) .map(|node| node.as_str().to_string()) .unwrap_or(term.value().to_string()); @@ -591,7 +593,8 @@ impl Publisher { }) .unwrap_or(Horizontal::Left); - let foo = iri_input(self.ontology.prefixes(), "Object", value.as_str()) + let object_input = iri_input(self.ontology.prefixes(), "Object", value.as_str()) + .align_x(value_alignment) .on_input(move |value| Message::ValueUpdated(key, value)) .on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object(key))); @@ -608,8 +611,7 @@ impl Publisher { .on_input(move |input| { let value = if input.is_empty() { None } else { Some(input) }; Message::DatatypeUpdated(key, value) - }) - .into() + }).into() }; let language_input = match term.datatype() { @@ -647,9 +649,7 @@ impl Publisher { button_area, property, value_label, - //value_input, - foo, - //search_launcher, + object_input, datatype_selector, language_input, direction_slider, @@ -668,7 +668,7 @@ impl Publisher { let buttons = entities.map(|entity| { let label = self .ontology - .info(&entity) + .info(&entity, &conversion::english()) .label .unwrap_or(entity.as_str().to_string()); button(text(label)) @@ -699,17 +699,15 @@ impl Publisher { 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) + /*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.0 - .get("iri") + let iri = document.get("iri") .and_then(|values| values.first()) .map(|value| match value { OwnedValue::Str(string) => string, @@ -719,50 +717,9 @@ impl Publisher { button(text(cell_value).wrapping(Wrapping::Word)) .on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri))) .style(button::text) - }).width(Length::Fixed(256.0))); + }).width(Length::Fixed(256.0)));*/ } - /*let label_column = table::column("Label", |result: &NamedNode| { - let header_text = self - .ontology - .info(result.as_ref()) - .and_then(|info| info.label.clone()) - .unwrap_or("".to_string()); - - button(text(header_text)) - .on_press(Message::SearchResultClicked(result.clone())) - .style(button::text) - }); - - let type_column = table::column("Type", |result: &NamedNode| { - let header_text = self - .ontology - .info(result.as_ref()) - .and_then(|info| self.ontology.info(info.type_.as_ref())) - .and_then(|info| info.label.clone()) - .unwrap_or("Unknown".to_string()); - - button(text(header_text)) - .on_press(Message::SearchResultClicked(result.clone())) - .style(button::text) - }); - - let description_column = table::column("Description", |result: &NamedNode| { - let header_text = self.ontology.info(result.as_ref()) - .and_then(|info| info.comment.clone()) - .unwrap_or("Unknown".to_string()); - - button(text(header_text)) - .on_press(Message::SearchResultClicked(result.clone())) - .style(button::text) - }); - - let iri_column = table::column("IRI", |result: &NamedNode| { - button(text(result.as_str())) - .on_press(Message::SearchResultClicked(result.clone())) - .style(button::text) - });*/ - let results_table = if columns.is_empty() { None } else { @@ -779,9 +736,10 @@ impl Publisher { let index_button = button("Index").on_press(Message::Traverse); - let search_launcher = button(text("\u{1f50e}")) - .on_press(Message::OpenQueryWindow(SearchResultClickAction::URLInput)) - .style(button::text); + let address_input = iri_input(self.ontology.prefixes(), "URL", &self.url_input) + .on_input(Message::URLInputChanged) + .on_submit(Message::URLInputSubmitted) + .on_control_click(Message::OpenQueryWindow(SearchResultClickAction::URLInput)); let mut rows: Vec> = vec![]; rows = self @@ -812,10 +770,7 @@ impl Publisher { row![ add_row_button, index_button, - search_launcher, - text_input("URL", &self.url_input) - .on_input(Message::URLInputChanged) - .on_submit(Message::URLInputSubmitted), + address_input, save_button, ], scrollable(body), diff --git a/publish/src/error.rs b/publish/src/error.rs index 025aee7..ca27bee 100644 --- a/publish/src/error.rs +++ b/publish/src/error.rs @@ -24,4 +24,10 @@ pub(crate) enum Error { #[error(transparent)] QueryEvaluation(#[from] oxigraph::sparql::QueryEvaluationError), + + #[error(transparent)] + UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError), + + #[error(transparent)] + SparqlSyntax(#[from] oxigraph::sparql::SparqlSyntaxError), } diff --git a/publish/src/rdf/conversion.rs b/publish/src/rdf/conversion.rs index 1949cb6..8c03a0f 100644 --- a/publish/src/rdf/conversion.rs +++ b/publish/src/rdf/conversion.rs @@ -1,9 +1,37 @@ use oxigraph::model::{NamedNode, Quad, Term}; use oxigraph::model::vocab::{rdf, xsd}; -use tracing::info; +use oxilangtag::LanguageTag; -pub fn quad_to_term(quad: &Quad) -> &Term { - &quad.object +pub enum LanguageCondition { + ExactMatch(LanguageTag), + RelaxedMatch(LanguageTag), + Untagged, +} + +pub fn quad_into_term(quad: Quad) -> Term { + 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> { @@ -14,7 +42,11 @@ pub fn term_to_named_node(term: &Term) -> Option<&NamedNode> { } } -pub fn term_to_string(term: &Term) -> Option<&str> { +pub fn term_into_string(term: Term) -> Option { + term_as_str(&term).map(String::from) +} + +pub fn term_as_str(term: &Term) -> Option<&str> { if let Term::Literal(literal) = term { match literal.datatype() { xsd::STRING | xsd::NORMALIZED_STRING | rdf::LANG_STRING | rdf::DIR_LANG_STRING => { diff --git a/publish/src/rdf/materialize.rs b/publish/src/rdf/materialize.rs index 556b5a8..14b2256 100644 --- a/publish/src/rdf/materialize.rs +++ b/publish/src/rdf/materialize.rs @@ -1,10 +1,17 @@ -use oxigraph::model::{Dataset, GraphName, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term}; -use oxigraph::sparql::{QueryResults, SparqlEvaluator}; +use oxigraph::model::{Dataset, GraphName, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term}; +use oxigraph::sparql::SparqlEvaluator; use oxigraph::store::Store; +use tracing::{debug, debug_span}; use crate::error; use crate::rdf::vocab::owl; +const INFERENCE_GRAPH: NamedNodeRef = NamedNodeRef::new_unchecked("https://graphofliberty.org/inference"); +const RDF_SCHEMA: NamedNodeRef = NamedNodeRef::new_unchecked("http://www.w3.org/2000/01/rdf-schema#"); + pub fn same_as(store: &mut Store) -> error::Result<()> { + let span = debug_span!("Materialize owl:sameAs"); + let _enter = span.enter(); + let additional_quads = store .quads_for_pattern(None, Some(owl::SAME_AS), None, None) .filter_map(Result::ok) @@ -19,6 +26,7 @@ pub fn same_as(store: &mut Store) -> error::Result<()> { None, ).filter_map(Result::ok) { quad.subject = NamedOrBlankNode::NamedNode(y.clone()); + quad.graph_name = GraphName::NamedNode(INFERENCE_GRAPH.into_owned()); new_dataset.insert(quad.as_ref()); } @@ -29,6 +37,7 @@ pub fn same_as(store: &mut Store) -> error::Result<()> { None, ).filter_map(Result::ok) { quad.subject = NamedOrBlankNode::NamedNode(x.clone()); + quad.graph_name = GraphName::NamedNode(INFERENCE_GRAPH.into_owned()); new_dataset.insert(quad.as_ref()); } } @@ -40,97 +49,70 @@ pub fn same_as(store: &mut Store) -> error::Result<()> { let new_size = store.len()?; if new_size > old_size { - same_as(store) - } else { - Ok(()) + let difference = new_size - old_size; + debug!(?old_size, ?new_size, ?difference, "same_as"); + same_as(store)?; } + + Ok(()) } -pub fn super_classes(store: &mut Store) -> error::Result<()> { - let query = SparqlEvaluator::new() - .parse_query( - r#"PREFIX rdfs: +pub fn super_properties(store: &mut Store) -> error::Result<()> { + let span = debug_span!("Materialize rdfs:subPropertyOf"); + let _enter = span.enter(); -CONSTRUCT { - ?item a ?parent -} WHERE { - ?item a ?class . - ?class rdfs:subClassOf ?parent . -}"#, - ) - .expect("Unable to parse superclass query"); - - let mut additional_quads = Dataset::new(); - if let QueryResults::Graph(graph) = query.on_store(&store).execute().unwrap() - { - additional_quads.extend(graph.filter_map(Result::ok).map(|triple| { - Quad::new( - triple.subject, - triple.predicate, - triple.object, - GraphName::DefaultGraph, - ) - })); + let update = SparqlEvaluator::new() + .with_prefix("rdfs", RDF_SCHEMA.as_str())? + .with_prefix("inference", INFERENCE_GRAPH.as_str())? + .parse_update(r#"INSERT { + GRAPH inference: { + ?property rdfs:subPropertyOf ?parent . + ?subject ?parent ?object . } +} WHERE { + GRAPH ?g1 { ?property rdfs:subPropertyOf+ ?parent } + OPTIONAL { GRAPH ?g2 { ?subject ?parent ?object } } +}"#)?; let old_size = store.len()?; - store.extend(&additional_quads)?; + update.on_store(&store).execute()?; let new_size = store.len()?; if new_size > old_size { - super_classes(store) - } else { - Ok(()) + let difference = new_size - old_size; + debug!(?old_size, ?new_size, ?difference, "super_properties"); + super_properties(store)?; } + + Ok(()) } -/*fn iri_information(dataset: &Dataset) -> HashMap { - let query = SparqlEvaluator::new() - .parse_query( - r#"PREFIX rdf: -PREFIX rdfs: -PREFIX gl: +pub fn super_classes(store: &mut Store) -> error::Result<()> { + let span = debug_span!("Materialize rdfs:subClassOf"); + let _enter = span.enter(); -SELECT DISTINCT ?class ?subject ?label ?comment ?read_only { - VALUES ?class { rdf:Property rdfs:Class } - ?subject a ?class . - OPTIONAL { - ?subject rdfs:label ?label - FILTER (langMATCHES(LANG(?label), "en") || !hasLANG(?label)) + let update = SparqlEvaluator::new() + .with_prefix("rdfs", RDF_SCHEMA.as_str())? + .with_prefix("inference", INFERENCE_GRAPH.as_str())? + .parse_update(r#"INSERT { + GRAPH inference: { + ?class rdfs:subClassOf ?parent . + ?item a ?parent . } - OPTIONAL { - ?subject rdfs:comment ?comment - FILTER (langMATCHES(LANG(?label), "en") || !hasLANG(?comment)) - } - OPTIONAL { ?subject gl:readOnly ?read_only } -}"#, - ) - .expect("Unable to parse property query"); +} WHERE { + GRAPH ?g1 { ?item a ?class } + GRAPH ?g2 { ?class rdfs:subClassOf+ ?parent } +}"#)?; - let mut results = HashMap::new(); - if let QueryResults::Solutions(solutions) = - query.on_queryable_dataset(dataset).execute().unwrap() - { - for solution in solutions.filter_map(Result::ok) { - let type_ = solution.get("class").and_then(crate::rdf::ontology::term_to_named_node); - let subject = solution.get("subject").and_then(crate::rdf::ontology::term_to_named_node); - let label = solution.get("label").and_then(crate::rdf::ontology::term_to_string); - let comment = solution.get("comment").and_then(crate::rdf::ontology::term_to_string); - let read_only = solution - .get("read_only") - .and_then(crate::rdf::ontology::term_to_boolean) - .unwrap_or(false); + let old_size = store.len()?; + update.on_store(&store).execute()?; + let new_size = store.len()?; - if let Some(subject) = subject && let Some(type_) = type_ { - let info = IriInformation { - type_: type_.clone(), - label: label.map(String::from), - comment: comment.map(String::from), - read_only, - }; - results.insert(subject.to_owned(), info); - } - } + if new_size > old_size { + let difference = new_size - old_size; + debug!(?old_size, ?new_size, ?difference, "super_classes"); + super_classes(store)?; } - results -}*/ \ No newline at end of file + + Ok(()) +} \ No newline at end of file diff --git a/publish/src/rdf/ontologies/ontology.ttl b/publish/src/rdf/ontologies/ontology.ttl index a1a14ef..14f2ced 100644 --- a/publish/src/rdf/ontologies/ontology.ttl +++ b/publish/src/rdf/ontologies/ontology.ttl @@ -15,6 +15,7 @@ @prefix rdaw: . @prefix rdax: . @prefix rdfs: . +@prefix skos: . @prefix fedora: . @base . @@ -212,6 +213,14 @@ fedora:lastModifiedBy rdf:type owl:NamedIndividual ; :readOnly "true"^^xsd:boolean . +### http://rdaregistry.info/Elements/x/P00028 +rdax:P00028 rdf:type owl:NamedIndividual ; + :associatedProperty rdfs:label , + skos:definition ; + :catalogId "2"^^xsd:nonNegativeInteger ; + rdfs:label "has related entity of RDA entity"@en . + + ### http://www.w3.org/1999/02/22-rdf-syntax-ns#Property rdf:Property rdf:type owl:NamedIndividual ; :associatedProperty rdfs:comment , @@ -228,12 +237,17 @@ rdfs:Class rdf:type owl:NamedIndividual ; ### http://www.w3.org/2000/01/rdf-schema#comment rdfs:comment rdf:type owl:NamedIndividual ; - :indexedByField :comment . + :indexedByField :Comment . ### http://www.w3.org/2000/01/rdf-schema#label rdfs:label rdf:type owl:NamedIndividual ; - :indexedByField :label . + :indexedByField :Label . + + +### http://www.w3.org/2004/02/skos/core#definition +skos:definition rdf:type owl:NamedIndividual ; + :indexedByField :Definition . ### http://www.w3.org/ns/ldp#BasicContainer @@ -261,16 +275,24 @@ ldp:contains rdf:type owl:NamedIndividual ; :readOnly "true"^^xsd:boolean . -### https://graphofliberty.org/2026/04/ont/comment -:comment rdf:type owl:NamedIndividual , +### https://graphofliberty.org/2026/04/ont/Comment +:Comment rdf:type owl:NamedIndividual , :IndexDocumentField ; :fieldLabel "Comment"@en ; :fieldName "comment" ; rdfs:label "Comment Field"@en . -### https://graphofliberty.org/2026/04/ont/label -:label rdf:type owl:NamedIndividual , +### https://graphofliberty.org/2026/04/ont/Definition +:Definition rdf:type owl:NamedIndividual , + :IndexDocumentField ; + :fieldLabel "Definition"@en ; + :fieldName "definition" ; + rdfs:label "Definition Field"@en . + + +### https://graphofliberty.org/2026/04/ont/Label +:Label rdf:type owl:NamedIndividual , :IndexDocumentField ; :fieldLabel "Label"@en ; :fieldName "label" ; diff --git a/publish/src/rdf/ontology.rs b/publish/src/rdf/ontology.rs index febdc58..b82b1d2 100644 --- a/publish/src/rdf/ontology.rs +++ b/publish/src/rdf/ontology.rs @@ -1,16 +1,16 @@ use crate::error; -use crate::rdf::vocab::{gl, owl, rda}; -use oxigraph::io::{RdfFormat, RdfParser}; +use crate::rdf::vocab::gl; use oxigraph::model::vocab::{rdf, rdfs, xsd}; -use oxigraph::model::{Dataset, GraphName, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef}; +use oxigraph::model::{LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef}; use oxigraph::sparql::{QueryResults, SparqlEvaluator}; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Display; use std::path::{Path, PathBuf}; use std::sync::LazyLock; use oxigraph::store::Store; -use tracing::{info, instrument}; +use tracing::debug_span; use crate::rdf::{conversion, materialize}; +use crate::rdf::conversion::LanguageCondition; static PREFIXES: LazyLock> = LazyLock::new(|| { BTreeMap::from_iter([ @@ -70,8 +70,9 @@ impl OntologyBuilder { .collect::>(); materialize::same_as(&mut store)?; + materialize::super_properties(&mut store)?; materialize::super_classes(&mut store)?; - store.optimize()?; + debug_span!("Optimize Ontology").in_scope(|| store.optimize())?; // Full-text search index field names let fields = Self::fields(&store)?; @@ -82,12 +83,16 @@ impl OntologyBuilder { .filter_map(Result::ok) { let label = store.quads_for_pattern(Some(quad.subject.as_ref().into()), Some(rdfs::LABEL), None, None) .filter_map(Result::ok) - .filter_map(|quad| conversion::term_to_string(&quad.object).map(String::from)) + .map(conversion::quad_into_term) + .filter(|term| conversion::language_matches(term, &conversion::english())) + .filter_map(conversion::term_into_string) .next(); let comment = store.quads_for_pattern(Some(quad.subject.as_ref().into()), Some(rdfs::COMMENT), None, None) .filter_map(Result::ok) - .filter_map(|quad| conversion::term_to_string(&quad.object).map(String::from)) + .map(conversion::quad_into_term) + .filter(|term| conversion::language_matches(term, &conversion::english())) + .filter_map(conversion::term_into_string) .next(); if let Some(catalog_id) = conversion::term_to_u64(&quad.object) && @@ -130,28 +135,25 @@ impl OntologyBuilder { fn fields(store: &Store) -> error::Result> { let query = SparqlEvaluator::new() + .with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")? + .with_prefix("gl", "https://graphofliberty.org/2026/04/ont/")? .parse_query( - r#"PREFIX rdfs: -PREFIX gl: - -SELECT DISTINCT ?subject ?name ?label { + 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)) } -}"#, - ) - .expect("Unable to parse field query"); +}"#)?; 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_to_string); - let label = solution.get("label").and_then(conversion::term_to_string); + 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 { @@ -306,15 +308,19 @@ impl Ontology { } else { None }) } - pub fn info(&self, iri: &NamedNode) -> 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) .filter_map(Result::ok) - .filter_map(|quad| conversion::term_to_string(&quad.object).map(String::from)) + .map(conversion::quad_into_term) + .filter(|term| conversion::language_matches(term, language)) + .filter_map(conversion::term_into_string) .next(); let comment = self.store.quads_for_pattern(Some(iri.as_ref().into()), Some(rdfs::COMMENT), None, None) .filter_map(Result::ok) - .filter_map(|quad| conversion::term_to_string(&quad.object).map(String::from)) + .map(conversion::quad_into_term) + .filter(|term| conversion::language_matches(term, language)) + .filter_map(conversion::term_into_string) .next(); LabeledIri { diff --git a/publish/src/widget/iri_input.rs b/publish/src/widget/iri_input.rs index 34d7200..59b85a0 100644 --- a/publish/src/widget/iri_input.rs +++ b/publish/src/widget/iri_input.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use iced::{keyboard, widget, Element, Event, Length, Rectangle, Size}; +use iced::{alignment, keyboard, widget, Element, Event, Length, Rectangle, Size}; use iced::advanced::{mouse, text, Shell}; use iced::advanced::layout::{Limits, Node}; use iced::advanced::renderer::Style; @@ -53,6 +53,12 @@ where text_input, } } + + pub fn align_x(mut self, alignment: impl Into) -> Self { + self.text_input = self.text_input.align_x(alignment); + self + } + pub fn on_control_click(mut self, on_control_click: Message) -> Self { self.on_control_click = Some(on_control_click); self @@ -67,6 +73,11 @@ where self.text_input = self.text_input.on_input(wrapped); self } + + pub fn on_submit(mut self, message: Message) -> Self { + self.text_input = self.text_input.on_submit(message); + self + } } impl<'a, Message, Theme, Renderer> From> diff --git a/search/src/index.rs b/search/src/index.rs index f8e871b..b5683c0 100644 --- a/search/src/index.rs +++ b/search/src/index.rs @@ -1,14 +1,15 @@ -use crate::{error, SearchDocument}; +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet}; +use crate::{error, update}; use crate::schema::Schema; use std::path::PathBuf; use tantivy::collector::TopDocs; use tantivy::directory::{ManagedDirectory, MmapDirectory}; use tantivy::query::{BooleanQuery, Occur, QueryParser, TermQuery}; -use tantivy::schema::{Field, IndexRecordOption, NamedFieldDocument, Value}; +use tantivy::schema::{Field, IndexRecordOption}; use tantivy::tokenizer::{LowerCaser, NgramTokenizer, TextAnalyzer, TokenizerManager}; -use tantivy::{Document, Index, IndexReader, IndexWriter, ReloadPolicy, Score, TantivyDocument, Term}; -use tantivy::indexer::UserOperation; -use tracing::{info, instrument, span, Level}; +use tantivy::{Index, IndexReader, ReloadPolicy, TantivyDocument, Term}; +use tracing::{span, Level}; #[derive(Default)] pub struct SearchIndexBuilder { @@ -65,15 +66,9 @@ impl SearchIndex { SearchIndexBuilder::default() } - pub fn writer(&self) -> crate::Result { - Ok(self.index.writer(128_000_000)?) - } - - pub fn remove_all_of_type(&mut self, type_: u64) -> crate::Result<()> { - let mut writer: IndexWriter = self.index.writer(64_000_000)?; - writer.delete_term(Term::from_field_u64(Schema::type_field(), type_)); - writer.commit()?; - Ok(()) + pub fn writer(&self) -> crate::Result { + let inner = self.index.writer(128 * 1024usize.pow(2))?; + Ok(crate::IndexWriter { inner }) } pub fn query( @@ -81,8 +76,9 @@ impl SearchIndex { type_: Option, user_query: &str, default_fields: Vec, - ) -> error::Result> { - let span = span!(Level::INFO, "Search Query"); + limit: usize, + ) -> error::Result> { + let span = span!(Level::DEBUG, "Search Query"); let _enter = span.enter(); let parser = QueryParser::for_index(&self.index, default_fields); @@ -100,12 +96,11 @@ impl SearchIndex { let query = BooleanQuery::new(subqueries); let searcher = self.reader.searcher(); - let results = searcher.search(&query, &TopDocs::with_limit(10000).order_by_score())? + let results: Vec = searcher.search(&query, &TopDocs::with_limit(limit).order_by_score())? .iter() .map(|(_, address)| searcher.doc(*address)) .filter_map(Result::ok) - .map(|doc: SearchDocument| doc.to_named_doc(Schema::schema())) .collect(); Ok(results) } -} +} \ No newline at end of file diff --git a/search/src/lib.rs b/search/src/lib.rs index 6bb9d8f..0383146 100644 --- a/search/src/lib.rs +++ b/search/src/lib.rs @@ -1,12 +1,13 @@ mod error; mod index; mod schema; +mod update; pub use tantivy::TantivyDocument as SearchDocument; pub use tantivy::doc; -pub use tantivy::schema::{NamedFieldDocument, OwnedValue}; -pub use tantivy::IndexWriter; +pub use tantivy::schema::OwnedValue; pub use error::{Result, SearchError}; pub use index::{SearchIndex, SearchIndexBuilder}; pub use schema::Schema; +pub use update::IndexWriter; \ No newline at end of file diff --git a/search/src/schema.rs b/search/src/schema.rs index ee160af..13567a0 100644 --- a/search/src/schema.rs +++ b/search/src/schema.rs @@ -28,11 +28,7 @@ impl Schema { schema_builder.add_text_field("iri", schema::STORED | schema::STRING); schema_builder.add_text_field("label", stored_ngram32.clone()); - schema_builder.add_text_field("comment", stored_en_stem.clone()); - - /*schema_builder.add_text_field("nomen", stored_ngram32.clone()); - schema_builder.add_text_field("givenName", stored_ngram32.clone()); - schema_builder.add_text_field("surname", stored_ngram32);*/ + schema_builder.add_text_field("definition", stored_en_stem.clone()); /*schema_builder.add_text_field("title", en_stem.clone()); schema_builder.add_text_field("description", en_stem.clone()); diff --git a/search/src/update.rs b/search/src/update.rs new file mode 100644 index 0000000..1ea2c2b --- /dev/null +++ b/search/src/update.rs @@ -0,0 +1,24 @@ +use tantivy::Term; +use crate::Schema; + +pub struct IndexWriter { + pub(crate) inner: tantivy::IndexWriter, +} + +impl IndexWriter { + pub fn add(&self, document: crate::SearchDocument) -> crate::Result<()> { + let _ = self.inner.add_document(document)?; + Ok(()) + } + + pub fn remove_all_of_type(&mut self, type_: u64) -> crate::Result<()> { + self.inner.delete_term(Term::from_field_u64(Schema::type_field(), type_)); + self.inner.commit()?; + Ok(()) + } + + pub fn commit(&mut self) -> crate::Result<()> { + self.inner.commit()?; + Ok(()) + } +} \ No newline at end of file