From 40a9c7ecd16477e439aef25a8185940033cafe6e Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 10:47:28 +0200 Subject: [PATCH 01/23] Include element name in inline element error message Helps debugging by showing which element is missing a type definition. Co-Authored-By: Claude Opus 4.5 --- src/Reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Reader.cpp b/src/Reader.cpp index 654de8b..14009bd 100644 --- a/src/Reader.cpp +++ b/src/Reader.cpp @@ -539,7 +539,7 @@ class Reader return true; } } - return (_error = String::fromPrintf("Could not find 'element', 'complexType' or 'simpleType' in '%s'", (const char*)position.element->type)), false; + return (_error = String::fromPrintf("Could not find 'element', 'complexType' or 'simpleType' in '%s' (name='%s')", (const char*)position.element->type, (const char*)name)), false; } return (_error = String::fromPrintf("Missing element 'ref', 'type' or 'name' attribute in '%s'", (const char*)position.element->type)), false; From 92d6b8bc3bc2ab87a639c05b0d942bebf2afbc15 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 10:47:28 +0200 Subject: [PATCH 02/23] Handle references to abstract elements in XSD Abstract elements serve as placeholders for substitution groups and have no type definition. When processing a ref to an abstract element, skip the recursive processXsElement call and just record the reference for later resolution by resolveElementRefs(). Co-Authored-By: Claude Opus 4.5 --- src/Reader.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Reader.cpp b/src/Reader.cpp index 14009bd..141949f 100644 --- a/src/Reader.cpp +++ b/src/Reader.cpp @@ -432,6 +432,16 @@ class Reader if (!refPos) return (_error = String::fromPrintf("Could not find ref '%s'", (const char*)refName.name)), false; + // Abstract elements have no type definition - they serve as placeholders for substitution groups. + // Record the reference for later resolution by resolveElementRefs(). + if (getXmlAttribute(*refPos.element, "abstract", "false").toBool()) + { + elementRef.minOccurs = getXmlAttribute(*position.element, "minOccurs", "1").toUInt(); + elementRef.maxOccurs = getXmlAttribute(*position.element, "maxOccurs", "1").toUInt(); + elementRef.refName = refName; + return true; + } + if (!processXsElement(refPos, Xsd::Name(), elementRef, atRoot)) return false; From fc7a2ed18f7618da86836df4a4258db097a1bc57 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 10:47:28 +0200 Subject: [PATCH 03/23] Handle elements without type definitions Two fixes: 1. Refine abstract element handling: only skip processing for abstract elements that have no type attribute. Abstract elements with a type attribute still need processing for substitution groups to work. 2. Handle elements with no type attribute and no inline type definition. In XSD, these default to xs:anyType. Map this to a string element type. Co-Authored-By: Claude Opus 4.5 --- src/Reader.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Reader.cpp b/src/Reader.cpp index 141949f..e4a835e 100644 --- a/src/Reader.cpp +++ b/src/Reader.cpp @@ -432,9 +432,10 @@ class Reader if (!refPos) return (_error = String::fromPrintf("Could not find ref '%s'", (const char*)refName.name)), false; - // Abstract elements have no type definition - they serve as placeholders for substitution groups. + // 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()) + if (getXmlAttribute(*refPos.element, "abstract", "false").toBool() && + getXmlAttribute(*refPos.element, "type").isEmpty()) { elementRef.minOccurs = getXmlAttribute(*position.element, "minOccurs", "1").toUInt(); elementRef.maxOccurs = getXmlAttribute(*position.element, "maxOccurs", "1").toUInt(); @@ -527,6 +528,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; @@ -542,14 +544,26 @@ 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; } } - return (_error = String::fromPrintf("Could not find 'element', 'complexType' or 'simpleType' in '%s' (name='%s')", (const char*)position.element->type, (const char*)name)), false; + + // 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 = getXmlAttribute(*position.element, "maxOccurs", "1").toUInt(); + return true; } return (_error = String::fromPrintf("Missing element 'ref', 'type' or 'name' attribute in '%s'", (const char*)position.element->type)), false; From a346b18e1f62b91794078be9cdededb3f806e22d Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 04/23] Fix code generation for complex XSD schemas - Handle duplicate enum values by appending counter suffix - Support inheritance from SimpleRefKind types using xsd::base<> wrapper Co-Authored-By: Claude Opus 4.5 --- src/Generator.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 439751a..0b34f95 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -1005,8 +1005,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 + ");"); @@ -1097,7 +1112,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)); From e427be42c8457a9e64853e23662b261e7ac926c6 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 05/23] Add ORE XSD test suite Add the ORE (Open Source Risk Engine) XSD files as a test case. These XSDs exercise complex patterns including abstract elements without types and elements without type definitions. Note: The generated code does not yet compile due to incomplete type ordering issues in the generator. This will be addressed in a follow-up commit. Co-Authored-By: Claude Opus 4.5 --- test/CMakeLists.txt | 3 +- test/ore/CMakeLists.txt | 37 + test/ore/Ore_test.cpp | 46 + test/ore/baselTrafficLightconfig.xsd | 24 + test/ore/calendaradjustment.xsd | 27 + test/ore/collateralbalance.xsd | 20 + test/ore/conventions.xsd | 567 +++ test/ore/counterparty.xsd | 52 + test/ore/creditsimulation.xsd | 58 + test/ore/currencyconfig.xsd | 29 + test/ore/curveconfig.xsd | 1399 +++++++ test/ore/historicalreturnconfig.xsd | 29 + test/ore/iborfallbackconfig.xsd | 35 + test/ore/input.xsd | 47 + test/ore/instruments.xsd | 5172 ++++++++++++++++++++++++++ test/ore/nettingsetdefinitions.xsd | 100 + test/ore/ore.xsd | 42 + test/ore/ore_types.xsd | 995 +++++ test/ore/pricingengines.xsd | 50 + test/ore/referencedata.xsd | 226 ++ test/ore/scriptlibrary.xsd | 165 + test/ore/sensitivity.xsd | 626 ++++ test/ore/simmcalibration.xsd | 743 ++++ test/ore/simulation.xsd | 1656 +++++++++ test/ore/stress.xsd | 139 + test/ore/todaysmarket.xsd | 395 ++ 26 files changed, 12681 insertions(+), 1 deletion(-) create mode 100644 test/ore/CMakeLists.txt create mode 100644 test/ore/Ore_test.cpp create mode 100644 test/ore/baselTrafficLightconfig.xsd create mode 100755 test/ore/calendaradjustment.xsd create mode 100644 test/ore/collateralbalance.xsd create mode 100755 test/ore/conventions.xsd create mode 100755 test/ore/counterparty.xsd create mode 100644 test/ore/creditsimulation.xsd create mode 100644 test/ore/currencyconfig.xsd create mode 100755 test/ore/curveconfig.xsd create mode 100644 test/ore/historicalreturnconfig.xsd create mode 100644 test/ore/iborfallbackconfig.xsd create mode 100755 test/ore/input.xsd create mode 100755 test/ore/instruments.xsd create mode 100755 test/ore/nettingsetdefinitions.xsd create mode 100755 test/ore/ore.xsd create mode 100644 test/ore/ore_types.xsd create mode 100755 test/ore/pricingengines.xsd create mode 100755 test/ore/referencedata.xsd create mode 100755 test/ore/scriptlibrary.xsd create mode 100755 test/ore/sensitivity.xsd create mode 100644 test/ore/simmcalibration.xsd create mode 100755 test/ore/simulation.xsd create mode 100755 test/ore/stress.xsd create mode 100755 test/ore/todaysmarket.xsd diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 15bd3c6..2af60ac 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -103,8 +103,9 @@ target_require_cpp11(XsdLib_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 PROPERTIES FOLDER "test" ) 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..dad7177 --- /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, "Swap"); + EXPECT_EQ(portfolio.Trade[1].id, "trade2"); + EXPECT_EQ(portfolio.Trade[1].TradeType, "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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b4e35757d881f37e137bc5f8f6bd69b202a555a7 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 06/23] Fix duplicate elements and add vector safety - Deduplicate elements when same name appears in multiple choice branches - Track generated ElementInfo by C++ name to avoid duplicates for mapped types - Add xsd::vector specialization using char storage to avoid std::vector proxy issues with .back() Co-Authored-By: Claude Opus 4.5 --- src/Generator.cpp | 22 +++++++++++++++----- src/Reader.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++--- src/xsd.hpp | 21 ++++++++++++++++++- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 0b34f95..7b77193 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -334,6 +334,7 @@ class Generator String _cppNamespace; HashSet _generatedTypes2; HashSet _generatedElementInfos2; + HashSet _generatedElementInfoCppNames; // Track by C++ name to avoid duplicates for base types HashSet _generatedTypeSetters; HashSet _generatedAttributeSetDefaultValueFunctions; HashMap _generatedAttributeTrackBits; @@ -398,7 +399,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,9 +436,10 @@ class Generator cppName == "uint16_t" || cppName == "int16_t" || cppName == "double" || - cppName == "float" || - cppName == "bool") + cppName == "float") return String("xsdcpp::set_") + cppName; + if (cppName == "bool") + return String("xsdcpp::set_bool"); return toCppNamespacePrefix(typeName) + "::_set_" + cppName; } @@ -862,7 +864,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 +885,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; } diff --git a/src/Reader.cpp b/src/Reader.cpp index e4a835e..192470e 100644 --- a/src/Reader.cpp +++ b/src/Reader.cpp @@ -757,6 +757,18 @@ class Reader 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; @@ -818,6 +830,18 @@ class Reader 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; @@ -982,14 +1006,25 @@ class Reader if (elementRef.name.name.isEmpty() || elementRef.typeName.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, _)) @@ -999,10 +1034,22 @@ class Reader { uint minOccurs = getXmlAttribute(element, "minOccurs", getXmlAttribute(*position.element, "minOccurs", "1")).toUInt(); uint maxOccurs = getXmlAttribute(element, "maxOccurs", getXmlAttribute(*position.element, "maxOccurs", "1")).toUInt(); - + 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; diff --git a/src/xsd.hpp b/src/xsd.hpp index 2ef903b..d91cb23 100644 --- a/src/xsd.hpp +++ b/src/xsd.hpp @@ -12,8 +12,27 @@ namespace xsd { typedef std::string string; +// Wrapper for vector to allow specialization for bool +// std::vector is specialized and .back() returns a proxy, not a real reference template -using vector = std::vector; +class vector : public std::vector +{ +public: + using std::vector::vector; + using std::vector::operator=; +}; + +// Specialization for vector using char storage to avoid proxy issues +template <> +class vector : public std::vector +{ +public: + using std::vector::vector; + 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 From 82335deff2acfa9fd9ce7e3e72ecd1af0b8e41aa Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 07/23] Fix Ore_test to use enum values for TradeType comparison TradeType is generated as oreTradeType enum, not a string. Co-Authored-By: Claude Opus 4.5 --- test/ore/Ore_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ore/Ore_test.cpp b/test/ore/Ore_test.cpp index dad7177..bb14d4e 100644 --- a/test/ore/Ore_test.cpp +++ b/test/ore/Ore_test.cpp @@ -30,9 +30,9 @@ TEST(Ore, load_portfolio) EXPECT_EQ(portfolio.Trade.size(), 2); EXPECT_EQ(portfolio.Trade[0].id, "trade1"); - EXPECT_EQ(portfolio.Trade[0].TradeType, "Swap"); + EXPECT_EQ(portfolio.Trade[0].TradeType, input::oreTradeType::Swap); EXPECT_EQ(portfolio.Trade[1].id, "trade2"); - EXPECT_EQ(portfolio.Trade[1].TradeType, "FxForward"); + EXPECT_EQ(portfolio.Trade[1].TradeType, input::oreTradeType::FxForward); } TEST(Ore, load_empty_portfolio) From 09af66e65ff5ce8a961dbd2f853ed6e1e1b10bcf Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 08/23] Add command line options for separate output dirs and wrap namespace New options: - -H/--header-output: Separate directory for header files (.hpp) - -C/--cpp-output: Separate directory for implementation files (.cpp) - -w/--wrap-namespace: Wrap generated code in additional namespace(s) The wrap namespace option supports C++11-compatible nested namespace syntax (e.g., -w outer::inner generates separate namespace declarations). Co-Authored-By: Claude Opus 4.5 --- src/Generator.cpp | 94 +++++++++++++++++++++++++++++++++++++++-- src/Generator.hpp | 2 +- src/Main.cpp | 34 ++++++++++++++- test/Generator_test.cpp | 4 +- 4 files changed, 126 insertions(+), 8 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 7b77193..2c37677 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); @@ -1321,7 +1366,7 @@ class Generator } -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, String& error) { List cppOutput; List hppOutput; @@ -1330,20 +1375,38 @@ bool generateCpp(const Xsd& xsd, const String& outputDir, const List& ex 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 namespace declaration + if (!wroteNsOpen && i->startsWith("namespace ")) + { + 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; @@ -1354,13 +1417,36 @@ bool generateCpp(const Xsd& xsd, const String& outputDir, const List& ex if (!outputFile.write(XmlParser_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..a7a380d 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, String& error); diff --git a/src/Main.cpp b/src/Main.cpp index 6ca8ace..1a5f3d3 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -18,6 +18,14 @@ 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\ -e , --extern=\n\ A namespace that should not be generated in the output files and hence\n\ @@ -30,6 +38,10 @@ Options:\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\ +\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').\n\ \n\ -t , --type=\n\ By default, C++ type definitions are only generated for types that are\n\ @@ -45,13 +57,19 @@ int main(int argc, char* argv[]) { String inputFile; String outputDir = "."; + String headerOutputDir; + String cppOutputDir; String name; + String wrapNamespace; List externalNamespacePrefixes; List forceTypeProcessing; { Process::Option options[] = { {'o', "output", Process::argumentFlag}, + {'H', "header-output", Process::argumentFlag}, + {'C', "cpp-output", 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 +84,18 @@ int main(int argc, char* argv[]) case 'o': outputDir = argument; break; + case 'H': + headerOutputDir = argument; + break; + case 'C': + cppOutputDir = argument; + break; case 'n': name = argument; break; + case 'w': + wrapNamespace = argument; + break; case 'e': externalNamespacePrefixes.append(argument); break; @@ -89,6 +116,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 +130,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, error)) { Console::errorf("error: %s\n", (const char*)error); return 1; diff --git a/test/Generator_test.cpp b/test/Generator_test.cpp index 20f2fdb..0040049 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(), 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(), error)); } } From a07112504fc4efcfc23a20e53928594d58ba5f90 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 09/23] Add --no-inner-namespace option to skip inner namespace generation When -N/--no-inner-namespace is specified, types are placed directly in the wrap namespace without an additional inner namespace derived from the schema name. Example: ores::ore::domain::Portfolio instead of ores::ore::domain::input::Portfolio Co-Authored-By: Claude Opus 4.5 --- src/Generator.cpp | 50 ++++++++++++++++++++++++++++------------- src/Generator.hpp | 2 +- src/Main.cpp | 11 ++++++++- test/Generator_test.cpp | 4 ++-- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 2c37677..cd24517 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -171,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, bool noInnerNamespace) : _xsd(xsd) , _externalNamespacePrefixes(externalNamespacePrefixes) , _cppOutputFinal(cppOutput) , _hppOutput(hppOutput) + , _noInnerNamespace(noInnerNamespace) { for (List::Iterator i = forceTypeProcessing.begin(), end = forceTypeProcessing.end(); i != end; ++i) { @@ -259,8 +260,11 @@ class Generator _hppOutput.append(String("#include \"") + _cppNamespace + "_xsd.hpp\""); _hppOutput.append(""); - _hppOutput.append(String("namespace ") + _cppNamespace + " {"); - _hppOutput.append(""); + if (!_noInnerNamespace) + { + _hppOutput.append(String("namespace ") + _cppNamespace + " {"); + _hppOutput.append(""); + } for (HashSet::Iterator i = localElementTypes.begin(), end = localElementTypes.end(); i != end; ++i) { @@ -299,13 +303,16 @@ class Generator _hppOutput.append(""); } - _hppOutput.append("}"); + if (!_noInnerNamespace) + _hppOutput.append("}"); - _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); + if (!_noInnerNamespace) + _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); _cppOutputFinal.append(""); _cppOutputFinal.append(_cppOutputNamespaceElementInfoExtern); _cppOutputFinal.append(""); - _cppOutputFinal.append("}"); + if (!_noInnerNamespace) + _cppOutputFinal.append("}"); _cppOutputFinal.append(""); _cppOutputFinal.append("namespace {"); @@ -315,11 +322,13 @@ class Generator _cppOutputFinal.append("}"); _cppOutputFinal.append(""); - _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); + if (!_noInnerNamespace) + _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); _cppOutputFinal.append(""); _cppOutputFinal.append(_cppOutputNamespaceSetValue); _cppOutputFinal.append(""); - _cppOutputFinal.append("}"); + if (!_noInnerNamespace) + _cppOutputFinal.append("}"); _cppOutputFinal.append(""); _cppOutputFinal.append("namespace {"); @@ -329,7 +338,8 @@ class Generator _cppOutputFinal.append("}"); _cppOutputFinal.append(""); - _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); + if (!_noInnerNamespace) + _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); _cppOutputFinal.append(""); _cppOutputFinal.append(_cppOutputNamespace); _cppOutputFinal.append(""); @@ -357,7 +367,8 @@ class Generator } - _cppOutputFinal.append("}"); + if (!_noInnerNamespace) + _cppOutputFinal.append("}"); _cppOutputFinal.append(""); return true; @@ -366,6 +377,7 @@ class Generator private: const Xsd& _xsd; const List& _externalNamespacePrefixes; + bool _noInnerNamespace; HashMap _externalNamespaces; @@ -458,6 +470,8 @@ class Generator String namespacePrefix; if (isNamespaceExternal(typeName.xsdNamespace, namespacePrefix)) return namespacePrefix + "::" + result; + if (_noInnerNamespace) + return result; return _cppNamespace + "::" + result; } @@ -466,6 +480,8 @@ class Generator String namespacePrefix; if (isNamespaceExternal(typeName.xsdNamespace, namespacePrefix)) return namespacePrefix; + if (_noInnerNamespace) + return String(); return _cppNamespace; } @@ -485,7 +501,10 @@ class Generator return String("xsdcpp::set_") + cppName; if (cppName == "bool") return String("xsdcpp::set_bool"); - return toCppNamespacePrefix(typeName) + "::_set_" + cppName; + String prefix = toCppNamespacePrefix(typeName); + if (prefix.isEmpty()) + return String("_set_") + cppName; + return prefix + "::_set_" + cppName; } usize getChildrenCount(const Xsd::Name& typeName) const @@ -1366,11 +1385,11 @@ class Generator } -bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cppOutputDir, const List& excludedNamespacePrefixes, const List& forceTypeProcessing, const String& wrapNamespace, String& error) +bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cppOutputDir, const List& excludedNamespacePrefixes, const List& forceTypeProcessing, const String& wrapNamespace, bool noInnerNamespace, String& error) { List cppOutput; List hppOutput; - Generator generator(xsd, excludedNamespacePrefixes, forceTypeProcessing, cppOutput, hppOutput); + Generator generator(xsd, excludedNamespacePrefixes, forceTypeProcessing, cppOutput, hppOutput, noInnerNamespace); if (!generator.process()) return (error = generator.getError()), false; @@ -1388,8 +1407,9 @@ bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cp bool wroteNsOpen = wrapNamespace.isEmpty(); for (List::Iterator i = hppOutput.begin(), end = hppOutput.end(); i != end; ++i) { - // Insert wrap namespace before the first namespace declaration - if (!wroteNsOpen && i->startsWith("namespace ")) + // 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; diff --git a/src/Generator.hpp b/src/Generator.hpp index a7a380d..76da087 100644 --- a/src/Generator.hpp +++ b/src/Generator.hpp @@ -3,4 +3,4 @@ #include "Reader.hpp" -bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cppOutputDir, const List& externalNamespacePrefixes, const List& forceTypeProcessing, const String& wrapNamespace, String& error); +bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cppOutputDir, const List& externalNamespacePrefixes, const List& forceTypeProcessing, const String& wrapNamespace, bool noInnerNamespace, String& error); diff --git a/src/Main.cpp b/src/Main.cpp index 1a5f3d3..fbe8d72 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -42,6 +42,10 @@ Options:\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').\n\ +\n\ + -N, --no-inner-namespace\n\ + Skip generating the inner namespace (derived from --name or schema\n\ + filename). Types will be placed directly in the wrap namespace.\n\ \n\ -t , --type=\n\ By default, C++ type definitions are only generated for types that are\n\ @@ -61,6 +65,7 @@ int main(int argc, char* argv[]) String cppOutputDir; String name; String wrapNamespace; + bool noInnerNamespace = false; List externalNamespacePrefixes; List forceTypeProcessing; { @@ -70,6 +75,7 @@ int main(int argc, char* argv[]) {'C', "cpp-output", Process::argumentFlag}, {'n', "name", Process::argumentFlag}, {'w', "wrap-namespace", Process::argumentFlag}, + {'N', "no-inner-namespace", Process::optionFlag}, {'h', "help", Process::optionFlag}, {'e', "extern", Process::argumentFlag}, {'t', "type", Process::argumentFlag}, @@ -96,6 +102,9 @@ int main(int argc, char* argv[]) case 'w': wrapNamespace = argument; break; + case 'N': + noInnerNamespace = true; + break; case 'e': externalNamespacePrefixes.append(argument); break; @@ -130,7 +139,7 @@ int main(int argc, char* argv[]) String error; Xsd xsd; if (!readXsd(name, inputFile, forceTypeProcessing, xsd, error) || - !generateCpp(xsd, headerOutputDir, cppOutputDir, externalNamespacePrefixes, forceTypeProcessing, wrapNamespace, error)) + !generateCpp(xsd, headerOutputDir, cppOutputDir, externalNamespacePrefixes, forceTypeProcessing, wrapNamespace, noInnerNamespace, error)) { Console::errorf("error: %s\n", (const char*)error); return 1; diff --git a/test/Generator_test.cpp b/test/Generator_test.cpp index 0040049..7da4652 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", "test_temp", List(), List(), String(), error)); + EXPECT_TRUE(generateCpp(xsd, "test_temp", "test_temp", List(), List(), String(), false, 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", "test_temp", List(), List(), String(), error)); + EXPECT_TRUE(generateCpp(xsd, "test_temp", "test_temp", List(), List(), String(), false, error)); } } From 887660fc7cbb65c13123064bede7dce580dac416 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 10/23] Document command line options in README Add a "Command Line Options" section with a table of all options and examples showing common usage patterns. Co-Authored-By: Claude Opus 4.5 --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cdae696..49837c7 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`). | +| `-N`, `--no-inner-namespace` | Skip generating the inner namespace (derived from `--name` or schema filename). Types will be placed directly in the wrap namespace. | +| `-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`. + +Skip the inner namespace to place types directly in the wrap namespace: +``` +xsdcpp Example.xsd -o out/ -w myproject::xml -N +``` +This generates types like `myproject::xml::Person`. + ## Example An XSD schema *Example.xsd* like this: @@ -197,4 +238,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.. From 68baae1c4fb4c3a511f9df1a14c9f29247a60709 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 11/23] Add XML save/serialize functionality Add symmetric save_file() and save_data() functions to complement the existing load_file() and load_data() functionality. The generated API: void save_file(const std::string& file, const Type& t); std::string save_data(const Type& t); Implementation includes: - New XmlWriter class for building XML output with proper indentation - get_string() overloads for all primitive types - escape_xml() for XML character escaping - XSDCPP_MAYBE_UNUSED macro for cross-platform compatibility - _serialize_*() functions generated for all types - Handles vectors, optionals, inheritance, and substitution groups - Round-trip tests to verify save/load consistency Co-Authored-By: Claude Opus 4.5 --- src/CMakeLists.txt | 4 +- src/Generator.cpp | 415 ++++++++++++++++++++++++++++++++++++++--- src/XmlWriter.cpp | 9 + src/XmlWriter.hpp | 173 +++++++++++++++++ test/Features_test.cpp | 74 ++++++++ 5 files changed, 644 insertions(+), 31 deletions(-) create mode 100644 src/XmlWriter.cpp create mode 100644 src/XmlWriter.hpp 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 cd24517..ec3c361 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -171,12 +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, bool noInnerNamespace) + 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) - , _noInnerNamespace(noInnerNamespace) + , _includePrefix(includePrefix) { for (List::Iterator i = forceTypeProcessing.begin(), end = forceTypeProcessing.end(); i != end; ++i) { @@ -214,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) @@ -260,11 +263,8 @@ class Generator _hppOutput.append(String("#include \"") + _cppNamespace + "_xsd.hpp\""); _hppOutput.append(""); - if (!_noInnerNamespace) - { - _hppOutput.append(String("namespace ") + _cppNamespace + " {"); - _hppOutput.append(""); - } + _hppOutput.append(String("namespace ") + _cppNamespace + " {"); + _hppOutput.append(""); for (HashSet::Iterator i = localElementTypes.begin(), end = localElementTypes.end(); i != end; ++i) { @@ -300,19 +300,18 @@ 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(""); } - if (!_noInnerNamespace) - _hppOutput.append("}"); + _hppOutput.append("}"); - if (!_noInnerNamespace) - _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); + _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); _cppOutputFinal.append(""); _cppOutputFinal.append(_cppOutputNamespaceElementInfoExtern); _cppOutputFinal.append(""); - if (!_noInnerNamespace) - _cppOutputFinal.append("}"); + _cppOutputFinal.append("}"); _cppOutputFinal.append(""); _cppOutputFinal.append("namespace {"); @@ -322,24 +321,23 @@ class Generator _cppOutputFinal.append("}"); _cppOutputFinal.append(""); - if (!_noInnerNamespace) - _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); + _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); _cppOutputFinal.append(""); _cppOutputFinal.append(_cppOutputNamespaceSetValue); _cppOutputFinal.append(""); - if (!_noInnerNamespace) - _cppOutputFinal.append("}"); + _cppOutputFinal.append("}"); _cppOutputFinal.append(""); _cppOutputFinal.append("namespace {"); _cppOutputFinal.append(""); _cppOutputFinal.append(_cppOutputAnonymousFieldGetter); _cppOutputFinal.append(""); + _cppOutputFinal.append(_cppOutputAnonymousSerialize); + _cppOutputFinal.append(""); _cppOutputFinal.append("}"); _cppOutputFinal.append(""); - if (!_noInnerNamespace) - _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); + _cppOutputFinal.append(String("namespace ") + _cppNamespace + " {"); _cppOutputFinal.append(""); _cppOutputFinal.append(_cppOutputNamespace); _cppOutputFinal.append(""); @@ -364,11 +362,24 @@ 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(""); - if (!_noInnerNamespace) + _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(""); + } + + + _cppOutputFinal.append("}"); _cppOutputFinal.append(""); return true; @@ -377,7 +388,7 @@ class Generator private: const Xsd& _xsd; const List& _externalNamespacePrefixes; - bool _noInnerNamespace; + String _includePrefix; HashMap _externalNamespaces; @@ -386,6 +397,7 @@ class Generator List _cppOutputAnonymousEnumValues; List _cppOutputNamespaceSetValue; List _cppOutputAnonymousFieldGetter; + List _cppOutputAnonymousSerialize; List _cppOutputNamespace; List& _hppOutput; String _cppNamespace; @@ -393,6 +405,8 @@ class Generator 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; @@ -470,8 +484,6 @@ class Generator String namespacePrefix; if (isNamespaceExternal(typeName.xsdNamespace, namespacePrefix)) return namespacePrefix + "::" + result; - if (_noInnerNamespace) - return result; return _cppNamespace + "::" + result; } @@ -480,8 +492,6 @@ class Generator String namespacePrefix; if (isNamespaceExternal(typeName.xsdNamespace, namespacePrefix)) return namespacePrefix; - if (_noInnerNamespace) - return String(); return _cppNamespace; } @@ -1376,20 +1386,362 @@ class Generator _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); + _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); + _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) + { + // 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) + { + // Enum is serialized as text using to_string + _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) + { + // String types are serialized as text + _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) + { + // List is serialized as space-separated values + 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) + { + // 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) + { + // 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);"); + } + } + } + + 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& headerOutputDir, const String& cppOutputDir, const List& excludedNamespacePrefixes, const List& forceTypeProcessing, const String& wrapNamespace, bool noInnerNamespace, 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, noInnerNamespace); + Generator generator(xsd, excludedNamespacePrefixes, forceTypeProcessing, cppOutput, hppOutput, includePrefix); if (!generator.process()) return (error = generator.getError()), false; @@ -1436,6 +1788,11 @@ bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cp 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: 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/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 From 2d280f1cca3ac5522dacfa3542f793e40e53c1b8 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 12/23] Add include-prefix option and update README with save functions Command line changes: - Add -P/--include-prefix option for #include directive prefix in generated .cpp files when headers are in a different directory Documentation: - Add -P/--include-prefix to command line options table - Document save_file() and save_data() alongside load functions - Fix parameter name consistency (List -> list) Tests: - Add WrapNamespace_test for -w option - Add RenameNamespace_test for -w and -n options combined - Update Generator_test for new generateCpp() signature Co-Authored-By: Claude Opus 4.5 --- README.md | 16 +++++++++------- src/Generator.hpp | 2 +- src/Main.cpp | 30 ++++++++++++++++------------- test/CMakeLists.txt | 36 ++++++++++++++++++++++++++++++++++- test/Generator_test.cpp | 4 ++-- test/RenameNamespace_test.cpp | 24 +++++++++++++++++++++++ test/WrapNamespace_test.cpp | 24 +++++++++++++++++++++++ 7 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 test/RenameNamespace_test.cpp create mode 100644 test/WrapNamespace_test.cpp diff --git a/README.md b/README.md index 49837c7..6f2fbef 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ xsdcpp [] [options] | `-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`). | -| `-N`, `--no-inner-namespace` | Skip generating the inner namespace (derived from `--name` or schema filename). Types will be placed directly in the wrap namespace. | +| `-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. | @@ -96,11 +96,11 @@ xsdcpp Example.xsd -o out/ -w myproject::xml ``` This generates types like `myproject::xml::Example::Person`. -Skip the inner namespace to place types directly in the wrap namespace: +Rename the inner namespace and wrap in an outer namespace: ``` -xsdcpp Example.xsd -o out/ -w myproject::xml -N +xsdcpp Example.xsd -o out/ -w myproject::xml -n types ``` -This generates types like `myproject::xml::Person`. +This generates types like `myproject::xml::types::Person`. ## Example @@ -201,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.) diff --git a/src/Generator.hpp b/src/Generator.hpp index 76da087..9e41fdc 100644 --- a/src/Generator.hpp +++ b/src/Generator.hpp @@ -3,4 +3,4 @@ #include "Reader.hpp" -bool generateCpp(const Xsd& xsd, const String& headerOutputDir, const String& cppOutputDir, const List& externalNamespacePrefixes, const List& forceTypeProcessing, const String& wrapNamespace, bool noInnerNamespace, 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 fbe8d72..24d68ef 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -26,6 +26,12 @@ Options:\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\ @@ -36,16 +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').\n\ -\n\ - -N, --no-inner-namespace\n\ - Skip generating the inner namespace (derived from --name or schema\n\ - filename). Types will be placed directly in the wrap namespace.\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\ @@ -65,7 +69,7 @@ int main(int argc, char* argv[]) String cppOutputDir; String name; String wrapNamespace; - bool noInnerNamespace = false; + String includePrefix; List externalNamespacePrefixes; List forceTypeProcessing; { @@ -73,9 +77,9 @@ int main(int argc, char* argv[]) {'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}, - {'N', "no-inner-namespace", Process::optionFlag}, {'h', "help", Process::optionFlag}, {'e', "extern", Process::argumentFlag}, {'t', "type", Process::argumentFlag}, @@ -96,15 +100,15 @@ int main(int argc, char* argv[]) case 'C': cppOutputDir = argument; break; + case 'P': + includePrefix = argument; + break; case 'n': name = argument; break; case 'w': wrapNamespace = argument; break; - case 'N': - noInnerNamespace = true; - break; case 'e': externalNamespacePrefixes.append(argument); break; @@ -139,7 +143,7 @@ int main(int argc, char* argv[]) String error; Xsd xsd; if (!readXsd(name, inputFile, forceTypeProcessing, xsd, error) || - !generateCpp(xsd, headerOutputDir, cppOutputDir, externalNamespacePrefixes, forceTypeProcessing, wrapNamespace, noInnerNamespace, error)) + !generateCpp(xsd, headerOutputDir, cppOutputDir, externalNamespacePrefixes, forceTypeProcessing, wrapNamespace, includePrefix, error)) { Console::errorf("error: %s\n", (const char*)error); return 1; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2af60ac..4aa4d5b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -101,11 +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 Ore_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/Generator_test.cpp b/test/Generator_test.cpp index 7da4652..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", "test_temp", List(), List(), String(), false, 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", "test_temp", List(), List(), String(), false, 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); +} From 773707fd13fb74dabe9905053f246bd092826aba Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 13/23] Add support for group refs, substitution groups, and fix unbounded in choice - Add xs:group definition storage and xs:group ref expansion - Process elements with substitutionGroup attribute properly - Fix maxOccurs="unbounded" inside xs:choice to generate vectors - Add forward declarations for serialize functions to handle recursive types - Change processedElements2 to dynamic vector to support types with many children Co-Authored-By: Claude Opus 4.5 --- src/Generator.cpp | 27 +++- src/Reader.cpp | 320 +++++++++++++++++++++++++++++++++++++++++++--- src/Reader.hpp | 3 + src/XmlParser.cpp | 2 +- src/XmlParser.hpp | 3 +- 5 files changed, 332 insertions(+), 23 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index ec3c361..6aee7c0 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -332,6 +332,11 @@ 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("}"); @@ -397,7 +402,8 @@ class Generator List _cppOutputAnonymousEnumValues; List _cppOutputNamespaceSetValue; List _cppOutputAnonymousFieldGetter; - List _cppOutputAnonymousSerialize; + List _cppOutputAnonymousSerializeDecl; // Forward declarations + List _cppOutputAnonymousSerialize; // Implementations List _cppOutputNamespace; List& _hppOutput; String _cppNamespace; @@ -1421,6 +1427,7 @@ class Generator 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);"); @@ -1437,6 +1444,7 @@ class Generator 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));")); @@ -1449,6 +1457,9 @@ class Generator 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) { @@ -1473,7 +1484,8 @@ class Generator if (type.kind == Xsd::Type::EnumKind) { - // Enum is serialized as text using to_string + // 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));"); @@ -1485,7 +1497,8 @@ class Generator if (type.kind == Xsd::Type::StringKind || type.kind == Xsd::Type::UnionKind) { - // String types are serialized as text + // 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);"); @@ -1497,7 +1510,8 @@ class Generator if (type.kind == Xsd::Type::ListKind) { - // List is serialized as space-separated values + // 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) @@ -1519,6 +1533,8 @@ class Generator 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; @@ -1532,6 +1548,9 @@ class Generator 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()) { diff --git a/src/Reader.cpp b/src/Reader.cpp index 192470e..38ff2be 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() @@ -391,6 +408,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(':'); @@ -438,7 +463,7 @@ class Reader getXmlAttribute(*refPos.element, "type").isEmpty()) { 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; } @@ -447,7 +472,7 @@ class Reader 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; } @@ -478,7 +503,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"); @@ -562,7 +587,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")); return true; } @@ -751,8 +776,7 @@ 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) { @@ -770,9 +794,78 @@ class Reader 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")); + + 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 (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; + } } } } @@ -824,8 +917,7 @@ 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) { @@ -843,9 +935,77 @@ class Reader 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")); + + 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 (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; + } } } } @@ -1032,8 +1192,7 @@ 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) { @@ -1051,9 +1210,83 @@ class Reader 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")); + + // 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); + // If the group ref is optional, make all elements optional + if (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; + } } } } @@ -1080,6 +1313,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..7f15077 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 diff --git a/src/XmlParser.cpp b/src/XmlParser.cpp index fda2fdf..5257a4d 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 diff --git a/src/XmlParser.hpp b/src/XmlParser.hpp index c862e4d..e5144cb 100644 --- a/src/XmlParser.hpp +++ b/src/XmlParser.hpp @@ -1,6 +1,7 @@ #include #include +#include namespace xsdcpp { @@ -58,7 +59,7 @@ struct ElementContext { const ElementInfo* info; void* element; - size_t processedElements2[64]; + std::vector processedElements2; uint64_t processedAttributes2; ElementContext(const ElementInfo* info, void* element); From de691ca9b6fd62f8d5ad5aab09093b76e0ce7635 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 14/23] Fix abstract element refs (substitution groups) being silently dropped Abstract elements without a type attribute (used as substitution group heads) were silently dropped when referenced via xs:element ref. Two issues caused this: the element name was never set for abstract refs, and the caller skipped elements with empty typeName even when a refName was pending resolution. Also add substitution group registration for elements with inline complex types, and improve resolveElementRefs() to remove unresolvable refs instead of leaving typeless elements. Co-Authored-By: Claude Opus 4.6 --- src/Reader.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/Reader.cpp b/src/Reader.cpp index 38ff2be..c8df38d 100644 --- a/src/Reader.cpp +++ b/src/Reader.cpp @@ -155,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); } } } @@ -462,6 +468,7 @@ class Reader 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; @@ -588,6 +595,41 @@ class Reader 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 true; } @@ -1163,7 +1205,7 @@ 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; // Skip if an element with the same name already exists (can happen with choice branches) From b0fef6d0757374821685fb20410ed6f61d5f3fc6 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 15/23] Fix xs:any for complex types, add CDATA support, and fix choice group minOccurs Three bug fixes: 1. Generator.cpp: xs:any processContents="lax" on complex types (e.g. AdditionalFields) was incorrectly getting SkipMode instead of SkipProcessingMode because the SkipProcessContentsFlag check only applied inside the StringKind branch. Moved the check before the kind-specific logic and set addText=nullptr for complex types in SkipProcessingMode. 2. XmlParser.cpp: Added CDATA section support. skipText was infinite-looping on ' expands a , mark each choice element as minOccurs=0 (they are mutually exclusive alternatives, not individually required). Previously, group refs without explicit minOccurs="0" caused all choice elements to be marked minOccurs=1, breaking parsing of any non-first alternative. Co-Authored-By: Claude Sonnet 4.6 --- src/Generator.cpp | 21 +++++++++++++++++---- src/Reader.cpp | 39 +++++++++++++++++++++++++++++++++++---- src/XmlParser.cpp | 43 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 6aee7c0..74abbc6 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -639,13 +639,13 @@ 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) return ReadAndProcessTextMode; return SkipMode; @@ -918,7 +918,20 @@ 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) + { + addTextFunction = "nullptr"; + return true; + } + } if (!generateTypeSetter(typeName)) return false; diff --git a/src/Reader.cpp b/src/Reader.cpp index c8df38d..bf2ee1e 100644 --- a/src/Reader.cpp +++ b/src/Reader.cpp @@ -694,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); @@ -883,6 +901,10 @@ class Reader 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; @@ -899,7 +921,7 @@ class Reader continue; Xsd::ElementRef& elementRef = elements.append(groupElem); - if (groupRefMinOccurs == 0) + if (groupIsChoice || groupRefMinOccurs == 0) elementRef.minOccurs = 0; if (groupRefMaxOccurs == UNBOUNDED || groupElem.maxOccurs == UNBOUNDED) elementRef.maxOccurs = UNBOUNDED; @@ -1023,6 +1045,9 @@ class Reader 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; @@ -1039,7 +1064,7 @@ class Reader continue; Xsd::ElementRef& elementRef = elements.append(groupElem); - if (groupRefMinOccurs == 0) + if (groupIsChoice || groupRefMinOccurs == 0) elementRef.minOccurs = 0; if (groupRefMaxOccurs == UNBOUNDED || groupElem.maxOccurs == UNBOUNDED) elementRef.maxOccurs = UNBOUNDED; @@ -1300,6 +1325,11 @@ class Reader 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) { @@ -1318,8 +1348,9 @@ class Reader continue; Xsd::ElementRef& elementRef = elements.append(groupElem); - // If the group ref is optional, make all elements optional - if (groupRefMinOccurs == 0) + // 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) diff --git a/src/XmlParser.cpp b/src/XmlParser.cpp index 5257a4d..9a7f93a 100644 --- a/src/XmlParser.cpp +++ b/src/XmlParser.cpp @@ -159,6 +159,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 +290,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; @@ -527,7 +568,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)); From bf2b363b2854b40a97452d1b13b1f55664c76336 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 16/23] Fix xs:union types generating { 0, nullptr } ElementInfo (missing ReadTextFlag) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xs:union types were resolved to typedef xsd::string in the header (correct), but their ElementInfo was emitted as { 0, nullptr } instead of { ReadTextFlag, set_string } because getReadTextMode() only returned ReadAndProcessTextMode for StringKind, BaseKind, EnumKind, and ListKind — UnionKind was missing from the list, so it fell through to SkipMode. Without ReadTextFlag the XML parser never reads the element's text content, so a field typed as an xs:union (e.g. extendedCurrencyCode) would always round-trip as an empty string regardless of the XML value. Fix: add UnionKind to the ReadAndProcessTextMode check in getReadTextMode(), and add it to the parallel guard in generateAddTextFunction2() for the SkipProcessingMode (xs:any) path for consistency. Co-Authored-By: Claude Sonnet 4.6 --- src/Generator.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 74abbc6..0d54b00 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -646,7 +646,7 @@ class Generator Xsd::Type rootType = getType(getRootTypeName(typeName)); if (rootType.kind == Xsd::Type::Kind::StringKind) 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; } @@ -926,7 +926,8 @@ class Generator 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::ListKind && + rootType2.kind != Xsd::Type::Kind::UnionKind) { addTextFunction = "nullptr"; return true; From 0e585343a9568548ce5e520769ce74e545ebd6fd Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 17/23] Add xs:any child element capture via other_elements field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xs:any with processContents="lax" (used by ORE's AdditionalFields envelope) was handled by SkipProcessContentsFlag, which silently discarded all free-form child elements. This made faithful round-tripping impossible for any ORE portfolio using AdditionalFields as an extension point. The fix mirrors the xs:anyAttribute → other_attributes mechanism and adds an analogous xs:any → other_elements capture path: - xsd.hpp: add xsd::any_element { name, value } struct - XmlParser.hpp: add AnyElementFlag = 0x20, set_any_element_t typedef, and setOtherElement callback field to ElementInfo - XmlParser.cpp: when enterElement cannot match a known child but the parent has AnyElementFlag, return a thread-local capture context instead of throwing "Unexpected element"; after checkElement, call the parent's setOtherElement callback with the captured name and text value - Reader.hpp: add AnyElementFlag = 4 to Xsd::Type::Flags - Reader.cpp: xs:any with processContents="skip" keeps SkipProcessContentsFlag (truly discard); "lax", "strict", or unspecified now sets AnyElementFlag; also handle xs:any as a direct child of xs:complexType - Generator.cpp: when AnyElementFlag is set, emit the other_elements field, _any_elem_* setter, AnyElementFlag in ElementInfo, setOtherElement pointer, and a serialization loop writing each captured element as value Co-Authored-By: Claude Sonnet 4.6 --- src/Generator.cpp | 15 +++++++++++++-- src/Reader.cpp | 8 +++++++- src/Reader.hpp | 1 + src/XmlParser.cpp | 37 +++++++++++++++++++++++++++++++++++++ src/XmlParser.hpp | 3 +++ src/xsd.hpp | 6 ++++++ 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index 0d54b00..cc76855 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -1201,6 +1201,8 @@ class Generator } 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; @@ -1347,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()) @@ -1381,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"); @@ -1396,12 +1402,13 @@ 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); @@ -1719,6 +1726,10 @@ class Generator _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) diff --git a/src/Reader.cpp b/src/Reader.cpp index bf2ee1e..bae0bfb 100644 --- a/src/Reader.cpp +++ b/src/Reader.cpp @@ -1091,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); } @@ -1375,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); diff --git a/src/Reader.hpp b/src/Reader.hpp index 7f15077..7b83360 100644 --- a/src/Reader.hpp +++ b/src/Reader.hpp @@ -75,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 9a7f93a..7ba1ff7 100644 --- a/src/XmlParser.cpp +++ b/src/XmlParser.cpp @@ -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 @@ -447,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 + "'"); } @@ -597,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 e5144cb..abff6f9 100644 --- a/src/XmlParser.hpp +++ b/src/XmlParser.hpp @@ -13,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 { @@ -43,6 +44,7 @@ struct ElementInfo SkipProcessingFlag = 0x04, AnyAttributeFlag = 0x08, CheckChildrenFlag = 0x10, + AnyElementFlag = 0x20, }; size_t flags; @@ -53,6 +55,7 @@ struct ElementInfo uint64_t checkAttributeMask; const ElementInfo* base; set_any_attribute_t setOtherAttribute; + set_any_element_t setOtherElement; }; struct ElementContext diff --git a/src/xsd.hpp b/src/xsd.hpp index d91cb23..284f0e2 100644 --- a/src/xsd.hpp +++ b/src/xsd.hpp @@ -162,6 +162,12 @@ struct any_attribute xsd::string value; }; +struct any_element +{ + std::string name; + xsd::string value; +}; + } #endif From 1146c4d24ad390f05b37d5a6ae5bee5a16baafe2 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 18/23] Value-initialise mandatory struct fields to fix uninitialised member UB Mandatory element and attribute fields (minOccurs=1/maxOccurs=1, non-optional attributes) were emitted as plain `T field;` declarations, leaving primitive members (bool, int, float, etc.) uninitialised in the default-constructed aggregate. Accessing those members before parsing is technically UB and reliably detected by Valgrind Memcheck as Cond/Value8 errors. Append `{}` to the generated declaration so every mandatory field is value-initialised: false for bool, 0 for integers and floats, empty for std::string, and recursively zero-initialised for nested struct types. Co-Authored-By: Claude Sonnet 4.6 --- src/Generator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Generator.cpp b/src/Generator.cpp index cc76855..1f7a2cd 100644 --- a/src/Generator.cpp +++ b/src/Generator.cpp @@ -1197,7 +1197,7 @@ 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"); @@ -1210,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 From a4f39f41f501ef8e19b28d4f32d708f168cf6cc1 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 19/23] [xsd] Fix vector to use alias instead of inheritance for non-bool types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit class vector : public std::vector requires T to be complete at the point of instantiation on MSVC/Clang-CL, because the inheritance forces std::vector's template machinery to run type traits on T. xsdcpp-generated headers forward- declare types before their definitions, so members like xsd::vector were emitted with lgm still incomplete, causing Windows build failures. Refactor: introduce detail::bool_vector (the char-backed class) and detail::vector_selector to select between std::vector and bool_vector. xsd::vector is now a using-alias that resolves to std::vector for non-bool types — preserving MSVC's lenient incomplete-type handling — and to bool_vector for bool. Co-Authored-By: Claude Sonnet 4.6 --- src/xsd.hpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/xsd.hpp b/src/xsd.hpp index 284f0e2..56dd8b4 100644 --- a/src/xsd.hpp +++ b/src/xsd.hpp @@ -12,19 +12,10 @@ namespace xsd { typedef std::string string; -// Wrapper for vector to allow specialization for bool -// std::vector is specialized and .back() returns a proxy, not a real reference -template -class vector : public std::vector -{ -public: - using std::vector::vector; - using std::vector::operator=; -}; +namespace detail { -// Specialization for vector using char storage to avoid proxy issues -template <> -class vector : public std::vector +// Custom bool vector using char storage to avoid std::vector's proxy type. +class bool_vector : public std::vector { public: using std::vector::vector; @@ -34,6 +25,20 @@ class vector : public std::vector const bool& back() const { return reinterpret_cast(std::vector::back()); } }; +template +struct vector_selector { using type = std::vector; }; + +template <> +struct vector_selector { using type = bool_vector; }; + +} // namespace detail + +// vector is std::vector for all non-bool types (preserving MSVC's lenient +// handling of incomplete element types in generated headers), and the char-backed +// bool_vector for bool (avoiding std::vector's bit-packing proxy). +template +using vector = typename detail::vector_selector::type; + template class optional { From e18d68db3f9eae8135f9e8f2059c1d03a1f47c23 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 20/23] [xsd] Add missing operator= to bool_vector Co-Authored-By: Claude Sonnet 4.6 --- src/xsd.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xsd.hpp b/src/xsd.hpp index 56dd8b4..d274df3 100644 --- a/src/xsd.hpp +++ b/src/xsd.hpp @@ -19,6 +19,7 @@ class bool_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()); } From 04897518437a6d95fea25c4b6dd6053599cc66da Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 21/23] [xsd] Fix vector incomplete type failure on GCC/Clang + libstdc++ 15 Replace the type-alias-based xsd::vector with a pointer-based wrapper that stores std::vector* impl_. This avoids requiring T to be a complete type at class-definition time. With libstdc++ 15 the compiler eagerly evaluates is_trivially_destructible the moment any struct containing std::vector is defined, even when T is only forward-declared. Generated domain headers routinely use xsd::vector hundreds of lines before T is fully defined, causing hard errors under GCC 15 and Clang + libstdc++ 15. Using a pointer defers all T-completeness requirements to method call sites, where T is always fully defined. The destructor is declared out-of-class for the same reason (delete impl_ requires a complete T). Full standard-container typedefs (value_type, iterator, reverse_iterator, etc.) are added using T directly to avoid instantiating std::vector at typedef time, satisfying algorithms such as std::transform that inspect these traits. Co-Authored-By: Claude Sonnet 4.6 --- src/xsd.hpp | 136 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 21 deletions(-) diff --git a/src/xsd.hpp b/src/xsd.hpp index d274df3..e97e6ea 100644 --- a/src/xsd.hpp +++ b/src/xsd.hpp @@ -1,22 +1,130 @@ - #pragma once #ifndef XSDCPP_H #define XSDCPP_H -#include +#include +#include #include #include +#include namespace xsd { typedef std::string string; -namespace detail { +// 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_; -// Custom bool vector using char storage to avoid std::vector's proxy type. -class bool_vector : public std::vector -{ +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) { delete impl_; impl_ = new std::vector(*o.impl_); } + 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 +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=; @@ -26,20 +134,6 @@ class bool_vector : public std::vector const bool& back() const { return reinterpret_cast(std::vector::back()); } }; -template -struct vector_selector { using type = std::vector; }; - -template <> -struct vector_selector { using type = bool_vector; }; - -} // namespace detail - -// vector is std::vector for all non-bool types (preserving MSVC's lenient -// handling of incomplete element types in generated headers), and the char-backed -// bool_vector for bool (avoiding std::vector's bit-packing proxy). -template -using vector = typename detail::vector_selector::type; - template class optional { @@ -144,7 +238,7 @@ class base : _value() { } - base(T value) + base(T value) : _value(value) { } From bb69d60fb7bb44425feb6745103e05378cfd2385 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 22/23] [xsd] Restore include dropped during vector rewrite The rewrite added and but accidentally dropped . Generated domain headers use uint64_t (for xs:unsignedLong) which requires . Co-Authored-By: Claude Sonnet 4.6 --- src/xsd.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xsd.hpp b/src/xsd.hpp index e97e6ea..1dc5bf5 100644 --- a/src/xsd.hpp +++ b/src/xsd.hpp @@ -4,6 +4,7 @@ #define XSDCPP_H #include +#include #include #include #include From 5feaf44e121a7670fafae387eace351506b0bb38 Mon Sep 17 00:00:00 2001 From: Marco Craveiro Date: Sat, 1 Aug 2026 11:01:47 +0200 Subject: [PATCH 23/23] [xsd] Fix copy-assignment exception safety and moved-from null deref Allocate the new std::vector before deleting the old one so that a std::bad_alloc leaves *this unchanged. Also guard against o.impl_ being null (moved-from state) instead of unconditionally dereferencing it. Co-Authored-By: Claude Sonnet 4.6 --- src/xsd.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/xsd.hpp b/src/xsd.hpp index 1dc5bf5..6738aad 100644 --- a/src/xsd.hpp +++ b/src/xsd.hpp @@ -60,7 +60,13 @@ class vector { // ---- Assignment -------------------------------------------------------- vector& operator=(const vector& o) { - if (this != &o) { delete impl_; impl_ = new std::vector(*o.impl_); } + 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; }