From a7019614896fdd3e29b9a28f6a8cfd2c1b365983 Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Sun, 1 Mar 2015 20:47:25 +0100 Subject: Renamed domain to ontology. --- test/core/model/DocumentTest.cpp | 42 +- test/core/model/DomainTest.cpp | 689 -------------------------------- test/core/model/OntologyTest.cpp | 689 ++++++++++++++++++++++++++++++++ test/core/model/TestAdvanced.hpp | 68 ++-- test/core/model/TestDocument.hpp | 8 +- test/core/model/TestDocumentBuilder.hpp | 12 +- test/core/model/TestDomain.hpp | 93 ----- test/core/model/TestOntology.hpp | 93 +++++ 8 files changed, 847 insertions(+), 847 deletions(-) delete mode 100644 test/core/model/DomainTest.cpp create mode 100644 test/core/model/OntologyTest.cpp delete mode 100644 test/core/model/TestDomain.hpp create mode 100644 test/core/model/TestOntology.hpp (limited to 'test/core/model') diff --git a/test/core/model/DocumentTest.cpp b/test/core/model/DocumentTest.cpp index 1bb2356..8ed59f5 100644 --- a/test/core/model/DocumentTest.cpp +++ b/test/core/model/DocumentTest.cpp @@ -23,10 +23,10 @@ #include #include #include -#include +#include #include "TestDocument.hpp" -#include "TestDomain.hpp" +#include "TestOntology.hpp" namespace ousia { @@ -36,10 +36,10 @@ TEST(Document, construct) TerminalLogger logger{std::cerr, true}; Manager mgr{1}; Rooted sys{new SystemTypesystem(mgr)}; - // Get the domain. - Rooted domain = constructBookDomain(mgr, sys, logger); + // Get the ontology. + Rooted ontology = constructBookOntology(mgr, sys, logger); // Construct the document. - Rooted doc = constructBookDocument(mgr, logger, domain); + Rooted doc = constructBookDocument(mgr, logger, ontology); // Check the document content. ASSERT_FALSE(doc.isNull()); @@ -111,22 +111,22 @@ TEST(Document, construct) TEST(Document, validate) { - // Let's start with a trivial domain and a trivial document. + // Let's start with a trivial ontology and a trivial document. TerminalLogger logger{std::cerr, true}; Manager mgr{1}; Rooted sys{new SystemTypesystem(mgr)}; - Rooted domain{new Domain(mgr, sys, "trivial")}; + Rooted ontology{new Ontology(mgr, sys, "trivial")}; Cardinality single; single.merge({1}); // Set up the "root" StructuredClass. Rooted rootClass{new StructuredClass( - mgr, "root", domain, single, {nullptr}, false, true)}; + mgr, "root", ontology, single, {nullptr}, false, true)}; // set up a document for it. { // first an invalid one, which is empty. Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(domain); + doc->referenceOntology(ontology); ASSERT_EQ(ValidationState::UNKNOWN, doc->getValidationState()); ASSERT_FALSE(doc->validate(logger)); // then add a root, which should make it valid. @@ -138,7 +138,7 @@ TEST(Document, validate) { // A root with an invalid name, however, should make it invalid Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(domain); + doc->referenceOntology(ontology); Rooted root = buildRootStructuredEntity( doc, logger, {"root"}, {}, "my invalid root"); ASSERT_EQ(ValidationState::UNKNOWN, doc->getValidationState()); @@ -150,7 +150,7 @@ TEST(Document, validate) rootClass->createFieldDescriptor(logger).first; // and add a child class for it. Rooted childClass{ - new StructuredClass(mgr, "child", domain, single)}; + new StructuredClass(mgr, "child", ontology, single)}; rootField->addChild(childClass); { /* @@ -158,7 +158,7 @@ TEST(Document, validate) * document should be invalid again. */ Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(domain); + doc->referenceOntology(ontology); Rooted root = buildRootStructuredEntity(doc, logger, {"root"}); ASSERT_EQ(ValidationState::UNKNOWN, doc->getValidationState()); @@ -173,16 +173,16 @@ TEST(Document, validate) ASSERT_FALSE(doc->validate(logger)); } /* - * Add a further extension to the domain: We add a subclass to child. + * Add a further extension to the ontology: We add a subclass to child. */ Rooted childSubClass{ - new StructuredClass(mgr, "childSub", domain, single, childClass)}; + new StructuredClass(mgr, "childSub", ontology, single, childClass)}; { /* * A document with one instance of the Child subclass should be valid. */ Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(domain); + doc->referenceOntology(ontology); Rooted root = buildRootStructuredEntity(doc, logger, {"root"}); buildStructuredEntity(doc, logger, root, {"childSub"}); @@ -202,7 +202,7 @@ TEST(Document, validate) * invalid, because it has no children of itself. */ Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(domain); + doc->referenceOntology(ontology); Rooted root = buildRootStructuredEntity(doc, logger, {"root"}); buildStructuredEntity(doc, logger, root, {"childSub"}); @@ -223,7 +223,7 @@ TEST(Document, validate) * valid, because of the override. */ Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(domain); + doc->referenceOntology(ontology); Rooted root = buildRootStructuredEntity(doc, logger, {"root"}); buildStructuredEntity(doc, logger, root, {"childSub"}); @@ -241,7 +241,7 @@ TEST(Document, validate) * invalid again, because we are missing the primitive content. */ Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(domain); + doc->referenceOntology(ontology); Rooted root = buildRootStructuredEntity(doc, logger, {"root"}); Rooted child = @@ -259,14 +259,14 @@ TEST(Document, validate) ASSERT_TRUE(doc->validate(logger)); } - // Now add an Annotation class to the domain. - Rooted annoClass{new AnnotationClass(mgr, "anno", domain)}; + // Now add an Annotation class to the ontology. + Rooted annoClass{new AnnotationClass(mgr, "anno", ontology)}; { /* * Create a valid document in itself. */ Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(domain); + doc->referenceOntology(ontology); Rooted root = buildRootStructuredEntity(doc, logger, {"root"}); Rooted start{new Anchor(mgr, root)}; diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp deleted file mode 100644 index 6bbf26d..0000000 --- a/test/core/model/DomainTest.cpp +++ /dev/null @@ -1,689 +0,0 @@ -/* - Ousía - Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include - -#include - -#include -#include -#include - -#include "TestDomain.hpp" - -namespace ousia { - -void assert_path(const ResolutionResult &res, const Rtti *expected_type, - std::vector expected_path) -{ - // Check class/type - ASSERT_TRUE(res.node->isa(expected_type)); - - // Check path - ASSERT_EQ(expected_path, res.node->path()); -} - -TEST(Domain, testDomainResolving) -{ - // Construct Manager - Logger logger; - Manager mgr{1}; - Rooted sys{new SystemTypesystem(mgr)}; - // Get the domain. - Rooted domain = constructBookDomain(mgr, sys, logger); - - std::vector res; - - // There is one domain called "book" - res = domain->resolve(&RttiTypes::Domain, "book"); - ASSERT_EQ(1U, res.size()); - assert_path(res[0], &RttiTypes::Domain, {"book"}); - - // There is one domain called "book" - res = domain->resolve(&RttiTypes::StructuredClass, "book"); - ASSERT_EQ(1U, res.size()); - assert_path(res[0], &RttiTypes::StructuredClass, {"book", "book"}); - - // If we explicitly ask for the "book, book" path, then only the - // StructuredClass should be returned. - res = domain->resolve(&RttiTypes::Domain, - std::vector{"book", "book"}); - ASSERT_EQ(0U, res.size()); - - res = domain->resolve(&RttiTypes::StructuredClass, - std::vector{"book", "book"}); - ASSERT_EQ(1U, res.size()); - - // If we ask for "section" the result should be unique as well. - res = domain->resolve(&RttiTypes::StructuredClass, "section"); - ASSERT_EQ(1U, res.size()); - assert_path(res[0], &RttiTypes::StructuredClass, {"book", "section"}); - - // If we ask for "paragraph" it is referenced two times in the Domain graph, - // but should be returned only once. - res = domain->resolve(&RttiTypes::StructuredClass, "paragraph"); - ASSERT_EQ(1U, res.size()); - assert_path(res[0], &RttiTypes::StructuredClass, {"book", "paragraph"}); -} - -// i use this wrapper due to the strange behaviour of GTEST. -static void assertFalse(bool b){ - ASSERT_FALSE(b); -} - -static Rooted createUnsortedPrimitiveField( - Handle strct, Handle type, Logger &logger, bool tree, - std::string name) -{ - FieldDescriptor::FieldType fieldType = FieldDescriptor::FieldType::SUBTREE; - if (tree) { - fieldType = FieldDescriptor::FieldType::TREE; - } - - auto res = strct->createPrimitiveFieldDescriptor(type, logger, fieldType, - std::move(name)); - assertFalse(res.second); - return res.first; -} - -TEST(StructuredClass, getFieldDescriptors) -{ - /* - * We construct a case with the three levels: - * 1.) A has the SUBTREE fields a and b as well as a TREE field. - * 2.) B is a subclass of A and has the SUBTREE fields b and c as well as - * a TREE field. - * 3.) C is a subclass of B and has the SUBTREE field a. - * As a result we expect C to have none of As fields, the TREE field of B, - * and the SUBTREE fields a (of C) , b and c (of B). - */ - TerminalLogger logger{std::cout}; - Manager mgr{1}; - Rooted sys{new SystemTypesystem(mgr)}; - Rooted domain{new Domain(mgr, sys, "myDomain")}; - - Rooted A{new StructuredClass( - mgr, "A", domain, Cardinality::any(), nullptr, false, true)}; - Rooted A_a = createUnsortedPrimitiveField( - A, sys->getStringType(), logger, false, "a"); - Rooted A_b = createUnsortedPrimitiveField( - A, sys->getStringType(), logger, false, "b"); - Rooted A_main = createUnsortedPrimitiveField( - A, sys->getStringType(), logger, true, "somename"); - - Rooted B{new StructuredClass( - mgr, "B", domain, Cardinality::any(), A, false, true)}; - Rooted B_b = createUnsortedPrimitiveField( - B, sys->getStringType(), logger, false, "b"); - Rooted B_c = createUnsortedPrimitiveField( - B, sys->getStringType(), logger, false, "c"); - Rooted B_main = createUnsortedPrimitiveField( - B, sys->getStringType(), logger, true, "othername"); - - Rooted C{new StructuredClass( - mgr, "C", domain, Cardinality::any(), B, false, true)}; - Rooted C_a = createUnsortedPrimitiveField( - C, sys->getStringType(), logger, false, "a"); - - ASSERT_TRUE(domain->validate(logger)); - - // check all FieldDescriptors - { - NodeVector fds = A->getFieldDescriptors(); - ASSERT_EQ(3, fds.size()); - ASSERT_EQ(A_a, fds[0]); - ASSERT_EQ(A_b, fds[1]); - ASSERT_EQ(A_main, fds[2]); - } - { - NodeVector fds = B->getFieldDescriptors(); - ASSERT_EQ(4, fds.size()); - ASSERT_EQ(A_a, fds[0]); - ASSERT_EQ(B_b, fds[1]); - ASSERT_EQ(B_c, fds[2]); - ASSERT_EQ(B_main, fds[3]); - } - { - NodeVector fds = C->getFieldDescriptors(); - ASSERT_EQ(4, fds.size()); - ASSERT_EQ(B_b, fds[0]); - ASSERT_EQ(B_c, fds[1]); - // superclass fields come before subclass fields (except for the TREE - // field, which is always last). - ASSERT_EQ(C_a, fds[2]); - ASSERT_EQ(B_main, fds[3]); - } -} - - -TEST(StructuredClass, getFieldDescriptorsCycles) -{ - Logger logger; - Manager mgr{1}; - Rooted sys{new SystemTypesystem(mgr)}; - Rooted domain{new Domain(mgr, sys, "myDomain")}; - - Rooted A{new StructuredClass( - mgr, "A", domain, Cardinality::any(), nullptr, false, true)}; - A->addSubclass(A, logger); - Rooted A_a = createUnsortedPrimitiveField( - A, sys->getStringType(), logger, false, "a"); - ASSERT_FALSE(domain->validate(logger)); - // if we call getFieldDescriptors that should still return a valid result. - NodeVector fds = A->getFieldDescriptors(); - ASSERT_EQ(1, fds.size()); - ASSERT_EQ(A_a, fds[0]); -} - -Rooted getClass(const std::string name, Handle dom) -{ - std::vector res = - dom->resolve(&RttiTypes::StructuredClass, name); - return res[0].node.cast(); -} - -TEST(Descriptor, pathTo) -{ - // Start with some easy examples from the book domain. - TerminalLogger logger{std::cout}; - Manager mgr{1}; - Rooted sys{new SystemTypesystem(mgr)}; - // Get the domain. - Rooted domain = constructBookDomain(mgr, sys, logger); - - // get the book node and the section node. - Rooted book = getClass("book", domain); - Rooted section = getClass("section", domain); - // get the path in between. - NodeVector path = book->pathTo(section, logger); - ASSERT_EQ(1U, path.size()); - ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); - - // get the text node. - Rooted text = getClass("text", domain); - // get the path between book and text via paragraph. - path = book->pathTo(text, logger); - ASSERT_EQ(3U, path.size()); - ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); - ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); - ASSERT_EQ("paragraph", path[1]->getName()); - ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); - - // get the subsection node. - Rooted subsection = getClass("subsection", domain); - // try to get the path between book and subsection. - path = book->pathTo(subsection, logger); - // this should be impossible. - ASSERT_EQ(0U, path.size()); - - // try to construct the path between section and the text field. - auto res = section->pathTo(text->getFieldDescriptor(), logger); - ASSERT_TRUE(res.second); - path = res.first; - ASSERT_EQ(4U, path.size()); - ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); - ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); - ASSERT_EQ("paragraph", path[1]->getName()); - ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); - ASSERT_TRUE(path[3]->isa(&RttiTypes::StructuredClass)); - ASSERT_EQ("text", path[3]->getName()); -} - -TEST(Descriptor, pathToAdvanced) -{ - /* - * Now we build a really nasty domain with lots of transparency - * and inheritance. The basic idea is to have three paths from start to - * finish, where one is blocked by overriding fields and the longer valid - * one is found first such that it has to be replaced by the shorter one - * during the search. - * - * To achieve that we have the following structure: - * 1.) The start class inherits from A. - * 2.) A has B as child in the default field. - * 3.) B is transparent and has no children (but C as subclass) - * 4.) C is a subclass of B, transparent and has - * the target as child (shortest path). - * 5.) A has D as child in the default field. - * 6.) D is transparent has E as child in the default field. - * 7.) E is transparent and has target as child in the default field - * (longer path) - * - * So the path A_second_field, C, C_field should be returned. - */ - Manager mgr{1}; - TerminalLogger logger{std::cout}; - Rooted sys{new SystemTypesystem(mgr)}; - // Construct the domain - Rooted domain{new Domain(mgr, sys, "nasty")}; - - // Let's create the classes that we need first - Rooted A{new StructuredClass( - mgr, "A", domain, Cardinality::any(), {nullptr}, false, true)}; - - Rooted start{new StructuredClass( - mgr, "start", domain, Cardinality::any(), A, false, false)}; - - Rooted B{new StructuredClass( - mgr, "B", domain, Cardinality::any(), {nullptr}, true, false)}; - - Rooted C{new StructuredClass( - mgr, "C", domain, Cardinality::any(), B, true, false)}; - - Rooted D{new StructuredClass( - mgr, "D", domain, Cardinality::any(), {nullptr}, true, false)}; - - Rooted E{new StructuredClass( - mgr, "E", domain, Cardinality::any(), {nullptr}, true, false)}; - - Rooted target{ - new StructuredClass(mgr, "target", domain, Cardinality::any())}; - - // We create a field for A - Rooted A_field = A->createFieldDescriptor(logger).first; - A_field->addChild(B); - A_field->addChild(D); - - // We create no field for B - // One for C - Rooted C_field = C->createFieldDescriptor(logger).first; - C_field->addChild(target); - - // One for D - Rooted D_field = D->createFieldDescriptor(logger).first; - D_field->addChild(E); - - // One for E - Rooted E_field = E->createFieldDescriptor(logger).first; - E_field->addChild(target); - - ASSERT_TRUE(domain->validate(logger)); - -#ifdef MANAGER_GRAPHVIZ_EXPORT - // dump the manager state - mgr.exportGraphviz("nastyDomain.dot"); -#endif - - // and now we should be able to find the shortest path as suggested - NodeVector path = start->pathTo(target, logger); - ASSERT_EQ(3U, path.size()); - ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); - ASSERT_EQ("", path[0]->getName()); - ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); - ASSERT_EQ("C", path[1]->getName()); - ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); - ASSERT_EQ("", path[2]->getName()); -} - -TEST(Descriptor, pathToCycles) -{ - // build a domain with a cycle. - Manager mgr{1}; - Logger logger; - Rooted sys{new SystemTypesystem(mgr)}; - // Construct the domain - Rooted domain{new Domain(mgr, sys, "cycles")}; - Rooted A{new StructuredClass( - mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)}; - A->addSubclass(A, logger); - ASSERT_FALSE(domain->validate(logger)); - Rooted B{new StructuredClass( - mgr, "B", domain, Cardinality::any(), {nullptr}, false, true)}; - Rooted A_field = A->createFieldDescriptor(logger).first; - A_field->addChild(B); - /* - * Now try to create the path from A to B. A direct path is possible but - * in the worst case this could also try to find shorter paths via an - * endless repition of A instances. - * As we cut the search tree at paths that are longer than our current - * optimum this should not happen, though. - */ - NodeVector path = A->pathTo(B, logger); - ASSERT_EQ(1, path.size()); - ASSERT_EQ(A_field, path[0]); -} - -TEST(Descriptor, getDefaultFields) -{ - // construct a domain with lots of default fields to test. - // start with a single structure class. - Manager mgr{1}; - TerminalLogger logger{std::cout}; - Rooted sys{new SystemTypesystem(mgr)}; - // Construct the domain - Rooted domain{new Domain(mgr, sys, "nasty")}; - - Rooted A{new StructuredClass( - mgr, "A", domain, Cardinality::any(), nullptr, false, true)}; - - // in this trivial case no field should be found. - ASSERT_TRUE(A->getDefaultFields().empty()); - - // create a field. - Rooted A_prim_field = - A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; - // now we should find that. - auto fields = A->getDefaultFields(); - ASSERT_EQ(1U, fields.size()); - ASSERT_EQ(A_prim_field, fields[0]); - - // remove that field from A and add it to another class. - - Rooted B{new StructuredClass( - mgr, "B", domain, Cardinality::any(), nullptr, false, true)}; - - B->moveFieldDescriptor(A_prim_field, logger); - - // new we shouldn't find the field anymore. - ASSERT_TRUE(A->getDefaultFields().empty()); - - // but we should find it again if we set B as superclass of A. - A->setSuperclass(B, logger); - fields = A->getDefaultFields(); - ASSERT_EQ(1U, fields.size()); - ASSERT_EQ(A_prim_field, fields[0]); - - // and we should not be able to find it if we override the field. - Rooted A_field = A->createFieldDescriptor(logger).first; - ASSERT_TRUE(A->getDefaultFields().empty()); - - // add a transparent child class. - - Rooted C{new StructuredClass( - mgr, "C", domain, Cardinality::any(), nullptr, true, false)}; - A_field->addChild(C); - - // add a primitive field for it. - Rooted C_field = - C->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; - - // now we should find that. - fields = A->getDefaultFields(); - ASSERT_EQ(1U, fields.size()); - ASSERT_EQ(C_field, fields[0]); - - // add another transparent child class to A with a daughter class that has - // in turn a subclass with a primitive field. - Rooted D{new StructuredClass( - mgr, "D", domain, Cardinality::any(), nullptr, true, false)}; - A_field->addChild(D); - Rooted D_field = D->createFieldDescriptor(logger).first; - Rooted E{new StructuredClass( - mgr, "E", domain, Cardinality::any(), nullptr, true, false)}; - D_field->addChild(E); - Rooted F{new StructuredClass( - mgr, "E", domain, Cardinality::any(), E, true, false)}; - Rooted F_field = - F->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; - - // now we should find both primitive fields, but the C field first. - fields = A->getDefaultFields(); - ASSERT_EQ(2U, fields.size()); - ASSERT_EQ(C_field, fields[0]); - ASSERT_EQ(F_field, fields[1]); -} - -TEST(Descriptor, getDefaultFieldsCycles) -{ - // build a domain with a cycle. - Manager mgr{1}; - Logger logger; - Rooted sys{new SystemTypesystem(mgr)}; - // Construct the domain - Rooted domain{new Domain(mgr, sys, "cycles")}; - Rooted A{new StructuredClass( - mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)}; - A->addSubclass(A, logger); - ASSERT_FALSE(domain->validate(logger)); - Rooted A_field = - A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; - /* - * Now try to get the default fields of A. This should not lead to cycles - * if we correctly note all already visited nodes. - */ - NodeVector defaultFields = A->getDefaultFields(); - ASSERT_EQ(1, defaultFields.size()); - ASSERT_EQ(A_field, defaultFields[0]); -} - -TEST(Descriptor, getPermittedChildren) -{ - // analyze the book domain. - TerminalLogger logger{std::cout}; - Manager mgr{1}; - Rooted sys{new SystemTypesystem(mgr)}; - // Get the domain. - Rooted domain = constructBookDomain(mgr, sys, logger); - // get the relevant classes. - Rooted book = getClass("book", domain); - Rooted section = getClass("section", domain); - Rooted paragraph = getClass("paragraph", domain); - Rooted text = getClass("text", domain); - /* - * as permitted children we expect section, paragraph and text in exactly - * that order. section should be before paragraph because of declaration - * order and text should be last because it needs a transparent paragraph - * in between. - */ - NodeVector children = book->getPermittedChildren(); - ASSERT_EQ(3U, children.size()); - ASSERT_EQ(section, children[0]); - ASSERT_EQ(paragraph, children[1]); - ASSERT_EQ(text, children[2]); - - // Now we add a subclass to text. - Rooted sub{new StructuredClass( - mgr, "Subclass", domain, Cardinality::any(), text, true, false)}; - // And that should be in the result list as well now. - children = book->getPermittedChildren(); - ASSERT_EQ(4U, children.size()); - ASSERT_EQ(section, children[0]); - ASSERT_EQ(paragraph, children[1]); - ASSERT_EQ(text, children[2]); - ASSERT_EQ(sub, children[3]); -} - -TEST(Descriptor, getPermittedChildrenCycles) -{ - // build a domain with a cycle. - Manager mgr{1}; - Logger logger; - Rooted sys{new SystemTypesystem(mgr)}; - // Construct the domain - Rooted domain{new Domain(mgr, sys, "cycles")}; - Rooted A{new StructuredClass( - mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)}; - A->addSubclass(A, logger); - ASSERT_FALSE(domain->validate(logger)); - Rooted A_field = A->createFieldDescriptor(logger).first; - // we make the cycle worse by adding A as child of itself. - A_field->addChild(A); - /* - * Now try to get the permitted children of A. This should not lead to - * cycles - * if we correctly note all already visited nodes. - */ - NodeVector children = A->getPermittedChildren(); - ASSERT_EQ(1, children.size()); - ASSERT_EQ(A, children[0]); -} - -TEST(StructuredClass, isSubclassOf) -{ - // create an inheritance hierarchy. - Manager mgr{1}; - Rooted sys{new SystemTypesystem(mgr)}; - Rooted domain{new Domain(mgr, sys, "inheritance")}; - Rooted A{new StructuredClass( - mgr, "A", domain, Cardinality::any(), {nullptr}, false, true)}; - // first branch - Rooted B{ - new StructuredClass(mgr, "B", domain, Cardinality::any(), A)}; - Rooted C{ - new StructuredClass(mgr, "C", domain, Cardinality::any(), B)}; - // second branch - Rooted D{ - new StructuredClass(mgr, "D", domain, Cardinality::any(), A)}; - Rooted E{ - new StructuredClass(mgr, "E", domain, Cardinality::any(), D)}; - Rooted F{ - new StructuredClass(mgr, "F", domain, Cardinality::any(), D)}; - - // check function results - ASSERT_FALSE(A->isSubclassOf(A)); - ASSERT_FALSE(A->isSubclassOf(B)); - ASSERT_FALSE(A->isSubclassOf(C)); - ASSERT_FALSE(A->isSubclassOf(D)); - ASSERT_FALSE(A->isSubclassOf(E)); - ASSERT_FALSE(A->isSubclassOf(F)); - - ASSERT_TRUE(B->isSubclassOf(A)); - ASSERT_FALSE(B->isSubclassOf(B)); - ASSERT_FALSE(B->isSubclassOf(C)); - ASSERT_FALSE(B->isSubclassOf(D)); - ASSERT_FALSE(B->isSubclassOf(E)); - ASSERT_FALSE(B->isSubclassOf(F)); - - ASSERT_TRUE(C->isSubclassOf(A)); - ASSERT_TRUE(C->isSubclassOf(B)); - ASSERT_FALSE(C->isSubclassOf(C)); - ASSERT_FALSE(C->isSubclassOf(D)); - ASSERT_FALSE(C->isSubclassOf(E)); - ASSERT_FALSE(C->isSubclassOf(F)); - - ASSERT_TRUE(D->isSubclassOf(A)); - ASSERT_FALSE(D->isSubclassOf(B)); - ASSERT_FALSE(D->isSubclassOf(C)); - ASSERT_FALSE(D->isSubclassOf(D)); - ASSERT_FALSE(D->isSubclassOf(E)); - ASSERT_FALSE(D->isSubclassOf(F)); - - ASSERT_TRUE(E->isSubclassOf(A)); - ASSERT_FALSE(E->isSubclassOf(B)); - ASSERT_FALSE(E->isSubclassOf(C)); - ASSERT_TRUE(E->isSubclassOf(D)); - ASSERT_FALSE(E->isSubclassOf(E)); - ASSERT_FALSE(E->isSubclassOf(F)); - - ASSERT_TRUE(F->isSubclassOf(A)); - ASSERT_FALSE(F->isSubclassOf(B)); - ASSERT_FALSE(F->isSubclassOf(C)); - ASSERT_TRUE(F->isSubclassOf(D)); - ASSERT_FALSE(F->isSubclassOf(E)); - ASSERT_FALSE(F->isSubclassOf(F)); -} - -TEST(Domain, validate) -{ - TerminalLogger logger{std::cerr, true}; - Manager mgr{1}; - Rooted sys{new SystemTypesystem(mgr)}; - // start with an easy example: Our book domain should be valid. - { - Rooted domain = constructBookDomain(mgr, sys, logger); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - } - { - // Even easier: An empty domain should be valid. - Rooted domain{new Domain(mgr, sys, "domain")}; - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // if we add a StructureClass it should be valid still. - Rooted base{ - new StructuredClass(mgr, "myClass", domain)}; - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // if we tamper with the name, however, it shouldn't be valid anymore. - base->setName(""); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_FALSE(domain->validate(logger)); - base->setName("my class"); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_FALSE(domain->validate(logger)); - base->setName("myClass"); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // Let's add a primitive field (without a primitive type at first) - Rooted base_field = - base->createPrimitiveFieldDescriptor(nullptr, logger).first; - // this should not be valid. - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_FALSE(domain->validate(logger)); - // but it should be if we set the type. - base_field->setPrimitiveType(sys->getStringType()); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // add a subclass for our base class. - Rooted sub{new StructuredClass(mgr, "sub", domain)}; - // this should be valid in itself. - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // and still if we add a superclass. - sub->setSuperclass(base, logger); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // and still if we remove the subclass from the base class. - base->removeSubclass(sub, logger); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - ASSERT_EQ(nullptr, sub->getSuperclass()); - // and still if we re-add it. - base->addSubclass(sub, logger); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - ASSERT_EQ(base, sub->getSuperclass()); - // add a non-primitive field to the child class. - Rooted sub_field = - sub->createFieldDescriptor(logger).first; - // this should not be valid because we allow no children. - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_FALSE(domain->validate(logger)); - // we should also be able to add a child and make it valid. - sub_field->addChild(base); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // it should be invalid if we add it twice. - sub_field->addChild(base); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_FALSE(domain->validate(logger)); - // and valid again if we remove it once. - sub_field->removeChild(base); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // if we set a primitive type it should be invalid - sub_field->setPrimitiveType(sys->getStringType()); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_FALSE(domain->validate(logger)); - // and valid again if we unset it. - sub_field->setPrimitiveType(nullptr); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // It should be invalid if we set another TREE field. - Rooted sub_field2 = - sub->createFieldDescriptor(logger, FieldDescriptor::FieldType::TREE, - "test", false).first; - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_FALSE(domain->validate(logger)); - // but valid again if we remove it - sub->removeFieldDescriptor(sub_field2); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - } -} -} \ No newline at end of file diff --git a/test/core/model/OntologyTest.cpp b/test/core/model/OntologyTest.cpp new file mode 100644 index 0000000..764dcb4 --- /dev/null +++ b/test/core/model/OntologyTest.cpp @@ -0,0 +1,689 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include + +#include +#include +#include + +#include "TestOntology.hpp" + +namespace ousia { + +void assert_path(const ResolutionResult &res, const Rtti *expected_type, + std::vector expected_path) +{ + // Check class/type + ASSERT_TRUE(res.node->isa(expected_type)); + + // Check path + ASSERT_EQ(expected_path, res.node->path()); +} + +TEST(Ontology, testOntologyResolving) +{ + // Construct Manager + Logger logger; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + // Get the ontology. + Rooted ontology = constructBookOntology(mgr, sys, logger); + + std::vector res; + + // There is one ontology called "book" + res = ontology->resolve(&RttiTypes::Ontology, "book"); + ASSERT_EQ(1U, res.size()); + assert_path(res[0], &RttiTypes::Ontology, {"book"}); + + // There is one ontology called "book" + res = ontology->resolve(&RttiTypes::StructuredClass, "book"); + ASSERT_EQ(1U, res.size()); + assert_path(res[0], &RttiTypes::StructuredClass, {"book", "book"}); + + // If we explicitly ask for the "book, book" path, then only the + // StructuredClass should be returned. + res = ontology->resolve(&RttiTypes::Ontology, + std::vector{"book", "book"}); + ASSERT_EQ(0U, res.size()); + + res = ontology->resolve(&RttiTypes::StructuredClass, + std::vector{"book", "book"}); + ASSERT_EQ(1U, res.size()); + + // If we ask for "section" the result should be unique as well. + res = ontology->resolve(&RttiTypes::StructuredClass, "section"); + ASSERT_EQ(1U, res.size()); + assert_path(res[0], &RttiTypes::StructuredClass, {"book", "section"}); + + // If we ask for "paragraph" it is referenced two times in the Ontology graph, + // but should be returned only once. + res = ontology->resolve(&RttiTypes::StructuredClass, "paragraph"); + ASSERT_EQ(1U, res.size()); + assert_path(res[0], &RttiTypes::StructuredClass, {"book", "paragraph"}); +} + +// i use this wrapper due to the strange behaviour of GTEST. +static void assertFalse(bool b){ + ASSERT_FALSE(b); +} + +static Rooted createUnsortedPrimitiveField( + Handle strct, Handle type, Logger &logger, bool tree, + std::string name) +{ + FieldDescriptor::FieldType fieldType = FieldDescriptor::FieldType::SUBTREE; + if (tree) { + fieldType = FieldDescriptor::FieldType::TREE; + } + + auto res = strct->createPrimitiveFieldDescriptor(type, logger, fieldType, + std::move(name)); + assertFalse(res.second); + return res.first; +} + +TEST(StructuredClass, getFieldDescriptors) +{ + /* + * We construct a case with the three levels: + * 1.) A has the SUBTREE fields a and b as well as a TREE field. + * 2.) B is a subclass of A and has the SUBTREE fields b and c as well as + * a TREE field. + * 3.) C is a subclass of B and has the SUBTREE field a. + * As a result we expect C to have none of As fields, the TREE field of B, + * and the SUBTREE fields a (of C) , b and c (of B). + */ + TerminalLogger logger{std::cout}; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + Rooted ontology{new Ontology(mgr, sys, "myOntology")}; + + Rooted A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), nullptr, false, true)}; + Rooted A_a = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, false, "a"); + Rooted A_b = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, false, "b"); + Rooted A_main = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, true, "somename"); + + Rooted B{new StructuredClass( + mgr, "B", ontology, Cardinality::any(), A, false, true)}; + Rooted B_b = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, false, "b"); + Rooted B_c = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, false, "c"); + Rooted B_main = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, true, "othername"); + + Rooted C{new StructuredClass( + mgr, "C", ontology, Cardinality::any(), B, false, true)}; + Rooted C_a = createUnsortedPrimitiveField( + C, sys->getStringType(), logger, false, "a"); + + ASSERT_TRUE(ontology->validate(logger)); + + // check all FieldDescriptors + { + NodeVector fds = A->getFieldDescriptors(); + ASSERT_EQ(3, fds.size()); + ASSERT_EQ(A_a, fds[0]); + ASSERT_EQ(A_b, fds[1]); + ASSERT_EQ(A_main, fds[2]); + } + { + NodeVector fds = B->getFieldDescriptors(); + ASSERT_EQ(4, fds.size()); + ASSERT_EQ(A_a, fds[0]); + ASSERT_EQ(B_b, fds[1]); + ASSERT_EQ(B_c, fds[2]); + ASSERT_EQ(B_main, fds[3]); + } + { + NodeVector fds = C->getFieldDescriptors(); + ASSERT_EQ(4, fds.size()); + ASSERT_EQ(B_b, fds[0]); + ASSERT_EQ(B_c, fds[1]); + // superclass fields come before subclass fields (except for the TREE + // field, which is always last). + ASSERT_EQ(C_a, fds[2]); + ASSERT_EQ(B_main, fds[3]); + } +} + + +TEST(StructuredClass, getFieldDescriptorsCycles) +{ + Logger logger; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + Rooted ontology{new Ontology(mgr, sys, "myOntology")}; + + Rooted A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), nullptr, false, true)}; + A->addSubclass(A, logger); + Rooted A_a = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, false, "a"); + ASSERT_FALSE(ontology->validate(logger)); + // if we call getFieldDescriptors that should still return a valid result. + NodeVector fds = A->getFieldDescriptors(); + ASSERT_EQ(1, fds.size()); + ASSERT_EQ(A_a, fds[0]); +} + +Rooted getClass(const std::string name, Handle dom) +{ + std::vector res = + dom->resolve(&RttiTypes::StructuredClass, name); + return res[0].node.cast(); +} + +TEST(Descriptor, pathTo) +{ + // Start with some easy examples from the book ontology. + TerminalLogger logger{std::cout}; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + // Get the ontology. + Rooted ontology = constructBookOntology(mgr, sys, logger); + + // get the book node and the section node. + Rooted book = getClass("book", ontology); + Rooted section = getClass("section", ontology); + // get the path in between. + NodeVector path = book->pathTo(section, logger); + ASSERT_EQ(1U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + + // get the text node. + Rooted text = getClass("text", ontology); + // get the path between book and text via paragraph. + path = book->pathTo(text, logger); + ASSERT_EQ(3U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("paragraph", path[1]->getName()); + ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); + + // get the subsection node. + Rooted subsection = getClass("subsection", ontology); + // try to get the path between book and subsection. + path = book->pathTo(subsection, logger); + // this should be impossible. + ASSERT_EQ(0U, path.size()); + + // try to construct the path between section and the text field. + auto res = section->pathTo(text->getFieldDescriptor(), logger); + ASSERT_TRUE(res.second); + path = res.first; + ASSERT_EQ(4U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("paragraph", path[1]->getName()); + ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_TRUE(path[3]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("text", path[3]->getName()); +} + +TEST(Descriptor, pathToAdvanced) +{ + /* + * Now we build a really nasty ontology with lots of transparency + * and inheritance. The basic idea is to have three paths from start to + * finish, where one is blocked by overriding fields and the longer valid + * one is found first such that it has to be replaced by the shorter one + * during the search. + * + * To achieve that we have the following structure: + * 1.) The start class inherits from A. + * 2.) A has B as child in the default field. + * 3.) B is transparent and has no children (but C as subclass) + * 4.) C is a subclass of B, transparent and has + * the target as child (shortest path). + * 5.) A has D as child in the default field. + * 6.) D is transparent has E as child in the default field. + * 7.) E is transparent and has target as child in the default field + * (longer path) + * + * So the path A_second_field, C, C_field should be returned. + */ + Manager mgr{1}; + TerminalLogger logger{std::cout}; + Rooted sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted ontology{new Ontology(mgr, sys, "nasty")}; + + // Let's create the classes that we need first + Rooted A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, false, true)}; + + Rooted start{new StructuredClass( + mgr, "start", ontology, Cardinality::any(), A, false, false)}; + + Rooted B{new StructuredClass( + mgr, "B", ontology, Cardinality::any(), {nullptr}, true, false)}; + + Rooted C{new StructuredClass( + mgr, "C", ontology, Cardinality::any(), B, true, false)}; + + Rooted D{new StructuredClass( + mgr, "D", ontology, Cardinality::any(), {nullptr}, true, false)}; + + Rooted E{new StructuredClass( + mgr, "E", ontology, Cardinality::any(), {nullptr}, true, false)}; + + Rooted target{ + new StructuredClass(mgr, "target", ontology, Cardinality::any())}; + + // We create a field for A + Rooted A_field = A->createFieldDescriptor(logger).first; + A_field->addChild(B); + A_field->addChild(D); + + // We create no field for B + // One for C + Rooted C_field = C->createFieldDescriptor(logger).first; + C_field->addChild(target); + + // One for D + Rooted D_field = D->createFieldDescriptor(logger).first; + D_field->addChild(E); + + // One for E + Rooted E_field = E->createFieldDescriptor(logger).first; + E_field->addChild(target); + + ASSERT_TRUE(ontology->validate(logger)); + +#ifdef MANAGER_GRAPHVIZ_EXPORT + // dump the manager state + mgr.exportGraphviz("nastyOntology.dot"); +#endif + + // and now we should be able to find the shortest path as suggested + NodeVector path = start->pathTo(target, logger); + ASSERT_EQ(3U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_EQ("", path[0]->getName()); + ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("C", path[1]->getName()); + ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_EQ("", path[2]->getName()); +} + +TEST(Descriptor, pathToCycles) +{ + // build a ontology with a cycle. + Manager mgr{1}; + Logger logger; + Rooted sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted ontology{new Ontology(mgr, sys, "cycles")}; + Rooted A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(ontology->validate(logger)); + Rooted B{new StructuredClass( + mgr, "B", ontology, Cardinality::any(), {nullptr}, false, true)}; + Rooted A_field = A->createFieldDescriptor(logger).first; + A_field->addChild(B); + /* + * Now try to create the path from A to B. A direct path is possible but + * in the worst case this could also try to find shorter paths via an + * endless repition of A instances. + * As we cut the search tree at paths that are longer than our current + * optimum this should not happen, though. + */ + NodeVector path = A->pathTo(B, logger); + ASSERT_EQ(1, path.size()); + ASSERT_EQ(A_field, path[0]); +} + +TEST(Descriptor, getDefaultFields) +{ + // construct a ontology with lots of default fields to test. + // start with a single structure class. + Manager mgr{1}; + TerminalLogger logger{std::cout}; + Rooted sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted ontology{new Ontology(mgr, sys, "nasty")}; + + Rooted A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), nullptr, false, true)}; + + // in this trivial case no field should be found. + ASSERT_TRUE(A->getDefaultFields().empty()); + + // create a field. + Rooted A_prim_field = + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + // now we should find that. + auto fields = A->getDefaultFields(); + ASSERT_EQ(1U, fields.size()); + ASSERT_EQ(A_prim_field, fields[0]); + + // remove that field from A and add it to another class. + + Rooted B{new StructuredClass( + mgr, "B", ontology, Cardinality::any(), nullptr, false, true)}; + + B->moveFieldDescriptor(A_prim_field, logger); + + // new we shouldn't find the field anymore. + ASSERT_TRUE(A->getDefaultFields().empty()); + + // but we should find it again if we set B as superclass of A. + A->setSuperclass(B, logger); + fields = A->getDefaultFields(); + ASSERT_EQ(1U, fields.size()); + ASSERT_EQ(A_prim_field, fields[0]); + + // and we should not be able to find it if we override the field. + Rooted A_field = A->createFieldDescriptor(logger).first; + ASSERT_TRUE(A->getDefaultFields().empty()); + + // add a transparent child class. + + Rooted C{new StructuredClass( + mgr, "C", ontology, Cardinality::any(), nullptr, true, false)}; + A_field->addChild(C); + + // add a primitive field for it. + Rooted C_field = + C->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + + // now we should find that. + fields = A->getDefaultFields(); + ASSERT_EQ(1U, fields.size()); + ASSERT_EQ(C_field, fields[0]); + + // add another transparent child class to A with a daughter class that has + // in turn a subclass with a primitive field. + Rooted D{new StructuredClass( + mgr, "D", ontology, Cardinality::any(), nullptr, true, false)}; + A_field->addChild(D); + Rooted D_field = D->createFieldDescriptor(logger).first; + Rooted E{new StructuredClass( + mgr, "E", ontology, Cardinality::any(), nullptr, true, false)}; + D_field->addChild(E); + Rooted F{new StructuredClass( + mgr, "E", ontology, Cardinality::any(), E, true, false)}; + Rooted F_field = + F->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + + // now we should find both primitive fields, but the C field first. + fields = A->getDefaultFields(); + ASSERT_EQ(2U, fields.size()); + ASSERT_EQ(C_field, fields[0]); + ASSERT_EQ(F_field, fields[1]); +} + +TEST(Descriptor, getDefaultFieldsCycles) +{ + // build a ontology with a cycle. + Manager mgr{1}; + Logger logger; + Rooted sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted ontology{new Ontology(mgr, sys, "cycles")}; + Rooted A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(ontology->validate(logger)); + Rooted A_field = + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + /* + * Now try to get the default fields of A. This should not lead to cycles + * if we correctly note all already visited nodes. + */ + NodeVector defaultFields = A->getDefaultFields(); + ASSERT_EQ(1, defaultFields.size()); + ASSERT_EQ(A_field, defaultFields[0]); +} + +TEST(Descriptor, getPermittedChildren) +{ + // analyze the book ontology. + TerminalLogger logger{std::cout}; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + // Get the ontology. + Rooted ontology = constructBookOntology(mgr, sys, logger); + // get the relevant classes. + Rooted book = getClass("book", ontology); + Rooted section = getClass("section", ontology); + Rooted paragraph = getClass("paragraph", ontology); + Rooted text = getClass("text", ontology); + /* + * as permitted children we expect section, paragraph and text in exactly + * that order. section should be before paragraph because of declaration + * order and text should be last because it needs a transparent paragraph + * in between. + */ + NodeVector children = book->getPermittedChildren(); + ASSERT_EQ(3U, children.size()); + ASSERT_EQ(section, children[0]); + ASSERT_EQ(paragraph, children[1]); + ASSERT_EQ(text, children[2]); + + // Now we add a subclass to text. + Rooted sub{new StructuredClass( + mgr, "Subclass", ontology, Cardinality::any(), text, true, false)}; + // And that should be in the result list as well now. + children = book->getPermittedChildren(); + ASSERT_EQ(4U, children.size()); + ASSERT_EQ(section, children[0]); + ASSERT_EQ(paragraph, children[1]); + ASSERT_EQ(text, children[2]); + ASSERT_EQ(sub, children[3]); +} + +TEST(Descriptor, getPermittedChildrenCycles) +{ + // build a ontology with a cycle. + Manager mgr{1}; + Logger logger; + Rooted sys{new SystemTypesystem(mgr)}; + // Construct the ontology + Rooted ontology{new Ontology(mgr, sys, "cycles")}; + Rooted A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(ontology->validate(logger)); + Rooted A_field = A->createFieldDescriptor(logger).first; + // we make the cycle worse by adding A as child of itself. + A_field->addChild(A); + /* + * Now try to get the permitted children of A. This should not lead to + * cycles + * if we correctly note all already visited nodes. + */ + NodeVector children = A->getPermittedChildren(); + ASSERT_EQ(1, children.size()); + ASSERT_EQ(A, children[0]); +} + +TEST(StructuredClass, isSubclassOf) +{ + // create an inheritance hierarchy. + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + Rooted ontology{new Ontology(mgr, sys, "inheritance")}; + Rooted A{new StructuredClass( + mgr, "A", ontology, Cardinality::any(), {nullptr}, false, true)}; + // first branch + Rooted B{ + new StructuredClass(mgr, "B", ontology, Cardinality::any(), A)}; + Rooted C{ + new StructuredClass(mgr, "C", ontology, Cardinality::any(), B)}; + // second branch + Rooted D{ + new StructuredClass(mgr, "D", ontology, Cardinality::any(), A)}; + Rooted E{ + new StructuredClass(mgr, "E", ontology, Cardinality::any(), D)}; + Rooted F{ + new StructuredClass(mgr, "F", ontology, Cardinality::any(), D)}; + + // check function results + ASSERT_FALSE(A->isSubclassOf(A)); + ASSERT_FALSE(A->isSubclassOf(B)); + ASSERT_FALSE(A->isSubclassOf(C)); + ASSERT_FALSE(A->isSubclassOf(D)); + ASSERT_FALSE(A->isSubclassOf(E)); + ASSERT_FALSE(A->isSubclassOf(F)); + + ASSERT_TRUE(B->isSubclassOf(A)); + ASSERT_FALSE(B->isSubclassOf(B)); + ASSERT_FALSE(B->isSubclassOf(C)); + ASSERT_FALSE(B->isSubclassOf(D)); + ASSERT_FALSE(B->isSubclassOf(E)); + ASSERT_FALSE(B->isSubclassOf(F)); + + ASSERT_TRUE(C->isSubclassOf(A)); + ASSERT_TRUE(C->isSubclassOf(B)); + ASSERT_FALSE(C->isSubclassOf(C)); + ASSERT_FALSE(C->isSubclassOf(D)); + ASSERT_FALSE(C->isSubclassOf(E)); + ASSERT_FALSE(C->isSubclassOf(F)); + + ASSERT_TRUE(D->isSubclassOf(A)); + ASSERT_FALSE(D->isSubclassOf(B)); + ASSERT_FALSE(D->isSubclassOf(C)); + ASSERT_FALSE(D->isSubclassOf(D)); + ASSERT_FALSE(D->isSubclassOf(E)); + ASSERT_FALSE(D->isSubclassOf(F)); + + ASSERT_TRUE(E->isSubclassOf(A)); + ASSERT_FALSE(E->isSubclassOf(B)); + ASSERT_FALSE(E->isSubclassOf(C)); + ASSERT_TRUE(E->isSubclassOf(D)); + ASSERT_FALSE(E->isSubclassOf(E)); + ASSERT_FALSE(E->isSubclassOf(F)); + + ASSERT_TRUE(F->isSubclassOf(A)); + ASSERT_FALSE(F->isSubclassOf(B)); + ASSERT_FALSE(F->isSubclassOf(C)); + ASSERT_TRUE(F->isSubclassOf(D)); + ASSERT_FALSE(F->isSubclassOf(E)); + ASSERT_FALSE(F->isSubclassOf(F)); +} + +TEST(Ontology, validate) +{ + TerminalLogger logger{std::cerr, true}; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + // start with an easy example: Our book ontology should be valid. + { + Rooted ontology = constructBookOntology(mgr, sys, logger); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + } + { + // Even easier: An empty ontology should be valid. + Rooted ontology{new Ontology(mgr, sys, "ontology")}; + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // if we add a StructureClass it should be valid still. + Rooted base{ + new StructuredClass(mgr, "myClass", ontology)}; + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // if we tamper with the name, however, it shouldn't be valid anymore. + base->setName(""); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + base->setName("my class"); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + base->setName("myClass"); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // Let's add a primitive field (without a primitive type at first) + Rooted base_field = + base->createPrimitiveFieldDescriptor(nullptr, logger).first; + // this should not be valid. + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // but it should be if we set the type. + base_field->setPrimitiveType(sys->getStringType()); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // add a subclass for our base class. + Rooted sub{new StructuredClass(mgr, "sub", ontology)}; + // this should be valid in itself. + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // and still if we add a superclass. + sub->setSuperclass(base, logger); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // and still if we remove the subclass from the base class. + base->removeSubclass(sub, logger); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + ASSERT_EQ(nullptr, sub->getSuperclass()); + // and still if we re-add it. + base->addSubclass(sub, logger); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + ASSERT_EQ(base, sub->getSuperclass()); + // add a non-primitive field to the child class. + Rooted sub_field = + sub->createFieldDescriptor(logger).first; + // this should not be valid because we allow no children. + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // we should also be able to add a child and make it valid. + sub_field->addChild(base); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // it should be invalid if we add it twice. + sub_field->addChild(base); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // and valid again if we remove it once. + sub_field->removeChild(base); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // if we set a primitive type it should be invalid + sub_field->setPrimitiveType(sys->getStringType()); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // and valid again if we unset it. + sub_field->setPrimitiveType(nullptr); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + // It should be invalid if we set another TREE field. + Rooted sub_field2 = + sub->createFieldDescriptor(logger, FieldDescriptor::FieldType::TREE, + "test", false).first; + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_FALSE(ontology->validate(logger)); + // but valid again if we remove it + sub->removeFieldDescriptor(sub_field2); + ASSERT_EQ(ValidationState::UNKNOWN, ontology->getValidationState()); + ASSERT_TRUE(ontology->validate(logger)); + } +} +} \ No newline at end of file diff --git a/test/core/model/TestAdvanced.hpp b/test/core/model/TestAdvanced.hpp index 71379d2..c92effa 100644 --- a/test/core/model/TestAdvanced.hpp +++ b/test/core/model/TestAdvanced.hpp @@ -20,19 +20,19 @@ #define _MODEL_TEST_ADVANCED_HPP_ #include -#include +#include #include #include "TestDocumentBuilder.hpp" namespace ousia { -static Rooted resolveDescriptor(Handle domain, +static Rooted resolveDescriptor(Handle ontology, const std::string &className) { // use the actual resolve method. std::vector resolved = - domain->resolve(&RttiTypes::StructuredClass, className); + ontology->resolve(&RttiTypes::StructuredClass, className); // take the first valid result. for (auto &r : resolved) { return r.node.cast(); @@ -42,53 +42,53 @@ static Rooted resolveDescriptor(Handle domain, } /** - * This constructs the "heading" domain given the book domain. + * This constructs the "heading" ontology given the book ontology. */ -static Rooted constructHeadingDomain(Manager &mgr, +static Rooted constructHeadingOntology(Manager &mgr, Handle sys, - Handle bookDomain, + Handle bookOntology, Logger &logger) { - // set up domain node. - Rooted domain{new Domain(mgr, sys, "headings")}; + // set up ontology node. + Rooted ontology{new Ontology(mgr, sys, "headings")}; // set up cardinality (every section may have at most one heading). Cardinality card; card.merge({0, 1}); // set up heading StructuredClass. Rooted heading{ - new StructuredClass(mgr, "heading", domain, card, {nullptr}, true)}; + new StructuredClass(mgr, "heading", ontology, card, {nullptr}, true)}; // as field want to reference the field of paragraph. - Rooted p = resolveDescriptor(bookDomain, "paragraph"); + Rooted p = resolveDescriptor(bookOntology, "paragraph"); heading->addFieldDescriptor(p->getFieldDescriptor(), logger); // create a new field for headings in each section type. std::vector secclasses{"book", "section", "subsection", "paragraph"}; for (auto &s : secclasses) { - Rooted desc = resolveDescriptor(bookDomain, s); + Rooted desc = resolveDescriptor(bookOntology, s); Rooted heading_field = desc->createFieldDescriptor(logger, FieldDescriptor::FieldType::SUBTREE, "heading", true).first; heading_field->addChild(heading); } - return domain; + return ontology; } /** - * This constructs the "list" domain given the book domain. + * This constructs the "list" ontology given the book ontology. */ -static Rooted constructListDomain(Manager &mgr, +static Rooted constructListOntology(Manager &mgr, Handle sys, - Handle bookDomain, + Handle bookOntology, Logger &logger) { - // set up domain node. - Rooted domain{new Domain(mgr, sys, "list")}; + // set up ontology node. + Rooted ontology{new Ontology(mgr, sys, "list")}; // get book.paragraph - Rooted p = resolveDescriptor(bookDomain, "paragraph"); + Rooted p = resolveDescriptor(bookOntology, "paragraph"); // set up item StructuredClass; Rooted item{new StructuredClass( - mgr, "item", domain, Cardinality::any(), {nullptr}, false)}; + mgr, "item", ontology, Cardinality::any(), {nullptr}, false)}; // as field we want to reference the field of paragraph. item->addFieldDescriptor(p->getFieldDescriptor(), logger); @@ -96,28 +96,28 @@ static Rooted constructListDomain(Manager &mgr, std::vector listTypes{"ol", "ul"}; for (auto &listType : listTypes) { Rooted list{new StructuredClass( - mgr, listType, domain, Cardinality::any(), p, false)}; + mgr, listType, ontology, Cardinality::any(), p, false)}; Rooted list_field{new FieldDescriptor(mgr, list)}; list_field->addChild(item); } - return domain; + return ontology; } /** - * This constructs the "emphasis" domain. + * This constructs the "emphasis" ontology. */ -static Rooted constructEmphasisDomain(Manager &mgr, +static Rooted constructEmphasisOntology(Manager &mgr, Handle sys, Logger &logger) { - // set up domain node. - Rooted domain{new Domain(mgr, sys, "emphasis")}; + // set up ontology node. + Rooted ontology{new Ontology(mgr, sys, "emphasis")}; // create AnnotationClasses - Rooted em{new AnnotationClass(mgr, "emphasized", domain)}; + Rooted em{new AnnotationClass(mgr, "emphasized", ontology)}; - Rooted strong{new AnnotationClass(mgr, "strong", domain)}; + Rooted strong{new AnnotationClass(mgr, "strong", ontology)}; - return domain; + return ontology; } static bool addText(Logger &logger, Handle doc, @@ -173,17 +173,17 @@ static bool addAnnotation(Logger &logger, Handle doc, /** * This constructs a more advanced book document using not only the book - * domain but also headings, emphasis and lists. + * ontology but also headings, emphasis and lists. */ static Rooted constructAdvancedDocument(Manager &mgr, Logger &logger, - Handle bookDom, - Handle headingDom, - Handle listDom, - Handle emphasisDom) + Handle bookDom, + Handle headingDom, + Handle listDom, + Handle emphasisDom) { // Start with the (empty) document. Rooted doc{new Document(mgr, "kant_was_ist_aufklaerung.oxd")}; - doc->referenceDomains({bookDom, headingDom, listDom, emphasisDom}); + doc->referenceOntologys({bookDom, headingDom, listDom, emphasisDom}); // Add the root. Rooted book = diff --git a/test/core/model/TestDocument.hpp b/test/core/model/TestDocument.hpp index 7675dab..a4a8f7c 100644 --- a/test/core/model/TestDocument.hpp +++ b/test/core/model/TestDocument.hpp @@ -20,7 +20,7 @@ #define _MODEL_TEST_DOCUMENT_HPP_ #include -#include +#include #include #include "TestDocumentBuilder.hpp" @@ -28,15 +28,15 @@ namespace ousia { /** - * This constructs a fairly simple test document for the "book" domain. The + * This constructs a fairly simple test document for the "book" ontology. The * structure of the document can be seen in the Code below. */ static Rooted constructBookDocument(Manager &mgr, Logger &logger, - Rooted bookDomain) + Rooted bookOntology) { // Start with the (empty) document. Rooted doc{new Document(mgr, "myDoc.oxd")}; - doc->referenceDomain(bookDomain); + doc->referenceOntology(bookOntology); // Add the root. Rooted root = diff --git a/test/core/model/TestDocumentBuilder.hpp b/test/core/model/TestDocumentBuilder.hpp index fc76b07..745f1ab 100644 --- a/test/core/model/TestDocumentBuilder.hpp +++ b/test/core/model/TestDocumentBuilder.hpp @@ -23,7 +23,7 @@ #include #include -#include +#include #include namespace ousia { @@ -81,7 +81,7 @@ static Rooted resolveDescriptor(Handle doc, * @param name is the name of this StructuredEntity (empty per * default). * @return the newly created StructuredEntity or a nullptr if some - * input handle was empty or the given domains did not + * input handle was empty or the given ontologies did not * contain a StructuredClass with the given name. */ Rooted buildRootStructuredEntity( @@ -112,7 +112,7 @@ Rooted buildRootStructuredEntity( * This builds a StructuredEntity as child of the given DocumentEntity. It * automatically appends the newly build entity to its parent. * - * @param document is the document this entity shall be build for. The domains + * @param document is the document this entity shall be build for. The ontologies * referenced here are the basis to resolve the given path. * @param logger is the current logger. * @param parent is the parent DocumentEntity. The newly constructed @@ -126,7 +126,7 @@ Rooted buildRootStructuredEntity( * @param name is the name of this StructuredEntity (empty per * default). * @return the newly created StructuredEntity or a nullptr if some - * input handle was empty or the given domains did not + * input handle was empty or the given ontologies did not * contain a StructuredClass with the given name. */ Rooted buildStructuredEntity( @@ -167,7 +167,7 @@ Rooted buildStructuredEntity( * This builds an AnnotationEntity as child of the given Document. It * automatically appends the newly build entity to its parent. * - * @param document is the document this entity shall be build for. The domains + * @param document is the document this entity shall be build for. The ontologies * referenced here are the basis to resolve the given path. * @param logger is the current logger. * @param path is the name of the AnnotationClass or a path specifying it @@ -179,7 +179,7 @@ Rooted buildStructuredEntity( * @param name is the name of this AnnotationEntity (empty per * default). * @return the newly created AnnotationEntity or a nullptr if some - * input handle was empty or the given domains did not + * input handle was empty or the given ontologies did not * contain a AnnotationClass with the given name. */ Rooted buildAnnotationEntity( diff --git a/test/core/model/TestDomain.hpp b/test/core/model/TestDomain.hpp deleted file mode 100644 index 779ef03..0000000 --- a/test/core/model/TestDomain.hpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - Ousía - Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef _MODEL_TEST_DOMAIN_HPP_ -#define _MODEL_TEST_DOMAIN_HPP_ - -#include -#include - -namespace ousia { -/** - * This constructs the "book" domain for test purposes. The structure of the - * domain is fairly simple and can be seen from the construction itself. - */ -static Rooted constructBookDomain(Manager &mgr, - Handle sys, - Logger &logger) -{ - // Start with the Domain itself. - Rooted domain{new Domain(mgr, sys, "book")}; - // Set up the cardinalities we'll need. - Cardinality single; - single.merge({1}); - - // Set up the "book" node. - Rooted book{new StructuredClass( - mgr, "book", domain, single, {nullptr}, false, true)}; - - // The structure field of it. - Rooted book_field = - book->createFieldDescriptor(logger).first; - - // From there on the "section". - Rooted section{ - new StructuredClass(mgr, "section", domain, Cardinality::any())}; - book_field->addChild(section); - - // And the field of it. - Rooted section_field = - section->createFieldDescriptor(logger).first; - - // We also add the "paragraph", which is transparent. - Rooted paragraph{new StructuredClass( - mgr, "paragraph", domain, Cardinality::any(), {nullptr}, true)}; - section_field->addChild(paragraph); - book_field->addChild(paragraph); - - // And the field of it. - Rooted paragraph_field = - paragraph->createFieldDescriptor(logger).first; - - // We append "subsection" to section. - Rooted subsection{ - new StructuredClass(mgr, "subsection", domain, Cardinality::any())}; - section_field->addChild(subsection); - - // And the field of it. - Rooted subsection_field = - subsection->createFieldDescriptor(logger).first; - - // and we add the paragraph to subsections fields - subsection_field->addChild(paragraph); - - // Finally we add the "text" node, which is transparent as well. - Rooted text{new StructuredClass( - mgr, "text", domain, Cardinality::any(), {nullptr}, true)}; - paragraph_field->addChild(text); - - // ... and has a primitive field. - Rooted text_field = - text->createPrimitiveFieldDescriptor(sys->getStringType(), logger) - .first; - - return domain; -} -} - -#endif /* _TEST_DOMAIN_HPP_ */ \ No newline at end of file diff --git a/test/core/model/TestOntology.hpp b/test/core/model/TestOntology.hpp new file mode 100644 index 0000000..c65d6f4 --- /dev/null +++ b/test/core/model/TestOntology.hpp @@ -0,0 +1,93 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _MODEL_TEST_DOMAIN_HPP_ +#define _MODEL_TEST_DOMAIN_HPP_ + +#include +#include + +namespace ousia { +/** + * This constructs the "book" ontology for test purposes. The structure of the + * ontology is fairly simple and can be seen from the construction itself. + */ +static Rooted constructBookOntology(Manager &mgr, + Handle sys, + Logger &logger) +{ + // Start with the Ontology itself. + Rooted ontology{new Ontology(mgr, sys, "book")}; + // Set up the cardinalities we'll need. + Cardinality single; + single.merge({1}); + + // Set up the "book" node. + Rooted book{new StructuredClass( + mgr, "book", ontology, single, {nullptr}, false, true)}; + + // The structure field of it. + Rooted book_field = + book->createFieldDescriptor(logger).first; + + // From there on the "section". + Rooted section{ + new StructuredClass(mgr, "section", ontology, Cardinality::any())}; + book_field->addChild(section); + + // And the field of it. + Rooted section_field = + section->createFieldDescriptor(logger).first; + + // We also add the "paragraph", which is transparent. + Rooted paragraph{new StructuredClass( + mgr, "paragraph", ontology, Cardinality::any(), {nullptr}, true)}; + section_field->addChild(paragraph); + book_field->addChild(paragraph); + + // And the field of it. + Rooted paragraph_field = + paragraph->createFieldDescriptor(logger).first; + + // We append "subsection" to section. + Rooted subsection{ + new StructuredClass(mgr, "subsection", ontology, Cardinality::any())}; + section_field->addChild(subsection); + + // And the field of it. + Rooted subsection_field = + subsection->createFieldDescriptor(logger).first; + + // and we add the paragraph to subsections fields + subsection_field->addChild(paragraph); + + // Finally we add the "text" node, which is transparent as well. + Rooted text{new StructuredClass( + mgr, "text", ontology, Cardinality::any(), {nullptr}, true)}; + paragraph_field->addChild(text); + + // ... and has a primitive field. + Rooted text_field = + text->createPrimitiveFieldDescriptor(sys->getStringType(), logger) + .first; + + return ontology; +} +} + +#endif /* _TEST_DOMAIN_HPP_ */ \ No newline at end of file -- cgit v1.2.3