diff --git a/README.md b/README.md index cdae696..6f2fbef 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ XSDCPP solves the issue by creating a data model and parser that can easily be a ## Features and Limitations -Since XSD is full of features (and unnecessary complexity), it's very hard to support all of them. +Since XSD is full of features (and unnecessary complexity), it's very hard to support all of them. So, XSDCPP does currently just support what was thrown at it so far and there are probably some severe limitations. Notable supported features: @@ -61,6 +61,47 @@ Intentionally not supported features: * Initialize submodules. `cd xsdcpp && git submodule update --init` * Build the project using CMake (`mkdir build && cd build && cmake .. && cmake --build .`) or Conan (`conan build .`). +## Command Line Options + +``` +xsdcpp [] [options] +``` + +| Option | Description | +|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `-o `, `--output=` | The folder in which the output files are created. | +| `-H `, `--header-output=` | The folder in which the header files (.hpp) are created. Overrides `-o` for header files. | +| `-C `, `--cpp-output=` | The folder in which the implementation files (.cpp) are created. Overrides `-o` for implementation files. | +| `-n `, `--name=` | The namespace used for the generated data model and base name of the output files. The default is derived from the XSD filename. | +| `-w `, `--wrap-namespace=` | Wrap all generated code in an additional C++ namespace. Supports nested namespaces using `::` syntax (e.g., `a::b::c`). | +| `-P `, `--include-prefix=` | Prefix for `#include` directives in generated `.cpp` files. Use when headers are in a different directory structure than sources. | +| `-e `, `--extern=` | A namespace that should not be generated in the output files and hence must be provided separately. Use this to avoid code duplication if you have a schema that is the base for multiple other schemas. Set to `xsdcpp` to omit the generation of the core parser library. | +| `-t `, `--type=` | Enforce the generation of a type that is not directly referenced from a root element. Can be specified multiple times. | + +### Examples + +Basic usage: +``` +xsdcpp Example.xsd -o /your/output/folder +``` + +Separate header and implementation directories: +``` +xsdcpp Example.xsd -H include/ -C src/ +``` + +Wrap generated code in a namespace: +``` +xsdcpp Example.xsd -o out/ -w myproject::xml +``` +This generates types like `myproject::xml::Example::Person`. + +Rename the inner namespace and wrap in an outer namespace: +``` +xsdcpp Example.xsd -o out/ -w myproject::xml -n types +``` +This generates types like `myproject::xml::types::Person`. + ## Example An XSD schema *Example.xsd* like this: @@ -160,10 +201,12 @@ struct Country : xsd::base } ``` -And functions to load an XML file or XML data from a string: +And functions to load and save XML: ```cpp -void load_file(const std::string& file, List& List); -void load_data(const std::string& data, List& List); +void load_file(const std::string& file, List& list); +void load_data(const std::string& data, List& list); +void save_file(const std::string& file, const List& list); +std::string save_data(const List& list); ``` (The implementation of these functions can be found in *Example.cpp* and *Example_xsd.hpp* provides the types of the *xsd* namespace.) @@ -197,4 +240,4 @@ int main() return 0; } ``` -The generated functions will validate the input data to some degree and throw exceptions for missing or unknown elements or attributes etc.. \ No newline at end of file +The generated functions will validate the input data to some degree and throw exceptions for missing or unknown elements or attributes etc.. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c32a5b6..d40d5ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,8 +3,8 @@ add_subdirectory(ResourceCompiler) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Resources.hpp" - COMMAND ResourceCompiler "${CMAKE_CURRENT_SOURCE_DIR}/xsd.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlParser.hpp" -o "${CMAKE_CURRENT_BINARY_DIR}/Resources.hpp" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/xsd.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlParser.hpp" "$" + COMMAND ResourceCompiler "${CMAKE_CURRENT_SOURCE_DIR}/xsd.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlParser.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlWriter.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlWriter.cpp" -o "${CMAKE_CURRENT_BINARY_DIR}/Resources.hpp" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/xsd.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlParser.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlWriter.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/XmlWriter.cpp" "$" ) add_library(libxsdcpp STATIC diff --git a/src/Generator.cpp b/src/Generator.cpp index 439751a..1f7a2cd 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -11,6 +11,51 @@ namespace { +// Split a namespace string like "a::b::c" into individual parts +List splitNamespace(const String& ns) +{ + List parts; + if (ns.isEmpty()) + return parts; + + const char* base = (const char*)ns; + const char* start = base; + for (;;) + { + const char* pos = String::find(start, "::"); + if (!pos) + { + // Remaining part after last :: + parts.append(ns.substr(start - base)); + break; + } + // Part between start and pos + parts.append(ns.substr(start - base, pos - start)); + start = pos + 2; + } + return parts; +} + +// Generate opening namespace declarations for C++11 (one per line) +String generateNamespaceOpen(const String& ns) +{ + List parts = splitNamespace(ns); + String result; + for (List::Iterator i = parts.begin(), end = parts.end(); i != end; ++i) + result += String("namespace ") + *i + " {\n"; + return result; +} + +// Generate closing braces for namespace (one per line) +String generateNamespaceClose(const String& ns) +{ + List parts = splitNamespace(ns); + String result; + for (List::Iterator i = parts.begin(), end = parts.end(); i != end; ++i) + result += "}\n"; + return result; +} + HashSet loadCppKeywords() { HashSet keywords(100); @@ -126,11 +171,12 @@ bool compareXsName(const Xsd::Name& name, const String& rh) class Generator { public: - Generator(const Xsd& xsd, const List& externalNamespacePrefixes, const List& forceTypeProcessing, List& cppOutput, List& hppOutput) + Generator(const Xsd& xsd, const List& externalNamespacePrefixes, const List& forceTypeProcessing, List& cppOutput, List& hppOutput, const String& includePrefix) : _xsd(xsd) , _externalNamespacePrefixes(externalNamespacePrefixes) , _cppOutputFinal(cppOutput) , _hppOutput(hppOutput) + , _includePrefix(includePrefix) { for (List::Iterator i = forceTypeProcessing.begin(), end = forceTypeProcessing.end(); i != end; ++i) { @@ -168,7 +214,10 @@ class Generator return false; _cppOutputFinal.append(""); - _cppOutputFinal.append(String("#include \"") + _cppNamespace + ".hpp\""); + if (_includePrefix.isEmpty()) + _cppOutputFinal.append(String("#include \"") + _cppNamespace + ".hpp\""); + else + _cppOutputFinal.append(String("#include \"") + _includePrefix + "/" + _cppNamespace + ".hpp\""); _cppOutputFinal.append(""); for (HashMap>::Iterator i = externalTypes.begin(), end = externalTypes.end(); i != end; ++i) @@ -251,6 +300,8 @@ class Generator _hppOutput.append(String("void load_file(const std::string& file, ") + elementTypeCppName + "& " + elementCppName + ");"); _hppOutput.append(String("void load_data(const std::string& data, ") + elementTypeCppName + "& " + elementCppName + ");"); + _hppOutput.append(String("void save_file(const std::string& file, const ") + elementTypeCppName + "& " + elementCppName + ");"); + _hppOutput.append(String("std::string save_data(const ") + elementTypeCppName + "& " + elementCppName + ");"); _hppOutput.append(""); } @@ -281,6 +332,13 @@ class Generator _cppOutputFinal.append(""); _cppOutputFinal.append(_cppOutputAnonymousFieldGetter); _cppOutputFinal.append(""); + // Output forward declarations for serialize functions first + _cppOutputFinal.append("// Serialize function forward declarations"); + _cppOutputFinal.append(_cppOutputAnonymousSerializeDecl); + _cppOutputFinal.append(""); + // Then output implementations + _cppOutputFinal.append(_cppOutputAnonymousSerialize); + _cppOutputFinal.append(""); _cppOutputFinal.append("}"); _cppOutputFinal.append(""); @@ -309,6 +367,20 @@ class Generator _cppOutputFinal.append(" load_data(xsdcpp::read_file(filePath), output);"); _cppOutputFinal.append("}"); _cppOutputFinal.append(""); + + _cppOutputFinal.append(String("std::string save_data(const ") + elementTypeCppName + "& input)"); + _cppOutputFinal.append("{"); + _cppOutputFinal.append(" xsdcpp::XmlWriter w;"); + _cppOutputFinal.append(String(" _serialize_") + elementTypeCppName + "(w, \"" + i->name.name + "\", input);"); + _cppOutputFinal.append(" return w.str();"); + _cppOutputFinal.append("}"); + _cppOutputFinal.append(""); + + _cppOutputFinal.append(String("void save_file(const std::string& filePath, const ") + elementTypeCppName + "& input)"); + _cppOutputFinal.append("{"); + _cppOutputFinal.append(" xsdcpp::write_file(filePath, save_data(input));"); + _cppOutputFinal.append("}"); + _cppOutputFinal.append(""); } @@ -321,6 +393,7 @@ class Generator private: const Xsd& _xsd; const List& _externalNamespacePrefixes; + String _includePrefix; HashMap _externalNamespaces; @@ -329,12 +402,17 @@ class Generator List _cppOutputAnonymousEnumValues; List _cppOutputNamespaceSetValue; List _cppOutputAnonymousFieldGetter; + List _cppOutputAnonymousSerializeDecl; // Forward declarations + List _cppOutputAnonymousSerialize; // Implementations List _cppOutputNamespace; List& _hppOutput; String _cppNamespace; HashSet _generatedTypes2; HashSet _generatedElementInfos2; + HashSet _generatedElementInfoCppNames; // Track by C++ name to avoid duplicates for base types HashSet _generatedTypeSetters; + HashSet _generatedSerializeFunctions; + HashSet _generatedPrimitiveSerializers; HashSet _generatedAttributeSetDefaultValueFunctions; HashMap _generatedAttributeTrackBits; HashSet _requiredTypes; @@ -398,7 +476,7 @@ class Generator { String result = toCppTypeIdentifier2(typeName); if (result == "_root_t" || - result == "xsd::string" || + result == "xsd::string" || result == "uint64_t" || result == "int64_t" || result == "uint32_t" || @@ -435,10 +513,14 @@ class Generator cppName == "uint16_t" || cppName == "int16_t" || cppName == "double" || - cppName == "float" || - cppName == "bool") + cppName == "float") return String("xsdcpp::set_") + cppName; - return toCppNamespacePrefix(typeName) + "::_set_" + cppName; + if (cppName == "bool") + return String("xsdcpp::set_bool"); + String prefix = toCppNamespacePrefix(typeName); + if (prefix.isEmpty()) + return String("_set_") + cppName; + return prefix + "::_set_" + cppName; } usize getChildrenCount(const Xsd::Name& typeName) const @@ -557,14 +639,14 @@ class Generator ReadTextMode getReadTextMode(const Xsd::Name& typeName) const { + // xs:any content (SkipProcessContentsFlag) applies to all type kinds, + // not just strings. Check this before the kind-specific logic. + if (isSkipProcessContentsFlagSet(typeName) && getChildrenCount(typeName) == 0) + return SkipProcessingMode; Xsd::Type rootType = getType(getRootTypeName(typeName)); if (rootType.kind == Xsd::Type::Kind::StringKind) - { - if (isSkipProcessContentsFlagSet(typeName) && getChildrenCount(typeName) == 0) - return SkipProcessingMode; return ReadAndProcessTextMode; - } - if (rootType.kind == Xsd::Type::Kind::BaseKind || rootType.kind == Xsd::Type::Kind::EnumKind || rootType.kind == Xsd::Type::Kind::ListKind) + if (rootType.kind == Xsd::Type::Kind::BaseKind || rootType.kind == Xsd::Type::Kind::EnumKind || rootType.kind == Xsd::Type::Kind::ListKind || rootType.kind == Xsd::Type::Kind::UnionKind) return ReadAndProcessTextMode; return SkipMode; } @@ -836,7 +918,21 @@ class Generator } flags.append("xsdcpp::ElementInfo::ReadTextFlag"); if (readTextMode == SkipProcessingMode) + { flags.append("xsdcpp::ElementInfo::SkipProcessingFlag"); + // For complex types with xs:any, there is no field to write text into. + // The content is skipped; addText is nullptr (guarded in XmlParser). + Xsd::Type rootType2 = getType(getRootTypeName(typeName)); + if (rootType2.kind != Xsd::Type::Kind::StringKind && + rootType2.kind != Xsd::Type::Kind::BaseKind && + rootType2.kind != Xsd::Type::Kind::EnumKind && + rootType2.kind != Xsd::Type::Kind::ListKind && + rootType2.kind != Xsd::Type::Kind::UnionKind) + { + addTextFunction = "nullptr"; + return true; + } + } if (!generateTypeSetter(typeName)) return false; @@ -862,7 +958,17 @@ class Generator _generatedElementInfos2.append(typeName); return true; } - + + String cppName = toCppTypeIdentifier2(typeName); + + // Check if ElementInfo with this C++ name was already generated + // (multiple XSD types like nonNegativeInteger, positiveInteger, unsignedLong map to uint64_t) + if (_generatedElementInfoCppNames.contains(cppName)) + { + _generatedElementInfos2.append(typeName); + return true; + } + List flags; String addTextFunction; @@ -873,10 +979,10 @@ class Generator if (!flags.isEmpty()) flagsStr.join(flags, '|'); - String cppName = toCppTypeIdentifier2(typeName); _cppOutputNamespaceSetValue.append(String("const xsdcpp::ElementInfo _") + cppName + "_Info = { " + flagsStr + ", " + addTextFunction + " };"); _generatedElementInfos2.append(typeName); + _generatedElementInfoCppNames.append(cppName); return true; } @@ -1005,8 +1111,23 @@ class Generator String cppName = toCppTypeIdentifier2(typeName); _hppOutput.append(String("enum class ") + cppName); _hppOutput.append("{"); + HashSet usedEnumValues; for (List::Iterator i = type.enumEntries.begin(), end = type.enumEntries.end(); i != end; ++i) - _hppOutput.append(String(" ") + toCppIdentifier(*i) + ","); + { + String enumValue = toCppIdentifier(*i); + // Handle duplicate enum values by appending a counter + if (usedEnumValues.contains(enumValue)) + { + int counter = 2; + String uniqueValue; + do { + uniqueValue = enumValue + "_" + String::fromInt(counter++); + } while (usedEnumValues.contains(uniqueValue)); + enumValue = uniqueValue; + } + usedEnumValues.append(enumValue); + _hppOutput.append(String(" ") + enumValue + ","); + } _hppOutput.append("};"); _hppOutput.append(""); _hppOutput.append(String("std::string to_string(") + cppName + ");"); @@ -1076,10 +1197,12 @@ class Generator if (optionalWithoutDefaultValue) structFields.append(String("xsd::optional<") + toCppTypeIdentifierWithNamespace2(attributeRef.typeName) + "> " + toCppFieldIdentifier(attributeRef.name)); else - structFields.append(toCppTypeIdentifierWithNamespace2(attributeRef.typeName) + " " + toCppFieldIdentifier(attributeRef.name)); + structFields.append(toCppTypeIdentifierWithNamespace2(attributeRef.typeName) + " " + toCppFieldIdentifier(attributeRef.name) + "{}"); } if (type.flags & Xsd::Type::AnyAttributeFlag) structFields.append("xsd::vector other_attributes"); + if (type.flags & Xsd::Type::AnyElementFlag) + structFields.append("xsd::vector other_elements"); for (List::Iterator i = type.elements.begin(), end = type.elements.end(); i != end; ++i) { const Xsd::ElementRef& elementRef = *i; @@ -1087,7 +1210,7 @@ class Generator if (!processType2(elementRef.typeName, level + 1, typeDefinitionRequired)) return false; if (elementRef.minOccurs == 1 && elementRef.maxOccurs == 1) - structFields.append(toCppTypeIdentifierWithNamespace2(elementRef.typeName) + " " + toCppFieldIdentifier(elementRef.name)); + structFields.append(toCppTypeIdentifierWithNamespace2(elementRef.typeName) + " " + toCppFieldIdentifier(elementRef.name) + "{}"); else if (elementRef.maxOccurs == 1) structFields.append(String("xsd::optional<") + toCppTypeIdentifierWithNamespace2(elementRef.typeName) + "> " + toCppFieldIdentifier(elementRef.name)); else @@ -1097,7 +1220,10 @@ class Generator List structDefintiion; if (baseType) { - if (baseType->kind == Xsd::Type::BaseKind || baseType->kind == Xsd::Type::EnumKind) + // Use xsd::base<> wrapper for primitive-derived types (BaseKind, EnumKind, SimpleRefKind) + // These are typedefs that can't be directly inherited in C++ + if (baseType->kind == Xsd::Type::BaseKind || baseType->kind == Xsd::Type::EnumKind || + baseType->kind == Xsd::Type::SimpleRefKind) structDefintiion.append(String("struct ") + cppName + " : xsd::base<" + toCppTypeIdentifierWithNamespace2(type.baseType) + ">"); else structDefintiion.append(String("struct ") + cppName + " : " + toCppTypeIdentifierWithNamespace2(type.baseType)); @@ -1223,6 +1349,8 @@ class Generator } if (type.flags & Xsd::Type::AnyAttributeFlag) _cppOutputAnonymousFieldGetter.append(String("void _any_") + cppName + "(" + toCppTypeIdentifierWithNamespace2(typeName) + "* element, std::string&& name, std::string&& value) { element->other_attributes.emplace_back(xsd::any_attribute{std::move(name), std::move(value)}); }"); + if (type.flags & Xsd::Type::AnyElementFlag) + _cppOutputAnonymousFieldGetter.append(String("void _any_elem_") + cppName + "(" + toCppTypeIdentifierWithNamespace2(typeName) + "* element, std::string&& name, std::string&& value) { element->other_elements.emplace_back(xsd::any_element{std::move(name), std::move(value)}); }"); String attributes("nullptr"); if (!type.attributes.isEmpty()) @@ -1257,6 +1385,8 @@ class Generator flags.append("xsdcpp::ElementInfo::EntryPointFlag"); if (type.flags & Xsd::Type::AnyAttributeFlag) flags.append("xsdcpp::ElementInfo::AnyAttributeFlag"); + if (type.flags & Xsd::Type::AnyElementFlag) + flags.append("xsdcpp::ElementInfo::AnyElementFlag"); if (mandatoryChildrenCount) flags.append("xsdcpp::ElementInfo::CheckChildrenFlag"); @@ -1272,48 +1402,427 @@ class Generator if (!flags.isEmpty()) flagsStr.join(flags, '|'); - _cppOutputNamespace.append(String("const xsdcpp::ElementInfo _") + cppName + "_Info = { " + flagsStr + _cppOutputNamespace.append(String("const xsdcpp::ElementInfo _") + cppName + "_Info = { " + flagsStr + ", " + addTextFunction + ", " + children + ", " + String::fromUInt64(childrenCount) + ", " + attributes + ", " + String::fromUInt64(checkAttributesMask) + "ULL" - + ", " + (parentElementCppName.isEmpty() ? String("nullptr") : String("&") + toCppNamespacePrefix(type.baseType) + "::_" + parentElementCppName + "_Info") + + ", " + (parentElementCppName.isEmpty() ? String("nullptr") : String("&") + toCppNamespacePrefix(type.baseType) + "::_" + parentElementCppName + "_Info") + ", " + (type.flags & Xsd::Type::AnyAttributeFlag ? String("(xsdcpp::set_any_attribute_t)&_any_") + cppName : String("nullptr")) + + ", " + (type.flags & Xsd::Type::AnyElementFlag ? String("(xsdcpp::set_any_element_t)&_any_elem_") + cppName : String("nullptr")) + " };"); _generatedElementInfos2.append(typeName); + // Generate serialize function for this element type + if (!generateSerializeFunction(typeName)) + return false; + return true; } return false; // logic error } + + bool generateSerializeFunction(const Xsd::Name& typeName) + { + if (_generatedSerializeFunctions.contains(typeName)) + return true; + + // Skip the root type - it's internal only + if (typeName == _xsd.rootType) + return true; + + HashMap::Iterator it = _xsd.types.find(typeName); + if (it == _xsd.types.end()) + return _error = String::fromPrintf("Type '%s' not found for serialization", (const char*)typeName.name), false; + + _generatedSerializeFunctions.append(typeName); + + const Xsd::Type& type = *it; + String cppName = toCppTypeIdentifier2(typeName); + String cppNameWithNamespace = toCppTypeIdentifierWithNamespace2(typeName); + + // Generate serialize functions for primitive types (track by cppName to avoid duplicates) + if (cppName == "xsd::string") + { + if (!_generatedPrimitiveSerializers.contains(cppName)) + { + _generatedPrimitiveSerializers.append(cppName); + _cppOutputAnonymousSerializeDecl.append("void _serialize_xsd__string(xsdcpp::XmlWriter& w, const char* name, const xsd::string& v);"); + _cppOutputAnonymousSerialize.append(String("XSDCPP_MAYBE_UNUSED void _serialize_xsd__string(xsdcpp::XmlWriter& w, const char* name, const xsd::string& v) {")); + _cppOutputAnonymousSerialize.append(" w.startElement(name);"); + _cppOutputAnonymousSerialize.append(" w.writeText(v);"); + _cppOutputAnonymousSerialize.append(" w.endElement(name);"); + _cppOutputAnonymousSerialize.append("}"); + _cppOutputAnonymousSerialize.append(""); + } + return true; + } + if (cppName == "uint64_t" || cppName == "int64_t" || + cppName == "uint32_t" || cppName == "int32_t" || cppName == "uint16_t" || + cppName == "int16_t" || cppName == "double" || cppName == "float" || cppName == "bool") + { + if (!_generatedPrimitiveSerializers.contains(cppName)) + { + _generatedPrimitiveSerializers.append(cppName); + _cppOutputAnonymousSerializeDecl.append(String("void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, " + cppName + " v);"); + _cppOutputAnonymousSerialize.append(String("XSDCPP_MAYBE_UNUSED void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, " + cppName + " v) {"); + _cppOutputAnonymousSerialize.append(" w.startElement(name);"); + _cppOutputAnonymousSerialize.append(String(" w.writeText(xsdcpp::get_string(v));")); + _cppOutputAnonymousSerialize.append(" w.endElement(name);"); + _cppOutputAnonymousSerialize.append("}"); + _cppOutputAnonymousSerialize.append(""); + } + return true; + } + + if (type.kind == Xsd::Type::SubstitutionGroupKind) + { + // Add forward declaration + _cppOutputAnonymousSerializeDecl.append(String("void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char*, const " + cppNameWithNamespace + "& v);"); + + // First generate serialize functions for child types (before opening parent function) + for (List::Iterator i = type.elements.begin(), end = type.elements.end(); i != end; ++i) + { + if (!generateSerializeFunction(i->typeName)) + return false; + } + + // Then generate serialize for choice type - serialize whichever option is present + _cppOutputAnonymousSerialize.append(String("XSDCPP_MAYBE_UNUSED void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char*, const " + cppNameWithNamespace + "& v) {"); + bool first = true; + for (List::Iterator i = type.elements.begin(), end = type.elements.end(); i != end; ++i) + { + const Xsd::ElementRef& elementRef = *i; + String prefix = first ? String(" if") : String(" else if"); + first = false; + _cppOutputAnonymousSerialize.append(prefix + " ((&v)->" + toCppFieldIdentifier(elementRef.name) + ") _serialize_" + toCppTypeIdentifier2(elementRef.typeName) + "(w, \"" + elementRef.name.name + "\", *(&v)->" + toCppFieldIdentifier(elementRef.name) + ");"); + } + _cppOutputAnonymousSerialize.append("}"); + _cppOutputAnonymousSerialize.append(""); + return true; + } + + if (type.kind == Xsd::Type::EnumKind) + { + // Forward declaration and implementation for enum + _cppOutputAnonymousSerializeDecl.append(String("void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v);"); + _cppOutputAnonymousSerialize.append(String("XSDCPP_MAYBE_UNUSED void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v) {"); + _cppOutputAnonymousSerialize.append(" w.startElement(name);"); + _cppOutputAnonymousSerialize.append(String(" w.writeText(") + toCppNamespacePrefix(typeName) + "::to_string(v));"); + _cppOutputAnonymousSerialize.append(" w.endElement(name);"); + _cppOutputAnonymousSerialize.append("}"); + _cppOutputAnonymousSerialize.append(""); + return true; + } + + if (type.kind == Xsd::Type::StringKind || type.kind == Xsd::Type::UnionKind) + { + // Forward declaration and implementation for string types + _cppOutputAnonymousSerializeDecl.append(String("void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v);"); + _cppOutputAnonymousSerialize.append(String("XSDCPP_MAYBE_UNUSED void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v) {"); + _cppOutputAnonymousSerialize.append(" w.startElement(name);"); + _cppOutputAnonymousSerialize.append(" w.writeText(v);"); + _cppOutputAnonymousSerialize.append(" w.endElement(name);"); + _cppOutputAnonymousSerialize.append("}"); + _cppOutputAnonymousSerialize.append(""); + return true; + } + + if (type.kind == Xsd::Type::ListKind) + { + // Forward declaration and implementation for list types + _cppOutputAnonymousSerializeDecl.append(String("void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v);"); + String itemCppName = toCppTypeIdentifier2(type.baseType); + Xsd::Type itemType = getType(type.baseType); + String itemSerializer = (itemType.kind == Xsd::Type::EnumKind) + ? String("to_string(v[i])") + : String("xsdcpp::get_string(v[i])"); + _cppOutputAnonymousSerialize.append(String("XSDCPP_MAYBE_UNUSED void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v) {"); + _cppOutputAnonymousSerialize.append(" w.startElement(name);"); + _cppOutputAnonymousSerialize.append(" std::string text;"); + _cppOutputAnonymousSerialize.append(" for (size_t i = 0; i < v.size(); ++i) {"); + _cppOutputAnonymousSerialize.append(" if (i > 0) text += ' ';"); + _cppOutputAnonymousSerialize.append(String(" text += ") + itemSerializer + ";"); + _cppOutputAnonymousSerialize.append(" }"); + _cppOutputAnonymousSerialize.append(" w.writeText(text);"); + _cppOutputAnonymousSerialize.append(" w.endElement(name);"); + _cppOutputAnonymousSerialize.append("}"); + _cppOutputAnonymousSerialize.append(""); + return true; + } + + if (type.kind == Xsd::Type::SimpleRefKind) + { + // Forward declaration + _cppOutputAnonymousSerializeDecl.append(String("void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v);"); + // SimpleRef is a typedef - delegate to base type + if (!generateSerializeFunction(type.baseType)) + return false; + String baseCppName = toCppTypeIdentifier2(type.baseType); + _cppOutputAnonymousSerialize.append(String("XSDCPP_MAYBE_UNUSED void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v) {"); + _cppOutputAnonymousSerialize.append(String(" _serialize_") + baseCppName + "(w, name, v);"); + _cppOutputAnonymousSerialize.append("}"); + _cppOutputAnonymousSerialize.append(""); + return true; + } + + if (type.kind == Xsd::Type::ElementKind) + { + // Add forward declaration first + _cppOutputAnonymousSerializeDecl.append(String("void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v);"); + + // Generate serialize for base type first if it exists + if (!type.baseType.name.isEmpty()) + { + if (!generateSerializeFunction(type.baseType)) + return false; + } + + // Generate serialize for child element types + for (List::Iterator i = type.elements.begin(), end = type.elements.end(); i != end; ++i) + { + if (!generateSerializeFunction(i->typeName)) + return false; + } + + _cppOutputAnonymousSerialize.append(String("XSDCPP_MAYBE_UNUSED void _serialize_") + cppName + "(xsdcpp::XmlWriter& w, const char* name, const " + cppNameWithNamespace + "& v) {"); + _cppOutputAnonymousSerialize.append(" w.startElement(name);"); + + // Serialize attributes (from this type and inherited) + generateSerializeAttributes(typeName); + + // Serialize child elements (from this type and inherited) + generateSerializeChildren(typeName); + + // Check if this type has simple content (inherits from simple type) + Xsd::Name simpleBaseTypeName = getSimpleBaseTypeName(typeName); + if (!simpleBaseTypeName.name.isEmpty()) + { + Xsd::Type simpleBaseType = getType(simpleBaseTypeName); + if (simpleBaseType.kind == Xsd::Type::EnumKind) + { + // Write enum value as text + _cppOutputAnonymousSerialize.append(String(" w.writeText(to_string(static_cast<") + toCppTypeIdentifierWithNamespace2(simpleBaseTypeName) + ">(v)));"); + } + else if (simpleBaseType.kind == Xsd::Type::StringKind) + { + // Write string value as text + _cppOutputAnonymousSerialize.append(String(" w.writeText(static_cast(v));")); + } + else if (simpleBaseType.kind == Xsd::Type::BaseKind) + { + // Write base type value as text + String baseCppType = toCppTypeIdentifier2(simpleBaseTypeName); + _cppOutputAnonymousSerialize.append(String(" w.writeText(xsdcpp::get_string(static_cast<") + baseCppType + ">(v)));"); + } + } + + _cppOutputAnonymousSerialize.append(" w.endElement(name);"); + _cppOutputAnonymousSerialize.append("}"); + _cppOutputAnonymousSerialize.append(""); + return true; + } + + return true; + } + + void generateSerializeAttributes(const Xsd::Name& typeName) + { + if (typeName.name.isEmpty()) + return; + + HashMap::Iterator it = _xsd.types.find(typeName); + if (it == _xsd.types.end()) + return; + + const Xsd::Type& type = *it; + + // First serialize inherited attributes + if (!type.baseType.name.isEmpty() && type.kind == Xsd::Type::ElementKind) + { + Xsd::Type baseType = getType(type.baseType); + if (baseType.kind == Xsd::Type::ElementKind) + generateSerializeAttributes(type.baseType); + } + + // Then serialize this type's attributes + for (List::Iterator i = type.attributes.begin(), end = type.attributes.end(); i != end; ++i) + { + const Xsd::AttributeRef& attrRef = *i; + String fieldName = toCppFieldIdentifier(attrRef.name); + bool optionalWithoutDefaultValue = !attrRef.isMandatory && attrRef.defaultValue.isNull(); + + if (optionalWithoutDefaultValue) + { + // Optional attribute - only write if present (use (&v)->field to avoid ambiguity) + _cppOutputAnonymousSerialize.append(String(" if ((&v)->") + fieldName + ") w.writeAttribute(\"" + attrRef.name.name + "\", " + toGetStringCall(attrRef.typeName, String("*(&v)->") + fieldName) + ");"); + } + else + { + // Mandatory or has default value - always write + _cppOutputAnonymousSerialize.append(String(" w.writeAttribute(\"") + attrRef.name.name + "\", " + toGetStringCall(attrRef.typeName, String("(&v)->") + fieldName) + ");"); + } + } + + // Handle any_attribute + if (type.flags & Xsd::Type::AnyAttributeFlag) + { + _cppOutputAnonymousSerialize.append(" for (const auto& attr : (&v)->other_attributes) w.writeAttribute(attr.name.c_str(), attr.value);"); + } + } + + void generateSerializeChildren(const Xsd::Name& typeName, const Xsd::Name& ownerTypeName = Xsd::Name()) + { + if (typeName.name.isEmpty()) + return; + + HashMap::Iterator it = _xsd.types.find(typeName); + if (it == _xsd.types.end()) + return; + + const Xsd::Type& type = *it; + + // Use the owner type for field access, or the current type if we're the owner + const Xsd::Name& effectiveOwner = ownerTypeName.name.isEmpty() ? typeName : ownerTypeName; + + // When accessing fields, use cast to the type that defines them to avoid name lookup issues + String fieldAccess; + if (typeName == effectiveOwner) + fieldAccess = "v."; + else + fieldAccess = String("static_cast(v)."; + + // First serialize inherited children + if (!type.baseType.name.isEmpty() && type.kind == Xsd::Type::ElementKind) + { + Xsd::Type baseType = getType(type.baseType); + if (baseType.kind == Xsd::Type::ElementKind) + generateSerializeChildren(type.baseType, effectiveOwner); + } + + // Then serialize this type's children + for (List::Iterator i = type.elements.begin(), end = type.elements.end(); i != end; ++i) + { + const Xsd::ElementRef& elemRef = *i; + String fieldName = toCppFieldIdentifier(elemRef.name); + String elemTypeCppName = toCppTypeIdentifier2(elemRef.typeName); + + const Xsd::Type& elemType = *_xsd.types.find(elemRef.typeName); + + if (elemRef.minOccurs == 1 && elemRef.maxOccurs == 1) + { + // Mandatory single element + _cppOutputAnonymousSerialize.append(String(" _serialize_") + elemTypeCppName + "(w, \"" + elemRef.name.name + "\", " + fieldAccess + fieldName + ");"); + } + else if (elemRef.maxOccurs == 1) + { + // Optional single element + _cppOutputAnonymousSerialize.append(String(" if (") + fieldAccess + fieldName + ") _serialize_" + elemTypeCppName + "(w, \"" + elemRef.name.name + "\", *" + fieldAccess + fieldName + ");"); + } + else + { + // Vector of elements + _cppOutputAnonymousSerialize.append(String(" for (const auto& item : ") + fieldAccess + fieldName + ") _serialize_" + elemTypeCppName + "(w, \"" + elemRef.name.name + "\", item);"); + } + } + + // Serialize xs:any elements captured in other_elements + if (type.flags & Xsd::Type::AnyElementFlag) + _cppOutputAnonymousSerialize.append(String(" for (const auto& e : ") + fieldAccess + "other_elements) { w.startElement(e.name.c_str()); w.writeText(e.value); w.endElement(e.name.c_str()); }"); + } + + String toGetStringCall(const Xsd::Name& typeName, const String& value) + { + String cppName = toCppTypeIdentifier2(typeName); + + // For primitive types, use xsdcpp::get_string + if (cppName == "xsd::string") + return value; + if (cppName == "uint64_t" || cppName == "int64_t" || cppName == "uint32_t" || + cppName == "int32_t" || cppName == "uint16_t" || cppName == "int16_t" || + cppName == "double" || cppName == "float" || cppName == "bool") + return String("xsdcpp::get_string(") + value + ")"; + + // For enum types, use to_string + HashMap::Iterator it = _xsd.types.find(typeName); + if (it != _xsd.types.end()) + { + const Xsd::Type& type = *it; + if (type.kind == Xsd::Type::EnumKind) + return String("to_string(") + value + ")"; + if (type.kind == Xsd::Type::StringKind || type.kind == Xsd::Type::UnionKind) + return value; + if (type.kind == Xsd::Type::SimpleRefKind) + return toGetStringCall(type.baseType, value); + if (type.kind == Xsd::Type::ListKind) + { + // For list types, check if items are enums + Xsd::Type itemType = getType(type.baseType); + if (itemType.kind == Xsd::Type::EnumKind) + { + // For list of enums, use to_string - generate inline lambda + return String("[&]() { std::string r; for (size_t i = 0; i < ") + value + ".size(); ++i) { if (i > 0) r += ' '; r += " + toCppNamespacePrefix(type.baseType) + "::to_string(" + value + "[i]); } return r; }()"; + } + return String("xsdcpp::_serialize_list(") + value + ")"; + } + // For element kind that wraps a simple type (xsd:base<>) + if (type.kind == Xsd::Type::ElementKind && !type.baseType.name.isEmpty()) + { + Xsd::Name simpleBase = getSimpleBaseTypeName(typeName); + if (!simpleBase.name.isEmpty()) + return toGetStringCall(simpleBase, String("static_cast<") + toCppTypeIdentifierWithNamespace2(simpleBase) + ">(" + value + ")"); + } + } + + return String("xsdcpp::get_string(") + value + ")"; + } }; } -bool generateCpp(const Xsd& xsd, const String& outputDir, const List& excludedNamespacePrefixes, const List& forceTypeProcessing, String& error) +bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cppOutputDir, const List& excludedNamespacePrefixes, const List& forceTypeProcessing, const String& wrapNamespace, const String& includePrefix, String& error) { List cppOutput; List hppOutput; - Generator generator(xsd, excludedNamespacePrefixes, forceTypeProcessing, cppOutput, hppOutput); + Generator generator(xsd, excludedNamespacePrefixes, forceTypeProcessing, cppOutput, hppOutput, includePrefix); if (!generator.process()) return (error = generator.getError()), false; String cppName = toCppIdentifier(xsd.name); + String nsOpen = generateNamespaceOpen(wrapNamespace); + String nsClose = generateNamespaceClose(wrapNamespace); + // Write header file (.hpp) { - String outputFilePath = outputDir + "/" + cppName + ".hpp"; + String outputFilePath = headerOutputDir + "/" + cppName + ".hpp"; File outputFile; if (!outputFile.open(outputFilePath, File::writeFlag)) return (error = String::fromPrintf("Could not open file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; + bool wroteNsOpen = wrapNamespace.isEmpty(); for (List::Iterator i = hppOutput.begin(), end = hppOutput.end(); i != end; ++i) + { + // Insert wrap namespace before the first content (namespace, struct, enum, etc.) + // but after #pragma, #include, and empty lines + if (!wroteNsOpen && !i->isEmpty() && !i->startsWith("#")) + { + if (!outputFile.write(nsOpen)) + return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; + wroteNsOpen = true; + } if (!outputFile.write(*i + "\n")) return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; + } + // Close wrap namespace at end + if (!wrapNamespace.isEmpty()) + if (!outputFile.write(nsClose)) + return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; } + // Write implementation file (.cpp) { - String outputFilePath = outputDir + "/" + cppName + ".cpp"; + String outputFilePath = cppOutputDir + "/" + cppName + ".cpp"; File outputFile; if (!outputFile.open(outputFilePath, File::writeFlag)) return (error = String::fromPrintf("Could not open file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; @@ -1323,14 +1832,42 @@ bool generateCpp(const Xsd& xsd, const String& outputDir, const List& ex if (excludedNamespacePrefixes.isEmpty()) if (!outputFile.write(XmlParser_cpp)) return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; + if (!outputFile.write(XmlWriter_hpp)) + return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; + if (excludedNamespacePrefixes.isEmpty()) + if (!outputFile.write(XmlWriter_cpp)) + return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; + + // Write content, inserting wrap namespace at the appropriate point + // The content structure is: + // 1. #include line and external namespace declarations - OUTSIDE wrap + // 2. anonymous/named namespace blocks with actual code - INSIDE wrap + bool wroteNsOpen = wrapNamespace.isEmpty(); + String ourNamespaceStart = String("namespace ") + cppName + " {"; + String anonNamespaceStart = "namespace {"; for (List::Iterator i = cppOutput.begin(), end = cppOutput.end(); i != end; ++i) + { + // Insert wrap namespace before first anonymous or our namespace declaration + if (!wroteNsOpen && (*i == anonNamespaceStart || *i == ourNamespaceStart)) + { + if (!outputFile.write(nsOpen)) + return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; + wroteNsOpen = true; + } if (!outputFile.write(*i + "\n")) return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; + } + + // Write wrap namespace close + if (!wrapNamespace.isEmpty()) + if (!outputFile.write(nsClose)) + return (error = String::fromPrintf("Could not write to file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; } + // Write xsd header file (_xsd.hpp) { - String outputFilePath = outputDir + "/" + cppName + "_xsd.hpp"; + String outputFilePath = headerOutputDir + "/" + cppName + "_xsd.hpp"; File outputFile; if (!outputFile.open(outputFilePath, File::writeFlag)) return (error = String::fromPrintf("Could not open file '%s': %s", (const char*)outputFilePath, (const char*)Error::getErrorString())), false; diff --git a/src/Generator.hpp b/src/Generator.hpp index a73bf5b..9e41fdc 100644 --- a/src/Generator.hpp +++ b/src/Generator.hpp @@ -3,4 +3,4 @@ #include "Reader.hpp" -bool generateCpp(const Xsd& xsd, const String& outputDir, const List& externalNamespacePrefixes, const List& forceTypeProcessing, String& error); +bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cppOutputDir, const List& externalNamespacePrefixes, const List& forceTypeProcessing, const String& wrapNamespace, const String& includePrefix, String& error); diff --git a/src/Main.cpp b/src/Main.cpp index 6ca8ace..24d68ef 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -18,6 +18,20 @@ Options:\n\ \n\ -o , --output=\n\ The folder in which the output files are created.\n\ +\n\ + -H , --header-output=\n\ + The folder in which the header files (.hpp) are created. Overrides -o\n\ + for header files.\n\ +\n\ + -C , --cpp-output=\n\ + The folder in which the implementation files (.cpp) are created.\n\ + Overrides -o for implementation files.\n\ +\n\ + -P , --include-prefix=\n\ + The prefix to use for #include directives in generated .cpp files.\n\ + Use this when headers are in a different directory structure than\n\ + sources. For example, if headers are in include/mylib/ and the\n\ + include path is include/, use -P mylib.\n\ \n\ -e , --extern=\n\ A namespace that should not be generated in the output files and hence\n\ @@ -28,8 +42,14 @@ Options:\n\ generated data models to the same library or executable.\n\ \n\ -n , --name=\n\ - The namespace used for the generated data model and base name of the\n\ - output files. The default, is derived from .\n\ + The namespace used for the generated data model (inner namespace) and\n\ + base name of the output files. The default is derived from .\n\ + Use this to rename the inner namespace when combined with -w.\n\ +\n\ + -w , --wrap-namespace=\n\ + Wrap all generated code in an additional C++ namespace. Supports nested\n\ + namespaces using '::' syntax (e.g., 'a::b::c'). Combined with -n, this\n\ + allows full control over the namespace structure.\n\ \n\ -t , --type=\n\ By default, C++ type definitions are only generated for types that are\n\ @@ -45,13 +65,21 @@ int main(int argc, char* argv[]) { String inputFile; String outputDir = "."; + String headerOutputDir; + String cppOutputDir; String name; + String wrapNamespace; + String includePrefix; List externalNamespacePrefixes; List forceTypeProcessing; { Process::Option options[] = { {'o', "output", Process::argumentFlag}, + {'H', "header-output", Process::argumentFlag}, + {'C', "cpp-output", Process::argumentFlag}, + {'P', "include-prefix", Process::argumentFlag}, {'n', "name", Process::argumentFlag}, + {'w', "wrap-namespace", Process::argumentFlag}, {'h', "help", Process::optionFlag}, {'e', "extern", Process::argumentFlag}, {'t', "type", Process::argumentFlag}, @@ -66,9 +94,21 @@ int main(int argc, char* argv[]) case 'o': outputDir = argument; break; + case 'H': + headerOutputDir = argument; + break; + case 'C': + cppOutputDir = argument; + break; + case 'P': + includePrefix = argument; + break; case 'n': name = argument; break; + case 'w': + wrapNamespace = argument; + break; case 'e': externalNamespacePrefixes.append(argument); break; @@ -89,6 +129,11 @@ int main(int argc, char* argv[]) return 1; } } + // Use outputDir as default if specific directories not set + if (headerOutputDir.isEmpty()) + headerOutputDir = outputDir; + if (cppOutputDir.isEmpty()) + cppOutputDir = outputDir; if (inputFile.isEmpty()) { usage(argv[0]); @@ -98,7 +143,7 @@ int main(int argc, char* argv[]) String error; Xsd xsd; if (!readXsd(name, inputFile, forceTypeProcessing, xsd, error) || - !generateCpp(xsd, outputDir, externalNamespacePrefixes, forceTypeProcessing, error)) + !generateCpp(xsd, headerOutputDir, cppOutputDir, externalNamespacePrefixes, forceTypeProcessing, wrapNamespace, includePrefix, error)) { Console::errorf("error: %s\n", (const char*)error); return 1; diff --git a/src/Reader.cpp b/src/Reader.cpp index 654de8b..bae0bfb 100644 --- a/src/Reader.cpp +++ b/src/Reader.cpp @@ -32,6 +32,16 @@ String getXmlAttribute(const Xml::Element& element, const String& name, const St return *it; } +// Special constant for unbounded occurrences +const uint UNBOUNDED = 0xFFFFFFFF; + +uint parseOccurs(const String& value) +{ + if (value == "unbounded") + return UNBOUNDED; + return value.toUInt(); +} + Variant getXmlAttributeVariant(const Xml::Element& element, const String& name, const Variant& defaultValue = Variant()) { @@ -130,6 +140,13 @@ class Reader HashMap _namespaces; String _error; + // Storage for xs:group definitions + struct GroupDef + { + Position position; + }; + HashMap _groups; + private: void resolveElementRefs() @@ -138,20 +155,26 @@ class Reader { Xsd::Type& type = *i; - for (List::Iterator i = type.elements.begin(), end = type.elements.end(); i != end; ++i) + for (List::Iterator i = type.elements.begin(); i != type.elements.end();) { Xsd::ElementRef& elementRef = *i; if (elementRef.refName.name.isEmpty()) + { + ++i; continue; + } Xsd::Name substitutionGroupTypeName = elementRef.refName; substitutionGroupTypeName.name.append("_group_t"); if (_output.types.contains(substitutionGroupTypeName)) + { elementRef.typeName = substitutionGroupTypeName; + ++i; + } else - elementRef.refName = Xsd::Name(); + i = type.elements.remove(i); } } } @@ -391,6 +414,14 @@ class Reader return findXmlElementByNamespaceAndXmlType(position, "http://www.w3.org/2001/XMLSchema", type); } + Position findGroupByName(const Xsd::Name& name) + { + HashMap::Iterator it = _groups.find(name); + if (it != _groups.end()) + return it->position; + return Position(); + } + bool resolveNamespacePrefix(const Position& position, const String& typeNameWithNamespacePrefix, Xsd::Name& result) { const char* n = typeNameWithNamespacePrefix.find(':'); @@ -432,11 +463,23 @@ class Reader if (!refPos) return (_error = String::fromPrintf("Could not find ref '%s'", (const char*)refName.name)), false; + // Abstract elements without a type attribute serve as placeholders for substitution groups. + // Record the reference for later resolution by resolveElementRefs(). + if (getXmlAttribute(*refPos.element, "abstract", "false").toBool() && + getXmlAttribute(*refPos.element, "type").isEmpty()) + { + elementRef.name = refName; + elementRef.minOccurs = getXmlAttribute(*position.element, "minOccurs", "1").toUInt(); + elementRef.maxOccurs = parseOccurs(getXmlAttribute(*position.element, "maxOccurs", "1")); + elementRef.refName = refName; + return true; + } + if (!processXsElement(refPos, Xsd::Name(), elementRef, atRoot)) return false; elementRef.minOccurs = getXmlAttribute(*position.element, "minOccurs", "1").toUInt(); - elementRef.maxOccurs = getXmlAttribute(*position.element, "maxOccurs", "1").toUInt(); + elementRef.maxOccurs = parseOccurs(getXmlAttribute(*position.element, "maxOccurs", "1")); elementRef.refName = refName; return true; } @@ -467,7 +510,7 @@ class Reader elementRef.name.name = getXmlAttribute(*position.element, "name"); elementRef.name.xsdNamespace = position.xsdFileData->targetNamespace; elementRef.minOccurs = getXmlAttribute(*position.element, "minOccurs", "1").toUInt(); - elementRef.maxOccurs = getXmlAttribute(*position.element, "maxOccurs", "1").toUInt(); + elementRef.maxOccurs = parseOccurs(getXmlAttribute(*position.element, "maxOccurs", "1")); // add the type to its substitution group String substitutionGroupWithNamespacePrefix = getXmlAttribute(*position.element, "substitutionGroup"); @@ -517,6 +560,7 @@ class Reader elementRef.typeName.name = parentTypeName.name + "_" + name + "_t"; elementRef.typeName.xsdNamespace = position.xsdFileData->targetNamespace; + bool foundTypeDefinition = false; for (List::Iterator i = position.element->content.begin(), end = position.element->content.end(); i != end; ++i) { const Xml::Variant& variant = *i; @@ -532,14 +576,61 @@ class Reader if (!processTypeElement(childPosition, elementRef.typeName)) return false; - elementRef.name.name = getXmlAttribute(*position.element, "name"); - elementRef.name.xsdNamespace = position.xsdFileData->targetNamespace; - elementRef.minOccurs = getXmlAttribute(*position.element, "minOccurs", "1").toUInt(); - elementRef.maxOccurs = getXmlAttribute(*position.element, "maxOccurs", "1").toUInt(); - return true; + foundTypeDefinition = true; + break; + } + } + + // Elements with no type attribute and no inline type definition default to xs:anyType. + // Map this to a string element type. + if (!foundTypeDefinition) + { + Xsd::Type& type = _output.types.append(elementRef.typeName, Xsd::Type()); + type.kind = Xsd::Type::ElementKind; + type.baseType.name = "string"; + type.baseType.xsdNamespace = "http://www.w3.org/2001/XMLSchema"; + } + + elementRef.name.name = getXmlAttribute(*position.element, "name"); + elementRef.name.xsdNamespace = position.xsdFileData->targetNamespace; + elementRef.minOccurs = getXmlAttribute(*position.element, "minOccurs", "1").toUInt(); + elementRef.maxOccurs = parseOccurs(getXmlAttribute(*position.element, "maxOccurs", "1")); + + // add the type to its substitution group + String substitutionGroupWithNamespacePrefix = getXmlAttribute(*position.element, "substitutionGroup"); + if (!substitutionGroupWithNamespacePrefix.isEmpty()) + { + Xsd::Name substitutionGroup; + if (!resolveNamespacePrefix(position, substitutionGroupWithNamespacePrefix, substitutionGroup)) + return false; + + Xsd::Name substitutionGroupTypeName = substitutionGroup; + substitutionGroupTypeName.name.append("_group_t"); + + Xsd::Type& type = _output.types.append(substitutionGroupTypeName, Xsd::Type(), false); + + bool addNewGroupMember = true; + for (List::Iterator i = type.elements.begin(), end = type.elements.end(); i != end; ++i) + { + const Xsd::ElementRef& elementRefInGroup = *i; + if (elementRefInGroup.name == elementRef.name) + { + addNewGroupMember = false; + break; + } + } + + if (addNewGroupMember) + { + type.kind = Xsd::Type::Kind::SubstitutionGroupKind; + Xsd::ElementRef& elementRefInGroup = type.elements.append(Xsd::ElementRef()); + elementRefInGroup.name = elementRef.name; + elementRefInGroup.typeName = elementRef.typeName; + elementRefInGroup.minOccurs = 0; } } - return (_error = String::fromPrintf("Could not find 'element', 'complexType' or 'simpleType' in '%s'", (const char*)position.element->type)), false; + + return true; } return (_error = String::fromPrintf("Missing element 'ref', 'type' or 'name' attribute in '%s'", (const char*)position.element->type)), false; @@ -603,6 +694,24 @@ class Reader if (!enumEntries.isEmpty()) { + // If a type with this name already exists as an enum (e.g. multiple + // anonymous attributes share the same generated name like "type_t"), + // merge the new values into the existing enum rather than overwriting. + HashMap::Iterator existingIt = _output.types.find(typeName); + if (existingIt != _output.types.end() && existingIt->kind == Xsd::Type::EnumKind) + { + for (List::Iterator ei = enumEntries.begin(), eend = enumEntries.end(); ei != eend; ++ei) + { + bool found = false; + for (List::Iterator ej = existingIt->enumEntries.begin(), ejend = existingIt->enumEntries.end(); ej != ejend; ++ej) + { + if (*ej == *ei) { found = true; break; } + } + if (!found) + existingIt->enumEntries.append(*ei); + } + return true; + } Xsd::Type& type = _output.types.append(typeName, Xsd::Type()); type.kind = Xsd::Type::EnumKind; type.enumEntries.swap(enumEntries); @@ -727,16 +836,100 @@ class Reader if (!choiceElements.isEmpty()) { - uint minOccurs = getXmlAttribute(element, "minOccurs", "1").toUInt(); - uint maxOccurs = getXmlAttribute(element, "maxOccurs", "1").toUInt(); + uint choiceMaxOccurs = parseOccurs(getXmlAttribute(element, "maxOccurs", "1")); for (List::Iterator i = choiceElements.begin(), end = choiceElements.end(); i != end; ++i) { const Xsd::ElementRef& choiceElement = *i; + // Skip if an element with the same name already exists (can happen with choice branches) + bool exists = false; + for (List::Iterator j = elements.begin(), jend = elements.end(); j != jend; ++j) + { + if (j->name.name == choiceElement.name.name) + { + exists = true; + break; + } + } + if (exists) + continue; Xsd::ElementRef& elementRef = elements.append(choiceElement); - //elementRef.minOccurs = minOccurs; // todo: skip this if min/max was not actually set in choiceElement? elementRef.minOccurs = 0; - elementRef.maxOccurs = maxOccurs; + // Take the maximum of choice's maxOccurs and element's maxOccurs + if (choiceMaxOccurs == UNBOUNDED || choiceElement.maxOccurs == UNBOUNDED) + elementRef.maxOccurs = UNBOUNDED; + else + elementRef.maxOccurs = choiceMaxOccurs > choiceElement.maxOccurs ? choiceMaxOccurs : choiceElement.maxOccurs; + } + } + } + else if (compareXsName(position, element.type, "group")) + { + // Handle group reference in direct complexType + String refWithNamespacePrefix = getXmlAttribute(element, "ref"); + if (!refWithNamespacePrefix.isEmpty()) + { + Xsd::Name refName; + if (!resolveNamespacePrefix(position, refWithNamespacePrefix, refName)) + return false; + + Position groupPos = findGroupByName(refName); + if (!groupPos) + return (_error = String::fromPrintf("Could not find group '%s'", (const char*)refName.name)), false; + + // Process the group's content + for (List::Iterator gi = groupPos.element->content.begin(), gend = groupPos.element->content.end(); gi != gend; ++gi) + { + const Xml::Variant& gvariant = *gi; + if (!gvariant.isElement()) + continue; + const Xml::Element& groupChild = gvariant.toElement(); + + Position groupChildPos; + groupChildPos.element = &groupChild; + groupChildPos.xsdFileData = groupPos.xsdFileData; + + if (compareXsName(groupPos, groupChild.type, "choice") || + compareXsName(groupPos, groupChild.type, "sequence") || + compareXsName(groupPos, groupChild.type, "all")) + { + List groupElements; + uint32 groupFlags; + if (!processXsAllEtAl(groupChildPos, typeName, groupElements, groupFlags)) + return false; + + uint groupRefMinOccurs = parseOccurs(getXmlAttribute(element, "minOccurs", "1")); + uint groupRefMaxOccurs = parseOccurs(getXmlAttribute(element, "maxOccurs", "1")); + + // Choice elements are mutually exclusive alternatives — each is + // individually optional regardless of the outer group ref's minOccurs. + bool groupIsChoice = compareXsName(groupPos, groupChild.type, "choice"); + + for (List::Iterator gei = groupElements.begin(), geend = groupElements.end(); gei != geend; ++gei) + { + Xsd::ElementRef& groupElem = *gei; + bool exists = false; + for (List::Iterator j = elements.begin(), jend = elements.end(); j != jend; ++j) + { + if (j->name.name == groupElem.name.name) + { + exists = true; + break; + } + } + if (exists) + continue; + + Xsd::ElementRef& elementRef = elements.append(groupElem); + if (groupIsChoice || groupRefMinOccurs == 0) + elementRef.minOccurs = 0; + if (groupRefMaxOccurs == UNBOUNDED || groupElem.maxOccurs == UNBOUNDED) + elementRef.maxOccurs = UNBOUNDED; + else if (groupRefMaxOccurs > 1) + elementRef.maxOccurs = groupRefMaxOccurs > groupElem.maxOccurs ? groupRefMaxOccurs : groupElem.maxOccurs; + } + flags |= groupFlags; + } } } } @@ -788,16 +981,98 @@ class Reader if (!choiceElements.isEmpty()) { - uint minOccurs = getXmlAttribute(element, "minOccurs", "1").toUInt(); - uint maxOccurs = getXmlAttribute(element, "maxOccurs", "1").toUInt(); + uint choiceMaxOccurs = parseOccurs(getXmlAttribute(element, "maxOccurs", "1")); for (List::Iterator i = choiceElements.begin(), end = choiceElements.end(); i != end; ++i) { const Xsd::ElementRef& choiceElement = *i; + // Skip if an element with the same name already exists (can happen with choice branches) + bool exists = false; + for (List::Iterator j = elements.begin(), jend = elements.end(); j != jend; ++j) + { + if (j->name.name == choiceElement.name.name) + { + exists = true; + break; + } + } + if (exists) + continue; Xsd::ElementRef& elementRef = elements.append(choiceElement); - //elementRef.minOccurs = minOccurs; // todo: skip this if min/max was set actually set in choiceElement? - elementRef.minOccurs = 0; - elementRef.maxOccurs = maxOccurs; + elementRef.minOccurs = 0; + // Take the maximum of choice's maxOccurs and element's maxOccurs + if (choiceMaxOccurs == UNBOUNDED || choiceElement.maxOccurs == UNBOUNDED) + elementRef.maxOccurs = UNBOUNDED; + else + elementRef.maxOccurs = choiceMaxOccurs > choiceElement.maxOccurs ? choiceMaxOccurs : choiceElement.maxOccurs; + } + } + } + else if (compareXsName(position, element.type, "group")) + { + // Handle group reference in extension/restriction + String refWithNamespacePrefix = getXmlAttribute(element, "ref"); + if (!refWithNamespacePrefix.isEmpty()) + { + Xsd::Name refName; + if (!resolveNamespacePrefix(position, refWithNamespacePrefix, refName)) + return false; + + Position groupPos = findGroupByName(refName); + if (!groupPos) + return (_error = String::fromPrintf("Could not find group '%s'", (const char*)refName.name)), false; + + for (List::Iterator gi = groupPos.element->content.begin(), gend = groupPos.element->content.end(); gi != gend; ++gi) + { + const Xml::Variant& gvariant = *gi; + if (!gvariant.isElement()) + continue; + const Xml::Element& groupChild = gvariant.toElement(); + + Position groupChildPos; + groupChildPos.element = &groupChild; + groupChildPos.xsdFileData = groupPos.xsdFileData; + + if (compareXsName(groupPos, groupChild.type, "choice") || + compareXsName(groupPos, groupChild.type, "sequence") || + compareXsName(groupPos, groupChild.type, "all")) + { + List groupElements; + uint32 groupFlags; + if (!processXsAllEtAl(groupChildPos, typeName, groupElements, groupFlags)) + return false; + + uint groupRefMinOccurs = parseOccurs(getXmlAttribute(element, "minOccurs", "1")); + uint groupRefMaxOccurs = parseOccurs(getXmlAttribute(element, "maxOccurs", "1")); + + // Choice elements are mutually exclusive alternatives. + bool groupIsChoice = compareXsName(groupPos, groupChild.type, "choice"); + + for (List::Iterator gei = groupElements.begin(), geend = groupElements.end(); gei != geend; ++gei) + { + Xsd::ElementRef& groupElem = *gei; + bool exists = false; + for (List::Iterator j = elements.begin(), jend = elements.end(); j != jend; ++j) + { + if (j->name.name == groupElem.name.name) + { + exists = true; + break; + } + } + if (exists) + continue; + + Xsd::ElementRef& elementRef = elements.append(groupElem); + if (groupIsChoice || groupRefMinOccurs == 0) + elementRef.minOccurs = 0; + if (groupRefMaxOccurs == UNBOUNDED || groupElem.maxOccurs == UNBOUNDED) + elementRef.maxOccurs = UNBOUNDED; + else if (groupRefMaxOccurs > 1) + elementRef.maxOccurs = groupRefMaxOccurs > groupElem.maxOccurs ? groupRefMaxOccurs : groupElem.maxOccurs; + } + flags |= groupFlags; + } } } } @@ -816,6 +1091,10 @@ class Reader { flags |= Xsd::Type::AnyAttributeFlag; } + else if (compareXsName(position, element.type, "any")) + { + flags |= Xsd::Type::AnyElementFlag; + } else Console::printf("skipped %s\n", (const char*)element.type); } @@ -955,17 +1234,28 @@ class Reader if (!processXsElement(elementPosition, parentTypeName, elementRef)) return false; - if (elementRef.name.name.isEmpty() || elementRef.typeName.name.isEmpty()) + if (elementRef.name.name.isEmpty() || (elementRef.typeName.name.isEmpty() && elementRef.refName.name.isEmpty())) continue; - elements.append(elementRef); + // Skip if an element with the same name already exists (can happen with choice branches) + bool exists = false; + for (List::Iterator j = elements.begin(), jend = elements.end(); j != jend; ++j) + { + if (j->name.name == elementRef.name.name) + { + exists = true; + break; + } + } + if (!exists) + elements.append(elementRef); } else if (compareXsName(position, element.type, "choice")) { Position choicePosition; choicePosition.element = &element; choicePosition.xsdFileData = position.xsdFileData; - + List choiceElements; uint32 _; if (!processXsAllEtAl(choicePosition, parentTypeName, choiceElements, _)) @@ -973,16 +1263,107 @@ class Reader if (!choiceElements.isEmpty()) { - uint minOccurs = getXmlAttribute(element, "minOccurs", getXmlAttribute(*position.element, "minOccurs", "1")).toUInt(); - uint maxOccurs = getXmlAttribute(element, "maxOccurs", getXmlAttribute(*position.element, "maxOccurs", "1")).toUInt(); - + uint choiceMaxOccurs = parseOccurs(getXmlAttribute(element, "maxOccurs", getXmlAttribute(*position.element, "maxOccurs", "1"))); + for (List::Iterator i = choiceElements.begin(), end = choiceElements.end(); i != end; ++i) { const Xsd::ElementRef& choiceElement = *i; + // Skip if an element with the same name already exists (can happen with choice branches) + bool exists = false; + for (List::Iterator j = elements.begin(), jend = elements.end(); j != jend; ++j) + { + if (j->name.name == choiceElement.name.name) + { + exists = true; + break; + } + } + if (exists) + continue; Xsd::ElementRef& elementRef = elements.append(choiceElement); - //elementRef.minOccurs = minOccurs; // todo: skip this if min/max was set actually set in choiceElement? elementRef.minOccurs = 0; - elementRef.maxOccurs = maxOccurs; + // Take the maximum of choice's maxOccurs and element's maxOccurs + // Either being unbounded means the result is unbounded + if (choiceMaxOccurs == UNBOUNDED || choiceElement.maxOccurs == UNBOUNDED) + elementRef.maxOccurs = UNBOUNDED; + else + elementRef.maxOccurs = choiceMaxOccurs > choiceElement.maxOccurs ? choiceMaxOccurs : choiceElement.maxOccurs; + } + } + } + else if (compareXsName(position, element.type, "group")) + { + // Handle group reference + String refWithNamespacePrefix = getXmlAttribute(element, "ref"); + if (!refWithNamespacePrefix.isEmpty()) + { + Xsd::Name refName; + if (!resolveNamespacePrefix(position, refWithNamespacePrefix, refName)) + return false; + + Position groupPos = findGroupByName(refName); + if (!groupPos) + return (_error = String::fromPrintf("Could not find group '%s'", (const char*)refName.name)), false; + + // Process the group's content (choice, sequence, or all) + for (List::Iterator gi = groupPos.element->content.begin(), gend = groupPos.element->content.end(); gi != gend; ++gi) + { + const Xml::Variant& gvariant = *gi; + if (!gvariant.isElement()) + continue; + const Xml::Element& groupChild = gvariant.toElement(); + + Position groupChildPos; + groupChildPos.element = &groupChild; + groupChildPos.xsdFileData = groupPos.xsdFileData; + + if (compareXsName(groupPos, groupChild.type, "choice") || + compareXsName(groupPos, groupChild.type, "sequence") || + compareXsName(groupPos, groupChild.type, "all")) + { + List groupElements; + uint32 groupFlags; + if (!processXsAllEtAl(groupChildPos, parentTypeName, groupElements, groupFlags)) + return false; + + uint groupRefMinOccurs = parseOccurs(getXmlAttribute(element, "minOccurs", "1")); + uint groupRefMaxOccurs = parseOccurs(getXmlAttribute(element, "maxOccurs", "1")); + + // When a group's content is a , elements are mutually + // exclusive alternatives. Each individual element is therefore + // optional (minOccurs=0) regardless of the group ref's minOccurs. + bool groupIsChoice = compareXsName(groupPos, groupChild.type, "choice"); + + // Add elements from the group, adjusting occurrences + for (List::Iterator gei = groupElements.begin(), geend = groupElements.end(); gei != geend; ++gei) + { + Xsd::ElementRef& groupElem = *gei; + // Skip if an element with the same name already exists + bool exists = false; + for (List::Iterator j = elements.begin(), jend = elements.end(); j != jend; ++j) + { + if (j->name.name == groupElem.name.name) + { + exists = true; + break; + } + } + if (exists) + continue; + + Xsd::ElementRef& elementRef = elements.append(groupElem); + // Choice elements are alternatives so each is individually optional. + // Also make all elements optional when the group ref itself is optional. + if (groupIsChoice || groupRefMinOccurs == 0) + elementRef.minOccurs = 0; + // Propagate maxOccurs from group ref + if (groupRefMaxOccurs == UNBOUNDED || groupElem.maxOccurs == UNBOUNDED) + elementRef.maxOccurs = UNBOUNDED; + else if (groupRefMaxOccurs > 1) + elementRef.maxOccurs = groupRefMaxOccurs > groupElem.maxOccurs ? groupRefMaxOccurs : groupElem.maxOccurs; + } + flags |= groupFlags; + } } } } @@ -998,8 +1379,10 @@ class Reader else if (compareXsName(position, element.type, "any")) { String processContents = getXmlAttribute(element, "processContents"); - if (processContents == "skip" || processContents == "lax") + if (processContents == "skip") flags |= Xsd::Type::SkipProcessContentsFlag; + else // "lax", "strict", or unspecified: capture as any_element + flags |= Xsd::Type::AnyElementFlag; } else Console::printf("skipped %s\n", (const char*)element.type); @@ -1009,6 +1392,59 @@ class Reader bool process(List& elements) { + // First pass: collect group definitions and substitution group members + for (HashMap::Iterator i = _namespaces.begin(), end = _namespaces.end(); i != end; ++i) + { + NamespaceData& namespaceData = *i; + for (HashMap::Iterator i = namespaceData.files.begin(), end = namespaceData.files.end(); i != end; ++i) + { + const XsdFileData& xsdFileData = *i; + Position position; + position.element = &xsdFileData.xsd; + position.xsdFileData = &xsdFileData; + + for (List::Iterator i = position.element->content.begin(), end = position.element->content.end(); i != end; ++i) + { + const Xml::Variant& variant = *i; + if (!variant.isElement()) + continue; + const Xml::Element& element = variant.toElement(); + + // Collect group definitions + if (compareXsName(position, element.type, "group")) + { + String name = getXmlAttribute(element, "name"); + if (!name.isEmpty()) + { + Xsd::Name groupName; + groupName.name = name; + groupName.xsdNamespace = xsdFileData.targetNamespace; + + GroupDef& groupDef = _groups.append(groupName, GroupDef()); + groupDef.position.element = &element; + groupDef.position.xsdFileData = &xsdFileData; + } + } + // Process elements with substitutionGroup to register them + else if (compareXsName(position, element.type, "element")) + { + String substitutionGroupAttr = getXmlAttribute(element, "substitutionGroup"); + if (!substitutionGroupAttr.isEmpty()) + { + Position elementPosition; + elementPosition.element = &element; + elementPosition.xsdFileData = position.xsdFileData; + + Xsd::ElementRef elementRef; + if (!processXsElement(elementPosition, Xsd::Name(), elementRef, false)) + return false; + } + } + } + } + } + + // Second pass: collect root elements for (HashMap::Iterator i = _namespaces.begin(), end = _namespaces.end(); i != end; ++i) { NamespaceData& namespaceData = *i; diff --git a/src/Reader.hpp b/src/Reader.hpp index 9063718..7b83360 100644 --- a/src/Reader.hpp +++ b/src/Reader.hpp @@ -6,6 +6,9 @@ #include #include +// Special constant for unbounded maxOccurs +const uint XSD_UNBOUNDED = 0xFFFFFFFF; + struct Xsd { struct Name @@ -72,6 +75,7 @@ struct Xsd { SkipProcessContentsFlag = 1, AnyAttributeFlag = 2, + AnyElementFlag = 4, }; uint32 flags; List attributes; diff --git a/src/XmlParser.cpp b/src/XmlParser.cpp index fda2fdf..7ba1ff7 100644 --- a/src/XmlParser.cpp +++ b/src/XmlParser.cpp @@ -9,9 +9,9 @@ namespace xsdcpp { ElementContext::ElementContext(const ElementInfo* info, void* element) : info(info) , element(element) + , processedElements2(info->childrenCount, 0) , processedAttributes2(0) { - memset(processedElements2, 0, sizeof(size_t) * info->childrenCount); } struct Position @@ -26,6 +26,25 @@ struct Position namespace { +// Support for xs:any child elements: a static capture target and its ElementInfo. +// When enterElement encounters an unknown child element inside a parent that has +// AnyElementFlag, it returns a context pointing here so that the text content of +// the unknown element is captured into g_any_element_capture. After checkElement, +// parseElement calls the parent's setOtherElement callback. +static thread_local std::string g_any_element_capture; + +static void _capture_any_element_text(std::string* s, const xsdcpp::Position&, + std::string&& val) +{ + *s = std::move(val); +} + +static const xsdcpp::ElementInfo g_any_element_info = { + xsdcpp::ElementInfo::ReadTextFlag, + (xsdcpp::set_value_t)&_capture_any_element_text, + nullptr, 0, nullptr, 0, nullptr, nullptr, nullptr +}; + struct Token { enum Type @@ -159,6 +178,37 @@ void skipText(xsdcpp::Position& pos) default: if (pos.pos[1] == '!') { + if (strncmp(pos.pos + 2, "[CDATA[", 7) == 0) + { + pos.pos += 9; // skip past "", 3) == 0) + { + pos.pos += 3; + break; + } + else + ++pos.pos; + } + continue; + } skipSpace(pos); continue; } @@ -259,6 +309,16 @@ std::string stripComments(const char* str, size_t len) else result.append(i, next - i); i = next; + if (strncmp(i + 1, "![CDATA[", 8) == 0) + { + i += 9; // skip ""); + if (!cdataEnd) + return result.append(i, end - i); // malformed, include remainder + result.append(i, cdataEnd - i); // extract CDATA content + i = cdataEnd + 3; // skip "]]>" + continue; + } if (strncmp(i + 1, "!--", 3) != 0) return result.append(i, end - i); i += 4; @@ -406,6 +466,13 @@ xsdcpp::ElementContext enterElement(Context& context, xsdcpp::ElementContext& pa if (nameWithoutNamespace == c->name) return enterElement(context, parentElementContext, *c); } + // Support xs:any: if the parent declares AnyElementFlag, capture as any_element. + for (const xsdcpp::ElementInfo* i = parentElementContext.info; i; i = i->base) + if (i->flags & xsdcpp::ElementInfo::AnyElementFlag) + { + g_any_element_capture.clear(); + return xsdcpp::ElementContext(&g_any_element_info, &g_any_element_capture); + } throw VerificationException(context.pos, "Unexpected element '" + name + "'"); } @@ -527,7 +594,7 @@ void parseElement(Context& context, xsdcpp::ElementContext& parentElementContext skipTextAndSubElements(context, elementName); else skipText(context.pos); - if (context.pos.pos != start) + if (context.pos.pos != start && elementContext.info->addText) { std::string text = stripComments(start, context.pos.pos - start); elementContext.info->addText(elementContext.element, context.pos, std::move(text)); @@ -556,6 +623,17 @@ void parseElement(Context& context, xsdcpp::ElementContext& parentElementContext if (context.token.type != Token::tagEndType) throw SyntaxException(context.token.pos, "Expected '>'"); checkElement(context, elementContext); + + // If this was an xs:any capture, notify the parent via setOtherElement. + if (elementContext.info == &g_any_element_info) + for (const xsdcpp::ElementInfo* i = parentElementContext.info; i; i = i->base) + if (i->setOtherElement) + { + i->setOtherElement(parentElementContext.element, + std::move(elementName), + std::move(g_any_element_capture)); + break; + } } } diff --git a/src/XmlParser.hpp b/src/XmlParser.hpp index c862e4d..abff6f9 100644 --- a/src/XmlParser.hpp +++ b/src/XmlParser.hpp @@ -1,6 +1,7 @@ #include #include +#include namespace xsdcpp { @@ -12,6 +13,7 @@ typedef void* (*get_field_t)(void*); typedef void (*set_value_t)(void* obj, const Position&, std::string&&); typedef void (*set_default_t)(void*); typedef void (*set_any_attribute_t)(void*, std::string&& name, std::string&& value); +typedef void (*set_any_element_t)(void*, std::string&& name, std::string&& value); struct ChildElementInfo { @@ -42,6 +44,7 @@ struct ElementInfo SkipProcessingFlag = 0x04, AnyAttributeFlag = 0x08, CheckChildrenFlag = 0x10, + AnyElementFlag = 0x20, }; size_t flags; @@ -52,13 +55,14 @@ struct ElementInfo uint64_t checkAttributeMask; const ElementInfo* base; set_any_attribute_t setOtherAttribute; + set_any_element_t setOtherElement; }; struct ElementContext { const ElementInfo* info; void* element; - size_t processedElements2[64]; + std::vector processedElements2; uint64_t processedAttributes2; ElementContext(const ElementInfo* info, void* element); diff --git a/src/XmlWriter.cpp b/src/XmlWriter.cpp new file mode 100644 index 0000000..4d25af8 --- /dev/null +++ b/src/XmlWriter.cpp @@ -0,0 +1,9 @@ + +// XmlWriter implementation +// Most functionality is in XmlWriter.hpp as inline functions + +namespace xsdcpp { + +// Placeholder for any future non-inline implementations + +} diff --git a/src/XmlWriter.hpp b/src/XmlWriter.hpp new file mode 100644 index 0000000..6104355 --- /dev/null +++ b/src/XmlWriter.hpp @@ -0,0 +1,173 @@ + +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#define XSDCPP_MAYBE_UNUSED __attribute__((unused)) +#elif defined(_MSC_VER) +#define XSDCPP_MAYBE_UNUSED +#else +#define XSDCPP_MAYBE_UNUSED +#endif + +namespace xsdcpp { + +class XmlWriter { +public: + XmlWriter() + : _depth(0) + , _tagOpen(false) + , _hasContent(false) + { + } + + void startElement(const char* name) + { + closeTagIfOpen(); + _buffer += std::string(_depth * 2, ' '); + _buffer += '<'; + _buffer += name; + _tagOpen = true; + _hasContent = false; + ++_depth; + } + + void endElement(const char* name) + { + --_depth; + if (_tagOpen) + { + _buffer += "/>\n"; + _tagOpen = false; + } + else + { + if (!_hasContent) + _buffer += std::string(_depth * 2, ' '); + _buffer += "\n"; + } + _hasContent = false; + } + + void writeAttribute(const char* name, const std::string& value) + { + _buffer += ' '; + _buffer += name; + _buffer += "=\""; + _buffer += escape_xml(value); + _buffer += '"'; + } + + void writeText(const std::string& text) + { + // Close tag without newline - text follows immediately + if (_tagOpen) + { + _buffer += ">"; + _tagOpen = false; + } + _buffer += escape_xml(text); + _hasContent = true; + } + + std::string str() const + { + return _buffer; + } + +private: + void closeTagIfOpen() + { + if (_tagOpen) + { + _buffer += ">\n"; + _tagOpen = false; + } + } + + static std::string escape_xml(const std::string& s) + { + std::string result; + result.reserve(s.size()); + for (char c : s) + { + switch (c) + { + case '<': + result += "<"; + break; + case '>': + result += ">"; + break; + case '&': + result += "&"; + break; + case '"': + result += """; + break; + case '\'': + result += "'"; + break; + default: + result += c; + break; + } + } + return result; + } + + std::string _buffer; + int _depth; + bool _tagOpen; + bool _hasContent; +}; + +inline std::string get_string(const std::string& v) { return v; } +inline std::string get_string(uint64_t v) { return std::to_string(v); } +inline std::string get_string(int64_t v) { return std::to_string(v); } +inline std::string get_string(uint32_t v) { return std::to_string(v); } +inline std::string get_string(int32_t v) { return std::to_string(v); } +inline std::string get_string(uint16_t v) { return std::to_string(v); } +inline std::string get_string(int16_t v) { return std::to_string(v); } +inline std::string get_string(bool v) { return v ? "true" : "false"; } + +inline std::string get_string(float v) +{ + std::ostringstream ss; + ss << std::setprecision(9) << v; + return ss.str(); +} + +inline std::string get_string(double v) +{ + std::ostringstream ss; + ss << std::setprecision(17) << v; + return ss.str(); +} + +inline void write_file(const std::string& filePath, const std::string& content) +{ + std::ofstream file; + file.exceptions(std::ofstream::failbit | std::ofstream::badbit); + file.open(filePath); + file << content; +} + +template +inline std::string _serialize_list(const T& vec) +{ + std::string result; + for (size_t i = 0; i < vec.size(); ++i) + { + if (i > 0) result += ' '; + result += get_string(vec[i]); + } + return result; +} + +} diff --git a/src/xsd.hpp b/src/xsd.hpp index 2ef903b..6738aad 100644 --- a/src/xsd.hpp +++ b/src/xsd.hpp @@ -1,19 +1,145 @@ - #pragma once #ifndef XSDCPP_H #define XSDCPP_H +#include #include +#include #include #include +#include namespace xsd { typedef std::string string; +// vector wraps std::vector for all non-bool types. +// +// The internal std::vector is held through a pointer rather than by value +// or inheritance. This prevents Clang/GCC from eagerly instantiating +// std::vector when xsd::vector appears as a struct member with an +// incomplete T (a pattern that arises throughout the generated domain.hpp). +// The pointer itself is valid even when T is an incomplete type; the wrapped +// std::vector is only instantiated lazily, at the point where individual +// methods are called — by which time T is always fully defined. +// +// For bool: char storage avoids std::vector's bit-packing proxy type. +template +class vector { + std::vector* impl_; + +public: + // ---- Typedefs (match std::vector interface) ---------------------------- + // All use T directly (not via std::vector) to avoid instantiating + // std::vector with potentially incomplete T at class-definition time. + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // ---- Construction / destruction ---------------------------------------- + vector() : impl_(new std::vector()) {} + + vector(std::initializer_list il) : impl_(new std::vector(il)) {} + + vector(const vector& o) : impl_(new std::vector(*o.impl_)) {} + + vector(vector&& o) noexcept : impl_(o.impl_) { o.impl_ = nullptr; } + + // Defined out-of-class so that the body (delete impl_) is only instantiated + // at ODR-use, not at the point where xsd::vector is first seen. + ~vector() noexcept; + + // ---- Assignment -------------------------------------------------------- + vector& operator=(const vector& o) { + if (this != &o) { + // Allocate first so that if new throws, impl_ is unchanged. + // Also handles moved-from o (o.impl_ == nullptr). + auto* tmp = o.impl_ ? new std::vector(*o.impl_) : nullptr; + delete impl_; + impl_ = tmp; + } + return *this; + } + + vector& operator=(vector&& o) noexcept { + delete impl_; impl_ = o.impl_; o.impl_ = nullptr; return *this; + } + + // ---- Size / capacity --------------------------------------------------- + std::size_t size() const noexcept { return impl_->size(); } + bool empty() const noexcept { return impl_->empty(); } + void reserve(std::size_t n) { impl_->reserve(n); } + void resize(std::size_t n) { impl_->resize(n); } + void clear() noexcept { impl_->clear(); } + + // ---- Element access ---------------------------------------------------- + T& operator[](std::size_t n) { return (*impl_)[n]; } + const T& operator[](std::size_t n) const { return (*impl_)[n]; } + T& at(std::size_t n) { return impl_->at(n); } + const T& at(std::size_t n) const { return impl_->at(n); } + T& front() { return impl_->front(); } + const T& front() const { return impl_->front(); } + T& back() { return impl_->back(); } + const T& back() const { return impl_->back(); } + T* data() { return impl_->data(); } + const T* data() const { return impl_->data(); } + + // ---- Modifiers --------------------------------------------------------- + void push_back(const T& v) { impl_->push_back(v); } + void push_back(T&& v) { impl_->push_back(std::move(v)); } + + template + T& emplace_back(Args&&... args) { + return impl_->emplace_back(std::forward(args)...); + } + + // ---- Iterators --------------------------------------------------------- + iterator begin() { return impl_->data(); } + const_iterator begin() const { return impl_->data(); } + iterator end() { return impl_->data() + impl_->size(); } + const_iterator end() const { return impl_->data() + impl_->size(); } + const_iterator cbegin() const { return impl_->data(); } + const_iterator cend() const { return impl_->data() + impl_->size(); } + + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + // ---- Comparison -------------------------------------------------------- + friend bool operator==(const vector& a, const vector& b) { + if (!a.impl_ || !b.impl_) return a.impl_ == b.impl_; + return *a.impl_ == *b.impl_; + } + friend bool operator!=(const vector& a, const vector& b) { return !(a == b); } +}; + +// Out-of-class destructor definition: T is only required to be complete at +// the call site (where objects are actually destroyed), not at the point where +// xsd::vector is used as a struct member with forward-declared T. template -using vector = std::vector; +inline vector::~vector() noexcept { delete impl_; } + +// Specialisation for bool: char storage avoids std::vector's proxy type. +template <> +class vector : public std::vector { +public: + using std::vector::vector; + using std::vector::operator=; + void push_back(bool value) { std::vector::push_back(value ? 1 : 0); } + void emplace_back() { std::vector::emplace_back(0); } + bool& back() { return reinterpret_cast(std::vector::back()); } + const bool& back() const { return reinterpret_cast(std::vector::back()); } +}; template class optional @@ -119,7 +245,7 @@ class base : _value() { } - base(T value) + base(T value) : _value(value) { } @@ -143,6 +269,12 @@ struct any_attribute xsd::string value; }; +struct any_element +{ + std::string name; + xsd::string value; +}; + } #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 15bd3c6..4aa4d5b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -101,10 +101,45 @@ target_link_libraries(XsdLib_test PRIVATE mingtest::gtest mingtest::gtest_main) add_test(NAME XsdLib_test COMMAND XsdLib_test) target_require_cpp11(XsdLib_test) +# Test wrap namespace option (-w) +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/wrap") +add_custom_command( + COMMAND "$" "${CMAKE_CURRENT_SOURCE_DIR}/Example.xsd" -H "${CMAKE_CURRENT_BINARY_DIR}/wrap" -C "${CMAKE_CURRENT_BINARY_DIR}/wrap" -w testns::inner + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/wrap/Example.hpp" "${CMAKE_CURRENT_BINARY_DIR}/wrap/Example.cpp" "${CMAKE_CURRENT_BINARY_DIR}/wrap/Example_xsd.hpp" + DEPENDS "$" "${CMAKE_CURRENT_SOURCE_DIR}/Example.xsd" +) +add_executable(WrapNamespace_test + WrapNamespace_test.cpp + "${CMAKE_CURRENT_BINARY_DIR}/wrap/Example.hpp" + "${CMAKE_CURRENT_BINARY_DIR}/wrap/Example.cpp" +) +target_require_cpp11(WrapNamespace_test) +target_link_libraries(WrapNamespace_test PRIVATE mingtest::gtest mingtest::gtest_main) +target_include_directories(WrapNamespace_test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/wrap") +add_test(NAME WrapNamespace_test COMMAND WrapNamespace_test) + +# Test wrap namespace + inner namespace rename options (-w -n) +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rename") +add_custom_command( + COMMAND "$" "${CMAKE_CURRENT_SOURCE_DIR}/Example.xsd" -H "${CMAKE_CURRENT_BINARY_DIR}/rename" -C "${CMAKE_CURRENT_BINARY_DIR}/rename" -w flatns -n domain + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rename/domain.hpp" "${CMAKE_CURRENT_BINARY_DIR}/rename/domain.cpp" "${CMAKE_CURRENT_BINARY_DIR}/rename/domain_xsd.hpp" + DEPENDS "$" "${CMAKE_CURRENT_SOURCE_DIR}/Example.xsd" +) +add_executable(RenameNamespace_test + RenameNamespace_test.cpp + "${CMAKE_CURRENT_BINARY_DIR}/rename/domain.hpp" + "${CMAKE_CURRENT_BINARY_DIR}/rename/domain.cpp" +) +target_require_cpp11(RenameNamespace_test) +target_link_libraries(RenameNamespace_test PRIVATE mingtest::gtest mingtest::gtest_main) +target_include_directories(RenameNamespace_test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/rename") +add_test(NAME RenameNamespace_test COMMAND RenameNamespace_test) + add_subdirectory(ecic) add_subdirectory(ecoa) +add_subdirectory(ore) -set_target_properties(XmlParser_test Ecic_test Ecoa_test Generator_test Reader_test Features_test XsdLib_test +set_target_properties(XmlParser_test Ecic_test Ecoa_test Ore_test Generator_test Reader_test Features_test XsdLib_test WrapNamespace_test RenameNamespace_test PROPERTIES FOLDER "test" ) diff --git a/test/Features_test.cpp b/test/Features_test.cpp index 6cbcc38..d97c44a 100644 --- a/test/Features_test.cpp +++ b/test/Features_test.cpp @@ -332,6 +332,80 @@ TEST(Features, Example) } } +TEST(Features, SaveLoad_RoundTrip) +{ + // Create original data + Example::List original; + original.Person.emplace_back(); + static_cast(original.Person[0].Name) = "John Smith"; + original.Person[0].Name.age = 40; + original.Person[0].Name.hidden = true; + original.Person[0].Country = Example::Country(); + static_cast&>(*original.Person[0].Country) = Example::CountryCode::UK; + original.Person[0].Country->comment = "home country"; + + original.Person.emplace_back(); + static_cast(original.Person[1].Name) = "Mary Jones"; + original.Person[1].Name.age = 54; + original.Person[1].Name.hidden = false; + + // Save to XML string + std::string xml = Example::save_data(original); + + // Load back + Example::List loaded; + Example::load_data(xml, loaded); + + // Verify round-trip + EXPECT_EQ(loaded.Person.size(), 2); + EXPECT_EQ(loaded.Person[0].Name, "John Smith"); + EXPECT_EQ(loaded.Person[0].Name.age, 40); + EXPECT_EQ(loaded.Person[0].Name.hidden, true); + EXPECT_TRUE(loaded.Person[0].Country); + EXPECT_EQ(*loaded.Person[0].Country, Example::CountryCode::UK); + EXPECT_TRUE(loaded.Person[0].Country->comment); + EXPECT_EQ(*loaded.Person[0].Country->comment, "home country"); + + EXPECT_EQ(loaded.Person[1].Name, "Mary Jones"); + EXPECT_EQ(loaded.Person[1].Name.age, 54); + EXPECT_EQ(loaded.Person[1].Name.hidden, false); + EXPECT_FALSE(loaded.Person[1].Country); +} + +TEST(Features, SaveLoad_SubstitutionGroup_RoundTrip) +{ + // Create original data with substitution group + SubstitutionGroup::Main original; + original.Property.emplace_back(); + original.Property[0].BooleanProperty = SubstitutionGroup::BooleanProperty(); + original.Property[0].BooleanProperty->name = "enabled"; + original.Property[0].BooleanProperty->value = true; + + original.Property.emplace_back(); + original.Property[1].FloatingPointProperty = SubstitutionGroup::FloatingPointProperty(); + original.Property[1].FloatingPointProperty->name = "ratio"; + original.Property[1].FloatingPointProperty->value = 3.14; + + // Save to XML string + std::string xml = SubstitutionGroup::save_data(original); + + // Load back + SubstitutionGroup::Main loaded; + SubstitutionGroup::load_data(xml, loaded); + + // Verify round-trip + EXPECT_EQ(loaded.Property.size(), 2); + EXPECT_TRUE(loaded.Property[0].BooleanProperty); + EXPECT_FALSE(loaded.Property[0].FloatingPointProperty); + EXPECT_EQ(loaded.Property[0].BooleanProperty->name, "enabled"); + EXPECT_EQ(loaded.Property[0].BooleanProperty->value, true); + + EXPECT_FALSE(loaded.Property[1].BooleanProperty); + EXPECT_TRUE(loaded.Property[1].FloatingPointProperty); + EXPECT_EQ(loaded.Property[1].FloatingPointProperty->name, "ratio"); + EXPECT_EQ(loaded.Property[1].FloatingPointProperty->value, 3.14); +} + // todo: // Int Attribute out of range diff --git a/test/Generator_test.cpp b/test/Generator_test.cpp index 20f2fdb..41319b5 100644 --- a/test/Generator_test.cpp +++ b/test/Generator_test.cpp @@ -14,7 +14,7 @@ TEST(Generator, generateCpp) Xsd xsd; EXPECT_TRUE(Directory::create("test_temp")); EXPECT_TRUE(readXsd(String(), inputFile, List(), xsd, error)); - EXPECT_TRUE(generateCpp(xsd, "test_temp", List(), List(), error)); + EXPECT_TRUE(generateCpp(xsd, "test_temp", "test_temp", List(), List(), String(), String(), error)); } { String inputFile = FOLDER "/SubstitutionGroup.xsd"; @@ -22,6 +22,6 @@ TEST(Generator, generateCpp) Xsd xsd; EXPECT_TRUE(Directory::create("test_temp")); EXPECT_TRUE(readXsd(String(), inputFile, List(), xsd, error)); - EXPECT_TRUE(generateCpp(xsd, "test_temp", List(), List(), error)); + EXPECT_TRUE(generateCpp(xsd, "test_temp", "test_temp", List(), List(), String(), String(), error)); } } diff --git a/test/RenameNamespace_test.cpp b/test/RenameNamespace_test.cpp new file mode 100644 index 0000000..58a2b4b --- /dev/null +++ b/test/RenameNamespace_test.cpp @@ -0,0 +1,24 @@ + +#include "domain.hpp" + +#include + +// Test wrap namespace + inner namespace rename options (-w -n) +// Types should be in flatns::domain namespace +TEST(RenameNamespace, LoadData) +{ + flatns::domain::List list; + flatns::domain::load_data(R"( + + + Jane Doe + DE + +)", list); + + EXPECT_EQ(list.Person.size(), 1); + EXPECT_EQ((std::string)list.Person[0].Name, "Jane Doe"); + EXPECT_EQ(list.Person[0].Name.age, 25); + EXPECT_TRUE(list.Person[0].Country); + EXPECT_EQ(*list.Person[0].Country, flatns::domain::CountryCode::DE); +} diff --git a/test/WrapNamespace_test.cpp b/test/WrapNamespace_test.cpp new file mode 100644 index 0000000..3b7709f --- /dev/null +++ b/test/WrapNamespace_test.cpp @@ -0,0 +1,24 @@ + +#include "Example.hpp" + +#include + +// Test wrap namespace option (-w) +// Types should be in testns::inner::Example namespace +TEST(WrapNamespace, LoadData) +{ + testns::inner::Example::List list; + testns::inner::Example::load_data(R"( + + + John Smith + UK + +)", list); + + EXPECT_EQ(list.Person.size(), 1); + EXPECT_EQ((std::string)list.Person[0].Name, "John Smith"); + EXPECT_EQ(list.Person[0].Name.age, 40); + EXPECT_TRUE(list.Person[0].Country); + EXPECT_EQ(*list.Person[0].Country, testns::inner::Example::CountryCode::UK); +} diff --git a/test/ore/CMakeLists.txt b/test/ore/CMakeLists.txt new file mode 100644 index 0000000..c7182e2 --- /dev/null +++ b/test/ore/CMakeLists.txt @@ -0,0 +1,37 @@ + +add_custom_command( + COMMAND "$" "${CMAKE_CURRENT_SOURCE_DIR}/input.xsd" -o "${CMAKE_CURRENT_BINARY_DIR}" + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/input.hpp" "${CMAKE_CURRENT_BINARY_DIR}/input.cpp" + DEPENDS "$" + "${CMAKE_CURRENT_SOURCE_DIR}/input.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/ore_types.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/instruments.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/referencedata.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/simulation.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/creditsimulation.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/curveconfig.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/conventions.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/nettingsetdefinitions.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/pricingengines.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/todaysmarket.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/sensitivity.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/stress.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/ore.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/calendaradjustment.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/currencyconfig.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/iborfallbackconfig.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/baselTrafficLightconfig.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/scriptlibrary.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/simmcalibration.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/counterparty.xsd" + "${CMAKE_CURRENT_SOURCE_DIR}/historicalreturnconfig.xsd" +) +add_executable(Ore_test + Ore_test.cpp + "${CMAKE_CURRENT_BINARY_DIR}/input.hpp" + "${CMAKE_CURRENT_BINARY_DIR}/input.cpp" +) +target_require_cpp11(Ore_test) +target_link_libraries(Ore_test PRIVATE mingtest::gtest mingtest::gtest_main) +target_include_directories(Ore_test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") +add_test(NAME Ore_test COMMAND Ore_test) diff --git a/test/ore/Ore_test.cpp b/test/ore/Ore_test.cpp new file mode 100644 index 0000000..bb14d4e --- /dev/null +++ b/test/ore/Ore_test.cpp @@ -0,0 +1,46 @@ + +#include "input.hpp" + +#include + +// This test verifies that xsdcpp correctly processes the ORE XSD files. +// The ORE XSDs exercise several patterns that required fixes: +// 1. Abstract elements without type attributes +// 2. Elements without type definitions (default to xs:anyType/string) + +TEST(Ore, Constructor) +{ + // Verify the main types are constructible + input::portfolio portfolio; + input::trade trade; +} + +TEST(Ore, load_portfolio) +{ + input::portfolio portfolio; + input::load_data(R"( + + + Swap + + + FxForward + +)", portfolio); + + EXPECT_EQ(portfolio.Trade.size(), 2); + EXPECT_EQ(portfolio.Trade[0].id, "trade1"); + EXPECT_EQ(portfolio.Trade[0].TradeType, input::oreTradeType::Swap); + EXPECT_EQ(portfolio.Trade[1].id, "trade2"); + EXPECT_EQ(portfolio.Trade[1].TradeType, input::oreTradeType::FxForward); +} + +TEST(Ore, load_empty_portfolio) +{ + input::portfolio portfolio; + input::load_data(R"( + +)", portfolio); + + EXPECT_EQ(portfolio.Trade.size(), 0); +} diff --git a/test/ore/baselTrafficLightconfig.xsd b/test/ore/baselTrafficLightconfig.xsd new file mode 100644 index 0000000..0398aad --- /dev/null +++ b/test/ore/baselTrafficLightconfig.xsd @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/calendaradjustment.xsd b/test/ore/calendaradjustment.xsd new file mode 100755 index 0000000..1c08679 --- /dev/null +++ b/test/ore/calendaradjustment.xsd @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/collateralbalance.xsd b/test/ore/collateralbalance.xsd new file mode 100644 index 0000000..9b385b5 --- /dev/null +++ b/test/ore/collateralbalance.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/conventions.xsd b/test/ore/conventions.xsd new file mode 100755 index 0000000..a92caae --- /dev/null +++ b/test/ore/conventions.xsd @@ -0,0 +1,567 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/counterparty.xsd b/test/ore/counterparty.xsd new file mode 100755 index 0000000..6dd7468 --- /dev/null +++ b/test/ore/counterparty.xsd @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/creditsimulation.xsd b/test/ore/creditsimulation.xsd new file mode 100644 index 0000000..506dedb --- /dev/null +++ b/test/ore/creditsimulation.xsd @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/currencyconfig.xsd b/test/ore/currencyconfig.xsd new file mode 100644 index 0000000..0772257 --- /dev/null +++ b/test/ore/currencyconfig.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/curveconfig.xsd b/test/ore/curveconfig.xsd new file mode 100755 index 0000000..525286f --- /dev/null +++ b/test/ore/curveconfig.xsd @@ -0,0 +1,1399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/historicalreturnconfig.xsd b/test/ore/historicalreturnconfig.xsd new file mode 100644 index 0000000..0b77774 --- /dev/null +++ b/test/ore/historicalreturnconfig.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/ore/iborfallbackconfig.xsd b/test/ore/iborfallbackconfig.xsd new file mode 100644 index 0000000..369a664 --- /dev/null +++ b/test/ore/iborfallbackconfig.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/input.xsd b/test/ore/input.xsd new file mode 100755 index 0000000..1fe50e4 --- /dev/null +++ b/test/ore/input.xsd @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/instruments.xsd b/test/ore/instruments.xsd new file mode 100755 index 0000000..212aac3 --- /dev/null +++ b/test/ore/instruments.xsd @@ -0,0 +1,5172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/nettingsetdefinitions.xsd b/test/ore/nettingsetdefinitions.xsd new file mode 100755 index 0000000..b21a36b --- /dev/null +++ b/test/ore/nettingsetdefinitions.xsd @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/ore.xsd b/test/ore/ore.xsd new file mode 100755 index 0000000..0a2f0b5 --- /dev/null +++ b/test/ore/ore.xsd @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/ore_types.xsd b/test/ore/ore_types.xsd new file mode 100644 index 0000000..47eb181 --- /dev/null +++ b/test/ore/ore_types.xsd @@ -0,0 +1,995 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The non-negative-decimal type specifies a non-negative decimal value. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/pricingengines.xsd b/test/ore/pricingengines.xsd new file mode 100755 index 0000000..57657be --- /dev/null +++ b/test/ore/pricingengines.xsd @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/referencedata.xsd b/test/ore/referencedata.xsd new file mode 100755 index 0000000..6a25035 --- /dev/null +++ b/test/ore/referencedata.xsd @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/scriptlibrary.xsd b/test/ore/scriptlibrary.xsd new file mode 100755 index 0000000..f5e41e3 --- /dev/null +++ b/test/ore/scriptlibrary.xsd @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/sensitivity.xsd b/test/ore/sensitivity.xsd new file mode 100755 index 0000000..0381d47 --- /dev/null +++ b/test/ore/sensitivity.xsd @@ -0,0 +1,626 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/simmcalibration.xsd b/test/ore/simmcalibration.xsd new file mode 100644 index 0000000..d07a029 --- /dev/null +++ b/test/ore/simmcalibration.xsd @@ -0,0 +1,743 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/simulation.xsd b/test/ore/simulation.xsd new file mode 100755 index 0000000..9027409 --- /dev/null +++ b/test/ore/simulation.xsd @@ -0,0 +1,1656 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/stress.xsd b/test/ore/stress.xsd new file mode 100755 index 0000000..71c168e --- /dev/null +++ b/test/ore/stress.xsd @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ore/todaysmarket.xsd b/test/ore/todaysmarket.xsd new file mode 100755 index 0000000..8d075a7 --- /dev/null +++ b/test/ore/todaysmarket.xsd @@ -0,0 +1,395 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +