Examples
The following examples are designed to get you started using Cowl. They are ordered by complexity, so it is recommended that you read them sequentially.
Reading ontologies
Related documentation: ontology reading
1/*
2 * This introductory example shows how to read an ontology
3 * and log its axioms and annotations.
4 *
5 * @note Most errors are not handled for the sake of simplicity.
6 *
7 * @author Ivano Bilenchi
8 *
9 * @copyright Copyright (c) 2019 SisInf Lab, Polytechnic University of Bari
10 * @copyright <https://swot.sisinflab.poliba.it>
11 * @copyright SPDX-License-Identifier: EPL-2.0
12 */
13#include "cowl.h"
14#include "ulib.h"
15#include <stdio.h>
16#include <stdlib.h>
17
18#define ONTO "example_pizza.owl"
19
20int main(void) {
21 // The library must always be initialized before use.
22 cowl_init();
23
24 // Read an ontology from file.
25 CowlOntology *onto = cowl_ontology_from_path(ustring_literal(ONTO), NULL);
26
27 if (!onto) {
28 // The ontology could not be read.
29 fprintf(stderr, "Failed to load ontology " ONTO "\n");
30 return EXIT_FAILURE;
31 }
32
33 // Do stuff with the ontology. In this case it is simply logged
34 // to the standard output using the default writer.
35 cowl_ontology_to_stream(onto, uostream_std());
36
37 // Release the ontology.
38 cowl_release(onto);
39
40 return EXIT_SUCCESS;
41}
Error handling
Related documentation: error handling
1/*
2 * This example is the same as the previous one,
3 * except all errors are handled and logged.
4 *
5 * @author Ivano Bilenchi
6 *
7 * @copyright Copyright (c) 2019 SisInf Lab, Polytechnic University of Bari
8 * @copyright <https://swot.sisinflab.poliba.it>
9 * @copyright SPDX-License-Identifier: EPL-2.0
10 */
11#include "cowl.h"
12#include "ulib.h"
13#include <errno.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17
18#define ONTO "example_pizza.owl"
19
20static void log_error(char const *msg) {
21 fprintf(stderr, "Error: %s\n", msg);
22}
23
24static void log_cowl_error(CowlError const *error) {
25 // Cowl integrates with the uLib I/O API. This is usually preferable as it allows
26 // writing most objects, including errors, to output streams without needing to
27 // convert them to strings first (i.e. without additional allocations).
28 UOStream *stream = uostream_stderr();
29 cowl_write_literal(stream, "Error: ");
30 cowl_write_error(stream, error);
31 cowl_write_literal(stream, "\n");
32}
33
34int main(void) {
35 // API initialization can fail due to low memory.
36 if (cowl_is_err(cowl_init())) {
37 log_error("API initialization failure");
38 return EXIT_FAILURE;
39 }
40
41 // Cowl objects are allocated on the heap, so we need to check for NULL.
42 CowlReader *reader = cowl_reader_functional();
43
44 if (!reader) {
45 log_error("allocation failure");
46 return EXIT_FAILURE;
47 }
48
49 UString const path = ustring_literal(ONTO);
50
51 // The returned ontology is NULL if an error occurs during reading,
52 // e.g. due to I/O or syntax errors. More details about the error can be obtained
53 // by inspecting the return code.
54 cowl_ret ret;
55 CowlOntology *onto = cowl_reader_read_ontology_from_path(reader, path, &ret);
56
57 if (!onto) {
58 if (ret == COWL_ERR_IO) {
59 // An I/O error occurred, e.g. the file does not exist or is not
60 // readable. Further details can be obtained by inspecting `errno`.
61 log_error(errno ? strerror(errno) : "unknown I/O error");
62 } else if (ret == COWL_ERR_SYNTAX) {
63 // In case of syntax errors, we can log the last one.
64 log_cowl_error(cowl_reader_last_error(reader));
65 } else {
66 // Some other error occurred.
67 log_error("ontology read failure");
68 }
69 return EXIT_FAILURE;
70 }
71
72 // Stream operations can fail, so we need to check for errors.
73 ret = cowl_ontology_to_stream(onto, uostream_std());
74
75 if (cowl_is_err(ret)) {
76 log_error("ontology write failure");
77 return EXIT_FAILURE;
78 }
79
80 cowl_release_all(onto, reader);
81 return EXIT_SUCCESS;
82}
Querying ontologies
Related documentation: ontology querying
Basic queries
1/*
2 * In this example we will be logging the direct atomic subclasses of a class.
3 *
4 * @note Most errors are not handled for the sake of simplicity.
5 *
6 * @author Ivano Bilenchi
7 *
8 * @copyright Copyright (c) 2019 SisInf Lab, Polytechnic University of Bari
9 * @copyright <https://swot.sisinflab.poliba.it>
10 * @copyright SPDX-License-Identifier: EPL-2.0
11 */
12#include "cowl.h"
13#include "ulib.h"
14#include <stdio.h>
15#include <stdlib.h>
16
17#define ONTO "example_pizza.owl"
18#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
19#define CLASS_NAME "Food"
20
21// Iterator body, invoked for each class expression matching the query.
22static cowl_ret for_each_cls(void *std_out, CowlAny *cls) {
23 // We are only interested in atomic classes. Note that due to pseudo-inheritance
24 // this check ensures that the concrete type of 'exp' is CowlClass.
25 if (cowl_cls_exp_get_type(cls) == COWL_CET_CLASS) {
26 cowl_write_string(std_out, cowl_get_rem(cls));
27 cowl_write_literal(std_out, "\n");
28 }
29 return COWL_CONTINUE;
30}
31
32int main(void) {
33 cowl_init();
34
35 CowlOntology *onto = cowl_ontology_from_path(ustring_literal(ONTO), NULL);
36
37 if (!onto) {
38 fprintf(stderr, "Failed to load ontology " ONTO "\n");
39 return EXIT_FAILURE;
40 }
41
42 // Get the class whose atomic subclasses we are interested in.
43 CowlClass *cls = cowl_class_from_literal(NS CLASS_NAME);
44
45 // Run the query.
46 cowl_write_literal(uostream_std(), "Subclasses of " CLASS_NAME ":\n");
47
48 CowlIterator iter = { uostream_std(), for_each_cls };
49 cowl_ontology_iterate_sub_classes(onto, cls, &iter);
50
51 // Cleanup.
52 cowl_release_all(cls, onto);
53
54 return EXIT_SUCCESS;
55}
Recursive queries
1/*
2 * In this example we will be logging the atomic subclasses of a class recursively.
3 *
4 * @note Most errors are not handled for the sake of simplicity.
5 *
6 * @author Ivano Bilenchi
7 *
8 * @copyright Copyright (c) 2019 SisInf Lab, Polytechnic University of Bari
9 * @copyright <https://swot.sisinflab.poliba.it>
10 * @copyright SPDX-License-Identifier: EPL-2.0
11 */
12#include "cowl.h"
13#include "ulib.h"
14#include <stdio.h>
15#include <stdlib.h>
16
17#define ONTO "example_pizza.owl"
18#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
19#define CLASS_NAME "Food"
20
21// Custom context struct for the query.
22typedef struct CustomContext {
23 CowlOntology *onto;
24 UOStream *stream;
25} CustomContext;
26
27// Iterator body, invoked for each class expression matching the query.
28static cowl_ret for_each_cls(void *ptr, CowlAny *cls) {
29 if (cowl_cls_exp_get_type(cls) != COWL_CET_CLASS) return COWL_CONTINUE;
30
31 // Log the IRI remainder.
32 CustomContext *ctx = ptr;
33 cowl_write_string(ctx->stream, cowl_get_rem(cls));
34 cowl_write_literal(ctx->stream, "\n");
35
36 // Recurse.
37 CowlIterator iter = { ctx, for_each_cls };
38 return cowl_ontology_iterate_sub_classes(ctx->onto, cls, &iter);
39}
40
41int main(void) {
42 cowl_init();
43
44 CowlOntology *onto = cowl_ontology_from_path(ustring_literal(ONTO), NULL);
45
46 if (!onto) {
47 fprintf(stderr, "Failed to load ontology " ONTO "\n");
48 return EXIT_FAILURE;
49 }
50
51 CowlClass *cls = cowl_class_from_literal(NS CLASS_NAME);
52 cowl_write_literal(uostream_std(), "Recursive subclasses of " CLASS_NAME ":\n");
53
54 // Since we are going to perform a recursive query,
55 // we need the ontology to be part of the context.
56 CustomContext ctx = { onto, uostream_std() };
57 CowlIterator iter = { &ctx, for_each_cls };
58 cowl_ontology_iterate_sub_classes(onto, cls, &iter);
59
60 cowl_release_all(cls, onto);
61 return EXIT_SUCCESS;
62}
Advanced queries
1/*
2 * In this example we will be logging axioms of different types referencing
3 * multiple entities.
4 *
5 * @note Most errors are not handled for the sake of simplicity.
6 *
7 * @author Ivano Bilenchi
8 *
9 * @copyright Copyright (c) 2024 SisInf Lab, Polytechnic University of Bari
10 * @copyright <https://swot.sisinflab.poliba.it>
11 * @copyright SPDX-License-Identifier: EPL-2.0
12 */
13#include "cowl.h"
14#include "cowl_axiom_filter.h"
15#include "cowl_axiom_flags.h"
16#include "cowl_obj_prop.h"
17#include "cowl_writer.h"
18#include "ulib.h"
19#include <stdio.h>
20#include <stdlib.h>
21
22#define ONTO "example_pizza.owl"
23#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
24#define CLASS_NAME "ThinAndCrispyBase"
25#define PROPERTY_NAME "hasBase"
26
27int main(void) {
28 cowl_init();
29
30 CowlOntology *onto = cowl_ontology_from_path(ustring_literal(ONTO), NULL);
31
32 if (!onto) {
33 fprintf(stderr, "Failed to load ontology " ONTO "\n");
34 return EXIT_FAILURE;
35 }
36
37 cowl_write_literal(uostream_std(), "Matching axioms:\n");
38 CowlClass *cls = cowl_class_from_literal(NS CLASS_NAME);
39 CowlObjProp *prop = cowl_obj_prop_from_literal(NS PROPERTY_NAME);
40
41 // We want to log all SubClassOf and EquivalentClasses axioms that
42 // reference both the class and the property.
43
44 // Note that this query can be done via other functions as well,
45 // though using a CowlAxiomFilter is usually more efficient, as it
46 // is used to determine the best indexing strategy for the query.
47 CowlAxiomFlags types = COWL_AF_SUB_CLASS | COWL_AF_EQUIV_CLASSES;
48 CowlAxiomFilter filter = cowl_axiom_filter(types);
49 cowl_axiom_filter_add_primitive(&filter, cls);
50 cowl_axiom_filter_add_primitive(&filter, prop);
51
52 // Other than using custom iterators, Cowl provides some built-in ones
53 // for common tasks. Here we use a set iterator: all axioms matching
54 // the filter will be collected in a set, removing any duplicates.
55 UHash(CowlObjectPtr) set = uhset(CowlObjectPtr);
56 CowlIterator iter = cowl_iterator_set(&set, false);
57 cowl_ontology_iterate_axioms_matching(onto, &filter, &iter);
58
59 // [Optional] Adding the pizza prefix to the default prefix map
60 // allows for more concise IRIs when logging the axioms.
61 CowlPrefixMap *pm = cowl_get_prefix_map();
62 UString const prefix = ustring_literal("pizza");
63 UString const ns = ustring_literal(NS);
64 cowl_prefix_map_add_raw(pm, prefix, ns, false);
65
66 // We can now log the axioms by iterating over the set.
67 UOStream *stream = uostream_std();
68 uhash_foreach (CowlObjectPtr, &set, item) {
69 cowl_write(stream, *item.key);
70 cowl_write_literal(stream, "\n");
71 }
72
73 uhash_deinit(CowlObjectPtr, &set);
74 cowl_release_all(cls, prop, onto);
75 return EXIT_SUCCESS;
76}
Editing and writing ontologies
Related documentation: ontology editing, ontology writing
1/*
2 * This example demonstrates ontology editing and serialization to file.
3 *
4 * @note Most errors are not handled for the sake of simplicity.
5 *
6 * @author Ivano Bilenchi
7 *
8 * @copyright Copyright (c) 2022 SisInf Lab, Polytechnic University of Bari
9 * @copyright <https://swot.sisinflab.poliba.it>
10 * @copyright SPDX-License-Identifier: EPL-2.0
11 */
12#include "cowl.h"
13#include "ulib.h"
14#include <stdio.h>
15#include <stdlib.h>
16
17#define IN_PATH "example_pizza.owl"
18#define OUT_PATH "example_pizza_new.owl"
19#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
20
21int main(void) {
22 cowl_init();
23
24 // We will be editing the pizza ontology by adding the Porcini pizza.
25 printf("Reading ontology " IN_PATH "... ");
26 CowlOntology *onto = cowl_ontology_from_path(ustring_literal(IN_PATH), NULL);
27
28 if (onto) {
29 puts("done!");
30 } else {
31 puts("failed");
32 return EXIT_FAILURE;
33 }
34
35 // Declaration(Class(pizza:PorciniTopping))
36 CowlClass *porcini_topping = cowl_class_from_literal(NS "PorciniTopping");
37 CowlAnyAxiom *axiom = cowl_decl_axiom(porcini_topping, NULL);
38 cowl_ontology_add_axiom(onto, axiom);
39 cowl_release(axiom);
40
41 // Declaration(Class(pizza:Porcini))
42 CowlClass *porcini = cowl_class_from_literal(NS "Porcini");
43 axiom = cowl_decl_axiom(porcini, NULL);
44 cowl_ontology_add_axiom(onto, axiom);
45 cowl_release(axiom);
46
47 // SubClassOf(pizza:PorciniTopping pizza:MushroomTopping)
48 CowlClass *mushroom_topping = cowl_class_from_literal(NS "MushroomTopping");
49 axiom = cowl_sub_cls_axiom(porcini_topping, mushroom_topping, NULL);
50 cowl_ontology_add_axiom(onto, axiom);
51 cowl_release_all(axiom, mushroom_topping);
52
53 // SubClassOf(pizza:Porcini pizza:NamedPizza)
54 CowlClass *named_pizza = cowl_class_from_literal(NS "NamedPizza");
55 axiom = cowl_sub_cls_axiom(porcini, named_pizza, NULL);
56 cowl_ontology_add_axiom(onto, axiom);
57 cowl_release_all(axiom, named_pizza);
58
59 // SubClassOf(pizza:Porcini
60 // ObjectSomeValuesFrom(pizza:hasTopping pizza:MozzarellaTopping))
61 CowlObjProp *has_topping = cowl_obj_prop_from_literal(NS "hasTopping");
62 CowlClass *mozzarella_topping = cowl_class_from_literal(NS "MozzarellaTopping");
63 CowlObjQuant *obj_quant = cowl_obj_some(has_topping, mozzarella_topping);
64 axiom = cowl_sub_cls_axiom(porcini, obj_quant, NULL);
65 cowl_ontology_add_axiom(onto, axiom);
66 cowl_release_all(axiom, obj_quant, mozzarella_topping);
67
68 // SubClassOf(pizza:Porcini
69 // ObjectSomeValuesFrom(pizza:hasTopping pizza:PorciniTopping))
70 obj_quant = cowl_obj_some(has_topping, porcini_topping);
71 axiom = cowl_sub_cls_axiom(porcini, obj_quant, NULL);
72 cowl_ontology_add_axiom(onto, axiom);
73 cowl_release_all(axiom, obj_quant);
74
75 // SubClassOf(pizza:Porcini ObjectAllValuesFrom(pizza:hasTopping
76 // ObjectUnionOf(pizza:MozzarellaTopping pizza:PorciniTopping)))
77 CowlNAryCls *closure = cowl_obj_union_of(mozzarella_topping, porcini_topping);
78 obj_quant = cowl_obj_all(has_topping, closure);
79 axiom = cowl_sub_cls_axiom(porcini, obj_quant, NULL);
80 cowl_ontology_add_axiom(onto, axiom);
81 cowl_release_all(axiom, obj_quant, closure);
82 cowl_release_all(porcini_topping, porcini, has_topping);
83
84 // Serialize the edited ontology to a new file.
85 printf("Writing ontology " OUT_PATH "... ");
86 cowl_ret ret = cowl_ontology_to_path(onto, ustring_literal(OUT_PATH));
87
88 if (cowl_is_err(ret)) {
89 puts("failed");
90 } else {
91 puts("done!");
92 }
93
94 cowl_release(onto);
95 return EXIT_SUCCESS;
96}
Ontology streams
Related documentation: input streams, output streams
Input streams
1/*
2 * In this example we will be logging the direct atomic subclasses
3 * of a certain class, but we will do so without instantiating a
4 * CowlOntology object.
5 *
6 * @note Most errors are not handled for the sake of simplicity.
7 *
8 * @author Ivano Bilenchi
9 *
10 * @copyright Copyright (c) 2023 SisInf Lab, Polytechnic University of Bari
11 * @copyright <https://swot.sisinflab.poliba.it>
12 * @copyright SPDX-License-Identifier: EPL-2.0
13 */
14#include "cowl.h"
15#include "ulib.h"
16#include <stdio.h>
17#include <stdlib.h>
18
19#define ONTO "example_pizza.owl"
20#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
21#define CLASS_NAME "Food"
22
23// Change handler, invoked for each construct read from the ontology document.
24static cowl_ret handle_axiom(void *target_class, CowlChange change) {
25 // We are only interested in subclass axioms.
26 if (change.part != COWL_PART_AXIOM) return COWL_OK;
27
28 CowlAnyAxiom *axiom = change.value;
29 if (cowl_axiom_get_type(axiom) != COWL_AT_SUB_CLASS) return COWL_OK;
30
31 // We are only interested in axioms where the superclass is the target class.
32 CowlAnyClsExp *cls = cowl_sub_cls_axiom_get_super(axiom);
33 if (!cowl_equals(target_class, cls)) return COWL_OK;
34
35 // We are only interested in axioms where the subclass is atomic.
36 cls = cowl_sub_cls_axiom_get_sub(axiom);
37 if (cowl_cls_exp_get_type(cls) != COWL_CET_CLASS) return COWL_OK;
38
39 // Log the IRI remainder.
40 puts(cowl_string_get_cstring(cowl_get_rem(cls)));
41 return COWL_OK;
42}
43
44int main(void) {
45 cowl_init();
46
47 CowlClass *target_class = cowl_class_from_literal(NS CLASS_NAME);
48
49 // Configure the change handler for the incoming stream.
50 CowlChangeHandler handler = {
51 .ctx = target_class,
52 .handle = handle_axiom,
53 };
54
55 // Process the ontology document as a stream of changes.
56 puts("Atomic subclasses of " CLASS_NAME ":");
57 CowlReader *reader = cowl_get_reader();
58 if (cowl_reader_read_path(reader, ustring_literal(ONTO), handler)) {
59 return EXIT_FAILURE;
60 }
61
62 cowl_release(target_class);
63 return EXIT_SUCCESS;
64}
Output streams
1/*
2 * In this example we will be creating a new ontology document by using an
3 * ontology output stream.
4 *
5 * @note Most errors are not handled for the sake of simplicity.
6 *
7 * @author Ivano Bilenchi
8 *
9 * @copyright Copyright (c) 2023 SisInf Lab, Polytechnic University of Bari
10 * @copyright <https://swot.sisinflab.poliba.it>
11 * @copyright SPDX-License-Identifier: EPL-2.0
12 */
13
14#include "cowl.h"
15#include "ulib.h"
16#include <stdio.h>
17#include <stdlib.h>
18
19#define PATH "porcini_pizza.owl"
20
21#define IMPORT_IRI "http://www.co-ode.org/ontologies/pizza"
22#define IMPORT_NS IMPORT_IRI "/pizza.owl#"
23
24#define IRI "http://example.com/ontologies/porcini_pizza"
25#define NS IRI "/porcini_pizza.owl#"
26
27int main(void) {
28 cowl_init();
29
30 printf("Generating ontology " PATH "... ");
31
32 UOStream ostream;
33 if (uostream_to_path(&ostream, PATH)) {
34 // Initializing and writing to the stream may fail.
35 // IO errors should be handled as fit for the application.
36 goto err_io;
37 }
38
39 CowlWriter *writer = cowl_get_writer();
40
41 // Optional: setup prefixes so that IRIs can be rendered in their prefixed form.
42 CowlPrefixMap *map = cowl_prefix_map();
43 cowl_prefix_map_add_raw(map, ustring_literal(""), ustring_literal(NS), false);
44 cowl_prefix_map_add_raw(map, ustring_literal("pizza"),
45 ustring_literal(IMPORT_NS), false);
46
47 // Write the ontology header.
48 CowlIRI *iri = cowl_iri_from_literal(IRI);
49 CowlIRI *import_iri = cowl_iri_from_literal(IMPORT_IRI);
50 UVec(CowlObjectPtr) imports = uvec(CowlObjectPtr);
51 uvec_push(CowlObjectPtr, &imports, import_iri);
52
53 CowlOntologyHeader header = {
54 .pm = map,
55 .iri = iri,
56 .imports = &imports,
57 };
58
59 if (cowl_writer_write_header(writer, &ostream, header)) goto err_io;
60
61 cowl_release_all(iri, import_iri, map);
62 uvec_deinit(CowlObjectPtr, &imports);
63
64 // Write the axioms.
65 // Declaration(Class(:PorciniTopping))
66 CowlClass *porcini = cowl_class_from_literal(NS "PorciniTopping");
67 CowlAnyAxiom *axiom = cowl_decl_axiom(porcini, NULL);
68 if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
69 cowl_release(axiom);
70
71 // Declaration(Class(:Porcini))
72 CowlClass *porcini_pizza = cowl_class_from_literal(NS "Porcini");
73 axiom = cowl_decl_axiom(porcini_pizza, NULL);
74 if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
75 cowl_release(axiom);
76
77 // SubClassOf(:PorciniTopping pizza:MushroomTopping)
78 CowlClass *mushroom = cowl_class_from_literal(IMPORT_NS "MushroomTopping");
79 axiom = cowl_sub_cls_axiom(porcini, mushroom, NULL);
80 if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
81 cowl_release_all(axiom, mushroom);
82
83 // SubClassOf(:Porcini pizza:NamedPizza)
84 CowlClass *named_pizza = cowl_class_from_literal(IMPORT_NS "NamedPizza");
85 axiom = cowl_sub_cls_axiom(porcini_pizza, named_pizza, NULL);
86 if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
87 cowl_release_all(axiom, named_pizza);
88
89 // SubClassOf(:Porcini
90 // ObjectSomeValuesFrom(pizza:hasTopping pizza:MozzarellaTopping))
91 CowlObjProp *has_topping = cowl_obj_prop_from_literal(IMPORT_NS "hasTopping");
92 CowlClass *mozzarella = cowl_class_from_literal(IMPORT_NS "MozzarellaTopping");
93 CowlObjQuant *obj_quant = cowl_obj_some(has_topping, mozzarella);
94 axiom = cowl_sub_cls_axiom(porcini_pizza, obj_quant, NULL);
95 if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
96 cowl_release_all(axiom, obj_quant);
97
98 // SubClassOf(:Porcini
99 // ObjectSomeValuesFrom(pizza:hasTopping :PorciniTopping))
100 obj_quant = cowl_obj_some(has_topping, porcini);
101 axiom = cowl_sub_cls_axiom(porcini_pizza, obj_quant, NULL);
102 if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
103 cowl_release_all(axiom, obj_quant);
104
105 // SubClassOf(:Porcini ObjectAllValuesFrom(pizza:hasTopping
106 // ObjectUnionOf(pizza:MozzarellaTopping :PorciniTopping)))
107 CowlNAryCls *closure = cowl_obj_union_of(mozzarella, porcini);
108 obj_quant = cowl_obj_all(has_topping, closure);
109 axiom = cowl_sub_cls_axiom(porcini_pizza, obj_quant, NULL);
110 if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
111 cowl_release_all(axiom, obj_quant, closure);
112 cowl_release_all(porcini, porcini_pizza, has_topping, mozzarella);
113
114 // Finally, write the footer.
115 if (cowl_writer_write_footer(writer, &ostream)) goto err_io;
116 uostream_deinit(&ostream);
117
118 puts("done!");
119 return EXIT_SUCCESS;
120
121err_io:
122 puts("failed");
123 return EXIT_FAILURE;
124}