MUSX Document Model
Loading...
Searching...
No Matches
FieldPopulatorsOthers.h
1/*
2 * Copyright (C) 2025, Robert Patterson
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22#pragma once
23
24#include "musx/dom/BaseClasses.h"
25#include "musx/dom/Others.h"
26#include "musx/dom/Details.h"
27#include "musx/xml/XmlInterface.h"
28#include "FactoryBase.h"
29
30#ifndef DOXYGEN_SHOULD_IGNORE_THIS
31
32namespace musx {
33namespace factory {
34
35using namespace ::musx::xml;
36using namespace ::musx::dom::others;
37
38// Field populators are maintained to populate in the order that nodes are observed to occur in EnigmaXml.
39// The goal is that this may facilitate serialization in the future.
40
41template <>
42inline Enclosure::Shape toEnum<Enclosure::Shape>(const uint8_t& value)
43{
44 if (value >= static_cast<uint8_t>(Enclosure::Shape::NoEnclosure) &&
45 value <= static_cast<uint8_t>(Enclosure::Shape::Octogon)) {
46 return static_cast<Enclosure::Shape>(value);
47 }
48 MUSX_UNKNOWN_XML("Invalid <sides> value in XML for enclosure: " + std::to_string(value));
49 return {};
50}
51
52template <>
53struct FieldPopulator<TextExpressionEnclosure> : private FieldPopulator<Enclosure>
54{
55 using FieldPopulator<Enclosure>::populate;
56};
57
58template <>
59struct FieldPopulator<TextRepeatEnclosure> : private FieldPopulator<Enclosure>
60{
61 using FieldPopulator<Enclosure>::populate;
62};
63
64MUSX_RESOLVER_ENTRY(KeyMapArray, {
65 [](const dom::DocumentPtr& document) {
66 auto arrays = document->getOthers()->getArray<KeyMapArray>(SCORE_PARTID);
67 for (const auto& array : arrays) {
68 auto trimSteps = [&](size_t newSize) {
69 while (array->steps.size() > newSize) {
70 const auto& elt = array->steps[array->steps.size() - 1];
71 if (elt->diatonic || elt->hlevel != 0) {
72 break; // itegrity check below will catch this error
73 }
74 array->steps.pop_back();
75 }
76 };
77 if (auto keyFormat = document->getOthers()->get<others::KeyFormat>(SCORE_PARTID, array->getCmper())) {
78 trimSteps(keyFormat->semitones);
79 if (keyFormat->scaleTones != array->countDiatonicSteps() || keyFormat->semitones != array->steps.size()) {
80 MUSX_INTEGRITY_ERROR("KeyMapArray " + std::to_string(array->getCmper()) + " does not match KeyFormat.");
81 }
82 } else {
83 trimSteps(12);
84 if (array->countDiatonicSteps() != 7 || array->steps.size() != 12) { // default diatonic
85 MUSX_INTEGRITY_ERROR("KeyMapArray " + std::to_string(array->getCmper()) + " has no KeyFormat but does not match default values.");
86 }
87 }
88 }
89 }
90});
91
92MUSX_RESOLVER_ENTRY(LayerAttributes, {
93 [](const dom::DocumentPtr& document) {
94 auto layers = document->getOthers()->getArray<LayerAttributes>(SCORE_PARTID);
95 if (layers.size() != 4) {
96 MUSX_INTEGRITY_ERROR("Expected exactly 4 <layerAtts> elements.");
97 }
98 for (size_t i = 0; i < layers.size(); i++) {
99 if (layers[i]->getCmper() != i) {
100 MUSX_INTEGRITY_ERROR("Expected <layerAtts> elements to have cmper values 0, 1, 2, 3 in order.");
101 }
102 }
103 }
104});
105
106MUSX_RESOLVER_ENTRY(MarkingCategory, {
107 [](const dom::DocumentPtr& document) {
108 auto cats = document->getOthers()->getArray<MarkingCategory>(SCORE_PARTID);
109 for (const auto& cat : cats) {
110 if (cat->categoryType == MarkingCategory::CategoryType::Invalid) {
111 MUSX_INTEGRITY_ERROR("Encountered <markingsCategory> node (cmper " + std::to_string(cat->getCmper()) + ") with no categoryType");
112 }
113 }
114 }
115});
116
117MUSX_RESOLVER_ENTRY(MultiStaffGroupId, {
118 [](const dom::DocumentPtr& document) {
119 auto parts = document->getOthers()->getArray<PartDefinition>(SCORE_PARTID);
120 for (const auto& part : parts) {
121 auto instGroups = document->getOthers()->getArray<MultiStaffGroupId>(part->getCmper());
122 for (const auto& instance : instGroups) {
123 if (auto group = document->getDetails()->get<details::StaffGroup>(part->getCmper(), BASE_SYSTEM_ID, instance->staffGroupId)) {
124 group->multiStaffGroupId = instance->getCmper();
125 } else {
126 MUSX_INTEGRITY_ERROR("Group " + std::to_string(instance->staffGroupId) + " appears in MultiStaffGroupId "
127 + std::to_string(instance->getCmper()) + " but does not exist.");
128 }
129 }
130 }
131 }
132});
133
134MUSX_RESOLVER_ENTRY(MultiStaffInstrumentGroup, {
135 [](const dom::DocumentPtr& document) {
136 auto instGroups = document->getOthers()->getArray<MultiStaffInstrumentGroup>(SCORE_PARTID);
137 for (const auto& instance : instGroups) {
138 for (size_t x = 0; x < instance->staffNums.size(); x++) {
139 auto staff = instance->getStaffAtIndex(x);
140 if (staff) {
141 if (staff->multiStaffInstId) {
143 "Staff " + std::to_string(staff->getCmper()) + " appears in more than one instance of MultiStaffInstrumentGroup.");
144 } else {
145 staff->multiStaffInstId = instance->getCmper();
146 }
147 }
148 }
149 }
151 }
152});
153
154MUSX_RESOLVER_ENTRY(Page, {
155 [](const dom::DocumentPtr& document) {
156 auto linkedParts = document->getOthers()->getArray<PartDefinition>(SCORE_PARTID);
157 for (const auto& part : linkedParts) {
158 auto pages = document->getOthers()->getArray<Page>(part->getCmper());
159 auto systems = document->getOthers()->getArray<StaffSystem>(part->getCmper());
160 for (size_t x = 0; x < pages.size(); x++) {
161 auto page = pages[x];
162 if (!page->isBlank()) {
163 page->lastSystem = [&]() -> SystemCmper {
164 size_t nextIndex = x + 1;
165 while (nextIndex < pages.size()) {
166 auto nextPage = pages[nextIndex++];
167 if (!nextPage->isBlank()) {
168 return nextPage->firstSystem - 1;
169 }
170 }
171 return SystemCmper(systems.size());
172 }();
173 if (*page->lastSystem < page->firstSystem) {
174 page->lastSystem = std::nullopt;
175 MUSX_INTEGRITY_ERROR("Page " + std::to_string(page->getCmper()) + " of part " + part->getName()
176 + " has a last system smaller than the first system.");
177 }
178 }
179 }
180 }
181 }
182});
183
184MUSX_RESOLVER_ENTRY(ShapeExpressionDef, {
185 [](const dom::DocumentPtr& document) {
186 auto exps = document->getOthers()->getArray<ShapeExpressionDef>(SCORE_PARTID);
187 for (const auto& instance : exps) {
188 if (instance->categoryId) {
189 auto markingCat = document->getOthers()->get<MarkingCategory>(instance->getPartId(), instance->categoryId);
190 if (!markingCat) {
191 MUSX_INTEGRITY_ERROR("Marking category for shape expression " + std::to_string(instance->getCmper()) + " does not exist.");
192 }
193 markingCat->shapeExpressions.emplace(instance->getCmper(), instance);
194 }
195 }
196 }
197});
198
199MUSX_RESOLVER_ENTRY(Staff, {
200 [](const dom::DocumentPtr& document) {
201 auto instGroups = document->getOthers()->getArray<MultiStaffInstrumentGroup>(SCORE_PARTID);
202 // If no MultiStaffInstrumentGroup records exist, then we need to do this here.
203 if (instGroups.empty()) {
205 }
206 }
207});
208
209MUSX_RESOLVER_ENTRY(TextExpressionDef, {
210 [](const dom::DocumentPtr& document) {
211 auto exps = document->getOthers()->getArray<TextExpressionDef>(SCORE_PARTID);
212 for (const auto& instance : exps) {
213 if (instance->categoryId) {
214 auto markingCat = document->getOthers()->get<MarkingCategory>(instance->getPartId(), instance->categoryId);
215 if (!markingCat) {
216 MUSX_INTEGRITY_ERROR("Marking category for text expression " + std::to_string(instance->getCmper()) + " does not exist.");
217 }
218 markingCat->textExpressions.emplace(instance->getCmper(), instance);
219 }
220 }
221 }
222});
223
224} // namespace factory
225} // namespace musx
226
227#endif // DOXYGEN_SHOULD_IGNORE THIS
static void calcAutoNumberValues(const DocumentPtr &document)
Get the auto-numbering value for this staff, if applicable.
Definition Implementations.cpp:1931
@ Verbose
Informational messages that should only displayed when verbose logging is requested.
static void log(LogLevel level, const std::string &message)
Logs a message with a specific severity level.
Definition Logger.h:87
Classes in the OthersPool.
Definition BaseClasses.h:40
constexpr Cmper SCORE_PARTID
The part id of the score.
Definition Fundamentals.h:75
std::shared_ptr< Document > DocumentPtr
Shared Document pointer.
Definition BaseClasses.h:55
constexpr Cmper BASE_SYSTEM_ID
The base system cmper that gives a list of all available staves and their score order (others::Instru...
Definition Fundamentals.h:76
int16_t SystemCmper
Enigma systems Cmper (may be negative when not applicable)
Definition Fundamentals.h:66
Provides interfaces and optional implementations for traversing XML.
Definition PugiXmlImpl.h:33
object model for musx file (enigmaxml)
Definition BaseClasses.h:32