owlish/
serializer.rs

1use std::{cmp::Ordering, collections::HashMap};
2
3use crate::{
4    api::Ontology,
5    owl::{
6        well_known, AnnotationPropertyIRI, ClassConstructor, ClassIRI, DataPropertyIRI,
7        DatatypeIRI, IndividualIRI, Literal, LiteralOrIRI, ObjectPropertyConstructor,
8        ObjectPropertyIRI, IRI,
9    },
10};
11use crate::owl::ResourceId;
12
13pub trait ToTtl {
14    fn ttl(&self) -> String;
15}
16
17enum Triple {
18    T(String, String, String),
19    Comment(String),
20    LB,
21}
22
23fn t(s: String, p: String, o: String) -> Triple {
24    Triple::T(s, p, o)
25}
26
27impl ToTtl for Ontology {
28    fn ttl(&self) -> String {
29        let mut triples: Vec<Triple> = Vec::new();
30
31        let mut sorted_imports: Vec<(&String, &IRI)> = self.imports.iter().collect();
32        sorted_imports.sort_by(|a, b| {
33            #[allow(clippy::comparison_chain)]
34            if a.0.len() > b.0.len() {
35                Ordering::Greater
36            } else if a.0.len() == b.0.len() {
37                a.0.cmp(b.0)
38            } else {
39                Ordering::Less
40            }
41        });
42
43        for (pre, iri) in sorted_imports {
44            triples.push(t(
45                "@prefix".into(),
46                format!("{}:", pre),
47                iri.ttl(&Default::default()),
48            ));
49        }
50
51        triples.push(Triple::LB);
52
53        triples.push(t(
54            self.iri.ttl(&Default::default()),
55            well_known::rdf_type().ttl(&self.imports),
56            well_known::owl_Ontology().ttl(&self.imports),
57        ));
58
59        triples.push(Triple::LB);
60        triples.push(Triple::Comment("#### Declarations #####".into()));
61        triples.push(Triple::LB);
62
63        let imports = &self.imports;
64
65        for d in self.declarations() {
66            match d {
67                crate::owl::Declaration::Class {
68                    iri,
69                    annotations: _,
70                } => triples.push(t(
71                    iri.ttl(imports),
72                    well_known::rdf_type().ttl(imports),
73                    well_known::owl_Class().ttl(imports),
74                )),
75                crate::owl::Declaration::NamedIndividual {
76                    iri,
77                    annotations: _,
78                } => triples.push(t(
79                    iri.ttl(imports),
80                    well_known::rdf_type().ttl(imports),
81                    well_known::owl_NamedIndividual().ttl(imports),
82                )),
83                crate::owl::Declaration::ObjectProperty {
84                    iri,
85                    annotations: _,
86                } => triples.push(t(
87                    iri.ttl(imports),
88                    well_known::rdf_type().ttl(imports),
89                    well_known::owl_ObjectProperty().ttl(imports),
90                )),
91                crate::owl::Declaration::DataProperty {
92                    iri,
93                    annotations: _,
94                } => triples.push(t(
95                    iri.ttl(imports),
96                    well_known::rdf_type().ttl(imports),
97                    well_known::owl_DatatypeProperty().ttl(imports),
98                )),
99                crate::owl::Declaration::AnnotationProperty {
100                    iri,
101                    annotations: _,
102                } => triples.push(t(
103                    iri.ttl(imports),
104                    well_known::rdf_type().ttl(imports),
105                    well_known::owl_AnnotationProperty().ttl(imports),
106                )),
107                crate::owl::Declaration::Datatype {
108                    iri,
109                    annotations: _,
110                } => triples.push(t(
111                    iri.ttl(imports),
112                    well_known::rdf_type().ttl(imports),
113                    well_known::owl_Datatype().ttl(imports),
114                )),
115            }
116        }
117
118        let mut class_assertions: Vec<(Triple, Vec<Triple>)> = Vec::new();
119        let mut sub_class_ofs: Vec<(Triple, Vec<Triple>)> = Vec::new();
120
121        let mut anno_prop_assertions: Vec<Triple> = Vec::new();
122        let mut anno_prop_domains_ranges: Vec<Triple> = Vec::new();
123
124        let mut data_prop_assertions: Vec<Triple> = Vec::new();
125        let mut data_prop_domains_ranges: Vec<Triple> = Vec::new();
126
127        let mut obj_prop_assertions: Vec<Triple> = Vec::new();
128        let mut obj_prop_domains_ranges: Vec<Triple> = Vec::new();
129
130        for a in self.axioms() {
131            match a {
132                crate::owl::Axiom::AnnotationAssertion(a) => {
133                    match &a.subject {
134                        ResourceId::IRI(subject_iri) => {
135                            anno_prop_assertions.push(t(
136                                subject_iri.ttl(imports),
137                                a.iri.ttl(imports),
138                                a.value.ttl(imports),
139                            ));
140                        }
141                        ResourceId::BlankNode(_) => {
142                            unimplemented!("Can't serialize AnnotationAssertion with blankNode subject")
143                        }
144                    }
145                }
146                crate::owl::Axiom::DataPropertyAssertion(d) => {
147                    data_prop_assertions.push(t(
148                        d.subject.ttl(imports),
149                        d.iri.ttl(imports),
150                        d.value.ttl(imports),
151                    ));
152                }
153                crate::owl::Axiom::ClassAssertion(c) => {
154                    let (blank_node, triples) = class_triples(&c.cls, imports, 1);
155                    class_assertions.push((
156                        t(
157                            c.individual.ttl(imports),
158                            well_known::rdf_type().ttl(imports),
159                            blank_node,
160                        ),
161                        triples,
162                    ));
163                }
164                crate::owl::Axiom::ObjectPropertyAssertion(o) => {
165                    obj_prop_assertions.push(t(
166                        o.subject.ttl(imports),
167                        o.iri.ttl(imports),
168                        o.object.ttl(imports),
169                    ));
170                }
171                crate::owl::Axiom::AnnotationPropertyRange(a) => {
172                    anno_prop_domains_ranges.push(t(
173                        a.iri.ttl(imports),
174                        well_known::rdfs_range().ttl(imports),
175                        a.datatype_iri.ttl(imports),
176                    ));
177                }
178                crate::owl::Axiom::AnnotationPropertyDomain(a) => {
179                    anno_prop_domains_ranges.push(t(
180                        a.iri.ttl(imports),
181                        well_known::rdfs_range().ttl(imports),
182                        a.class_iri.ttl(imports),
183                    ));
184                }
185
186                crate::owl::Axiom::DataPropertyDomain(a) => {
187                    data_prop_domains_ranges.push(t(
188                        a.iri.ttl(imports),
189                        well_known::rdfs_domain().ttl(imports),
190                        class_triples(&a.cls, imports, 1).0,
191                    ));
192                }
193                crate::owl::Axiom::DataPropertyRange(a) => {
194                    data_prop_domains_ranges.push(t(
195                        a.iri.ttl(imports),
196                        well_known::rdfs_range().ttl(imports),
197                        a.datatype_iri.ttl(imports),
198                    ));
199                }
200
201                crate::owl::Axiom::ObjectPropertyDomain(a) => {
202                    obj_prop_domains_ranges.push(t(
203                        a.iri.ttl(imports),
204                        well_known::rdfs_domain().ttl(imports),
205                        class_triples(&a.cls, imports, 1).0,
206                    ));
207                }
208                crate::owl::Axiom::ObjectPropertyRange(a) => {
209                    obj_prop_domains_ranges.push(t(
210                        a.iri.ttl(imports),
211                        well_known::rdfs_range().ttl(imports),
212                        class_triples(&a.cls, imports, 1).0,
213                    ));
214                }
215
216                crate::owl::Axiom::SubClassOf(sco) => {
217                    let mut context = Vec::new();
218                    let (cls, extra) = class_triples(&sco.cls, imports, 1);
219                    for t in extra {
220                        context.push(t);
221                    }
222                    let (pcls, extra) = class_triples(&sco.parent_class, imports, 1);
223                    for t in extra {
224                        context.push(t);
225                    }
226                    let subclass = well_known::rdfs_subClassOf().ttl(imports);
227                    sub_class_ofs.push((t(cls, subclass, pcls), context));
228                }
229
230                crate::owl::Axiom::SubObjectPropertyOf(_) => {}
231                crate::owl::Axiom::SubDataPropertyOf(_) => {}
232                crate::owl::Axiom::SubAnnotationPropertyOf(_) => {}
233                crate::owl::Axiom::EquivalentObjectProperties(_) => {}
234                crate::owl::Axiom::EquivalentDataProperties(_) => {}
235                crate::owl::Axiom::InverseObjectProperties(_) => {}
236                crate::owl::Axiom::DisjointObjectProperties(_) => {}
237                crate::owl::Axiom::SymmetricObjectProperty(_) => {}
238                crate::owl::Axiom::AsymmetricObjectProperty(_) => {}
239                crate::owl::Axiom::ReflexiveObjectProperty(_) => {}
240                crate::owl::Axiom::IrreflexiveObjectProperty(_) => {}
241                crate::owl::Axiom::FunctionalObjectProperty(_) => {}
242                crate::owl::Axiom::InverseFunctionalObjectProperty(_) => {}
243                crate::owl::Axiom::TransitiveObjectProperty(_) => {}
244                crate::owl::Axiom::FunctionalDataProperty(_) => {}
245                crate::owl::Axiom::EquivalentClasses(_) => {}
246                crate::owl::Axiom::DisjointClasses(_) => {}
247                crate::owl::Axiom::DatatypeDefinition(_) => {}
248                crate::owl::Axiom::SameIndividual(_) => {}
249                crate::owl::Axiom::DifferentIndividuals(_) => {}
250                crate::owl::Axiom::NegativeObjectPropertyAssertion(_) => {}
251                crate::owl::Axiom::NegativeDataPropertyAssertion(_) => {}
252                crate::owl::Axiom::HasKey(_) => {}
253            }
254        }
255
256        if !anno_prop_domains_ranges.is_empty() {
257            triples.push(Triple::LB);
258            triples.push(Triple::Comment("#### AnnotationProperties #####".into()));
259            triples.push(Triple::LB);
260
261            for t in anno_prop_domains_ranges {
262                triples.push(t);
263            }
264        }
265
266        if !data_prop_domains_ranges.is_empty() {
267            triples.push(Triple::LB);
268            triples.push(Triple::Comment("#### DataProperties #####".into()));
269            triples.push(Triple::LB);
270
271            for t in data_prop_domains_ranges {
272                triples.push(t);
273            }
274        }
275
276        if !obj_prop_domains_ranges.is_empty() {
277            triples.push(Triple::LB);
278            triples.push(Triple::Comment("#### ObjectProperties #####".into()));
279            triples.push(Triple::LB);
280
281            for t in obj_prop_domains_ranges {
282                triples.push(t);
283            }
284        }
285
286        triples.push(Triple::LB);
287        triples.push(Triple::Comment("#### ClassAssertions #####".into()));
288        triples.push(Triple::LB);
289
290        for (t, context) in class_assertions {
291            triples.push(t);
292            for t in context {
293                triples.push(t);
294            }
295        }
296        for (t, context) in sub_class_ofs {
297            triples.push(t);
298            for t in context {
299                triples.push(t);
300            }
301        }
302
303        triples.push(Triple::LB);
304        triples.push(Triple::Comment("#### AnnotationAssertions #####".into()));
305        triples.push(Triple::LB);
306
307        for t in anno_prop_assertions {
308            triples.push(t);
309        }
310
311        triples.push(Triple::LB);
312        triples.push(Triple::Comment("#### DataPropertyAssertions #####".into()));
313        triples.push(Triple::LB);
314
315        for t in data_prop_assertions {
316            triples.push(t);
317        }
318
319        triples.push(Triple::LB);
320        triples.push(Triple::Comment(
321            "#### ObjectPropertyAssertions #####".into(),
322        ));
323        triples.push(Triple::LB);
324
325        for t in obj_prop_assertions {
326            triples.push(t);
327        }
328
329        let mut ttl = "".to_string();
330        for triple in triples {
331            match triple {
332                Triple::T(s, p, o) => {
333                    ttl = format!("{}{} {} {} . \n", ttl, s, p, o);
334                }
335                Triple::Comment(c) => {
336                    ttl = format!("{}#{}\n", ttl, c);
337                }
338                Triple::LB => ttl = format!("{}\n", ttl),
339            }
340        }
341        ttl
342    }
343}
344
345fn indentation(level: usize) -> String {
346    String::from_utf8(vec![b' '; level * 4]).unwrap()
347}
348
349// fn bn() -> String {
350//     format!("_:{}", uuid::Uuid::new_v4())
351// }
352
353pub trait IriToTtl {
354    fn ttl(&self, imports: &HashMap<String, IRI>) -> String;
355}
356
357impl IriToTtl for Literal {
358    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
359        match self {
360            Literal::Raw { data, type_iri } => {
361                format!("\"{:?}\"^^{}", data, type_iri.ttl(imports))
362            }
363            Literal::String(s) => format!("\"{}\"", s),
364            Literal::DateTime(d) => {
365                format!("\"{}\"^^{}", d, well_known::xsd_dateTime().ttl(imports))
366            }
367            Literal::LangString { string, lang } => format!("\"{}\"@{}", string, lang),
368            Literal::Number { number, type_iri } => match type_iri {
369                Some(type_iri) => format!("\"{}\"^^{}", number, type_iri.ttl(imports)),
370                None => format!("{}", number),
371            },
372            Literal::Duration(duration) => format!("\"{}\"^^{}", duration, well_known::xsd_duration().ttl(imports)),
373            Literal::YearMonthDuration(duration) => format!("\"{}\"^^{}", duration, well_known::xsd_yearMonthDuration().ttl(imports)),
374            Literal::DayTimeDuration(duration) => format!("\"{}\"^^{}", duration, well_known::xsd_dayTimeDuration().ttl(imports)),
375            Literal::Bool(b) => format!("{}", b),
376        }
377    }
378}
379
380impl IriToTtl for LiteralOrIRI {
381    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
382        match self {
383            LiteralOrIRI::IRI(iri) => iri.ttl(imports),
384            LiteralOrIRI::Literal(l) => l.ttl(imports),
385        }
386    }
387}
388
389impl IriToTtl for IRI {
390    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
391        let s = self.to_string();
392        for (prefix, prefix_iri) in imports {
393            let p = prefix_iri.as_str();
394            if s.starts_with(p) {
395                return format!("{}:{}", prefix, s.replace(p, ""));
396            }
397        }
398        format!("<{}>", self.as_str())
399    }
400}
401impl IriToTtl for AnnotationPropertyIRI {
402    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
403        self.as_iri().ttl(imports)
404    }
405}
406impl IriToTtl for ClassIRI {
407    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
408        self.as_iri().ttl(imports)
409    }
410}
411impl IriToTtl for DatatypeIRI {
412    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
413        self.as_iri().ttl(imports)
414    }
415}
416impl IriToTtl for IndividualIRI {
417    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
418        self.as_iri().ttl(imports)
419    }
420}
421impl IriToTtl for DataPropertyIRI {
422    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
423        self.as_iri().ttl(imports)
424    }
425}
426impl IriToTtl for ObjectPropertyIRI {
427    fn ttl(&self, imports: &HashMap<String, IRI>) -> String {
428        self.as_iri().ttl(imports)
429    }
430}
431
432fn property_triples(
433    prop: &ObjectPropertyConstructor,
434    imports: &HashMap<String, IRI>,
435) -> (String, Vec<Triple>) {
436    match prop {
437        ObjectPropertyConstructor::IRI(iri) => (iri.ttl(imports), Vec::new()),
438        ObjectPropertyConstructor::ObjectInverseOf(inv) => {
439            let inverse_of = well_known::owl_inverseOf().ttl(imports);
440            (
441                format!("[ {inverse_of} {} ]", inv.0.ttl(imports)),
442                Vec::new(),
443            )
444        }
445        ObjectPropertyConstructor::ObjectPropertyChain(_) => todo!(),
446    }
447}
448
449fn class_triples(
450    cls: &ClassConstructor,
451    imports: &HashMap<String, IRI>,
452    level: usize,
453) -> (String, Vec<Triple>) {
454    let indent_sub1 = indentation(0.max(level - 1));
455    let indent = indentation(level);
456    match cls {
457        ClassConstructor::IRI(iri) => (iri.ttl(imports), Vec::new()),
458        ClassConstructor::ObjectIntersectionOf(inter) => {
459            // let root_bn = bn();
460            // let mut context = Vec::new();
461            // context.push(t(
462            //     root_bn.clone(),
463            //     well_known::rdf_type().ttl(imports),
464            //     well_known::owl_Class().ttl(imports),
465            // ));
466
467            // let first_bn = bn();
468            // context.push(t(
469            //     root_bn.clone(),
470            //     well_known::owl_intersectionOf().ttl(imports),
471            //     first_bn.clone(),
472            // ));
473            // let mut next_bn = first_bn.clone();
474            // for cls in &inter.classes {
475            //     let (object, ctx) = class_triples(cls, imports);
476
477            //     // add rest part of former iteration (the last one will have no rest)
478            //     if next_bn != first_bn {
479            //         context.push(t(
480            //             next_bn.clone(),
481            //             well_known::rdf_rest().ttl(imports),
482            //             object.clone(),
483            //         ));
484            //     }
485
486            //     // add first of this iteration
487            //     context.push(t(
488            //         next_bn.clone(),
489            //         well_known::rdf_first().ttl(imports),
490            //         object.clone(),
491            //     ));
492            //     next_bn = bn();
493            //     for c in ctx {
494            //         context.push(c);
495            //     }
496            // }
497            // let mut s = "(".into();
498            // for cls in &inter.classes {
499            //     let (cls, _) = class_triples(cls, imports);
500            //     s = format!("{} {}", s, cls)
501            // }
502            let typ = well_known::rdf_type().ttl(imports);
503            let cls = well_known::owl_Class().ttl(imports);
504            let owl_intersection_of = well_known::owl_intersectionOf().ttl(imports);
505            (
506                format!(
507                    "[\n{indent}{typ} {cls} ;\n{indent}{owl_intersection_of} ({})\n{indent_sub1}]",
508                    inter.classes.iter().fold(String::new(), |acc, x| {
509                        let (c, _) = class_triples(x, imports, level + 1);
510                        format!("{} {}", acc, c)
511                    })
512                ),
513                Default::default(),
514            )
515        }
516        ClassConstructor::SubClassOf(_) => todo!(),
517        ClassConstructor::DataSomeValuesFrom(d) => {
518            let typ = well_known::rdf_type().ttl(imports);
519            let owl_restriction = well_known::owl_Restriction().ttl(imports);
520            // let mut on_class = String::new();
521            // let owl_cardinality = if let Some(iri) = &d.class_iri {
522            //     on_class = format!(
523            //         ";\n{indent}{} {}",
524            //         well_known::owl_onClass().ttl(imports),
525            //         iri.ttl(imports),
526            //     );
527            //     well_known::owl_minCardinality().ttl(imports)
528            // } else {
529            //     well_known::owl_minQualifiedCardinality().ttl(imports)
530            // };
531            // let cardinality = Literal::Number {
532            //     number: d.value.into(),
533            //     type_iri: well_known::xsd_nonNegativeInteger().into(),
534            // }
535            // .ttl(imports);
536            let some_values_from = well_known::owl_someValuesFrom().ttl(imports);
537            let on_prop = well_known::owl_onProperty().ttl(imports);
538            let prop = d.data_property_iri.ttl(imports);
539            let restriction = restriction(&d.restriction, imports, level + 1);
540            (
541                format!(
542                    "[\n{indent}{typ} {owl_restriction} ;\n{indent}{on_prop} {prop} ;\n{indent}{some_values_from} {restriction} \n{indent_sub1}]"
543                ),
544                Vec::new(),
545            )
546        }
547        ClassConstructor::EquivalentClasses(_) => todo!(),
548        ClassConstructor::DisjointClasses(_) => todo!(),
549        ClassConstructor::ObjectComplementOf(oco) => {
550            let typ = well_known::rdf_type().ttl(imports);
551            let cls = well_known::owl_Class().ttl(imports);
552            let owl_complement_of = well_known::owl_complementOf().ttl(imports);
553
554            (
555                format!(
556                    "[\n{indent}{typ} {cls} ;\n{indent}{owl_complement_of} {}\n{indent_sub1}]",
557                    class_triples(&oco.cls, imports, level + 1).0
558                ),
559                Vec::new(),
560            )
561        }
562        ClassConstructor::ObjectMaxCardinality(omc) => {
563            // let root_bn = bn();
564            // let mut context = Vec::new();
565            // context.push(t(
566            //     root_bn.clone(),
567            //     well_known::rdf_type().ttl(imports),
568            //     well_known::owl_Restriction().ttl(imports),
569            // ));
570
571            // context.push(t(
572            //     root_bn.clone(),
573            //     well_known::owl_onProperty().ttl(imports),
574            //     omc.object_property_iri.ttl(imports),
575            // ));
576
577            // if let Some(iri) = &omc.class_iri {
578            //     context.push(t(
579            //         root_bn.clone(),
580            //         well_known::owl_maxQualifiedCardinality().ttl(imports),
581            //         Literal::Number {
582            //             number: omc.value.into(),
583            //             type_iri: well_known::xsd_nonNegativeInteger().into(),
584            //         }
585            //         .ttl(imports),
586            //     ));
587            //     context.push(t(
588            //         root_bn.clone(),
589            //         well_known::owl_onClass().ttl(imports),
590            //         iri.ttl(imports),
591            //     ));
592            // } else {
593            //     context.push(t(
594            //         root_bn.clone(),
595            //         well_known::owl_maxCardinality().ttl(imports),
596            //         Literal::Number {
597            //             number: omc.value.into(),
598            //             type_iri: well_known::xsd_nonNegativeInteger().into(),
599            //         }
600            //         .ttl(imports),
601            //     ));
602            // }
603            // (root_bn, context)
604            let indent_sub1 = indentation(0.max(level - 1));
605            let indent = indentation(level);
606            let typ = well_known::rdf_type().ttl(imports);
607            let restriction = well_known::owl_Restriction().ttl(imports);
608            let mut on_class = String::new();
609            let owl_cardinality = if let Some(iri) = &omc.class_iri {
610                on_class = format!(
611                    ";\n{indent}{} {}",
612                    well_known::owl_onClass().ttl(imports),
613                    iri.ttl(imports),
614                );
615                well_known::owl_maxCardinality().ttl(imports)
616            } else {
617                well_known::owl_maxQualifiedCardinality().ttl(imports)
618            };
619            let cardinality = Literal::Number {
620                number: omc.value.into(),
621                type_iri: well_known::xsd_nonNegativeInteger().into(),
622            }
623            .ttl(imports);
624            let on_prop = well_known::owl_onProperty().ttl(imports);
625            let prop = omc.object_property_iri.ttl(imports);
626
627            (
628                format!(
629                    "[\n{indent}{typ} {restriction} ;\n{indent}{owl_cardinality} {cardinality} ;\n{indent}{on_prop} {prop} {on_class} \n{indent_sub1}]"
630                ),
631                Vec::new(),
632            )
633        }
634        ClassConstructor::ObjectUnionOf(_) => todo!(),
635        ClassConstructor::ObjectSomeValuesFrom(o) => {
636            let typ = well_known::rdf_type().ttl(imports);
637            let owl_restriction = well_known::owl_Restriction().ttl(imports);
638            let owl_some_values_from = well_known::owl_someValuesFrom().ttl(imports);
639            let on_property = well_known::owl_onProperty().ttl(imports);
640            (
641                format!(
642                    "[\n{indent}{typ} {owl_restriction} ;\n{indent}{on_property} {} ;\n{indent}{owl_some_values_from} {}\n{indent_sub1}]",
643                    property_triples(&o.object_property, imports).0,
644                    o.class_iri.ttl(imports)
645                ),
646                Vec::new(),
647            )
648        }
649        ClassConstructor::ObjectMinCardinality(omc) => {
650            let typ = well_known::rdf_type().ttl(imports);
651            let restriction = well_known::owl_Restriction().ttl(imports);
652            let mut on_class = String::new();
653            let owl_cardinality = if let Some(iri) = &omc.class_iri {
654                on_class = format!(
655                    ";\n{indent}{} {}",
656                    well_known::owl_onClass().ttl(imports),
657                    iri.ttl(imports),
658                );
659                well_known::owl_minCardinality().ttl(imports)
660            } else {
661                well_known::owl_minQualifiedCardinality().ttl(imports)
662            };
663            let cardinality = Literal::Number {
664                number: omc.value.into(),
665                type_iri: well_known::xsd_nonNegativeInteger().into(),
666            }
667            .ttl(imports);
668            let on_prop = well_known::owl_onProperty().ttl(imports);
669            let prop = omc.object_property_iri.ttl(imports);
670
671            (
672                format!(
673                    "[\n{indent}{typ} {restriction} ;\n{indent}{owl_cardinality} {cardinality} ;\n{indent}{on_prop} {prop} {on_class} \n{indent_sub1}]"
674                ),
675                Vec::new(),
676            )
677        }
678        ClassConstructor::ObjectExactCardinality(oec) => {
679            let typ = well_known::rdf_type().ttl(imports);
680            let restriction = well_known::owl_Restriction().ttl(imports);
681            let mut on_class = String::new();
682            let owl_cardinality = if let Some(iri) = &oec.class_iri {
683                on_class = format!(
684                    ";\n{indent}{} {}",
685                    well_known::owl_onClass().ttl(imports),
686                    iri.ttl(imports),
687                );
688                well_known::owl_cardinality().ttl(imports)
689            } else {
690                well_known::owl_qualifiedCardinality().ttl(imports)
691            };
692            let cardinality = Literal::Number {
693                number: oec.value.into(),
694                type_iri: well_known::xsd_nonNegativeInteger().into(),
695            }
696            .ttl(imports);
697            let on_prop = well_known::owl_onProperty().ttl(imports);
698            let prop = oec.object_property_iri.ttl(imports);
699
700            (
701                format!(
702                    "[\n{indent}{typ} {restriction} ;\n{indent}{owl_cardinality} {cardinality} ;\n{indent}{on_prop} {prop} {on_class} \n{indent_sub1}]"
703                ),
704                Vec::new(),
705            )
706        }
707        ClassConstructor::ObjectAllValuesFrom(o) => {
708            let typ = well_known::rdf_type().ttl(imports);
709            let cls = well_known::owl_Class().ttl(imports);
710            let on_prop = well_known::owl_onProperty().ttl(imports);
711            let owl_all_from = well_known::owl_allValuesFrom().ttl(imports);
712            (
713                format!(
714                    "[\n{indent}{typ} {cls} ;\n{indent}{on_prop} {} ;\n{indent}{owl_all_from} {} \n{indent_sub1}]",
715                    property_triples(&o.object_property, imports).0,
716                    o.class_iri.ttl(imports)
717                ),
718                Vec::new(),
719            )
720        }
721        ClassConstructor::ObjectOneOf(o) => {
722            let typ = well_known::rdf_type().ttl(imports);
723            let cls = well_known::owl_Class().ttl(imports);
724            let owl_one_of = well_known::owl_oneOf().ttl(imports);
725            (
726                format!(
727                    "[\n{indent}{typ} {cls} ;\n{indent}{owl_one_of} ({}) \n{indent_sub1}]",
728                    o.individuals.iter().fold(String::new(), |acc, x| format!(
729                        "{} {}",
730                        acc,
731                        x.ttl(imports)
732                    ))
733                ),
734                Vec::new(),
735            )
736        }
737        ClassConstructor::ObjectHasValue(_) => todo!(),
738        ClassConstructor::ObjectHasSelf(_) => todo!(),
739    }
740}
741
742fn restriction(
743    restriction: &crate::owl::DatatypeRestriction,
744    imports: &HashMap<String, IRI>,
745    level: usize,
746) -> String {
747    let indent_sub1 = indentation(0.max(level - 1));
748    let indent_add1 = indentation(0.max(level + 1));
749    let indent = indentation(level);
750    let typ = well_known::rdf_type().ttl(imports);
751    let rdfs_datatype = well_known::rdfs_Datatype().ttl(imports);
752    let on_datatype = well_known::owl_onDatatype().ttl(imports);
753    let datatype = restriction.datatype_iri.ttl(imports);
754    let with_restrictions = well_known::owl_withRestrictions().ttl(imports);
755    format!(
756        "[\n{indent}{typ} {rdfs_datatype} ;\n{indent}{on_datatype} {datatype} ;\n{indent}{with_restrictions} ({}\n{indent})\n{indent_sub1}]",
757        restriction.restrictions.iter().fold(String::new(), |acc, x| format!("{} {}", acc, match x {
758            crate::owl::Restriction::Numeric { datatype_iri, value } => {
759                format!("\n{indent_add1}[{} {}]", datatype_iri.ttl(imports), value.ttl(imports))
760            },
761        }))
762    )
763}
764
765#[cfg(test)]
766mod tests {
767    use crate::owl::well_known;
768
769    use super::ToTtl;
770
771    const EXPECTED: &str = include_str!("../tests/expected.ttl");
772
773    #[test]
774    fn test() {
775        let mut onto = crate::examples::family();
776        onto.imports.insert("".into(), onto.iri.clone());
777        onto.imports.insert("owl".into(), well_known::owl());
778        onto.imports.insert("rdfs".into(), well_known::rdfs());
779        onto.imports.insert("rdf".into(), well_known::rdf());
780        onto.imports.insert("xsd".into(), well_known::xsd());
781        assert_eq!(onto.ttl(), EXPECTED)
782    }
783}