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
349pub 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 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 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 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}