1use crate::manchester::class_expression_to_manchester;
2use crate::span::{annotate_spans, find_entity_block, short_name_from_iri};
3use horned_owl::model::{
4 AnnotatedComponent, AnnotationSubject, AnnotationValue, ClassExpression, Component,
5 ComponentKind, Individual, ObjectPropertyExpression, PropertyExpression, RcAnnotatedComponent,
6 RcStr, SubObjectPropertyExpression,
7};
8use horned_owl::ontology::component_mapped::{ComponentMappedIndex, ComponentMappedOntology};
9use std::collections::BTreeMap;
10use strixonomy_core::{
11 Annotation, Axiom, AxiomAnnotation, Entity, EntityKind, Import, Namespace, SourceLocation,
12 AXIOM_KIND_CLASS_ASSERTION, AXIOM_KIND_DATATYPE_DEFINITION, AXIOM_KIND_DATA_PROPERTY_ASSERTION,
13 AXIOM_KIND_DIFFERENT_INDIVIDUALS, AXIOM_KIND_DISJOINT_CLASS,
14 AXIOM_KIND_DISJOINT_DATA_PROPERTIES, AXIOM_KIND_DISJOINT_OBJECT_PROPERTIES,
15 AXIOM_KIND_DISJOINT_UNION, AXIOM_KIND_DOMAIN, AXIOM_KIND_EQUIVALENT_CLASS,
16 AXIOM_KIND_EQUIVALENT_DATA_PROPERTIES, AXIOM_KIND_EQUIVALENT_OBJECT_PROPERTIES,
17 AXIOM_KIND_HAS_KEY, AXIOM_KIND_INVERSE_OBJECT_PROPERTIES,
18 AXIOM_KIND_NEGATIVE_DATA_PROPERTY_ASSERTION, AXIOM_KIND_NEGATIVE_OBJECT_PROPERTY_ASSERTION,
19 AXIOM_KIND_OBJECT_PROPERTY_ASSERTION, AXIOM_KIND_PROPERTY_CHAIN, AXIOM_KIND_RANGE,
20 AXIOM_KIND_SAME_INDIVIDUAL, AXIOM_KIND_SUB_CLASS_OF, AXIOM_KIND_SUB_DATA_PROPERTY_OF,
21 AXIOM_KIND_SUB_OBJECT_PROPERTY_OF,
22};
23
24const RDFS_LABEL: &str = "http://www.w3.org/2000/01/rdf-schema#label";
25const RDFS_COMMENT: &str = "http://www.w3.org/2000/01/rdf-schema#comment";
26const RDFS_SUB_CLASS_OF: &str = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
27const RDFS_SUB_PROPERTY_OF: &str = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf";
28const OWL_DEPRECATED: &str = "http://www.w3.org/2002/07/owl#deprecated";
29const OWL_SAME_AS: &str = "http://www.w3.org/2002/07/owl#sameAs";
30const OWL_DIFFERENT_FROM: &str = "http://www.w3.org/2002/07/owl#differentFrom";
31const OWL_HAS_KEY: &str = "http://www.w3.org/2002/07/owl#hasKey";
32const OWL_DISJOINT_UNION_OF: &str = "http://www.w3.org/2002/07/owl#disjointUnionOf";
33const OWL_INVERSE_OF: &str = "http://www.w3.org/2002/07/owl#inverseOf";
34const OWL_EQUIVALENT_PROPERTY: &str = "http://www.w3.org/2002/07/owl#equivalentProperty";
35const OWL_PROPERTY_DISJOINT_WITH: &str = "http://www.w3.org/2002/07/owl#propertyDisjointWith";
36const OWL_EQUIVALENT_CLASS: &str = "http://www.w3.org/2002/07/owl#equivalentClass";
37const MEMBER_SEP: &str = " | ";
38
39#[derive(Debug, Clone, Default)]
41pub struct OwlBridgeResult {
42 pub entities: Vec<Entity>,
43 pub annotations: Vec<Annotation>,
44 pub axioms: Vec<Axiom>,
45 pub imports: Vec<Import>,
46 pub namespace_rows: Vec<Namespace>,
47 pub base_iri: Option<String>,
48}
49
50pub fn bridge_ontology(
51 ontology: impl Into<ComponentMappedOntology<RcStr, RcAnnotatedComponent>>,
52 ontology_id: &str,
53 source_text: &str,
54 namespaces: &BTreeMap<String, String>,
55) -> OwlBridgeResult {
56 let mapped = ontology.into();
57 let idx: &ComponentMappedIndex<RcStr, RcAnnotatedComponent> = mapped.i();
58
59 let mut result = OwlBridgeResult::default();
60 let ont_id = idx
61 .the_ontology_id()
62 .and_then(|id| id.iri.as_ref().map(|iri| iri.to_string()))
63 .unwrap_or_else(|| ontology_id.to_string());
64 result.base_iri = Some(ont_id.clone());
65
66 let mut entity_map: BTreeMap<String, Entity> = BTreeMap::new();
67
68 for decl in idx.declare_class() {
69 insert_entity(&mut entity_map, decl.0.to_string(), EntityKind::Class, &ont_id);
70 }
71 for decl in idx.declare_object_property() {
72 insert_entity(&mut entity_map, decl.0.to_string(), EntityKind::ObjectProperty, &ont_id);
73 }
74 for decl in idx.declare_data_property() {
75 insert_entity(&mut entity_map, decl.0.to_string(), EntityKind::DataProperty, &ont_id);
76 }
77 for decl in idx.declare_annotation_property() {
78 insert_entity(&mut entity_map, decl.0.to_string(), EntityKind::AnnotationProperty, &ont_id);
79 }
80 for decl in idx.declare_named_individual() {
81 insert_entity(&mut entity_map, decl.0.to_string(), EntityKind::Individual, &ont_id);
82 }
83 for decl in idx.declare_datatype() {
84 insert_entity(&mut entity_map, decl.0.to_string(), EntityKind::Datatype, &ont_id);
85 }
86
87 for ann_ax in idx.annotation_assertion() {
88 let subject = annotation_subject_iri(&ann_ax.subject);
89 let predicate = ann_ax.ann.ap.to_string();
90 let object = annotation_value_string(&ann_ax.ann.av);
91 if predicate == RDFS_LABEL {
92 entity_map.entry(subject.clone()).and_modify(|e| e.labels.push(object.clone()));
93 } else if predicate == RDFS_COMMENT {
94 entity_map.entry(subject.clone()).and_modify(|e| e.comments.push(object.clone()));
95 } else if predicate == OWL_DEPRECATED
96 && strixonomy_core::parse_boolean_literal(&object) == Some(true)
97 {
98 entity_map.entry(subject.clone()).and_modify(|e| e.deprecated = true);
99 }
100 result.annotations.push(Annotation {
101 subject,
102 predicate,
103 object,
104 ontology_id: ont_id.clone(),
105 source_location: SourceLocation::default(),
106 });
107 }
108
109 for si in idx.same_individual() {
111 for a in &si.0 {
112 let subject = individual_to_iri(a);
113 for b in &si.0 {
114 if a == b {
115 continue;
116 }
117 result.annotations.push(Annotation {
118 subject: subject.clone(),
119 predicate: OWL_SAME_AS.to_string(),
120 object: individual_to_iri(b),
121 ontology_id: ont_id.clone(),
122 source_location: SourceLocation::default(),
123 });
124 }
125 }
126 }
127
128 let mut axiom_counter = 0usize;
129
130 for ac in idx.component_for_kind(ComponentKind::SubClassOf) {
131 let Component::SubClassOf(ax) = &ac.component else {
132 continue;
133 };
134 if let ClassExpression::Class(sub) = &ax.sub {
135 if let Some(sup) = class_expr_display(&ax.sup, namespaces) {
136 push_axiom(
137 &mut result.axioms,
138 &mut axiom_counter,
139 ontology_id,
140 &ont_id,
141 sub.to_string(),
142 RDFS_SUB_CLASS_OF.to_string(),
143 sup,
144 AXIOM_KIND_SUB_CLASS_OF,
145 ann_bag(ac),
146 );
147 }
148 }
149 }
150
151 for ac in idx.component_for_kind(ComponentKind::EquivalentClasses) {
152 let Component::EquivalentClasses(eq) = &ac.component else {
153 continue;
154 };
155 let anns = ann_bag(ac);
156 for ce in &eq.0 {
157 if let ClassExpression::Class(sub) = ce {
158 for other in &eq.0 {
159 if other == ce {
160 continue;
161 }
162 if let Some(obj) = class_expr_display(other, namespaces) {
163 push_axiom(
164 &mut result.axioms,
165 &mut axiom_counter,
166 ontology_id,
167 &ont_id,
168 sub.to_string(),
169 OWL_EQUIVALENT_CLASS.to_string(),
170 obj,
171 AXIOM_KIND_EQUIVALENT_CLASS,
172 anns.clone(),
173 );
174 }
175 }
176 }
177 }
178 }
179
180 for ac in idx.component_for_kind(ComponentKind::DisjointClasses) {
181 let Component::DisjointClasses(disj) = &ac.component else {
182 continue;
183 };
184 let anns = ann_bag(ac);
185 for ce in &disj.0 {
186 if let ClassExpression::Class(sub) = ce {
187 for other in &disj.0 {
188 if other == ce {
189 continue;
190 }
191 if let Some(obj) = class_expr_display(other, namespaces) {
192 push_axiom(
193 &mut result.axioms,
194 &mut axiom_counter,
195 ontology_id,
196 &ont_id,
197 sub.to_string(),
198 "http://www.w3.org/2002/07/owl#disjointWith".to_string(),
199 obj,
200 AXIOM_KIND_DISJOINT_CLASS,
201 anns.clone(),
202 );
203 }
204 }
205 }
206 }
207 }
208
209 for ac in idx.component_for_kind(ComponentKind::DisjointUnion) {
210 let Component::DisjointUnion(du) = &ac.component else {
211 continue;
212 };
213 let members: Vec<String> =
214 du.1.iter().filter_map(|ce| class_expr_display(ce, namespaces)).collect();
215 push_axiom(
216 &mut result.axioms,
217 &mut axiom_counter,
218 ontology_id,
219 &ont_id,
220 du.0.to_string(),
221 OWL_DISJOINT_UNION_OF.to_string(),
222 members.join(MEMBER_SEP),
223 AXIOM_KIND_DISJOINT_UNION,
224 ann_bag(ac),
225 );
226 }
227
228 for ac in idx.component_for_kind(ComponentKind::HasKey) {
229 let Component::HasKey(hk) = &ac.component else {
230 continue;
231 };
232 let ClassExpression::Class(cls) = &hk.ce else {
233 continue;
234 };
235 let props: Vec<String> = hk.vpe.iter().map(property_expr_to_iri).collect();
236 push_axiom(
237 &mut result.axioms,
238 &mut axiom_counter,
239 ontology_id,
240 &ont_id,
241 cls.to_string(),
242 OWL_HAS_KEY.to_string(),
243 props.join(MEMBER_SEP),
244 AXIOM_KIND_HAS_KEY,
245 ann_bag(ac),
246 );
247 }
248
249 for ac in idx.component_for_kind(ComponentKind::InverseObjectProperties) {
250 let Component::InverseObjectProperties(inv) = &ac.component else {
251 continue;
252 };
253 let anns = ann_bag(ac);
254 let a = inv.0.to_string();
255 let b = inv.1.to_string();
256 push_axiom(
257 &mut result.axioms,
258 &mut axiom_counter,
259 ontology_id,
260 &ont_id,
261 a.clone(),
262 OWL_INVERSE_OF.to_string(),
263 b.clone(),
264 AXIOM_KIND_INVERSE_OBJECT_PROPERTIES,
265 anns.clone(),
266 );
267 push_axiom(
268 &mut result.axioms,
269 &mut axiom_counter,
270 ontology_id,
271 &ont_id,
272 b,
273 OWL_INVERSE_OF.to_string(),
274 a,
275 AXIOM_KIND_INVERSE_OBJECT_PROPERTIES,
276 anns,
277 );
278 }
279
280 for ac in idx.component_for_kind(ComponentKind::EquivalentObjectProperties) {
281 let Component::EquivalentObjectProperties(eq) = &ac.component else {
282 continue;
283 };
284 let anns = ann_bag(ac);
285 let iris: Vec<String> = eq.0.iter().map(ope_to_iri).collect();
286 push_pairwise_property_axioms(
287 &mut result.axioms,
288 &mut axiom_counter,
289 ontology_id,
290 &ont_id,
291 &iris,
292 OWL_EQUIVALENT_PROPERTY,
293 AXIOM_KIND_EQUIVALENT_OBJECT_PROPERTIES,
294 anns,
295 );
296 }
297
298 for ac in idx.component_for_kind(ComponentKind::DisjointObjectProperties) {
299 let Component::DisjointObjectProperties(dj) = &ac.component else {
300 continue;
301 };
302 let anns = ann_bag(ac);
303 let iris: Vec<String> = dj.0.iter().map(ope_to_iri).collect();
304 push_pairwise_property_axioms(
305 &mut result.axioms,
306 &mut axiom_counter,
307 ontology_id,
308 &ont_id,
309 &iris,
310 OWL_PROPERTY_DISJOINT_WITH,
311 AXIOM_KIND_DISJOINT_OBJECT_PROPERTIES,
312 anns,
313 );
314 }
315
316 for ac in idx.component_for_kind(ComponentKind::EquivalentDataProperties) {
317 let Component::EquivalentDataProperties(eq) = &ac.component else {
318 continue;
319 };
320 let anns = ann_bag(ac);
321 let iris: Vec<String> = eq.0.iter().map(|dp| dp.to_string()).collect();
322 push_pairwise_property_axioms(
323 &mut result.axioms,
324 &mut axiom_counter,
325 ontology_id,
326 &ont_id,
327 &iris,
328 OWL_EQUIVALENT_PROPERTY,
329 AXIOM_KIND_EQUIVALENT_DATA_PROPERTIES,
330 anns,
331 );
332 }
333
334 for ac in idx.component_for_kind(ComponentKind::DisjointDataProperties) {
335 let Component::DisjointDataProperties(dj) = &ac.component else {
336 continue;
337 };
338 let anns = ann_bag(ac);
339 let iris: Vec<String> = dj.0.iter().map(|dp| dp.to_string()).collect();
340 push_pairwise_property_axioms(
341 &mut result.axioms,
342 &mut axiom_counter,
343 ontology_id,
344 &ont_id,
345 &iris,
346 OWL_PROPERTY_DISJOINT_WITH,
347 AXIOM_KIND_DISJOINT_DATA_PROPERTIES,
348 anns,
349 );
350 }
351
352 for ac in idx.component_for_kind(ComponentKind::ObjectPropertyDomain) {
353 let Component::ObjectPropertyDomain(dom) = &ac.component else {
354 continue;
355 };
356 let prop = ope_to_iri(&dom.ope);
357 if let ClassExpression::Class(cls) = &dom.ce {
358 push_axiom(
359 &mut result.axioms,
360 &mut axiom_counter,
361 ontology_id,
362 &ont_id,
363 prop,
364 "http://www.w3.org/2000/01/rdf-schema#domain".to_string(),
365 cls.to_string(),
366 AXIOM_KIND_DOMAIN,
367 ann_bag(ac),
368 );
369 }
370 }
371 for ac in idx.component_for_kind(ComponentKind::ObjectPropertyRange) {
372 let Component::ObjectPropertyRange(rng) = &ac.component else {
373 continue;
374 };
375 let prop = ope_to_iri(&rng.ope);
376 if let ClassExpression::Class(cls) = &rng.ce {
377 push_axiom(
378 &mut result.axioms,
379 &mut axiom_counter,
380 ontology_id,
381 &ont_id,
382 prop,
383 "http://www.w3.org/2000/01/rdf-schema#range".to_string(),
384 cls.to_string(),
385 AXIOM_KIND_RANGE,
386 ann_bag(ac),
387 );
388 }
389 }
390 for ac in idx.component_for_kind(ComponentKind::DataPropertyDomain) {
391 let Component::DataPropertyDomain(dom) = &ac.component else {
392 continue;
393 };
394 let prop = dom.dp.to_string();
395 if let ClassExpression::Class(cls) = &dom.ce {
396 push_axiom(
397 &mut result.axioms,
398 &mut axiom_counter,
399 ontology_id,
400 &ont_id,
401 prop,
402 "http://www.w3.org/2000/01/rdf-schema#domain".to_string(),
403 cls.to_string(),
404 AXIOM_KIND_DOMAIN,
405 ann_bag(ac),
406 );
407 }
408 }
409 for ac in idx.component_for_kind(ComponentKind::DataPropertyRange) {
410 let Component::DataPropertyRange(rng) = &ac.component else {
411 continue;
412 };
413 push_axiom(
414 &mut result.axioms,
415 &mut axiom_counter,
416 ontology_id,
417 &ont_id,
418 rng.dp.to_string(),
419 "http://www.w3.org/2000/01/rdf-schema#range".to_string(),
420 data_range_display(&rng.dr),
421 AXIOM_KIND_RANGE,
422 ann_bag(ac),
423 );
424 }
425
426 for ac in idx.component_for_kind(ComponentKind::SubObjectPropertyOf) {
427 let Component::SubObjectPropertyOf(sub_prop) = &ac.component else {
428 continue;
429 };
430 match &sub_prop.sub {
431 SubObjectPropertyExpression::ObjectPropertyChain(chain) => {
432 let chain_display = chain.iter().map(ope_to_iri).collect::<Vec<_>>().join(" o ");
433 push_axiom(
434 &mut result.axioms,
435 &mut axiom_counter,
436 ontology_id,
437 &ont_id,
438 ope_to_iri(&sub_prop.sup),
439 "http://www.w3.org/2002/07/owl#propertyChainAxiom".to_string(),
440 chain_display,
441 AXIOM_KIND_PROPERTY_CHAIN,
442 ann_bag(ac),
443 );
444 }
445 SubObjectPropertyExpression::ObjectPropertyExpression(sub) => {
446 push_axiom(
447 &mut result.axioms,
448 &mut axiom_counter,
449 ontology_id,
450 &ont_id,
451 ope_to_iri(sub),
452 RDFS_SUB_PROPERTY_OF.to_string(),
453 ope_to_iri(&sub_prop.sup),
454 AXIOM_KIND_SUB_OBJECT_PROPERTY_OF,
455 ann_bag(ac),
456 );
457 }
458 }
459 }
460
461 for ac in idx.component_for_kind(ComponentKind::SubDataPropertyOf) {
462 let Component::SubDataPropertyOf(sub_prop) = &ac.component else {
463 continue;
464 };
465 push_axiom(
466 &mut result.axioms,
467 &mut axiom_counter,
468 ontology_id,
469 &ont_id,
470 sub_prop.sub.to_string(),
471 RDFS_SUB_PROPERTY_OF.to_string(),
472 sub_prop.sup.to_string(),
473 AXIOM_KIND_SUB_DATA_PROPERTY_OF,
474 ann_bag(ac),
475 );
476 }
477
478 for ac in idx.component_for_kind(ComponentKind::ClassAssertion) {
479 let Component::ClassAssertion(ca) = &ac.component else {
480 continue;
481 };
482 if let ClassExpression::Class(cls) = &ca.ce {
483 push_axiom(
484 &mut result.axioms,
485 &mut axiom_counter,
486 ontology_id,
487 &ont_id,
488 individual_to_iri(&ca.i),
489 "http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
490 cls.to_string(),
491 AXIOM_KIND_CLASS_ASSERTION,
492 ann_bag(ac),
493 );
494 }
495 }
496
497 for ac in idx.component_for_kind(ComponentKind::ObjectPropertyAssertion) {
498 let Component::ObjectPropertyAssertion(opa) = &ac.component else {
499 continue;
500 };
501 push_axiom(
502 &mut result.axioms,
503 &mut axiom_counter,
504 ontology_id,
505 &ont_id,
506 individual_to_iri(&opa.from),
507 ope_to_iri(&opa.ope),
508 individual_to_iri(&opa.to),
509 AXIOM_KIND_OBJECT_PROPERTY_ASSERTION,
510 ann_bag(ac),
511 );
512 }
513
514 for ac in idx.component_for_kind(ComponentKind::DataPropertyAssertion) {
515 let Component::DataPropertyAssertion(dpa) = &ac.component else {
516 continue;
517 };
518 push_axiom(
519 &mut result.axioms,
520 &mut axiom_counter,
521 ontology_id,
522 &ont_id,
523 individual_to_iri(&dpa.from),
524 dpa.dp.to_string(),
525 dpa.to.literal().clone(),
526 AXIOM_KIND_DATA_PROPERTY_ASSERTION,
527 ann_bag(ac),
528 );
529 }
530
531 for ac in idx.component_for_kind(ComponentKind::NegativeObjectPropertyAssertion) {
532 let Component::NegativeObjectPropertyAssertion(nopa) = &ac.component else {
533 continue;
534 };
535 push_axiom(
536 &mut result.axioms,
537 &mut axiom_counter,
538 ontology_id,
539 &ont_id,
540 individual_to_iri(&nopa.from),
541 ope_to_iri(&nopa.ope),
542 individual_to_iri(&nopa.to),
543 AXIOM_KIND_NEGATIVE_OBJECT_PROPERTY_ASSERTION,
544 ann_bag(ac),
545 );
546 }
547
548 for ac in idx.component_for_kind(ComponentKind::NegativeDataPropertyAssertion) {
549 let Component::NegativeDataPropertyAssertion(ndpa) = &ac.component else {
550 continue;
551 };
552 push_axiom(
553 &mut result.axioms,
554 &mut axiom_counter,
555 ontology_id,
556 &ont_id,
557 individual_to_iri(&ndpa.from),
558 ndpa.dp.to_string(),
559 ndpa.to.literal().clone(),
560 AXIOM_KIND_NEGATIVE_DATA_PROPERTY_ASSERTION,
561 ann_bag(ac),
562 );
563 }
564
565 for ac in idx.component_for_kind(ComponentKind::SameIndividual) {
566 let Component::SameIndividual(si) = &ac.component else {
567 continue;
568 };
569 let anns = ann_bag(ac);
570 let iris: Vec<String> = si.0.iter().map(individual_to_iri).collect();
571 for (i, a) in iris.iter().enumerate() {
572 for (j, b) in iris.iter().enumerate() {
573 if i == j {
574 continue;
575 }
576 push_axiom(
577 &mut result.axioms,
578 &mut axiom_counter,
579 ontology_id,
580 &ont_id,
581 a.clone(),
582 OWL_SAME_AS.to_string(),
583 b.clone(),
584 AXIOM_KIND_SAME_INDIVIDUAL,
585 anns.clone(),
586 );
587 }
588 }
589 }
590
591 for ac in idx.component_for_kind(ComponentKind::DifferentIndividuals) {
592 let Component::DifferentIndividuals(di) = &ac.component else {
593 continue;
594 };
595 let anns = ann_bag(ac);
596 let iris: Vec<String> = di.0.iter().map(individual_to_iri).collect();
597 for (i, a) in iris.iter().enumerate() {
598 for (j, b) in iris.iter().enumerate() {
599 if i >= j {
600 continue;
601 }
602 push_axiom(
603 &mut result.axioms,
604 &mut axiom_counter,
605 ontology_id,
606 &ont_id,
607 a.clone(),
608 OWL_DIFFERENT_FROM.to_string(),
609 b.clone(),
610 AXIOM_KIND_DIFFERENT_INDIVIDUALS,
611 anns.clone(),
612 );
613 push_axiom(
614 &mut result.axioms,
615 &mut axiom_counter,
616 ontology_id,
617 &ont_id,
618 b.clone(),
619 OWL_DIFFERENT_FROM.to_string(),
620 a.clone(),
621 AXIOM_KIND_DIFFERENT_INDIVIDUALS,
622 anns.clone(),
623 );
624 }
625 }
626 }
627
628 for ac in idx.component_for_kind(ComponentKind::DatatypeDefinition) {
629 let Component::DatatypeDefinition(dd) = &ac.component else {
630 continue;
631 };
632 insert_entity(&mut entity_map, dd.kind.to_string(), EntityKind::Datatype, &ont_id);
633 push_axiom(
634 &mut result.axioms,
635 &mut axiom_counter,
636 ontology_id,
637 &ont_id,
638 dd.kind.to_string(),
639 OWL_EQUIVALENT_CLASS.to_string(),
640 data_range_display(&dd.range),
641 AXIOM_KIND_DATATYPE_DEFINITION,
642 ann_bag(ac),
643 );
644 }
645
646 mark_functional_properties(&mut entity_map, idx);
647
648 for imp in idx.import() {
649 result.imports.push(Import { ontology_id: ont_id.clone(), import_iri: imp.0.to_string() });
650 }
651
652 for (prefix, iri) in namespaces {
653 result.namespace_rows.push(Namespace {
654 prefix: prefix.clone(),
655 iri: iri.clone(),
656 ontology_id: ont_id.clone(),
657 });
658 }
659
660 result.entities = entity_map.into_values().collect();
661 for entity in &mut result.entities {
662 entity.source_location =
663 find_entity_block(source_text, &entity.iri, &entity.short_name, namespaces);
664 }
665 annotate_spans(source_text, &mut result.entities, &mut result.annotations, &mut result.axioms);
666 result
667}
668
669fn ann_bag(ac: &AnnotatedComponent<RcStr>) -> Vec<AxiomAnnotation> {
670 ac.ann
671 .iter()
672 .map(|a| AxiomAnnotation {
673 predicate: a.ap.to_string(),
674 value: annotation_value_string(&a.av),
675 })
676 .collect()
677}
678
679#[allow(clippy::too_many_arguments)]
680fn push_axiom(
681 axioms: &mut Vec<Axiom>,
682 counter: &mut usize,
683 doc_id: &str,
684 ontology_id: &str,
685 subject: String,
686 predicate: String,
687 object: String,
688 kind: &str,
689 annotations: Vec<AxiomAnnotation>,
690) {
691 *counter += 1;
692 axioms.push(Axiom {
693 id: format!("{doc_id}#axiom-{counter}"),
694 ontology_id: ontology_id.to_string(),
695 subject,
696 predicate,
697 object,
698 axiom_kind: kind.to_string(),
699 source_location: SourceLocation::default(),
700 annotations,
701 });
702}
703
704#[allow(clippy::too_many_arguments)]
705fn push_pairwise_property_axioms(
706 axioms: &mut Vec<Axiom>,
707 counter: &mut usize,
708 doc_id: &str,
709 ontology_id: &str,
710 iris: &[String],
711 predicate: &str,
712 kind: &str,
713 annotations: Vec<AxiomAnnotation>,
714) {
715 for (i, a) in iris.iter().enumerate() {
716 for (j, b) in iris.iter().enumerate() {
717 if i == j {
718 continue;
719 }
720 push_axiom(
721 axioms,
722 counter,
723 doc_id,
724 ontology_id,
725 a.clone(),
726 predicate.to_string(),
727 b.clone(),
728 kind,
729 annotations.clone(),
730 );
731 }
732 }
733}
734
735fn insert_entity(
736 map: &mut BTreeMap<String, Entity>,
737 iri: String,
738 kind: EntityKind,
739 ontology_id: &str,
740) {
741 let short_name = short_name_from_iri(&iri);
742 map.entry(iri.clone()).or_insert_with(|| Entity {
743 iri,
744 short_name,
745 kind,
746 ontology_id: ontology_id.to_string(),
747 source_location: SourceLocation::default(),
748 labels: Vec::new(),
749 comments: Vec::new(),
750 deprecated: false,
751 obo_id: None,
752 characteristics: strixonomy_core::PropertyCharacteristics::default(),
753 });
754}
755
756fn individual_to_iri(individual: &Individual<RcStr>) -> String {
757 individual.to_string()
758}
759
760fn mark_functional_properties(
761 entity_map: &mut BTreeMap<String, Entity>,
762 idx: &ComponentMappedIndex<RcStr, RcAnnotatedComponent>,
763) {
764 for f in idx.functional_object_property() {
765 let iri = ope_to_iri(&f.0);
766 if let Some(e) = entity_map.get_mut(&iri) {
767 e.characteristics.functional = true;
768 }
769 }
770 for f in idx.inverse_functional_object_property() {
771 let iri = ope_to_iri(&f.0);
772 if let Some(e) = entity_map.get_mut(&iri) {
773 e.characteristics.inverse_functional = true;
774 }
775 }
776 for f in idx.transitive_object_property() {
777 let iri = ope_to_iri(&f.0);
778 if let Some(e) = entity_map.get_mut(&iri) {
779 e.characteristics.transitive = true;
780 }
781 }
782 for f in idx.symmetric_object_property() {
783 let iri = ope_to_iri(&f.0);
784 if let Some(e) = entity_map.get_mut(&iri) {
785 e.characteristics.symmetric = true;
786 }
787 }
788 for f in idx.asymmetric_object_property() {
789 let iri = ope_to_iri(&f.0);
790 if let Some(e) = entity_map.get_mut(&iri) {
791 e.characteristics.asymmetric = true;
792 }
793 }
794 for f in idx.reflexive_object_property() {
795 let iri = ope_to_iri(&f.0);
796 if let Some(e) = entity_map.get_mut(&iri) {
797 e.characteristics.reflexive = true;
798 }
799 }
800 for f in idx.irreflexive_object_property() {
801 let iri = ope_to_iri(&f.0);
802 if let Some(e) = entity_map.get_mut(&iri) {
803 e.characteristics.irreflexive = true;
804 }
805 }
806 for f in idx.functional_data_property() {
807 let iri = f.0.to_string();
808 if let Some(e) = entity_map.get_mut(&iri) {
809 e.characteristics.functional = true;
810 }
811 }
812}
813
814fn class_expr_display(
815 expr: &ClassExpression<RcStr>,
816 namespaces: &BTreeMap<String, String>,
817) -> Option<String> {
818 match expr {
819 ClassExpression::Class(c) => Some(c.to_string()),
820 _ => Some(class_expression_to_manchester(expr, namespaces)),
821 }
822}
823
824fn ope_to_iri(ope: &ObjectPropertyExpression<RcStr>) -> String {
825 match ope {
826 ObjectPropertyExpression::ObjectProperty(p) => p.to_string(),
827 ObjectPropertyExpression::InverseObjectProperty(p) => {
828 format!("inverse({})", p.0)
829 }
830 }
831}
832
833fn property_expr_to_iri(pe: &PropertyExpression<RcStr>) -> String {
834 match pe {
835 PropertyExpression::ObjectPropertyExpression(ope) => ope_to_iri(ope),
836 PropertyExpression::DataProperty(dp) => dp.to_string(),
837 PropertyExpression::AnnotationProperty(ap) => ap.to_string(),
838 }
839}
840
841fn data_range_display(dr: &horned_owl::model::DataRange<RcStr>) -> String {
842 crate::manchester::data_range_to_manchester(dr, &BTreeMap::new())
843}
844
845fn annotation_subject_iri(subject: &AnnotationSubject<RcStr>) -> String {
846 match subject {
847 AnnotationSubject::IRI(iri) => iri.to_string(),
848 AnnotationSubject::AnonymousIndividual(a) => format!("_:{}", a.as_ref()),
849 }
850}
851
852fn annotation_value_string(value: &AnnotationValue<RcStr>) -> String {
853 match value {
854 AnnotationValue::Literal(l) => l.literal().clone(),
855 AnnotationValue::IRI(iri) => iri.to_string(),
856 AnnotationValue::AnonymousIndividual(a) => format!("_:{}", a.as_ref()),
857 }
858}
859
860#[cfg(test)]
861mod tests {
862 use super::*;
863 use crate::load::load_turtle_text;
864 use oxigraph::io::RdfParser;
865 use oxigraph::model::Quad;
866 use std::path::Path;
867
868 #[test]
869 fn bridge_extracts_subclass_axioms() {
870 let ttl = include_str!("../../../fixtures/example.ttl");
871 let parser = RdfParser::from_format(oxigraph::io::RdfFormat::Turtle);
872 let quads: Vec<Quad> =
873 parser.for_reader(ttl.as_bytes()).collect::<std::result::Result<Vec<_>, _>>().unwrap();
874 let namespaces =
875 BTreeMap::from([("ex".to_string(), "http://example.org/people#".to_string())]);
876 let loaded = load_turtle_text(Path::new("example.ttl"), "doc-1", ttl, &quads, &namespaces)
877 .expect("load");
878 assert!(loaded.bridge.axioms.iter().any(|a| a.axiom_kind == AXIOM_KIND_SUB_CLASS_OF));
879 }
880
881 #[test]
882 fn bridge_extracts_disjoint_and_property_chain() {
883 let ttl = include_str!("../../../fixtures/disjoint-classes.ttl");
884 let parser = RdfParser::from_format(oxigraph::io::RdfFormat::Turtle);
885 let quads: Vec<Quad> =
886 parser.for_reader(ttl.as_bytes()).collect::<std::result::Result<Vec<_>, _>>().unwrap();
887 let namespaces =
888 BTreeMap::from([("ex".to_string(), "http://example.org/org#".to_string())]);
889 let loaded =
890 load_turtle_text(Path::new("disjoint-classes.ttl"), "doc-1", ttl, &quads, &namespaces)
891 .expect("load");
892 assert!(loaded.bridge.axioms.iter().any(|a| a.axiom_kind == AXIOM_KIND_DISJOINT_CLASS));
893 assert!(loaded.bridge.axioms.iter().any(|a| a.axiom_kind == AXIOM_KIND_PROPERTY_CHAIN));
894 }
895
896 #[test]
897 fn bridge_extracts_owl2_keys_and_inverse() {
898 let ttl = include_str!("../../../examples/protege-roundtrip/owl2-keys.ttl");
899 let parser = RdfParser::from_format(oxigraph::io::RdfFormat::Turtle);
900 let quads: Vec<Quad> =
901 parser.for_reader(ttl.as_bytes()).collect::<std::result::Result<Vec<_>, _>>().unwrap();
902 let namespaces =
903 BTreeMap::from([("ex".to_string(), "http://example.org/keys#".to_string())]);
904 let loaded =
905 load_turtle_text(Path::new("owl2-keys.ttl"), "doc-keys", ttl, &quads, &namespaces)
906 .expect("load");
907 assert!(loaded
908 .bridge
909 .axioms
910 .iter()
911 .any(|a| a.axiom_kind == AXIOM_KIND_HAS_KEY
912 && a.subject == "http://example.org/keys#Person"));
913 assert!(loaded.bridge.axioms.iter().any(|a| a.axiom_kind == AXIOM_KIND_DISJOINT_UNION
914 && a.subject == "http://example.org/keys#Sex"));
915 assert!(loaded.bridge.axioms.iter().any(|a| {
916 a.axiom_kind == AXIOM_KIND_INVERSE_OBJECT_PROPERTIES
917 && a.subject == "http://example.org/keys#hasParent"
918 && a.object == "http://example.org/keys#hasChild"
919 }));
920 }
921
922 #[test]
923 fn bridge_extracts_owl2_abox() {
924 let ttl = include_str!("../../../examples/protege-roundtrip/owl2-abox.ttl");
925 let parser = RdfParser::from_format(oxigraph::io::RdfFormat::Turtle);
926 let quads: Vec<Quad> =
927 parser.for_reader(ttl.as_bytes()).collect::<std::result::Result<Vec<_>, _>>().unwrap();
928 let namespaces =
929 BTreeMap::from([("ex".to_string(), "http://example.org/abox#".to_string())]);
930 let loaded =
931 load_turtle_text(Path::new("owl2-abox.ttl"), "doc-abox", ttl, &quads, &namespaces)
932 .expect("load");
933 assert!(loaded.bridge.axioms.iter().any(|a| {
934 a.axiom_kind == AXIOM_KIND_SAME_INDIVIDUAL
935 && a.subject == "http://example.org/abox#alice"
936 }));
937 assert!(loaded.bridge.axioms.iter().any(|a| {
938 a.axiom_kind == AXIOM_KIND_DIFFERENT_INDIVIDUALS
939 && a.subject == "http://example.org/abox#alice"
940 }));
941 assert!(loaded.bridge.axioms.iter().any(|a| {
942 a.axiom_kind == AXIOM_KIND_NEGATIVE_OBJECT_PROPERTY_ASSERTION
943 && a.subject == "http://example.org/abox#alice"
944 && a.object == "http://example.org/abox#bob"
945 }));
946 }
947}