28#include <unordered_set>
29#include <unordered_map>
35#include "musx/util/Logger.h"
36#include "musx/factory/FactoryExceptions.h"
37#include "musx/xml/XmlInterface.h"
38#include "musx/dom/BaseClasses.h"
39#include "musx/dom/Document.h"
40#include "musx/factory/ConstructionContext.h"
69 template<
typename DataType,
typename ParserFunc>
70 static void getFieldFromXml(
const XmlElementPtr& element,
const std::string& nodeName, DataType& dataField, ParserFunc parserFunc,
bool expected =
false)
72 if (
auto childElement = element->getFirstChildElement(nodeName)) {
73 dataField = parserFunc(childElement);
74 }
else if (expected) {
75 std::stringstream msg;
76 msg <<
"Expected field <" << element->getTagName() <<
"><" << nodeName <<
"> not found.";
87 auto childElement = element->getFirstChildElement(childElementName);
89 throw std::invalid_argument(
"Missing <" + childElementName +
"> element.");
97 auto childElement = element->getFirstChildElement(childElementName);
101 return childElement->getText();
108 auto childElement = element->getFirstChildElement(childElementName);
112 return childElement->getTextAs<T>(defaultValue);
116 virtual ~FactoryBase() {}
119#ifndef DOXYGEN_SHOULD_IGNORE_THIS
121template <
typename EnumClass,
typename FromClass = std::
string_view>
122using XmlEnumMappingElement = std::unordered_map<FromClass, EnumClass>;
123template <
typename EnumClass,
typename FromClass = std::
string_view>
126 static const XmlEnumMappingElement<EnumClass, FromClass> mapping;
129#define MUSX_XML_ENUM_MAPPING(Type, ...) \
131const XmlEnumMappingElement<Type> XmlEnumMapping<Type>::mapping = __VA_ARGS__
134#define MUSX_ENUM_CONVERSION_SIGNATURE __FUNCSIG__
136#define MUSX_ENUM_CONVERSION_SIGNATURE __PRETTY_FUNCTION__
139template <
typename EnumClass,
bool IgnoreUnknown,
typename FromClass = std::
string_view>
145 static EnumClass xmlToEnum(
const FromClass& value)
147 auto it = XmlEnumMapping<EnumClass>::mapping.find(value);
148 if (it != XmlEnumMapping<EnumClass>::mapping.end()) {
151 if constexpr (!IgnoreUnknown) {
152 std::string msg = [value]() {
153 if constexpr (std::is_arithmetic_v<FromClass>) {
154 return "Invalid enum value from xml: `" + std::to_string(value) +
"`";
157 return "Invalid enum value from xml: `" + std::string(value) +
"`";
160 msg +=
" in " + std::string(MUSX_ENUM_CONVERSION_SIGNATURE);
161 MUSX_UNKNOWN_XML(msg);
167#undef MUSX_ENUM_CONVERSION_SIGNATURE
169template<
typename EnumClass,
typename FromClass,
bool IgnoreUnknown = false>
170EnumClass toEnum(
const FromClass& value)
172 if constexpr (std::is_convertible_v<FromClass, std::string_view>) {
173 return EnumMapper<EnumClass, IgnoreUnknown, std::string_view>::xmlToEnum(value);
175 return EnumMapper<EnumClass, IgnoreUnknown, FromClass>::xmlToEnum(value);
179template<
typename EnumClass,
bool IgnoreUnknown = false>
180EnumClass toEnum(const ::musx::xml::XmlElementPtr& e)
182 return toEnum<EnumClass, std::string_view, IgnoreUnknown>(e->getTextTrimmed());
185#define MUSX_XML_ELEMENT_ARRAY(Type, ...) \
186const ::musx::xml::XmlElementArray<Type>& Type::xmlMappingArray() { \
187 static const ::musx::xml::XmlElementArray<Type> instance = __VA_ARGS__; \
190static_assert(true, "")
193struct FieldPopulator :
public FactoryBase
195 static void populateField(ConstructionContext& context,
const std::shared_ptr<T>& instance,
const XmlElementPtr& fieldElement)
197 auto it = elementXref().find(fieldElement->getTagName());
198 if (it != elementXref().end()) {
199 std::get<1>(*it)(context, fieldElement, instance);
201 const bool requireFields = [instance]() {
202 if constexpr (std::is_base_of_v<EnigmaBase, T>) {
203 return instance->requireAllFields();
209 MUSX_UNKNOWN_XML(
"xml element <" + fieldElement->getParent()->getTagName() +
"> has child <" + fieldElement->getTagName() +
"> which is not in the element list.");
214 static void populate(ConstructionContext& context,
const std::shared_ptr<T>& instance,
const XmlElementPtr& element)
216 if constexpr (std::is_base_of_v<TextsBase, T>) {
217 instance->text = element->getText();
219 for (
auto child = element->getFirstChildElement(); child; child = child->getNextSibling()) {
220 populateField(context, instance, child);
225 template <
typename... Args>
226 static std::shared_ptr<T> createAndPopulate(ConstructionContext& context,
const XmlElementPtr& element, Args&&... args)
228 return FieldPopulator<T>::createAndPopulateImpl(context, element, std::forward<Args>(args)...);
232 template <
typename... Args>
233 static std::shared_ptr<T> populateExistingOrCreate(
234 ConstructionContext& context,
const XmlElementPtr& element, std::shared_ptr<T> instance, Args&&... args)
237 instance = std::make_shared<T>(std::forward<Args>(args)...);
239 FieldPopulator<T>::populate(context, instance, element);
244 static const std::unordered_map<std::string_view, XmlElementPopulator<T>>& elementXref()
246 static const std::unordered_map<std::string_view, XmlElementPopulator<T>> xref = []()
248 std::unordered_map<std::string_view, XmlElementPopulator<T>> retval;
249 auto mappingArray = T::xmlMappingArray();
250 for (std::size_t i = 0; i < mappingArray.size(); i++) {
252 retval[std::get<0>(descriptor)] = std::get<1>(descriptor);
259 template <
typename... Args>
260 static std::shared_ptr<T> createAndPopulateImpl(ConstructionContext& context,
const XmlElementPtr& element, Args&&... args)
262 auto instance = std::make_shared<T>(std::forward<Args>(args)...);
263 FieldPopulator<T>::populate(context, instance, element);
274template <
typename EnumClass,
typename EmbeddedClass,
typename... Args>
275inline void populateEmbeddedClass(ConstructionContext& context,
const XmlElementPtr& e,
276 std::unordered_map<EnumClass, std::shared_ptr<EmbeddedClass>>& listArray, Args&&... args)
278 auto typeAttr = e->findAttribute(
"type");
280 throw std::invalid_argument(
"<" + e->getTagName() +
"> element has no type attribute");
282 listArray.emplace(toEnum<EnumClass>(typeAttr->getValueTrimmed()), FieldPopulator<EmbeddedClass>::createAndPopulate(context, e, std::forward<Args>(args)...));
291inline std::vector<T> populateEmbeddedArray(ConstructionContext& context,
const XmlElementPtr& e,
const std::string_view& elementNodeName)
293 std::vector<T> result;
294 for (
auto child = e->getFirstChildElement(); child; child = child->getNextSibling()) {
295 if (child->getTagName() != elementNodeName) {
296 MUSX_UNKNOWN_XML(
"Unknown tag <" + child->getTagName() +
"> while processing embedded xml array <" + e->getTagName() +
">");
299 if constexpr (std::is_fundamental_v<T>) {
300 result.push_back(child->getTextAs<T>());
301 }
else if constexpr (std::is_same_v<T, std::string>) {
302 result.push_back(child->getText());
304 result.push_back(FieldPopulator<T>::createAndPopulate(context, child));
310void populateFontEfx(ConstructionContext& context,
const XmlElementPtr& e,
const std::shared_ptr<dom::FontInfo>& i);
318 context.registerFontId(fontId);
322inline bool populateBoolean(ConstructionContext&,
const XmlElementPtr& element,
const std::shared_ptr<T>& instance)
324 MUSX_ASSERT_IF(!element) {
325 throw std::logic_error(
"Null element passed to populateBoolean function.");
328 if (!element->getFirstChildElement(
"offInPart")) {
332 if constexpr (std::is_base_of_v<EnigmaBase, T>) {
340inline std::vector<std::uint8_t> hexToBytes(std::string_view hex)
342 std::vector<std::uint8_t> out;
344 if (hex.size() % 2 == 0) {
345 out.reserve(hex.size() / 2);
346 for (std::size_t i = 0; i < hex.size(); i += 2) {
348 const char* first = hex.data() + i;
349 const char* last = first + 2;
350 auto [ptr, ec] = std::from_chars(first, last, value, 16);
351 if (ec != std::errc()) {
353 MUSX_INTEGRITY_ERROR(
"Invalid hex digit in hex string.");
355 out.push_back(
static_cast<std::uint8_t
>(value));
358 MUSX_INTEGRITY_ERROR(
"Encountered odd-length hex string.");
Base for DOM classes that are represented in EnigmaData.
Definition BaseClasses.h:89
Cmper getSourcePartId() const
Gets the source partId for this instance. If an instance is fully shared with the score,...
Definition BaseClasses.h:112
Factory base class.
Definition FactoryBase.h:57
static void getFieldFromXml(const XmlElementPtr &element, const std::string &nodeName, DataType &dataField, ParserFunc parserFunc, bool expected=false)
Helper function to check if a child exists and populate it if so.
Definition FactoryBase.h:70
static XmlElementPtr getFirstChildElement(const XmlElementPtr &element, const std::string &childElementName)
Helper function to throw when child element does not exist.
Definition FactoryBase.h:85
static std::optional< T > getOptionalChildTextAs(const XmlElementPtr &element, const std::string &childElementName, T defaultValue={})
Helper function to return std::nullopt when child element does not exist.
Definition FactoryBase.h:106
static std::optional< std::string > getOptionalChildText(const XmlElementPtr &element, const std::string &childElementName)
Helper function to return std::nullopt when child element does not exist.
Definition FactoryBase.h:95
@ Warning
Warning messages indicating potential issues.
static void log(LogLevel level, const std::string &message)
Logs a message with a specific severity level.
Definition Logger.h:87
The DOM (document object model) for musx files.
constexpr Cmper SCORE_PARTID
The part id of the score.
Definition Fundamentals.h:81
uint16_t Cmper
Enigma "comperator" key type.
Definition Fundamentals.h:57
Provides interfaces and optional implementations for traversing XML.
Definition PugiXmlImpl.h:33
std::tuple< const std::string_view, XmlElementPopulator< T > > XmlElementDescriptor
associates an xml node name with and XmlElementPopulator
Definition XmlInterface.h:131
std::shared_ptr< IXmlElement > XmlElementPtr
shared pointer to IXmlElement
Definition XmlInterface.h:127
object model for musx file (enigmaxml)
Definition BaseClasses.h:39