Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
23cd459
RDF/OFN reader+writer fidelity fixes, O(N) list stitching, and FxHash…
jamesamcl Jun 22, 2026
14a8e71
io/rdf, ontology/set: use a wasm-safe clock (web-time)
jamesamcl Jun 23, 2026
f221723
time: build and run on wasm64 (host clock; indexmap std)
claude Jun 29, 2026
130d8f4
Merge pull request #1 from EBISPOT/claude/wasm64-deps
jamesamcl Jun 29, 2026
ed027d6
Emit ROBOT/OWLAPI-style grouped functional syntax from the OFN writer
jamesamcl Jul 15, 2026
46255a8
OFN reader: accept W3C LANGTAG and ROBOT/OWLAPI bare anonymous node ids
jamesamcl Jul 16, 2026
d8e6a1f
RDF/XML: type the whole entity signature, not just declared entities
jamesamcl Jul 22, 2026
322c788
OFN writer: reproduce OWLAPI's FunctionalSyntaxObjectRenderer byte-fo…
jamesamcl Jul 23, 2026
73b7acf
rdf reader: lenient parsing for OWLAPI-legal-but-strict inputs
jamesamcl Jul 24, 2026
05024b4
OWLAPI-parity OFN/RDF/OBO writers, lenient parsing, wasm, FxHash
jamesamcl Aug 2, 2026
91e46ac
RDF/XML writer: inline a collection that is referenced more than once
jamesamcl Aug 2, 2026
97cc277
OFN writer: order like OWLAPI's compareTo, not horned's derived Ord
jamesamcl Aug 2, 2026
e0a663c
OFN writer: leave xsd:string implicit, as OWLAPI does
jamesamcl Aug 3, 2026
ae3cd48
OFN writer: optional explicit ^^xsd:string
jamesamcl Aug 3, 2026
a904de9
build: let the workspace resolve, and its tests compile
jamesamcl Aug 4, 2026
231ac22
rdf: a class expression handed to an axiom is not an incomplete parse
jamesamcl Aug 4, 2026
7503532
ofn: order the declaration block by OWLAPI's type index
jamesamcl Aug 4, 2026
b6f2cab
ofn: separate a SWRL rule's annotations and atoms with a space
jamesamcl Aug 4, 2026
c27ee4a
ofn: group entity sections by signature and add missing declarations
jamesamcl Aug 7, 2026
1a348bc
Order OFN entity sections the way OWLAPI orders IRIs
jamesamcl Aug 7, 2026
1e08c8b
Read the abbreviated IRIs ROBOT writes, and order assertions as OWLAP…
claude Aug 8, 2026
2596d58
Pick the banner label OWLAPI reaches first, and order SWRL rules
claude Aug 8, 2026
fa01496
A two-atom SWRL body comes out in the opposite order
claude Aug 8, 2026
d040b93
Break a banner-label bucket tie by the ontology-wide insertion order
claude Aug 8, 2026
c5d1936
Revert the ontology-wide banner tie-break: the evidence contradicts it
claude Aug 8, 2026
f25c44c
An untyped literal is xsd:string once the document has been through Jena
claude Aug 10, 2026
d9a38ce
Compare IRIs at the NCName suffix, and annotations by OWLAPI's keys
claude Aug 11, 2026
30e7e18
Write a punned entity's annotations under its first section
claude Aug 11, 2026
a1e2d64
State the banner tie-break's instability as behaviour
claude Aug 11, 2026
d7dc175
Order an entity's class axioms by OWLAPI's class-expression index
claude Aug 11, 2026
ed4bd07
Keep a data range sequence readable until all its members resolve
jamesamcl Aug 18, 2026
d5afbac
An abbreviated IRI's local part starts with a name start character
claude Aug 18, 2026
d9d301e
The whole abbreviation rule, the OWL 2 datatype map, and a literal's …
claude Aug 18, 2026
676fbac
Restore the base triple of a reification the document leaves unstated
claude Aug 18, 2026
d1d00b1
A restored triple is merged into the document's order, not sorted int…
claude Aug 18, 2026
c3bfe45
The line break after `Ontology(` belongs to the ontology IRI
claude Aug 18, 2026
f224d4f
An anonymous individual may say more than one thing about itself
claude Aug 18, 2026
556150e
A bare node id must be a whole token, not the head of a longer one
claude Aug 18, 2026
1b2d963
A blank node is an individual because a class types it
claude Aug 18, 2026
58659e2
A document's blank nodes carry the ids it was numbered with
claude Aug 19, 2026
099e3d7
Number the individuals after the nodes, in the order the document men…
claude Aug 19, 2026
38010ad
Read the count out before writing it back
claude Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 1 addition & 4 deletions .github/workflows/run-precommit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ jobs:
with:
distribution: 'zulu' # See 'Supported distributions' for available options
java-version: '21'
- run: |
mkdir --parents $HOME/.local/bin
wget https://github.com/phillord/tawny-bubo/releases/download/0.3.2/bubo -O $HOME/.local/bin/bubo
chmod +x $HOME/.local/bin/bubo
- run: make fetch_bubo
shell: bash
- run: python -m pip install pre-commit
shell: bash
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ Cargo.lock

# IDEs
.vscode/
.claude/

# Mac OS X
.DS_Store
/dev/bubo-0.4.0
/flamegraph.svg
/perf.data
/perf.data.old
.cargo-ok
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ repos:
args: ["--check", "--"]
pass_filenames: false
- id: clippy
args: ["--workspace", "--all-targets"]
pass_filenames: false
- repo: local
hooks:
Expand Down
34 changes: 25 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,29 @@ indexmap={workspace=true}
oxiri={workspace=true}
pest = "2.7.8"
pest_derive = "2.7.8"
pretty_rdf={workspace=true}
horned-pretty-rdf={workspace=true}
horned-catalog={workspace=true}
rustc-hash = "2"
oxrdf={workspace=true}
oxrdfio={workspace=true}
ureq={version="2.1.1", optional=true}
ureq={version="3.3.0", optional=true}

[workspace]
members=["horned-bin"]
default-members=[".", "horned-bin"]
members=["horned-bin", "horned-pretty-rdf", "horned-catalog", "horned-macro"]
default-members=[".", "horned-bin", "horned-pretty-rdf", "horned-catalog", "horned-macro"]

[workspace.dependencies]
indexmap="1.0.2"
# `features = ["std"]` is required for tier-3 wasm targets (e.g. wasm64-unknown-
# unknown): indexmap 1.x has no default features and only exposes the RandomState
# default + `IndexMap::new()` when its build script sees `CARGO_FEATURE_STD`;
# otherwise it falls back to an autocfg sysroot probe that fails where there is no
# prebuilt std, compiling no-std. Harmless elsewhere (std is on there anyway).
indexmap = { version = "1.0.2", features = ["std"] }
mktemp="0.5.1"
oxiri="0.2.11"
##pretty_rdf={path="./pretty_rdf"}
pretty_rdf="0.11.0"
horned-pretty-rdf={path="./horned-pretty-rdf", version="2.0.0"}
horned-catalog={path="./horned-catalog", version="0.1.0"}
horned-macro={path="./horned-macro", version="0.1.0"}
oxrdf="0.3.0"
oxrdfio="0.2.0"

Expand All @@ -48,13 +57,15 @@ remote = ["ureq"]
encoding = ["quick-xml/encoding"]

[dev-dependencies]
proptest = "1"
horned-owl = {path=".", features = ["remote"]}
criterion = "0.7.0"
mktemp = "0.5.1"
mktemp = {workspace = true}
pretty_assertions = "1.0.0"
slurp = "1.0.1"
test-generator = { version = "^0.3" }
rstest = "0.23"
horned-bin = {path="horned-bin"}
horned-macro = {workspace = true}

[profile.release]
debug = true
Expand All @@ -63,3 +74,8 @@ debug = true
name = "horned"
harness = false


# web-time: a drop-in `std::time::Instant` that works on wasm (where the std
# clock traps). Used via the `crate::time` shim (src/lib.rs).
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-time = "1"
15 changes: 13 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ bench-prepare:


## Build the Unit test Ontology code
##
## `test_resources` (the test-generator proc-macro driving src/ont/*
## fixture discovery) globs those directories at compile time with
## nothing telling Cargo to invalidate the build when files there
## change, so a stale test binary can silently miss new/changed/removed
## fixtures. `cargo clean` unconditionally after bubo runs so the next
## `cargo test` always sees current fixtures.
just-bubo:
$(MAKE) -C src/ont/bubo

bubo: just-bubo clean test
bubo: just-bubo test

clean:
cargo clean
Expand Down Expand Up @@ -112,9 +119,13 @@ triples-round-all:
done

clippy:
cargo clippy
cargo clippy --workspace --all-targets

install:
cargo install --path horned-bin

fetch_bubo:
wget https://github.com/phillord/tawny-bubo/releases/download/0.4.0/bubo-0.4.0 -O dev/bubo-0.4.0
chmod +x dev/bubo-0.4.0

-include makefile-local
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ with millions of terms.
+ [x] RDF/XML
+ [x] OWL/XML
+ [x] Functional Syntax
+ [ ] Manchester Syntax
+ [x] Manchester Syntax
+ A [visitor](https://en.wikipedia.org/wiki/Visitor_pattern) trait to navigate and manipulate ontologies
+ Traits and implementations for several types of ontologies

Expand All @@ -34,7 +34,7 @@ To use the latest version of the library in your Rust project, add the following
```toml
[dependencies]
...
horned-owl = "1.0.0"
horned-owl = "2.1.0"
```


Expand Down
10 changes: 7 additions & 3 deletions benches/horned.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use criterion::criterion_main;

mod io;
mod io_read;
mod io_write;
mod iteration;
mod model;

use crate::io::io;
use crate::io_read::io_read;
use crate::io_write::io_write;
use crate::iteration::iteration;
use crate::model::model;

criterion_main!(model, io);
criterion_main!(model, io_read, io_write, iteration);
7 changes: 3 additions & 4 deletions benches/io.rs → benches/io_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::fs::{File, create_dir_all};
use std::io::BufReader;
use std::time::Duration;

fn io_read(c: &mut Criterion) {
fn bench_io_read(c: &mut Criterion) {
let mut group = c.benchmark_group("io_read");
group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));

Expand Down Expand Up @@ -58,7 +58,6 @@ fn io_read(c: &mut Criterion) {
ParserConfiguration {
rdf: RDFParserConfiguration {
format: Some(oxrdfio::RdfFormat::Turtle),
..Default::default()
},
..Default::default()
},
Expand All @@ -70,9 +69,9 @@ fn io_read(c: &mut Criterion) {
}

criterion_group! {
name = io;
name = io_read;
config = Criterion::default()
.sample_size(50)
.measurement_time(Duration::from_secs(20));
targets = io_read
targets = bench_io_read
}
81 changes: 81 additions & 0 deletions benches/io_write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use criterion::{AxisScale, BenchmarkId, Criterion, PlotConfiguration, criterion_group};
use horned_owl::model::{Build, MutableOntology, RcStr};
use horned_owl::ontology::component_mapped::RcComponentMappedOntology;
use horned_owl::ontology::set::SetOntology;
use horned_pretty_rdf::ox::WriterQuadSerializerAdaptor;
use oxrdfio::RdfSerializer;
use std::io::sink;
use std::time::Duration;

fn build_ontology(n: isize) -> RcComponentMappedOntology {
let b = Build::new_rc();
let mut o: SetOntology<RcStr> = SetOntology::new_rc();
for i in 1..=n {
o.declare(b.class(format!("https://www.example.com/o{}", i)));
}
o.into()
}

fn bench_io_write(c: &mut Criterion) {
let mut group = c.benchmark_group("io_write");
group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));

for n in [10, 100, 1_000, 2500, 5000, 10_000].iter() {
let ont = build_ontology(*n);

// RDF/XML: pretty formatter (horned-pretty-rdf)
group.bench_function(BenchmarkId::new("rdf_xml_pretty_io_write", n), |b| {
b.iter(|| {
horned_owl::io::rdf::writer::write(sink(), &ont).ok();
})
});

// RDF/XML: plain oxrdfio serializer
group.bench_function(BenchmarkId::new("rdf_xml_plain_io_write", n), |b| {
b.iter(|| {
let f = WriterQuadSerializerAdaptor::new(
RdfSerializer::from_format(oxrdfio::RdfFormat::RdfXml).for_writer(sink()),
);
horned_owl::io::rdf::writer::write_to_rdf_formatter(&ont, f).ok();
})
});

group.bench_function(BenchmarkId::new("ttl_io_write", n), |b| {
b.iter(|| {
horned_owl::io::rdf::writer::write_to_rdf_format(sink(), &ont, "ttl").ok();
})
});

group.bench_function(BenchmarkId::new("nt_io_write", n), |b| {
b.iter(|| {
horned_owl::io::rdf::writer::write_to_rdf_format(sink(), &ont, "nt").ok();
})
});

group.bench_function(BenchmarkId::new("owx_io_write", n), |b| {
b.iter(|| {
horned_owl::io::owx::writer::write(sink(), &ont, None).ok();
})
});

group.bench_function(BenchmarkId::new("ofn_io_write", n), |b| {
b.iter(|| {
horned_owl::io::ofn::writer::write(sink(), &ont, None).ok();
})
});

group.bench_function(BenchmarkId::new("omn_io_write", n), |b| {
b.iter(|| {
horned_owl::io::omn::writer::write(sink(), &ont, None).ok();
})
});
}
}

criterion_group! {
name = io_write;
config = Criterion::default()
.sample_size(50)
.measurement_time(Duration::from_secs(20));
targets = bench_io_write
}
Loading