From 4ee3c4042d267c010babb2ab86e15a6b31950849 Mon Sep 17 00:00:00 2001 From: Andreas Stöckel Date: Tue, 3 Mar 2015 18:23:15 +0100 Subject: added a method to find the matching start anchor for some end anchor. --- src/core/model/Document.cpp | 114 +++++++++++++++++++++++++++++++++++++- src/core/model/Document.hpp | 41 ++++++++++++-- test/core/model/DocumentTest.cpp | 115 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 257 insertions(+), 13 deletions(-) diff --git a/src/core/model/Document.cpp b/src/core/model/Document.cpp index b29767e..62dad11 100644 --- a/src/core/model/Document.cpp +++ b/src/core/model/Document.cpp @@ -452,6 +452,112 @@ Rooted DocumentEntity::createChildAnchor(const size_t &fieldIdx) return Rooted{new Anchor(subInst->getManager(), subInst, fieldIdx)}; } +static bool matchStartAnchor(Handle desc, + const std::string &name, Handle a) +{ + return (a->getAnnotation() != nullptr) && + (a->getAnnotation()->getEnd() == nullptr) && + (desc == nullptr || a->getAnnotation()->getDescriptor() == desc) && + (name.empty() || a->getAnnotation()->getName() == name); +} + +template +Rooted DocumentEntity::searchStartAnchorInField( + Handle desc, const std::string &name, Iterator begin, + Iterator end) +{ + for (Iterator it = begin; it != end; it++) { + Handle strct = *it; + if (strct->isa(&RttiTypes::Anchor)) { + // check if this Anchor is the right one. + Handle a = strct.cast(); + if (matchStartAnchor(desc, name, a)) { + return a; + } + continue; + } else if (strct->isa(&RttiTypes::StructuredEntity)) { + // search downwards. + Rooted a = + strct.cast()->searchStartAnchorDownwards( + desc, name); + if (a != nullptr) { + return a; + } + } + } + return nullptr; +} + +Rooted DocumentEntity::searchStartAnchorDownwards( + Handle desc, const std::string &name) +{ + if (fields.empty()) { + return nullptr; + } + // get the default field. + NodeVector children = fields[fields.size() - 1]; + // search it from back to front. + return searchStartAnchorInField(desc, name, children.rbegin(), + children.rend()); +} + +Rooted DocumentEntity::searchStartAnchorUpwards( + Handle desc, const std::string &name, + const DocumentEntity *child) +{ + if (fields.empty()) { + return nullptr; + } + // get the default field. + NodeVector children = fields[fields.size() - 1]; + // search for the child from back to front. + auto it = children.rbegin(); + while (static_cast(it->get()) != child->subInst.get() && + it != children.rend()) { + it++; + } + // increment the reverse iterator once more to prevent downwards search + // to the child. + if (it != children.rend()) { + it++; + return searchStartAnchorInField(desc, name, it, children.rend()); + } + throw OusiaException("Internal error: Child node not found in parent!"); +} + +Rooted DocumentEntity::searchStartAnchor(size_t fieldIdx, + Handle desc, + const std::string &name) +{ + // get the correct field. + NodeVector children = fields[fieldIdx]; + // search it from back to front. + Rooted a = searchStartAnchorInField(desc, name, children.rbegin(), + children.rend()); + // if we found the Anchor, return it. + if (a != nullptr) { + return a; + } + + // If this is either an AnnotationEntity or a SUBTREE field we can not + // search upwards. + if (subInst->isa(&RttiTypes::AnnotationEntity) || + fieldIdx + 1 < fields.size()) { + return nullptr; + } + // if the children here did not contain the right start Anchor go upwards. + if (subInst->getParent()->isa(&RttiTypes::StructuredEntity)) { + return subInst->getParent() + .cast() + ->searchStartAnchorUpwards(desc, name, this); + } + if (subInst->getParent()->isa(&RttiTypes::AnnotationEntity)) { + subInst->getParent().cast()->searchStartAnchorUpwards( + desc, name, this); + } + return nullptr; +} + /* Class StructureNode */ bool StructureNode::doValidate(Logger &logger) const @@ -702,7 +808,9 @@ void AnnotationEntity::setStart(Handle s) } invalidate(); start = acquire(s); - s->setAnnotation(this, true); + if (s != nullptr) { + s->setAnnotation(this, true); + } } void AnnotationEntity::setEnd(Handle e) @@ -712,7 +820,9 @@ void AnnotationEntity::setEnd(Handle e) } invalidate(); end = acquire(e); - e->setAnnotation(this, false); + if (e != nullptr) { + e->setAnnotation(this, false); + } } /* Class Document */ diff --git a/src/core/model/Document.hpp b/src/core/model/Document.hpp index dc0f73f..81e2f41 100644 --- a/src/core/model/Document.hpp +++ b/src/core/model/Document.hpp @@ -26,8 +26,8 @@ * * A Document, from top to bottom, consists of "Document" instance, * which "owns" the structural root node of the in-document graph. This might - * for example be a "book" node of the "book" ontology. That root node in turn has - * structure nodes as children, which in turn may have children. This + * for example be a "book" node of the "book" ontology. That root node in turn + * has structure nodes as children, which in turn may have children. This * constitutes a Structure Tree. Additionally annotations may be attached to * Structure Nodes, effectively resulting in a Document Graph instead of a * Document Tree (other references may introduce cycles as well). @@ -142,7 +142,7 @@ class Anchor; */ class DocumentEntity { private: - /* + /** * this is a rather dirty method that should not be used in other cases: * We store a handle to the Node instance that inherits from * DocumentEntity. This Handle is not registered and would lead to Segfaults @@ -156,6 +156,18 @@ private: void invalidateSubInstance(); + template + Rooted searchStartAnchorInField(Handle desc, + const std::string &name, + Iterator begin, Iterator end); + + Rooted searchStartAnchorDownwards(Handle desc, + const std::string &name); + + Rooted searchStartAnchorUpwards(Handle desc, + const std::string &name, + const DocumentEntity *child); + protected: bool doValidate(Logger &logger) const; @@ -420,7 +432,7 @@ public: Rooted createChildStructuredEntity( Handle descriptor, const size_t &fieldIdx, Variant attributes = Variant::mapType{}, std::string name = ""); - /* + /** * Creates a new DocumentPrimitive as child of this DocumentEntity. * * @param content is a Variant containing the content of this @@ -434,7 +446,7 @@ public: */ Rooted createChildDocumentPrimitive( Variant content, const std::string &fieldName = DEFAULT_FIELD_NAME); - /* + /** * Creates a new DocumentPrimitive as child of this DocumentEntity. * * @param fieldIdx is the index of the field, where the newly created @@ -469,6 +481,23 @@ public: * @return the newly created Anchor. */ Rooted createChildAnchor(const size_t &fieldIdx); + + /** + * Does an inverse depth first search starting at this DocumentEntity to + * find a child Anchor element that matches the given seach criteria. + * The search will not cross SUBTREE to TREE field boundaries and will not + * leave AnnotationEntities upwards. If no Anchor is found a nullptr is + * returned. AnnotationEntities which already have an end Anchor won't be + * returned. + * + * @param desc is the AnnotationClass of the AnnotationEntity whose + * start Anchor you are looking for. + * @param name is the AnnotationEntities name. + * @return the start Anchor or a nullptr if no Anchor could be found. + */ + Rooted searchStartAnchor(size_t fieldIdx, + Handle desc = nullptr, + const std::string &name = ""); }; /** @@ -1061,4 +1090,4 @@ extern const Rtti Anchor; } } -#endif /* _OUSIA_MODEL_DOCUMENT_HPP_ */ \ No newline at end of file +#endif /* _OUSIA_MODEL_DOCUMENT_HPP_ */ diff --git a/test/core/model/DocumentTest.cpp b/test/core/model/DocumentTest.cpp index 8ed59f5..9362af8 100644 --- a/test/core/model/DocumentTest.cpp +++ b/test/core/model/DocumentTest.cpp @@ -30,6 +30,108 @@ namespace ousia { +TEST(DocumentEntity, searchStartAnchor) +{ + // create a trivial ontology. + TerminalLogger logger{std::cerr, true}; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + Rooted ontology{new Ontology(mgr, sys, "trivial")}; + // we only have one StructuredClass that may have itself as a child. + Rooted A = ontology->createStructuredClass( + "A", Cardinality::any(), nullptr, false, true); + Rooted A_field = A->createFieldDescriptor(logger).first; + A_field->addChild(A); + // create two AnnotationClasses. + Rooted Alpha = ontology->createAnnotationClass("Alpha"); + Rooted Beta = ontology->createAnnotationClass("Beta"); + // validate this ontology. + ASSERT_TRUE(ontology->validate(logger)); + + // create a trivial document. + Rooted doc{new Document(mgr, "myDoc")}; + Rooted root = doc->createRootStructuredEntity(A); + // add an Anchor. + Rooted a = root->createChildAnchor(); + // create an AnnotationEntity with the Anchor as start. + doc->createChildAnnotation(Alpha, a, nullptr, Variant::mapType{}, "myAnno"); + // We should be able to find the Anchor now if we look for it. + ASSERT_EQ(a, root->searchStartAnchor(0)); + ASSERT_EQ(a, root->searchStartAnchor(0, Alpha)); + ASSERT_EQ(a, root->searchStartAnchor(0, nullptr, "myAnno")); + ASSERT_EQ(a, root->searchStartAnchor(0, Alpha, "myAnno")); + // but we should not find it if we look for an Anchor of a different + // AnnotationClass. + ASSERT_EQ(nullptr, root->searchStartAnchor(0, Beta)); + + // now add a child to the root node and place the Anchor there. + Rooted child = root->createChildStructuredEntity(A); + Rooted b = root->createChildAnchor(); + doc->createChildAnnotation(Alpha, b, nullptr, Variant::mapType{}, "myAnno"); + // now b should be returned because its closer. + ASSERT_EQ(b, root->searchStartAnchor(0)); + ASSERT_EQ(b, root->searchStartAnchor(0, Alpha)); + ASSERT_EQ(b, root->searchStartAnchor(0, nullptr, "myAnno")); + ASSERT_EQ(b, root->searchStartAnchor(0, Alpha, "myAnno")); +} + +TEST(DocumentEntity, searchStartAnchorUpwards) +{ + // create a trivial ontology. + TerminalLogger logger{std::cerr, true}; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + Rooted ontology{new Ontology(mgr, sys, "trivial")}; + // we only have one StructuredClass that may have itself as a child in the + // default field or a subtree field. + Rooted A = ontology->createStructuredClass( + "A", Cardinality::any(), nullptr, false, true); + Rooted A_field = A->createFieldDescriptor(logger).first; + Rooted A_sub_field = + A->createFieldDescriptor(logger, FieldDescriptor::FieldType::SUBTREE, + "sub").first; + A_field->addChild(A); + A_sub_field->addChild(A); + // create two AnnotationClasses. + Rooted Alpha = ontology->createAnnotationClass("Alpha"); + Rooted Beta = ontology->createAnnotationClass("Beta"); + // add a tree field to the annotation class. + Rooted Alpha_field = + Alpha->createFieldDescriptor(logger).first; + Alpha_field->addChild(A); + // validate this ontology. + ASSERT_TRUE(ontology->validate(logger)); + + // create a document with a root node, and two children, one in the + // default and one in the subtree field. + Rooted doc{new Document(mgr, "myDoc")}; + Rooted root = doc->createRootStructuredEntity(A); + // add an Anchor. + Rooted a = root->createChildAnchor(); + // create an AnnotationEntity with the Anchor as start. + Rooted anno = doc->createChildAnnotation( + Alpha, a, nullptr, Variant::mapType{}, "myAnno"); + // add a child. + Rooted child = root->createChildStructuredEntity(A); + // We should be able to find the Anchor from the child node now. if we look + // for it. + ASSERT_EQ(a, child->searchStartAnchor(1)); + ASSERT_EQ(a, child->searchStartAnchor(1, Alpha)); + ASSERT_EQ(a, child->searchStartAnchor(1, nullptr, "myAnno")); + ASSERT_EQ(a, child->searchStartAnchor(1, Alpha, "myAnno")); + // we should not be able to find it from the subtree field, however. + ASSERT_EQ(nullptr, child->searchStartAnchor(0)); + // and also we should not be able to find it from the annotation itself. + ASSERT_EQ(nullptr, anno->searchStartAnchor(0)); + // but we can find a new anchor inside the annotation. + Rooted b = anno->createChildAnchor(); + doc->createChildAnnotation(Beta, b, nullptr, Variant::mapType{}, "myAnno"); + ASSERT_EQ(b, anno->searchStartAnchor(0)); + ASSERT_EQ(b, anno->searchStartAnchor(0, Beta)); + ASSERT_EQ(b, anno->searchStartAnchor(0, nullptr, "myAnno")); + ASSERT_EQ(b, anno->searchStartAnchor(0, Beta, "myAnno")); +} + TEST(Document, construct) { // Construct Manager @@ -213,8 +315,9 @@ TEST(Document, validate) * Override the default field in childSubClass with an optional field. */ Rooted childSubField = - childSubClass->createFieldDescriptor( - logger, FieldDescriptor::FieldType::TREE, "dummy", true).first; + childSubClass->createFieldDescriptor(logger, + FieldDescriptor::FieldType::TREE, + "dummy", true).first; // add a child pro forma to make it valid. childSubField->addChild(childSubClass); { @@ -233,8 +336,9 @@ TEST(Document, validate) // add a primitive field to the subclass with integer content. Rooted primitive_field = childSubClass->createPrimitiveFieldDescriptor( - sys->getIntType(), logger, FieldDescriptor::FieldType::SUBTREE, - "int", false).first; + sys->getIntType(), logger, + FieldDescriptor::FieldType::SUBTREE, "int", + false).first; { /* * Now a document with one instance of the Child subclass should be @@ -260,7 +364,8 @@ TEST(Document, validate) } // Now add an Annotation class to the ontology. - Rooted annoClass{new AnnotationClass(mgr, "anno", ontology)}; + Rooted annoClass{ + new AnnotationClass(mgr, "anno", ontology)}; { /* * Create a valid document in itself. -- cgit v1.2.3