MUSX Document Model
Loading...
Searching...
No Matches
DocumentFactory.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 <algorithm>
25#include <cctype>
26#include <cstddef>
27#include <filesystem>
28#include <limits>
29#include <memory>
30#include <optional>
31#include <set>
32#include <string>
33#include <string_view>
34#include <type_traits>
35#include <utility>
36#include <vector>
37
38#include "musx/dom/Document.h"
39#include "musx/factory/ConstructionContext.h"
40#include "musx/factory/FactoryExceptions.h"
41#include "musx/xml/XmlInterface.h"
42
43namespace musx {
44namespace factory {
45
48{
49 using DocumentPtr = dom::DocumentPtr;
50
51public:
52 template <typename Container>
53 using IsCharContainer = std::enable_if_t<
54 (sizeof(std::remove_cv_t<typename Container::value_type>) == 1)
55 && !std::is_same_v<std::remove_cv_t<typename Container::value_type>, bool>>;
56
59 {
61 std::optional<double> scoreDurationSeconds;
62 dom::EmbeddedGraphicsMap embeddedGraphics;
63 std::optional<std::filesystem::path> sourcePath;
64 };
65
68 {
70 {
71 std::string filename;
73 };
74 using EmbeddedGraphicFiles = std::vector<EmbeddedGraphicFile>;
75
76 CreateOptions() = default;
77 CreateOptions(dom::PartVoicingPolicy policy) : partVoicingPolicy(policy) {}
78 CreateOptions(std::filesystem::path inputFilepath,
79 std::vector<char> notationMetadata,
80 EmbeddedGraphicFiles embeddedGraphicFiles,
82 : partVoicingPolicy(policy),
83 m_notationMetadata(std::move(notationMetadata)),
84 m_embeddedGraphics(parseEmbeddedGraphicFiles(std::move(embeddedGraphicFiles))),
85 m_sourcePath(std::move(inputFilepath))
86 {}
87
89
90 [[nodiscard]] const std::vector<char>& getNotationMetadata() const { return m_notationMetadata; }
91 [[nodiscard]] const dom::EmbeddedGraphicsMap& getEmbeddedGraphics() const { return m_embeddedGraphics; }
92 [[nodiscard]] const std::optional<std::filesystem::path>& getSourcePath() const { return m_sourcePath; }
93 [[nodiscard]] dom::EmbeddedGraphicsMap takeEmbeddedGraphics() { return std::move(m_embeddedGraphics); }
94
95 private:
96 static dom::EmbeddedGraphicsMap parseEmbeddedGraphicFiles(EmbeddedGraphicFiles&& files)
97 {
99 for (auto& file : files) {
100 const auto dot = file.filename.find('.');
101 if (dot == std::string::npos || dot == 0 || dot + 1 >= file.filename.size()) continue;
102 const std::string_view idText(file.filename.data(), dot);
103 if (!std::all_of(idText.begin(), idText.end(),
104 [](unsigned char ch) { return std::isdigit(ch) != 0; })) continue;
105 unsigned long long id = 0;
106 try {
107 id = std::stoull(std::string(idText));
108 } catch (...) {
109 continue;
110 }
111 if (id > static_cast<unsigned long long>((std::numeric_limits<dom::Cmper>::max)())) continue;
112 auto& entry = result[static_cast<dom::Cmper>(id)];
113 entry.extension = file.filename.substr(dot + 1);
114 entry.bytes = std::move(file.bytes);
115 }
116 return result;
117 }
118
119 std::vector<char> m_notationMetadata;
120 dom::EmbeddedGraphicsMap m_embeddedGraphics;
121 std::optional<std::filesystem::path> m_sourcePath;
122 };
123
126 {
127 public:
129 ConstructionSession& operator=(const ConstructionSession&) = delete;
130 ConstructionSession(ConstructionSession&&) noexcept = default;
131 ConstructionSession& operator=(ConstructionSession&&) noexcept = default;
132
133 [[nodiscard]] const DocumentPtr& getDocument() const;
134 [[nodiscard]] ConstructionContext& getConstructionContext() { return m_context; }
135 [[nodiscard]] const ConstructionContext& getConstructionContext() const { return m_context; }
136 [[nodiscard]] DocumentPtr finish() &&;
137
138 private:
139 enum class State { Active, Consumed };
140 explicit ConstructionSession(DocumentPtr document) : m_document(std::move(document)) {}
141
142 DocumentPtr m_document;
143 ConstructionContext m_context;
144 State m_state = State::Active;
145 friend class DocumentFactory;
146 };
147
149 [[nodiscard]] static ConstructionSession begin();
150 [[nodiscard]] static ConstructionSession begin(ConstructionOptions options);
151
152 template <typename XmlDocumentType>
153 [[nodiscard]] static DocumentPtr create(const char* data, size_t size)
154 {
155 return create<XmlDocumentType>(data, size, CreateOptions{});
156 }
157
158 template <typename XmlDocumentType, typename Container, typename = IsCharContainer<Container>>
159 [[nodiscard]] static DocumentPtr create(const Container& xmlBuffer)
160 {
161 return create<XmlDocumentType>(asCharData(xmlBuffer), xmlBuffer.size(), CreateOptions{});
162 }
163
164 template <typename XmlDocumentType>
165 [[nodiscard]] static DocumentPtr create(const char* data, size_t size, CreateOptions&& createOptions)
166 {
167 static_assert(std::is_base_of_v<xml::IXmlDocument, XmlDocumentType>,
168 "XmlDocumentType must derive from IXmlDocument.");
169 auto xmlDocument = std::make_unique<XmlDocumentType>();
170 xmlDocument->loadFromBuffer(data, size);
171 auto root = xmlDocument->getRootElement();
172 if (!root || root->getTagName() != "finale") {
173 throw std::invalid_argument("Missing <finale> element.");
174 }
175
176 ConstructionOptions options;
177 options.partVoicingPolicy = createOptions.partVoicingPolicy;
178 options.scoreDurationSeconds = parseScoreDurationSeconds<XmlDocumentType>(
179 createOptions.getNotationMetadata());
180 options.embeddedGraphics = createOptions.takeEmbeddedGraphics();
181 options.sourcePath = createOptions.getSourcePath();
182 return createFromXmlRoot(root, std::move(options));
183 }
184
185 template <typename XmlDocumentType, typename Container, typename = IsCharContainer<Container>>
186 [[nodiscard]] static DocumentPtr create(const Container& xmlBuffer, CreateOptions&& createOptions)
187 {
188 return create<XmlDocumentType>(asCharData(xmlBuffer), xmlBuffer.size(), std::move(createOptions));
189 }
190
191private:
192 static DocumentPtr createFromXmlRoot(
193 const xml::XmlElementPtr& root, ConstructionOptions&& options);
194 static void finalize(const DocumentPtr& document, ConstructionContext& context);
195
196 template <typename Container>
197 static const char* asCharData(const Container& buffer)
198 {
199 return reinterpret_cast<const char*>(buffer.data());
200 }
201
202 template <typename XmlDocumentType>
203 static std::optional<double> parseScoreDurationSeconds(const std::vector<char>& metadata)
204 {
205 if (metadata.empty()) return std::nullopt;
206 auto document = std::make_unique<XmlDocumentType>();
207 try {
208 document->loadFromBuffer(metadata.data(), metadata.size());
209 } catch (...) {
210 return std::nullopt;
211 }
212 auto root = document->getRootElement();
213 if (!root || root->getTagName() != "metadata") return std::nullopt;
214 auto fileInfo = root->getFirstChildElement("fileInfo");
215 if (!fileInfo) return std::nullopt;
216 auto creator = fileInfo->getFirstChildElement("creatorString");
217 if (!creator || creator->getTextTrimmed().rfind("Finale", 0) != 0) return std::nullopt;
218 auto duration = fileInfo->getFirstChildElement("scoreDuration");
219 if (!duration) return std::nullopt;
220 try {
221 return duration->template getTextAs<double>();
222 } catch (...) {
223 return std::nullopt;
224 }
225 }
226};
227
228} // namespace factory
229} // namespace musx
State shared by all factories during one document construction.
Definition ConstructionContext.h:12
A move-only draft document that can be populated and finalized exactly once.
Definition DocumentFactory.h:126
Creates and finalizes musxdom documents from XML or client-provided data.
Definition DocumentFactory.h:48
static ConstructionSession begin()
Begins construction of a document from client-provided data.
Definition DocumentFactory.cpp:344
uint16_t Cmper
Enigma "comperator" key type.
Definition Fundamentals.h:57
PartVoicingPolicy
Controls whether Finale-style part voicing is applied when iterating entries via musx::dom::details::...
Definition Document.h:82
std::vector< uint8_t > EmbeddedGraphicBlob
Raw bytes for one embedded graphic payload from a musx archive.
Definition Document.h:67
std::unordered_map< Cmper, EmbeddedGraphicData > EmbeddedGraphicsMap
Embedded graphics keyed by cmper (filename stem in musx archive).
Definition Document.h:76
std::shared_ptr< Document > DocumentPtr
Shared Document pointer.
Definition DocumentElement.h:35
std::shared_ptr< IXmlElement > XmlElementPtr
shared pointer to IXmlElement
Definition XmlInterface.h:127
object model for musx file (enigmaxml)
Definition BaseClasses.h:38
Normalized inputs for a non-XML document construction session.
Definition DocumentFactory.h:59
Optional inputs accepted by the existing XML creation interface.
Definition DocumentFactory.h:68