.
This commit is contained in:
Generated
+1
@@ -1585,6 +1585,7 @@ name = "gl-graph"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxigraph",
|
"oxigraph",
|
||||||
|
"thiserror 2.0.19",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
oxigraph.workspace = true
|
oxigraph.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type Result<R> = std::result::Result<R, Error>;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
IriParse(#[from] oxigraph::model::IriParseError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Storage(#[from] oxigraph::store::StorageError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
SparqlSyntax(#[from] oxigraph::sparql::SparqlSyntaxError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
QueryEvaluation(#[from] oxigraph::sparql::QueryEvaluationError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError)
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
use oxigraph::model::{Dataset, GraphNameRef, NamedNodeRef, Quad, QuadRef};
|
||||||
|
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||||
|
use oxigraph::store::{Store, Transaction};
|
||||||
|
use tracing::{debug_span, field};
|
||||||
|
|
||||||
|
const RDF_SCHEMA_PREFIX: &str = "http://www.w3.org/2000/01/rdf-schema#";
|
||||||
|
const GL_PREFIX: &str = "https://graphofliberty.org/";
|
||||||
|
|
||||||
|
const INFERENCE_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/inference"));
|
||||||
|
const INPUT_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/input"));
|
||||||
|
|
||||||
|
pub struct InferenceEngine {
|
||||||
|
ontology: Store,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `prp-spo1`
|
||||||
|
fn sub_property_of(transaction: Transaction) -> crate::Result<Dataset> {
|
||||||
|
let span = debug_span!("rdfs:subPropertyOf", inferences = field::Empty).entered();
|
||||||
|
|
||||||
|
let mut query = SparqlEvaluator::new()
|
||||||
|
.with_prefix("rdfs", RDF_SCHEMA_PREFIX)?
|
||||||
|
.with_prefix("gl", GL_PREFIX)?
|
||||||
|
.parse_query(r#"CONSTRUCT {
|
||||||
|
?x ?p2 ?y .
|
||||||
|
} WHERE {
|
||||||
|
?p1 rdfs:subPropertyOf+ ?p2 .
|
||||||
|
GRAPH gl:input { ?x ?p1 ?y . }
|
||||||
|
}"#)?;
|
||||||
|
|
||||||
|
query.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
|
let inferences = if let QueryResults::Graph(result) = query.on_transaction(&transaction).execute()? {
|
||||||
|
result.filter_map(Result::ok)
|
||||||
|
.map(|triple| Quad::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Dataset::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
span.record("inferences", inferences.len());
|
||||||
|
Ok(inferences)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InferenceEngine {
|
||||||
|
pub fn new(store: Store) -> Self {
|
||||||
|
Self {
|
||||||
|
ontology: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&self, dataset: &Dataset) -> crate::Result<Dataset> {
|
||||||
|
let _span = debug_span!("Inference").entered();
|
||||||
|
|
||||||
|
let mut transaction = self.ontology.start_transaction()?;
|
||||||
|
let input = dataset.iter().map(|triple| QuadRef::new(triple.subject, triple.predicate, triple.object, INPUT_GRAPH));
|
||||||
|
transaction.extend(input);
|
||||||
|
sub_property_of(transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
mod curie;
|
mod curie;
|
||||||
|
//mod materialize;
|
||||||
|
mod error;
|
||||||
|
|
||||||
pub mod vocab;
|
pub mod vocab;
|
||||||
|
pub mod inference;
|
||||||
|
|
||||||
pub use curie::{PREFIXES, CurieHelper};
|
pub use curie::{PREFIXES, CurieHelper};
|
||||||
|
pub use error::{Error, Result};
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
use crate::error;
|
use crate::vocab;
|
||||||
use gl_graph::vocab::owl;
|
|
||||||
use oxigraph::model::{Dataset, GraphName, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term};
|
use oxigraph::model::{Dataset, GraphName, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term};
|
||||||
use oxigraph::sparql::SparqlEvaluator;
|
use oxigraph::sparql::SparqlEvaluator;
|
||||||
use oxigraph::store::Store;
|
use oxigraph::store::Store;
|
||||||
use tracing::{debug, debug_span};
|
use tracing::{debug, debug_span};
|
||||||
|
|
||||||
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#");
|
const RDF_SCHEMA: NamedNodeRef = NamedNodeRef::new_unchecked("http://www.w3.org/2000/01/rdf-schema#");
|
||||||
|
|
||||||
pub fn same_as(store: &mut Store) -> error::Result<()> {
|
pub fn same_as(store: &mut Store) -> crate::Result<()> {
|
||||||
let _span = debug_span!("Materialize owl:sameAs").entered();
|
let _span = debug_span!("Materialize owl:sameAs").entered();
|
||||||
|
|
||||||
let additional_quads = store
|
let additional_quads = store
|
||||||
.quads_for_pattern(None, Some(owl::SAME_AS), None, None)
|
.quads_for_pattern(None, Some(vocab::owl::SAME_AS), None, None)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.fold(Dataset::new(), |mut new_dataset, alias| {
|
.fold(Dataset::new(), |mut new_dataset, alias| {
|
||||||
if let NamedOrBlankNode::NamedNode(x) = alias.subject
|
if let NamedOrBlankNode::NamedNode(x) = alias.subject
|
||||||
@@ -56,7 +55,7 @@ pub fn same_as(store: &mut Store) -> error::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn super_properties(store: &mut Store) -> error::Result<()> {
|
pub fn super_properties(store: &mut Store) -> crate::Result<()> {
|
||||||
let _span = debug_span!("Materialize rdfs:subPropertyOf").entered();
|
let _span = debug_span!("Materialize rdfs:subPropertyOf").entered();
|
||||||
|
|
||||||
let update = SparqlEvaluator::new()
|
let update = SparqlEvaluator::new()
|
||||||
@@ -85,7 +84,7 @@ pub fn super_properties(store: &mut Store) -> error::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn super_classes(store: &mut Store) -> error::Result<()> {
|
pub fn super_classes(store: &mut Store) -> crate::Result<()> {
|
||||||
let _span = debug_span!("Materialize rdfs:subClassOf").entered();
|
let _span = debug_span!("Materialize rdfs:subClassOf").entered();
|
||||||
|
|
||||||
let update = SparqlEvaluator::new()
|
let update = SparqlEvaluator::new()
|
||||||
@@ -87,11 +87,6 @@ xsd:unsignedShort rdf:type rdfs:Datatype ;
|
|||||||
# Object Properties
|
# Object Properties
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/associatedProperty
|
|
||||||
:associatedProperty rdf:type owl:ObjectProperty ;
|
|
||||||
rdfs:label "associated property"@en .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/derivedWith
|
### https://graphofliberty.org/2026/04/ont/derivedWith
|
||||||
:derivedWith rdf:type owl:ObjectProperty ;
|
:derivedWith rdf:type owl:ObjectProperty ;
|
||||||
rdfs:range :DerivationMethodology ;
|
rdfs:range :DerivationMethodology ;
|
||||||
@@ -99,62 +94,10 @@ xsd:unsignedShort rdf:type rdfs:Datatype ;
|
|||||||
rdfs:label "derived with"@en .
|
rdfs:label "derived with"@en .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/indexDocument
|
|
||||||
:indexDocument rdf:type owl:ObjectProperty ;
|
|
||||||
rdfs:range :IndexDocument ;
|
|
||||||
rdfs:comment "Relates an entity to an Index Document."@en ;
|
|
||||||
rdfs:label "has index document"@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/subtitles
|
|
||||||
:subtitles rdf:type owl:ObjectProperty ;
|
|
||||||
rdfs:comment "Relates an entity to a File which contains subtitles for the entity."@en ;
|
|
||||||
rdfs:label "has subtitles"@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
|
# Data properties
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/categoryId
|
|
||||||
:categoryId rdf:type owl:DatatypeProperty ;
|
|
||||||
rdfs:domain :SearchableClass ;
|
|
||||||
rdfs:range xsd:nonNegativeInteger ;
|
|
||||||
rdfs:comment "An integer associated with the class for fast lookup in a database."@en ;
|
|
||||||
rdfs:label "category id" .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/fieldLabel
|
|
||||||
:fieldLabel rdf:type owl:DatatypeProperty ;
|
|
||||||
rdfs:domain :IndexDocumentField ;
|
|
||||||
rdfs:range rdf:dirLangString ,
|
|
||||||
rdf:langString ,
|
|
||||||
xsd:string ;
|
|
||||||
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:range xsd:string ;
|
|
||||||
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 ;
|
||||||
@@ -165,47 +108,12 @@ xsd:unsignedShort rdf:type rdfs:Datatype ;
|
|||||||
# Classes
|
# Classes
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/AudioBook
|
|
||||||
:AudioBook rdf:type owl:Class ;
|
|
||||||
rdfs:subClassOf :Entity .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/DerivationMethodology
|
### https://graphofliberty.org/2026/04/ont/DerivationMethodology
|
||||||
:DerivationMethodology rdf:type owl:Class ;
|
:DerivationMethodology rdf:type owl:Class ;
|
||||||
rdfs:comment "The methodology used to derive content. Individuals of this class should provide enough information to replicate the process by which the content was generated. For example, if Whisper was used derive captions for an audio book, the precise model used (e.g. large v3), a link to the source code (e.g. HuggingFace) and/or a paper should be provided."@en ;
|
rdfs:comment "The methodology used to derive content. Individuals of this class should provide enough information to replicate the process by which the content was generated. For example, if Whisper was used derive captions for an audio book, the precise model used (e.g. large v3), a link to the source code (e.g. HuggingFace) and/or a paper should be provided."@en ;
|
||||||
rdfs:label "Derivation Methodology"@en .
|
rdfs:label "Derivation Methodology"@en .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/Entity
|
|
||||||
:Entity rdf:type owl:Class ;
|
|
||||||
rdfs:comment "An Entity is a first-class citizen of the Graph of Liberty catalog. It is the class of all cultural artifacts which are to be preserved."@en ;
|
|
||||||
rdfs:label "Graph of Liberty Entity"@en .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/IndexDocument
|
|
||||||
:IndexDocument rdf:type owl:Class ;
|
|
||||||
rdfs:comment "A document which is intended to be passed verbatim to a full-text search engine for indexing."@en ;
|
|
||||||
rdfs:label "Index Document"@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 .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/SearchableClass
|
|
||||||
:SearchableClass rdf:type owl:Class ;
|
|
||||||
rdfs:comment "The class of classes which are to be indexed in the full-text search database."@en ;
|
|
||||||
rdfs:label "Searchable Class"@en .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/Transcription
|
|
||||||
:Transcription rdf:type owl:Class ;
|
|
||||||
rdfs:comment "A structured or unstructured textual file which contains a transcription of an audio file."@en ;
|
|
||||||
rdfs:label "Transcription"@en .
|
|
||||||
|
|
||||||
|
|
||||||
#################################################################
|
#################################################################
|
||||||
# Individuals
|
# Individuals
|
||||||
#################################################################
|
#################################################################
|
||||||
@@ -255,24 +163,6 @@ fedora:lastModifiedBy rdf:type owl:NamedIndividual ;
|
|||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
### http://rdaregistry.info/Elements/a/datatype/P50291
|
|
||||||
<http://rdaregistry.info/Elements/a/datatype/P50291> rdf:type owl:NamedIndividual ;
|
|
||||||
:indexedByField :Surname .
|
|
||||||
|
|
||||||
|
|
||||||
### http://rdaregistry.info/Elements/a/datatype/P50292
|
|
||||||
<http://rdaregistry.info/Elements/a/datatype/P50292> rdf:type owl:NamedIndividual ;
|
|
||||||
:indexedByField :GivenName .
|
|
||||||
|
|
||||||
|
|
||||||
### http://rdaregistry.info/Elements/c/C10004
|
|
||||||
rdac:C10004 rdf:type owl:NamedIndividual ,
|
|
||||||
:SearchableClass ;
|
|
||||||
:associatedProperty <http://rdaregistry.info/Elements/a/datatype/P50291> ,
|
|
||||||
<http://rdaregistry.info/Elements/a/datatype/P50292> ;
|
|
||||||
:categoryId "3"^^xsd:nonNegativeInteger .
|
|
||||||
|
|
||||||
|
|
||||||
### http://www.loc.gov/premis/rdf/v1#hasMessageDigest
|
### http://www.loc.gov/premis/rdf/v1#hasMessageDigest
|
||||||
premis:hasMessageDigest rdf:type owl:NamedIndividual ;
|
premis:hasMessageDigest rdf:type owl:NamedIndividual ;
|
||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
@@ -293,52 +183,6 @@ premis3:size rdf:type owl:NamedIndividual ;
|
|||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
|
|
||||||
rdf:Property rdf:type owl:NamedIndividual ,
|
|
||||||
:SearchableClass ;
|
|
||||||
:associatedProperty rdfs:comment ,
|
|
||||||
rdfs:label ,
|
|
||||||
skos:definition ;
|
|
||||||
:categoryId "0"^^xsd:nonNegativeInteger .
|
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/2000/01/rdf-schema#Class
|
|
||||||
rdfs:Class rdf:type owl:NamedIndividual ,
|
|
||||||
:SearchableClass ;
|
|
||||||
:associatedProperty rdfs:comment ,
|
|
||||||
rdfs:label ,
|
|
||||||
skos:definition ;
|
|
||||||
:categoryId "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#Concept
|
|
||||||
skos:Concept rdf:type owl:NamedIndividual ,
|
|
||||||
:SearchableClass ;
|
|
||||||
:associatedProperty skos:definition ,
|
|
||||||
skos:prefLabel ;
|
|
||||||
:categoryId "2"^^xsd:nonNegativeInteger .
|
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/2004/02/skos/core#definition
|
|
||||||
skos:definition rdf:type owl:NamedIndividual ;
|
|
||||||
:indexedByField :Definition .
|
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/2004/02/skos/core#prefLabel
|
|
||||||
skos:prefLabel 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 .
|
||||||
@@ -369,49 +213,4 @@ ldp:contains rdf:type owl:NamedIndividual ;
|
|||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/AudioBook
|
|
||||||
:AudioBook rdf:type owl:NamedIndividual ,
|
|
||||||
:SearchableClass ;
|
|
||||||
:categoryId "4"^^xsd:nonNegativeInteger .
|
|
||||||
|
|
||||||
|
|
||||||
### 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/GivenName
|
|
||||||
:GivenName rdf:type owl:NamedIndividual ,
|
|
||||||
:IndexDocumentField ;
|
|
||||||
:fieldLabel "Given Name"@en ;
|
|
||||||
:fieldName "given_name" ;
|
|
||||||
rdfs:label "Given Name 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 .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/Surname
|
|
||||||
:Surname rdf:type owl:NamedIndividual ,
|
|
||||||
:IndexDocumentField ;
|
|
||||||
:fieldLabel "Surname"@en ;
|
|
||||||
:fieldName "surname" ;
|
|
||||||
rdfs:label "Surname Field"@en .
|
|
||||||
|
|
||||||
|
|
||||||
#################################################################
|
|
||||||
# Annotations
|
|
||||||
#################################################################
|
|
||||||
|
|
||||||
:AudioBook rdfs:label "Audio Book"@en .
|
|
||||||
|
|
||||||
|
|
||||||
### Generated by the OWL API (version 4.5.29.2024-05-13T12:11:03Z) https://github.com/owlcs/owlapi
|
### Generated by the OWL API (version 4.5.29.2024-05-13T12:11:03Z) https://github.com/owlcs/owlapi
|
||||||
|
|||||||
+2
-2
@@ -733,10 +733,10 @@ impl Publisher {
|
|||||||
text_input("Query", &search_state.query).on_input(Message::QueryUpdated)
|
text_input("Query", &search_state.query).on_input(Message::QueryUpdated)
|
||||||
.id("query");
|
.id("query");
|
||||||
|
|
||||||
let entities = self.ontology
|
let entities = Vec::new(); /*self.ontology
|
||||||
.searchable_classes(&*language::ENGLISH_OR_UNTAGGED)
|
.searchable_classes(&*language::ENGLISH_OR_UNTAGGED)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();*/
|
||||||
|
|
||||||
let type_selector = pick_list(search_state.entity_selection.as_ref(), entities, ToString::to_string)
|
let type_selector = pick_list(search_state.entity_selection.as_ref(), entities, ToString::to_string)
|
||||||
.on_select(|selection| Message::QueryTypeUpdated(selection));
|
.on_select(|selection| Message::QueryTypeUpdated(selection));
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub(crate) struct QueryArgs {
|
||||||
|
#[arg(short, long, value_name = "DATASET PATH")]
|
||||||
|
pub(crate) dataset_path: PathBuf,
|
||||||
|
|
||||||
|
#[arg(short, long, value_name = "QUERY PATH")]
|
||||||
|
pub(crate) query_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub(crate) struct SearchArgs {
|
pub(crate) struct SearchArgs {
|
||||||
#[arg(short, long, value_name = "DOC TYPE")]
|
#[arg(short, long, value_name = "DOC TYPE")]
|
||||||
@@ -11,6 +21,7 @@ pub(crate) struct SearchArgs {
|
|||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub(crate) enum Command {
|
pub(crate) enum Command {
|
||||||
|
Query(QueryArgs),
|
||||||
Search(SearchArgs),
|
Search(SearchArgs),
|
||||||
Reindex,
|
Reindex,
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-13
@@ -7,18 +7,25 @@ mod navigator;
|
|||||||
mod theme;
|
mod theme;
|
||||||
mod args;
|
mod args;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use iced::futures::StreamExt;
|
use iced::futures::StreamExt;
|
||||||
use ldp::middleware::BasicAuthMiddleware;
|
use ldp::middleware::BasicAuthMiddleware;
|
||||||
use ldp::reqwest::{Client, Url};
|
use ldp::reqwest::{Client, Url};
|
||||||
use ldp::reqwest_middleware::ClientBuilder;
|
use ldp::reqwest_middleware::ClientBuilder;
|
||||||
use ldp::traverse::Traverse;
|
use ldp::traverse::Traverse;
|
||||||
|
use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
|
||||||
|
use oxigraph::model::{Dataset, Graph, GraphName, GraphNameRef, NamedNode, Quad, Triple};
|
||||||
|
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||||
|
use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer};
|
||||||
|
use oxigraph::store::Store;
|
||||||
use tracing::{debug, debug_span, error, field};
|
use tracing::{debug, debug_span, error, field};
|
||||||
use crate::app::Publisher;
|
use crate::app::Publisher;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use tracing_subscriber::{EnvFilter, fmt};
|
use tracing_subscriber::{EnvFilter, fmt};
|
||||||
use tracing_subscriber::fmt::format::FmtSpan;
|
use tracing_subscriber::fmt::format::FmtSpan;
|
||||||
|
use gl_graph::inference::InferenceEngine;
|
||||||
use gl_search::{Document, Schema, SearchIndex};
|
use gl_search::{Document, Schema, SearchIndex};
|
||||||
use crate::args::{AppArgs, Command};
|
use crate::args::{AppArgs, Command};
|
||||||
use crate::rdf::ontology::Ontology;
|
use crate::rdf::ontology::Ontology;
|
||||||
@@ -31,10 +38,8 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
.init();
|
.init();
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
|
|
||||||
let ontology = Ontology::builder()
|
let store = Store::open("/home/alex/.local/share/org.graphofliberty.desktop/ontology")?;
|
||||||
.with_path("/home/alex/.local/share/org.graphofliberty.desktop/ontology")
|
let inference_engine = InferenceEngine::new(store.clone());
|
||||||
.build()
|
|
||||||
.expect("Failed to build ontology");
|
|
||||||
|
|
||||||
let mut index = SearchIndex::builder()
|
let mut index = SearchIndex::builder()
|
||||||
.with_path("/home/alex/.local/share/org.graphofliberty.desktop/index")
|
.with_path("/home/alex/.local/share/org.graphofliberty.desktop/index")
|
||||||
@@ -43,10 +48,48 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
|
|
||||||
let args = AppArgs::parse();
|
let args = AppArgs::parse();
|
||||||
match args.command {
|
match args.command {
|
||||||
Some(Command::Search(args)) => {
|
Some(Command::Query(args)) => {
|
||||||
for doc in index.query(args.discriminant, &args.query, Schema::all_fields(), 500000)? {
|
let graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!("file://{}", args.dataset_path.to_string_lossy())));
|
||||||
println!("{}", doc.to_json(Schema::schema()));
|
let mut dataset = RdfParser::from_format(RdfFormat::Turtle)
|
||||||
|
.with_default_graph(graph_name)
|
||||||
|
.for_reader(File::open(&args.dataset_path)?)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect::<Dataset>();
|
||||||
|
let inferences = inference_engine.run(&dataset)?;
|
||||||
|
dataset.extend(&inferences);
|
||||||
|
|
||||||
|
let raw_query = String::from_utf8(std::fs::read(&args.query_path)?)?;
|
||||||
|
let mut query = SparqlEvaluator::new()
|
||||||
|
.parse_query(&raw_query)?;
|
||||||
|
query.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
|
match query.on_queryable_dataset(&dataset).execute()? {
|
||||||
|
QueryResults::Graph(graph) => {
|
||||||
|
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
||||||
|
.for_writer(std::io::stdout());
|
||||||
|
for triple in graph.filter_map(Result::ok) {
|
||||||
|
serializer.serialize_triple(triple.as_ref())?;
|
||||||
}
|
}
|
||||||
|
serializer.finish()?;
|
||||||
|
}
|
||||||
|
QueryResults::Solutions(solutions) => {
|
||||||
|
let json_serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Json);
|
||||||
|
let mut writer = json_serializer.serialize_solutions_to_writer(std::io::stdout(), Vec::from_iter(solutions.variables().iter().cloned()))?;
|
||||||
|
for solution in solutions.filter_map(Result::ok) {
|
||||||
|
writer.serialize(&solution)?;
|
||||||
|
}
|
||||||
|
writer.finish()?;
|
||||||
|
}
|
||||||
|
QueryResults::Boolean(result) => {
|
||||||
|
let json_serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Json);
|
||||||
|
json_serializer.serialize_boolean_to_writer(std::io::stdout(), result)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Command::Search(args)) => {
|
||||||
|
/*for doc in index.query(args.discriminant, &args.query, Schema::all_fields(), 500000)? {
|
||||||
|
println!("{}", doc.to_json(Schema::schema()));
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
Some(Command::Reindex) => {
|
Some(Command::Reindex) => {
|
||||||
let mut writer = index.writer()?;
|
let mut writer = index.writer()?;
|
||||||
@@ -57,7 +100,6 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let span = debug_span!("Index Schema", documents = field::Empty).entered();
|
let span = debug_span!("Index Schema", documents = field::Empty).entered();
|
||||||
let store = ontology.store();
|
|
||||||
let count = gl_search::rdf::index_schema(store, &*gl_search::language::ENGLISH_OR_UNTAGGED, &writer)?;
|
let count = gl_search::rdf::index_schema(store, &*gl_search::language::ENGLISH_OR_UNTAGGED, &writer)?;
|
||||||
span.record("documents", count);
|
span.record("documents", count);
|
||||||
}
|
}
|
||||||
@@ -76,17 +118,25 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let mut documents = 0usize;
|
let mut rdf_source_count = 0usize;
|
||||||
|
let mut dataset = Dataset::new();
|
||||||
let mut traversal = Traverse::new(http_client, starting_url, None);
|
let mut traversal = Traverse::new(http_client, starting_url, None);
|
||||||
while let Some(result) = traversal.next().await {
|
while let Some(result) = traversal.next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(rdf_source) => {
|
Ok(rdf_source) => dataset.extend(rdf_source.dataset()),
|
||||||
documents += gl_search::rdf::index_entity(rdf_source.dataset(), &*gl_search::language::ENGLISH_OR_UNTAGGED, &writer)?;
|
|
||||||
}
|
|
||||||
Err(err) => error!(?err),
|
Err(err) => error!(?err),
|
||||||
}
|
}
|
||||||
|
rdf_source_count += 1;
|
||||||
}
|
}
|
||||||
debug!(documents, "Repository Traversal");
|
|
||||||
|
let inferences = inference_engine.run(&dataset).unwrap();
|
||||||
|
dataset.extend(&inferences);
|
||||||
|
|
||||||
|
let span = debug_span!("Index Repository", rdf_sources = field::Empty, documents = field::Empty).entered();
|
||||||
|
let document_count = gl_search::rdf::index_entity(&dataset, &*gl_search::language::ENGLISH_OR_UNTAGGED, &writer)?;
|
||||||
|
span.record("rdf_sources", rdf_source_count);
|
||||||
|
span.record("documents", document_count);
|
||||||
|
|
||||||
Ok::<_, gl_search::SearchError>(writer)
|
Ok::<_, gl_search::SearchError>(writer)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
pub(crate) mod ontology;
|
pub(crate) mod ontology;
|
||||||
pub(crate) mod term_helper;
|
pub(crate) mod term_helper;
|
||||||
pub(crate) mod materialize;
|
|
||||||
pub(crate) mod conversion;
|
pub(crate) mod conversion;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::rdf::{conversion, materialize};
|
use crate::rdf::conversion;
|
||||||
use gl_graph::vocab::gl;
|
use gl_graph::vocab::gl;
|
||||||
use gl_search::language::LanguageCondition;
|
use gl_search::language::LanguageCondition;
|
||||||
use iced::futures::TryFutureExt;
|
use iced::futures::TryFutureExt;
|
||||||
@@ -91,13 +91,6 @@ impl OntologyBuilder {
|
|||||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
.collect::<HashMap<String, String>>();
|
.collect::<HashMap<String, String>>();
|
||||||
|
|
||||||
if self.materialize_inferences {
|
|
||||||
materialize::same_as(&mut store)?;
|
|
||||||
materialize::super_properties(&mut store)?;
|
|
||||||
materialize::super_classes(&mut store)?;
|
|
||||||
debug_span!("Optimize Ontology").in_scope(|| store.optimize())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut indexed_by = HashMap::new();
|
let mut indexed_by = HashMap::new();
|
||||||
for quad in store.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None)
|
for quad in store.quads_for_pattern(None, Some(gl::INDEXED_BY_FIELD), None, None)
|
||||||
.filter_map(Result::ok) {
|
.filter_map(Result::ok) {
|
||||||
@@ -297,25 +290,6 @@ WHERE {{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn searchable_classes(&self, language: &LanguageCondition) -> BTreeSet<LabeledIri> {
|
|
||||||
self.store.quads_for_pattern(None, Some(rdf::TYPE), Some(gl::SEARCHABLE_CLASS.into()), None)
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.filter_map(|quad| {
|
|
||||||
let label = self.store.quads_for_pattern(Some(quad.subject.as_ref().into()), Some(rdfs::LABEL), None, None)
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.map(conversion::quad_into_term)
|
|
||||||
.filter(|term| language.primary_matches_term(term))
|
|
||||||
.filter_map(conversion::term_into_string)
|
|
||||||
.next();
|
|
||||||
if let Some(label) = label && let NamedOrBlankNode::NamedNode(subject) = quad.subject {
|
|
||||||
Some(LabeledIri {
|
|
||||||
iri: subject,
|
|
||||||
label,
|
|
||||||
})
|
|
||||||
} else { None }
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subclasses_of(&self, class: &NamedNode) -> BTreeSet<NamedNode> {
|
pub fn subclasses_of(&self, class: &NamedNode) -> BTreeSet<NamedNode> {
|
||||||
self.store.quads_for_pattern(None, Some(rdfs::SUB_CLASS_OF), Some(class.into()), None)
|
self.store.quads_for_pattern(None, Some(rdfs::SUB_CLASS_OF), Some(class.into()), None)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ impl DocType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_named_node<'a>(node: impl Into<NamedNodeRef<'a>>) -> Option<Self> {
|
pub fn try_from_named_node<'a>(node: impl Into<NamedNodeRef<'a>>) -> Option<Self> {
|
||||||
let node = node.into();
|
let node = node.into();
|
||||||
match node {
|
match node {
|
||||||
vocab::rdf::PROPERTY => Some(DocType::RdfProperty),
|
vocab::rdf::PROPERTY => Some(DocType::RdfProperty),
|
||||||
|
|||||||
+2
-3
@@ -5,7 +5,6 @@ use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
|||||||
use oxigraph::store::Store;
|
use oxigraph::store::Store;
|
||||||
use tantivy::IndexWriter;
|
use tantivy::IndexWriter;
|
||||||
use tantivy::schema::{Field, OwnedValue};
|
use tantivy::schema::{Field, OwnedValue};
|
||||||
use tracing::debug;
|
|
||||||
use gl_graph::{vocab, CurieHelper};
|
use gl_graph::{vocab, CurieHelper};
|
||||||
use crate::DocType;
|
use crate::DocType;
|
||||||
use crate::language::LanguageCondition;
|
use crate::language::LanguageCondition;
|
||||||
@@ -68,7 +67,7 @@ pub fn index_entity(dataset: &Dataset, language: &LanguageCondition, writer: &In
|
|||||||
let mut counter = 0usize;
|
let mut counter = 0usize;
|
||||||
for (subject, types) in subject_type_map {
|
for (subject, types) in subject_type_map {
|
||||||
for type_ in types {
|
for type_ in types {
|
||||||
if let Some(doc_type) = term_ref_as_named_node(type_).and_then(DocType::from_named_node) {
|
if let Some(doc_type) = term_ref_as_named_node(type_).and_then(DocType::try_from_named_node) {
|
||||||
let mut doc = match doc_type {
|
let mut doc = match doc_type {
|
||||||
DocType::Person => index_person(dataset, subject, language),
|
DocType::Person => index_person(dataset, subject, language),
|
||||||
DocType::CorporateBody => index_corporate_body(dataset, subject, language),
|
DocType::CorporateBody => index_corporate_body(dataset, subject, language),
|
||||||
@@ -170,7 +169,7 @@ pub fn index_schema(store: Store, language: &LanguageCondition, writer: &IndexWr
|
|||||||
|
|
||||||
let discriminant = solution.get("class")
|
let discriminant = solution.get("class")
|
||||||
.and_then(term_to_named_node)
|
.and_then(term_to_named_node)
|
||||||
.and_then(DocType::from_named_node)
|
.and_then(DocType::try_from_named_node)
|
||||||
.map(|doc_type| doc_type as u64)
|
.map(|doc_type| doc_type as u64)
|
||||||
.map(OwnedValue::U64)
|
.map(OwnedValue::U64)
|
||||||
.unwrap_or(OwnedValue::Null);
|
.unwrap_or(OwnedValue::Null);
|
||||||
|
|||||||
Reference in New Issue
Block a user