diff options
36 files changed, 647 insertions, 347 deletions
diff --git a/data/domain/bibliography.osxml b/data/domain/bibliography.osxml index 11e2606..5953e5f 100644 --- a/data/domain/bibliography.osxml +++ b/data/domain/bibliography.osxml @@ -12,16 +12,31 @@ <field name="bibliography" isSubtree="true"/> </parentRef> </struct> - <struct name="bibEntry"> - <primitive name="name" type="string" isSubtree="true"/> - <primitive name="year" type="int" isSubtree="true"/> - <primitive name="journal" type="string" isSubtree="true" optional="true"/> - <primitive name="pages" type="cardinality" isSubtree="true" optional="true"/> - <!-- Here a geographical enum or something would be more exact --> - <primitive name="location" type="string" isSubtree="true" optional="true"/> - <field name="authors" optional="true"> - <childRef ref="meta.author"/> + <field> + <childRef ref="meta.authors"/> + <childRef ref="title"/> + <childRef ref="year"/> + <childRef ref="journal"/> + <childRef ref="pages"/> + <childRef ref="location"/> </field> </struct> + <struct name="title" cardinality="{1}"> + <primitive type="string"/> + </struct> + <struct name="year" cardinality="{1}"> + <primitive type="int"/> + </struct> + <struct name="journal" cardinality="{0-1}"> + <!-- here some kind of database reference would be better --> + <primitive type="string"/> + </struct> + <struct name="pages" cardinality="{0-1}"> + <primitive type="cardinality"/> + </struct> + <struct name="location" cardinality="{0-1}"> + <!-- here some kind of database reference would be better --> + <primitive type="string"/> + </struct> </domain> diff --git a/data/domain/meta.osxml b/data/domain/meta.osxml index 7e96699..d214921 100644 --- a/data/domain/meta.osxml +++ b/data/domain/meta.osxml @@ -9,7 +9,6 @@ <struct name="meta" cardinality="{1}" transparent="true"> <field> - <childRef ref="title"/> <childRef ref="author"/> <childRef ref="version"/> </field> @@ -22,21 +21,24 @@ <!-- One could also include "article" and other things here --> </struct> - <!-- A title is just a heading --> - <struct name="title"> - <field> - <childRef ref="heading"/> - </field> - </struct> - - <!-- no explicit cardinality, because we might have multiple authors --> - <struct name="author"> + <struct name="person"> <primitive isSubtree="true" name="firstName" type="string"/> <primitive isSubtree="true" name="lastName" type="string"/> <primitive isSubtree="true" name="email" type="email" optional="true"/> <primitive isSubtree="true" name="affiliation" type="affiliation" optional="true"/> </struct> + <!-- wrapper author tag to allow specifying no authors whatsoever. But if + an author is specified it has to be at least one primary author. --> + <struct name="authors" transparent="true" cardinality="{0-1}"> + <field> + <childRef ref="author"/> + </field> + </struct> + + <!-- no explicit cardinality, because we might have multiple authors --> + <struct name="author" isa="person"/> + <!-- but we need at least one primary author --> <struct name="primaryAuthor" cardinality="{>0}" isa="author"/> diff --git a/src/core/common/VariantConverter.cpp b/src/core/common/VariantConverter.cpp index a9ca467..b43d04e 100644 --- a/src/core/common/VariantConverter.cpp +++ b/src/core/common/VariantConverter.cpp @@ -262,7 +262,7 @@ bool VariantConverter::toString(Variant &var, Logger &logger, Mode mode) // Print cardinality syntax Variant::cardinalityType card = var.asCardinality(); std::stringstream ss; - ss << "<cardinality {"; + ss << "{"; bool first = true; for (Variant::rangeType r : card.getRanges()) { if (first) { @@ -288,7 +288,7 @@ bool VariantConverter::toString(Variant &var, Logger &logger, Mode mode) ss << ">" << std::to_string(r.start - 1); } } - ss << "}>"; + ss << "}"; var = ss.str().c_str(); return true; } diff --git a/src/core/common/VariantWriter.cpp b/src/core/common/VariantWriter.cpp index 427ac5d..b1bafe4 100644 --- a/src/core/common/VariantWriter.cpp +++ b/src/core/common/VariantWriter.cpp @@ -104,8 +104,9 @@ static void writeLinebreak(std::ostream &stream, bool pretty) * @param pretty if true, the resulting value is properly indented. * @param level is the current indentation level. */ -static void writeJsonInternal(const Variant &var, std::ostream &stream, - bool pretty, int level) +template <char ObjectStart, char ObjectEnd, char Equals> +static void writeInternal(const Variant &var, std::ostream &stream, bool pretty, + int level) { switch (var.getType()) { case VariantType::NULLPTR: @@ -127,7 +128,8 @@ static void writeJsonInternal(const Variant &var, std::ostream &stream, const Variant::arrayType &arr = var.asArray(); for (size_t i = 0; i < arr.size(); i++) { writeIndentation(stream, pretty, level + 1); - writeJsonInternal(arr[i], stream, pretty, level + 1); + writeInternal<ObjectStart, ObjectEnd, Equals>( + arr[i], stream, pretty, level + 1); if (i + 1 != arr.size()) { stream << ","; } @@ -139,21 +141,22 @@ static void writeJsonInternal(const Variant &var, std::ostream &stream, } case VariantType::MAP: { writeIndentation(stream, pretty, level); - stream << "{"; + stream << ObjectStart; writeLinebreak(stream, pretty); const Variant::mapType &map = var.asMap(); for (auto it = map.cbegin(); it != map.cend();) { writeIndentation(stream, pretty, level + 1); writeJsonString(it->first, stream); - stream << (pretty ? ": " : ":"); - writeJsonInternal(it->second, stream, pretty, level + 1); + stream << Equals << (pretty ? " " : ""); + writeInternal<ObjectStart, ObjectEnd, Equals>( + it->second, stream, pretty, level + 1); if ((++it) != map.cend()) { stream << ","; } writeLinebreak(stream, pretty); } writeIndentation(stream, pretty, level); - stream << "}"; + stream << ObjectEnd; return; } } @@ -162,7 +165,7 @@ static void writeJsonInternal(const Variant &var, std::ostream &stream, void VariantWriter::writeJson(const Variant &var, std::ostream &stream, bool pretty) { - writeJsonInternal(var, stream, pretty, 0); + writeInternal<'{', '}', ':'>(var, stream, pretty, 0); } std::string VariantWriter::writeJsonToString(const Variant &var, bool pretty) @@ -172,6 +175,16 @@ std::string VariantWriter::writeJsonToString(const Variant &var, bool pretty) return ss.str(); } - +void VariantWriter::writeOusia(const Variant &var, std::ostream &stream, + bool pretty) +{ + writeInternal<'[', ']', '='>(var, stream, pretty, 0); } +std::string VariantWriter::writeOusiaToString(const Variant &var, bool pretty) +{ + std::stringstream ss; + writeOusia(var, ss, pretty); + return ss.str(); +} +}
\ No newline at end of file diff --git a/src/core/common/VariantWriter.hpp b/src/core/common/VariantWriter.hpp index 7fe32fb..12f4bba 100644 --- a/src/core/common/VariantWriter.hpp +++ b/src/core/common/VariantWriter.hpp @@ -59,8 +59,28 @@ public: * @param var is the variant that should be serialized. * @param pretty if true, the resulting value is properly indented. */ - static std::string writeJsonToString(const Variant &var, bool pretty = true); + static std::string writeJsonToString(const Variant &var, + bool pretty = true); + /** + * Dumps the Variant as re-readable ousia data. Note that the resulting + * data is invalid if the Variant consists of function or object references. + * + * @param var is the variant that should be serialized. + * @param stream is the stream the result should be written to. + * @param pretty if true, the resulting value is properly indented. + */ + static void writeOusia(const Variant &var, std::ostream &stream, + bool pretty = true); + + /** + * Dumps the Variant as re-readable ousia data to a string. + * + * @param var is the variant that should be serialized. + * @param pretty if true, the resulting value is properly indented. + */ + static std::string writeOusiaToString(const Variant &var, + bool pretty = true); }; } diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index ca8f889..8255401 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -324,21 +324,31 @@ bool FieldDescriptor::doValidate(Logger &logger) const return valid; } -static void gatherSubclasses(NodeVector<StructuredClass> &res, - Handle<StructuredClass> strct) +static void gatherSubclasses( + std::unordered_set<const StructuredClass *>& visited, + NodeVector<StructuredClass> &res, Handle<StructuredClass> strct) { + // this check is to prevent cycles. + if (!visited.insert(strct.get()).second) { + return; + } for (auto sub : strct->getSubclasses()) { + // this check is to prevent cycles. + if(visited.count(sub.get())){ + continue; + } res.push_back(sub); - gatherSubclasses(res, sub); + gatherSubclasses(visited, res, sub); } } NodeVector<StructuredClass> FieldDescriptor::getChildrenWithSubclasses() const { + std::unordered_set<const StructuredClass *> visited; NodeVector<StructuredClass> res; for (auto c : children) { res.push_back(c); - gatherSubclasses(res, c); + gatherSubclasses(visited, res, c); } return res; } @@ -566,8 +576,9 @@ bool Descriptor::addAndSortFieldDescriptor(Handle<FieldDescriptor> fd, if (fds.find(fd) == fds.end()) { invalidate(); // check if the previous field is a tree field already. - if (!fds.empty() && !fieldDescriptors.empty() && - fds.back()->getFieldType() == FieldDescriptor::FieldType::TREE && + if (!fieldDescriptors.empty() && + fieldDescriptors.back()->getFieldType() == + FieldDescriptor::FieldType::TREE && fd->getFieldType() != FieldDescriptor::FieldType::TREE) { // if so we add the new field before the TREE field. fieldDescriptors.insert(fieldDescriptors.end() - 1, fd); @@ -776,8 +787,13 @@ void StructuredClass::removeSubclass(Handle<StructuredClass> sc, Logger &logger) Rooted<FieldDescriptor> StructuredClass::gatherFieldDescriptors( NodeVector<FieldDescriptor> ¤t, + std::unordered_set<const StructuredClass *> &visited, std::set<std::string> &overriddenFields, bool hasTREE) const { + // this check is to prevent cycles of inheritance to mess up this function. + if (!visited.insert(this).second) { + return nullptr; + } Rooted<FieldDescriptor> mainField; NodeVector<FieldDescriptor> tmp; // first gather the non-overridden fields. @@ -798,8 +814,8 @@ Rooted<FieldDescriptor> StructuredClass::gatherFieldDescriptors( if (superclass != nullptr) { Rooted<FieldDescriptor> super_main_field = - superclass->gatherFieldDescriptors(current, overriddenFields, - hasTREE); + superclass->gatherFieldDescriptors(current, visited, + overriddenFields, hasTREE); if (!hasTREE) { mainField = super_main_field; } @@ -814,9 +830,10 @@ NodeVector<FieldDescriptor> StructuredClass::getFieldDescriptors() const { // in this case we return a NodeVector of Rooted entries without owner. NodeVector<FieldDescriptor> vec; + std::unordered_set<const StructuredClass *> visited; std::set<std::string> overriddenFields; Rooted<FieldDescriptor> mainField = - gatherFieldDescriptors(vec, overriddenFields, false); + gatherFieldDescriptors(vec, visited, overriddenFields, false); if (mainField != nullptr) { vec.push_back(mainField); } @@ -961,4 +978,4 @@ const Rtti Domain = RttiBuilder<ousia::Domain>("Domain") .parent(&RootNode) .composedOf({&StructuredClass, &AnnotationClass}); } -} +}
\ No newline at end of file diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index 476a38c..b3fc6c2 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -808,6 +808,7 @@ private: */ Rooted<FieldDescriptor> gatherFieldDescriptors( NodeVector<FieldDescriptor> ¤t, + std::unordered_set<const StructuredClass *> &visited, std::set<std::string> &overriddenFields, bool hasTREE) const; protected: diff --git a/src/core/parser/ParserScope.cpp b/src/core/parser/ParserScope.cpp index b0a4945..dabb03c 100644 --- a/src/core/parser/ParserScope.cpp +++ b/src/core/parser/ParserScope.cpp @@ -216,7 +216,7 @@ void ParserScope::push(Handle<Node> node) nodes.push_back(node); } -void ParserScope::pop() +void ParserScope::pop(Logger &logger) { // Make sure pop is not called without an element on the stack const size_t currentDepth = nodes.size(); @@ -235,10 +235,14 @@ void ParserScope::pop() flags.resize(newLen); // Whenever a RootNode is popped from the stack, we have to perform deferred - // resolution -- however, postpone issuing error messages - if (nodes.back()->isa(&RttiTypes::RootNode)) { - Logger logger; + // resolution and validate the subtree + Rooted<Node> node = nodes.back(); + if (node->isa(&RttiTypes::RootNode)) { + // Perform pending resolutions -- do not issue errors now performDeferredResolution(logger, true); + + // Perform validation of the subtree. + node->validate(logger); } // Remove the element from the stack diff --git a/src/core/parser/ParserScope.hpp b/src/core/parser/ParserScope.hpp index 24af6b8..e27c81e 100644 --- a/src/core/parser/ParserScope.hpp +++ b/src/core/parser/ParserScope.hpp @@ -286,7 +286,13 @@ enum class ParserFlag { * Set to the boolean value "true" if the head section of a file has passed. * This happens once the first non-import tag is reached. */ - POST_HEAD + POST_HEAD, + + /** + * Set to the boolean value "true" if explicit fields may no longer be + * defined inside a structure element. + */ + POST_EXPLICIT_FIELDS }; /** @@ -423,9 +429,14 @@ public: void push(Handle<Node> node); /** - * Removes the last pushed node from the scope. + * Removes the last pushed node from the scope. If the node that is popped + * from the internal stack is a RootNode, pending resolutions are performed + * and the RootNode is validated. + * + * @param logger is the Logger instance to which error messages should be + * logged. */ - void pop(); + void pop(Logger &logger); /** * Returns the top-level nodes. These are the nodes that are pushed onto the @@ -792,6 +803,7 @@ public: bool resolveFieldDescriptor(const std::string &name, Handle<Node> owner, Logger &logger, ResolutionResultCallback resultCallback); + /** * Tries to resolve all currently deferred resolution steps. The list of * pending deferred resolutions is cleared after this function has run. diff --git a/src/core/parser/stack/DocumentHandler.cpp b/src/core/parser/stack/DocumentHandler.cpp index 35146b1..bb04bd3 100644 --- a/src/core/parser/stack/DocumentHandler.cpp +++ b/src/core/parser/stack/DocumentHandler.cpp @@ -47,7 +47,7 @@ bool DocumentHandler::start(Variant::mapType &args) return true; } -void DocumentHandler::end() { scope().pop(); } +void DocumentHandler::end() { scope().pop(logger()); } /* DocumentChildHandler */ @@ -98,6 +98,9 @@ void DocumentChildHandler::createPath(const NodeVector<Node> &path, manager(), scope().getLeaf(), parent->getDescriptor()->getFieldDescriptorIndex(), true)}; scope().push(field); + + // Generally allow explicit fields in the new field + scope().setFlag(ParserFlag::POST_EXPLICIT_FIELDS, false); } void DocumentChildHandler::createPath(const size_t &firstFieldIdx, @@ -113,6 +116,9 @@ void DocumentChildHandler::createPath(const size_t &firstFieldIdx, parent = static_cast<DocumentEntity *>(transparent.get()); createPath(path, parent, 2); + + // Generally allow explicit fields in the new field + scope().setFlag(ParserFlag::POST_EXPLICIT_FIELDS, false); } bool DocumentChildHandler::start(Variant::mapType &args) @@ -136,6 +142,14 @@ bool DocumentChildHandler::start(Variant::mapType &args) Rooted<StructuredEntity> entity; // handle the root note specifically. if (parentNode->isa(&RttiTypes::Document)) { + // if we already have a root node, stop. + if (parentNode.cast<Document>()->getRoot() != nullptr) { + logger().warning( + "This document already has a root node. The additional " + "node is ignored.", + location()); + return false; + } Rooted<StructuredClass> strct = scope().resolve<StructuredClass>( Utils::split(name(), ':'), logger()); if (strct == nullptr) { @@ -170,12 +184,25 @@ bool DocumentChildHandler::start(Variant::mapType &args) ssize_t newFieldIdx = parent->getDescriptor()->getFieldDescriptorIndex(name()); if (newFieldIdx != -1) { - Rooted<DocumentField> field{new DocumentField( - manager(), parentNode, newFieldIdx, false)}; - field->setLocation(location()); - scope().push(field); - isExplicitField = true; - return true; + // Check whether explicit fields are allowed here, if not + if (scope().getFlag(ParserFlag::POST_EXPLICIT_FIELDS)) { + logger().note( + std::string( + "Data or structure commands have already been " + "given, command \"") + + name() + std::string( + "\" is not interpreted explicit " + "field. Move explicit field " + "references to the beginning."), + location()); + } else { + Rooted<DocumentField> field{new DocumentField( + manager(), parentNode, newFieldIdx, false)}; + field->setLocation(location()); + scope().push(field); + isExplicitField = true; + return true; + } } } @@ -200,9 +227,9 @@ bool DocumentChildHandler::start(Variant::mapType &args) // if we have transparent elements above us in the structure // tree we try to unwind them before we give up. // pop the implicit field. - scope().pop(); + scope().pop(logger()); // pop the implicit element. - scope().pop(); + scope().pop(logger()); continue; } throw LoggableException( @@ -218,11 +245,17 @@ bool DocumentChildHandler::start(Variant::mapType &args) parent->getDescriptor()->getFieldDescriptorIndex(); } // create the entity for the new element at last. - //TODO: REMOVE + // TODO: REMOVE strct_name = strct->getName(); entity = parent->createChildStructuredEntity(strct, lastFieldIdx, args, nameAttr); } + + // We're past the region in which explicit fields can be defined in the + // parent structure element + scope().setFlag(ParserFlag::POST_EXPLICIT_FIELDS, true); + + // Bush the entity onto the stack entity->setLocation(location()); scope().push(entity); return true; @@ -237,7 +270,7 @@ void DocumentChildHandler::end() return; } // pop the "main" element. - scope().pop(); + scope().pop(logger()); } bool DocumentChildHandler::fieldStart(bool &isDefault, size_t fieldIdx) @@ -259,6 +292,9 @@ bool DocumentChildHandler::fieldStart(bool &isDefault, size_t fieldIdx) parent->getDescriptor()->getFieldDescriptors(); if (isDefault) { + if(fields.empty()){ + return false; + } fieldIdx = fields.size() - 1; } else { if (fieldIdx >= fields.size()) { @@ -271,6 +307,10 @@ bool DocumentChildHandler::fieldStart(bool &isDefault, size_t fieldIdx) new DocumentField(manager(), parentNode, fieldIdx, false)}; field->setLocation(location()); scope().push(field); + + // Generally allow explicit fields in the new field + scope().setFlag(ParserFlag::POST_EXPLICIT_FIELDS, false); + return true; } @@ -279,15 +319,15 @@ void DocumentChildHandler::fieldEnd() assert(scope().getLeaf()->isa(&RttiTypes::DocumentField)); // pop the field from the stack. - scope().pop(); + scope().pop(logger()); // pop all remaining transparent elements. while (scope().getLeaf()->isa(&RttiTypes::StructuredEntity) && scope().getLeaf().cast<StructuredEntity>()->isTransparent()) { // pop the transparent element. - scope().pop(); + scope().pop(logger()); // pop the transparent field. - scope().pop(); + scope().pop(logger()); } } @@ -334,6 +374,10 @@ bool DocumentChildHandler::convertData(Handle<FieldDescriptor> field, bool DocumentChildHandler::data(Variant &data) { + // We're past the region in which explicit fields can be defined in the + // parent structure element + scope().setFlag(ParserFlag::POST_EXPLICIT_FIELDS, true); + Rooted<Node> parentField = scope().getLeaf(); assert(parentField->isa(&RttiTypes::DocumentField)); @@ -427,4 +471,4 @@ namespace RttiTypes { const Rtti DocumentField = RttiBuilder<ousia::parser_stack::DocumentField>( "DocumentField").parent(&Node); } -} +}
\ No newline at end of file diff --git a/src/core/parser/stack/DomainHandler.cpp b/src/core/parser/stack/DomainHandler.cpp index ddec1ee..aa18faa 100644 --- a/src/core/parser/stack/DomainHandler.cpp +++ b/src/core/parser/stack/DomainHandler.cpp @@ -53,7 +53,7 @@ bool DomainHandler::start(Variant::mapType &args) return true; } -void DomainHandler::end() { scope().pop(); } +void DomainHandler::end() { scope().pop(logger()); } /* DomainStructHandler */ @@ -85,7 +85,7 @@ bool DomainStructHandler::start(Variant::mapType &args) return true; } -void DomainStructHandler::end() { scope().pop(); } +void DomainStructHandler::end() { scope().pop(logger()); } /* DomainAnnotationHandler */ bool DomainAnnotationHandler::start(Variant::mapType &args) @@ -102,7 +102,7 @@ bool DomainAnnotationHandler::start(Variant::mapType &args) return true; } -void DomainAnnotationHandler::end() { scope().pop(); } +void DomainAnnotationHandler::end() { scope().pop(logger()); } /* DomainAttributesHandler */ @@ -118,7 +118,7 @@ bool DomainAttributesHandler::start(Variant::mapType &args) return true; } -void DomainAttributesHandler::end() { scope().pop(); } +void DomainAttributesHandler::end() { scope().pop(logger()); } /* DomainFieldHandler */ @@ -148,7 +148,7 @@ bool DomainFieldHandler::start(Variant::mapType &args) return true; } -void DomainFieldHandler::end() { scope().pop(); } +void DomainFieldHandler::end() { scope().pop(logger()); } /* DomainFieldRefHandler */ @@ -218,7 +218,7 @@ bool DomainPrimitiveHandler::start(Variant::mapType &args) return true; } -void DomainPrimitiveHandler::end() { scope().pop(); } +void DomainPrimitiveHandler::end() { scope().pop(logger()); } /* DomainChildHandler */ @@ -251,7 +251,7 @@ bool DomainParentHandler::start(Variant::mapType &args) return true; } -void DomainParentHandler::end() { scope().pop(); } +void DomainParentHandler::end() { scope().pop(logger()); } /* DomainParentFieldHandler */ @@ -414,4 +414,4 @@ namespace RttiTypes { const Rtti DomainParent = RttiBuilder<ousia::parser_stack::DomainParent>( "DomainParent").parent(&Node); } -}
\ No newline at end of file +} diff --git a/src/core/parser/stack/Stack.cpp b/src/core/parser/stack/Stack.cpp index 08f86e5..5b67248 100644 --- a/src/core/parser/stack/Stack.cpp +++ b/src/core/parser/stack/Stack.cpp @@ -16,8 +16,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <sstream> - #include <core/common/Logger.hpp> #include <core/common/Utils.hpp> #include <core/common/Exceptions.hpp> @@ -256,7 +254,9 @@ void Stack::endCurrentHandler() // Make sure the fieldEnd handler is called if the element still // is in a field if (info.inField) { - info.handler->fieldEnd(); + if (info.inValidField) { + info.handler->fieldEnd(); + } info.fieldEnd(); } @@ -300,8 +300,6 @@ bool Stack::ensureHandlerIsInField() // Try to start a new default field, abort if this did not work bool isDefault = true; if (!info.handler->fieldStart(isDefault, info.fieldIdx)) { - info.handler->fieldEnd(); - endCurrentHandler(); return false; } @@ -505,10 +503,9 @@ void Stack::fieldStart(bool isDefault) // (the default field always is the last field) -- mark the command as // invalid if (info.hadDefaultField) { - logger().error( - std::string("Got field start, but command \"") + - currentCommandName() + - std::string("\" does not have any more fields")); + logger().error(std::string("Got field start, but command \"") + + currentCommandName() + + std::string("\" does not have any more fields")); } // Copy the isDefault flag to a local variable, the fieldStart method will @@ -559,7 +556,7 @@ void Stack::fieldEnd() // Only continue if the current handler stack is in a valid state, do not // call the fieldEnd function if something went wrong before - if (handlersValid() && !info.hadDefaultField) { + if (handlersValid() && !info.hadDefaultField && info.inValidField) { try { info.handler->fieldEnd(); } @@ -587,5 +584,4 @@ void Stack::token(Variant token) // TODO } } -} - +}
\ No newline at end of file diff --git a/src/core/parser/stack/TypesystemHandler.cpp b/src/core/parser/stack/TypesystemHandler.cpp index 8fd9525..de8ee49 100644 --- a/src/core/parser/stack/TypesystemHandler.cpp +++ b/src/core/parser/stack/TypesystemHandler.cpp @@ -16,11 +16,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <core/model/Typesystem.hpp> +#include <core/model/Document.hpp> #include <core/model/Domain.hpp> +#include <core/model/Typesystem.hpp> #include <core/parser/ParserScope.hpp> #include <core/parser/ParserContext.hpp> +#include "DocumentHandler.hpp" #include "DomainHandler.hpp" #include "State.hpp" #include "TypesystemHandler.hpp" @@ -38,10 +40,16 @@ bool TypesystemHandler::start(Variant::mapType &args) typesystem->setLocation(location()); // If the typesystem is defined inside a domain, add a reference to the - // typesystem to the domain + // typesystem to the domain -- do the same with a document, if no domain + // is found Rooted<Domain> domain = scope().select<Domain>(); if (domain != nullptr) { domain->reference(typesystem); + } else { + Rooted<Document> document = scope().select<Document>(); + if (document != nullptr) { + document->reference(typesystem); + } } // Push the typesystem onto the scope, set the POST_HEAD flag to true @@ -51,7 +59,7 @@ bool TypesystemHandler::start(Variant::mapType &args) return true; } -void TypesystemHandler::end() { scope().pop(); } +void TypesystemHandler::end() { scope().pop(logger()); } /* TypesystemEnumHandler */ @@ -70,7 +78,7 @@ bool TypesystemEnumHandler::start(Variant::mapType &args) return true; } -void TypesystemEnumHandler::end() { scope().pop(); } +void TypesystemEnumHandler::end() { scope().pop(logger()); } /* TypesystemEnumEntryHandler */ @@ -112,7 +120,7 @@ bool TypesystemStructHandler::start(Variant::mapType &args) return true; } -void TypesystemStructHandler::end() { scope().pop(); } +void TypesystemStructHandler::end() { scope().pop(logger()); } /* TypesystemStructFieldHandler */ @@ -182,7 +190,7 @@ bool TypesystemConstantHandler::start(Variant::mapType &args) namespace States { const State Typesystem = StateBuilder() - .parents({&None, &Domain}) + .parents({&None, &Domain, &Document}) .createdNodeType(&RttiTypes::Typesystem) .elementHandler(TypesystemHandler::create) .arguments({Argument::String("name", "")}); diff --git a/src/formats/osxml/OsxmlEventParser.cpp b/src/formats/osxml/OsxmlEventParser.cpp index 788f376..c9254b0 100644 --- a/src/formats/osxml/OsxmlEventParser.cpp +++ b/src/formats/osxml/OsxmlEventParser.cpp @@ -253,6 +253,12 @@ static void xmlStartElementHandler(void *ref, const XML_Char *name, // Convert the C string to a std::string const std::string key{*(attr++)}; + // Ignore xml namespace declarations + if (Utils::startsWith(key, "xmlns:") && parser->getData().depth == 1) { + attr++; + continue; + } + // Search the location of the key SourceLocation keyLoc; auto it = attributeOffsets.find(key); diff --git a/src/plugins/xml/XmlOutput.cpp b/src/plugins/xml/XmlOutput.cpp index 37d95ec..8af29fd 100644 --- a/src/plugins/xml/XmlOutput.cpp +++ b/src/plugins/xml/XmlOutput.cpp @@ -16,6 +16,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <cassert> + #include "XmlOutput.hpp" #include <core/common/Variant.hpp> @@ -94,7 +96,7 @@ static std::string toString(Variant v, bool pretty) if (v.isString()) { return v.asString(); } else { - return VariantWriter::writeJsonToString(v, pretty); + return VariantWriter::writeOusiaToString(v, pretty); } } @@ -137,26 +139,40 @@ void XmlTransformer::transformChildren(DocumentEntity *parentEntity, Rooted<FieldDescriptor> fieldDesc = fieldDescs[f]; // if this is not the default field create an intermediate node for it. Rooted<Element> par = parent; - if (fieldDesc->getFieldType() != FieldDescriptor::FieldType::TREE && - !fieldDesc->isPrimitive()) { + if (fieldDesc->getFieldType() != FieldDescriptor::FieldType::TREE) { par = Rooted<Element>{new Element(mgr, parent, fieldDesc->getName())}; parent->addChild(par); } - for (auto c : field) { - // transform each child. - Rooted<Node> child; - if (c->isa(&RttiTypes::StructuredEntity)) { - child = transformStructuredEntity( - par, c.cast<StructuredEntity>(), logger, pretty); - } else if (c->isa(&RttiTypes::DocumentPrimitive)) { - child = transformPrimitive(par, c.cast<DocumentPrimitive>(), - logger, pretty); - } else { - child = transformAnchor(par, c.cast<Anchor>(), logger, pretty); + if (!fieldDesc->isPrimitive()) { + for (auto c : field) { + // transform each child. + Rooted<Element> child; + if (c->isa(&RttiTypes::StructuredEntity)) { + child = transformStructuredEntity( + par, c.cast<StructuredEntity>(), logger, pretty); + } else { + assert(c->isa(&RttiTypes::Anchor)); + child = + transformAnchor(par, c.cast<Anchor>(), logger, pretty); + } + if (child != nullptr) { + par->addChild(child); + } + } + + } else { + // if the field is primitive we expect a single child. + if (field.empty()) { + continue; } - if (child != nullptr) { - par->addChild(child); + assert(field.size() == 1); + assert(field[0]->isa(&RttiTypes::DocumentPrimitive)); + Rooted<DocumentPrimitive> prim = field[0].cast<DocumentPrimitive>(); + // transform the primitive content. + Rooted<Text> text = transformPrimitive(par, fieldDesc->getPrimitiveType(), prim, logger, pretty); + if(text != nullptr){ + par->addChild(text); } } } @@ -217,12 +233,28 @@ Rooted<Element> XmlTransformer::transformAnchor(Handle<Element> parent, } Rooted<Text> XmlTransformer::transformPrimitive(Handle<Element> parent, + Handle<Type> type, Handle<DocumentPrimitive> p, Logger &logger, bool pretty) { Manager &mgr = parent->getManager(); // transform the primitive content. - Rooted<Text> text{new Text(mgr, parent, toString(p->getContent(), pretty))}; + Variant content = p->getContent(); + if(!type->build(content, logger)){ + return nullptr; + } + // special treatment for struct types because they get built as arrays, + // which is not so nice for output purposes. + if(type->isa(&RttiTypes::StructType)){ + Variant::mapType map; + Variant::arrayType arr = content.asArray(); + size_t a = 0; + for(Handle<Attribute> attr : type.cast<StructType>()->getAttributes()){ + map.emplace(attr->getName(), arr[a++]); + } + content = std::move(map); + } + Rooted<Text> text{new Text(mgr, parent, toString(content, pretty))}; return text; } } diff --git a/src/plugins/xml/XmlOutput.hpp b/src/plugins/xml/XmlOutput.hpp index 24f2d49..d0caf62 100644 --- a/src/plugins/xml/XmlOutput.hpp +++ b/src/plugins/xml/XmlOutput.hpp @@ -53,7 +53,7 @@ private: Rooted<Element> transformAnchor(Handle<Element> parent, Handle<Anchor> a, Logger &logger, bool pretty); - Rooted<Text> transformPrimitive(Handle<Element> parent, + Rooted<Text> transformPrimitive(Handle<Element> parent,Handle<Type> type, Handle<DocumentPrimitive> p, Logger &logger, bool pretty); diff --git a/test/core/common/VariantConverterTest.cpp b/test/core/common/VariantConverterTest.cpp index 9654a7b..2860777 100644 --- a/test/core/common/VariantConverterTest.cpp +++ b/test/core/common/VariantConverterTest.cpp @@ -244,7 +244,7 @@ TEST(VariantConverter, toString) VariantConverter::Mode::ALL, logger); assertStringConversion(M, "{\"b\":true,\"d\":2.7,\"i\":6,\"s\":\"test\"}", true, VariantConverter::Mode::ALL, logger); - assertStringConversion(C, "<cardinality {2-4, >6}>", true, + assertStringConversion(C, "{2-4, >6}", true, VariantConverter::Mode::ALL, logger); } diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index d68648e..6bbf26d 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -81,6 +81,115 @@ TEST(Domain, testDomainResolving) 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<FieldDescriptor> createUnsortedPrimitiveField( + Handle<StructuredClass> strct, Handle<Type> 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<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + Rooted<Domain> domain{new Domain(mgr, sys, "myDomain")}; + + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", domain, Cardinality::any(), nullptr, false, true)}; + Rooted<FieldDescriptor> A_a = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, false, "a"); + Rooted<FieldDescriptor> A_b = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, false, "b"); + Rooted<FieldDescriptor> A_main = createUnsortedPrimitiveField( + A, sys->getStringType(), logger, true, "somename"); + + Rooted<StructuredClass> B{new StructuredClass( + mgr, "B", domain, Cardinality::any(), A, false, true)}; + Rooted<FieldDescriptor> B_b = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, false, "b"); + Rooted<FieldDescriptor> B_c = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, false, "c"); + Rooted<FieldDescriptor> B_main = createUnsortedPrimitiveField( + B, sys->getStringType(), logger, true, "othername"); + + Rooted<StructuredClass> C{new StructuredClass( + mgr, "C", domain, Cardinality::any(), B, false, true)}; + Rooted<FieldDescriptor> C_a = createUnsortedPrimitiveField( + C, sys->getStringType(), logger, false, "a"); + + ASSERT_TRUE(domain->validate(logger)); + + // check all FieldDescriptors + { + NodeVector<FieldDescriptor> 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<FieldDescriptor> 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<FieldDescriptor> 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<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + Rooted<Domain> domain{new Domain(mgr, sys, "myDomain")}; + + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", domain, Cardinality::any(), nullptr, false, true)}; + A->addSubclass(A, logger); + Rooted<FieldDescriptor> 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<FieldDescriptor> fds = A->getFieldDescriptors(); + ASSERT_EQ(1, fds.size()); + ASSERT_EQ(A_a, fds[0]); +} + Rooted<StructuredClass> getClass(const std::string name, Handle<Domain> dom) { std::vector<ResolutionResult> res = @@ -221,6 +330,34 @@ TEST(Descriptor, pathToAdvanced) ASSERT_EQ("", path[2]->getName()); } +TEST(Descriptor, pathToCycles) +{ + // build a domain with a cycle. + Manager mgr{1}; + Logger logger; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct the domain + Rooted<Domain> domain{new Domain(mgr, sys, "cycles")}; + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(domain->validate(logger)); + Rooted<StructuredClass> B{new StructuredClass( + mgr, "B", domain, Cardinality::any(), {nullptr}, false, true)}; + Rooted<FieldDescriptor> 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<Node> 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. @@ -301,6 +438,29 @@ TEST(Descriptor, getDefaultFields) ASSERT_EQ(F_field, fields[1]); } +TEST(Descriptor, getDefaultFieldsCycles) +{ + // build a domain with a cycle. + Manager mgr{1}; + Logger logger; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct the domain + Rooted<Domain> domain{new Domain(mgr, sys, "cycles")}; + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(domain->validate(logger)); + Rooted<FieldDescriptor> 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<FieldDescriptor> defaultFields = A->getDefaultFields(); + ASSERT_EQ(1, defaultFields.size()); + ASSERT_EQ(A_field, defaultFields[0]); +} + TEST(Descriptor, getPermittedChildren) { // analyze the book domain. @@ -338,6 +498,31 @@ TEST(Descriptor, getPermittedChildren) ASSERT_EQ(sub, children[3]); } +TEST(Descriptor, getPermittedChildrenCycles) +{ + // build a domain with a cycle. + Manager mgr{1}; + Logger logger; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct the domain + Rooted<Domain> domain{new Domain(mgr, sys, "cycles")}; + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)}; + A->addSubclass(A, logger); + ASSERT_FALSE(domain->validate(logger)); + Rooted<FieldDescriptor> 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<StructuredClass> children = A->getPermittedChildren(); + ASSERT_EQ(1, children.size()); + ASSERT_EQ(A, children[0]); +} + TEST(StructuredClass, isSubclassOf) { // create an inheritance hierarchy. diff --git a/test/core/parser/ParserScopeTest.cpp b/test/core/parser/ParserScopeTest.cpp index 7f89f2c..2ead924 100644 --- a/test/core/parser/ParserScopeTest.cpp +++ b/test/core/parser/ParserScopeTest.cpp @@ -18,6 +18,7 @@ #include <gtest/gtest.h> +#include <core/common/Logger.hpp> #include <core/managed/Manager.hpp> #include <core/model/Node.hpp> #include <core/parser/ParserScope.hpp> @@ -26,6 +27,7 @@ namespace ousia { TEST(ParserScope, flags) { + Logger logger; Manager mgr; ParserScope scope; @@ -42,9 +44,9 @@ TEST(ParserScope, flags) ASSERT_TRUE(scope.getFlag(ParserFlag::POST_HEAD)); scope.setFlag(ParserFlag::POST_HEAD, false); ASSERT_FALSE(scope.getFlag(ParserFlag::POST_HEAD)); - scope.pop(); + scope.pop(logger); ASSERT_TRUE(scope.getFlag(ParserFlag::POST_HEAD)); - scope.pop(); + scope.pop(logger); ASSERT_FALSE(scope.getFlag(ParserFlag::POST_HEAD)); scope.setFlag(ParserFlag::POST_HEAD, true); ASSERT_TRUE(scope.getFlag(ParserFlag::POST_HEAD)); diff --git a/test/core/parser/stack/StackTest.cpp b/test/core/parser/stack/StackTest.cpp index e25f487..a93f14a 100644 --- a/test/core/parser/stack/StackTest.cpp +++ b/test/core/parser/stack/StackTest.cpp @@ -449,10 +449,10 @@ TEST(Stack, noImplicitDefaultFieldOnIncompatibleCommand) tracker.fieldStartResult = false; s.command("b", {}); - tracker.expect(2, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc + tracker.expect(2, 1, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc ASSERT_EQ("b", s.currentCommandName()); } - tracker.expect(2, 2, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc + tracker.expect(2, 2, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc ASSERT_FALSE(logger.hasError()); } @@ -563,9 +563,9 @@ TEST(Stack, invalidDefaultField) tracker.fieldStartResult = false; s.fieldStart(true); s.fieldEnd(); - tracker.expect(1, 0, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc + tracker.expect(1, 0, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc } - tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc + tracker.expect(1, 1, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc ASSERT_FALSE(logger.hasError()); } @@ -583,9 +583,9 @@ TEST(Stack, errorInvalidDefaultFieldData) s.data("test"); ASSERT_TRUE(logger.hasError()); s.fieldEnd(); - tracker.expect(1, 0, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc + tracker.expect(1, 0, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc } - tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc + tracker.expect(1, 1, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc } TEST(Stack, errorInvalidFieldData) @@ -602,9 +602,9 @@ TEST(Stack, errorInvalidFieldData) ASSERT_TRUE(logger.hasError()); s.data("test"); s.fieldEnd(); - tracker.expect(1, 0, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc + tracker.expect(1, 0, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc } - tracker.expect(1, 1, 1, 1, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc + tracker.expect(1, 1, 1, 0, 0, 0, 0); // sc, ec, fsc, fse, asc, aec, dc } TEST(Stack, errorFieldStartNoCommand) @@ -743,7 +743,5 @@ TEST(Stack, fieldAfterDefaultField) tracker.expect(2, 2, 3, 3, 0, 0, 2); // sc, ec, fsc, fse, asc, aec, dc ASSERT_FALSE(logger.hasError()); } - -} } - +}
\ No newline at end of file diff --git a/test/formats/osml/OsmlParserTest.cpp b/test/formats/osml/OsmlParserTest.cpp index 3472e5f..5127b32 100644 --- a/test/formats/osml/OsmlParserTest.cpp +++ b/test/formats/osml/OsmlParserTest.cpp @@ -158,5 +158,46 @@ TEST(OsmlParser, structureInheritance) ASSERT_TRUE(node != nullptr); ASSERT_TRUE(node->isa(&RttiTypes::Domain)); } + +TEST(OsmlParser, structWithNoField) +{ + OsmlStandaloneEnvironment env(logger); + logger.reset(); + + Rooted<Node> node = env.parse("struct_with_no_field.osml", "", "", + RttiSet{&RttiTypes::Node}); + ASSERT_FALSE(logger.hasError()); + + ASSERT_TRUE(node != nullptr); + ASSERT_TRUE(node->isa(&RttiTypes::Document)); +} + +TEST(OsmlParser, invalidExplicitFields) +{ + OsmlStandaloneEnvironment env(logger); + logger.reset(); + + ASSERT_FALSE(logger.hasError()); + Rooted<Node> node = env.parse("invalid_explicit_fields.osml", "", "", + RttiSet{&RttiTypes::Node}); + ASSERT_TRUE(logger.hasError()); + + ASSERT_TRUE(node != nullptr); + ASSERT_TRUE(node->isa(&RttiTypes::Document)); +} + +TEST(OsmlParser, explicitFields) +{ + OsmlStandaloneEnvironment env(logger); + logger.reset(); + + Rooted<Node> node = env.parse("explicit_fields.osml", "", "", + RttiSet{&RttiTypes::Node}); + ASSERT_FALSE(logger.hasError()); + + ASSERT_TRUE(node != nullptr); + ASSERT_TRUE(node->isa(&RttiTypes::Document)); +} + } diff --git a/test/formats/osxml/OsxmlParserTest.cpp b/test/formats/osxml/OsxmlParserTest.cpp index 5cc0669..3bf4a47 100644 --- a/test/formats/osxml/OsxmlParserTest.cpp +++ b/test/formats/osxml/OsxmlParserTest.cpp @@ -66,15 +66,6 @@ TEST(OsxmlParser, mismatchedTag) ASSERT_TRUE(logger.hasError()); } -TEST(OsxmlParser, generic) -{ - XmlStandaloneEnvironment env(logger); - env.parse("generic.osxml", "", "", RttiSet{&RttiTypes::Node}); -#ifdef MANAGER_GRAPHVIZ_EXPORT - env.manager.exportGraphviz("xmlDocument.dot"); -#endif -} - static void checkAttributes(Handle<StructType> expected, Handle<Descriptor> desc) { @@ -347,6 +338,7 @@ static void checkText(Handle<Node> p, Handle<Node> expectedParent, TEST(OsxmlParser, documentParsing) { + logger.reset(); XmlStandaloneEnvironment env(logger); Rooted<Node> book_document_node = env.parse("simple_book.osxml", "", "", RttiSet{&RttiTypes::Document}); @@ -391,5 +383,21 @@ TEST(OsxmlParser, documentParsing) } } } + + +TEST(OsxmlParser, complexDocumentParsing) +{ + logger.reset(); + XmlStandaloneEnvironment env(logger); + Rooted<Node> book_document_node = + env.parse("complex_book.osxml", "", "", RttiSet{&RttiTypes::Document}); + ASSERT_FALSE(logger.hasError()); + ASSERT_FALSE(book_document_node == nullptr); + ASSERT_TRUE(book_document_node->isa(&RttiTypes::Document)); + Rooted<Document> doc = book_document_node.cast<Document>(); + ASSERT_TRUE(doc->validate(logger)); + ASSERT_FALSE(logger.hasError()); +} + } diff --git a/test/plugins/xml/XmlOutputTest.cpp b/test/plugins/xml/XmlOutputTest.cpp index 403078d..fcf72d2 100644 --- a/test/plugins/xml/XmlOutputTest.cpp +++ b/test/plugins/xml/XmlOutputTest.cpp @@ -107,5 +107,45 @@ TEST(DemoHTMLTransformer, AnnotationProcessing) "><book:text>blub</book:text><a:end:emphasized/" "><book:text>bla</book:text><a:end:strong/>") != std::string::npos); } + +TEST(DemoHTMLTransformer, PrimitiveSubtreeFields) +{ + // Construct Manager + TerminalLogger logger{std::cerr, true}; + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + // Construct a simple domain. + Rooted<Domain> domain{new Domain(mgr, sys, "myDomain")}; + + Rooted<StructuredClass> A{new StructuredClass( + mgr, "A", domain, Cardinality::any(), nullptr, false, true)}; + Rooted<FieldDescriptor> A_a = + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger, + FieldDescriptor::FieldType::SUBTREE, + "a").first; + Rooted<FieldDescriptor> A_b = + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger, + FieldDescriptor::FieldType::SUBTREE, + "b").first; + Rooted<FieldDescriptor> A_main = + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; + ASSERT_TRUE(domain->validate(logger)); + // Construct a document for it. + Rooted<Document> doc{new Document(mgr, "myDoc")}; + Rooted<StructuredEntity> A_impl = doc->createRootStructuredEntity(A); + A_impl->createChildDocumentPrimitive("test_a", "a"); + A_impl->createChildDocumentPrimitive("test_b", "b"); + A_impl->createChildDocumentPrimitive("test"); + ASSERT_TRUE(doc->validate(logger)); + // now transform this document. + ResourceManager dummy; + XmlTransformer transformer; + std::stringstream out; + transformer.writeXml(doc, out, logger, dummy, false); + const std::string res = out.str(); + ASSERT_TRUE( + res.find("<myDomain:A><a>test_a</a><b>test_b</b>test</myDomain:A>") != + std::string::npos); +} } }
\ No newline at end of file diff --git a/testdata/osmlparser/explicit_fields.osml b/testdata/osmlparser/explicit_fields.osml new file mode 100644 index 0000000..a9ba1a3 --- /dev/null +++ b/testdata/osmlparser/explicit_fields.osml @@ -0,0 +1,16 @@ +\document + +\domain#test + \struct#a[isRoot=true] + \primitive#b[type=string,isSubtree=true] + \primitive#c[type=string,isSubtree=true] + \primitive#d[type=string,isSubtree=false] + + +\a{! + \b{test} + \c{test} + test + test + test +} diff --git a/testdata/osmlparser/invalid_explicit_fields.osml b/testdata/osmlparser/invalid_explicit_fields.osml new file mode 100644 index 0000000..9986204 --- /dev/null +++ b/testdata/osmlparser/invalid_explicit_fields.osml @@ -0,0 +1,16 @@ +\document + +\domain#test + \struct#a[isRoot=true] + \primitive#b[type=string,isSubtree=true] + \primitive#c[type=string,isSubtree=true] + \primitive#d[type=string,isSubtree=false] + + +\a{! + \b{test} + test + \c{test} + test + test +} diff --git a/testdata/osmlparser/struct_with_no_field.osml b/testdata/osmlparser/struct_with_no_field.osml new file mode 100644 index 0000000..8cf2d02 --- /dev/null +++ b/testdata/osmlparser/struct_with_no_field.osml @@ -0,0 +1,12 @@ +\document + +\domain#test + \struct#a[isRoot=true] + \field + \childRef[ref=b] + \struct#b + +\a + \b + \b + diff --git a/testdata/osxmlparser/affiliation.osxml b/testdata/osxmlparser/affiliation.osxml deleted file mode 100644 index d84dc30..0000000 --- a/testdata/osxmlparser/affiliation.osxml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" standalone="yes"?> -<typesystem name="affiliation"> - <struct name="affiliation"> - <field name="workgroup" type="string"/> - <field name="departement" type="string"/> - <field name="institution" type="string"/> - </struct> - - <constant name="citec.sc" type="affiliation" value="[workgroup=Semantic Computing Group,departement=Center of Excellence Cognitive Interaction Technology (CITEC), institution=Bielefeld University]"/> -</typesystem> diff --git a/testdata/osxmlparser/bibliography_domain.osxml b/testdata/osxmlparser/bibliography_domain.osxml deleted file mode 100644 index 53ba531..0000000 --- a/testdata/osxmlparser/bibliography_domain.osxml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0"?> -<domain name="bibliography"> - - <import rel="domain" src="./book_domain.osxml"/> - <import rel="domain" src="./meta_domain.osxml"/> - - <struct name="bibliography" transparent="true"> - <field> - <childRef ref="bibEntry"/> - </field> - <parentRef ref="book"> - <field name="bibliography" isSubtree="true"/> - </parentRef> - </struct> - <struct name="bibEntry"> - <primitive name="name" type="string" isSubtree="true"/> - <primitive name="year" type="int" isSubtree="true"/> - <primitive name="journal" type="string" isSubtree="true" optional="true"/> - <primitive name="pages" type="cardinality" isSubtree="true" optional="true"/> - <!-- Here a geographical enum or something would be more exact --> - <primitive name="location" type="string" isSubtree="true" optional="true"/> - <field name="authors" optional="true"> - <childRef ref="meta.author"/> - </field> - </struct> -</domain> diff --git a/testdata/osxmlparser/color.osxml b/testdata/osxmlparser/color.osxml deleted file mode 100644 index 17adea4..0000000 --- a/testdata/osxmlparser/color.osxml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" standalone="yes"?> -<typesystem name="color"> - <struct name="color"> - <field name="r" type="int"/> - <field name="g" type="int"/> - <field name="b" type="int"/> - </struct> - - <constant name="aquamarine1" type="color" value="[127,255,212]"/> - <constant name="aquamarine2" type="color" value="[118,238,198]"/> - <constant name="aquamarine3" type="color" value="[102,205,170]"/> - <constant name="aquamarine" type="color" value="[127,255,212]"/> - <constant name="azure1" type="color" value="[240,255,255]"/> - <constant name="azure2" type="color" value="[224,238,238]"/> - <constant name="azure3" type="color" value="[193,205,205]"/> - <constant name="azure4" type="color" value="[131,139,139]"/> - <constant name="azure" type="color" value="[240,255,255]"/> -</typesystem> diff --git a/testdata/osxmlparser/complex_book.osxml b/testdata/osxmlparser/complex_book.osxml index b610454..8fff93d 100644 --- a/testdata/osxmlparser/complex_book.osxml +++ b/testdata/osxmlparser/complex_book.osxml @@ -1,13 +1,12 @@ <?xml version="1.0"?> <document> - <import rel="domain" src="./book_domain.osxml"/> - <import rel="domain" src="./headings_domain.osxml"/> - <import rel="domain" src="./meta_domain.osxml"/> - <import rel="domain" src="./bibliography_domain.osxml"/> + <import rel="domain" src="book"/> + <import rel="domain" src="headings"/> + <import rel="domain" src="meta"/> + <import rel="domain" src="bibliography"/> <!--<import rel="domain" src="emphasis.oxm"/> <import rel="domain" src="comments.oxm"/> - <import rel="domain" src="lists.oxm"/> <alias tag="paragraph" aka="p"/> <alias tag="emphasized" aka="em"/>--> @@ -19,40 +18,18 @@ <email>[ikant,philo.albertus-koenigsberg,de]</email> <affiliation>[Logic and Metaphysics, Faculty of Philosophy, Albertus-University Königsberg]</affiliation> </primaryAuthor> - <!-- Using this version tag leads to a very interesting bug: - A new transparent meta element gets created for it. Because only - one meta tag is allowed this leads to an error. So what to do? - - <version>[1,0,0]</version> --> + <version>[1,0,0]</version> </meta> - - <heading> - Beantwortung der Frage: Was ist Aufklärung? - </heading> - - <chapter name="content"> - <heading>Was ist Aufklärung?</heading> - - Aufklärung ist der Ausgang des Menschen aus seiner - selbstverschuldeten Unmündigkeit. Unmündigkeit ist - das Unvermögen, sich seines Verstandes ohne Leitung eines anderen zu - bedienen. Selbstverschuldet ist diese Unmündigkeit, wenn - die Ursache derselben nicht am Mangel des Verstandes, sondern der - Entschließung und des Mutes liegt, sich seiner ohne Leitung eines - andern zu bedienen. - Sapere aude! Habe Mut, dich deines eigenen Verstandes zu - bedienen! ist also der Wahlspruch der Aufklärung. - </chapter> <bibliography> <bibEntry> - <name>Dezember-Heft</name> + <title>Dezember-Heft</title> <journal>Berlinische Monatsschrift</journal> <year>1784</year> <pages>{481-494}</pages> </bibEntry> <bibEntry> - <name>Kleine Schriften</name> + <title>Kleine Schriften</title> <primaryAuthor> <firstName>Immanuel</firstName> <lastName>Kant</lastName> @@ -62,7 +39,7 @@ <location>Neuwied</location> </bibEntry> <bibEntry> - <name>Zerstreute Aufsätze</name> + <title>Zerstreute Aufsätze</title> <primaryAuthor> <firstName>Immanuel</firstName> <lastName>Kant</lastName> @@ -72,7 +49,7 @@ <location>Frankfurt und Leipzig</location> </bibEntry> <bibEntry> - <name>Sämmtliche kleine Schriften</name> + <title>Sämmtliche kleine Schriften</title> <primaryAuthor> <firstName>Immanuel</firstName> <lastName>Kant</lastName> @@ -82,7 +59,7 @@ <location>Königsberg u. Leipzig</location> </bibEntry> <bibEntry> - <name>I. Kant's vermischte Schriften</name> + <title>I. Kant's vermischte Schriften</title> <primaryAuthor> <firstName>Immanuel</firstName> <lastName>Kant</lastName> @@ -92,7 +69,7 @@ <location>Halle</location> </bibEntry> <bibEntry> - <name>Vorzügliche kleine Schriften und Aufsätze</name> + <title>Vorzügliche kleine Schriften und Aufsätze</title> <primaryAuthor> <firstName>Immanuel</firstName> <lastName>Kant</lastName> @@ -102,5 +79,23 @@ <location>Leipzig</location> </bibEntry> </bibliography> + + <heading> + Beantwortung der Frage: Was ist Aufklärung? + </heading> + + <chapter name="content"> + <heading>Was ist Aufklärung?</heading> + + Aufklärung ist der Ausgang des Menschen aus seiner + selbstverschuldeten Unmündigkeit. Unmündigkeit ist + das Unvermögen, sich seines Verstandes ohne Leitung eines anderen zu + bedienen. Selbstverschuldet ist diese Unmündigkeit, wenn + die Ursache derselben nicht am Mangel des Verstandes, sondern der + Entschließung und des Mutes liegt, sich seiner ohne Leitung eines + andern zu bedienen. + Sapere aude! Habe Mut, dich deines eigenen Verstandes zu + bedienen! ist also der Wahlspruch der Aufklärung. + </chapter> </book> </document> diff --git a/testdata/osxmlparser/email.osxml b/testdata/osxmlparser/email.osxml deleted file mode 100644 index 325f89a..0000000 --- a/testdata/osxmlparser/email.osxml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" standalone="yes"?> -<typesystem name="email"> - <struct name="email"> - <field name="local" type="string"/> - <field name="domainName" type="string"/> - <field name="domainSuffix" type="string"/> - </struct> -</typesystem> diff --git a/testdata/osxmlparser/generic.osxml b/testdata/osxmlparser/generic.osxml deleted file mode 100644 index 799ed41..0000000 --- a/testdata/osxmlparser/generic.osxml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" standalone="yes"?> -<typesystem name="border"> - <import>./color.osxml</import> - - <enum name="border-style"> - <entry>none</entry> - <entry>dotted</entry> - <entry>dashed</entry> - <entry>solid</entry> - <entry>double</entry> - <entry>groove</entry> - <entry>ridge</entry> - <entry>inset</entry> - <entry>outset</entry> - </enum> - - <constant name="zero" value="0" type="int" /> - <constant name="black" value="[zero, zero, zero]" type="color" /> - - <struct name="border"> - <field name="style" type="border-style"/> - <field name="color" type="color" default="black" /> - </struct> - - <constant name="beautifulBorder" type="border" value="[color=aquamarine,style=solid]" /> - <constant name="moreBeautifulBorder" type="border" value="[dotted, azure]" /> -</typesystem> -<!--<domain name="color"> - <struct name="bla" cardinality="{1,2}" isa="blub"/> - <struct name="blub" cardinality="{1-3,5,>7}"> - <fields> - <field></field> - <primitive type="bla"/> - </fields> - </struct> -</domain>--> - diff --git a/testdata/osxmlparser/lists_domain.osxml b/testdata/osxmlparser/lists_domain.osxml deleted file mode 100644 index cacb9cc..0000000 --- a/testdata/osxmlparser/lists_domain.osxml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0"?> -<domain name="lists"> - <import rel="domain" src="book.osxml"/> - - <struct name="ul" isa="book.paragraph"> - <!-- Here we solve the problem of parents using the isa - mechanism, because a list may occur whereever a paragraph - may occur. However we do want to override the default field. --> - <field> - <childRef name="item"/> - </field> - </struct> - <struct name="ol" isa="book.paragraph"> - <!-- Here we solve the problem of parents using the isa - mechanism, because a list may occur whereever a paragraph - may occur. However we do want to override the default field. --> - <field> - <childRef name="item"/> - </field> - </struct> - <struct name="item"> - <field> - <childRef name="book.paragaph"/> - </field> - </struct> -</domain> diff --git a/testdata/osxmlparser/meta_domain.osxml b/testdata/osxmlparser/meta_domain.osxml deleted file mode 100644 index 52dffc5..0000000 --- a/testdata/osxmlparser/meta_domain.osxml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0"?> -<domain name="meta"> - - <import rel="typesystem" src="./affiliation.osxml"/> - <import rel="typesystem" src="./email.osxml"/> - <import rel="typesystem" src="./version.osxml"/> - <import rel="domain" src="./book_domain.osxml"/> - - <struct name="meta" cardinality="{1}" transparent="true"> - <field name="authors"> - <childRef ref="author"/> - <childRef ref="version"/> - </field> - <parentRef ref="book"> - <field name="meta" isSubtree="true" optional="true"/> - </parentRef> - <parentRef ref="chapter"> - <field name="meta" isSubtree="true" optional="true"/> - </parentRef> - <!-- One could also include "article" and other things here --> - </struct> - <!-- no explicit cardinality, because we might have multiple authors --> - <struct name="author"> - <primitive isSubtree="true" name="firstName" type="string"/> - <primitive isSubtree="true" name="lastName" type="string"/> - <primitive isSubtree="true" name="email" type="email" optional="true"/> - <primitive isSubtree="true" name="affiliation" type="affiliation" optional="true"/> - </struct> - <!-- but we need at least one primary author --> - <struct name="primaryAuthor" cardinality="{>0}" isa="author"/> - <!-- version intermediate struct --> - <struct name="version" cardinality="{0-1}"> - <primitive type="version"/> - </struct> -</domain> diff --git a/testdata/osxmlparser/test.osxml b/testdata/osxmlparser/test.osxml deleted file mode 100644 index e492488..0000000 --- a/testdata/osxmlparser/test.osxml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" standalone="yes"?> -<domain name="test"> - - <import rel="domain" src="./book_domain.osxml"/> - <import rel="typesystem" src="./color.osxml"/> - - <struct name="fancycolor"> - - <primitive type="color"/> - - <parentRef ref="book.book"> - <fieldRef ref="$default"/> - </parentRef> - </struct> -</domain> diff --git a/testdata/osxmlparser/version.osxml b/testdata/osxmlparser/version.osxml deleted file mode 100644 index 0d52736..0000000 --- a/testdata/osxmlparser/version.osxml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" standalone="yes"?> -<typesystem name="version"> - <struct name="version"> - <field name="major" type="int"/> - <field name="minor" type="int"/> - <field name="patch" type="int"/> - </struct> -</typesystem> |