From f812e01570aedd5033245a76846b5afc0063bc17 Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Wed, 11 Feb 2015 17:51:50 +0100 Subject: made isSubtree (fieldType) and primitivity orthogonal concepts: PRIMITIVE is no FieldType anymore. --- test/core/model/DocumentTest.cpp | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) (limited to 'test/core/model/DocumentTest.cpp') diff --git a/test/core/model/DocumentTest.cpp b/test/core/model/DocumentTest.cpp index 3164b7e..5acf13f 100644 --- a/test/core/model/DocumentTest.cpp +++ b/test/core/model/DocumentTest.cpp @@ -67,11 +67,9 @@ TEST(Document, construct) ASSERT_EQ("text", text->getDescriptor()->getName()); ASSERT_TRUE(text->getDescriptor()->hasField()); ASSERT_EQ(1U, text->getField().size()); - ASSERT_TRUE( - text->getField()[0]->isa(typeOf())); - Variant content = text->getField()[0] - .cast() - ->getContent(); + ASSERT_TRUE(text->getField()[0]->isa(typeOf())); + Variant content = + text->getField()[0].cast()->getContent(); ASSERT_EQ("Some introductory text", content.asString()); } } @@ -101,11 +99,10 @@ TEST(Document, construct) ASSERT_EQ("text", text->getDescriptor()->getName()); ASSERT_TRUE(text->getDescriptor()->hasField()); ASSERT_EQ(1U, text->getField().size()); - ASSERT_TRUE(text->getField()[0]->isa( - typeOf())); - Variant content = text->getField()[0] - .cast() - ->getContent(); + ASSERT_TRUE( + text->getField()[0]->isa(typeOf())); + Variant content = + text->getField()[0].cast()->getContent(); ASSERT_EQ("Some actual text", content.asString()); } } @@ -149,7 +146,8 @@ TEST(Document, validate) } // now let's extend the rootClass with a default field. - Rooted rootField{new FieldDescriptor(mgr, rootClass)}; + Rooted rootField = + rootClass->createFieldDescriptor(logger); // and add a child class for it. Rooted childClass{ new StructuredClass(mgr, "child", domain, single)}; @@ -195,7 +193,8 @@ TEST(Document, validate) * Make it even more complicated: child gets a field for further child * instances now. */ - Rooted childField{new FieldDescriptor(mgr, childClass)}; + Rooted childField = + childClass->createFieldDescriptor(logger); childField->addChild(childClass); { /* @@ -211,10 +210,13 @@ TEST(Document, validate) ASSERT_FALSE(doc->validate(logger)); } /* - * Override the default field in childSubClass. + * Override the default field in childSubClass with an optional field. */ - Rooted childSubField{ - new FieldDescriptor(mgr, childSubClass)}; + Rooted childSubField = + childSubClass->createFieldDescriptor( + logger, FieldDescriptor::FieldType::TREE, "dummy", true); + // add a child pro forma to make it valid. + childSubField->addChild(childSubClass); { /* * Now a document with one instance of the Child subclass should be @@ -229,8 +231,10 @@ TEST(Document, validate) ASSERT_TRUE(doc->validate(logger)); } // add a primitive field to the subclass with integer content. - Rooted primitive_field{new FieldDescriptor( - mgr, childSubClass, sys->getIntType(), "int", false)}; + Rooted primitive_field = + childSubClass->createPrimitiveFieldDescriptor( + sys->getIntType(), logger, FieldDescriptor::FieldType::SUBTREE, + "int", false); { /* * Now a document with one instance of the Child subclass should be -- cgit v1.2.3 From ebac41111fa33790acce7be45e599f8de37e7f43 Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Thu, 12 Feb 2015 11:33:01 +0100 Subject: Anchors do not have a name anymore and have a unique mapping to their AnnotationEntities. This also makes serialization much easier. --- src/core/model/Document.cpp | 112 +++++++++++++++++++++++++++++++---- src/core/model/Document.hpp | 66 ++++++++++++++++----- src/plugins/html/DemoOutput.cpp | 53 ++++++----------- src/plugins/html/DemoOutput.hpp | 16 ++--- test/core/model/DocumentTest.cpp | 4 +- test/core/model/TestAdvanced.hpp | 6 +- test/plugins/html/DemoOutputTest.cpp | 23 +++---- 7 files changed, 185 insertions(+), 95 deletions(-) (limited to 'test/core/model/DocumentTest.cpp') diff --git a/src/core/model/Document.cpp b/src/core/model/Document.cpp index 8b31f14..b38f2c0 100644 --- a/src/core/model/Document.cpp +++ b/src/core/model/Document.cpp @@ -275,9 +275,9 @@ static int enforceGetFieldDescriptorIndex(Handle desc, { ssize_t idx = desc->getFieldDescriptorIndex(fieldName); if (idx == -1) { - throw OusiaException( - std::string("Descriptor \"") + desc->getName() + - "\" has no field with the name \"" + fieldName + "\""); + throw OusiaException(std::string("Descriptor \"") + desc->getName() + + "\" has no field with the name \"" + fieldName + + "\""); } return idx; } @@ -287,8 +287,7 @@ static int enforceGetFieldDescriptorIndex( { ssize_t idx = desc->getFieldDescriptorIndex(fieldDescriptor); if (idx == -1) { - throw OusiaException(std::string("Descriptor \"") + - desc->getName() + + throw OusiaException(std::string("Descriptor \"") + desc->getName() + "\" does not reference the given field \"" + fieldDescriptor->getName() + "\""); } @@ -419,11 +418,10 @@ Rooted DocumentEntity::createChildDocumentPrimitive( subInst->getManager(), subInst, std::move(content), fieldName)}; } -Rooted DocumentEntity::createChildAnchor(std::string name, - const std::string &fieldName) +Rooted DocumentEntity::createChildAnchor(const std::string &fieldName) { return Rooted{ - new Anchor(subInst->getManager(), std::move(name), subInst, fieldName)}; + new Anchor(subInst->getManager(), subInst, fieldName)}; } /* Class StructureNode */ @@ -494,13 +492,61 @@ bool Anchor::doValidate(Logger &logger) const { bool valid = true; // check name - if (getName().empty()) { - logger.error("An Anchor needs a name!", *this); + if (!getName().empty()) { + logger.error( + "This anchor has a name! Anchors should only be referred to by " + "reference, not by name!", + *this); valid = false; } + if (annotation == nullptr) { + // this is valid but should throw a warning. + logger.warning("This anchor is disconnected.", *this); + } return valid & StructureNode::doValidate(logger); } +void Anchor::setAnnotation(Handle anno, bool start) +{ + if (annotation == anno) { + return; + } + invalidate(); + // unset the old reference. + if (annotation != nullptr) { + if (isStart()) { + annotation->setStart(nullptr); + } else { + annotation->setEnd(nullptr); + } + } + annotation = acquire(anno); + // set the new reference. + if (anno != nullptr) { + if (start) { + anno->setStart(this); + } else { + anno->setEnd(this); + } + } +} + +bool Anchor::isStart() const +{ + if (annotation == nullptr) { + return false; + } + return annotation->getStart() == this; +} + +bool Anchor::isEnd() const +{ + if (annotation == nullptr) { + return false; + } + return annotation->getEnd() == this; +} + /* Class AnnotationEntity */ AnnotationEntity::AnnotationEntity(Manager &mgr, Handle parent, @@ -508,13 +554,13 @@ AnnotationEntity::AnnotationEntity(Manager &mgr, Handle parent, Handle start, Handle end, Variant attributes, std::string name) : Node(mgr, std::move(name), parent), - DocumentEntity(this, descriptor, attributes), - start(acquire(start)), - end(acquire(end)) + DocumentEntity(this, descriptor, attributes) { if (parent != nullptr) { parent->addAnnotation(this); } + setStart(start); + setEnd(end); } bool AnnotationEntity::doValidate(Logger &logger) const @@ -567,10 +613,50 @@ bool AnnotationEntity::doValidate(Logger &logger) const valid = false; } } + // check if the Anchors reference this AnnotationEntity correctly. + if (start != nullptr) { + if (start->getAnnotation() != this) { + logger.error( + "This annotations start anchor does not have the correct " + "annotation as parent!", + *this); + valid = false; + } + } + if (end != nullptr) { + if (end->getAnnotation() != this) { + logger.error( + "This annotations end anchor does not have the correct " + "annotation as parent!", + *this); + valid = false; + } + } + // check the validity as a DocumentEntity. return valid & DocumentEntity::doValidate(logger); } +void AnnotationEntity::setStart(Handle s) +{ + if (start == s) { + return; + } + invalidate(); + start = acquire(s); + s->setAnnotation(this, true); +} + +void AnnotationEntity::setEnd(Handle e) +{ + if (end == e) { + return; + } + invalidate(); + end = acquire(e); + e->setAnnotation(this, false); +} + /* Class Document */ void Document::doResolve(ResolutionState &state) diff --git a/src/core/model/Document.hpp b/src/core/model/Document.hpp index b41393e..bffd397 100644 --- a/src/core/model/Document.hpp +++ b/src/core/model/Document.hpp @@ -126,6 +126,7 @@ class Document; class StructureNode; class StructuredEntity; class DocumentPrimitive; +class AnnotationEntity; class Anchor; /** @@ -409,14 +410,13 @@ public: /** * Creates a new Anchor as child of this DocumentEntity. * - * @param name is the Anchor id. * @param fieldName is the name of the field, where the newly created * Anchor shall be added to this DocumentEntity. * * @return the newly created Anchor. */ Rooted createChildAnchor( - std::string name, const std::string &fieldName = DEFAULT_FIELD_NAME); + const std::string &fieldName = DEFAULT_FIELD_NAME); }; /** @@ -577,6 +577,9 @@ public: * Please refer to the AnnotationEntity documentation for more information. */ class Anchor : public StructureNode { +private: + Owned annotation; + protected: bool doValidate(Logger &logger) const override; @@ -589,15 +592,55 @@ public: * not the AnnotationEntity that references this Anchor. * Note that this Anchor will automatically register itself * as child of the given parent. - * @param name is the Anchor id. * @param fieldName is the name of the field in the parent DocumentEntity * where this Anchor shall be added. */ - Anchor(Manager &mgr, std::string name, Handle parent, + Anchor(Manager &mgr, Handle parent, const std::string &fieldName = DEFAULT_FIELD_NAME) - : StructureNode(mgr, std::move(name), parent, fieldName) + : StructureNode(mgr, "", parent, fieldName) { } + + /** + * Returns the AnnotationEntity this Anchor belongs to. + * + * @return the AnnotationEntity this Anchor belongs to. + */ + Rooted getAnnotation() const { return annotation; } + + /** + * Sets the AnnotationEntity this Anchor belongs to. If this Anchor belonged + * to an AnnotationEntity before already, this reference is removed. This + * also sets the start/end reference of the new AnnotationEntity this Anchor + * shall belong to. + * + * @param anno the new AnnotationEntity this Anchor shall belong to. + * @param start true if this Anchor should be added as start anchor, false + * if it should be added as end Anchor. + */ + void setAnnotation(Handle anno, bool start); + + /** + * Returns true if and only if this Anchor is the start Anchor of the + * AnnotationEntity it belongs to. Note that this will return false also if + * no AnnotationEntity is set yet. So isStart() == false and isEnd() == + * false is possible and occurs if and only if getAnnotation() == nullptr. + * + * @return true if and only if this Anchor is the start Anchor of the + * AnnotationEntity it belongs to. + */ + bool isStart() const; + + /** + * Returns true if and only if this Anchor is the end Anchor of the + * AnnotationEntity it belongs to. Note that this will return false also if + * no AnnotationEntity is set yet. So isStart() == false and isEnd() == + * false is possible and occurs if and only if getAnnotation() == nullptr. + * + * @return true if and only if this Anchor is the end Anchor of the + * AnnotationEntity it belongs to. + */ + bool isEnd() const; }; /** @@ -679,22 +722,13 @@ public: * * @param s is the new start Anchor for this AnnotationEntity. */ - void setStart(Handle s) - { - invalidate(); - start = acquire(s); - } - + void setStart(Handle s); /** * Sets the end Anchor of this AnnotationEntity. * * @param e is the new end Anchor for this AnnotationEntity. */ - void setEnd(Handle e) - { - invalidate(); - end = acquire(e); - } + void setEnd(Handle e); }; /** diff --git a/src/plugins/html/DemoOutput.cpp b/src/plugins/html/DemoOutput.cpp index cb34cbe..3c54763 100644 --- a/src/plugins/html/DemoOutput.cpp +++ b/src/plugins/html/DemoOutput.cpp @@ -55,23 +55,13 @@ void DemoHTMLTransformer::writeHTML(Handle doc, std::ostream &out, // So far was the "preamble". No we have to get to the document content. - // build the start and end map for annotation processing. - AnnoMap startMap; - AnnoMap endMap; - for (auto &a : doc->getAnnotations()) { - // we assume uniquely IDed annotations, which should be checked in the - // validation process. - startMap.emplace(a->getStart()->getName(), a); - endMap.emplace(a->getEnd()->getName(), a); - } - // extract the book root node. Rooted root = doc->getRoot(); if (root->getDescriptor()->getName() != "book") { throw OusiaException("The given documents root is no book node!"); } // transform the book node. - Rooted book = transformSection(body, root, startMap, endMap); + Rooted book = transformSection(body, root); // add it as child to the body node. body->addChild(book); @@ -100,8 +90,7 @@ SectionType getSectionType(const std::string &name) } Rooted DemoHTMLTransformer::transformSection( - Handle parent, Handle section, - AnnoMap &startMap, AnnoMap &endMap) + Handle parent, Handle section) { Manager &mgr = section->getManager(); // check the section type. @@ -140,8 +129,7 @@ Rooted DemoHTMLTransformer::transformSection( Rooted h{new xml::Element{mgr, sec, headingclass}}; sec->addChild(h); // extract the heading text, enveloped in a paragraph Element. - Rooted h_content = - transformParagraph(h, heading, startMap, endMap); + Rooted h_content = transformParagraph(h, heading); // We omit the paragraph Element and add the children directly to the // heading Element for (auto &n : h_content->getChildren()) { @@ -165,11 +153,11 @@ Rooted DemoHTMLTransformer::transformSection( const std::string childDescriptorName = s->getDescriptor()->getName(); Rooted child; if (childDescriptorName == "paragraph") { - child = transformParagraph(sec, s, startMap, endMap); + child = transformParagraph(sec, s); } else if (childDescriptorName == "ul" || childDescriptorName == "ol") { - child = transformList(sec, s, startMap, endMap); + child = transformList(sec, s); } else { - child = transformSection(sec, s, startMap, endMap); + child = transformSection(sec, s); } if (!child.isNull()) { sec->addChild(child); @@ -179,8 +167,7 @@ Rooted DemoHTMLTransformer::transformSection( } Rooted DemoHTMLTransformer::transformList( - Handle parent, Handle list, - AnnoMap &startMap, AnnoMap &endMap) + Handle parent, Handle list) { Manager &mgr = list->getManager(); // create the list Element, which is either ul or ol (depends on descriptor) @@ -195,8 +182,7 @@ Rooted DemoHTMLTransformer::transformList( Rooted li{new xml::Element{mgr, l, "li"}}; l->addChild(li); // extract the item text, enveloped in a paragraph Element. - Rooted li_content = - transformParagraph(li, item, startMap, endMap); + Rooted li_content = transformParagraph(li, item); // We omit the paragraph Element and add the children directly to // the list item for (auto &n : li_content->getChildren()) { @@ -229,8 +215,7 @@ static Rooted openAnnotation(Manager &mgr, AnnoStack &opened, } Rooted DemoHTMLTransformer::transformParagraph( - Handle parent, Handle par, - AnnoMap &startMap, AnnoMap &endMap) + Handle parent, Handle par) { Manager &mgr = par->getManager(); // create the p Element @@ -245,8 +230,7 @@ Rooted DemoHTMLTransformer::transformParagraph( Rooted strong{new xml::Element{mgr, p, "strong"}}; p->addChild(strong); // extract the heading text, enveloped in a paragraph Element. - Rooted h_content = - transformParagraph(strong, heading, startMap, endMap); + Rooted h_content = transformParagraph(strong, heading); // We omit the paragraph Element and add the children directly to the // heading Element for (auto &n : h_content->getChildren()) { @@ -267,17 +251,15 @@ Rooted DemoHTMLTransformer::transformParagraph( Rooted current = p; for (auto &n : par->getField()) { if (n->isa(&RttiTypes::Anchor)) { + Rooted a = n.cast(); // check if this is a start Anchor. - // here we assume, again, that the ids/names of anchors are unique. - auto it = startMap.find(n->getName()); - if (it != startMap.end()) { + if (a->isStart()) { // if we have a start anchor, we open an annotation element. - current = openAnnotation(mgr, opened, it->second, current); + current = + openAnnotation(mgr, opened, a->getAnnotation(), current); continue; - } - // check if this is an end Anchor. - auto it2 = endMap.find(n->getName()); - if (it2 != endMap.end()) { + // check if this is an end Anchor. + } else if (a->isEnd()) { /* * Now it gets somewhat interesting: We have to close all * tags that started after the one that is closed now and @@ -289,7 +271,7 @@ Rooted DemoHTMLTransformer::transformParagraph( Rooted closed = opened.top(); current = current->getParent(); opened.pop(); - while (closed->getEnd()->getName() != n->getName()) { + while (closed != a->getAnnotation()) { /* * We implicitly do close tags by climbing up the XML tree * until we are at the right element. @@ -312,6 +294,7 @@ Rooted DemoHTMLTransformer::transformParagraph( current = openAnnotation(mgr, opened, closed, current); } } + // otherwise it is a disconnected Anchor and we can ignore it. continue; } // if this is not an anchor, we can only handle text. diff --git a/src/plugins/html/DemoOutput.hpp b/src/plugins/html/DemoOutput.hpp index 67b7494..0650621 100644 --- a/src/plugins/html/DemoOutput.hpp +++ b/src/plugins/html/DemoOutput.hpp @@ -39,8 +39,6 @@ namespace ousia { namespace html { -typedef std::map> AnnoMap; - class DemoHTMLTransformer { private: /** @@ -50,23 +48,20 @@ private: * called recursively. */ Rooted transformSection(Handle parent, - Handle sec, - AnnoMap &startMap, AnnoMap &endMap); + Handle sec); /** * This transforms a list entity, namely ul and ol to an XHTML element. * For each item, the transformParagraph function is called. */ Rooted transformList(Handle parent, - Handle list, - AnnoMap &startMap, AnnoMap &endMap); + Handle list); /** * This transforms a paragraph-like entity, namely heading, item and * paragraph, to an XHTML element including the text and the anchors - * contained. For anchor handling we require the AnnoMaps. + * contained. */ Rooted transformParagraph(Handle parent, - Handle par, - AnnoMap &startMap, AnnoMap &endMap); + Handle par); public: /** @@ -89,8 +84,7 @@ public: * @param pretty is a flag that manipulates whether newlines and tabs are * used. */ - void writeHTML(Handle doc, std::ostream &out, - bool pretty = true); + void writeHTML(Handle doc, std::ostream &out, bool pretty = true); }; } } diff --git a/test/core/model/DocumentTest.cpp b/test/core/model/DocumentTest.cpp index 5acf13f..0c6eea6 100644 --- a/test/core/model/DocumentTest.cpp +++ b/test/core/model/DocumentTest.cpp @@ -269,12 +269,12 @@ TEST(Document, validate) doc->referenceDomain(domain); Rooted root = buildRootStructuredEntity(doc, logger, {"root"}); - Rooted start{new Anchor(mgr, "start", root)}; + Rooted start{new Anchor(mgr, root)}; Rooted child = buildStructuredEntity(doc, logger, root, {"childSub"}); Rooted primitive{ new DocumentPrimitive(mgr, child, {2}, "int")}; - Rooted end{new Anchor(mgr, "end", root)}; + Rooted end{new Anchor(mgr, root)}; ASSERT_EQ(ValidationState::UNKNOWN, doc->getValidationState()); ASSERT_TRUE(doc->validate(logger)); // then add an AnnotationEntity without Anchors. diff --git a/test/core/model/TestAdvanced.hpp b/test/core/model/TestAdvanced.hpp index 575860b..27f33cc 100644 --- a/test/core/model/TestAdvanced.hpp +++ b/test/core/model/TestAdvanced.hpp @@ -150,19 +150,17 @@ static bool addHeading(Logger &logger, Handle doc, return true; } -static int annoIdx = 1; - // Only works for non-overlapping annotations! static bool addAnnotation(Logger &logger, Handle doc, Handle parent, const std::string &text, const std::string &annoClass) { Manager &mgr = parent->getManager(); - Rooted start{new Anchor(mgr, std::to_string(annoIdx++), parent)}; + Rooted start{new Anchor(mgr, parent)}; if (!addText(logger, doc, parent, text)) { return false; } - Rooted end{new Anchor(mgr, std::to_string(annoIdx++), parent)}; + Rooted end{new Anchor(mgr, parent)}; Rooted anno = buildAnnotationEntity(doc, logger, {annoClass}, start, end); if (anno.isNull()) { diff --git a/test/plugins/html/DemoOutputTest.cpp b/test/plugins/html/DemoOutputTest.cpp index 5006655..2f56e40 100644 --- a/test/plugins/html/DemoOutputTest.cpp +++ b/test/plugins/html/DemoOutputTest.cpp @@ -41,14 +41,11 @@ TEST(DemoHTMLTransformer, writeHTML) Manager mgr{1}; Rooted sys{new SystemTypesystem(mgr)}; // Get the domains. - Rooted bookDom = - constructBookDomain(mgr, sys, logger); + Rooted bookDom = constructBookDomain(mgr, sys, logger); Rooted headingDom = constructHeadingDomain(mgr, sys, bookDom, logger); - Rooted listDom = - constructListDomain(mgr, sys, bookDom, logger); - Rooted emDom = - constructEmphasisDomain(mgr, sys, logger); + Rooted listDom = constructListDomain(mgr, sys, bookDom, logger); + Rooted emDom = constructEmphasisDomain(mgr, sys, logger); // Construct the document. Rooted doc = constructAdvancedDocument( mgr, logger, bookDom, headingDom, listDom, emDom); @@ -79,10 +76,8 @@ TEST(DemoHTMLTransformer, AnnotationProcessing) Manager mgr{1}; Rooted sys{new SystemTypesystem(mgr)}; // Get the domains. - Rooted bookDom = - constructBookDomain(mgr, sys, logger); - Rooted emDom = - constructEmphasisDomain(mgr, sys, logger); + Rooted bookDom = constructBookDomain(mgr, sys, logger); + Rooted emDom = constructEmphasisDomain(mgr, sys, logger); // Construct a document only containing overlapping annotations. // it has the form: blablubbla Rooted doc{new Document(mgr, "annotations.oxd")}; @@ -93,13 +88,13 @@ TEST(DemoHTMLTransformer, AnnotationProcessing) Rooted p = buildStructuredEntity(doc, logger, book, {"paragraph"}); ASSERT_TRUE(p != nullptr); - Rooted em_start{new Anchor(mgr, "1", p)}; + Rooted em_start{new Anchor(mgr, p)}; ASSERT_TRUE(addText(logger, doc, p, "bla")); - Rooted strong_start{new Anchor(mgr, "2", p)}; + Rooted strong_start{new Anchor(mgr, p)}; ASSERT_TRUE(addText(logger, doc, p, "blub")); - Rooted em_end{new Anchor(mgr, "3", p)}; + Rooted em_end{new Anchor(mgr, p)}; ASSERT_TRUE(addText(logger, doc, p, "bla")); - Rooted strong_end{new Anchor(mgr, "4", p)}; + Rooted strong_end{new Anchor(mgr, p)}; buildAnnotationEntity(doc, logger, {"emphasized"}, em_start, em_end); buildAnnotationEntity(doc, logger, {"strong"}, strong_start, strong_end); -- cgit v1.2.3