denigma 4.0.0
Loading...
Searching...
No Matches
conversion.h
1/*
2 * Copyright (C) 2026, 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 <cassert>
25#include <cstddef>
26#include <cstring>
27#include <functional>
28#include <memory>
29#include <optional>
30#include <ostream>
31#include <span>
32#include <sstream>
33#include <stdexcept>
34#include <string>
35#include <string_view>
36#include <utility>
37#include <vector>
38
39#include "denigma/io/random_access_reader.h"
40
43namespace denigma {
44
45namespace classify {
46class GapCollector;
47} // namespace classify
48
51enum class FormatId {
52 Musx,
53 EnigmaXml,
54 MnxJson,
55 MusicXml,
56 MssXml,
57 Svg
58};
59
62enum class MessageSeverity {
63 Info,
64 Warning,
65 Error,
66 Verbose
67};
68
72{
73 MessageSeverity severity{MessageSeverity::Info};
74 std::string message;
75};
76
80{
82 std::string sourceName;
84 bool validate{true};
86 bool verbose{false};
88 bool quiet{false};
90 bool allFontsAvailable{false};
94 std::function<void(MessageSeverity severity, std::string_view message)> logCallback = [](MessageSeverity, std::string_view) {};
95};
96
100{
101public:
102 virtual ~IOptions() = default;
103};
104
108{
111};
112
116{
117public:
119 [[nodiscard]] std::span<const Diagnostic> diagnostics() const noexcept { return m_diagnostics; }
120
122 [[nodiscard]] bool hasError() const noexcept { return m_hasError; }
123
125 explicit operator bool() const noexcept { return !hasError(); }
126
128 void addDiagnostic(MessageSeverity severity, std::string message) { addDiagnostic(Diagnostic{severity, std::move(message)}); }
129
131 void addDiagnostic(Diagnostic diagnostic)
132 {
133 m_hasError = m_hasError || diagnostic.severity == MessageSeverity::Error;
134 m_diagnostics.push_back(std::move(diagnostic));
135 }
136
137private:
138 std::vector<Diagnostic> m_diagnostics;
139 bool m_hasError{};
140};
141
145{
147 std::string suggestedName;
149 std::vector<std::byte> data;
150};
151
155{
156public:
158 ConversionArtifact(ConversionResult result, std::vector<ConversionOutput> outputs)
159 : m_result(std::move(result)), m_outputs(std::move(outputs))
160 {}
161
163 [[nodiscard]] const ConversionResult& result() const noexcept { return m_result; }
164
166 [[nodiscard]] std::span<const ConversionOutput> outputs() const noexcept { return m_outputs; }
167
169 [[nodiscard]] bool hasError() const noexcept { return m_result.hasError(); }
170
172 explicit operator bool() const noexcept { return static_cast<bool>(m_result); }
173
174private:
175 ConversionResult m_result;
176 std::vector<ConversionOutput> m_outputs;
177};
178
182template <typename OptionsT>
183OptionsT optionsFromRequest(const ConversionRequest& request, std::string_view converterName)
184{
185 if (!request.options) {
186 return {};
187 }
188 if (const auto* options = dynamic_cast<const OptionsT*>(request.options)) {
189 return *options;
190 }
191 assert(false && "incompatible conversion options");
192 throw std::invalid_argument(std::string(converterName) + " received incompatible conversion options.");
193}
194
198{
199public:
200 virtual ~IConverter() = default;
201
203 [[nodiscard]] virtual FormatId sourceFormat() const = 0;
205 [[nodiscard]] virtual FormatId targetFormat() const = 0;
206
208 virtual ConversionResult convert(std::span<const std::byte> input, std::ostream& output, const ConversionRequest& request = {}) const = 0;
209};
210
214{
215public:
216 virtual ~IReaderConverter() = default;
217
219 [[nodiscard]] virtual FormatId sourceFormat() const = 0;
221 [[nodiscard]] virtual FormatId targetFormat() const = 0;
222
224 virtual ConversionResult convert(const IRandomAccessReader& input, std::ostream& output, const ConversionRequest& request = {}) const = 0;
225};
226
228using MultiOutputCallback = std::function<void(std::string_view suggestedName, std::span<const std::byte> data)>;
229
233{
234public:
235 virtual ~IMultiOutputConverter() = default;
236
238 [[nodiscard]] virtual FormatId sourceFormat() const = 0;
240 [[nodiscard]] virtual FormatId targetFormat() const = 0;
241
244 std::span<const std::byte> input, const MultiOutputCallback& outputCallback, const ConversionRequest& request = {}) const = 0;
245};
246
250{
251public:
252 virtual ~IReaderMultiOutputConverter() = default;
253
255 [[nodiscard]] virtual FormatId sourceFormat() const = 0;
257 [[nodiscard]] virtual FormatId targetFormat() const = 0;
258
261 const IRandomAccessReader& input, const MultiOutputCallback& outputCallback, const ConversionRequest& request = {}) const = 0;
262};
263
267{
268public:
270 void add(std::unique_ptr<IConverter> converter)
271 {
272 if (!converter) {
273 throw std::invalid_argument("converter cannot be null");
274 }
275 m_converters.emplace_back(std::move(converter));
276 }
277
279 void add(std::unique_ptr<IMultiOutputConverter> converter)
280 {
281 if (!converter) {
282 throw std::invalid_argument("converter cannot be null");
283 }
284 m_multiOutputConverters.emplace_back(std::move(converter));
285 }
286
288 void add(std::unique_ptr<IReaderConverter> converter)
289 {
290 if (!converter) {
291 throw std::invalid_argument("converter cannot be null");
292 }
293 m_readerConverters.emplace_back(std::move(converter));
294 }
295
297 void add(std::unique_ptr<IReaderMultiOutputConverter> converter)
298 {
299 if (!converter) {
300 throw std::invalid_argument("converter cannot be null");
301 }
302 m_readerMultiOutputConverters.emplace_back(std::move(converter));
303 }
304
306 [[nodiscard]] const IConverter* find(FormatId sourceFormat, FormatId targetFormat) const
307 {
308 for (const auto& converter : m_converters) {
309 if (converter->sourceFormat() == sourceFormat && converter->targetFormat() == targetFormat) {
310 return converter.get();
311 }
312 }
313 return nullptr;
314 }
315
317 [[nodiscard]] const IMultiOutputConverter* findMultiOutput(FormatId sourceFormat, FormatId targetFormat) const
318 {
319 for (const auto& converter : m_multiOutputConverters) {
320 if (converter->sourceFormat() == sourceFormat && converter->targetFormat() == targetFormat) {
321 return converter.get();
322 }
323 }
324 return nullptr;
325 }
326
328 [[nodiscard]] const IReaderConverter* findReader(FormatId sourceFormat, FormatId targetFormat) const
329 {
330 for (const auto& converter : m_readerConverters) {
331 if (converter->sourceFormat() == sourceFormat && converter->targetFormat() == targetFormat) {
332 return converter.get();
333 }
334 }
335 return nullptr;
336 }
337
339 [[nodiscard]] const IReaderMultiOutputConverter* findReaderMultiOutput(FormatId sourceFormat, FormatId targetFormat) const
340 {
341 for (const auto& converter : m_readerMultiOutputConverters) {
342 if (converter->sourceFormat() == sourceFormat && converter->targetFormat() == targetFormat) {
343 return converter.get();
344 }
345 }
346 return nullptr;
347 }
348
352 FormatId sourceFormat, FormatId targetFormat, std::span<const std::byte> input, const ConversionRequest& request = {}) const
353 {
354 if (const auto* converter = find(sourceFormat, targetFormat)) {
355 return collectSingleOutput(*converter, input, request);
356 }
357 if (const auto* converter = findMultiOutput(sourceFormat, targetFormat)) {
358 return collectMultipleOutputs(*converter, input, request);
359 }
360 return unsupportedConversion();
361 }
362
366 FormatId sourceFormat, FormatId targetFormat, const IRandomAccessReader& input, const ConversionRequest& request = {}) const
367 {
368 if (const auto* converter = findReader(sourceFormat, targetFormat)) {
369 return collectSingleOutput(*converter, input, request);
370 }
371 if (const auto* converter = findReaderMultiOutput(sourceFormat, targetFormat)) {
372 return collectMultipleOutputs(*converter, input, request);
373 }
374 return unsupportedConversion();
375 }
376
377private:
378 template <typename Converter, typename Input>
379 static ConversionArtifact collectSingleOutput(const Converter& converter, const Input& input, const ConversionRequest& request)
380 {
381 std::ostringstream output;
382 auto result = converter.convert(input, output, request);
383 auto text = output.str();
384 std::vector<std::byte> data(text.size());
385 if (!text.empty()) {
386 std::memcpy(data.data(), text.data(), text.size());
387 }
388 std::vector<ConversionOutput> outputs;
389 outputs.push_back({{}, std::move(data)});
390 return {std::move(result), std::move(outputs)};
391 }
392
393 template <typename Converter, typename Input>
394 static ConversionArtifact collectMultipleOutputs(const Converter& converter, const Input& input, const ConversionRequest& request)
395 {
396 std::vector<ConversionOutput> outputs;
397 auto result = converter.convert(
398 input,
399 [&outputs](std::string_view suggestedName, std::span<const std::byte> data) {
400 outputs.push_back({std::string(suggestedName), {data.begin(), data.end()}});
401 },
402 request);
403 return {std::move(result), std::move(outputs)};
404 }
405
406 static ConversionArtifact unsupportedConversion()
407 {
408 ConversionResult result;
409 result.addDiagnostic(MessageSeverity::Error, "No converter is registered for the requested formats.");
410 return {std::move(result), {}};
411 }
412
413 std::vector<std::unique_ptr<IConverter>> m_converters;
414 std::vector<std::unique_ptr<IMultiOutputConverter>> m_multiOutputConverters;
415 std::vector<std::unique_ptr<IReaderConverter>> m_readerConverters;
416 std::vector<std::unique_ptr<IReaderMultiOutputConverter>> m_readerMultiOutputConverters;
417};
418
419} // namespace denigma
Owned documents and result metadata from one conversion.
Definition conversion.h:155
const ConversionResult & result() const noexcept
Returns converter result metadata.
Definition conversion.h:163
bool hasError() const noexcept
Returns true when the conversion result contains an error diagnostic.
Definition conversion.h:169
ConversionArtifact(ConversionResult result, std::vector< ConversionOutput > outputs)
Creates an artifact from converter result metadata and generated documents.
Definition conversion.h:158
std::span< const ConversionOutput > outputs() const noexcept
Returns the generated documents in converter emission order.
Definition conversion.h:166
Result metadata returned after a conversion completes.
Definition conversion.h:116
void addDiagnostic(MessageSeverity severity, std::string message)
Adds a diagnostic and updates the error state if needed.
Definition conversion.h:128
bool hasError() const noexcept
Returns true when at least one diagnostic has severity Error.
Definition conversion.h:122
void addDiagnostic(Diagnostic diagnostic)
Adds a diagnostic and updates the error state if needed.
Definition conversion.h:131
std::span< const Diagnostic > diagnostics() const noexcept
Returns the diagnostics collected during conversion.
Definition conversion.h:119
Lightweight registry for locating converters by source and target format.
Definition conversion.h:267
void add(std::unique_ptr< IReaderMultiOutputConverter > converter)
Adds a reader-backed multi-output converter to the registry.
Definition conversion.h:297
const IReaderConverter * findReader(FormatId sourceFormat, FormatId targetFormat) const
Returns the first registered reader-backed converter matching the requested formats,...
Definition conversion.h:328
ConversionArtifact convert(FormatId sourceFormat, FormatId targetFormat, std::span< const std::byte > input, const ConversionRequest &request={}) const
Converts an in-memory input with the registered adapter for the requested formats.
Definition conversion.h:351
const IReaderMultiOutputConverter * findReaderMultiOutput(FormatId sourceFormat, FormatId targetFormat) const
Returns the first registered reader-backed multi-output converter matching the requested formats,...
Definition conversion.h:339
const IMultiOutputConverter * findMultiOutput(FormatId sourceFormat, FormatId targetFormat) const
Returns the first registered multi-output converter matching the requested formats,...
Definition conversion.h:317
const IConverter * find(FormatId sourceFormat, FormatId targetFormat) const
Returns the first registered converter matching the requested formats, or nullptr.
Definition conversion.h:306
ConversionArtifact convert(FormatId sourceFormat, FormatId targetFormat, const IRandomAccessReader &input, const ConversionRequest &request={}) const
Converts a random-access input with the registered adapter for the requested formats.
Definition conversion.h:365
void add(std::unique_ptr< IMultiOutputConverter > converter)
Adds a multi-output converter to the registry.
Definition conversion.h:279
void add(std::unique_ptr< IConverter > converter)
Adds a converter to the registry.
Definition conversion.h:270
void add(std::unique_ptr< IReaderConverter > converter)
Adds a reader-backed converter to the registry.
Definition conversion.h:288
Public interface implemented by each conversion adapter.
Definition conversion.h:198
virtual ~IConverter()=default
virtual destructor
virtual FormatId sourceFormat() const =0
Returns the source format accepted by this converter.
virtual FormatId targetFormat() const =0
Returns the target format produced by this converter.
virtual ConversionResult convert(std::span< const std::byte > input, std::ostream &output, const ConversionRequest &request={}) const =0
Converts the input memory buffer and writes the converted output to the provided stream.
Public interface implemented by adapters that may produce multiple output documents.
Definition conversion.h:233
virtual FormatId targetFormat() const =0
Returns the target format produced by this converter.
virtual ConversionResult convert(std::span< const std::byte > input, const MultiOutputCallback &outputCallback, const ConversionRequest &request={}) const =0
Converts the input memory buffer and invokes outputCallback once for each generated output.
virtual FormatId sourceFormat() const =0
Returns the source format accepted by this converter.
virtual ~IMultiOutputConverter()=default
virtual destructor
Base class for adapter-specific option structs.
Definition conversion.h:100
virtual ~IOptions()=default
Virtual destructor.
Random-access byte reader used for container formats such as MUSX.
Definition random_access_reader.h:32
Public interface implemented by adapters whose input is a random-access container.
Definition conversion.h:214
virtual ConversionResult convert(const IRandomAccessReader &input, std::ostream &output, const ConversionRequest &request={}) const =0
Converts the input reader and writes the converted output to the provided stream.
virtual ~IReaderConverter()=default
virtual destructor
virtual FormatId targetFormat() const =0
Returns the target format produced by this converter.
virtual FormatId sourceFormat() const =0
Returns the source format accepted by this converter.
Public interface implemented by reader-backed adapters that may produce multiple output documents.
Definition conversion.h:250
virtual ConversionResult convert(const IRandomAccessReader &input, const MultiOutputCallback &outputCallback, const ConversionRequest &request={}) const =0
Converts the input reader and invokes outputCallback once for each generated output.
virtual ~IReaderMultiOutputConverter()=default
virtual destructor
virtual FormatId sourceFormat() const =0
Returns the source format accepted by this converter.
virtual FormatId targetFormat() const =0
Returns the target format produced by this converter.
Collects typed conversion gaps for inspection or later serialization.
Definition gaps.h:102
Core public API for the Denigma conversion libraries.
Definition articulations.h:33
FormatId
Stable identifiers for converter input and output formats.
Definition conversion.h:51
@ MssXml
MuseScore style sheet XML.
@ EnigmaXml
Finale Enigma XML.
@ Svg
Scalable Vector Graphics XML.
@ MnxJson
MNX JSON as produced by mnxdom.
@ Musx
Finale MUSX archive.
@ MusicXml
MusicXML score-partwise XML.
MessageSeverity
Severity for log messages and diagnostics emitted by converters.
Definition conversion.h:62
OptionsT optionsFromRequest(const ConversionRequest &request, std::string_view converterName)
Returns typed options from an erased request, or default options when none were supplied.
Definition conversion.h:183
std::function< void(std::string_view suggestedName, std::span< const std::byte > data)> MultiOutputCallback
Callback used by converters that may emit zero, one, or many output buffers.
Definition conversion.h:228
Options common to all public converter adapters.
Definition conversion.h:80
std::function< void(MessageSeverity severity, std::string_view message)> logCallback
Optional callback that receives converter log messages. Defaults to no-op.
Definition conversion.h:94
classify::GapCollector * gapCollector
Optional non-owning destination for typed conversion gaps. Null disables gap collection.
Definition conversion.h:92
bool validate
Enables converter-specific output validation when supported.
Definition conversion.h:84
bool allFontsAvailable
Every source font will be available in the environment that reads the converted output.
Definition conversion.h:90
std::string sourceName
Caller-supplied source name used for diagnostics and metadata.
Definition conversion.h:82
bool verbose
Enables verbose logging when supported by the caller.
Definition conversion.h:86
bool quiet
Suppresses info/verbose logging when true.
Definition conversion.h:88
One document produced by a conversion.
Definition conversion.h:145
std::string suggestedName
Suggested output filename. Empty when the converter does not supply one.
Definition conversion.h:147
std::vector< std::byte > data
Owned output bytes.
Definition conversion.h:149
Type-erased request used by registry-based converter calls.
Definition conversion.h:108
const IOptions * options
Adapter-specific options. Must remain valid for the duration of the conversion call.
Definition conversion.h:110
A non-fatal message emitted by a converter.
Definition conversion.h:72
std::string message
Human-readable diagnostic text.
Definition conversion.h:74
MessageSeverity severity
Message severity.
Definition conversion.h:73