29#include <unordered_set>
30#include <unordered_map>
35#include "musx/util/Logger.h"
36#include "musx/xml/XmlInterface.h"
37#include "musx/dom/BaseClasses.h"
38#include "musx/dom/Document.h"
58 using std::runtime_error::runtime_error;
93 if (resolvers.count(std::string(key)) > 0) {
97 resolvers[std::string(key)] = std::move(resolver);
110 for (
const auto& [key, resolver] : resolvers) {
124 std::map<std::string, Resolver> resolvers;
144 template<
typename DataType,
typename ParserFunc>
145 static void getFieldFromXml(
const XmlElementPtr& element,
const std::string& nodeName, DataType& dataField, ParserFunc parserFunc,
bool expected =
false)
147 if (
auto childElement = element->getFirstChildElement(nodeName)) {
148 dataField = parserFunc(childElement);
149 }
else if (expected) {
150 std::stringstream msg;
151 msg <<
"Expected field <" << element->getTagName() <<
"><" << nodeName <<
"> not found.";
162 auto childElement = element->getFirstChildElement(childElementName);
164 throw std::invalid_argument(
"Missing <" + childElementName +
"> element.");
172 auto childElement = element->getFirstChildElement(childElementName);
176 return childElement->getText();
183 auto childElement = element->getFirstChildElement(childElementName);
187 return childElement->getTextAs<T>(defaultValue);
191 virtual ~FactoryBase() {}
194#ifndef DOXYGEN_SHOULD_IGNORE_THIS
196template <
typename EnumClass,
typename FromClass = std::
string_view>
197using XmlEnumMappingElement = std::unordered_map<FromClass, EnumClass>;
198template <
typename EnumClass,
typename FromClass = std::
string_view>
201 static const XmlEnumMappingElement<EnumClass, FromClass> mapping;
204#define MUSX_XML_ENUM_MAPPING(Type, ...) \
206const XmlEnumMappingElement<Type> XmlEnumMapping<Type>::mapping = __VA_ARGS__
208template <
typename EnumClass,
bool IgnoreUnknown,
typename FromClass = std::
string_view>
214 static EnumClass xmlToEnum(
const FromClass& value)
216 auto it = XmlEnumMapping<EnumClass>::mapping.find(value);
217 if (it != XmlEnumMapping<EnumClass>::mapping.end()) {
220 if constexpr (!IgnoreUnknown) {
221 std::string msg = [value]() {
222 if constexpr (std::is_arithmetic_v<FromClass>) {
223 return "Invalid enum value from xml: " + std::to_string(value);
226 return "Invalid enum value from xml: " + std::string(value);
229 MUSX_UNKNOWN_XML(msg);
235template<
typename EnumClass,
typename FromClass,
bool IgnoreUnknown = false>
236EnumClass toEnum(
const FromClass& value)
238 if constexpr (std::is_convertible_v<FromClass, std::string_view>) {
239 return EnumMapper<EnumClass, IgnoreUnknown, std::string_view>::xmlToEnum(value);
241 return EnumMapper<EnumClass, IgnoreUnknown, FromClass>::xmlToEnum(value);
245template<
typename EnumClass,
bool IgnoreUnknown = false>
246EnumClass toEnum(const ::musx::xml::XmlElementPtr& e)
248 return toEnum<EnumClass, std::string_view, IgnoreUnknown>(e->getTextTrimmed());
251#define MUSX_XML_ELEMENT_ARRAY(Type, ...) \
252const ::musx::xml::XmlElementArray<Type>& Type::xmlMappingArray() { \
253 static const ::musx::xml::XmlElementArray<Type> instance = __VA_ARGS__; \
256static_assert(true, "")
258using ResolverEntry = std::optional<ElementLinker::Resolver>;
260struct ResolverContainer
262 inline static const ResolverEntry resolver = {};
265#define MUSX_RESOLVER_ENTRY(Type, ...) \
267struct ResolverContainer<Type> { \
268 inline static const ResolverEntry resolver = ElementLinker::Resolver(__VA_ARGS__); \
272struct FieldPopulator :
public FactoryBase
274 static void populateField(
const std::shared_ptr<T>& instance,
const XmlElementPtr& fieldElement)
276 auto it = elementXref().find(fieldElement->getTagName());
277 if (it != elementXref().end()) {
278 std::get<1>(*it)(fieldElement, instance);
280 const bool requireFields = [instance]() {
281 if constexpr (std::is_base_of_v<Base, T>) {
282 return instance->requireAllFields();
288 MUSX_UNKNOWN_XML(
"xml element <" + fieldElement->getParent()->getTagName() +
"> has child <" + fieldElement->getTagName() +
"> which is not in the element list.");
293 static void populate(
const std::shared_ptr<T>& instance,
const XmlElementPtr& element)
295 if constexpr (std::is_base_of_v<TextsBase, T>) {
296 instance->text = element->getText();
298 for (
auto child = element->getFirstChildElement(); child; child = child->getNextSibling()) {
299 populateField(instance, child);
302 if constexpr (std::is_base_of_v<Base, T>) {
303 instance->integrityCheck();
307 static void populate(
const std::shared_ptr<T>& instance,
const XmlElementPtr& element, ElementLinker& elementLinker)
309 populate(instance, element);
310 if (ResolverContainer<T>::resolver.has_value()) {
311 elementLinker.addResolver(ResolverContainer<T>::resolver.value(), element->getTagName());
315 template <
typename... Args>
316 static std::shared_ptr<T> createAndPopulate(
const XmlElementPtr& element, Args&&... args)
318 return FieldPopulator<T>::createAndPopulateImpl(element, std::forward<Args>(args)...);
322 static const std::unordered_map<std::string_view, XmlElementPopulator<T>>& elementXref()
324 static const std::unordered_map<std::string_view, XmlElementPopulator<T>> xref = []()
326 std::unordered_map<std::string_view, XmlElementPopulator<T>> retval;
327 auto mappingArray = T::xmlMappingArray();
328 for (std::size_t i = 0; i < mappingArray.size(); i++) {
330 retval[std::get<0>(descriptor)] = std::get<1>(descriptor);
337 template <
typename... Args>
338 static std::shared_ptr<T> createAndPopulateImpl(
const XmlElementPtr& element, Args&&... args)
340 auto instance = std::make_shared<T>(std::forward<Args>(args)...);
341 FieldPopulator<T>::populate(instance, element);
351template <
typename EnumClass,
typename EmbeddedClass>
352static void populateEmbeddedClass(
const XmlElementPtr& e, std::unordered_map<EnumClass, std::shared_ptr<EmbeddedClass>>& listArray)
354 auto typeAttr = e->findAttribute(
"type");
356 throw std::invalid_argument(
"<" + e->getTagName() +
"> element has no type attribute");
358 listArray.emplace(toEnum<EnumClass>(typeAttr->getValueTrimmed()), FieldPopulator<EmbeddedClass>::createAndPopulate(e));
367static std::vector<T> populateEmbeddedArray(
const XmlElementPtr& e,
const std::string_view& elementNodeName)
369 std::vector<T> result;
370 for (
auto child = e->getFirstChildElement(); child; child = child->getNextSibling()) {
371 if (child->getTagName() != elementNodeName) {
372 MUSX_UNKNOWN_XML(
"Unknown tag <" + child->getTagName() +
"> while processing embedded xml array <" + e->getTagName() +
">");
375 if constexpr (std::is_fundamental_v<T>) {
376 result.push_back(child->getTextAs<T>());
377 }
else if constexpr (std::is_same_v<T, std::string>) {
378 result.push_back(child->getText());
380 result.push_back(FieldPopulator<T>::createAndPopulate(child));
387template <
typename... Args>
388inline std::shared_ptr<FontInfo> FieldPopulator<FontInfo>::createAndPopulate(
const XmlElementPtr& element, Args&&... args)
390 if (!element->getFirstChildElement())
return nullptr;
391 return FieldPopulator<FontInfo>::createAndPopulateImpl(element, std::forward<Args>(args)...);
394void populateFontEfx(
const XmlElementPtr& e,
const std::shared_ptr<dom::FontInfo>& i);
A utility class for managing deferred relationships between elements during document construction.
Definition FactoryBase.h:70
std::function< void(const dom::DocumentPtr &)> Resolver
A callable type representing a deferred relationship resolver.
Definition FactoryBase.h:79
void resolveAll(const dom::DocumentPtr &document)
Resolves all deferred relationships.
Definition FactoryBase.h:108
void addResolver(Resolver resolver, const std::string_view &key)
Adds a resolver function to the linker.
Definition FactoryBase.h:90
Factory base class.
Definition FactoryBase.h:132
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:145
static XmlElementPtr getFirstChildElement(const XmlElementPtr &element, const std::string &childElementName)
Helper function to throw when child element does not exist.
Definition FactoryBase.h:160
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:181
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:170
Exception for unknown xml node errors. (Used when MUSX_THROW_ON_UNKNOWN_XML is defined....
Definition FactoryBase.h:56
@ 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.
std::shared_ptr< Document > DocumentPtr
Shared Document pointer.
Definition BaseClasses.h:55
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:125
std::shared_ptr< IXmlElement > XmlElementPtr
shared pointer to IXmlElement
Definition XmlInterface.h:121
object model for musx file (enigmaxml)
Definition BaseClasses.h:32