29#include <unordered_set>
30#include <unordered_map>
37#include "musx/util/Logger.h"
38#include "musx/xml/XmlInterface.h"
39#include "musx/dom/BaseClasses.h"
40#include "musx/dom/Document.h"
60 using std::runtime_error::runtime_error;
95 if (resolvers.count(std::string(key)) > 0) {
99 resolvers[std::string(key)] = std::move(resolver);
112 for (
const auto& [key, resolver] : resolvers) {
126 std::map<std::string, Resolver> resolvers;
146 template<
typename DataType,
typename ParserFunc>
147 static void getFieldFromXml(
const XmlElementPtr& element,
const std::string& nodeName, DataType& dataField, ParserFunc parserFunc,
bool expected =
false)
149 if (
auto childElement = element->getFirstChildElement(nodeName)) {
150 dataField = parserFunc(childElement);
151 }
else if (expected) {
152 std::stringstream msg;
153 msg <<
"Expected field <" << element->getTagName() <<
"><" << nodeName <<
"> not found.";
164 auto childElement = element->getFirstChildElement(childElementName);
166 throw std::invalid_argument(
"Missing <" + childElementName +
"> element.");
174 auto childElement = element->getFirstChildElement(childElementName);
178 return childElement->getText();
185 auto childElement = element->getFirstChildElement(childElementName);
189 return childElement->getTextAs<T>(defaultValue);
193 virtual ~FactoryBase() {}
196#ifndef DOXYGEN_SHOULD_IGNORE_THIS
198template <
typename EnumClass,
typename FromClass = std::
string_view>
199using XmlEnumMappingElement = std::unordered_map<FromClass, EnumClass>;
200template <
typename EnumClass,
typename FromClass = std::
string_view>
203 static const XmlEnumMappingElement<EnumClass, FromClass> mapping;
206#define MUSX_XML_ENUM_MAPPING(Type, ...) \
208const XmlEnumMappingElement<Type> XmlEnumMapping<Type>::mapping = __VA_ARGS__
210template <
typename EnumClass,
bool IgnoreUnknown,
typename FromClass = std::
string_view>
216 static EnumClass xmlToEnum(
const FromClass& value)
218 auto it = XmlEnumMapping<EnumClass>::mapping.find(value);
219 if (it != XmlEnumMapping<EnumClass>::mapping.end()) {
222 if constexpr (!IgnoreUnknown) {
223 std::string msg = [value]() {
224 if constexpr (std::is_arithmetic_v<FromClass>) {
225 return "Invalid enum value from xml: " + std::to_string(value);
228 return "Invalid enum value from xml: " + std::string(value);
231 MUSX_UNKNOWN_XML(msg);
237template<
typename EnumClass,
typename FromClass,
bool IgnoreUnknown = false>
238EnumClass toEnum(
const FromClass& value)
240 if constexpr (std::is_convertible_v<FromClass, std::string_view>) {
241 return EnumMapper<EnumClass, IgnoreUnknown, std::string_view>::xmlToEnum(value);
243 return EnumMapper<EnumClass, IgnoreUnknown, FromClass>::xmlToEnum(value);
247template<
typename EnumClass,
bool IgnoreUnknown = false>
248EnumClass toEnum(const ::musx::xml::XmlElementPtr& e)
250 return toEnum<EnumClass, std::string_view, IgnoreUnknown>(e->getTextTrimmed());
253#define MUSX_XML_ELEMENT_ARRAY(Type, ...) \
254const ::musx::xml::XmlElementArray<Type>& Type::xmlMappingArray() { \
255 static const ::musx::xml::XmlElementArray<Type> instance = __VA_ARGS__; \
258static_assert(true, "")
260using ResolverEntry = std::optional<ElementLinker::Resolver>;
262struct ResolverContainer
264 inline static const ResolverEntry resolver = {};
267#define MUSX_RESOLVER_ENTRY(Type, ...) \
269struct ResolverContainer<Type> { \
270 inline static const ResolverEntry resolver = ElementLinker::Resolver(__VA_ARGS__); \
274struct FieldPopulator :
public FactoryBase
276 static void populateField(
const std::shared_ptr<T>& instance,
const XmlElementPtr& fieldElement)
278 auto it = elementXref().find(fieldElement->getTagName());
279 if (it != elementXref().end()) {
280 std::get<1>(*it)(fieldElement, instance);
282 const bool requireFields = [instance]() {
283 if constexpr (std::is_base_of_v<Base, T>) {
284 return instance->requireAllFields();
290 MUSX_UNKNOWN_XML(
"xml element <" + fieldElement->getParent()->getTagName() +
"> has child <" + fieldElement->getTagName() +
"> which is not in the element list.");
295 static void populate(
const std::shared_ptr<T>& instance,
const XmlElementPtr& element)
297 if constexpr (std::is_base_of_v<TextsBase, T>) {
298 instance->text = element->getText();
300 for (
auto child = element->getFirstChildElement(); child; child = child->getNextSibling()) {
301 populateField(instance, child);
304 if constexpr (std::is_base_of_v<Base, T>) {
305 instance->integrityCheck(instance);
309 template <
typename SubClass = T>
310 static void populate(
const std::shared_ptr<T>& instance,
const XmlElementPtr& element, ElementLinker& elementLinker)
312 populate(instance, element);
313 if (ResolverContainer<SubClass>::resolver.has_value()) {
314 elementLinker.addResolver(ResolverContainer<SubClass>::resolver.value(), element->getTagName());
318 template <
typename... Args>
319 static std::shared_ptr<T> createAndPopulate(
const XmlElementPtr& element, Args&&... args)
321 return FieldPopulator<T>::createAndPopulateImpl(element, std::forward<Args>(args)...);
325 static const std::unordered_map<std::string_view, XmlElementPopulator<T>>& elementXref()
327 static const std::unordered_map<std::string_view, XmlElementPopulator<T>> xref = []()
329 std::unordered_map<std::string_view, XmlElementPopulator<T>> retval;
330 auto mappingArray = T::xmlMappingArray();
331 for (std::size_t i = 0; i < mappingArray.size(); i++) {
333 retval[std::get<0>(descriptor)] = std::get<1>(descriptor);
340 template <
typename... Args>
341 static std::shared_ptr<T> createAndPopulateImpl(
const XmlElementPtr& element, Args&&... args)
343 auto instance = std::make_shared<T>(std::forward<Args>(args)...);
344 FieldPopulator<T>::populate(instance, element);
355template <
typename EnumClass,
typename EmbeddedClass,
typename... Args>
356inline void populateEmbeddedClass(
const XmlElementPtr& e, std::unordered_map<EnumClass, std::shared_ptr<EmbeddedClass>>& listArray, Args&&... args)
358 auto typeAttr = e->findAttribute(
"type");
360 throw std::invalid_argument(
"<" + e->getTagName() +
"> element has no type attribute");
362 listArray.emplace(toEnum<EnumClass>(typeAttr->getValueTrimmed()), FieldPopulator<EmbeddedClass>::createAndPopulate(e, std::forward<Args>(args)...));
371inline std::vector<T> populateEmbeddedArray(
const XmlElementPtr& e,
const std::string_view& elementNodeName)
373 std::vector<T> result;
374 for (
auto child = e->getFirstChildElement(); child; child = child->getNextSibling()) {
375 if (child->getTagName() != elementNodeName) {
376 MUSX_UNKNOWN_XML(
"Unknown tag <" + child->getTagName() +
"> while processing embedded xml array <" + e->getTagName() +
">");
379 if constexpr (std::is_fundamental_v<T>) {
380 result.push_back(child->getTextAs<T>());
381 }
else if constexpr (std::is_same_v<T, std::string>) {
382 result.push_back(child->getText());
384 result.push_back(FieldPopulator<T>::createAndPopulate(child));
391template <
typename... Args>
392inline std::shared_ptr<FontInfo> FieldPopulator<FontInfo>::createAndPopulate(
const XmlElementPtr& element, Args&&... args)
394 if (!element->getFirstChildElement())
return nullptr;
395 return FieldPopulator<FontInfo>::createAndPopulateImpl(element, std::forward<Args>(args)...);
398void populateFontEfx(
const XmlElementPtr& e,
const std::shared_ptr<dom::FontInfo>& i);
401inline bool populateBoolean(
const XmlElementPtr& element,
const std::shared_ptr<T>& instance)
403 MUSX_ASSERT_IF(!element) {
404 throw std::logic_error(
"Null element passed to populateBoolean function.");
407 if (!element->getFirstChildElement(
"offInPart")) {
411 if constexpr (std::is_base_of_v<Base, T>) {
412 const Base& instAsBase = *instance;
419inline std::vector<std::uint8_t> hexToBytes(std::string_view hex)
421 std::vector<std::uint8_t> out;
423 if (hex.size() % 2 == 0) {
424 out.reserve(hex.size() / 2);
425 for (std::size_t i = 0; i < hex.size(); i += 2) {
427 const char* first = hex.data() + i;
428 const char* last = first + 2;
429 auto [ptr, ec] = std::from_chars(first, last, value, 16);
430 if (ec != std::errc()) {
432 MUSX_INTEGRITY_ERROR(
"Invalid hex digit in hex string.");
434 out.push_back(
static_cast<std::uint8_t
>(value));
437 MUSX_INTEGRITY_ERROR(
"Encountered odd-length hex string.");
Base class to enforce polymorphism across all DOM classes.
Definition BaseClasses.h:83
Cmper getSourcePartId() const
Gets the source partId for this instance. If an instance is fully shared with the score,...
Definition BaseClasses.h:124
A utility class for managing deferred relationships between elements during document construction.
Definition FactoryBase.h:72
std::function< void(const dom::DocumentPtr &)> Resolver
A callable type representing a deferred relationship resolver.
Definition FactoryBase.h:81
void resolveAll(const dom::DocumentPtr &document)
Resolves all deferred relationships.
Definition FactoryBase.h:110
void addResolver(Resolver resolver, const std::string_view &key)
Adds a resolver function to the linker.
Definition FactoryBase.h:92
Factory base class.
Definition FactoryBase.h:134
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:147
static XmlElementPtr getFirstChildElement(const XmlElementPtr &element, const std::string &childElementName)
Helper function to throw when child element does not exist.
Definition FactoryBase.h:162
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:183
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:172
Exception for unknown xml node errors. (Used when MUSX_THROW_ON_UNKNOWN_XML is defined....
Definition FactoryBase.h:58
@ 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:79
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:36