SHA256
1
0

Initial commit

This commit is contained in:
Alex Wied
2026-06-08 19:33:49 -04:00
commit 6906fb973a
24 changed files with 9382 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
/target
.cargo
.idea
Generated
+6053
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
[workspace]
resolver = "2"
members = [
"publish",
"search",
]
[workspace.dependencies]
gl-search = { path = "search" }
ldp = { path = "../../ldp/ldp", features = ["keyed"] }
async-trait = "0.1"
base64 = "0.22"
bytes = "1.11"
color-eyre = "0.6"
futures = "0.3"
http = "1.4"
iced = { git = "https://github.com/iced-rs/iced.git", branch = "master", features = ["tokio"] }
oxigraph = { version = "0.5", features = ["rdf-12"] }
oxilangtag = "0.1"
parse_link_header = "0.4"
rfd = "0.17"
slotmap = "1.1"
tantivy = "0.26"
tar = "0.4"
thiserror = "2.0"
tokio = { version = "1.52", features = ["rt", "rt-multi-thread", "macros", "fs"] }
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "gl-publisher"
version = "0.1.0"
edition = "2024"
[dependencies]
#gl-ldp.workspace = true
gl-search.workspace = true
ldp.workspace = true
color-eyre.workspace = true
csv = "1.4"
http.workspace = true
iced.workspace = true
rfd.workspace = true
oxigraph.workspace = true
tar.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-appender.workspace = true
tracing-subscriber.workspace = true
+759
View File
@@ -0,0 +1,759 @@
use crate::rdf::ontology;
use crate::rdf::ontology::Ontology;
use crate::rdf::term_helper::{TermHelper, TermHelperMut};
use gl_search::{Schema, SearchDocument, SearchIndex};
use http::StatusCode;
use iced::alignment::Horizontal;
use iced::widget::button::Style;
use iced::widget::grid::Sizing;
use iced::widget::{
button, center, column, combo_box, container, grid, mouse_area, opaque, row, scrollable, space,
stack, table, text, text_input, toggler,
};
use iced::window::Settings;
use iced::{Background, Color, Element, Length, Subscription, Task, color, window};
use ldp::middleware::BasicAuthMiddleware;
use ldp::model::{KeyedDataset, QuadKey};
use ldp::reqwest::{Client, Url};
use ldp::reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use ldp::traverse::Traverse;
use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions};
use oxigraph::io::RdfFormat;
use oxigraph::model::vocab::rdf;
use oxigraph::model::{BaseDirection, Dataset, NamedNode, Quad, Term, TermRef};
use tracing::{debug, error};
#[derive(Debug, Clone)]
pub(crate) enum Message {
None,
Traverse,
IndexRdfSource(RdfSource<Dataset>),
CommitIndex,
WindowClosed(window::Id),
URLInputChanged(String),
URLInputSubmitted,
FetchDocument(Url),
LoadDocument(RdfSource<KeyedDataset<RowState>>),
ShowNewDocumentButtons,
HideNewDocumentButtons,
ShowError(String),
AddRow(Option<QuadKey>),
DeleteRow(QuadKey),
DeleteAllRows,
OpenQueryWindow(SearchResultClickAction),
HoverRow(QuadKey),
UnhoverRow(QuadKey),
QueryUpdated(String),
SearchResultClicked(NamedNode),
DatatypeUpdated(QuadKey, Option<String>),
LanguageUpdated(QuadKey, Option<String>),
ValueUpdated(QuadKey, String),
DirectionToggled(QuadKey, BaseDirection),
SaveGraph(bool),
ShowOverwriteConfirmationModal,
HideOverwriteConfirmationModal,
ConfirmOverwrite,
NewDocument(NamedNode),
ResetState,
}
#[derive(Default, Debug, Clone)]
pub(crate) struct RowState {
read_only: bool,
datatype_state: combo_box::State<String>,
}
#[derive(Debug, Clone)]
pub(crate) enum SearchResultClickAction {
URLInput,
Predicate(QuadKey),
Object(QuadKey),
}
struct SearchState {
window_id: window::Id,
action: SearchResultClickAction,
query: String,
results: Vec<NamedNode>,
}
pub(crate) struct Publisher {
http_client: ClientWithMiddleware,
ontology: Ontology,
abbreviated_datatypes: Vec<String>,
window_id: window::Id,
url_input: String,
document: RdfSource<KeyedDataset<RowState>>,
hovered_row: Option<QuadKey>,
search_state: Option<SearchState>,
index: SearchIndex,
show_overwrite_confirmation: bool,
modified: bool,
show_new_document_buttons: bool,
}
impl Publisher {
pub(crate) fn new() -> (Self, Task<Message>) {
let ontology = Ontology::builder()
.with_default_ontologies()
.build()
.expect("Failed to build ontology");
let mut index = SearchIndex::builder()
.with_path("/home/alex/Documents/Index")
.build()
.expect("Failed to build search index");
index.remove_all_annotated_iris();
ontology.for_each_annotated_iri(|iri, label, comment| {
index
.add_annotated_iri(iri, label, comment)
.expect("Unable to add annotated IRI to search index");
});
index.commit().expect("Unable to commit ontology to index");
let mut abbreviated_datatypes = ontology
.datatypes()
.map(|node| ontology.abbreviate(node))
.collect::<Vec<_>>();
abbreviated_datatypes.sort();
let (id, task) = window::open(Settings::default());
let client = Client::new();
let http_client = ClientBuilder::new(client.clone())
.with(BasicAuthMiddleware::new(
"fedoraAdmin".to_string(),
Some("fedoraAdmin".to_string()),
))
.build();
let url_input = "http://fedora.quill.lan/rest/".to_string();
let document = RdfSource::new(Url::parse(&url_input).unwrap());
(
Self {
http_client,
ontology,
abbreviated_datatypes,
window_id: id,
url_input,
document,
hovered_row: None,
search_state: None,
index,
show_overwrite_confirmation: false,
modified: false,
show_new_document_buttons: false,
},
task.map(|_| Message::None),
)
}
pub(crate) fn update(&mut self, message: Message) -> Task<Message> {
let mut task = Task::none();
debug!(?message);
match message {
Message::Traverse => {
let client = self.http_client.clone();
let root = Url::parse(&self.url_input).expect("Invalid URL");
let stream = Traverse::new(client, root, None);
task = Task::run(stream, |result| match result {
Ok(rdf_source) => Message::IndexRdfSource(rdf_source),
Err(err) => Message::ShowError(format!("Unable to fetch RDF Source: {err}")),
})
.chain(Task::done(Message::CommitIndex));
}
Message::IndexRdfSource(rdf_source) => {
let catalog_id = rdf_source
.classes()
.filter_map(|class| self.ontology.catalog_id(&class.into_owned()))
.next();
if let Some(catalog_id) = catalog_id {
let mut document = SearchDocument::new();
document.add_u64(Schema::type_field(), catalog_id);
document.add_text(Schema::iri_field(), rdf_source.origin());
for quad in rdf_source.dataset() {
if let Some(field_name) =
self.ontology.field_name(&quad.predicate.into_owned())
&& let TermRef::Literal(literal) = quad.object
{
let field = Schema::schema()
.get_field(&field_name)
.expect("Field not found in schema");
document.add_text(field, literal.value().to_lowercase());
}
}
self.index
.add(document)
.expect("Unable to add document to search index");
}
}
Message::CommitIndex => {
if let Err(err) = self.index.commit() {
task = Task::done(Message::ShowError(format!("Unable to commit index: {err}")));
}
}
Message::WindowClosed(id) => {
if self.window_id == id {
task = iced::exit();
} else {
self.search_state = None;
}
}
Message::URLInputChanged(value) => {
self.url_input = value;
}
Message::URLInputSubmitted => {
let url = Url::parse(&self.url_input).expect("Invalid URL");
task = Task::done(Message::DeleteAllRows)
.chain(Task::done(Message::ResetState))
.chain(Task::done(Message::FetchDocument(url)));
}
Message::FetchDocument(url) => {
let client = self.http_client.clone();
task = Task::future(async {
let request = ResourceRequestBuilder::with_client_and_url(client, url)
.accept_rdf_format(RdfFormat::Turtle)
.follow_described_by(true)
.build();
match request.send().await {
Ok(resource) => match resource.into_rdf_source().await {
Ok(rdf_source) => Message::LoadDocument(rdf_source),
Err(err) => {
Message::ShowError(format!("Unable to parse response: {err}"))
}
},
Err(ldp::Error::Reqwest(err)) => {
if err.status() == Some(StatusCode::NOT_FOUND) {
Message::ShowNewDocumentButtons
} else {
Message::ShowError(err.to_string())
}
}
Err(err) => Message::ShowError(format!("Request failed: {err}")),
}
});
}
Message::LoadDocument(document) => {
let messages = document
.dataset()
.quads
.keys()
.map(|key| Message::AddRow(Some(key)));
task = Task::batch(messages.map(Task::done)).chain(Task::done(Message::ResetState));
self.document = document;
}
Message::ShowError(error) => {
error!(error);
}
Message::AddRow(None) => {
let quad = self.document.new_quad();
let key = self.document.dataset_mut().quads.insert(quad);
task = Task::done(Message::AddRow(Some(key)));
}
Message::AddRow(Some(key)) => {
let quad = self
.document
.dataset()
.quads
.get(key)
.expect("Failed to get quad from document");
let read_only = self.ontology.is_read_only(quad.as_ref());
let term = TermHelper::new(&quad.object);
let datatype = term
.datatype()
.map(|datatype| self.ontology.abbreviate(datatype));
let datatype_state = combo_box::State::with_selection(
self.abbreviated_datatypes.clone(),
datatype.as_ref(),
);
let state = RowState {
read_only,
datatype_state,
};
self.document
.dataset_mut()
.associated_data
.insert(key, state);
self.modified = true;
}
Message::DeleteRow(key) => {
self.document.dataset_mut().remove(key);
self.modified = true;
}
Message::DeleteAllRows => {
self.document.dataset_mut().clear();
self.modified = true;
}
Message::OpenQueryWindow(action) => {
if let Some(search_state) = &mut self.search_state {
search_state.action = action;
} else {
let (id, window_task) = window::open(Settings::default());
self.search_state = Some(SearchState {
window_id: id,
action,
query: String::new(),
results: Vec::new(),
});
task = window_task.map(|_| Message::None)
}
}
Message::QueryUpdated(new_query) => {
if let Some(search_state) = &mut self.search_state {
search_state.results = self
.index
.query(7, new_query.as_str(), Schema::default_fields())
.expect("Error encountered while querying index")
.iter()
.map(NamedNode::new_unchecked)
.collect();
search_state.query = new_query;
};
}
Message::SearchResultClicked(node) => {
if let Some(search_state) = &self.search_state {
match search_state.action {
SearchResultClickAction::URLInput => {
task = Task::done(Message::URLInputChanged(node.as_str().to_string()))
.chain(Task::done(Message::URLInputSubmitted));
}
SearchResultClickAction::Predicate(key) => {
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
quad.predicate = node;
}
}
SearchResultClickAction::Object(key) => {
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
quad.object = Term::NamedNode(node);
}
}
}
task = task.chain(window::close(search_state.window_id));
self.modified = true;
}
}
Message::HoverRow(index) => {
self.hovered_row = Some(index);
}
Message::UnhoverRow(index) if self.hovered_row == Some(index) => {
self.hovered_row = None;
}
Message::DatatypeUpdated(key, Some(maybe_prefixed_iri)) => {
let node = self
.ontology
.expand(&maybe_prefixed_iri)
.unwrap_or(NamedNode::new_unchecked(&maybe_prefixed_iri));
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
let mut term = TermHelperMut::new(&mut quad.object);
term.set_datatype(Some(node));
self.modified = true;
}
}
Message::DatatypeUpdated(key, None) => {
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
let mut term = TermHelperMut::new(&mut quad.object);
term.set_datatype(None);
self.modified = true;
}
}
Message::LanguageUpdated(key, Some(language)) => {
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
let mut term = TermHelperMut::new(&mut quad.object);
term.set_language(language.as_str());
self.modified = true;
}
}
Message::LanguageUpdated(key, None) => {
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
let mut term = TermHelperMut::new(&mut quad.object);
term.set_language("en");
self.modified = true;
}
}
Message::ValueUpdated(key, value) => {
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
let mut term = TermHelperMut::new(&mut quad.object);
term.set_value(value);
self.modified = true;
}
}
Message::DirectionToggled(key, direction) => {
if let Some(quad) = self.document.dataset_mut().quads.get_mut(key) {
let mut term = TermHelperMut::new(&mut quad.object);
term.set_direction(direction);
self.modified = true;
}
}
Message::SaveGraph(overwrite) => {
let client = self.http_client.clone();
let options = SerializationOptions::from_format(RdfFormat::Turtle)
.with_filter(self.ontology.exclude_read_only_predicate());
let request = self
.document
.to_update(options)
.expect("Failed to generate PUT request");
let url = self.document.origin().clone();
task = Task::future(async move {
match request.send(client, overwrite).await {
Ok(RdfSourceUpdateResponse::Success) => Message::FetchDocument(url),
Ok(RdfSourceUpdateResponse::DocumentModified(_)) => {
Message::ShowOverwriteConfirmationModal
}
Err(err) => Message::ShowError(format!("Failed to save graph: {err}")),
}
});
}
Message::ShowOverwriteConfirmationModal => {
self.show_overwrite_confirmation = true;
}
Message::HideOverwriteConfirmationModal => {
self.show_overwrite_confirmation = false;
}
Message::ConfirmOverwrite => {
task = Task::done(Message::SaveGraph(true))
.chain(Task::done(Message::HideOverwriteConfirmationModal));
}
Message::ShowNewDocumentButtons => {
self.show_new_document_buttons = true;
}
Message::HideNewDocumentButtons => {
self.show_new_document_buttons = false;
}
Message::NewDocument(class) => {
let url = Url::parse(self.url_input.as_str()).expect("Invalid URL");
let subject = NamedNode::new_unchecked(url.as_str());
self.document = RdfSource::new(url);
let quads = self
.ontology
.template_triples(class.as_ref(), subject.as_ref())
.map(|triples| {
triples
.map(|triple| self.document.quad_from_triple(triple))
.collect::<Vec<_>>()
});
if let Some(quads) = quads {
let new_keys = self.document.dataset_mut().extend(quads.into_iter());
let messages = new_keys.map(|triple| Message::AddRow(Some(triple)));
task = Task::done(Message::HideNewDocumentButtons)
.chain(Task::batch(messages.map(Task::done)));
}
}
Message::ResetState => {
self.modified = false;
self.show_new_document_buttons = false;
self.show_overwrite_confirmation = false;
}
_ => {}
}
task
}
pub(crate) fn title(&self, _window: window::Id) -> String {
"Graph of Liberty Publisher".to_string()
}
pub(crate) fn subscription(&self) -> Subscription<Message> {
window::close_events().map(Message::WindowClosed)
}
fn view_row<'a>(
&'a self,
key: QuadKey,
triple: &'a Quad,
state: &'a RowState,
) -> Element<'a, Message> {
const BUTTON_WIDTH: Length = Length::Fixed(35.0);
let delete_button = button("\u{274c}")
.style(|_, _| Style::default().with_background(Background::Color(color!(255, 0, 0))))
.on_press(Message::DeleteRow(key));
let button_area = if self.hovered_row == Some(key) && !state.read_only {
container(delete_button).width(BUTTON_WIDTH)
} else {
container(space()).width(BUTTON_WIDTH)
};
let property_label = self
.ontology
.label(triple.predicate.as_ref())
.unwrap_or(self.ontology.abbreviate(triple.predicate.as_ref()).as_str())
.to_string();
let property: Element<Message> = if state.read_only {
text(property_label).into()
} else {
button(text(property_label))
.on_press(Message::OpenQueryWindow(
SearchResultClickAction::Predicate(key),
))
.style(button::text)
.into()
};
let term = TermHelper::new(&triple.object);
let value_label = term.value_as_named_node().and_then(|node| {
self.ontology
.label(node)
.map(|label| container(text(label)))
});
let value = term
.value_as_named_node()
.map(|node| self.ontology.abbreviate(node))
.unwrap_or(term.value().to_string());
let value_alignment = term
.direction()
.map(|direction| match direction {
BaseDirection::Ltr => Horizontal::Left,
BaseDirection::Rtl => Horizontal::Right,
})
.unwrap_or(Horizontal::Left);
let value_input_base = text_input("Value", value.as_str()).align_x(value_alignment);
let value_input = if !state.read_only {
let is_named_node = term.is_named_node();
value_input_base.on_input(move |input| {
let expanded_input = if is_named_node {
self.ontology
.expand(input.as_str())
.map(|node| node.as_str().to_string())
.unwrap_or(input)
} else {
input
};
Message::ValueUpdated(key, expanded_input)
})
} else {
value_input_base
};
let search_launcher = if term.is_named_node() && !state.read_only {
Some(container(
button(text("\u{1f50e}"))
.on_press(Message::OpenQueryWindow(SearchResultClickAction::Object(
key,
)))
.style(button::text),
))
} else {
None
};
let selected_datatype = term.datatype().map(|node| self.ontology.abbreviate(node));
let datatype_selector: Element<Message> = if state.read_only {
selected_datatype.map(text).into()
} else {
combo_box(
&state.datatype_state,
"Datatype",
selected_datatype.as_ref(),
move |selection| Message::DatatypeUpdated(key, Some(selection)),
)
.on_input(move |input| {
let value = if input.is_empty() { None } else { Some(input) };
Message::DatatypeUpdated(key, value)
})
.into()
};
let language_input = match term.datatype() {
Some(rdf::LANG_STRING) | Some(rdf::DIR_LANG_STRING) => Some(container(
text_input("Language", term.language().unwrap_or("en")).on_input(move |input| {
let value = if input.is_empty() { None } else { Some(input) };
Message::LanguageUpdated(key, value)
}),
)),
_ => None,
};
let direction_slider = term
.direction()
.map(|direction| match direction {
BaseDirection::Ltr => toggler(false),
BaseDirection::Rtl => toggler(true),
})
.map(|toggler| {
container(row![
text("LTR"),
toggler.on_toggle(move |new_state| {
let direction = if new_state {
BaseDirection::Rtl
} else {
BaseDirection::Ltr
};
Message::DirectionToggled(key, direction)
}),
text("RTL"),
])
});
let row = row![
button_area,
property,
value_label,
value_input,
search_launcher,
datatype_selector,
language_input,
direction_slider,
];
mouse_area(row)
.on_enter(Message::HoverRow(key))
.on_exit(Message::UnhoverRow(key))
.into()
}
fn view_new_entity_buttons(
&self,
entities: impl Iterator<Item = NamedNode>,
) -> Element<'_, Message> {
let buttons = entities.map(|entity| {
let label = self
.ontology
.label(entity.as_ref())
.map(|label| label.to_string())
.unwrap_or(entity.as_str().to_string());
button(text(label))
.on_press(Message::NewDocument(entity))
.into()
});
grid(buttons)
.height(Sizing::EvenlyDistribute(Length::Shrink))
.into()
}
pub(crate) fn view(&self, window: window::Id) -> Element<'_, Message> {
if let Some(search_state) = &self.search_state
&& search_state.window_id == window
{
let search_input =
text_input("Query", &search_state.query).on_input(Message::QueryUpdated);
let label_column = table::column("Label", |result: &NamedNode| {
let label = self.ontology.label(result.as_ref()).unwrap_or("Unknown");
button(text(label))
.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 =
scrollable(table([label_column, iri_column], &search_state.results));
return column![search_input, results_table].into();
}
let add_row_button = button("Add row").on_press(Message::AddRow(None));
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 mut rows: Vec<Element<Message>> = vec![];
rows = self
.document
.dataset()
.iter_both()
.map(|(key, quad, state)| self.view_row(key, quad, state))
.collect();
let body: Element<Message> = if self.show_new_document_buttons {
column![
self.view_new_entity_buttons(self.ontology.subclass_of(ontology::GL_ENTITY)),
self.view_new_entity_buttons(self.ontology.subclass_of(ontology::RDA_ENTITY)),
]
.into()
} else {
column(rows).into()
};
let save_button_base = button(text("\u{1f4be}"));
let save_button = if self.modified {
save_button_base.on_press(Message::SaveGraph(false))
} else {
save_button_base
};
let content = column![
row![
add_row_button,
index_button,
search_launcher,
text_input("URL", &self.url_input)
.on_input(Message::URLInputChanged)
.on_submit(Message::URLInputSubmitted),
save_button,
],
scrollable(body),
space::vertical(),
];
if self.show_overwrite_confirmation {
let confirmation_modal = container(column![
text("The resource has changed since it was fetched. Overwrite?"),
row![
button(text("Yes"))
.style(button::danger)
.on_press(Message::ConfirmOverwrite),
space::horizontal(),
button(text("No")).on_press(Message::HideOverwriteConfirmationModal),
],
])
.width(Length::Shrink);
modal(content, confirmation_modal, Message::None)
} else {
content.into()
}
}
}
fn modal<'a, Message>(
base: impl Into<Element<'a, Message>>,
content: impl Into<Element<'a, Message>>,
on_blur: Message,
) -> Element<'a, Message>
where
Message: Clone + 'a,
{
stack![
base.into(),
opaque(
mouse_area(center(opaque(content)).style(|_theme| {
container::Style {
background: Some(
Color {
a: 0.8,
..Color::BLACK
}
.into(),
),
..container::Style::default()
}
}))
.on_press(on_blur)
)
]
.into()
}
+21
View File
@@ -0,0 +1,21 @@
use thiserror::Error;
pub type Result<R> = std::result::Result<R, Error>;
#[derive(Error, Debug)]
pub(crate) enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
IriParse(#[from] oxigraph::model::IriParseError),
#[error(transparent)]
RdfSyntax(#[from] oxigraph::io::RdfSyntaxError),
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
#[error(transparent)]
Search(#[from] gl_search::SearchError),
}
+25
View File
@@ -0,0 +1,25 @@
mod app;
mod error;
mod rdf;
mod windows;
use crate::app::Publisher;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, fmt};
fn main() -> color_eyre::Result<()> {
let appender = tracing_appender::rolling::never("/tmp", "publisher-log");
tracing_subscriber::registry()
.with(fmt::layer().with_writer(appender))
.with(EnvFilter::from_default_env())
.init();
color_eyre::install()?;
let application = iced::daemon(Publisher::new, Publisher::update, Publisher::view)
.title(Publisher::title)
.subscription(Publisher::subscription);
application.run()?;
Ok(())
}
+2
View File
@@ -0,0 +1,2 @@
pub(crate) mod ontology;
pub(crate) mod term_helper;
@@ -0,0 +1,154 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
<http://www.w3.org/1999/02/22-rdf-syntax-ns#> a owl:Ontology ;
dc:title "The RDF Concepts Vocabulary (RDF)" ;
dc:date "2019-12-16" ;
dc:description "This is the RDF Schema for the RDF vocabulary terms in the RDF Namespace, defined in RDF 1.1 Concepts." .
rdf:HTML a rdfs:Datatype ;
rdfs:subClassOf rdfs:Literal ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:seeAlso <http://www.w3.org/TR/rdf11-concepts/#section-html> ;
rdfs:label "HTML" ;
rdfs:comment "The datatype of RDF literals storing fragments of HTML content" .
rdf:langString a rdfs:Datatype ;
rdfs:subClassOf rdfs:Literal ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:seeAlso <http://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal> ;
rdfs:label "langString" ;
rdfs:comment "The datatype of language-tagged string values" .
rdf:PlainLiteral a rdfs:Datatype ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:subClassOf rdfs:Literal ;
rdfs:seeAlso <http://www.w3.org/TR/rdf-plain-literal/> ;
rdfs:label "PlainLiteral" ;
rdfs:comment "The class of plain (i.e. untyped) literal values, as used in RIF and OWL 2" .
rdf:type a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "type" ;
rdfs:comment "The subject is an instance of a class." ;
rdfs:range rdfs:Class ;
rdfs:domain rdfs:Resource .
rdf:Property a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "Property" ;
rdfs:comment "The class of RDF properties." ;
rdfs:subClassOf rdfs:Resource .
rdf:Statement a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "Statement" ;
rdfs:subClassOf rdfs:Resource ;
rdfs:comment "The class of RDF statements." .
rdf:subject a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "subject" ;
rdfs:comment "The subject of the subject RDF statement." ;
rdfs:domain rdf:Statement ;
rdfs:range rdfs:Resource .
rdf:predicate a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "predicate" ;
rdfs:comment "The predicate of the subject RDF statement." ;
rdfs:domain rdf:Statement ;
rdfs:range rdfs:Resource .
rdf:object a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "object" ;
rdfs:comment "The object of the subject RDF statement." ;
rdfs:domain rdf:Statement ;
rdfs:range rdfs:Resource .
rdf:Bag a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "Bag" ;
rdfs:comment "The class of unordered containers." ;
rdfs:subClassOf rdfs:Container .
rdf:Seq a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "Seq" ;
rdfs:comment "The class of ordered containers." ;
rdfs:subClassOf rdfs:Container .
rdf:Alt a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "Alt" ;
rdfs:comment "The class of containers of alternatives." ;
rdfs:subClassOf rdfs:Container .
rdf:value a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "value" ;
rdfs:comment "Idiomatic property used for structured values." ;
rdfs:domain rdfs:Resource ;
rdfs:range rdfs:Resource .
rdf:List a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "List" ;
rdfs:comment "The class of RDF Lists." ;
rdfs:subClassOf rdfs:Resource .
rdf:nil a rdf:List ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "nil" ;
rdfs:comment "The empty list, with no items in it. If the rest of a list is nil then the list has no more items in it." .
rdf:first a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "first" ;
rdfs:comment "The first item in the subject RDF list." ;
rdfs:domain rdf:List ;
rdfs:range rdfs:Resource .
rdf:rest a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "rest" ;
rdfs:comment "The rest of the subject RDF list after the first item." ;
rdfs:domain rdf:List ;
rdfs:range rdf:List .
rdf:XMLLiteral a rdfs:Datatype ;
rdfs:subClassOf rdfs:Literal ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:label "XMLLiteral" ;
rdfs:comment "The datatype of XML literal values." .
rdf:JSON a rdfs:Datatype ;
rdfs:label "JSON" ;
rdfs:comment "The datatype of RDF literals storing JSON content." ;
rdfs:subClassOf rdfs:Literal ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:seeAlso <https://www.w3.org/TR/json-ld11/#the-rdf-json-datatype> .
rdf:CompoundLiteral a rdfs:Class ;
rdfs:label "CompoundLiteral" ;
rdfs:comment "A class representing a compound literal." ;
rdfs:subClassOf rdfs:Resource ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:seeAlso <https://www.w3.org/TR/json-ld11/#the-rdf-compoundliteral-class-and-the-rdf-language-and-rdf-direction-properties> .
rdf:language a rdf:Property ;
rdfs:label "language" ;
rdfs:comment "The language component of a CompoundLiteral." ;
rdfs:domain rdf:CompoundLiteral ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:seeAlso <https://www.w3.org/TR/json-ld11/#the-rdf-compoundliteral-class-and-the-rdf-language-and-rdf-direction-properties> .
rdf:direction a rdf:Property ;
rdfs:label "direction" ;
rdfs:comment "The base direction component of a CompoundLiteral." ;
rdfs:domain rdf:CompoundLiteral ;
rdfs:isDefinedBy <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ;
rdfs:seeAlso <https://www.w3.org/TR/json-ld11/#the-rdf-compoundliteral-class-and-the-rdf-language-and-rdf-direction-properties> .
+515
View File
@@ -0,0 +1,515 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="owl2html.xsl"?>
<!DOCTYPE rdf:RDF [
<!ENTITY owl "http://www.w3.org/2002/07/owl#" >
<!ENTITY xsd "http://www.w3.org/2001/XMLSchema#" >
<!ENTITY rdfs "http://www.w3.org/2000/01/rdf-schema#" >
<!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#" >
]>
<rdf:RDF xmlns="http://fedora.info/definitions/v4/repository#"
xml:base="http://fedora.info/definitions/v4/repository"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<owl:Ontology rdf:about="http://fedora.info/definitions/v4/repository#">
<rdfs:label xml:lang="en">Fedora Commons Repository Ontology</rdfs:label>
<rdfs:comment xml:lang="en">Ontology for the Fedora data model, intended primarily to make it possible to expose Fedora-curated RDF predicates via de-reference-able URIs.</rdfs:comment>
<owl:versionInfo>v4/2016/10/18</owl:versionInfo>
<owl:priorVersion rdf:resource="http://fedora.info/definitions/v4/2016/03/01/repository" />
</owl:Ontology>
<!--
///////////////////////////////////////////////////////////////////////////////////////
//
// Data properties
//
///////////////////////////////////////////////////////////////////////////////////////
-->
<!-- http://fedora.info/definitions/v4/repository#computedChecksum -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#computedChecksum">
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#computedSize -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#computedSize">
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#couldNotStoreProperty -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#couldNotStoreProperty">
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#created -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#created">
<rdfs:range rdf:resource="&xsd;dateTime"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#createdBy -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#createdBy">
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#hasLocation -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#hasLocation">
<rdfs:range rdf:resource="&xsd;anyURI"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#hasVersionLabel -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#hasVersionLabel">
<rdfs:range rdf:resource="&xsd;string"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#isCheckedOut -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#isCheckedOut">
<rdfs:range rdf:resource="&xsd;boolean"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#lastModified -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#lastModified">
<rdfs:range rdf:resource="&xsd;dateTime"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#lastModifiedBy -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#lastModifiedBy">
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#numFixityChecks -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#numFixityChecks">
<rdfs:range rdf:resource="&xsd;nonNegativeInteger"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#numFixityErrors -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#numFixityErrors">
<rdfs:range rdf:resource="&xsd;nonNegativeInteger"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#numFixityRepaired -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#numFixityRepaired">
<rdfs:range rdf:resource="&xsd;nonNegativeInteger"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#numberOfChildren -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#numberOfChildren">
<rdfs:range rdf:resource="&xsd;nonNegativeInteger"/>
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#objectCount -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#objectCount">
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!-- http://fedora.info/definitions/v4/repository#objectSize -->
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#objectSize">
<rdfs:subPropertyOf rdf:resource="&owl;topDataProperty"/>
</owl:DatatypeProperty>
<!--
///////////////////////////////////////////////////////////////////////////////////////
//
// Object Properties
//
///////////////////////////////////////////////////////////////////////////////////////
-->
<!-- http://fedora.info/definitions/v4/repository#baseVersion -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#baseVersion">
<rdf:type rdf:resource="&owl;FunctionalProperty"/>
<rdfs:label xml:lang="en">base version</rdfs:label>
<rdfs:domain rdf:resource="http://fedora.info/definitions/v4/repository#Container"/>
<rdfs:range rdf:resource="http://fedora.info/definitions/v4/repository#Version"/>
</owl:ObjectProperty>
<!-- http://fedora.info/definitions/v4/repository#hasChild -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasChild">
<rdf:type rdf:resource="&owl;InverseFunctionalProperty"/>
<rdfs:label xml:lang="en">has child</rdfs:label>
<rdfs:domain rdf:resource="http://fedora.info/definitions/v4/repository#Container"/>
<rdfs:range>
<owl:Class>
<owl:unionOf rdf:parseType="Collection">
<rdf:Description rdf:about="http://fedora.info/definitions/v4/repository#NonRdfSourceDescription"/>
<rdf:Description rdf:about="http://fedora.info/definitions/v4/repository#Container"/>
</owl:unionOf>
</owl:Class>
</rdfs:range>
</owl:ObjectProperty>
<!-- http://fedora.info/definitions/v4/repository#hasContent -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasContent">
<rdfs:label xml:lang="en">has content</rdfs:label>
<rdfs:comment xml:lang="en">Indicates a binary in which content is stored for this datastream.</rdfs:comment>
<rdfs:range rdf:resource="http://fedora.info/definitions/v4/repository#Binary"/>
<rdfs:domain rdf:resource="http://fedora.info/definitions/v4/repository#NonRdfSourceDescription"/>
</owl:ObjectProperty>
<!-- http://fedora.info/definitions/v4/repository#hasMember -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasMember">
<rdfs:label xml:lang="en">has member</rdfs:label>
<rdfs:comment xml:lang="en">Links to a newly-minted identifier which can be used to create a repository resource.</rdfs:comment>
<rdfs:range rdf:resource="&xsd;anyURI"/>
</owl:ObjectProperty>
<!-- http://fedora.info/definitions/v4/repository#hasParent -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasParent">
<rdf:type rdf:resource="&owl;FunctionalProperty"/>
<rdfs:label xml:lang="en">has parent</rdfs:label>
<rdfs:range rdf:resource="http://fedora.info/definitions/v4/repository#Container"/>
<rdfs:domain rdf:resource="http://fedora.info/definitions/v4/repository#Resource"/>
</owl:ObjectProperty>
<!-- http://fedora.info/definitions/v4/repository#hasResultsMember -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasResultsMember">
<rdfs:label xml:lang="en">has results member</rdfs:label>
<rdfs:range rdf:resource="http://fedora.info/definitions/v4/repository#Resource"/>
</owl:ObjectProperty>
<!-- http://fedora.info/definitions/v4/repository#hasVersion -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasVersion">
<rdfs:label xml:lang="en">has version</rdfs:label>
<rdfs:domain rdf:resource="http://fedora.info/definitions/v4/repository#Container"/>
<rdfs:range rdf:resource="http://fedora.info/definitions/v4/repository#Version"/>
</owl:ObjectProperty>
<!-- http://fedora.info/definitions/v4/repository#isContentOf -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#isContentOf">
<rdf:type rdf:resource="&owl;InverseFunctionalProperty"/>
<rdfs:label xml:lang="en">is content of</rdfs:label>
<rdfs:comment xml:lang="en">Indicates a datastream for which this resource contains the content. </rdfs:comment>
<rdfs:domain rdf:resource="http://fedora.info/definitions/v4/repository#Binary"/>
<rdfs:range rdf:resource="http://fedora.info/definitions/v4/repository#NonRdfSourceDescription"/>
</owl:ObjectProperty>
<!-- http://fedora.info/definitions/v4/repository#predecessors -->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#predecessors">
<rdfs:label xml:lang="en">predecessors</rdfs:label>
<rdfs:range rdf:resource="http://fedora.info/definitions/v4/repository#Version"/>
<rdfs:domain rdf:resource="http://fedora.info/definitions/v4/repository#Version"/>
</owl:ObjectProperty>
<!--
///////////////////////////////////////////////////////////////////////////////////////
//
// Classes
//
///////////////////////////////////////////////////////////////////////////////////////
-->
<!-- http://fedora.info/definitions/v4/repository#AnnotatedResource -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#AnnotatedResource">
<rdfs:label xml:lang="en">annotated resource</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Resource"/>
<rdfs:comment xml:lang="en">A Resource that maintains properties in its own right.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Binary -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Binary">
<rdfs:label xml:lang="en">binary</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Resource"/>
<owl:disjointWith rdf:resource="http://fedora.info/definitions/v4/repository#NonRdfSourceDescription"/>
<owl:disjointWith rdf:resource="http://fedora.info/definitions/v4/repository#Container"/>
<rdfs:comment xml:lang="en">A bitstream, with no further data properties.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#RepositoryRoot -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#RepositoryRoot">
<rdfs:label xml:lang="en">repository root</rdfs:label>
<owl:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Container"/>
<rdfs:comment xml:lang="en">A repository root.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Skolem -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Skolem">
<rdfs:label xml:lang="en">skolem</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Thing"/>
<rdfs:comment xml:lang="en">An entity that is a representation of an RDF Skolem node.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Configuration -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Configuration">
<rdfs:label xml:lang="en">Fedora configuration</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Thing"/>
<rdfs:comment xml:lang="en">A container for a Fedora configuration.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#NonRdfSourceDescription -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#NonRdfSourceDescription">
<rdfs:label xml:lang="en">Fedora NonRdfSourceDescription</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#AnnotatedResource"/>
<owl:disjointWith rdf:resource="http://fedora.info/definitions/v4/repository#Container"/>
<rdfs:comment xml:lang="en">A container for a bitstream and associated properties.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#EmbedResources -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#EmbedResources">
<rdfs:label xml:lang="en">embed resources</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Thing"/>
<rdfs:comment xml:lang="en">The set of triples representing child resources of a given resource.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#InboundReferences -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#InboundReferences">
<rdfs:label xml:lang="en">inbound references</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Thing"/>
<rdfs:comment xml:lang="en">The set of triples representing other repository resources which link to a given resource.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Container -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Container">
<rdfs:label xml:lang="en">Fedora Container</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#AnnotatedResource"/>
<rdfs:comment xml:lang="en">A Fedora Container: the fundamental quantum of durable content in a Fedora repository.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Pairtree -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Pairtree">
<rdfs:label xml:lang="en">pair tree</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Thing"/>
<rdfs:comment xml:lang="en">An entity that is a an intermediary node created in a PairTree hierarchy.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Relations -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Relations">
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Thing"/>
<rdfs:comment xml:lang="en">An entity that may be related to other repository entities.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Resource -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Resource">
<rdfs:label xml:lang="en">Fedora resource</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Thing"/>
<rdfs:comment xml:lang="en">An entity that has been committed to the repository for safekeeping. For example, Fedora objects and datastreams are resources. A Fixity is not, because the provenance of the instance is entirely internal to the repository.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#ServerManaged -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#ServerManaged">
<rdfs:label xml:lang="en">server managed</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Thing"/>
<rdfs:comment xml:lang="en">The system-generated triples for a given resource (as opposed to explicity-declared properties).</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Thing -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Thing">
<rdfs:label xml:lang="en">Fedora thing</rdfs:label>
<rdfs:comment xml:lang="en">Something that is contemplated in the Fedora repository model.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Tombstone -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Tombstone">
<rdfs:label xml:lang="en">tombstone</rdfs:label>
<rdfs:comment xml:lang="en">An entity that is a marker for a deleted node.</rdfs:comment>
</owl:Class>
<!-- http://fedora.info/definitions/v4/repository#Version -->
<owl:Class rdf:about="http://fedora.info/definitions/v4/repository#Version">
<rdfs:label xml:lang="en">A snapshot of a Fedora object at a given point in time.</rdfs:label>
<rdfs:subClassOf rdf:resource="http://fedora.info/definitions/v4/repository#Container"/>
</owl:Class>
<!--
///////////////////////////////////////////////////////////////////////////////////////
//
// Individuals
//
///////////////////////////////////////////////////////////////////////////////////////
-->
<!-- http://fedora.info/definitions/v4/repository#inaccessibleResource -->
<owl:NamedIndividual rdf:about="http://fedora.info/definitions/v4/repository#inaccessibleResource">
<rdfs:label xml:lang="en">inaccessible resource</rdfs:label>
<rdfs:comment xml:lang="en">A Fedora resource that is inaccessible.</rdfs:comment>
</owl:NamedIndividual>
<!--
///////////////////////////////////////////////////////////////////////////////////////
//
// REST API
//
///////////////////////////////////////////////////////////////////////////////////////
-->
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasAccessRoles">
<rdfs:label xml:lang="en">has access roles</rdfs:label>
</owl:ObjectProperty>
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasFixityService">
<rdfs:label xml:lang="en">has fixity service</rdfs:label>
</owl:ObjectProperty>
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasNamespaces">
<rdfs:label xml:lang="en">has namespaces</rdfs:label>
</owl:ObjectProperty>
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#hasVersions">
<rdfs:label xml:lang="en">has versions</rdfs:label>
</owl:ObjectProperty>
<owl:ObjectProperty rdf:about="http://fedora.info/definitions/v4/repository#sparql">
<rdfs:label xml:lang="en">has sparql service</rdfs:label>
</owl:ObjectProperty>
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#hasMoreResults">
<rdfs:label xml:lang="en">has more results</rdfs:label>
<rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#boolean"/>
</owl:DatatypeProperty>
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#hasTransactionProvider">
<rdfs:label xml:lang="en">has transaction provider</rdfs:label>
</owl:DatatypeProperty>
<owl:DatatypeProperty rdf:about="http://fedora.info/definitions/v4/repository#writable">
<rdfs:label xml:lang="en">writable</rdfs:label>
<rdfs:comment>Whether or not the resource with this representation could have been altered by the same agent that received this representation if the request that retrieved this representation had been a mutating request.</rdfs:comment>
<rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#boolean"/>
</owl:DatatypeProperty>
</rdf:RDF>
<!-- Generated by the OWL API (version 3.4.2) http://owlapi.sourceforge.net -->
+264
View File
@@ -0,0 +1,264 @@
# See details within this document for linkage to specification and purpose.
# This ontology file is a non-normative supporting document.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix owl: <http://www.w3.org/2002/07/owl#>.
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix dcterms: <http://purl.org/dc/terms/>.
@prefix vs: <http://www.w3.org/2003/06/sw-vocab-status/ns#> .
@prefix vann: <http://purl.org/vocab/vann/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@prefix : <http://www.w3.org/ns/ldp#>.
:
a owl:Ontology;
dcterms:description "Vocabulary URIs defined in the Linked Data Platform (LDP) namespace.";
dcterms:title "The W3C Linked Data Platform (LDP) Vocabulary";
rdfs:label "W3C Linked Data Platform (LDP)";
rdfs:comment "This ontology provides an informal representation of the concepts and terms as defined in the LDP specification. Consult the LDP specification for normative reference.";
dcterms:publisher <http://www.w3.org/data#W3C>;
dcterms:creator [foaf:name "Steve Speicher"], [foaf:name "John Arwe"], [foaf:name "Ashok Malhotra"];
foaf:maker [foaf:homepage <http://www.w3.org/2012/ldp>];
dcterms:created "2015-02-26"^^xsd:date;
vann:preferredNamespaceUri "http://www.w3.org/ns/ldp#";
vann:preferredNamespacePrefix "ldp";
rdfs:seeAlso <http://www.w3.org/2012/ldp>,
<http://www.w3.org/TR/ldp-ucr/>,
<http://www.w3.org/TR/ldp/>,
<http://www.w3.org/TR/ldp-paging/>,
<http://www.w3.org/2011/09/LinkedData/>.
:Resource
a rdfs:Class;
rdfs:comment "A HTTP-addressable resource whose lifecycle is managed by a LDP server.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "Resource".
:RDFSource
a rdfs:Class;
rdfs:subClassOf :Resource;
rdfs:comment "A Linked Data Platform Resource (LDPR) whose state is represented as RDF.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "RDFSource".
:NonRDFSource
a rdfs:Class;
rdfs:subClassOf :Resource;
rdfs:comment "A Linked Data Platform Resource (LDPR) whose state is NOT represented as RDF.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "NonRDFSource".
:Container
a rdfs:Class;
rdfs:subClassOf :RDFSource;
rdfs:comment "A Linked Data Platform RDF Source (LDP-RS) that also conforms to additional patterns and conventions for managing membership. Readers should refer to the specification defining this ontology for the list of behaviors associated with it.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "Container".
:BasicContainer
a rdfs:Class;
rdfs:subClassOf :Container;
rdfs:comment "An LDPC that uses a predefined predicate to simply link to its contained resources.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "BasicContainer".
:DirectContainer
a rdfs:Class;
rdfs:subClassOf :Container;
rdfs:comment "An LDPC that is similar to a LDP-DC but it allows an indirection with the ability to list as member a resource, such as a URI representing a real-world object, that is different from the resource that is created.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "DirectContainer".
:IndirectContainer
a rdfs:Class;
rdfs:subClassOf :Container;
rdfs:comment "An LDPC that has the flexibility of choosing what form the membership triples take.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "IndirectContainer".
:hasMemberRelation
a rdf:Property;
rdfs:comment "Indicates which predicate is used in membership triples, and that the membership triple pattern is < membership-constant-URI , object-of-hasMemberRelation, member-URI >.";
vs:term_status "stable";
rdfs:domain :Container;
rdfs:isDefinedBy :;
rdfs:label "hasMemberRelation";
rdfs:range rdf:Property.
:isMemberOfRelation
a rdf:Property;
rdfs:comment "Indicates which predicate is used in membership triples, and that the membership triple pattern is < member-URI , object-of-isMemberOfRelation, membership-constant-URI >.";
vs:term_status "stable";
rdfs:domain :Container;
rdfs:isDefinedBy :;
rdfs:label "isMemmberOfRelation";
rdfs:range rdf:Property.
:membershipResource
a rdf:Property;
rdfs:comment "Indicates the membership-constant-URI in a membership triple. Depending upon the membership triple pattern a container uses, as indicated by the presence of ldp:hasMemberRelation or ldp:isMemberOfRelation, the membership-constant-URI might occupy either the subject or object position in membership triples.";
vs:term_status "stable";
rdfs:domain :Container;
rdfs:isDefinedBy :;
rdfs:label "membershipResource";
rdfs:range rdfs:Resource.
:insertedContentRelation
a rdf:Property;
rdfs:comment "Indicates which triple in a creation request should be used as the member-URI value in the membership triple added when the creation request is successful.";
vs:term_status "stable";
rdfs:domain :Container;
rdfs:isDefinedBy :;
rdfs:label "insertedContentRelation";
rdfs:range rdf:Property.
:member
a rdf:Property;
rdfs:comment "LDP servers should use this predicate as the membership predicate if there is no obvious predicate from an application vocabulary to use.";
vs:term_status "stable";
rdfs:domain :Resource;
rdfs:isDefinedBy :;
rdfs:label "member";
rdfs:range rdfs:Resource.
:contains
a rdf:Property;
rdfs:comment "Links a container with resources created through the container.";
vs:term_status "stable";
rdfs:domain :Container;
rdfs:isDefinedBy :;
rdfs:label "contains";
rdfs:range rdfs:Resource.
:MemberSubject
a owl:Individual;
rdfs:comment "Used to indicate default and typical behavior for ldp:insertedContentRelation, where the member-URI value in the membership triple added when a creation request is successful is the URI assigned to the newly created resource.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "MemberSubject".
:PreferContainment
a owl:Individual;
rdfs:comment "URI identifying a LDPC's containment triples, for example to allow clients to express interest in receiving them.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "PreferContainment".
:PreferMembership
a owl:Individual;
rdfs:comment "URI identifying a LDPC's membership triples, for example to allow clients to express interest in receiving them.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "PreferMembership".
:PreferEmptyContainer
a owl:Individual;
rdfs:comment "Archaic alias for ldp:PreferMinimalContainer";
vs:term_status "archaic";
rdfs:isDefinedBy :;
owl:equivalentProperty :PreferMinimalContainer;
rdfs:seeAlso :PreferMinimalContainer;
rdfs:label "PreferEmptyContainer".
:PreferMinimalContainer
a owl:Individual;
rdfs:comment "URI identifying the subset of a LDPC's triples present in an empty LDPC, for example to allow clients to express interest in receiving them. Currently this excludes containment and membership triples, but in the future other exclusions might be added. This definition is written to automatically exclude those new classes of triples.";
vs:term_status "stable";
rdfs:isDefinedBy :;
rdfs:label "PreferMinimalContainer".
:constrainedBy
a rdf:Property;
rdfs:comment "Links a resource with constraints that the server requires requests like creation and update to conform to.";
vs:term_status "stable";
rdfs:domain :Resource;
rdfs:isDefinedBy :;
rdfs:label "constrainedBy";
rdfs:range rdfs:Resource.
:pageSortCriteria
a rdf:Property;
rdfs:comment "Link to the list of sorting criteria used by the server in a representation. Typically used on Link response headers as an extension link relation URI in the rel= parameter.";
vs:term_status "testing";
rdfs:domain :Page;
rdfs:isDefinedBy :;
rdfs:label "pageSortCriteria";
rdfs:range rdf:List.
:PageSortCriterion
a rdfs:Class;
rdfs:comment "Element in the list of sorting criteria used by the server to assign container members to pages.";
vs:term_status "testing";
rdfs:label "PageSortCriterion";
rdfs:isDefinedBy :.
:pageSortPredicate
a rdf:Property;
rdfs:comment "Predicate used to specify the order of the members across a page sequence's in-sequence page resources; it asserts nothing about the order of members in the representation of a single page.";
vs:term_status "testing";
rdfs:domain :PageSortCriterion;
rdfs:isDefinedBy :;
rdfs:label "pageSortPredicate";
rdfs:range rdf:Property.
:pageSortOrder
a rdf:Property;
rdfs:comment "The ascending/descending/etc order used to order the members across pages in a page sequence.";
vs:term_status "testing";
rdfs:domain :PageSortCriterion;
rdfs:isDefinedBy :;
rdfs:label "pageSortOrder";
rdfs:range rdf:Resource.
:pageSortCollation
a rdf:Property;
rdfs:comment "The collation used to order the members across pages in a page sequence when comparing strings.";
vs:term_status "testing";
rdfs:domain :PageSortCriterion;
rdfs:isDefinedBy :;
rdfs:label "pageSortCollation";
rdfs:range rdf:Property.
:Ascending
a owl:Individual;
rdfs:comment "Ascending order.";
vs:term_status "testing";
rdfs:isDefinedBy :;
rdfs:label "Ascending".
:Descending
a owl:Individual;
rdfs:comment "Descending order.";
vs:term_status "testing";
rdfs:isDefinedBy :;
rdfs:label "Descending".
:Page
a rdfs:Class;
rdfs:comment "URI signifying that the resource is an in-sequence page resource, as defined by LDP Paging. Typically used on Link rel='type' response headers.";
vs:term_status "testing";
rdfs:isDefinedBy :;
rdfs:label "Page".
:pageSequence
a rdf:Property;
rdfs:comment "Link to a page sequence resource, as defined by LDP Paging. Typically used to communicate the sorting criteria used to allocate LDPC members to pages.";
vs:term_status "testing";
rdfs:isDefinedBy :;
rdfs:label "Page".
:inbox
a rdf:Property;
rdfs:comment "Links a resource to a container where notifications for the resource can be created and discovered.";
vs:term_status "stable";
rdfs:isDefinedBy <https://www.w3.org/TR/ldn/>;
rdfs:label "inbox";
dcterms:issued "2016-09-29"^^xsd:date;
dcterms:creator <http://csarven.ca/#i>, <https://rhiaro.co.uk/#me>.
+410
View File
@@ -0,0 +1,410 @@
@prefix : <https://graphofliberty.org/2026/04/ont/> .
@prefix ldp: <http://www.w3.org/ns/ldp#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdaa: <http://rdaregistry.info/Elements/a/> .
@prefix rdac: <http://rdaregistry.info/Elements/c/> .
@prefix rdae: <http://rdaregistry.info/Elements/e/> .
@prefix rdai: <http://rdaregistry.info/Elements/i/> .
@prefix rdam: <http://rdaregistry.info/Elements/m/> .
@prefix rdan: <http://rdaregistry.info/Elements/n/> .
@prefix rdap: <http://rdaregistry.info/Elements/p/> .
@prefix rdat: <http://rdaregistry.info/Elements/t/> .
@prefix rdaw: <http://rdaregistry.info/Elements/w/> .
@prefix rdax: <http://rdaregistry.info/Elements/x/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
@base <https://graphofliberty.org/2026/04/ont/> .
<https://graphofliberty.org/2026/04/ont> rdf:type owl:Ontology .
#################################################################
# Annotation properties
#################################################################
### https://graphofliberty.org/2026/04/ont/catalogId
:catalogId rdf:type owl:AnnotationProperty ;
rdfs:comment "An integer associated with the class for fast lookup in a database."@en ;
rdfs:label "catalog id" ;
rdfs:domain owl:Class .
### https://graphofliberty.org/2026/04/ont/indexedByField
:indexedByField rdf:type owl:AnnotationProperty ;
rdfs:comment "The property is associated with the given field in a full-text search database."@en ;
rdfs:label "indexed by field"@en ;
rdfs:domain owl:DatatypeProperty .
#################################################################
# 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#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://rdaregistry.info/Elements/a/P50094
rdaa:P50094 rdf:type owl:ObjectProperty .
### http://rdaregistry.info/Elements/a/identifierForPerson.en
rdaa:identifierForPerson.en rdf:type owl:ObjectProperty .
### http://rdaregistry.info/Elements/m/P30154
rdam:P30154 rdf:type owl:ObjectProperty .
### http://rdaregistry.info/Elements/m/uniformResourceLocator.en
rdam:uniformResourceLocator.en rdf:type owl:ObjectProperty .
### 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://rdaregistry.info/Elements/a/P50291
rdaa:P50291 rdf:type owl:DatatypeProperty .
### http://rdaregistry.info/Elements/a/P50292
rdaa:P50292 rdf:type owl:DatatypeProperty .
### http://rdaregistry.info/Elements/a/givenName.en
rdaa:givenName.en rdf:type owl:DatatypeProperty .
### http://rdaregistry.info/Elements/a/surname.en
rdaa:surname.en rdf:type owl:DatatypeProperty .
#################################################################
# Classes
#################################################################
### http://fedora.info/definitions/v4/repository#Container
fedora:Container rdf:type owl:Class ;
rdfs:subClassOf :ReadOnly .
### http://fedora.info/definitions/v4/repository#Resource
fedora:Resource rdf:type owl:Class ;
rdfs:subClassOf :ReadOnly .
### http://rdaregistry.info/Elements/c/C10001
rdac:C10001 rdf:type owl:Class ;
rdfs:subClassOf rdac:C10013 ;
rdfs:label "Work"@en .
### http://rdaregistry.info/Elements/c/C10002
rdac:C10002 rdf:type owl:Class ;
rdfs:subClassOf rdac:C10013 ;
rdfs:label "Agent"@en .
### http://rdaregistry.info/Elements/c/C10004
rdac:C10004 rdf:type owl:Class ;
rdfs:subClassOf rdac:C10002 ;
rdfs:label "Person"@en .
### http://rdaregistry.info/Elements/c/C10007
rdac:C10007 rdf:type owl:Class ;
rdfs:subClassOf rdac:C10013 .
### http://rdaregistry.info/Elements/c/C10013
rdac:C10013 rdf:type owl:Class ;
rdfs:label "RDA Entity"@en .
### http://www.w3.org/ns/ldp#BasicContainer
ldp:BasicContainer rdf:type owl:Class ;
rdfs:subClassOf :ReadOnly .
### http://www.w3.org/ns/ldp#Container
ldp:Container rdf:type owl:Class ;
rdfs:subClassOf :ReadOnly .
### http://www.w3.org/ns/ldp#RDFSource
ldp:RDFSource rdf:type owl:Class ;
rdfs:subClassOf :ReadOnly .
### http://www.w3.org/ns/ldp#Resource
ldp:Resource rdf:type owl:Class ;
rdfs:subClassOf :ReadOnly .
### https://graphofliberty.org/2026/04/ont/AudioBook
:AudioBook rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "Audio Book"@en ;
:catalogId "1"^^xsd:positiveInteger .
### https://graphofliberty.org/2026/04/ont/Book
:Book rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "Book"@en ;
:catalogId "2"^^xsd:positiveInteger .
### https://graphofliberty.org/2026/04/ont/Document
:Document rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "Document"@en ;
:catalogId "3"^^xsd:positiveInteger .
### 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/Meme
:Meme rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "Meme"@en ;
:catalogId "4"^^xsd:positiveInteger .
### https://graphofliberty.org/2026/04/ont/Movie
:Movie rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "Movie"@en ;
:catalogId "5"^^xsd:positiveInteger .
### https://graphofliberty.org/2026/04/ont/Music
:Music rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "Music"@en ;
:catalogId "6"^^xsd:positiveInteger .
### https://graphofliberty.org/2026/04/ont/Person
:Person rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "Person"@en ;
:catalogId "7"^^xsd:positiveInteger .
### https://graphofliberty.org/2026/04/ont/Podcast
:Podcast rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "Podcast"@en ;
:catalogId "8"^^xsd:positiveInteger .
### https://graphofliberty.org/2026/04/ont/ReadOnly
:ReadOnly rdf:type owl:Class ;
rdfs:label "Read Only"@en .
### https://graphofliberty.org/2026/04/ont/TVShow
:TVShow rdf:type owl:Class ;
rdfs:subClassOf :Entity ;
rdfs:label "TV Show"@en ;
:catalogId "9"^^xsd:positiveInteger .
#################################################################
# Individuals
#################################################################
### http://fedora.info/definitions/v4/repository#created
fedora:created rdf:type owl:NamedIndividual ,
:ReadOnly .
### http://fedora.info/definitions/v4/repository#createdBy
fedora:createdBy rdf:type owl:NamedIndividual ,
:ReadOnly .
### http://fedora.info/definitions/v4/repository#hasParent
fedora:hasParent rdf:type owl:NamedIndividual ,
:ReadOnly .
### http://fedora.info/definitions/v4/repository#lastModified
fedora:lastModified rdf:type owl:NamedIndividual ,
:ReadOnly .
### http://fedora.info/definitions/v4/repository#lastModifiedBy
fedora:lastModifiedBy rdf:type owl:NamedIndividual ,
:ReadOnly .
### http://rdaregistry.info/Elements/a/P50094
rdaa:P50094 rdf:type owl:NamedIndividual ;
owl:sameAs rdaa:identifierForPerson.en .
### http://rdaregistry.info/Elements/a/identifierForPerson.en
### http://rdaregistry.info/Elements/a/P50291
rdaa:P50291 rdf:type owl:NamedIndividual ;
owl:sameAs rdaa:surname.en .
### http://rdaregistry.info/Elements/a/surname.en
### http://rdaregistry.info/Elements/a/P50292
rdaa:P50292 rdf:type owl:NamedIndividual ;
owl:sameAs rdaa:givenName.en .
### http://rdaregistry.info/Elements/a/givenName.en
### http://rdaregistry.info/Elements/a/givenName.en
rdaa:givenName.en rdf:type owl:NamedIndividual .
### http://rdaregistry.info/Elements/a/identifierForPerson.en
rdaa:identifierForPerson.en rdf:type owl:NamedIndividual .
### http://rdaregistry.info/Elements/a/surname.en
rdaa:surname.en rdf:type owl:NamedIndividual .
### http://rdaregistry.info/Elements/c/C10007
rdac:C10007 rdf:type owl:NamedIndividual ;
:template _:genid1 .
_:genid1 rdf:type rdac:C10007 ;
rdam:uniformResourceLocator.en : .
### http://rdaregistry.info/Elements/m/P30154
rdam:P30154 rdf:type owl:NamedIndividual ;
owl:sameAs rdam:uniformResourceLocator.en .
### http://rdaregistry.info/Elements/m/uniformResourceLocator.en
### http://rdaregistry.info/Elements/m/uniformResourceLocator.en
rdam:uniformResourceLocator.en rdf:type owl:NamedIndividual .
### http://www.w3.org/ns/ldp#contains
ldp:contains rdf:type owl:NamedIndividual ,
:ReadOnly ;
rdfs:label "contains"@en .
### https://graphofliberty.org/2026/04/ont/
: rdf:type owl:NamedIndividual .
#################################################################
# Annotations
#################################################################
fedora:created rdfs:label "created"@en .
fedora:createdBy rdfs:label "created by"@en .
fedora:lastModified rdfs:label "last modified"@en .
fedora:lastModifiedBy rdfs:label "last modified by"@en .
rdaa:P50094 rdfs:label "has identifier for person"@en .
rdaa:P50291 rdfs:label "has surname"@en ;
:indexedByField "surname" .
rdaa:P50292 rdfs:label "has given name"@en ;
:indexedByField "given name" .
rdac:C10007 rdfs:label "Manifestation"@en .
rdam:P30154 rdfs:label "has uniform resource locator"@en .
### Generated by the OWL API (version 4.5.29.2024-05-13T12:11:03Z) https://github.com/owlcs/owlapi
+108
View File
@@ -0,0 +1,108 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
<http://www.w3.org/2000/01/rdf-schema#> a owl:Ontology ;
dc:title "The RDF Schema vocabulary (RDFS)" .
rdfs:Resource a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "Resource" ;
rdfs:comment "The class resource, everything." .
rdfs:Class a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "Class" ;
rdfs:comment "The class of classes." ;
rdfs:subClassOf rdfs:Resource .
rdfs:subClassOf a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "subClassOf" ;
rdfs:comment "The subject is a subclass of a class." ;
rdfs:range rdfs:Class ;
rdfs:domain rdfs:Class .
rdfs:subPropertyOf a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "subPropertyOf" ;
rdfs:comment "The subject is a subproperty of a property." ;
rdfs:range rdf:Property ;
rdfs:domain rdf:Property .
rdfs:comment a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "comment" ;
rdfs:comment "A description of the subject resource." ;
rdfs:domain rdfs:Resource ;
rdfs:range rdfs:Literal .
rdfs:label a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "label" ;
rdfs:comment "A human-readable name for the subject." ;
rdfs:domain rdfs:Resource ;
rdfs:range rdfs:Literal .
rdfs:domain a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "domain" ;
rdfs:comment "A domain of the subject property." ;
rdfs:range rdfs:Class ;
rdfs:domain rdf:Property .
rdfs:range a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "range" ;
rdfs:comment "A range of the subject property." ;
rdfs:range rdfs:Class ;
rdfs:domain rdf:Property .
rdfs:seeAlso a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "seeAlso" ;
rdfs:comment "Further information about the subject resource." ;
rdfs:range rdfs:Resource ;
rdfs:domain rdfs:Resource .
rdfs:isDefinedBy a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:subPropertyOf rdfs:seeAlso ;
rdfs:label "isDefinedBy" ;
rdfs:comment "The definition of the subject resource." ;
rdfs:range rdfs:Resource ;
rdfs:domain rdfs:Resource .
rdfs:Literal a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "Literal" ;
rdfs:comment "The class of literal values, eg. textual strings and integers." ;
rdfs:subClassOf rdfs:Resource .
rdfs:Container a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "Container" ;
rdfs:subClassOf rdfs:Resource ;
rdfs:comment "The class of RDF containers." .
rdfs:ContainerMembershipProperty a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "ContainerMembershipProperty" ;
rdfs:comment """The class of container membership properties, rdf:_1, rdf:_2, ..., all of which are sub-properties of 'member'.""" ;
rdfs:subClassOf rdf:Property .
rdfs:member a rdf:Property ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "member" ;
rdfs:comment "A member of the subject resource." ;
rdfs:domain rdfs:Resource ;
rdfs:range rdfs:Resource .
rdfs:Datatype a rdfs:Class ;
rdfs:isDefinedBy <http://www.w3.org/2000/01/rdf-schema#> ;
rdfs:label "Datatype" ;
rdfs:comment "The class of RDF datatypes." ;
rdfs:subClassOf rdfs:Class .
<http://www.w3.org/2000/01/rdf-schema#> rdfs:seeAlso <http://www.w3.org/2000/01/rdf-schema-more> .
+411
View File
@@ -0,0 +1,411 @@
use crate::error;
use oxigraph::io::{RdfFormat, RdfParser};
use oxigraph::model::vocab::{rdf, rdfs, xsd};
use oxigraph::model::{
Dataset, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple,
TripleRef,
};
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
use std::collections::{HashMap, HashSet};
const PREFIXES: &[(&str, &str)] = &[
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
("owl", "http://www.w3.org/2002/07/owl#"),
("xsd", "http://www.w3.org/2001/XMLSchema#"),
("ldp", "http://www.w3.org/ns/ldp#"),
("dc", "http://purl.org/dc/elements/1.1/"),
("posix", "http://www.w3.org/ns/posix/stat#"),
(
"ebucore",
"http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#",
),
("premis", "http://www.loc.gov/premis/rdf/v1#"),
("premis3", "http://www.loc.gov/premis/rdf/v3/"),
("dcterms", "http://purl.org/dc/terms/"),
("fedora", "http://fedora.info/definitions/v4/repository#"),
("rdaa", "http://rdaregistry.info/Elements/a/"),
("rdac", "http://rdaregistry.info/Elements/c/"),
("rdae", "http://rdaregistry.info/Elements/e/"),
("rdai", "http://rdaregistry.info/Elements/i/"),
("rdam", "http://rdaregistry.info/Elements/m/"),
("rdan", "http://rdaregistry.info/Elements/n/"),
("rdap", "http://rdaregistry.info/Elements/p/"),
("rdat", "http://rdaregistry.info/Elements/t/"),
("rdaw", "http://rdaregistry.info/Elements/w/"),
("rdax", "http://rdaregistry.info/Elements/x/"),
("schema", "https://schema.org/"),
("quill", "http://fedora.quill.lan/rest/"),
("gl", "https://graphofliberty.org/2026/04/ont/"),
];
const RDF_ONT: &[u8] = include_bytes!("ontologies/22-rdf-syntax-ns.ttl");
const RDFS_ONT: &[u8] = include_bytes!("ontologies/rdf-schema.ttl");
const LDP_ONT: &[u8] = include_bytes!("ontologies/ldp.ttl");
const FEDORA_ONT: &[u8] = include_bytes!("ontologies/fedora.xml");
const GL_ONT: &[u8] = include_bytes!("ontologies/ontology.ttl");
const GL_READ_ONLY: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/ReadOnly");
const GL_TEMPLATE: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/template");
const GL_INDEXED_BY_FIELD: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/indexedByField");
const GL_CATALOG_ID: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/catalogId");
const OWL_SAME_AS: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/2002/07/owl#sameAs");
pub const RDA_ENTITY: NamedNodeRef =
NamedNodeRef::new_unchecked("http://rdaregistry.info/Elements/c/C10013");
pub const GL_ENTITY: NamedNodeRef =
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/Entity");
pub struct OntologyBuilder<'a> {
ontologies: Vec<(RdfFormat, &'a [u8])>,
}
impl<'a> OntologyBuilder<'a> {
pub fn with_ontology_bytes(mut self, format: RdfFormat, bytes: &'a [u8]) -> Self {
self.ontologies.push((format, bytes));
self
}
pub fn with_default_ontologies(self) -> Self {
self.with_ontology_bytes(RdfFormat::Turtle, RDF_ONT)
.with_ontology_bytes(RdfFormat::Turtle, RDFS_ONT)
.with_ontology_bytes(RdfFormat::Turtle, LDP_ONT)
.with_ontology_bytes(RdfFormat::RdfXml, FEDORA_ONT)
.with_ontology_bytes(RdfFormat::Turtle, GL_ONT)
}
fn materialize_same_as(dataset: &mut Dataset) {
let additional_quads = dataset
.quads_for_pattern(None, Some(OWL_SAME_AS), None, None)
.fold(Dataset::new(), |mut new_dataset, alias| {
if let NamedOrBlankNodeRef::NamedNode(x) = alias.subject
&& let TermRef::NamedNode(y) = alias.object
{
for mut quad in dataset.quads_for_pattern(
Some(NamedOrBlankNodeRef::NamedNode(x)),
None,
None,
None,
) {
quad.subject = NamedOrBlankNodeRef::NamedNode(y);
new_dataset.insert(quad);
}
for mut quad in dataset.quads_for_pattern(
Some(NamedOrBlankNodeRef::NamedNode(y)),
None,
None,
None,
) {
quad.subject = NamedOrBlankNodeRef::NamedNode(x);
new_dataset.insert(quad);
}
}
new_dataset
});
let old_size = dataset.len();
dataset.extend(&additional_quads);
let new_size = dataset.len();
if new_size > old_size {
Self::materialize_same_as(dataset)
}
}
fn memoize(ontology: &mut Ontology) {
// Read-only properties
for quad in ontology.dataset.quads_for_pattern(
None,
Some(rdf::TYPE),
Some(TermRef::NamedNode(GL_READ_ONLY)),
None,
) {
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject {
ontology.read_only_properties.insert(subject.into_owned());
}
}
// Read-only classes
for quad in ontology.dataset.quads_for_pattern(
None,
Some(rdfs::SUB_CLASS_OF),
Some(TermRef::NamedNode(GL_READ_ONLY)),
None,
) {
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject {
ontology.read_only_classes.insert(subject.into_owned());
}
}
// Full-text search index field names
for quad in ontology
.dataset
.quads_for_pattern(None, Some(GL_INDEXED_BY_FIELD), None, None)
{
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject
&& let TermRef::Literal(literal) = quad.object
{
ontology
.field_map
.insert(subject.into_owned(), literal.value().to_string());
}
}
// Catalog IDs (used to quickly filter full-text search results)
for quad in ontology
.dataset
.quads_for_pattern(None, Some(GL_CATALOG_ID), None, None)
{
if let NamedOrBlankNodeRef::NamedNode(subject) = quad.subject
&& let TermRef::Literal(literal) = quad.object
{
if literal.datatype() == xsd::POSITIVE_INTEGER {
let value: u64 = literal.value().parse().expect("Failed to parse catalog ID from ontology. It ought to be a positive integer.");
ontology.catalog_ids.insert(subject.into_owned(), value);
}
}
}
}
pub fn build(&mut self) -> error::Result<Ontology> {
let prefixes = PREFIXES
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect::<HashMap<String, String>>();
let mut dataset = Dataset::new();
for (format, bytes) in &self.ontologies {
let quads = RdfParser::from_format(*format)
.for_slice(bytes)
.filter_map(Result::ok);
dataset.extend(quads);
}
Self::materialize_same_as(&mut dataset);
let mut ontology = Ontology {
dataset,
prefixes,
read_only_properties: HashSet::new(),
read_only_classes: HashSet::new(),
field_map: HashMap::new(),
catalog_ids: HashMap::new(),
};
Self::memoize(&mut ontology);
Ok(ontology)
}
}
pub struct Ontology {
dataset: Dataset,
prefixes: HashMap<String, String>,
read_only_properties: HashSet<NamedNode>,
read_only_classes: HashSet<NamedNode>,
field_map: HashMap<NamedNode, String>,
catalog_ids: HashMap<NamedNode, u64>,
}
fn term_to_string(term: &Term) -> Option<String> {
match term {
Term::Literal(literal) => Some(literal.value().to_string()),
_ => None,
}
}
impl Ontology {
pub fn builder<'a>() -> OntologyBuilder<'a> {
OntologyBuilder {
ontologies: Vec::new(),
}
}
pub fn label<'a>(&'a self, node: NamedNodeRef<'a>) -> Option<&'a str> {
let quads = self.dataset.quads_for_pattern(
Some(NamedOrBlankNodeRef::NamedNode(node)),
Some(rdfs::LABEL),
None,
None,
);
for quad in quads {
if let TermRef::Literal(literal) = quad.object {
match literal.language() {
None | Some("en") => return Some(literal.value()),
_ => {}
}
}
}
None
}
pub fn abbreviate(&self, node: NamedNodeRef<'_>) -> String {
for (prefix_name, prefix_iri) in &self.prefixes {
if let Some(local_name) = node.as_str().strip_prefix(prefix_iri) {
return if local_name.is_empty() {
format!("{prefix_name}:")
} else {
format!("{prefix_name}:{local_name}")
};
}
}
node.as_str().to_string()
}
pub fn expand(&self, prefixed_iri: &str) -> Option<NamedNode> {
let (prefix, name) = prefixed_iri.split_once(':')?;
self.prefixes
.get(prefix)
.map(|base| NamedNode::new_unchecked(format!("{base}{name}")))
}
pub fn field_name(&self, property: &NamedNode) -> Option<&String> {
self.field_map.get(property)
}
pub fn catalog_id(&self, class: &NamedNode) -> Option<u64> {
self.catalog_ids.get(class).copied()
}
pub fn for_each_annotated_iri<F>(&self, f: F)
where
F: Fn(&str, Option<&str>, Option<&str>),
{
let query = SparqlEvaluator::new()
.parse_query(
r"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?subject ?label ?comment WHERE {
OPTIONAL {
?subject rdfs:label ?label .
FILTER (LANG(?label) = 'en' || LANG(?label) = '')
}
OPTIONAL {
?subject rdfs:comment ?comment .
FILTER (LANG(?comment) = 'en' || LANG(?comment) = '')
}
}",
)
.expect("Unable to parse annotation query");
if let QueryResults::Solutions(solutions) =
query.on_queryable_dataset(&self.dataset).execute().unwrap()
{
for solution in solutions.filter_map(Result::ok) {
let label = solution.get("label").and_then(term_to_string);
let comment = solution.get("comment").and_then(term_to_string);
if let Some(Term::NamedNode(subject)) = solution.get("subject") {
f(subject.as_str(), label.as_deref(), comment.as_deref());
}
}
}
}
pub fn datatypes(&self) -> impl Iterator<Item = NamedNodeRef<'_>> {
self.dataset
.quads_for_pattern(
None,
Some(rdf::TYPE),
Some(TermRef::NamedNode(rdfs::DATATYPE)),
None,
)
.filter_map(|quad| match quad.subject {
NamedOrBlankNodeRef::NamedNode(subject) => Some(subject),
_ => None,
})
}
pub fn is_read_only<'a>(&self, triple: impl Into<TripleRef<'a>>) -> bool {
let triple = triple.into();
let read_only_property = self
.read_only_properties
.contains(&triple.predicate.into_owned());
let read_only_class = match (triple.predicate, triple.object) {
(rdf::TYPE, TermRef::NamedNode(node)) => {
self.read_only_classes.contains(&node.into_owned())
}
_ => false,
};
read_only_property || read_only_class
}
pub fn exclude_read_only_predicate(&self) -> impl Fn(TripleRef<'_>) -> bool + 'static {
let classes = self.read_only_classes.clone();
let properties = self.read_only_properties.clone();
move |triple| {
let read_only_property = properties.contains(&triple.predicate.into_owned());
let read_only_class = match (triple.predicate, triple.object) {
(rdf::TYPE, TermRef::NamedNode(node)) => classes.contains(&node.into_owned()),
_ => false,
};
!(read_only_property || read_only_class)
}
}
pub fn subclass_of(&self, class: NamedNodeRef<'_>) -> impl Iterator<Item = NamedNode> {
let query = SparqlEvaluator::new()
.parse_query(
format!(
"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?class {{
?class (rdfs:subClassOf|^rdfs:subClassOf)* {class} .
}}"
)
.as_str(),
)
.expect("Unable to parse subclass_of query");
if let QueryResults::Solutions(solutions) =
query.on_queryable_dataset(&self.dataset).execute().unwrap()
{
solutions.filter_map(Result::ok).filter_map(|solution| {
if let Some(Term::NamedNode(class)) = solution.get("class") {
Some(class.clone())
} else {
None
}
})
} else {
unreachable!()
}
}
pub fn template_triples<'a>(
&'a self,
class: NamedNodeRef<'a>,
subject: NamedNodeRef<'_>,
) -> Option<impl Iterator<Item = Triple>> {
if let Some(quad) = self
.dataset
.quads_for_pattern(
Some(NamedOrBlankNodeRef::NamedNode(class)),
Some(GL_TEMPLATE),
None,
None,
)
.next()
{
if let TermRef::BlankNode(blank_node) = quad.object {
let iter = self
.dataset
.quads_for_subject(blank_node)
.map(|quad| quad.into_owned())
.map(Triple::from)
.map(move |mut triple| {
triple.subject = NamedOrBlankNode::NamedNode(subject.into_owned());
triple
});
Some(iter)
} else {
None
}
} else {
None
}
}
}
+168
View File
@@ -0,0 +1,168 @@
use oxigraph::model::vocab::{rdf, xsd};
use oxigraph::model::{BaseDirection, Literal, NamedNode, NamedNodeRef, Term, TermRef};
#[derive(Debug)]
pub struct TermHelper<'a> {
term: &'a Term,
}
impl<'a> TermHelper<'a> {
pub fn new(term: &'a Term) -> Self {
TermHelper { term }
}
pub fn value(&self) -> &str {
match self.term.as_ref() {
TermRef::Literal(literal) => literal.value(),
TermRef::NamedNode(node) => node.as_str(),
_ => unimplemented!(),
}
}
pub fn value_as_named_node(&self) -> Option<NamedNodeRef<'a>> {
match self.term.as_ref() {
TermRef::NamedNode(node) => Some(node),
_ => None,
}
}
pub fn language(&self) -> Option<&str> {
if let TermRef::Literal(literal) = self.term.as_ref() {
literal.language()
} else {
None
}
}
pub fn direction(&self) -> Option<BaseDirection> {
if let TermRef::Literal(literal) = self.term.as_ref() {
literal.direction()
} else {
None
}
}
pub fn datatype(&self) -> Option<NamedNodeRef<'_>> {
if let TermRef::Literal(literal) = self.term.as_ref() {
Some(literal.datatype())
} else {
None
}
}
pub fn is_named_node(&self) -> bool {
self.term.is_named_node()
}
}
#[derive(Debug)]
pub struct TermHelperMut<'a> {
term: &'a mut Term,
}
impl<'a> TermHelperMut<'a> {
pub fn new(term: &'a mut Term) -> Self {
TermHelperMut { term }
}
pub fn set_datatype(&mut self, datatype: Option<NamedNode>) {
let value = match self.term.as_ref() {
TermRef::Literal(literal) => literal.value(),
TermRef::NamedNode(node) => node.as_str(),
_ => unimplemented!(),
};
if let Some(datatype) = datatype {
let language = if let TermRef::Literal(literal) = self.term.as_ref() {
literal.language()
} else {
None
};
let direction = if let TermRef::Literal(literal) = self.term.as_ref() {
literal.direction().unwrap_or(BaseDirection::Ltr)
} else {
BaseDirection::Ltr
};
let language = language.unwrap_or("en");
let literal = match datatype.as_ref() {
rdf::LANG_STRING => Literal::new_language_tagged_literal_unchecked(value, language),
rdf::DIR_LANG_STRING => Literal::new_directional_language_tagged_literal_unchecked(
value, language, direction,
),
xsd::STRING => Literal::new_simple_literal(value),
_ => Literal::new_typed_literal(value, datatype),
};
*self.term = Term::Literal(literal);
} else {
*self.term = Term::NamedNode(NamedNode::new_unchecked(value))
}
}
pub fn set_language(&mut self, language: &str) {
if let TermRef::Literal(literal) = self.term.as_ref() {
let value = literal.value();
let datatype = literal.datatype();
let base_direction = literal.direction();
let new_literal = match (datatype, base_direction) {
(rdf::LANG_STRING, _) => {
Literal::new_language_tagged_literal_unchecked(value, language)
}
(rdf::DIR_LANG_STRING, Some(base_direction)) => {
Literal::new_directional_language_tagged_literal_unchecked(
value,
language,
base_direction,
)
}
(xsd::STRING, _) => Literal::new_simple_literal(value),
_ => Literal::new_typed_literal(value, datatype),
};
*self.term = Term::Literal(new_literal)
}
}
pub fn set_direction(&mut self, direction: BaseDirection) {
let value = match self.term.as_ref() {
TermRef::Literal(literal) => literal.value(),
TermRef::NamedNode(node) => node.as_str(),
_ => unimplemented!(),
};
if let TermRef::Literal(literal) = self.term.as_ref() {
let language = literal.language().unwrap_or("en");
let new_literal = Literal::new_directional_language_tagged_literal_unchecked(
value, language, direction,
);
*self.term = Term::Literal(new_literal);
}
}
pub fn set_value(&mut self, value: String) {
match self.term.as_ref() {
TermRef::Literal(literal) => {
let datatype = literal.datatype();
let language = literal.language();
let base_direction = literal.direction();
let new_literal = match (datatype, language, base_direction) {
(rdf::LANG_STRING, Some(language), _) => {
Literal::new_language_tagged_literal_unchecked(value, language)
}
(rdf::DIR_LANG_STRING, Some(language), Some(base_direction)) => {
Literal::new_directional_language_tagged_literal_unchecked(
value,
language,
base_direction,
)
}
(xsd::STRING, _, _) => Literal::new_simple_literal(value),
_ => Literal::new_typed_literal(value, datatype),
};
*self.term = Term::Literal(new_literal)
}
TermRef::NamedNode(_) => *self.term = Term::NamedNode(NamedNode::new_unchecked(value)),
_ => unimplemented!(),
}
}
}
+3
View File
@@ -0,0 +1,3 @@
//mod search;
//pub use search::{SearchWindow, SearchWindowMessage};
+64
View File
@@ -0,0 +1,64 @@
use iced::{window, Element, Task};
use iced::widget::{column, mouse_area, space, table, text_input};
use tracing::info;
use gl_search::{SearchIndex, SearchIndexBuilder};
use gl_types::CatalogEntryType;
#[derive(Clone)]
pub enum SearchWindowMessage {
QueryInputUpdated(String),
SearchResultSelected(String),
}
pub struct SearchWindow {
window_id: window::Id,
index: SearchIndex,
query_input: String,
results: Vec<String>,
}
impl SearchWindow {
pub fn new(window_id: window::Id) -> Self {
let index = SearchIndex::builder()
.with_path("/tmp/name_index")
.build().expect("Unable to load search index");
Self {
window_id,
index,
query_input: String::new(),
results: vec![],
}
}
pub fn id(&self) -> window::Id {
self.window_id
}
pub fn title(&self) -> String {
"Graph of Liberty Publisher: Search".to_string()
}
pub fn update(&mut self, message: SearchWindowMessage) -> Task<SearchWindowMessage> {
match message {
SearchWindowMessage::QueryInputUpdated(value) => {
self.results = self.index.query(CatalogEntryType::Person, value.as_str()).unwrap();
self.query_input = value;
}
SearchWindowMessage::SearchResultSelected(value) => {
info!(value);
}
}
Task::none()
}
pub fn view(&self) -> Element<'_, SearchWindowMessage> {
let name_column = table::column("Name", |name: &String| {
mouse_area(name.as_str()).on_double_click(SearchWindowMessage::SearchResultSelected(name.clone()))
});
column![
text_input("Query", &self.query_input).on_input(SearchWindowMessage::QueryInputUpdated),
table(vec![name_column], &self.results),
].into()
}
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "gl-search"
version = "0.1.0"
edition = "2024"
publish = false
[dependencies]
tantivy.workspace = true
thiserror.workspace = true
#poppler-rs = "0.26.0-alpha.0"
tracing.workspace = true
#oxidize-pdf = { version = "1.6", features = ["ocr-tesseract"] }
#xml = "1.2"
+95
View File
@@ -0,0 +1,95 @@
use std::io::{BufReader, Read};
use tantivy::{doc, TantivyDocument};
use xml::EventReader;
use xml::reader::XmlEvent;
use crate::Schema;
/// Usage:
/// ```
/// let file = File::open("./EnglishNKJBible.xml")?;
/// let documents = gl_search::BibleXmlConverter::xml_to_tantivy_documents(9, "https://graphofliberty.org/entity/E0", file);
/// let mut output = File::create("./EnglishNKJBible.jsonl")?;
/// for document in documents {
/// let json = document.to_json(Schema::schema());
/// output.write(json.as_bytes())?;
/// output.write("\n".as_bytes())?;
/// }
/// ```
pub struct BibleXmlConverter {}
impl BibleXmlConverter {
pub fn xml_to_tantivy_documents<R: Read>(type_: u64, iri: &str, reader: R) -> Vec<TantivyDocument> {
let file = BufReader::new(reader);
let parser = EventReader::new(file);
let mut documents = vec![];
let mut book: Option<u64> = None;
let mut chapter: Option<u64> = None;
let mut verse: Option<u64> = None;
let mut content: Option<String> = None;
for event in parser {
match event {
Ok(XmlEvent::StartElement { name, attributes, .. }) => {
match name.local_name.as_str() {
"book" => {
for attr in attributes {
if attr.name.local_name == "number" {
book = attr.value.parse().ok();
}
}
},
"chapter" => {
for attr in attributes {
if attr.name.local_name == "number" {
chapter = attr.value.parse().ok();
}
}
},
"verse" => {
for attr in attributes {
if attr.name.local_name == "number" {
verse = attr.value.parse().ok();
}
}
}
_ => {}
}
}
Ok(XmlEvent::Characters(characters)) => {
content = Some(characters);
}
Ok(XmlEvent::EndElement { name }) => {
match name.local_name.as_str() {
"book" => {
book = None;
}
"chapter" => {
chapter = None;
}
"verse" => {
if let Some(book) = book && let Some(chapter) = chapter && let Some(verse) = verse && let Some(ref content) = content {
let document = doc!(
Schema::type_field() => type_,
Schema::iri_field() => iri,
Schema::book_field() => book,
Schema::chapter_field() => chapter,
Schema::verse_field() => verse,
Schema::content_field() => content.as_str(),
);
documents.push(document);
}
verse = None;
}
_ => {}
}
}
Err(e) => {
eprintln!("Error: {e}");
break;
}
_ => {}
}
}
documents
}
}
+17
View File
@@ -0,0 +1,17 @@
use thiserror::Error;
pub type Result<R> = std::result::Result<R, SearchError>;
#[derive(Debug, Error)]
pub enum SearchError {
#[error(transparent)]
TantivyError(#[from] tantivy::TantivyError),
#[error(transparent)]
DocumentParsingError(#[from] tantivy::schema::DocParsingError),
#[error(transparent)]
OpenDirectoryError(#[from] tantivy::directory::error::OpenDirectoryError),
#[error("Path to index not specified")]
IndexPathNotSpecified,
}
+132
View File
@@ -0,0 +1,132 @@
use crate::error;
use crate::error::SearchError;
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, Value};
use tantivy::tokenizer::{NgramTokenizer, TokenizerManager};
use tantivy::{Index, IndexReader, IndexWriter, ReloadPolicy, TantivyDocument, Term, doc};
#[derive(Default)]
pub struct SearchIndexBuilder {
path: Option<PathBuf>,
}
impl SearchIndexBuilder {
pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
self.path = Some(path.into());
self
}
pub fn build(self) -> error::Result<SearchIndex> {
if let Some(path) = self.path {
let ngram_32 = NgramTokenizer::new(1, 32, false)?;
let tokenizer_manager = TokenizerManager::default();
tokenizer_manager.register("ngram_32", ngram_32);
let mmap_directory = MmapDirectory::open(path)?;
let managed_directory = ManagedDirectory::wrap(Box::new(mmap_directory))?;
let index = Index::builder()
.schema(Schema::schema().clone())
.tokenizers(tokenizer_manager)
.open_or_create(managed_directory)?;
let reader = index
.reader_builder()
.reload_policy(ReloadPolicy::OnCommitWithDelay)
.try_into()?;
let writer = index.writer(50_000_000)?;
Ok(SearchIndex {
index,
reader,
writer,
})
} else {
Err(SearchError::IndexPathNotSpecified)
}
}
}
pub struct SearchIndex {
index: Index,
reader: IndexReader,
writer: IndexWriter,
}
impl SearchIndex {
pub fn builder() -> SearchIndexBuilder {
SearchIndexBuilder::default()
}
pub fn remove_all_annotated_iris(&mut self) {
self.writer
.delete_term(Term::from_field_u64(Schema::type_field(), 0u64));
self.writer.commit().unwrap();
}
pub fn add_annotated_iri(
&self,
iri: &str,
label: Option<&str>,
comment: Option<&str>,
) -> crate::Result<()> {
let mut document = doc!(
Schema::type_field() => 0u64,
Schema::iri_field() => iri,
);
if let Some(label) = label {
document.add_text(Schema::label_field(), label.to_lowercase());
}
if let Some(comment) = comment {
document.add_text(Schema::comment_field(), comment);
}
self.writer.add_document(document)?;
Ok(())
}
pub fn add<'a>(&self, document: TantivyDocument) -> crate::Result<()> {
self.writer.add_document(document)?;
Ok(())
}
pub fn commit(&mut self) -> crate::Result<()> {
self.writer.commit()?;
Ok(())
}
pub fn query(
&self,
type_: u64,
user_query: &str,
default_fields: Vec<Field>,
) -> error::Result<Vec<String>> {
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 parser = QueryParser::for_index(&self.index, default_fields);
let (user_query, _) = parser.parse_query_lenient(user_query);
let query = BooleanQuery::new(vec![
(Occur::Must, doc_type_query),
(Occur::Must, user_query),
]);
let searcher = self.reader.searcher();
let results = searcher.search(&query, &TopDocs::with_limit(10000).order_by_score())?;
let mut iris = vec![];
for (_score, address) in results.iter() {
let doc: TantivyDocument = searcher.doc(*address)?;
if let Some(doc_iri) = doc.get_first(Schema::iri_field()) {
let doc_iri_string = doc_iri.as_str().unwrap_or("???").to_string();
iris.push(doc_iri_string);
}
}
Ok(iris)
}
}
+9
View File
@@ -0,0 +1,9 @@
mod error;
mod index;
mod schema;
pub use tantivy::TantivyDocument as SearchDocument;
pub use error::{Result, SearchError};
pub use index::{SearchIndex, SearchIndexBuilder};
pub use schema::Schema;
+26
View File
@@ -0,0 +1,26 @@
use std::path::Path;
use tantivy::{doc, TantivyDocument};
use tracing::info;
use gl_types::CatalogEntryType;
use crate::Schema;
pub struct PdfTextExtractor {}
impl PdfTextExtractor {
pub fn process<P: AsRef<Path>>(path: P) -> Vec<TantivyDocument> {
let document = poppler::Document::from_file(path.as_ref().to_str().unwrap(), None).unwrap();
let mut tantivy_documents = vec![];
for page in 0..document.n_pages() {
info!("Processing page {}", page);
let page = document.page(page).unwrap();
let text = page.text().unwrap();
let clean_text = text.as_str().replace("-\n", "").replace("\n", " ");
let document = doc!(
Schema::type_field() => u64::from(CatalogEntryType::AudioBook),
Schema::content_field() => clean_text.as_str(),
);
tantivy_documents.push(document);
}
tantivy_documents
}
}
+77
View File
@@ -0,0 +1,77 @@
use std::sync::OnceLock;
use tantivy::schema;
use tantivy::schema::{
Field, IndexRecordOption, Schema as TantivySchema, TextFieldIndexing, TextOptions,
};
static SCHEMA: OnceLock<TantivySchema> = OnceLock::new();
pub struct Schema;
impl Schema {
pub fn schema() -> &'static TantivySchema {
SCHEMA.get_or_init(|| {
let ngram_32 = TextOptions::default().set_indexing_options(
TextFieldIndexing::default()
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
.set_tokenizer("ngram_32"),
);
let en_stem = TextOptions::default().set_indexing_options(
TextFieldIndexing::default()
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
.set_tokenizer("en_stem"),
);
let mut schema_builder = TantivySchema::builder();
schema_builder.add_u64_field("type", schema::FAST | schema::INDEXED);
schema_builder.add_text_field("iri", schema::STORED | schema::STRING);
schema_builder.add_text_field("label", ngram_32.clone());
schema_builder.add_text_field("comment", en_stem.clone());
schema_builder.add_text_field("given name", ngram_32.clone());
schema_builder.add_text_field("surname", ngram_32);
schema_builder.add_text_field("title", en_stem.clone());
schema_builder.add_text_field("description", en_stem.clone());
schema_builder.add_text_field("content", en_stem);
schema_builder.add_u64_field("page", schema::STORED);
schema_builder.add_u64_field("book", schema::STORED);
schema_builder.add_u64_field("chapter", schema::STORED);
schema_builder.add_u64_field("verse", schema::STORED);
schema_builder.build()
})
}
pub fn type_field() -> Field {
Self::schema().get_field("type").unwrap()
}
pub fn iri_field() -> Field {
Self::schema().get_field("iri").unwrap()
}
pub fn label_field() -> Field {
Self::schema().get_field("label").unwrap()
}
pub fn comment_field() -> Field {
Self::schema().get_field("comment").unwrap()
}
pub fn ontology_fields() -> Vec<Field> {
vec![Self::label_field(), Self::comment_field()]
}
pub fn default_fields() -> Vec<Field> {
vec![
Self::schema().get_field("given name").unwrap(),
Self::schema().get_field("surname").unwrap(),
Self::schema().get_field("title").unwrap(),
Self::schema().get_field("description").unwrap(),
Self::schema().get_field("content").unwrap(),
]
}
}