1use horned_owl::{
4 error::HornedError,
5 io::{ParserConfiguration, ParserOutput, ResourceType},
6 model::{Build, ForIRI, IRI, MutableOntology, OntologyID, RcAnnotatedComponent, RcStr},
7 ontology::{
8 component_mapped::{ComponentMappedOntology, RcComponentMappedOntology},
9 indexed::ForIndex,
10 set::SetOntology,
11 },
12 resolve::{localize_iri_favored, path_to_file_iri, strict_resolve_iri},
13};
14
15use std::{
16 fs::File,
17 io::{BufReader, Write as StdWrite},
18 path::{Path, PathBuf},
19 str::FromStr,
20};
21
22pub mod error {
23 use super::*;
24
25 pub fn error_missing_input() -> HornedError {
26 HornedError::CommandError("Command requires an INPUT parameter".to_string())
27 }
28}
29
30pub fn write<A: ForIRI, AA: ForIndex<A>, W: StdWrite>(
31 format: &str,
32 write: W,
33 ont: &ComponentMappedOntology<A, AA>,
34) -> Result<W, HornedError> {
35 match format {
36 "owx" => horned_owl::io::owx::writer::write(write, ont, None),
37 "ofn" => horned_owl::io::ofn::writer::write(write, ont, None),
38 "owl" | "ttl" => horned_owl::io::rdf::writer::write_to_rdf_format(write, ont, format),
39
40 _ => Err(HornedError::CommandError(format!(
41 "Format is unknown: {format}"
42 ))),
43 }
44}
45
46pub fn path_type(path: &Path) -> Option<ResourceType> {
47 match path.extension().and_then(|s| s.to_str()) {
48 Some("ofn") => Some(ResourceType::OFN),
49 Some("owx") => Some(ResourceType::OWX),
50 Some("owl") => Some(ResourceType::RDF),
51 _ => None,
52 }
53}
54
55pub fn parse_path(
56 path: &Path,
57 config: ParserConfiguration,
58) -> Result<ParserOutput<RcStr, RcAnnotatedComponent>, HornedError> {
59 Ok(match path_type(path) {
60 Some(ResourceType::OFN) => {
61 let file = File::open(path)?;
62 let mut bufreader = BufReader::new(file);
63 ParserOutput::ofn(horned_owl::io::ofn::reader::read(&mut bufreader, config)?)
64 }
65 Some(ResourceType::OWX) => {
66 let file = File::open(path)?;
67 let mut bufreader = BufReader::new(file);
68 ParserOutput::owx(horned_owl::io::owx::reader::read(&mut bufreader, config)?)
69 }
70 Some(ResourceType::RDF) => {
71 let b = Build::new();
72 let iri = horned_owl::resolve::path_to_file_iri(&b, path);
73 ParserOutput::rdf(horned_owl::io::rdf::closure_reader::read(&iri, config)?)
74 }
75 None => {
76 return Err(HornedError::CommandError(format!(
77 "Cannot parse a file of this format: {path:?}"
78 )));
79 }
80 })
81}
82
83pub fn parse_imports(
85 path: &Path,
86 config: ParserConfiguration,
87) -> Result<ParserOutput<RcStr, RcAnnotatedComponent>, HornedError> {
88 let file = File::open(path)?;
89 let mut bufreader = BufReader::new(file);
90 Ok(match path_type(path) {
91 Some(ResourceType::OFN) => {
92 ParserOutput::ofn(horned_owl::io::owx::reader::read(&mut bufreader, config)?)
93 }
94 Some(ResourceType::OWX) => {
95 ParserOutput::owx(horned_owl::io::owx::reader::read(&mut bufreader, config)?)
96 }
97 Some(ResourceType::RDF) => {
98 let b = Build::new();
99 let mut p = horned_owl::io::rdf::reader::parser_with_build(&mut bufreader, &b, config);
100 p.parse_imports()?;
101 ParserOutput::rdf(p.as_ontology_and_incomplete())
102 }
103 None => {
104 return Err(HornedError::CommandError(format!(
105 "Cannot parse a file of this format: {path:?}"
106 )));
107 }
108 })
109}
110
111pub fn materialize(
112 file_or_iri: &str,
113 config: ParserConfiguration,
114) -> Result<Vec<IRI<RcStr>>, HornedError> {
115 let mut v = vec![];
116 let b = Build::new();
117
118 let parsed = oxiri::Iri::parse(file_or_iri);
120
121 let file_pathbuf = match parsed {
126 Result::Ok(_) => ensure_local(&b.iri(file_or_iri), None)?,
127 Result::Err(_) => PathBuf::from_str(file_or_iri).expect("Result is infallable"),
128 };
129
130 materialize_1(&file_pathbuf, config, &mut v, true)?;
131 Ok(v)
132}
133
134fn ensure_local(
135 iri: &IRI<RcStr>,
136 relative_doc_iri: Option<&IRI<RcStr>>,
137) -> Result<PathBuf, HornedError> {
138 let local_path = localize_iri_favored(iri, relative_doc_iri);
139
140 if !local_path.exists() {
141 println!("Retrieving Ontology: {}", iri);
142 let imported_data = strict_resolve_iri(iri)?;
143 println!("Saving to {}", local_path.display());
144 let mut file = File::create(&local_path)?;
145 file.write_all(imported_data.as_bytes())?;
146 } else {
147 println!("Already Present: {}", local_path.display());
148 }
149 Ok(local_path)
150}
151
152fn materialize_1<'a>(
153 file_location: &PathBuf,
154 config: ParserConfiguration,
155 done: &'a mut Vec<IRI<RcStr>>,
156 recurse: bool,
157) -> Result<&'a mut Vec<IRI<RcStr>>, HornedError> {
158 println!("Parsing: {}", file_location.display());
159 let amont: RcComponentMappedOntology = parse_imports(Path::new(file_location), config)?.into();
160 let import = amont.i().import();
161
162 let b = Build::new_rc();
163 let doc_iri = path_to_file_iri(&b, file_location.as_path());
164 for i in import {
166 if !done.contains(&i.0) {
167 done.push(i.0.clone());
168 let local_path = ensure_local(&i.0, Some(&doc_iri))?;
169
170 if recurse {
171 materialize_1(&local_path, config, done, true)?;
172 }
173 } else {
174 println!("Already materialized: {}", &i.0);
175 }
176 }
177
178 Ok(done)
179}
180
181pub fn generate_big_owl<W: StdWrite>(size: isize, format: &str, w: W) -> Result<W, HornedError> {
182 let b = Build::new_rc();
183 let mut o = SetOntology::new_rc();
184
185 o.insert(OntologyID {
186 iri: Some(b.iri("http://www.example.com/iri")),
187 viri: None,
188 });
189
190 for i in 1..size + 1 {
191 o.declare(b.class(format!("https://www.example.com/o{}", i)));
192 }
193
194 let amo: RcComponentMappedOntology = o.into();
195 write(format, w, &amo)
196}
197
198pub mod naming {
199 use horned_owl::model::ComponentKind;
200 use horned_owl::model::ComponentKind::*;
201
202 pub fn name(axk: &ComponentKind) -> &'static str {
203 match axk {
204 OntologyID => "Ontology ID",
205 DocIRI => "Doc IRI",
206 OntologyAnnotation => "Ontology Annotation",
207 Import => "Import",
208 DeclareClass => "Declare Class",
209 DeclareObjectProperty => "Declare Object Property",
210 DeclareAnnotationProperty => "Declare Annotation Property",
211 DeclareDataProperty => "Declare Data Property",
212 DeclareNamedIndividual => "Declare Named Individual",
213 DeclareDatatype => "Declare Datatype",
214 SubClassOf => "Sub-Class Of",
215 EquivalentClasses => "Equivalent Classes",
216 DisjointClasses => "Disjoint Classes",
217 DisjointUnion => "Disjoint Union",
218 SubObjectPropertyOf => "Sub Object Property Of",
219 EquivalentObjectProperties => "Equivalent Object Properties",
220 DisjointObjectProperties => "Disjoint Object Properties",
221 InverseObjectProperties => "Inverse Object Properties",
222 ObjectPropertyDomain => "Object Property Domain",
223 ObjectPropertyRange => "Object Property Range",
224 FunctionalObjectProperty => "Functional Object Property",
225 InverseFunctionalObjectProperty => "Inverse Functional Object Property",
226 ReflexiveObjectProperty => "Reflexive Object Property",
227 IrreflexiveObjectProperty => "Irreflexive Object Property",
228 SymmetricObjectProperty => "Symmetric Object Property",
229 AsymmetricObjectProperty => "Asymmetric Object Property",
230 TransitiveObjectProperty => "Transitive Object Property",
231 SubDataPropertyOf => "Sub Data Property Of",
232 EquivalentDataProperties => "Equivalent Data Properties",
233 DisjointDataProperties => "Disjoint Data Properties",
234 DataPropertyDomain => "Data Property Domain",
235 DataPropertyRange => "Data Property Range",
236 FunctionalDataProperty => "Functional Data Property",
237 DatatypeDefinition => "Datatype Definition",
238 HasKey => "Has Key",
239 SameIndividual => "Same Individual",
240 DifferentIndividuals => "Different Individuals",
241 ClassAssertion => "Class Assertion",
242 ObjectPropertyAssertion => "Object Property Assertion",
243 NegativeObjectPropertyAssertion => "Negative Object Property Assertion",
244 DataPropertyAssertion => "Data Property Assertion",
245 NegativeDataPropertyAssertion => "Negative Data Property Assertion",
246 AnnotationAssertion => "Annotation Assertion",
247 SubAnnotationPropertyOf => "Sub Annotation Property Of",
248 AnnotationPropertyDomain => "Annotation Property Domain",
249 AnnotationPropertyRange => "Annotation Property Range",
250 Rule => "Rule",
251 }
252 }
253}
254
255pub mod validation {
256 use horned_owl::{io::rdf::reader::IncompleteParse, model::ForIRI};
257
258 pub fn write_incomplete<T: ForIRI>(incomplete: IncompleteParse<T>) {
259 println!("\n\nIncompleted Parsed");
260 println!("\tSimple Triples: {:#?}", incomplete.simple);
261 println!("\tbnode: {:#?}", incomplete.bnode);
262 println!("\tsequences: {:#?}", incomplete.bnode_seq);
263 println!("\tClass Expressions: {:#?}", incomplete.class_expression);
264 println!(
265 "\tObject Property Expressions: {:#?}",
266 incomplete.object_property_expression
267 );
268 println!("\tData Range: {:#?}", incomplete.data_range);
269 println!("\tAnnotations: {:#?}", incomplete.ann_map);
270 }
271}
272
273pub mod summary {
274
275 use horned_owl::{
276 model::{ComponentKind, HigherKinded},
277 ontology::component_mapped::RcComponentMappedOntology,
278 };
279 use indexmap::map::IndexMap;
280
281 #[derive(Debug)]
282 pub struct SummaryStatistics {
283 pub logical_axiom: usize,
284 pub annotation_axiom: usize,
285 pub meta_comp: usize,
286 pub axiom_type: IndexMap<ComponentKind, usize>,
287 }
288
289 impl SummaryStatistics {
290 pub fn with_axiom_types(&self) -> impl Iterator<Item = (&ComponentKind, &usize)> + '_ {
291 self.axiom_type.iter().filter(|&(_, v)| v > &0)
292 }
293 }
294
295 pub fn summarize<O: Into<RcComponentMappedOntology>>(ont: O) -> SummaryStatistics
296 where
297 O:,
298 {
299 let ont: RcComponentMappedOntology = ont.into();
300 SummaryStatistics {
301 logical_axiom: ont.i().iter().filter(|c| c.is_axiom()).count(),
302 annotation_axiom: ont.i().iter().map(|aa| aa.ann.len()).sum::<usize>(),
303 meta_comp: ont.i().iter().filter(|c| c.is_meta()).count(),
304 axiom_type: axiom_types(ont),
305 }
306 }
307
308 fn axiom_types<O: Into<RcComponentMappedOntology>>(ont: O) -> IndexMap<ComponentKind, usize> {
309 let ont: RcComponentMappedOntology = ont.into();
310 let mut im = IndexMap::new();
311 for ax in ComponentKind::all_kinds() {
312 im.insert(ax, ont.i().component(ax).count());
313 }
314
315 im
316 }
317}
318
319pub mod config {
320 use clap::App;
321 use clap::ArgAction;
322 use clap::ArgMatches;
323 use horned_owl::io::ParserConfiguration;
324 use horned_owl::io::RDFParserConfiguration;
325
326 pub fn parser_app(app: App<'static>) -> App<'static> {
327 app.arg(
328 clap::arg!(--"lax")
329 .required(false)
330 .action(ArgAction::SetTrue)
331 .help("Parse RDF in a lax manner"),
332 )
333 }
334
335 pub fn parser_config(matches: &ArgMatches) -> ParserConfiguration {
336 ParserConfiguration {
337 rdf: RDFParserConfiguration {
338 lax: *matches.get_one::<bool>("lax").unwrap_or(&false),
339 format: None,
340 },
341 ..Default::default()
342 }
343 }
344}