diff options
35 files changed, 1035 insertions, 431 deletions
diff --git a/data/domain/book.oxm b/data/domain/book.osxml index 9659334..9659334 100644 --- a/data/domain/book.oxm +++ b/data/domain/book.osxml diff --git a/data/domain/structure.oxm b/data/domain/structure.osxml index 63d0c5c..63d0c5c 100644 --- a/data/domain/structure.oxm +++ b/data/domain/structure.osxml diff --git a/data/typesystem/color.oxm b/data/typesystem/color.osxml index 782c856..782c856 100644 --- a/data/typesystem/color.oxm +++ b/data/typesystem/color.osxml diff --git a/src/core/Registry.cpp b/src/core/Registry.cpp index 2bb6a98..e950cdc 100644 --- a/src/core/Registry.cpp +++ b/src/core/Registry.cpp @@ -131,5 +131,30 @@ bool Registry::locateResource(Resource &resource, const std::string &path, return false; } + +std::vector<std::string> Registry::autocompleteResource( + const std::string &path, ResourceType type, + const Resource &relativeTo) const +{ + std::vector<std::string> res; + + // Try the locator of the given "relativeTo" resource first + if (relativeTo.isValid()) { + res = relativeTo.getLocator().autocomplete(path, type, relativeTo); + if (!res.empty()) { + return res; + } + } + + // Iterate over all registered locators and try to autocomplete the given + // path + for (auto &locator : locators) { + res = locator->autocomplete(path, type, relativeTo); + if (!res.empty()) { + return res; + } + } + return res; +} } diff --git a/src/core/Registry.hpp b/src/core/Registry.hpp index 4b4cb65..b4ce1a9 100644 --- a/src/core/Registry.hpp +++ b/src/core/Registry.hpp @@ -30,6 +30,7 @@ #include <map> #include <set> +#include <string> #include <vector> #include <core/common/Rtti.hpp> @@ -153,6 +154,23 @@ public: bool locateResource(Resource &resource, const std::string &path, ResourceType type = ResourceType::UNKNOWN, const Resource &relativeTo = NullResource) const; + + /** + * Performs autocompletion of resources with missing file extension and + * returns a list of possible files existing within the filesystem. + * + * @param path is the path for which the autocompletion shuold be performed. + * @param type is the ResourceType which is used to select the search paths. + * @param relativeTo is another resource relatie to which the resource may + * be looked up. + * @return a list of possible files to which the given path may be extended. + * If the file pointed to by "path" exists, it will be the only result in + * the list. Otherwise files which have the given path as a prefix but a + * different file extension are returned. + */ + std::vector<std::string> autocompleteResource( + const std::string &path, ResourceType type = ResourceType::UNKNOWN, + const Resource &relativeTo = NullResource) const; }; } diff --git a/src/core/common/Argument.cpp b/src/core/common/Argument.cpp index b10fad3..ee129a3 100644 --- a/src/core/common/Argument.cpp +++ b/src/core/common/Argument.cpp @@ -54,7 +54,8 @@ Argument Argument::Any(std::string name) Argument Argument::Any(std::string name, Variant defaultValue) { - return Argument{name, &RttiTypes::None, &RttiTypes::None, defaultValue, true}; + return Argument{name, &RttiTypes::None, &RttiTypes::None, defaultValue, + true}; } Argument Argument::Bool(std::string name) @@ -95,7 +96,8 @@ Argument Argument::String(std::string name) Argument Argument::String(std::string name, const Variant::stringType &defaultValue) { - return Argument{name, &RttiTypes::String, Variant::fromString(defaultValue)}; + return Argument{name, &RttiTypes::String, + Variant::fromString(defaultValue)}; } Argument Argument::Object(std::string name, const Rtti *type) @@ -158,7 +160,8 @@ Argument Argument::Map(std::string name, const Variant::mapType &defaultValue) Argument Argument::Map(std::string name, const Rtti *innerType) { - return Argument(std::move(name), &RttiTypes::Map, innerType, nullptr, false); + return Argument(std::move(name), &RttiTypes::Map, innerType, nullptr, + false); } Argument Argument::Map(std::string name, const Rtti *innerType, @@ -276,7 +279,7 @@ bool Arguments::validateArray(Variant::arrayType &arr, Logger &logger) const } bool Arguments::validateMap(Variant::mapType &map, Logger &logger, - bool ignoreUnknown) const + bool ignoreUnknown, bool allowNumericIndices) const { // Abort if no arguments were explicitly given -- everything is valid if (!valid) { @@ -289,28 +292,60 @@ bool Arguments::validateMap(Variant::mapType &map, Logger &logger, const size_t N = arguments.size(); std::vector<bool> set(N); bool ok = true; + std::unordered_map<std::string, std::string> keyReplacements; // Iterate over the map entries and search for the corresponding argument for (auto &e : map) { // Check whether an argument with the name of the current entry exists - auto it = names.find(e.first); + const std::string &key = e.first; + auto it = names.find(key); + ssize_t idx = -1; if (it != names.end()) { // Fetch the corresponding index in the "arguments" array - size_t idx = it->second; + idx = it->second; + } else if (!key.empty() && key[0] == '#' && allowNumericIndices) { + // Read the numeric index + try { + size_t i = stoul(key.substr(1)); + if (i >= 0 && i < arguments.size()) { + idx = i; + keyReplacements.emplace(key, arguments[i].getName()); + } else { + ok = false; + } + } + catch (std::exception ex) { + logger.error( + std::string("Invalid key \"") + key + std::string("\""), + e.second); + ok = false; + } + } + + // If the key could be resolved to an index, validate the argument + if (idx >= 0) { set[idx] = arguments[idx].validate(e.second, logger); ok = ok && set[idx]; } else { if (ignoreUnknown) { logger.note(std::string("Ignoring argument \"") + e.first + - std::string("\""), e.second); + std::string("\""), + e.second); } else { logger.error(std::string("Unknown argument \"") + e.first + - std::string("\""), e.second); + std::string("\""), + e.second); ok = false; } } } + // Execute all the key replacements + for (const auto &replacement : keyReplacements) { + map[replacement.second] = std::move(map[replacement.first]); + map.erase(replacement.first); + } + // Insert all unset arguments for (size_t a = 0; a < N; a++) { if (!set[a]) { diff --git a/src/core/common/Argument.hpp b/src/core/common/Argument.hpp index 679b4a5..39b3bb6 100644 --- a/src/core/common/Argument.hpp +++ b/src/core/common/Argument.hpp @@ -61,13 +61,13 @@ private: /** * Type that should be returned by the Variant rttiType function. */ - Rtti const* type; + Rtti const *type; /** * Describes the inner type of the variant -- e.g. the type of the elements * inside an array. Normally set to RttiTypes::None. */ - Rtti const* innerType; + Rtti const *innerType; /** * Default value. Note that a value of nullptr does not indicate that no @@ -421,7 +421,7 @@ public: * @return the default value that was given in the constructor (may be * nullptr) and nullptr if no default value was given. */ - const Variant& getDefaultValue() const; + const Variant &getDefaultValue() const; /** * Returns true if a default value was set in the constructor. @@ -502,10 +502,13 @@ public: * @param ignoreUnknown if set to true, unknown map entries are ignored * (a note is issued). This behaviour can be usefull if forward * compatibility must be achieved (such as for XML based formats). + * @param allowNumericIndices if set to true, allows numeric indices in the + * input map (such as "#1"). * @return true if the operation was successful, false if an error occured. */ bool validateMap(Variant::mapType &map, Logger &logger, - bool ignoreUnknown = false) const; + bool ignoreUnknown = false, + bool allowNumericIndices = false) const; }; } diff --git a/src/core/common/VariantConverter.cpp b/src/core/common/VariantConverter.cpp index 271fe75..a9ca467 100644 --- a/src/core/common/VariantConverter.cpp +++ b/src/core/common/VariantConverter.cpp @@ -81,7 +81,7 @@ bool VariantConverter::toBool(Variant &var, Logger &logger, Mode mode) } // No conversion possible, assign default value and log error - logger.error(msgUnexpectedType(var, VariantType::BOOL)); + logger.error(msgUnexpectedType(var, VariantType::BOOL), var); var = false; return false; } @@ -129,7 +129,7 @@ bool VariantConverter::toInt(Variant &var, Logger &logger, Mode mode) } } catch (LoggableException ex) { - logger.log(ex); + logger.log(ex, var); break; } } @@ -148,7 +148,7 @@ bool VariantConverter::toInt(Variant &var, Logger &logger, Mode mode) } // No conversion possible, assign default value and log error - logger.error(msgUnexpectedType(var, VariantType::INT)); + logger.error(msgUnexpectedType(var, VariantType::INT), var); var = 0; return false; } @@ -195,7 +195,7 @@ bool VariantConverter::toDouble(Variant &var, Logger &logger, Mode mode) return true; } catch (LoggableException ex) { - logger.log(ex); + logger.log(ex, var); break; } } @@ -214,7 +214,7 @@ bool VariantConverter::toDouble(Variant &var, Logger &logger, Mode mode) } // No conversion possible, assign default value and log error - logger.error(msgUnexpectedType(var, VariantType::DOUBLE)); + logger.error(msgUnexpectedType(var, VariantType::DOUBLE), var); var = 0.0; return false; } @@ -225,22 +225,22 @@ bool VariantConverter::toString(Variant &var, Logger &logger, Mode mode) const VariantType type = var.getType(); switch (type) { case VariantType::NULLPTR: - logger.warning(msgImplicitConversion(type, VariantType::STRING)); + logger.warning(msgImplicitConversion(type, VariantType::STRING), var); var = "null"; return true; case VariantType::BOOL: - logger.warning(msgImplicitConversion(type, VariantType::STRING)); + logger.warning(msgImplicitConversion(type, VariantType::STRING), var); var = var.asBool() ? "true" : "false"; return true; case VariantType::INT: { - logger.warning(msgImplicitConversion(type, VariantType::STRING)); + logger.warning(msgImplicitConversion(type, VariantType::STRING), var); std::stringstream ss; ss << var.asInt(); var = ss.str().c_str(); return true; } case VariantType::DOUBLE: { - logger.warning(msgImplicitConversion(type, VariantType::STRING)); + logger.warning(msgImplicitConversion(type, VariantType::STRING), var); std::stringstream ss; ss << var.asDouble(); var = ss.str().c_str(); @@ -325,7 +325,7 @@ bool VariantConverter::toString(Variant &var, Logger &logger, Mode mode) } // No conversion possible, assign default value and log error - logger.error(msgUnexpectedType(var, VariantType::STRING)); + logger.error(msgUnexpectedType(var, VariantType::STRING), var); var = ""; return false; } @@ -357,7 +357,7 @@ bool VariantConverter::toArray(Variant &var, const Rtti *innerType, } // No conversion possible, assign the default value and log an error - logger.error(msgUnexpectedType(var, VariantType::ARRAY)); + logger.error(msgUnexpectedType(var, VariantType::ARRAY), var); var.setArray(Variant::arrayType{}); return false; } @@ -384,7 +384,7 @@ bool VariantConverter::toMap(Variant &var, const Rtti *innerType, } // No conversion possible, assign the default value and log an error - logger.error(msgUnexpectedType(var, VariantType::MAP)); + logger.error(msgUnexpectedType(var, VariantType::MAP), var); var.setMap(Variant::mapType{}); return false; } @@ -401,7 +401,7 @@ bool VariantConverter::toCardinality(Variant &var, Logger &logger, Mode mode) Variant::cardinalityType &card = var.asCardinality(); if (value < 0) { logger.error( - "A value smaller 0 can not be converted to a cardinality!"); + "A value smaller 0 can not be converted to a cardinality!", var); return false; } card.merge({(unsigned int)value}); @@ -432,7 +432,7 @@ bool VariantConverter::toCardinality(Variant &var, Logger &logger, Mode mode) if (value < 0) { logger.error( "A value smaller 0 can not be converted to a " - "cardinality!"); + "cardinality!", var); return false; } card.merge({(unsigned int)value}); @@ -448,14 +448,14 @@ bool VariantConverter::toCardinality(Variant &var, Logger &logger, Mode mode) if (!startVar.isInt()) { logger.error( "A non-integer can not be interpreted as the start " - "of a range"); + "of a range", startVar); return false; } int start = startVar.asInt(); if (start < 0) { logger.error( "A value smaller 0 can not be converted to a " - "cardinality!"); + "cardinality!", startVar); return false; } it++; @@ -466,7 +466,7 @@ bool VariantConverter::toCardinality(Variant &var, Logger &logger, Mode mode) if (!endVar.isInt()) { logger.error( "A non-integer can not be interpreted as the end " - "of a range"); + "of a range", endVar); return false; } int end = endVar.asInt(); @@ -475,7 +475,7 @@ bool VariantConverter::toCardinality(Variant &var, Logger &logger, Mode mode) std::string("The supposed start value ") + std::to_string(start) + " was bigger than the supposed end value " + - std::to_string(end) + " of the Range."); + std::to_string(end) + " of the Range.", endVar); return false; } card.merge({(unsigned int)start, (unsigned int)end}); @@ -500,7 +500,7 @@ bool VariantConverter::toCardinality(Variant &var, Logger &logger, Mode mode) } // No conversion possible, assign the default value and log an error - logger.error(msgUnexpectedType(var, VariantType::CARDINALITY)); + logger.error(msgUnexpectedType(var, VariantType::CARDINALITY), var); var.setCardinality(Variant::cardinalityType{}); return false; } @@ -512,7 +512,7 @@ bool VariantConverter::toFunction(Variant &var, Logger &logger) } // No conversion possible, assign the default value and log an error - logger.error(msgUnexpectedType(var, VariantType::FUNCTION)); + logger.error(msgUnexpectedType(var, VariantType::FUNCTION), var); var.setFunction(std::shared_ptr<Function>{new FunctionStub()}); return false; } @@ -527,7 +527,7 @@ bool VariantConverter::convert(Variant &var, const Rtti *type, } else if (type == &RttiTypes::Nullptr) { // Make sure the variant is set to null if (!var.isNull()) { - logger.error(msgUnexpectedType(var, VariantType::NULLPTR)); + logger.error(msgUnexpectedType(var, VariantType::NULLPTR), var); var.setNull(); return false; } @@ -553,7 +553,7 @@ bool VariantConverter::convert(Variant &var, const Rtti *type, // If none of the above primitive types is requested, we were // obviously asked for a managed object. if (!var.isObject()) { - logger.error(msgUnexpectedType(var, VariantType::OBJECT)); + logger.error(msgUnexpectedType(var, VariantType::OBJECT), var); var.setObject(nullptr); return false; } @@ -561,7 +561,7 @@ bool VariantConverter::convert(Variant &var, const Rtti *type, // Make sure the object type is correct if (!var.getRtti()->isa(type)) { logger.error(std::string("Expected object of type ") + type->name + - " but got object of type " + var.getRtti()->name); + " but got object of type " + var.getRtti()->name, var); var.setObject(nullptr); return false; } diff --git a/src/core/common/VariantReader.cpp b/src/core/common/VariantReader.cpp index fb93ad0..601d086 100644 --- a/src/core/common/VariantReader.cpp +++ b/src/core/common/VariantReader.cpp @@ -848,17 +848,23 @@ std::pair<bool, Variant> VariantReader::parseGenericToken( std::pair<bool, Variant> VariantReader::parseGenericString( const std::string &str, Logger &logger, SourceId sourceId, size_t offs) { - CharReader reader{str, sourceId, offs}; - LoggerFork loggerFork = logger.fork(); + // If the given string is empty, just return it as a string (there is no + // other type for which something empty would be valid) + // TODO: How to integrate this into parseGenericToken? + if (!str.empty()) { + CharReader reader{str, sourceId, offs}; + LoggerFork loggerFork = logger.fork(); - // Try to parse a single token - std::pair<bool, Variant> res = - parseGenericToken(reader, loggerFork, std::unordered_set<char>{}, true); + // Try to parse a single token + std::pair<bool, Variant> res = parseGenericToken( + reader, loggerFork, std::unordered_set<char>{}, true); - // If the string was actually consisted of a single token, return that token - if (reader.atEnd()) { - loggerFork.commit(); - return res; + // If the string was actually consisted of a single token, return that + // token + if (reader.atEnd()) { + loggerFork.commit(); + return res; + } } // Otherwise return the given string as a string, set the location of the diff --git a/src/core/model/Document.cpp b/src/core/model/Document.cpp index 4e101fc..2fcd20d 100644 --- a/src/core/model/Document.cpp +++ b/src/core/model/Document.cpp @@ -314,7 +314,7 @@ const NodeVector<StructureNode> &DocumentEntity::getField( return fields[idx]; } -void DocumentEntity::addStructureNode(Handle<StructureNode> s, const int &i) +void DocumentEntity::addStructureNode(Handle<StructureNode> s, const size_t &i) { // only add the new node if we don't have it already. auto it = fields[i].find(s); @@ -419,6 +419,15 @@ Rooted<StructuredEntity> DocumentEntity::createChildStructuredEntity( fieldName, std::move(name))}; } +Rooted<StructuredEntity> DocumentEntity::createChildStructuredEntity( + Handle<StructuredClass> descriptor, const size_t &fieldIdx, + Variant attributes, std::string name) +{ + return Rooted<StructuredEntity>{ + new StructuredEntity(subInst->getManager(), subInst, descriptor, + fieldIdx, std::move(attributes), std::move(name))}; +} + Rooted<DocumentPrimitive> DocumentEntity::createChildDocumentPrimitive( Variant content, const std::string &fieldName) { @@ -426,11 +435,22 @@ Rooted<DocumentPrimitive> DocumentEntity::createChildDocumentPrimitive( subInst->getManager(), subInst, std::move(content), fieldName)}; } +Rooted<DocumentPrimitive> DocumentEntity::createChildDocumentPrimitive( + Variant content, const size_t &fieldIdx) +{ + return Rooted<DocumentPrimitive>{new DocumentPrimitive( + subInst->getManager(), subInst, std::move(content), fieldIdx)}; +} + Rooted<Anchor> DocumentEntity::createChildAnchor(const std::string &fieldName) { return Rooted<Anchor>{ new Anchor(subInst->getManager(), subInst, fieldName)}; } +Rooted<Anchor> DocumentEntity::createChildAnchor(const size_t &fieldIdx) +{ + return Rooted<Anchor>{new Anchor(subInst->getManager(), subInst, fieldIdx)}; +} /* Class StructureNode */ @@ -468,6 +488,19 @@ StructureNode::StructureNode(Manager &mgr, std::string name, } } +StructureNode::StructureNode(Manager &mgr, std::string name, + Handle<Node> parent, const size_t &fieldIdx) + : Node(mgr, std::move(name), parent) +{ + if (parent->isa(&RttiTypes::StructuredEntity)) { + parent.cast<StructuredEntity>()->addStructureNode(this, fieldIdx); + } else if (parent->isa(&RttiTypes::AnnotationEntity)) { + parent.cast<AnnotationEntity>()->addStructureNode(this, fieldIdx); + } else { + throw OusiaException("The proposed parent was no DocumentEntity!"); + } +} + /* Class StructuredEntity */ StructuredEntity::StructuredEntity(Manager &mgr, Handle<Document> doc, @@ -489,8 +522,25 @@ StructuredEntity::StructuredEntity(Manager &mgr, Handle<Node> parent, bool StructuredEntity::doValidate(Logger &logger) const { + bool valid = true; + // check the parent. + if (getDescriptor() == nullptr) { + logger.error("The descriptor is not set!", *this); + valid = false; + } else if (!getDescriptor()->isa(&RttiTypes::StructuredClass)) { + logger.error("The descriptor is not a structure descriptor!", *this); + valid = false; + } else if (transparent && + !getDescriptor().cast<StructuredClass>()->isTransparent()) { + logger.error( + "The entity is marked as transparent but the descriptor " + "does not allow transparency!", + *this); + valid = false; + } + // check the validity as a StructureNode and as a DocumentEntity. - return StructureNode::doValidate(logger) & + return valid & StructureNode::doValidate(logger) & DocumentEntity::doValidate(logger); } @@ -674,6 +724,7 @@ void Document::doResolve(ResolutionState &state) continueResolveCompositum(root, state); } continueResolveReferences(domains, state); + continueResolveReferences(typesystems, state); } bool Document::doValidate(Logger &logger) const @@ -713,11 +764,14 @@ void Document::doReference(Handle<Node> node) if (node->isa(&RttiTypes::Domain)) { referenceDomain(node.cast<Domain>()); } + if (node->isa(&RttiTypes::Typesystem)) { + referenceTypesystem(node.cast<Typesystem>()); + } } RttiSet Document::doGetReferenceTypes() const { - return RttiSet{&RttiTypes::Domain}; + return RttiSet{&RttiTypes::Domain, &RttiTypes::Typesystem}; } Rooted<StructuredEntity> Document::createRootStructuredEntity( @@ -821,4 +875,4 @@ const Rtti AnnotationEntity = .parent(&Node) .composedOf({&StructuredEntity, &DocumentPrimitive, &Anchor}); } -}
\ No newline at end of file +} diff --git a/src/core/model/Document.hpp b/src/core/model/Document.hpp index 5f06eb0..6903bb3 100644 --- a/src/core/model/Document.hpp +++ b/src/core/model/Document.hpp @@ -246,7 +246,7 @@ public: * FieldDescriptor in the Domain description. * @return a NodeVector of all StructuredEntities in that field. */ - const NodeVector<StructureNode> &getField(const size_t& idx ) const; + const NodeVector<StructureNode> &getField(const size_t &idx) const; /** * This adds a StructureNode to the field with the given index. @@ -259,7 +259,7 @@ public: * @param fieldIdx is the index of a field as specified in the * FieldDescriptor in the Domain description. */ - void addStructureNode(Handle<StructureNode> s, const int &fieldIdx); + void addStructureNode(Handle<StructureNode> s, const size_t &fieldIdx); /** * This adds a StructureNode to the field with the given name. * @@ -403,6 +403,23 @@ public: Variant attributes = Variant::mapType{}, const std::string &fieldName = DEFAULT_FIELD_NAME, std::string name = ""); + + /** + * This creates a new StructuredEntity as child of this DocumentEntity. + * + * @param descriptor is the StructuredClass of this StructuredEntity. + * @param attributes is a Map Variant containing attribute fillings for this + * StructuredEntity. It is empty per default. + * @param fieldIdx is the index of the field, where the newly created + * StructuredEntity shall be added to this DocumentEntity. + * @param name is some name for this StructuredEntity that may be used + * for later reference. It is empty per default. + * + * @return the newly created StructuredEntity. + */ + Rooted<StructuredEntity> createChildStructuredEntity( + Handle<StructuredClass> descriptor, const size_t &fieldIdx, + Variant attributes = Variant::mapType{}, std::string name = ""); /* * Creates a new DocumentPrimitive as child of this DocumentEntity. * @@ -416,8 +433,21 @@ public: * @return the newly created DocumentPrimitive. */ Rooted<DocumentPrimitive> createChildDocumentPrimitive( - Variant content = {}, - const std::string &fieldName = DEFAULT_FIELD_NAME); + Variant content, const std::string &fieldName = DEFAULT_FIELD_NAME); + /* + * Creates a new DocumentPrimitive as child of this DocumentEntity. + * + * @param fieldIdx is the index of the field, where the newly created + * StructuredEntity shall be added to this DocumentEntity. + * @param content is a Variant containing the content of this + * DocumentPrimitive. The Type of this Variant is + * specified at the parents Descriptor for the given + * fieldName. + * + * @return the newly created DocumentPrimitive. + */ + Rooted<DocumentPrimitive> createChildDocumentPrimitive( + Variant content, const size_t &fieldIdx); /** * Creates a new Anchor as child of this DocumentEntity. @@ -429,6 +459,16 @@ public: */ Rooted<Anchor> createChildAnchor( const std::string &fieldName = DEFAULT_FIELD_NAME); + + /** + * Creates a new Anchor as child of this DocumentEntity. + * + * @param fieldIdx is the index of the field, where the newly created + * Anchor shall be added to this DocumentEntity. + * + * @return the newly created Anchor. + */ + Rooted<Anchor> createChildAnchor(const size_t &fieldIdx); }; /** @@ -447,6 +487,11 @@ public: */ StructureNode(Manager &mgr, std::string name, Handle<Node> parent, const std::string &fieldName); + /** + * Constructor for a StructureNode in the StructureTree. + */ + StructureNode(Manager &mgr, std::string name, Handle<Node> parent, + const size_t &fieldIdx); /** * Constructor for an empty StructureNode. @@ -465,6 +510,9 @@ public: class StructuredEntity : public StructureNode, public DocumentEntity { friend Document; +private: + bool transparent = false; + protected: bool doValidate(Logger &logger) const override; @@ -494,6 +542,30 @@ public: DocumentEntity(this, descriptor, std::move(attributes)) { } + /** + * Constructor for a StructuredEntity in the Structure Tree. + * + * @param mgr is the Manager instance. + * @param parent is the parent DocumentEntity of this StructuredEntity + * in the DocumentTree. Note that this StructuredEntity + * will automatically register itself as child of this + * parent. + * @param descriptor is the StructuredClass of this StructuredEntity. + * @param fieldIdx is the index of the field in the parent DocumentEntity + * where this StructuredEntity shall be added. + * @param attributes is a Map Variant containing attribute fillings for this + * StructuredEntity. It is empty per default. + * @param name is some name for this StructuredEntity that may be used + * for later reference. It is empty per default. + */ + StructuredEntity(Manager &mgr, Handle<Node> parent, + Handle<StructuredClass> descriptor, const size_t &fieldIdx, + Variant attributes = Variant::mapType{}, + std::string name = "") + : StructureNode(mgr, std::move(name), parent, fieldIdx), + DocumentEntity(this, descriptor, std::move(attributes)) + { + } /** * Constructor for a StructuredEntity at the document root. @@ -530,6 +602,20 @@ public: Handle<StructuredClass> descriptor = nullptr, Variant attributes = Variant::mapType{}, std::string name = ""); + + /** + * Returns true if and only if this element was created using transparency/ + * if and only if this is an implicit element. + * + * @return true if and only if this element was created using transparency. + */ + bool isTransparent() const { return transparent; } + + /** + * @param trans true if and only if this element was created using + * transparency/if and only if this is an implicit element. + */ + void setTransparent(bool trans) { transparent = trans; } }; /** @@ -557,11 +643,31 @@ public: * @param fieldName is the name of the field in the parent DocumentEntity * where this DocumentPrimitive shall be added. */ - DocumentPrimitive(Manager &mgr, Handle<Node> parent, Variant content = {}, + DocumentPrimitive(Manager &mgr, Handle<Node> parent, Variant content, const std::string &fieldName = DEFAULT_FIELD_NAME) : StructureNode(mgr, "", parent, fieldName), content(content) { } + /** + * Constructor for a DocumentPrimitive. + * + * @param mgr is the Manager instance. + * @param parent is the parent DocumentEntity of this DocumentPrimitive + * in the DocumentTree. Note that this DocumentPrimitive + * will automatically register itself as child of this + * parent. + * @param content is a Variant containing the content of this + * DocumentPrimitive. The Type of this Variant is + * specified at the parents Descriptor for the given + * fieldName. + * @param fieldIdx is the index of the field in the parent DocumentEntity + * where this DocumentPrimitive shall be added. + */ + DocumentPrimitive(Manager &mgr, Handle<Node> parent, Variant content, + const size_t &fieldIdx) + : StructureNode(mgr, "", parent, fieldIdx), content(content) + { + } /** * Returns the content of this DocumentPrimitive. @@ -612,6 +718,21 @@ public: : StructureNode(mgr, "", parent, fieldName) { } + /** + * Constructor for Anchor. + * + * @param mgr is the Manager instance. + * @param parent is the parent of this Anchor in the Structure Tree (!), + * not the AnnotationEntity that references this Anchor. + * Note that this Anchor will automatically register itself + * as child of the given parent. + * @param fieldIdx is the index of the field in the parent DocumentEntity + * where this Anchor shall be added. + */ + Anchor(Manager &mgr, Handle<Node> parent, const size_t &fieldIdx) + : StructureNode(mgr, "", parent, fieldIdx) + { + } /** * Returns the AnnotationEntity this Anchor belongs to. @@ -754,6 +875,7 @@ private: Owned<StructuredEntity> root; NodeVector<AnnotationEntity> annotations; NodeVector<Domain> domains; + NodeVector<Typesystem> typesystems; protected: void doResolve(ResolutionState &state) override; @@ -771,7 +893,8 @@ public: Document(Manager &mgr, std::string name) : RootNode(mgr, std::move(name), nullptr), annotations(this), - domains(this) + domains(this), + typesystems(this) { } @@ -891,6 +1014,25 @@ public: } /** + * Adds a Typesystem reference to this Document. + */ + void referenceTypesystem(Handle<Typesystem> d) + { + invalidate(); + typesystems.push_back(d); + } + + /** + * Adds multiple Typesystem references to this Document. + */ + void referenceTypesystems(const std::vector<Handle<Typesystem>> &d) + { + invalidate(); + typesystems.insert(typesystems.end(), d.begin(), d.end()); + } + + + /** * Returns true if and only if the given StructureNode is part of this * document, meaning that there is a path of parent references in the * Structure Tree leading from the given StructureNode to this Document. @@ -914,4 +1056,3 @@ extern const Rtti Anchor; } #endif /* _OUSIA_MODEL_DOCUMENT_HPP_ */ - diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index ac0699e..f6c3956 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -558,7 +558,7 @@ Rooted<FieldDescriptor> Descriptor::getFieldDescriptor( } } -void Descriptor::addAndSortFieldDescriptor(Handle<FieldDescriptor> fd, +bool Descriptor::addAndSortFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) { // only add it if we need to. @@ -571,37 +571,25 @@ void Descriptor::addAndSortFieldDescriptor(Handle<FieldDescriptor> fd, fd->getFieldType() != FieldDescriptor::FieldType::TREE) { // if so we add the new field before the TREE field. fieldDescriptors.insert(fieldDescriptors.end() - 1, fd); - - // if the new field was from the same domain we warn the user - // because that is bad coding style. - if (fd->getParent() != nullptr && - fd->getParent().cast<Descriptor>()->getParent() == - getParent()) { - logger.warning( - std::string("Field \"") + fd->getName() + - "\" was declared after main field \"" + - fds.back()->getName() + - "\". The order of fields was changed to make the " - "main field the last field.", - *fd); - } + return true; } else { fieldDescriptors.push_back(fd); } } + return false; } -void Descriptor::addFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) +bool Descriptor::addFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) { - addAndSortFieldDescriptor(fd, logger); if (fd->getParent() == nullptr) { fd->setParent(this); } + return addAndSortFieldDescriptor(fd, logger); } -void Descriptor::moveFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) +bool Descriptor::moveFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) { - addAndSortFieldDescriptor(fd, logger); + bool sorted = addAndSortFieldDescriptor(fd, logger); Handle<Managed> par = fd->getParent(); if (par != this) { if (par != nullptr) { @@ -610,9 +598,10 @@ void Descriptor::moveFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) } fd->setParent(this); } + return sorted; } -void Descriptor::copyFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) +bool Descriptor::copyFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) { Rooted<FieldDescriptor> copy; if (fd->isPrimitive()) { @@ -631,7 +620,7 @@ void Descriptor::copyFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger) copy->addChild(c); } } - addFieldDescriptor(copy, logger); + return addFieldDescriptor(copy, logger); } bool Descriptor::removeFieldDescriptor(Handle<FieldDescriptor> fd) @@ -646,25 +635,27 @@ bool Descriptor::removeFieldDescriptor(Handle<FieldDescriptor> fd) return false; } -Rooted<FieldDescriptor> Descriptor::createPrimitiveFieldDescriptor( - Handle<Type> primitiveType, Logger &logger, - FieldDescriptor::FieldType fieldType, std::string name, bool optional) +std::pair<Rooted<FieldDescriptor>, bool> +Descriptor::createPrimitiveFieldDescriptor(Handle<Type> primitiveType, + Logger &logger, + FieldDescriptor::FieldType fieldType, + std::string name, bool optional) { Rooted<FieldDescriptor> fd{new FieldDescriptor(getManager(), primitiveType, this, fieldType, std::move(name), optional)}; - addFieldDescriptor(fd, logger); - return fd; + bool sorted = addFieldDescriptor(fd, logger); + return std::make_pair(fd, sorted); } -Rooted<FieldDescriptor> Descriptor::createFieldDescriptor( +std::pair<Rooted<FieldDescriptor>, bool> Descriptor::createFieldDescriptor( Logger &logger, FieldDescriptor::FieldType fieldType, std::string name, bool optional) { Rooted<FieldDescriptor> fd{new FieldDescriptor( getManager(), this, fieldType, std::move(name), optional)}; - addFieldDescriptor(fd, logger); - return fd; + bool sorted = addFieldDescriptor(fd, logger); + return std::make_pair(fd, sorted); } /* Class StructuredClass */ diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index 350c7ba..476a38c 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -469,7 +469,7 @@ private: Owned<StructType> attributesDescriptor; NodeVector<FieldDescriptor> fieldDescriptors; - void addAndSortFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger); + bool addAndSortFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger); protected: void doResolve(ResolutionState &state) override; @@ -557,8 +557,11 @@ public: * parent of the given FieldDescriptor if it is not set yet. * * @param fd is a FieldDescriptor. + * @return returns true if the given FieldDescriptor was not added at the + * end one place before because a TREE field already existed and + * the TREE field has to be at the end. */ - void addFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger); + bool addFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger); /** * Adds the given FieldDescriptor to this Descriptor. This also sets the @@ -566,16 +569,22 @@ public: * already and removes it from the old parent Descriptor. * * @param fd is a FieldDescriptor. + * @return returns true if the given FieldDescriptor was not added at the + * end one place before because a TREE field already existed and + * the TREE field has to be at the end. */ - void moveFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger); + bool moveFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger); /** * Copies a FieldDescriptor that belongs to another Descriptor to this * Descriptor. * * @param fd some FieldDescriptor belonging to another Descriptor. + * @return returns true if the given FieldDescriptor was not added at the + * end one place before because a TREE field already existed and + * the TREE field has to be at the end. */ - void copyFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger); + bool copyFieldDescriptor(Handle<FieldDescriptor> fd, Logger &logger); /** * Removes the given FieldDescriptor from this Descriptor. This also sets @@ -598,9 +607,12 @@ public: * filled in order for an instance of the parent * Descriptor to be valid. * - * @return the newly created FieldDescriptor. + * @return the newly created FieldDescriptor and a bool + * indicating whether the order of FieldDescriptors had + * to be changed for the TREE field to be in the last + * spot. */ - Rooted<FieldDescriptor> createPrimitiveFieldDescriptor( + std::pair<Rooted<FieldDescriptor>, bool> createPrimitiveFieldDescriptor( Handle<Type> primitiveType, Logger &logger, FieldDescriptor::FieldType fieldType = FieldDescriptor::FieldType::TREE, std::string name = "", bool optional = false); @@ -617,9 +629,12 @@ public: * filled in order for an instance of the parent * Descriptor to be valid. * - * @return the newly created FieldDescriptor. + * @return the newly created FieldDescriptor and a bool + * indicating whether the order of FieldDescriptors had + * to be changed for the TREE field to be in the last + * spot. */ - Rooted<FieldDescriptor> createFieldDescriptor( + std::pair<Rooted<FieldDescriptor>, bool> createFieldDescriptor( Logger &logger, FieldDescriptor::FieldType fieldType = FieldDescriptor::FieldType::TREE, std::string name = "", bool optional = false); diff --git a/src/core/parser/stack/DocumentHandler.cpp b/src/core/parser/stack/DocumentHandler.cpp index d514701..49bf26b 100644 --- a/src/core/parser/stack/DocumentHandler.cpp +++ b/src/core/parser/stack/DocumentHandler.cpp @@ -51,20 +51,15 @@ void DocumentHandler::end() { scope().pop(); } /* DocumentChildHandler */ -void DocumentChildHandler::preamble(Handle<Node> parentNode, - std::string &fieldName, - DocumentEntity *&parent, bool &inField) +void DocumentChildHandler::preamble(Rooted<Node> &parentNode, size_t &fieldIdx, + DocumentEntity *&parent) { // Check if the parent in the structure tree was an explicit field // reference. - inField = parentNode->isa(&RttiTypes::DocumentField); - if (inField) { - fieldName = parentNode->getName(); + if (parentNode->isa(&RttiTypes::DocumentField)) { + fieldIdx = parentNode.cast<DocumentField>()->fieldIdx; parentNode = scope().selectOrThrow( {&RttiTypes::StructuredEntity, &RttiTypes::AnnotationEntity}); - } else { - // If it wasn't an explicit reference, we use the default field. - fieldName = DEFAULT_FIELD_NAME; } // Reference the parent entity explicitly. @@ -78,124 +73,235 @@ void DocumentChildHandler::preamble(Handle<Node> parentNode, } } -static void createPath(const NodeVector<Node> &path, DocumentEntity *&parent, - size_t p0 = 1) +void DocumentChildHandler::createPath(const NodeVector<Node> &path, + DocumentEntity *&parent, size_t p0) { - // TODO (@benjamin): These should be pushed onto the scope and poped once - // the scope is left. Otherwise stuff may not be correclty resolved. size_t S = path.size(); for (size_t p = p0; p < S; p = p + 2) { - parent = static_cast<DocumentEntity *>( - parent->createChildStructuredEntity( - path[p].cast<StructuredClass>(), Variant::mapType{}, - path[p - 1]->getName(), "").get()); + // add the field. + Rooted<DocumentField> field{new DocumentField( + manager(), scope().getLeaf(), + parent->getDescriptor()->getFieldDescriptorIndex(), true)}; + scope().push(field); + // add the transparent/implicit structure element. + Rooted<StructuredEntity> transparent = + parent->createChildStructuredEntity(path[p].cast<StructuredClass>(), + Variant::mapType{}, + path[p - 1]->getName(), ""); + transparent->setLocation(location()); + transparent->setTransparent(true); + scope().push(transparent); + parent = static_cast<DocumentEntity *>(transparent.get()); } + // add the last field. + Rooted<DocumentField> field{new DocumentField( + manager(), scope().getLeaf(), + parent->getDescriptor()->getFieldDescriptorIndex(), true)}; + scope().push(field); } -static void createPath(const std::string &firstFieldName, - const NodeVector<Node> &path, DocumentEntity *&parent) +void DocumentChildHandler::createPath(const size_t &firstFieldIdx, + const NodeVector<Node> &path, + DocumentEntity *&parent) { // Add the first element - parent = static_cast<DocumentEntity *>( - parent->createChildStructuredEntity(path[0].cast<StructuredClass>(), - Variant::mapType{}, firstFieldName, - "").get()); + Rooted<StructuredEntity> transparent = parent->createChildStructuredEntity( + path[0].cast<StructuredClass>(), firstFieldIdx); + transparent->setLocation(location()); + transparent->setTransparent(true); + scope().push(transparent); + parent = static_cast<DocumentEntity *>(transparent.get()); createPath(path, parent, 2); } bool DocumentChildHandler::start(Variant::mapType &args) { + // extract the special "name" attribute from the input arguments. + // the remaining attributes will be forwarded to the newly constructed + // element. + std::string nameAttr; + { + auto it = args.find("name"); + if (it != args.end()) { + nameAttr = it->second.asString(); + args.erase(it); + } + } + scope().setFlag(ParserFlag::POST_HEAD, true); - Rooted<Node> parentNode = scope().selectOrThrow( - {&RttiTypes::Document, &RttiTypes::StructuredEntity, - &RttiTypes::AnnotationEntity, &RttiTypes::DocumentField}); + while (true) { + Rooted<Node> parentNode = scope().getLeaf(); + + Rooted<StructuredEntity> entity; + // handle the root note specifically. + if (parentNode->isa(&RttiTypes::Document)) { + Rooted<StructuredClass> strct = scope().resolve<StructuredClass>( + Utils::split(name(), ':'), logger()); + if (strct == nullptr) { + // if we could not resolve the name, throw an exception. + throw LoggableException( + std::string("\"") + name() + "\" could not be resolved.", + location()); + } + entity = parentNode.cast<Document>()->createRootStructuredEntity( + strct, args, nameAttr); + } else { + assert(parentNode->isa(&RttiTypes::DocumentField)); - std::string fieldName; - DocumentEntity *parent; - bool inField; - - preamble(parentNode, fieldName, parent, inField); - - // Try to find a FieldDescriptor for the given tag if we are not in a - // field already. This does _not_ try to construct transparent paths - // in between. - if (!inField && parent != nullptr && - parent->getDescriptor()->hasField(name())) { - Rooted<DocumentField> field{ - new DocumentField(parentNode->getManager(), name(), parentNode)}; - field->setLocation(location()); - scope().push(field); - return true; - } + size_t fieldIdx; + DocumentEntity *parent; - // Otherwise create a new StructuredEntity - // TODO: Consider Anchors and AnnotationEntities - Rooted<StructuredClass> strct = - scope().resolve<StructuredClass>(Utils::split(name(), ':'), logger()); - if (strct == nullptr) { - // if we could not resolve the name, throw an exception. - throw LoggableException( - std::string("\"") + name() + "\" could not be resolved.", - location()); - } + preamble(parentNode, fieldIdx, parent); - std::string name; - auto it = args.find("name"); - if (it != args.end()) { - name = it->second.asString(); - args.erase(it); - } + // TODO: REMOVE + std::string thisName = name(); + std::string parentClassName; + if (parent != nullptr) { + parentClassName = parent->getDescriptor()->getName(); + } - Rooted<StructuredEntity> entity; - if (parentNode->isa(&RttiTypes::Document)) { - entity = parentNode.cast<Document>()->createRootStructuredEntity( - strct, args, name); - } else { - // calculate a path if transparent entities are needed in between. - std::string lastFieldName = fieldName; - if (inField) { - Rooted<FieldDescriptor> field = - parent->getDescriptor()->getFieldDescriptor(fieldName); - auto pathRes = - field.cast<FieldDescriptor>()->pathTo(strct, logger()); - if (!pathRes.second) { + /* + * Try to find a FieldDescriptor for the given tag if we are not in + * a field already. This does _not_ try to construct transparent + * paths in between. + */ + { + 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; + } + } + + // Otherwise create a new StructuredEntity + // TODO: Consider Anchors and AnnotationEntities + Rooted<StructuredClass> strct = scope().resolve<StructuredClass>( + Utils::split(name(), ':'), logger()); + if (strct == nullptr) { + // if we could not resolve the name, throw an exception. throw LoggableException( - std::string("An instance of \"") + strct->getName() + - "\" is not allowed as child of field \"" + fieldName + - "\"", + std::string("\"") + name() + "\" could not be resolved.", location()); } - if (!pathRes.first.empty()) { - createPath(fieldName, pathRes.first, parent); - lastFieldName = DEFAULT_FIELD_NAME; - } - } else { - auto path = parent->getDescriptor()->pathTo(strct, logger()); - if (path.empty()) { + + // calculate a path if transparent entities are needed in between. + Rooted<FieldDescriptor> field = + parent->getDescriptor()->getFieldDescriptors()[fieldIdx]; + size_t lastFieldIdx = fieldIdx; + auto pathRes = field->pathTo(strct, logger()); + if (!pathRes.second) { + if (scope().getLeaf().cast<DocumentField>()->transparent) { + // 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(); + // pop the implicit element. + scope().pop(); + continue; + } throw LoggableException( std::string("An instance of \"") + strct->getName() + - "\" is not allowed as child of an instance of \"" + + "\" is not allowed as child of field \"" + + field->getName() + "\" of descriptor \"" + parent->getDescriptor()->getName() + "\"", location()); } - - // create all transparent entities until the last field. - createPath(path, parent); - if (path.size() > 1) { - lastFieldName = DEFAULT_FIELD_NAME; + if (!pathRes.first.empty()) { + createPath(lastFieldIdx, pathRes.first, parent); + lastFieldIdx = + parent->getDescriptor()->getFieldDescriptorIndex(); } + // create the entity for the new element at last. + entity = parent->createChildStructuredEntity(strct, lastFieldIdx, + args, nameAttr); + } + entity->setLocation(location()); + scope().push(entity); + return true; + } +} + +void DocumentChildHandler::end() +{ + // in case of explicit fields we do not want to pop something from the + // stack. + if (isExplicitField) { + return; + } + // pop the "main" element. + scope().pop(); +} + +bool DocumentChildHandler::fieldStart(bool &isDefault, size_t fieldIdx) +{ + if (isExplicitField) { + // In case of explicit fields we do not want to create another field. + isDefault = true; + return fieldIdx == 0; + } + Rooted<Node> parentNode = scope().getLeaf(); + assert(parentNode->isa(&RttiTypes::StructuredEntity) || + parentNode->isa(&RttiTypes::AnnotationEntity)); + size_t dummy; + DocumentEntity *parent; + + preamble(parentNode, dummy, parent); + + NodeVector<FieldDescriptor> fields = + parent->getDescriptor()->getFieldDescriptors(); + + if (isDefault) { + fieldIdx = fields.size() - 1; + } else { + if (fieldIdx >= fields.size()) { + return false; } - // create the entity for the new element at last. - entity = parent->createChildStructuredEntity(strct, args, lastFieldName, - name); + isDefault = fieldIdx == fields.size() - 1; } - entity->setLocation(location()); - scope().push(entity); + // push the field on the stack. + Rooted<DocumentField> field{ + new DocumentField(manager(), parentNode, fieldIdx, false)}; + field->setLocation(location()); + scope().push(field); return true; } -void DocumentChildHandler::end() { scope().pop(); } +void DocumentChildHandler::fieldEnd() +{ + assert(scope().getLeaf()->isa(&RttiTypes::DocumentField)); + + // pop the field from the stack. + scope().pop(); + + // pop all remaining transparent elements. + while (scope().getLeaf()->isa(&RttiTypes::StructuredEntity) && + scope().getLeaf().cast<StructuredEntity>()->isTransparent()) { + // pop the transparent element. + scope().pop(); + // pop the transparent field. + scope().pop(); + } +} + +bool DocumentChildHandler::annotationStart(const Variant &className, + Variant::mapType &args) +{ + // TODO: Implement + return false; +} + +bool DocumentChildHandler::annotationEnd(const Variant &className, + const Variant &elementName) +{ + // TODO: Implement + return false; +} bool DocumentChildHandler::convertData(Handle<FieldDescriptor> field, Variant &data, Logger &logger) @@ -226,71 +332,40 @@ bool DocumentChildHandler::convertData(Handle<FieldDescriptor> field, bool DocumentChildHandler::data(Variant &data) { - Rooted<Node> parentNode = scope().selectOrThrow( - {&RttiTypes::StructuredEntity, &RttiTypes::AnnotationEntity, - &RttiTypes::DocumentField}); - - std::string fieldName; - DocumentEntity *strctParent; - bool inField; - - preamble(parentNode, fieldName, strctParent, inField); - - Rooted<Descriptor> desc = strctParent->getDescriptor(); - // The parent from which we need to connect to the primitive content. - Rooted<Node> parentClass; - - // We distinguish two cases here: One for fields that are given. - if (inField) { - // Retrieve the actual FieldDescriptor - Rooted<FieldDescriptor> field = desc->getFieldDescriptor(fieldName); - if (field == nullptr) { - logger().error( - std::string("Can't handle data because no field with name \"") + - fieldName + "\" exists in descriptor\"" + desc->getName() + - "\".", - location()); + Rooted<Node> parentField = scope().getLeaf(); + assert(parentField->isa(&RttiTypes::DocumentField)); + + size_t fieldIdx; + DocumentEntity *parent; + + preamble(parentField, fieldIdx, parent); + + Rooted<Descriptor> desc = parent->getDescriptor(); + + // Retrieve the actual FieldDescriptor + Rooted<FieldDescriptor> field = desc->getFieldDescriptors()[fieldIdx]; + // If it is a primitive field directly, try to parse the content. + if (field->isPrimitive()) { + // Add it as primitive content. + if (!convertData(field, data, logger())) { return false; } - // If it is a primitive field directly, try to parse the content. - if (field->isPrimitive()) { - // Add it as primitive content. - if (!convertData(field, data, logger())) { - return false; - } - strctParent->createChildDocumentPrimitive(data, fieldName); - return true; - } - // If it is not primitive we need to connect via transparent elements - // and default fields. - parentClass = field; - } else { - // In case of default fields we need to construct via default fields - // and maybe transparent elements. - parentClass = desc; + parent->createChildDocumentPrimitive(data, fieldIdx); + return true; } // Search through all permitted default fields of the parent class that // allow primitive content at this point and could be constructed via // transparent intermediate entities. - - // Retrieve all default fields at this point, either from the field - // descriptor or the structured class - NodeVector<FieldDescriptor> defaultFields; - if (inField) { - defaultFields = parentClass.cast<FieldDescriptor>()->getDefaultFields(); - } else { - defaultFields = parentClass.cast<StructuredClass>()->getDefaultFields(); - } - + NodeVector<FieldDescriptor> defaultFields = field->getDefaultFields(); // Try to parse the data using the type specified by the respective field. // If that does not work we proceed to the next possible field. std::vector<LoggerFork> forks; - for (auto field : defaultFields) { + for (auto primitiveField : defaultFields) { // Then try to parse the content using the type specification. forks.emplace_back(logger().fork()); - if (!convertData(field, data, forks.back())) { + if (!convertData(primitiveField, data, forks.back())) { continue; } @@ -298,18 +373,12 @@ bool DocumentChildHandler::data(Variant &data) forks.back().commit(); // Construct the necessary path - if (inField) { - NodeVector<Node> path = - parentClass.cast<FieldDescriptor>()->pathTo(field, logger()); - createPath(fieldName, path, strctParent); - } else { - auto pathRes = desc->pathTo(field, logger()); - assert(pathRes.second); - createPath(pathRes.first, strctParent); - } + NodeVector<Node> path = field->pathTo(primitiveField, logger()); + // TODO: Create methods with indices instead of names. + createPath(fieldIdx, path, parent); // Then create the primitive element - strctParent->createChildDocumentPrimitive(data); + parent->createChildDocumentPrimitive(data); return true; } @@ -348,4 +417,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/DocumentHandler.hpp b/src/core/parser/stack/DocumentHandler.hpp index 2c474f9..5953e3a 100644 --- a/src/core/parser/stack/DocumentHandler.hpp +++ b/src/core/parser/stack/DocumentHandler.hpp @@ -76,29 +76,74 @@ public: */ class DocumentField : public Node { public: - using Node::Node; + const size_t fieldIdx; + const bool transparent; + + DocumentField(Manager &mgr, Handle<Node> parent, size_t fieldIdx, + bool transparent) + : Node(mgr, parent), fieldIdx(fieldIdx), transparent(transparent) + { + } }; /** * The DocumentChildHandler class performs the actual parsing of the user * defined elements in an Ousía document. */ -class DocumentChildHandler : public StaticHandler { +class DocumentChildHandler : public Handler { private: + bool isExplicitField = false; + /** - * Code shared by both the start() and the end() method. Checks whether the - * parser currently is in a field and returns the name of this field. + * Code shared by both the start(), fieldStart() and the data() method. + * Checks whether the parser currently is in a field and returns the name + * of this field. * * @param parentNode is the next possible parent node (a document, * a structured entity, an annotation entity or a field). - * @param fieldName is an output parameter to which the name of the current + * @param fieldIdx is an output parameter to which the index of the current * field is written (or unchanged if we're not in a field). * @param parent is an output parameter to which the parent document entity * will be written. - * @param inField is set to true if we actually are in a field. */ - void preamble(Handle<Node> parentNode, std::string &fieldName, - DocumentEntity *&parent, bool &inField); + void preamble(Rooted<Node> &parentNode, size_t &fieldIdx, + DocumentEntity *&parent); + + /** + * Creates transparent elements that are stored in the given path. + * + * @param path a NodeVector of alternating FieldDescriptors and + * StructuredClasses. For each of the StructuredClasses at + * index p an instance is created and added to the field at + * index p-1 of the previously created instance of the + * StructuredClass at index p-2. + * @param parent is the parent DocumentEntity for the first transparent + * element. This will be reset for each created transparent + * element. + * @param p0 is the index of the path vector of the first + * StructuredClass for which an instance shall be created. + * This is 1 per default. + */ + void createPath(const NodeVector<Node> &path, DocumentEntity *&parent, + size_t p0 = 1); + + /** + * Creates transparent elements that are stored in the given path. + * + * @param fieldIdx is the index of the field for which the first instance + * shall be added. + * @param path a NodeVector of alternating FieldDescriptors and + * StructuredClasses. For each of the StructuredClasses at + * index p an instance is created and added to the field at + * index p-1 of the previously created instance of the + * StructuredClass at index p-2. The first element has + * to be a StructuredClass. + * @param parent is the parent DocumentEntity for the first transparent + * element. This will be reset for each created transparent + * element. + */ + void createPath(const size_t &fieldIdx, const NodeVector<Node> &path, + DocumentEntity *&parent); /** * Tries to convert the given data to the type that is specified in the @@ -116,12 +161,22 @@ private: Logger &logger); public: - using StaticHandler::StaticHandler; + using Handler::Handler; bool start(Variant::mapType &args) override; void end() override; bool data(Variant &data) override; + bool fieldStart(bool &isDefault, size_t fieldIdx) override; + + void fieldEnd() override; + + bool annotationStart(const Variant &className, + Variant::mapType &args) override; + + bool annotationEnd(const Variant &className, + const Variant &elementName) override; + /** * Creates a new instance of the DocumentChildHandler. * @@ -146,7 +201,6 @@ extern const State Document; */ extern const State DocumentChild; } - } namespace RttiTypes { @@ -155,8 +209,6 @@ namespace RttiTypes { */ extern const Rtti DocumentField; } - } -#endif /* _OUSIA_PARSER_STACK_DOCUMENT_HANDLER_HPP_ */ - +#endif /* _OUSIA_PARSER_STACK_DOCUMENT_HANDLER_HPP_ */
\ No newline at end of file diff --git a/src/core/parser/stack/DomainHandler.cpp b/src/core/parser/stack/DomainHandler.cpp index a2c8eec..ddec1ee 100644 --- a/src/core/parser/stack/DomainHandler.cpp +++ b/src/core/parser/stack/DomainHandler.cpp @@ -133,11 +133,18 @@ bool DomainFieldHandler::start(Variant::mapType &args) Rooted<Descriptor> parent = scope().selectOrThrow<Descriptor>(); - Rooted<FieldDescriptor> field = parent->createFieldDescriptor( + auto res = parent->createFieldDescriptor( logger(), type, args["name"].asString(), args["optional"].asBool()); - field->setLocation(location()); + res.first->setLocation(location()); + if (res.second) { + logger().warning( + std::string("Field \"") + res.first->getName() + + "\" was declared after main field. The order of fields " + "was changed to make the main field the last field.", + *res.first); + } - scope().push(field); + scope().push(res.first); return true; } @@ -150,14 +157,24 @@ bool DomainFieldRefHandler::start(Variant::mapType &args) Rooted<Descriptor> parent = scope().selectOrThrow<Descriptor>(); const std::string &name = args["ref"].asString(); - scope().resolveFieldDescriptor( - name, parent, logger(), - [](Handle<Node> field, Handle<Node> parent, Logger &logger) { - if (field != nullptr) { - parent.cast<StructuredClass>()->addFieldDescriptor( - field.cast<FieldDescriptor>(), logger); - } - }); + + auto loc = location(); + + scope().resolveFieldDescriptor(name, parent, logger(), + [loc](Handle<Node> field, + Handle<Node> parent, Logger &logger) { + if (field != nullptr) { + if (parent.cast<StructuredClass>()->addFieldDescriptor( + field.cast<FieldDescriptor>(), logger)) { + logger.warning( + std::string("Field \"") + field->getName() + + "\" was referenced after main field was declared. The " + "order of fields was changed to make the main field " + "the last field.", + loc); + } + } + }); return true; } @@ -176,13 +193,20 @@ bool DomainPrimitiveHandler::start(Variant::mapType &args) fieldType = FieldDescriptor::FieldType::TREE; } - Rooted<FieldDescriptor> field = parent->createPrimitiveFieldDescriptor( + auto res = parent->createPrimitiveFieldDescriptor( new UnknownType(manager()), logger(), fieldType, args["name"].asString(), args["optional"].asBool()); - field->setLocation(location()); + res.first->setLocation(location()); + if (res.second) { + logger().warning( + std::string("Field \"") + res.first->getName() + + "\" was declared after main field. The order of fields " + "was changed to make the main field the last field.", + *res.first); + } const std::string &type = args["type"].asString(); - scope().resolve<Type>(type, field, logger(), + scope().resolve<Type>(type, res.first, logger(), [](Handle<Node> type, Handle<Node> field, Logger &logger) { if (type != nullptr) { @@ -190,7 +214,7 @@ bool DomainPrimitiveHandler::start(Variant::mapType &args) } }); - scope().push(field); + scope().push(res.first); return true; } @@ -254,8 +278,8 @@ bool DomainParentFieldHandler::start(Variant::mapType &args) Logger &logger) { if (parent != nullptr) { Rooted<FieldDescriptor> field = - parent.cast<Descriptor>()->createFieldDescriptor( - logger, type, name, optional); + (parent.cast<Descriptor>()->createFieldDescriptor( + logger, type, name, optional)).first; field->addChild(strct.cast<StructuredClass>()); } }); @@ -390,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 905edb4..b98cddb 100644 --- a/src/core/parser/stack/Stack.cpp +++ b/src/core/parser/stack/Stack.cpp @@ -361,9 +361,10 @@ void Stack::command(const Variant &name, const Variant::mapType &args) info.valid = false; if (validStack) { // Canonicalize the arguments (if this has not already been done), - // allow additional arguments + // allow additional arguments and numeric indices Variant::mapType canonicalArgs = args; - targetState->arguments.validateMap(canonicalArgs, loggerFork, true); + targetState->arguments.validateMap(canonicalArgs, loggerFork, true, + true); handler->setLogger(loggerFork); try { diff --git a/src/core/resource/ResourceLocator.cpp b/src/core/resource/ResourceLocator.cpp index b19542e..da38cbd 100644 --- a/src/core/resource/ResourceLocator.cpp +++ b/src/core/resource/ResourceLocator.cpp @@ -25,6 +25,37 @@ namespace ousia { /* Class ResourceLocator */ +std::vector<std::string> ResourceLocator::autocomplete( + const std::string &path, const ResourceType type, + const Resource &relativeTo) const +{ + // If the locator of the given relative resource is this locator instance, + // use the location specified in the resource, otherwise just use no + // "relativeTo" path. + if (&relativeTo.getLocator() == this) { + return autocomplete(path, type, relativeTo.getLocation()); + } + return autocomplete(path, type, ""); +} + +std::vector<std::string> ResourceLocator::autocomplete( + const std::string &path, const ResourceType type, + const std::string &relativeTo) const +{ + // Try to locate the resource for the specified type, if not found, use + // the "UNKNOWN" type. + std::vector<std::string> res = doAutocomplete(path, type, relativeTo); + if (!res.empty()) { + return res; + } + + // Use the "UNKNOWN" type + if (type != ResourceType::UNKNOWN) { + return doAutocomplete(path, ResourceType::UNKNOWN, relativeTo); + } + return std::vector<std::string>{}; +} + bool ResourceLocator::locate(Resource &resource, const std::string &path, const ResourceType type, const Resource &relativeTo) const @@ -59,6 +90,14 @@ std::unique_ptr<std::istream> ResourceLocator::stream( return doStream(location); } +std::vector<std::string> ResourceLocator::doAutocomplete( + const std::string &path, const ResourceType type, + const std::string &relativeTo) const +{ + // Default implementation + return std::vector<std::string>{}; +} + /* Class StaticResourceLocator */ bool StaticResourceLocator::doLocate(Resource &resource, diff --git a/src/core/resource/ResourceLocator.hpp b/src/core/resource/ResourceLocator.hpp index c1d0807..d6a2ffc 100644 --- a/src/core/resource/ResourceLocator.hpp +++ b/src/core/resource/ResourceLocator.hpp @@ -21,6 +21,7 @@ #include <istream> #include <memory> +#include <vector> #include "Resource.hpp" @@ -60,6 +61,23 @@ protected: const std::string &relativeTo) const = 0; /** + * Tries to autocomplete the given filename by searching for files with this + * basename but different extensions. Returns a list of possible extended + * paths. May return an empty list if this function is not supported. + * + * @param path is the given filename for which versions with extension + * should be searched. + * @param type is the resource type, determining the search paths in which + * the resource is looked up. + * @param relativeTo is an already resolved Resource relative to which the + * file should be searched. + * @return a list of matching, autocompleted file paths. + */ + virtual std::vector<std::string> doAutocomplete( + const std::string &path, const ResourceType type, + const std::string &relativeTo) const; + + /** * This method returns a stream containing the data of the resource at the * given location. * @@ -80,6 +98,41 @@ public: virtual ~ResourceLocator() {} /** + * Tries to autocomplete the given filename by searching for files with this + * basename but different extensions. Returns a list of possible extended + * paths. May return an empty list if this function is not supported. + * + * @param path is the given filename for which versions with extension + * should be searched. + * @param type is the resource type, determining the search paths in which + * the resource is looked up. + * @param relativeTo is an already resolved Resource relative to which the + * file should be searched. + * @return a list of matching, autocompleted file paths. + */ + std::vector<std::string> autocomplete( + const std::string &path, + const ResourceType type = ResourceType::UNKNOWN, + const Resource &relativeTo = NullResource) const; + + /** + * Tries to autocomplete the given filename by searching for files with this + * basename but different extensions. Returns a list of possible extended + * paths. May return an empty list if this function is not supported. + * + * @param path is the given filename for which versions with extension + * should be searched. + * @param type is the resource type, determining the search paths in which + * the resource is looked up. + * @param relativeTo is the location of an already resolved resource + * relative to which this resource should be located. + * @return a list of matching, autocompleted file paths. + */ + std::vector<std::string> autocomplete(const std::string &path, + const ResourceType type, + const std::string &relativeTo) const; + + /** * The locate function uses this ResourceLocator to search for a given * Resource name (path parameter). * @@ -109,8 +162,7 @@ public: * @return true if a resource could be found, false otherwise. */ bool locate(Resource &resource, const std::string &path, - const ResourceType type, - const std::string &relativeTo) const; + const ResourceType type, const std::string &relativeTo) const; /** * This method returns a stream containing the data of the resource at the @@ -123,7 +175,7 @@ public: * C++11 compiler does not yet support move semantics for * streams. */ - std::unique_ptr<std::istream> stream(const std::string &location) const; + std::unique_ptr<std::istream> stream(const std::string &location) const; }; /** diff --git a/src/core/resource/ResourceManager.cpp b/src/core/resource/ResourceManager.cpp index 74fd5ad..e3edfc8 100644 --- a/src/core/resource/ResourceManager.cpp +++ b/src/core/resource/ResourceManager.cpp @@ -101,10 +101,10 @@ NodeVector<Node> ResourceManager::parse( // Locate the resource relative to the old resource, abort if this did not // work - ResourceRequest req{path, mimetype, rel, supportedTypes}; + ResourceRequest req{path, mimetype, rel, supportedTypes, relativeTo}; Resource resource; if (!req.deduce(registry, logger) || - !req.locate(registry, logger, resource, relativeTo)) { + !req.locate(registry, logger, resource)) { return NodeVector<Node>{}; } diff --git a/src/core/resource/ResourceRequest.cpp b/src/core/resource/ResourceRequest.cpp index 0803208..0216388 100644 --- a/src/core/resource/ResourceRequest.cpp +++ b/src/core/resource/ResourceRequest.cpp @@ -136,11 +136,13 @@ static RttiSet limitSupportedTypes(ResourceType resourceType, ResourceRequest::ResourceRequest(const std::string &path, const std::string &mimetype, const std::string &rel, - const RttiSet &supportedTypes) + const RttiSet &supportedTypes, + const Resource &relativeTo) : path(path), mimetype(mimetype), rel(rel), supportedTypes(supportedTypes), + relativeTo(relativeTo), resourceType(ResourceType::UNKNOWN), parser(nullptr) { @@ -156,11 +158,49 @@ bool ResourceRequest::deduce(Registry ®istry, Logger &logger) return false; } + // Try to deduce the ResourceType from the "rel" string + if (!rel.empty()) { + resourceType = Resource::getResourceTypeByName(rel); + if (resourceType == ResourceType::UNKNOWN) { + logger.error(std::string("Unknown relation \"") + rel + + std::string("\", expected one of ") + + supportedResourceTypesString(supportedTypes)); + ok = false; + } + } + + // Try to deduce the ResourceType from the supportedTypes + if (resourceType == ResourceType::UNKNOWN) { + resourceType = deduceResourceType(supportedTypes); + } + + // If the given file has no extension, try to complete it + std::string ext = Utils::extractFileExtension(path); + if (ext.empty()) { + std::vector<std::string> paths = + registry.autocompleteResource(path, resourceType, relativeTo); + if (paths.size() > 1U) { + // There are multiple possible files + // TODO: Select the one which matches the other parameters + logger.error(std::string("Resource \"") + path + + std::string("\" is ambiguous.")); + logger.note(std::string("Possibly referenced files are:"), + SourceLocation{}, MessageMode::NO_CONTEXT); + for (const auto &p : paths) { + logger.note(p, SourceLocation{}, MessageMode::NO_CONTEXT); + } + ok = false; + } else if (paths.size() == 1U) { + // Otherwise just copy the first resolved element + path = paths[0]; + } + } + // Try to deduce the mimetype if none was given if (mimetype.empty()) { mimetype = registry.getMimetypeForFilename(path); if (mimetype.empty()) { - logger.error(std::string("Filename \"") + path + + logger.error(std::string("Resource \"") + path + std::string( "\" has an unknown file extension. Explicitly " "specify a mimetype.")); @@ -187,22 +227,6 @@ bool ResourceRequest::deduce(Registry ®istry, Logger &logger) } } - // Try to deduce the ResourceType from the "rel" string - if (!rel.empty()) { - resourceType = Resource::getResourceTypeByName(rel); - if (resourceType == ResourceType::UNKNOWN) { - logger.error(std::string("Unknown relation \"") + rel + - std::string("\", expected one of ") + - supportedResourceTypesString(supportedTypes)); - ok = false; - } - } - - // Try to deduce the ResourceType from the supportedTypes - if (resourceType == ResourceType::UNKNOWN) { - resourceType = deduceResourceType(supportedTypes); - } - // Further limit the supportedTypes to those types that correspond to the // specified resource type. if (resourceType != ResourceType::UNKNOWN) { @@ -227,8 +251,7 @@ bool ResourceRequest::deduce(Registry ®istry, Logger &logger) } bool ResourceRequest::locate(Registry ®istry, Logger &logger, - Resource &resource, - const Resource &relativeTo) const + Resource &resource) const { if (!registry.locateResource(resource, path, resourceType, relativeTo)) { logger.error(std::string("File not found: ") + path); diff --git a/src/core/resource/ResourceRequest.hpp b/src/core/resource/ResourceRequest.hpp index 9d5728f..a06d360 100644 --- a/src/core/resource/ResourceRequest.hpp +++ b/src/core/resource/ResourceRequest.hpp @@ -77,6 +77,11 @@ private: RttiSet parserTypes; /** + * The resource relative to which this resource is to be located. + */ + Resource relativeTo; + + /** * ResourceType as deduced from the user provided values. */ ResourceType resourceType; @@ -101,9 +106,12 @@ public: * @param supportedTypes specifies the types of the Node that may result * from the resource once it has been parsed. This value is not directly * provided by the user, but by the calling code. + * @param relativeTo is another resource relative to which the Resource + * should be looked up. */ ResourceRequest(const std::string &path, const std::string &mimetype, - const std::string &rel, const RttiSet &supportedTypes); + const std::string &rel, const RttiSet &supportedTypes, + const Resource &relativeTo = NullResource); /** * Tries to deduce all possible information and produces log messages for @@ -127,13 +135,10 @@ public: * logged. * @param resource is the Resource descriptor that should be filled with the * actual location. - * @param relativeTo is another resource relative to which the Resource - * should be looked up. * @return true if a resource was found, false otherwise. Equivalent to * the value of resource.isValid(). */ - bool locate(Registry ®istry, Logger &logger, Resource &resource, - const Resource &relativeTo = NullResource) const; + bool locate(Registry ®istry, Logger &logger, Resource &resource) const; /** * Returns the requested path of the file that should be included. diff --git a/src/plugins/filesystem/FileLocator.cpp b/src/plugins/filesystem/FileLocator.cpp index e11fd72..9a39901 100644 --- a/src/plugins/filesystem/FileLocator.cpp +++ b/src/plugins/filesystem/FileLocator.cpp @@ -29,6 +29,8 @@ #include <boost/filesystem.hpp> +#include <core/common/Utils.hpp> + #include "FileLocator.hpp" #include "SpecialPaths.hpp" @@ -116,42 +118,26 @@ void FileLocator::addUnittestSearchPath(const std::string &subdir, type); } -static bool checkPath(Resource &resource, const ResourceLocator &locator, - fs::path p, const ResourceType type) -{ - if (fs::exists(p) && fs::is_regular_file(p)) { - std::string location = fs::canonical(p).generic_string(); -#ifdef FILELOCATOR_DEBUG_PRINT - std::cout << "FileLocator: Found at " << location << std::endl; -#endif - resource = Resource(true, locator, type, location); - return true; - } - return false; -} - -bool FileLocator::doLocate(Resource &resource, const std::string &path, - const ResourceType type, - const std::string &relativeTo) const +template <typename CallbackType> +static bool iteratePaths(const FileLocator::SearchPaths &searchPaths, + const std::string &path, const ResourceType type, + const std::string &relativeTo, CallbackType callback) { #ifdef FILELOCATOR_DEBUG_PRINT std::cout << "FileLocator: Searching for \"" << path << "\"" << std::endl; #endif - // TODO: Check if with ./book.oxm relative paths are used and otherwise - // global search paths take precedence. + // Divide the given path into the directory and the filename + fs::path p{path}; + fs::path dir = p.parent_path(); + std::string filename = p.filename().generic_string(); - // If the path is an absolute path, look at this exact point. - { - fs::path p{path}; - if (p.is_absolute()) { - if (checkPath(resource, *this, p, type)) { - return true; - } else { - return false; - } - } + // Check whether the given resource has an absolute path -- if yes, call the + // callback function and do not try any search paths + if (dir.is_absolute()) { + return callback(dir, filename); } + // If the path starts with "./" or "../" only perform relative lookups! if (path.substr(0, 2) != "./" && path.substr(0, 3) != "../") { // Look in the search paths, search backwards, last defined search @@ -163,36 +149,87 @@ bool FileLocator::doLocate(Resource &resource, const std::string &path, #ifdef FILELOCATOR_DEBUG_PRINT std::cout << "FileLocator: Entering " << *it << std::endl; #endif - fs::path p{*it}; - p /= path; - if (checkPath(resource, *this, p, type)) { + // Concatenate the searchpath with the given directory + fs::path curDir = fs::path(*it) / dir; + if (callback(curDir, filename)) { return true; } } } } + + // Perform relative lookups if (!relativeTo.empty()) { - fs::path base(relativeTo); - if (fs::exists(base)) { + fs::path curDir(relativeTo); + if (fs::exists(curDir)) { // Look if 'relativeTo' is a directory already. - if (!fs::is_directory(base)) { + if (!fs::is_directory(curDir)) { // If not we use the parent directory. - base = base.parent_path(); + curDir = curDir.parent_path(); } - // Use the / operator to append the path. - base /= path; + // Append the directory to the base path and try to resolve this + // pair + curDir = curDir / dir; // If we already found a fitting resource there, use that. - if (checkPath(resource, *this, base, type)) { + if (callback(curDir, filename)) { return true; } } } - return false; } +bool FileLocator::doLocate(Resource &resource, const std::string &path, + const ResourceType type, + const std::string &relativeTo) const +{ + return iteratePaths( + searchPaths, path, type, relativeTo, + [&](const fs::path &dir, const std::string &filename) -> bool { + // Combine directory and filename + fs::path p = dir / filename; + + // Check whether p exists + if (fs::exists(p) && fs::is_regular_file(p)) { + std::string location = fs::canonical(p).generic_string(); +#ifdef FILELOCATOR_DEBUG_PRINT + std::cout << "FileLocator: Found at " << location << std::endl; +#endif + resource = Resource(true, *this, type, location); + return true; + } + return false; + }); +} + +std::vector<std::string> FileLocator::doAutocomplete( + const std::string &path, const ResourceType type, + const std::string &relativeTo) const +{ + std::vector<std::string> res; + iteratePaths(searchPaths, path, type, relativeTo, + [&](const fs::path &dir, const std::string &filename) -> bool { + // Make sure the given directory actually is a directory + if (!fs::is_directory(dir)) { + return false; + } + + // Iterate over the directory content + fs::directory_iterator end; + for (fs::directory_iterator it(dir); it != end; it++) { + const std::string fn = it->path().filename().generic_string(); + if (!fn.empty() && fn[fn.size() - 1] != '~' && + Utils::startsWith(fn, filename)) { + res.push_back(it->path().generic_string()); + } + } + return !res.empty(); + }); + return res; +} + std::unique_ptr<std::istream> FileLocator::doStream( const std::string &location) const { diff --git a/src/plugins/filesystem/FileLocator.hpp b/src/plugins/filesystem/FileLocator.hpp index 1c3e494..14988a8 100644 --- a/src/plugins/filesystem/FileLocator.hpp +++ b/src/plugins/filesystem/FileLocator.hpp @@ -81,6 +81,10 @@ protected: const ResourceType type, const std::string &relativeTo) const override; + std::vector<std::string> doAutocomplete( + const std::string &path, const ResourceType type, + const std::string &relativeTo) const override; + std::unique_ptr<std::istream> doStream( const std::string &location) const override; diff --git a/src/plugins/xml/XmlOutput.cpp b/src/plugins/xml/XmlOutput.cpp index 68edc0e..e1f1c31 100644 --- a/src/plugins/xml/XmlOutput.cpp +++ b/src/plugins/xml/XmlOutput.cpp @@ -44,7 +44,7 @@ void XmlTransformer::writeXml(Handle<Document> doc, std::ostream &out, transformStructuredEntity(document, doc->getRoot(), logger, pretty); document->addChild(root); // then serialize. - document->serialize(out, "<?xml version=\"1.0\"?>", pretty); + document->serialize(out, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", pretty); } Rooted<Element> XmlTransformer::transformStructuredEntity( diff --git a/test/core/common/ArgumentTest.cpp b/test/core/common/ArgumentTest.cpp index 0a2dcfa..ca93f45 100644 --- a/test/core/common/ArgumentTest.cpp +++ b/test/core/common/ArgumentTest.cpp @@ -792,8 +792,8 @@ TEST(Arguments, invalid) Variant::arrayType arr{1}; - ASSERT_TRUE(argsInvalid.validateArray(arr, logger)); // No error message - ASSERT_FALSE(argsValid.validateArray(arr, logger)); // Too many arguments + ASSERT_TRUE(argsInvalid.validateArray(arr, logger)); // No error message + ASSERT_FALSE(argsValid.validateArray(arr, logger)); // Too many arguments } TEST(Arguments, validateArray) @@ -879,6 +879,25 @@ TEST(Arguments, validateMap) {{"a", 2}, {"b", "test"}, {"c", true}, {"d", nullptr}}), map); } + + { + Variant::mapType map{{"#0", 2}, {"#1", "bla"}, {"#2", false}}; + ASSERT_FALSE(args.validateMap(map, logger, false, false)); + ASSERT_EQ(Variant::mapType({{"#0", 2}, + {"#1", "bla"}, + {"#2", false}, + {"a", 0}, + {"b", "test"}, + {"c", true}}), + map); + } + + { + Variant::mapType map{{"#0", 2}, {"#1", "bla"}, {"#2", false}}; + ASSERT_TRUE(args.validateMap(map, logger, false, true)); + ASSERT_EQ(Variant::mapType({{"a", 2}, {"b", "bla"}, {"c", false}}), + map); + } } TEST(Arguments, validateMissing) @@ -897,6 +916,5 @@ TEST(Arguments, validateMissing) ASSERT_EQ(Variant::arrayType({""}), arr); } } - } diff --git a/test/core/common/VariantReaderTest.cpp b/test/core/common/VariantReaderTest.cpp index a23af09..79e567b 100644 --- a/test/core/common/VariantReaderTest.cpp +++ b/test/core/common/VariantReaderTest.cpp @@ -1153,6 +1153,18 @@ TEST(VariantReader, parseGenericString) ASSERT_EQ(0U, loc.getStart()); ASSERT_EQ(15U, loc.getEnd()); } + + // Parse empty string + { + auto res = VariantReader::parseGenericString("", logger); + ASSERT_TRUE(res.first); + ASSERT_TRUE(res.second.isString()); + ASSERT_EQ("", res.second.asString()); + + SourceLocation loc = res.second.getLocation(); + ASSERT_EQ(0U, loc.getStart()); + ASSERT_EQ(0U, loc.getEnd()); + } } TEST(VariantReader, parseGenericComplex) diff --git a/test/core/model/DocumentTest.cpp b/test/core/model/DocumentTest.cpp index 0c6eea6..1bb2356 100644 --- a/test/core/model/DocumentTest.cpp +++ b/test/core/model/DocumentTest.cpp @@ -147,7 +147,7 @@ TEST(Document, validate) // now let's extend the rootClass with a default field. Rooted<FieldDescriptor> rootField = - rootClass->createFieldDescriptor(logger); + rootClass->createFieldDescriptor(logger).first; // and add a child class for it. Rooted<StructuredClass> childClass{ new StructuredClass(mgr, "child", domain, single)}; @@ -194,7 +194,7 @@ TEST(Document, validate) * instances now. */ Rooted<FieldDescriptor> childField = - childClass->createFieldDescriptor(logger); + childClass->createFieldDescriptor(logger).first; childField->addChild(childClass); { /* @@ -214,7 +214,7 @@ TEST(Document, validate) */ Rooted<FieldDescriptor> childSubField = childSubClass->createFieldDescriptor( - logger, FieldDescriptor::FieldType::TREE, "dummy", true); + logger, FieldDescriptor::FieldType::TREE, "dummy", true).first; // add a child pro forma to make it valid. childSubField->addChild(childSubClass); { @@ -234,7 +234,7 @@ TEST(Document, validate) Rooted<FieldDescriptor> primitive_field = childSubClass->createPrimitiveFieldDescriptor( sys->getIntType(), logger, FieldDescriptor::FieldType::SUBTREE, - "int", false); + "int", false).first; { /* * Now a document with one instance of the Child subclass should be diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index 4cb4331..d68648e 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -186,21 +186,21 @@ TEST(Descriptor, pathToAdvanced) new StructuredClass(mgr, "target", domain, Cardinality::any())}; // We create a field for A - Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger); + Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger).first; A_field->addChild(B); A_field->addChild(D); // We create no field for B // One for C - Rooted<FieldDescriptor> C_field = C->createFieldDescriptor(logger); + Rooted<FieldDescriptor> C_field = C->createFieldDescriptor(logger).first; C_field->addChild(target); // One for D - Rooted<FieldDescriptor> D_field = D->createFieldDescriptor(logger); + Rooted<FieldDescriptor> D_field = D->createFieldDescriptor(logger).first; D_field->addChild(E); // One for E - Rooted<FieldDescriptor> E_field = E->createFieldDescriptor(logger); + Rooted<FieldDescriptor> E_field = E->createFieldDescriptor(logger).first; E_field->addChild(target); ASSERT_TRUE(domain->validate(logger)); @@ -239,7 +239,7 @@ TEST(Descriptor, getDefaultFields) // create a field. Rooted<FieldDescriptor> A_prim_field = - A->createPrimitiveFieldDescriptor(sys->getStringType(), logger); + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; // now we should find that. auto fields = A->getDefaultFields(); ASSERT_EQ(1U, fields.size()); @@ -262,7 +262,7 @@ TEST(Descriptor, getDefaultFields) ASSERT_EQ(A_prim_field, fields[0]); // and we should not be able to find it if we override the field. - Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger); + Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger).first; ASSERT_TRUE(A->getDefaultFields().empty()); // add a transparent child class. @@ -273,7 +273,7 @@ TEST(Descriptor, getDefaultFields) // add a primitive field for it. Rooted<FieldDescriptor> C_field = - C->createPrimitiveFieldDescriptor(sys->getStringType(), logger); + C->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; // now we should find that. fields = A->getDefaultFields(); @@ -285,14 +285,14 @@ TEST(Descriptor, getDefaultFields) Rooted<StructuredClass> D{new StructuredClass( mgr, "D", domain, Cardinality::any(), nullptr, true, false)}; A_field->addChild(D); - Rooted<FieldDescriptor> D_field = D->createFieldDescriptor(logger); + Rooted<FieldDescriptor> D_field = D->createFieldDescriptor(logger).first; Rooted<StructuredClass> E{new StructuredClass( mgr, "E", domain, Cardinality::any(), nullptr, true, false)}; D_field->addChild(E); Rooted<StructuredClass> F{new StructuredClass( mgr, "E", domain, Cardinality::any(), E, true, false)}; Rooted<FieldDescriptor> F_field = - F->createPrimitiveFieldDescriptor(sys->getStringType(), logger); + F->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first; // now we should find both primitive fields, but the C field first. fields = A->getDefaultFields(); @@ -436,7 +436,7 @@ TEST(Domain, validate) ASSERT_TRUE(domain->validate(logger)); // Let's add a primitive field (without a primitive type at first) Rooted<FieldDescriptor> base_field = - base->createPrimitiveFieldDescriptor(nullptr, logger); + base->createPrimitiveFieldDescriptor(nullptr, logger).first; // this should not be valid. ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_FALSE(domain->validate(logger)); @@ -464,7 +464,8 @@ TEST(Domain, validate) ASSERT_TRUE(domain->validate(logger)); ASSERT_EQ(base, sub->getSuperclass()); // add a non-primitive field to the child class. - Rooted<FieldDescriptor> sub_field = sub->createFieldDescriptor(logger); + Rooted<FieldDescriptor> sub_field = + sub->createFieldDescriptor(logger).first; // this should not be valid because we allow no children. ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_FALSE(domain->validate(logger)); @@ -489,8 +490,9 @@ TEST(Domain, validate) ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_TRUE(domain->validate(logger)); // It should be invalid if we set another TREE field. - Rooted<FieldDescriptor> sub_field2 = sub->createFieldDescriptor( - logger, FieldDescriptor::FieldType::TREE, "test", false); + Rooted<FieldDescriptor> sub_field2 = + sub->createFieldDescriptor(logger, FieldDescriptor::FieldType::TREE, + "test", false).first; ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_FALSE(domain->validate(logger)); // but valid again if we remove it @@ -499,4 +501,4 @@ TEST(Domain, validate) ASSERT_TRUE(domain->validate(logger)); } } -} +}
\ No newline at end of file diff --git a/test/core/model/TestAdvanced.hpp b/test/core/model/TestAdvanced.hpp index 27f33cc..8e81554 100644 --- a/test/core/model/TestAdvanced.hpp +++ b/test/core/model/TestAdvanced.hpp @@ -66,7 +66,7 @@ static Rooted<Domain> constructHeadingDomain(Manager &mgr, for (auto &s : secclasses) { Rooted<StructuredClass> desc = resolveDescriptor(bookDomain, s); Rooted<FieldDescriptor> heading_field = desc->createFieldDescriptor( - logger, FieldDescriptor::FieldType::SUBTREE, "heading", true); + logger, FieldDescriptor::FieldType::SUBTREE, "heading", true).first; heading_field->addChild(heading); } return domain; diff --git a/test/core/model/TestDomain.hpp b/test/core/model/TestDomain.hpp index c107a0d..779ef03 100644 --- a/test/core/model/TestDomain.hpp +++ b/test/core/model/TestDomain.hpp @@ -42,7 +42,8 @@ static Rooted<Domain> constructBookDomain(Manager &mgr, mgr, "book", domain, single, {nullptr}, false, true)}; // The structure field of it. - Rooted<FieldDescriptor> book_field = book->createFieldDescriptor(logger); + Rooted<FieldDescriptor> book_field = + book->createFieldDescriptor(logger).first; // From there on the "section". Rooted<StructuredClass> section{ @@ -51,7 +52,7 @@ static Rooted<Domain> constructBookDomain(Manager &mgr, // And the field of it. Rooted<FieldDescriptor> section_field = - section->createFieldDescriptor(logger); + section->createFieldDescriptor(logger).first; // We also add the "paragraph", which is transparent. Rooted<StructuredClass> paragraph{new StructuredClass( @@ -61,7 +62,7 @@ static Rooted<Domain> constructBookDomain(Manager &mgr, // And the field of it. Rooted<FieldDescriptor> paragraph_field = - paragraph->createFieldDescriptor(logger); + paragraph->createFieldDescriptor(logger).first; // We append "subsection" to section. Rooted<StructuredClass> subsection{ @@ -70,7 +71,7 @@ static Rooted<Domain> constructBookDomain(Manager &mgr, // And the field of it. Rooted<FieldDescriptor> subsection_field = - subsection->createFieldDescriptor(logger); + subsection->createFieldDescriptor(logger).first; // and we add the paragraph to subsections fields subsection_field->addChild(paragraph); @@ -82,10 +83,11 @@ static Rooted<Domain> constructBookDomain(Manager &mgr, // ... and has a primitive field. Rooted<FieldDescriptor> text_field = - text->createPrimitiveFieldDescriptor(sys->getStringType(), logger); + text->createPrimitiveFieldDescriptor(sys->getStringType(), logger) + .first; return domain; } } -#endif /* _TEST_DOMAIN_HPP_ */ +#endif /* _TEST_DOMAIN_HPP_ */
\ No newline at end of file diff --git a/test/plugins/filesystem/FileLocatorTest.cpp b/test/plugins/filesystem/FileLocatorTest.cpp index 87a9223..361a3ca 100644 --- a/test/plugins/filesystem/FileLocatorTest.cpp +++ b/test/plugins/filesystem/FileLocatorTest.cpp @@ -213,11 +213,11 @@ TEST(FileLocator, testDefaultSearchPaths) FileLocator locator; locator.addDefaultSearchPaths(); - assert_not_located(locator, "book.oxm", "", ResourceType::UNKNOWN); - assert_located(locator, "domain/book.oxm", "", ResourceType::UNKNOWN); - assert_located(locator, "book.oxm", "", ResourceType::DOMAIN_DESC); - assert_not_located(locator, "color.oxm", "", ResourceType::UNKNOWN); - assert_located(locator, "typesystem/color.oxm", "", ResourceType::UNKNOWN); - assert_located(locator, "color.oxm", "", ResourceType::TYPESYSTEM); + assert_not_located(locator, "book.osxml", "", ResourceType::UNKNOWN); + assert_located(locator, "domain/book.osxml", "", ResourceType::UNKNOWN); + assert_located(locator, "book.osxml", "", ResourceType::DOMAIN_DESC); + assert_not_located(locator, "color.osxml", "", ResourceType::UNKNOWN); + assert_located(locator, "typesystem/color.osxml", "", ResourceType::UNKNOWN); + assert_located(locator, "color.osxml", "", ResourceType::TYPESYSTEM); } } diff --git a/testdata/osmlparser/simple_book.osml b/testdata/osmlparser/simple_book.osml index e8639b8..21751b9 100644 --- a/testdata/osmlparser/simple_book.osml +++ b/testdata/osmlparser/simple_book.osml @@ -1,55 +1,31 @@ -%{ -This is just a simple document for toying around -}% -\import{../osxmlparser/book_domain.osxml} -\import{../osxmlparser/headings_domain.osxml} - -% Define some local domain here -\domain#stuff - \import[rel=typesystem]{../osxmlparser/color.osxml} - \import[rel=domain]{../osxmlparser/book_domain.osxml} - \struct#fancy - \primitive#myLittleColor[type=color] - \parentRef[ref=book] - \fieldRef[ref="$default"] +\import[domain]{../osxmlparser/book_domain.osxml} +\import[domain]{../osxmlparser/headings_domain.osxml} + +%\domain#stuff +% \import[typesystem]{../osxmlparser/color.osxml} +% \struct#fancy +% \primitive#bla[type=color] +% \parentRef[ref=book] +% \fieldRef[ref=$default] \begin{book} This might be some introductory text or a dedication. -\chapter#myFirstChapter -\heading{My first chapter} +\chapter#myFirstChapter{My first chapter} Here we might have an introduction to the chapter. \section#myFirstSection \heading{A rather curious experiment} +\paragraph{!Here we might find the actual section content. +\heading{test}} -\paragraph +\section#mySndSection Here we might find the actual section content. -\paragraph -Test -\section#mySndSection -Here we might find the actual section content. +bla -% Create a simple fancy instance -\fancy{[10,g=10,b=10]} - -%{ -Note that this - \fancy [10,g=10,b=10] -will also work (since the braces are not counted as commands). However - \fancy [t=10,g=10,b=10] -will leave the fancy field empty and assign the data as string to the above -paragraph. -}% - -%{ -Another note: In the short term the behaviour of the "{" and "}" tokens should -be changed in the parser. They should only be parsed when they are valid (after -commands and after a closing field). This can easily be hacked in (albeit it -will be ugly) -}% +%\fancy{aquamarine} \end{book} diff --git a/testdata/osxmlparser/bibliography_domain.osxml b/testdata/osxmlparser/bibliography_domain.osxml index 6418c61..53ba531 100644 --- a/testdata/osxmlparser/bibliography_domain.osxml +++ b/testdata/osxmlparser/bibliography_domain.osxml @@ -13,14 +13,14 @@ </parentRef> </struct> <struct name="bibEntry"> - <field name="authors" optional="true"> - <childRef ref="meta.author"/> - </field> <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/book_domain.osxml b/testdata/osxmlparser/book_domain.osxml index 4218915..5a8cc67 100644 --- a/testdata/osxmlparser/book_domain.osxml +++ b/testdata/osxmlparser/book_domain.osxml @@ -75,7 +75,7 @@ <childRef ref="book.paragraph"/> </field> </struct> - <struct name="paragraph" transparent="true" role="paragraph"> + <struct name="paragraph" transparent="true"> <!-- implicitly: <struct name="subsection" isRoot="false" cardinality="{*}" transparent="true" isa="" attributesDescriptor=""> @@ -87,7 +87,7 @@ <childRef ref="book.text"/> </field> </struct> - <struct name="text" transparent="true" role="text"> + <struct name="text" transparent="true"> <!-- implicitly: <struct name="text" isRoot="false" cardinality="{*}" transparent="true" isa="" attributesDescriptor=""> |