SHA256
1
0
This commit is contained in:
Alex Wied
2026-06-09 19:25:47 -04:00
parent 1f8c6201b4
commit b53a3ad1e0
5 changed files with 155 additions and 117 deletions
+47 -10
View File
@@ -499,9 +499,9 @@ impl Publisher {
let property_label = self let property_label = self
.ontology .ontology
.label(triple.predicate.as_ref()) .info(triple.predicate.as_ref())
.unwrap_or(self.ontology.abbreviate(triple.predicate.as_ref()).as_str()) .and_then(|info| info.label.clone())
.to_string(); .unwrap_or(self.ontology.abbreviate(triple.predicate.as_ref()));
let property: Element<Message> = if state.read_only { let property: Element<Message> = if state.read_only {
text(property_label).into() text(property_label).into()
@@ -518,7 +518,8 @@ impl Publisher {
let value_label = term.value_as_named_node().and_then(|node| { let value_label = term.value_as_named_node().and_then(|node| {
self.ontology self.ontology
.label(node) .info(node)
.and_then(|info| info.label.clone())
.map(|label| container(text(label))) .map(|label| container(text(label)))
}); });
@@ -639,8 +640,8 @@ impl Publisher {
let buttons = entities.map(|entity| { let buttons = entities.map(|entity| {
let label = self let label = self
.ontology .ontology
.label(entity.as_ref()) .info(entity.as_ref())
.map(|label| label.to_string()) .and_then(|info| info.label.clone())
.unwrap_or(entity.as_str().to_string()); .unwrap_or(entity.as_str().to_string());
button(text(label)) button(text(label))
.on_press(Message::NewDocument(entity)) .on_press(Message::NewDocument(entity))
@@ -659,19 +660,55 @@ 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 type_column = table::column("Type", |result: &NamedNode| { let label_column = table::column("Label", |result: &NamedNode| {
let label = self.ontology.label(result.as_ref()).unwrap_or("Unknown"); let header_text = self
.ontology
.info(result.as_ref())
.and_then(|info| info.label.clone())
.unwrap_or("".to_string());
button(text(label)) button(text(header_text))
.on_press(Message::SearchResultClicked(result.clone())) .on_press(Message::SearchResultClicked(result.clone()))
.style(button::text) .style(button::text)
}); });
let type_column = table::column("Type", |result: &NamedNode| {
let header_text = self
.ontology
.info(result.as_ref())
.map(|info| info.type_.to_string())
.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| { 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([type_column, iri_column], &search_state.results));
let results_table = scrollable(table(
[
label_column,
//description_column,
type_column,
iri_column,
],
&search_state.results,
));
return column![search_input, results_table].into(); return column![search_input, results_table].into();
} }
+2 -1
View File
@@ -89,7 +89,8 @@ xsd:integer rdf:type rdfs:Datatype ;
### http://www.w3.org/2001/XMLSchema#nonNegativeInteger ### http://www.w3.org/2001/XMLSchema#nonNegativeInteger
xsd:nonNegativeInteger rdfs:label "Non-negative Integer"@en . xsd:nonNegativeInteger rdf:type rdfs:Datatype ;
rdfs:label "Non-negative Integer"@en .
### http://www.w3.org/2001/XMLSchema#string ### http://www.w3.org/2001/XMLSchema#string
+100 -88
View File
@@ -3,12 +3,12 @@ use crate::rdf::vocab::{gl, owl};
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::{
Dataset, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, Dataset, GraphName, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term,
TripleRef, TermRef, Triple, TripleRef,
}; };
use oxigraph::sparql::{QueryResults, SparqlEvaluator}; use oxigraph::sparql::{QueryResults, SparqlEvaluator};
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use tracing::debug; use std::fmt::Display;
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#"),
@@ -106,37 +106,62 @@ impl<'a> OntologyBuilder<'a> {
} }
} }
fn lookup_iri_information<'b>( fn materialize_super_classes(dataset: &mut Dataset) {
dataset: &Dataset,
classes: impl IntoIterator<Item = NamedNodeRef<'b>>,
) -> HashMap<NamedNode, IriInformation> {
let class_filter = classes
.into_iter()
.map(|node| node.to_string())
.collect::<Vec<_>>()
.join(",");
let query = SparqlEvaluator::new() let query = SparqlEvaluator::new()
.parse_query( .parse_query(
format!( r#"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
r#"PREFIX gl: <https://graphofliberty.org/2026/04/ont/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT DISTINCT ?subject ?label ?comment ?read_only {{ 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_queryable_dataset(&*dataset).execute().unwrap()
{
additional_quads.extend(graph.filter_map(Result::ok).map(|triple| {
Quad::new(
triple.subject,
triple.predicate,
triple.object,
GraphName::DefaultGraph,
)
}));
}
let old_size = dataset.len();
dataset.extend(&additional_quads);
let new_size = dataset.len();
if new_size > old_size {
Self::materialize_super_classes(dataset)
}
}
fn iri_information(dataset: &Dataset) -> HashMap<NamedNode, IriInformation> {
let query = SparqlEvaluator::new()
.parse_query(
r#"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX gl: <https://graphofliberty.org/2026/04/ont/>
SELECT DISTINCT ?class ?subject ?label ?comment ?read_only {
VALUES ?class { rdf:Property rdfs:Class }
?subject a ?class . ?subject a ?class .
FILTER(?class IN ({class_filter})) OPTIONAL {
OPTIONAL {{
?subject rdfs:label ?label ?subject rdfs:label ?label
FILTER (LANG(?label) = 'en' || LANG(?label) = '') FILTER (LANG(?label) = 'en' || LANG(?label) = '')
}} }
OPTIONAL {{ OPTIONAL {
?subject rdfs:comment ?comment ?subject rdfs:comment ?comment
FILTER (LANG(?comment) = 'en' || LANG(?comment) = '') FILTER (LANG(?comment) = 'en' || LANG(?comment) = '')
}} }
OPTIONAL {{ ?subject gl:readOnly ?read_only }} OPTIONAL { ?subject gl:readOnly ?read_only }
}}"# }"#,
)
.as_str(),
) )
.expect("Unable to parse property query"); .expect("Unable to parse property query");
@@ -145,6 +170,14 @@ SELECT DISTINCT ?subject ?label ?comment ?read_only {{
query.on_queryable_dataset(dataset).execute().unwrap() query.on_queryable_dataset(dataset).execute().unwrap()
{ {
for solution in solutions.filter_map(Result::ok) { for solution in solutions.filter_map(Result::ok) {
let resource_type = solution.get("class").and_then(term_to_named_node).and_then(
|class| match class.as_ref() {
rdf::PROPERTY => Some(ResourceType::Property),
rdfs::CLASS => Some(ResourceType::Class),
_ => None,
},
);
let subject = solution.get("subject").and_then(term_to_named_node); let subject = solution.get("subject").and_then(term_to_named_node);
let label = solution.get("label").and_then(term_to_string); let label = solution.get("label").and_then(term_to_string);
let comment = solution.get("comment").and_then(term_to_string); let comment = solution.get("comment").and_then(term_to_string);
@@ -153,8 +186,11 @@ SELECT DISTINCT ?subject ?label ?comment ?read_only {{
.and_then(term_to_boolean) .and_then(term_to_boolean)
.unwrap_or(false); .unwrap_or(false);
if let Some(subject) = subject { if let Some(subject) = subject
&& let Some(type_) = resource_type
{
let info = IriInformation { let info = IriInformation {
type_,
label: label.map(String::from), label: label.map(String::from),
comment: comment.map(String::from), comment: comment.map(String::from),
read_only, read_only,
@@ -180,21 +216,9 @@ SELECT DISTINCT ?subject ?label ?comment ?read_only {{
dataset.extend(quads); dataset.extend(quads);
} }
Self::materialize_same_as(&mut dataset); Self::materialize_same_as(&mut dataset);
Self::materialize_super_classes(&mut dataset);
let properties = Self::lookup_iri_information( let iri_info = Self::iri_information(&mut dataset);
&dataset,
[
rdf::PROPERTY,
rdfs::DATATYPE,
owl::ANNOTATION_PROPERTY,
owl::DATATYPE_PROPERTY,
owl::FUNCTIONAL_PROPERTY,
owl::OBJECT_PROPERTY,
owl::ONTOLOGY_PROPERTY,
],
);
let classes = Self::lookup_iri_information(&dataset, [rdfs::CLASS, owl::CLASS]);
// Full-text search index field names // Full-text search index field names
let mut field_map = HashMap::new(); let mut field_map = HashMap::new();
@@ -222,8 +246,7 @@ SELECT DISTINCT ?subject ?label ?comment ?read_only {{
Ok(Ontology { Ok(Ontology {
dataset, dataset,
prefixes, prefixes,
properties, iri_info,
classes,
field_map, field_map,
catalog_ids, catalog_ids,
}) })
@@ -231,17 +254,33 @@ SELECT DISTINCT ?subject ?label ?comment ?read_only {{
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct IriInformation { pub enum ResourceType {
label: Option<String>, Property,
comment: Option<String>, Class,
read_only: bool, }
impl Display for ResourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
ResourceType::Property => "Property".to_string(),
ResourceType::Class => "Class".to_string(),
};
write!(f, "{}", str)
}
}
#[derive(Clone, Debug)]
pub struct IriInformation {
pub type_: ResourceType,
pub label: Option<String>,
pub comment: Option<String>,
pub read_only: bool,
} }
pub struct Ontology { pub struct Ontology {
dataset: Dataset, dataset: Dataset,
prefixes: HashMap<String, String>, prefixes: HashMap<String, String>,
properties: HashMap<NamedNode, IriInformation>, iri_info: HashMap<NamedNode, IriInformation>,
classes: HashMap<NamedNode, IriInformation>,
field_map: HashMap<NamedNode, String>, field_map: HashMap<NamedNode, String>,
catalog_ids: HashMap<NamedNode, u64>, catalog_ids: HashMap<NamedNode, u64>,
} }
@@ -286,18 +325,9 @@ impl Ontology {
} }
} }
pub fn label<'a>(&'a self, node: NamedNodeRef<'a>) -> Option<&'a str> { pub fn info(&self, node: NamedNodeRef<'_>) -> Option<&IriInformation> {
let node = node.into_owned(); let node = node.into_owned();
self.classes self.iri_info.get(&node)
.get(&node)
.map(|info| info.label.as_deref())
.flatten()
.or_else(|| {
self.properties
.get(&node)
.map(|info| info.label.as_deref())
.flatten()
})
} }
pub fn abbreviate(&self, node: NamedNodeRef<'_>) -> String { pub fn abbreviate(&self, node: NamedNodeRef<'_>) -> String {
@@ -378,41 +408,23 @@ SELECT ?subject ?label ?comment WHERE {
pub fn is_read_only<'a>(&self, triple: impl Into<TripleRef<'a>>) -> bool { pub fn is_read_only<'a>(&self, triple: impl Into<TripleRef<'a>>) -> bool {
let triple = triple.into(); let triple = triple.into();
let read_only_property = self self.iri_info
.properties
.get(&triple.predicate.into_owned()) .get(&triple.predicate.into_owned())
.map(|info| info.read_only) .map(|info| info.read_only)
.unwrap_or(false); .unwrap_or(false)
let read_only_class = match (triple.predicate, triple.object) {
(rdf::TYPE, TermRef::NamedNode(node)) => self
.classes
.get(&node.into_owned())
.map(|info| info.read_only)
.unwrap_or(false),
_ => false,
};
read_only_property || read_only_class
} }
pub fn exclude_read_only_predicate(&self) -> impl Fn(TripleRef<'_>) -> bool + 'static { pub fn exclude_read_only_predicate(&self) -> impl Fn(TripleRef<'_>) -> bool + 'static {
let classes = self.classes.clone(); let iri_info = self.iri_info.clone();
let properties = self.properties.clone(); move |triple| match (triple.predicate, triple.object) {
move |triple| { (rdf::TYPE, TermRef::NamedNode(node)) => iri_info
let read_only_property = properties
.get(&triple.predicate.into_owned())
.map(|info| info.read_only)
.unwrap_or(false);
let read_only_class = match (triple.predicate, triple.object) {
(rdf::TYPE, TermRef::NamedNode(node)) => classes
.get(&node.into_owned()) .get(&node.into_owned())
.map(|info| info.read_only) .map(|info| info.read_only)
.unwrap_or(false), .unwrap_or(false),
_ => false, (predicate, _) => iri_info
}; .get(&predicate.into_owned())
!(read_only_property || read_only_class) .map(|info| info.read_only)
.unwrap_or(false),
} }
} }
-12
View File
@@ -17,18 +17,6 @@ pub mod owl {
pub const SAME_AS: NamedNodeRef = pub const SAME_AS: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#sameAs"); NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#sameAs");
pub const CLASS: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#Class");
pub const OBJECT_PROPERTY: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#ObjectProperty");
pub const DATATYPE_PROPERTY: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#DatatypeProperty");
pub const ANNOTATION_PROPERTY: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#AnnotationProperty");
pub const FUNCTIONAL_PROPERTY: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#FunctionalProperty");
pub const ONTOLOGY_PROPERTY: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#OntologyProperty");
} }
pub mod rda { pub mod rda {
+4 -4
View File
@@ -4,8 +4,8 @@ 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};
use tantivy::schema::{Field, IndexRecordOption, Value}; use tantivy::schema::{Field, Value};
use tantivy::tokenizer::{NgramTokenizer, TokenizerManager}; use tantivy::tokenizer::{NgramTokenizer, TokenizerManager};
use tantivy::{Index, IndexReader, IndexWriter, ReloadPolicy, TantivyDocument, Term}; use tantivy::{Index, IndexReader, IndexWriter, ReloadPolicy, TantivyDocument, Term};
@@ -79,11 +79,11 @@ impl SearchIndex {
pub fn query( pub fn query(
&self, &self,
type_: u64, _type_: u64,
user_query: &str, user_query: &str,
default_fields: Vec<Field>, default_fields: Vec<Field>,
) -> error::Result<Vec<String>> { ) -> error::Result<Vec<String>> {
let doc_type_term = Term::from_field_u64(Schema::type_field(), type_); //let doc_type_term = Term::from_field_u64(Schema::type_field(), type_);
//let doc_type_query = Box::new(TermQuery::new(doc_type_term, IndexRecordOption::Basic)); //let doc_type_query = Box::new(TermQuery::new(doc_type_term, IndexRecordOption::Basic));
let parser = QueryParser::for_index(&self.index, default_fields); let parser = QueryParser::for_index(&self.index, default_fields);