summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/model/Document.cpp112
-rw-r--r--src/core/model/Document.hpp66
-rw-r--r--src/plugins/html/DemoOutput.cpp53
-rw-r--r--src/plugins/html/DemoOutput.hpp16
-rw-r--r--test/core/model/DocumentTest.cpp4
-rw-r--r--test/core/model/TestAdvanced.hpp6
-rw-r--r--test/plugins/html/DemoOutputTest.cpp23
7 files changed, 185 insertions, 95 deletions
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<Descriptor> 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<DocumentPrimitive> DocumentEntity::createChildDocumentPrimitive(
subInst->getManager(), subInst, std::move(content), fieldName)};
}
-Rooted<Anchor> DocumentEntity::createChildAnchor(std::string name,
- const std::string &fieldName)
+Rooted<Anchor> DocumentEntity::createChildAnchor(const std::string &fieldName)
{
return Rooted<Anchor>{
- 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<AnnotationEntity> 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<Document> parent,
@@ -508,13 +554,13 @@ AnnotationEntity::AnnotationEntity(Manager &mgr, Handle<Document> parent,
Handle<Anchor> start, Handle<Anchor> 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<Anchor> s)
+{
+ if (start == s) {
+ return;
+ }
+ invalidate();
+ start = acquire(s);
+ s->setAnnotation(this, true);
+}
+
+void AnnotationEntity::setEnd(Handle<Anchor> 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<Anchor> 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<AnnotationEntity> 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<Node> parent,
+ Anchor(Manager &mgr, Handle<Node> 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<AnnotationEntity> 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<AnnotationEntity> 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<Anchor> s)
- {
- invalidate();
- start = acquire(s);
- }
-
+ void setStart(Handle<Anchor> s);
/**
* Sets the end Anchor of this AnnotationEntity.
*
* @param e is the new end Anchor for this AnnotationEntity.
*/
- void setEnd(Handle<Anchor> e)
- {
- invalidate();
- end = acquire(e);
- }
+ void setEnd(Handle<Anchor> 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<Document> 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<StructuredEntity> root = doc->getRoot();
if (root->getDescriptor()->getName() != "book") {
throw OusiaException("The given documents root is no book node!");
}
// transform the book node.
- Rooted<xml::Element> book = transformSection(body, root, startMap, endMap);
+ Rooted<xml::Element> 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<xml::Element> DemoHTMLTransformer::transformSection(
- Handle<xml::Element> parent, Handle<StructuredEntity> section,
- AnnoMap &startMap, AnnoMap &endMap)
+ Handle<xml::Element> parent, Handle<StructuredEntity> section)
{
Manager &mgr = section->getManager();
// check the section type.
@@ -140,8 +129,7 @@ Rooted<xml::Element> DemoHTMLTransformer::transformSection(
Rooted<xml::Element> h{new xml::Element{mgr, sec, headingclass}};
sec->addChild(h);
// extract the heading text, enveloped in a paragraph Element.
- Rooted<xml::Element> h_content =
- transformParagraph(h, heading, startMap, endMap);
+ Rooted<xml::Element> 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<xml::Element> DemoHTMLTransformer::transformSection(
const std::string childDescriptorName = s->getDescriptor()->getName();
Rooted<xml::Element> 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<xml::Element> DemoHTMLTransformer::transformSection(
}
Rooted<xml::Element> DemoHTMLTransformer::transformList(
- Handle<xml::Element> parent, Handle<StructuredEntity> list,
- AnnoMap &startMap, AnnoMap &endMap)
+ Handle<xml::Element> parent, Handle<StructuredEntity> list)
{
Manager &mgr = list->getManager();
// create the list Element, which is either ul or ol (depends on descriptor)
@@ -195,8 +182,7 @@ Rooted<xml::Element> DemoHTMLTransformer::transformList(
Rooted<xml::Element> li{new xml::Element{mgr, l, "li"}};
l->addChild(li);
// extract the item text, enveloped in a paragraph Element.
- Rooted<xml::Element> li_content =
- transformParagraph(li, item, startMap, endMap);
+ Rooted<xml::Element> 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<xml::Element> openAnnotation(Manager &mgr, AnnoStack &opened,
}
Rooted<xml::Element> DemoHTMLTransformer::transformParagraph(
- Handle<xml::Element> parent, Handle<StructuredEntity> par,
- AnnoMap &startMap, AnnoMap &endMap)
+ Handle<xml::Element> parent, Handle<StructuredEntity> par)
{
Manager &mgr = par->getManager();
// create the p Element
@@ -245,8 +230,7 @@ Rooted<xml::Element> DemoHTMLTransformer::transformParagraph(
Rooted<xml::Element> strong{new xml::Element{mgr, p, "strong"}};
p->addChild(strong);
// extract the heading text, enveloped in a paragraph Element.
- Rooted<xml::Element> h_content =
- transformParagraph(strong, heading, startMap, endMap);
+ Rooted<xml::Element> 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<xml::Element> DemoHTMLTransformer::transformParagraph(
Rooted<xml::Element> current = p;
for (auto &n : par->getField()) {
if (n->isa(&RttiTypes::Anchor)) {
+ Rooted<Anchor> a = n.cast<Anchor>();
// 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<xml::Element> DemoHTMLTransformer::transformParagraph(
Rooted<AnnotationEntity> 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<xml::Element> 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<std::string, Rooted<AnnotationEntity>> AnnoMap;
-
class DemoHTMLTransformer {
private:
/**
@@ -50,23 +48,20 @@ private:
* called recursively.
*/
Rooted<xml::Element> transformSection(Handle<xml::Element> parent,
- Handle<StructuredEntity> sec,
- AnnoMap &startMap, AnnoMap &endMap);
+ Handle<StructuredEntity> sec);
/**
* This transforms a list entity, namely ul and ol to an XHTML element.
* For each item, the transformParagraph function is called.
*/
Rooted<xml::Element> transformList(Handle<xml::Element> parent,
- Handle<StructuredEntity> list,
- AnnoMap &startMap, AnnoMap &endMap);
+ Handle<StructuredEntity> 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<xml::Element> transformParagraph(Handle<xml::Element> parent,
- Handle<StructuredEntity> par,
- AnnoMap &startMap, AnnoMap &endMap);
+ Handle<StructuredEntity> par);
public:
/**
@@ -89,8 +84,7 @@ public:
* @param pretty is a flag that manipulates whether newlines and tabs are
* used.
*/
- void writeHTML(Handle<Document> doc, std::ostream &out,
- bool pretty = true);
+ void writeHTML(Handle<Document> 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<StructuredEntity> root =
buildRootStructuredEntity(doc, logger, {"root"});
- Rooted<Anchor> start{new Anchor(mgr, "start", root)};
+ Rooted<Anchor> start{new Anchor(mgr, root)};
Rooted<StructuredEntity> child =
buildStructuredEntity(doc, logger, root, {"childSub"});
Rooted<DocumentPrimitive> primitive{
new DocumentPrimitive(mgr, child, {2}, "int")};
- Rooted<Anchor> end{new Anchor(mgr, "end", root)};
+ Rooted<Anchor> 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<Document> doc,
return true;
}
-static int annoIdx = 1;
-
// Only works for non-overlapping annotations!
static bool addAnnotation(Logger &logger, Handle<Document> doc,
Handle<StructuredEntity> parent,
const std::string &text, const std::string &annoClass)
{
Manager &mgr = parent->getManager();
- Rooted<Anchor> start{new Anchor(mgr, std::to_string(annoIdx++), parent)};
+ Rooted<Anchor> start{new Anchor(mgr, parent)};
if (!addText(logger, doc, parent, text)) {
return false;
}
- Rooted<Anchor> end{new Anchor(mgr, std::to_string(annoIdx++), parent)};
+ Rooted<Anchor> end{new Anchor(mgr, parent)};
Rooted<AnnotationEntity> 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<SystemTypesystem> sys{new SystemTypesystem(mgr)};
// Get the domains.
- Rooted<Domain> bookDom =
- constructBookDomain(mgr, sys, logger);
+ Rooted<Domain> bookDom = constructBookDomain(mgr, sys, logger);
Rooted<Domain> headingDom =
constructHeadingDomain(mgr, sys, bookDom, logger);
- Rooted<Domain> listDom =
- constructListDomain(mgr, sys, bookDom, logger);
- Rooted<Domain> emDom =
- constructEmphasisDomain(mgr, sys, logger);
+ Rooted<Domain> listDom = constructListDomain(mgr, sys, bookDom, logger);
+ Rooted<Domain> emDom = constructEmphasisDomain(mgr, sys, logger);
// Construct the document.
Rooted<Document> doc = constructAdvancedDocument(
mgr, logger, bookDom, headingDom, listDom, emDom);
@@ -79,10 +76,8 @@ TEST(DemoHTMLTransformer, AnnotationProcessing)
Manager mgr{1};
Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)};
// Get the domains.
- Rooted<Domain> bookDom =
- constructBookDomain(mgr, sys, logger);
- Rooted<Domain> emDom =
- constructEmphasisDomain(mgr, sys, logger);
+ Rooted<Domain> bookDom = constructBookDomain(mgr, sys, logger);
+ Rooted<Domain> emDom = constructEmphasisDomain(mgr, sys, logger);
// Construct a document only containing overlapping annotations.
// it has the form: <em>bla<strong>blub</em>bla</strong>
Rooted<Document> doc{new Document(mgr, "annotations.oxd")};
@@ -93,13 +88,13 @@ TEST(DemoHTMLTransformer, AnnotationProcessing)
Rooted<StructuredEntity> p =
buildStructuredEntity(doc, logger, book, {"paragraph"});
ASSERT_TRUE(p != nullptr);
- Rooted<Anchor> em_start{new Anchor(mgr, "1", p)};
+ Rooted<Anchor> em_start{new Anchor(mgr, p)};
ASSERT_TRUE(addText(logger, doc, p, "bla"));
- Rooted<Anchor> strong_start{new Anchor(mgr, "2", p)};
+ Rooted<Anchor> strong_start{new Anchor(mgr, p)};
ASSERT_TRUE(addText(logger, doc, p, "blub"));
- Rooted<Anchor> em_end{new Anchor(mgr, "3", p)};
+ Rooted<Anchor> em_end{new Anchor(mgr, p)};
ASSERT_TRUE(addText(logger, doc, p, "bla"));
- Rooted<Anchor> strong_end{new Anchor(mgr, "4", p)};
+ Rooted<Anchor> strong_end{new Anchor(mgr, p)};
buildAnnotationEntity(doc, logger, {"emphasized"}, em_start, em_end);
buildAnnotationEntity(doc, logger, {"strong"}, strong_start, strong_end);