diff --git a/CMakeLists.txt b/CMakeLists.txt index f68856697..3db6c34c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,8 +58,9 @@ if(NOT TARGET pugixml) endif() # mx_core: the generated C++ typed model plus its hand-written runtime -# (Decimal, Result, Error, Lexical, ParseContext, Token, NameToken, OneOrMore, -# Xml). The generated per-type sources live in src/private/mx/core/generated/ +# (Decimal, Result, Error, Lexical, ParseContext, Token, NameToken, Language, +# OneOrMore, Xml). The generated per-type sources live in +# src/private/mx/core/generated/ # (their own directory, so generator-owned code is recognizable at a glance) # and arrive via sources.cmake (emitted by `python3 -m gen gen/cpp/config.toml`). set(MX_CORE_RUNTIME_SOURCES @@ -70,6 +71,8 @@ set(MX_CORE_RUNTIME_SOURCES ${PRIVATE_DIR}/mx/core/Error.h ${PRIVATE_DIR}/mx/core/IdIntegrity.cpp ${PRIVATE_DIR}/mx/core/IdIntegrity.h + ${PRIVATE_DIR}/mx/core/Language.cpp + ${PRIVATE_DIR}/mx/core/Language.h ${PRIVATE_DIR}/mx/core/Lexical.cpp ${PRIVATE_DIR}/mx/core/Lexical.h ${PRIVATE_DIR}/mx/core/NameToken.cpp diff --git a/gen/cpp/config.toml b/gen/cpp/config.toml index 9dd494a18..5526f460c 100644 --- a/gen/cpp/config.toml +++ b/gen/cpp/config.toml @@ -64,6 +64,10 @@ date = "std::string" nmtoken = "NameToken" id = "Token" idref = "Token" +# The XML namespace's language tag: a hand-written repairing wrapper +# (Language.h/cpp) enforcing the builtin xs:language pattern, with the empty +# string admitted because the attribute's schema unions the un-declaration in. +language = "Language" decimal = "Decimal" integer = "int" positive_integer = "int" diff --git a/gen/cpp/templates/attr_default_expr.tmpl b/gen/cpp/templates/attr_default_expr.tmpl index 0ac44ec54..7a4ca5704 100644 --- a/gen/cpp/templates/attr_default_expr.tmpl +++ b/gen/cpp/templates/attr_default_expr.tmpl @@ -1 +1 @@ -{{#type_ref.is_value}}{{type_ref.ident}}::parse({{import_default_q}}){{/type_ref.is_value}}{{#type_ref.is_primitive_string}}std::string({{import_default_q}}){{/type_ref.is_primitive_string}}{{#type_ref.is_primitive_integer}}parseInt({{import_default_q}}){{/type_ref.is_primitive_integer}}{{#type_ref.is_primitive_decimal}}Decimal::parse({{import_default_q}}){{/type_ref.is_primitive_decimal}} \ No newline at end of file +{{#type_ref.is_value}}{{type_ref.ident}}::parse({{import_default_q}}){{/type_ref.is_value}}{{#type_ref.wrapper}}{{type_ref.ident}}::parse({{import_default_q}}){{/type_ref.wrapper}}{{#type_ref.is_primitive_string}}{{^type_ref.wrapper}}std::string({{import_default_q}}){{/type_ref.wrapper}}{{/type_ref.is_primitive_string}}{{#type_ref.is_primitive_integer}}parseInt({{import_default_q}}){{/type_ref.is_primitive_integer}}{{#type_ref.is_primitive_decimal}}Decimal::parse({{import_default_q}}){{/type_ref.is_primitive_decimal}} \ No newline at end of file diff --git a/gen/cpp/templates/attr_parse_expr.tmpl b/gen/cpp/templates/attr_parse_expr.tmpl index 30eb47782..86d2cac05 100644 --- a/gen/cpp/templates/attr_parse_expr.tmpl +++ b/gen/cpp/templates/attr_parse_expr.tmpl @@ -1 +1 @@ -{{#type_ref.is_value}}parseValue<{{type_ref.ident}}>(a.value(), context, el, {{name.wire_q}}){{/type_ref.is_value}}{{#type_ref.name_token}}{{#type_ref.unique_id}}parseIdValue<{{type_ref.ident}}>(a.value(), context, el, {{name.wire_q}}){{/type_ref.unique_id}}{{^type_ref.unique_id}}parseValue<{{type_ref.ident}}>(a.value(), context, el, {{name.wire_q}}){{/type_ref.unique_id}}{{/type_ref.name_token}}{{#type_ref.is_primitive_string}}{{^type_ref.name_token}}std::string{a.value()}{{/type_ref.name_token}}{{/type_ref.is_primitive_string}}{{#type_ref.is_primitive_integer}}parseIntegerValue(a.value(), context, el, {{name.wire_q}}){{/type_ref.is_primitive_integer}}{{#type_ref.is_primitive_decimal}}parseDecimalValue(a.value(), context, el, {{name.wire_q}}){{/type_ref.is_primitive_decimal}} \ No newline at end of file +{{#type_ref.is_value}}parseValue<{{type_ref.ident}}>(a.value(), context, el, {{name.wire_q}}){{/type_ref.is_value}}{{#type_ref.wrapper}}{{#type_ref.unique_id}}parseIdValue<{{type_ref.ident}}>(a.value(), context, el, {{name.wire_q}}){{/type_ref.unique_id}}{{^type_ref.unique_id}}parseValue<{{type_ref.ident}}>(a.value(), context, el, {{name.wire_q}}){{/type_ref.unique_id}}{{/type_ref.wrapper}}{{#type_ref.is_primitive_string}}{{^type_ref.wrapper}}std::string{a.value()}{{/type_ref.wrapper}}{{/type_ref.is_primitive_string}}{{#type_ref.is_primitive_integer}}parseIntegerValue(a.value(), context, el, {{name.wire_q}}){{/type_ref.is_primitive_integer}}{{#type_ref.is_primitive_decimal}}parseDecimalValue(a.value(), context, el, {{name.wire_q}}){{/type_ref.is_primitive_decimal}} \ No newline at end of file diff --git a/gen/cpp/templates/attributes_serialize.tmpl b/gen/cpp/templates/attributes_serialize.tmpl index 6cb61c1a0..648e3d956 100644 --- a/gen/cpp/templates/attributes_serialize.tmpl +++ b/gen/cpp/templates/attributes_serialize.tmpl @@ -1,11 +1,11 @@ {{#merged_attributes}} {{#is_required}} - setAttribute(el, {{name.wire_q}}, {{#type_ref.is_value}}v.{{ident}}().toString(){{/type_ref.is_value}}{{#type_ref.name_token}}v.{{ident}}().toString(){{/type_ref.name_token}}{{#type_ref.is_primitive_string}}{{^type_ref.name_token}}v.{{ident}}(){{/type_ref.name_token}}{{/type_ref.is_primitive_string}}{{#type_ref.is_primitive_integer}}formatInt(v.{{ident}}()){{/type_ref.is_primitive_integer}}{{#type_ref.is_primitive_decimal}}v.{{ident}}().toString(){{/type_ref.is_primitive_decimal}}); + setAttribute(el, {{name.wire_q}}, {{#type_ref.is_value}}v.{{ident}}().toString(){{/type_ref.is_value}}{{#type_ref.wrapper}}v.{{ident}}().toString(){{/type_ref.wrapper}}{{#type_ref.is_primitive_string}}{{^type_ref.wrapper}}v.{{ident}}(){{/type_ref.wrapper}}{{/type_ref.is_primitive_string}}{{#type_ref.is_primitive_integer}}formatInt(v.{{ident}}()){{/type_ref.is_primitive_integer}}{{#type_ref.is_primitive_decimal}}v.{{ident}}().toString(){{/type_ref.is_primitive_decimal}}); {{/is_required}} {{#is_optional}} if (v.{{ident}}()) { - setAttribute(el, {{name.wire_q}}, {{#type_ref.is_value}}v.{{ident}}()->toString(){{/type_ref.is_value}}{{#type_ref.name_token}}v.{{ident}}()->toString(){{/type_ref.name_token}}{{#type_ref.is_primitive_string}}{{^type_ref.name_token}}*v.{{ident}}(){{/type_ref.name_token}}{{/type_ref.is_primitive_string}}{{#type_ref.is_primitive_integer}}formatInt(*v.{{ident}}()){{/type_ref.is_primitive_integer}}{{#type_ref.is_primitive_decimal}}v.{{ident}}()->toString(){{/type_ref.is_primitive_decimal}}); + setAttribute(el, {{name.wire_q}}, {{#type_ref.is_value}}v.{{ident}}()->toString(){{/type_ref.is_value}}{{#type_ref.wrapper}}v.{{ident}}()->toString(){{/type_ref.wrapper}}{{#type_ref.is_primitive_string}}{{^type_ref.wrapper}}*v.{{ident}}(){{/type_ref.wrapper}}{{/type_ref.is_primitive_string}}{{#type_ref.is_primitive_integer}}formatInt(*v.{{ident}}()){{/type_ref.is_primitive_integer}}{{#type_ref.is_primitive_decimal}}v.{{ident}}()->toString(){{/type_ref.is_primitive_decimal}}); } {{/is_optional}} {{/merged_attributes}} diff --git a/gen/cpp/templates/attrs.h.tmpl b/gen/cpp/templates/attrs.h.tmpl index 3c9bef23c..0288a9553 100644 --- a/gen/cpp/templates/attrs.h.tmpl +++ b/gen/cpp/templates/attrs.h.tmpl @@ -12,6 +12,9 @@ #include "mx/core/NameToken.h" #include "mx/core/Token.h" {{/any_name_token}} +{{#any_language}} +#include "mx/core/Language.h" +{{/any_language}} #include #include diff --git a/gen/cpp/templates/composite.h.tmpl b/gen/cpp/templates/composite.h.tmpl index d3235bd91..918ef3ad3 100644 --- a/gen/cpp/templates/composite.h.tmpl +++ b/gen/cpp/templates/composite.h.tmpl @@ -12,6 +12,9 @@ #include "mx/core/NameToken.h" #include "mx/core/Token.h" {{/any_name_token}} +{{#any_language}} +#include "mx/core/Language.h" +{{/any_language}} {{#any_min1}} #include "mx/core/OneOrMore.h" {{/any_min1}} diff --git a/gen/cpp/templates/derived.h.tmpl b/gen/cpp/templates/derived.h.tmpl index 98e479f4e..513a84113 100644 --- a/gen/cpp/templates/derived.h.tmpl +++ b/gen/cpp/templates/derived.h.tmpl @@ -12,6 +12,9 @@ #include "mx/core/NameToken.h" #include "mx/core/Token.h" {{/any_name_token}} +{{#any_language}} +#include "mx/core/Language.h" +{{/any_language}} #include #include diff --git a/gen/cpp/templates/value_class.h.tmpl b/gen/cpp/templates/value_class.h.tmpl index c3a17ca9c..7a036f88f 100644 --- a/gen/cpp/templates/value_class.h.tmpl +++ b/gen/cpp/templates/value_class.h.tmpl @@ -12,6 +12,9 @@ #include "mx/core/NameToken.h" #include "mx/core/Token.h" {{/any_name_token}} +{{#any_language}} +#include "mx/core/Language.h" +{{/any_language}} #include #include diff --git a/gen/ir/build.py b/gen/ir/build.py index ed2d4a159..71c577c7e 100644 --- a/gen/ir/build.py +++ b/gen/ir/build.py @@ -21,7 +21,15 @@ "xs:nonNegativeInteger": "non_negative_integer", "xs:date": "date", "xs:anyURI": "string", - "xs:language": "token", + # The XML namespace's language tag: a token restricted by the builtin + # pattern [a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})* (XSD 1.0 Part 2). A distinct + # primitive for the same reason the identity tokens are: a target that + # wants validity-by-construction repairs it, and a non-validating target + # maps it straight back to its token spelling (zero output change). + "xs:language": "language", + # NCName is NMTOKEN minus the colon; no MusicXML type rests on it + # directly, but the imported xml:space enumeration does. + "xs:NCName": "nmtoken", # The identity builtins are NCName-derived: lexically NARROWER than token # (no whitespace, no empty value, no leading digit or ':' for ID/IDREF; # ID values are document-unique). Preserved as distinct primitives -- a @@ -32,18 +40,17 @@ "xs:IDREF": "idref", } -# The 10 attribute refs into the imported xml/xlink schemas, resolved to the -# primitive the emitter should use. This is the only place the IR reaches -# outside the main schema. +# The imported xml/xlink attribute refs the IR resolves to a builtin +# primitive instead of following the import: xml:lang's declared type is the +# xs:language BUILTIN (no schema declares it) and href/role/title are +# xs:anyURI/xs:string, which accept nearly any string. Every other external +# ref is lowered from its declaration in the imported schema, which the XSD +# parser has read beside the main one. _EXTERNAL_ATTR = { - "xml:lang": "token", - "xml:space": "token", + "xml:lang": "language", "xlink:href": "string", - "xlink:type": "token", "xlink:role": "string", "xlink:title": "string", - "xlink:show": "token", - "xlink:actuate": "token", } _NUMERIC = {"decimal", "integer", "positive_integer", "non_negative_integer"} @@ -82,6 +89,16 @@ def __init__(self, schema: xsd.Schema, source: str): self.source = source self.anon_names: dict[int, str] = {} # id(ComplexType) -> synthesized name self.synth: list[tuple[str, xsd.ComplexType]] = [] + # The imported schemas' attribute declarations, indexed by local name + # (the wire ref keeps its prefix). + self.external_attrs: dict[str, list[tuple[str, xsd.Attribute]]] = {} + for namespace, ext in schema.imported.items(): + for attr_name, decl in ext.attributes.items(): + self.external_attrs.setdefault(attr_name, []).append((namespace, decl)) + # Value types lowered out of the imported schemas, by IR name. They + # are picked up when an attribute ref reaches them (so no unreachable + # external type ever enters the IR). + self.external_values: dict[str, ir.ValueType] = {} # ----- top level ------------------------------------------------------- # @@ -90,11 +107,10 @@ def build(self) -> ir.Ir: reachable = reachable_types(self.schema) value_types = [ - self._value_type(st) + self._value_type(st, self.schema.simple_types) for name, st in self.schema.simple_types.items() if name in reachable ] - value_types = self._topo_sort_values(value_types) groups = [ ir.Group(name, self._particle(g.particle), g.doc) for name, g in self.schema.groups.items() @@ -116,6 +132,11 @@ def build(self) -> ir.Ir: ] complex_types += [self._complex_type(name, ct) for name, ct in self.synth] + # The attribute refs lowered above may have pulled value types out of + # the imported schemas; they join the emit order deps-first. + value_types += list(self.external_values.values()) + value_types = self._topo_sort_values(value_types) + resolver = Resolver(groups, attribute_groups, complex_types) for ct in complex_types: ct.deps = sorted(resolver.deps(ct)) @@ -169,20 +190,25 @@ def _scan(self, particle, qualifier: str, used: set) -> None: # ----- value types ----------------------------------------------------- # - def _value_type(self, st: xsd.SimpleType) -> ir.ValueType: + def _value_type(self, st: xsd.SimpleType, simple_types: dict, name: str | None = None) -> ir.ValueType: + """Lower one simple type. `simple_types` is the schema the type is + declared in (the main one, or an imported schema); `name` overrides + the type's own name, for an anonymous type hoisted under a name the + declaring schema gave it only by context.""" + type_name = st.name if name is None else name if isinstance(st.content, xsd.Union): - return self._union(st) + return self._union(type_name, st, simple_types) if isinstance(st.content, xsd.ListType): # MusicXML uses no xs:list; represent defensively as a token string. - return ir.StringType(st.name, "token", doc=st.doc) - primitive, facets = self._resolve_restriction(st.name) + return ir.StringType(type_name, "token", doc=st.doc) + primitive, facets = self._restriction(simple_types, st) if facets.enumerations: return ir.EnumType( - st.name, primitive, [e.value for e in facets.enumerations], st.doc + type_name, primitive, [e.value for e in facets.enumerations], st.doc ) if primitive in _NUMERIC: return ir.NumberType( - st.name, + type_name, primitive, facets.min_inclusive, facets.max_inclusive, @@ -191,7 +217,7 @@ def _value_type(self, st: xsd.SimpleType) -> ir.ValueType: st.doc, ) return ir.StringType( - st.name, + type_name, primitive, list(facets.patterns), facets.min_length, @@ -200,24 +226,24 @@ def _value_type(self, st: xsd.SimpleType) -> ir.ValueType: st.doc, ) - def _resolve_restriction(self, type_name: str) -> tuple[str, xsd.Facets]: + def _restriction(self, simple_types: dict, st: xsd.SimpleType) -> tuple[str, xsd.Facets]: + """A restriction's own facets merged onto its base chain.""" + primitive, merged = self._resolve_restriction(simple_types, st.content.base) + _merge_facets(merged, st.content.facets) + return primitive, merged + + def _resolve_restriction(self, simple_types: dict, type_name: str) -> tuple[str, xsd.Facets]: """Collapse a restriction chain to (primitive, merged facets). Child facets override inherited ones; patterns accumulate.""" - st = self.schema.simple_types.get(type_name) + st = simple_types.get(type_name) if st is None or not isinstance(st.content, xsd.Restriction): return _primitive(type_name), xsd.Facets() - base = st.content.base - if base in self.schema.simple_types: - primitive, merged = self._resolve_restriction(base) - else: - primitive, merged = _primitive(base), xsd.Facets() - _merge_facets(merged, st.content.facets) - return primitive, merged + return self._restriction(simple_types, st) - def _union(self, st: xsd.SimpleType) -> ir.UnionType: + def _union(self, name: str, st: xsd.SimpleType, simple_types: dict) -> ir.UnionType: members: list[ir.UnionMember] = [] for m in st.content.member_types: - if m in self.schema.simple_types: + if m in simple_types: members.append(ir.UnionMember(ir.Ref(m, "value"))) else: members.append(ir.UnionMember(ir.Ref(_primitive(m), "primitive"))) @@ -234,13 +260,64 @@ def _union(self, st: xsd.SimpleType) -> ir.UnionType: def _attr(self, a: xsd.Attribute) -> ir.Attr: if a.ref: - ref = ir.Ref(_primitive(a.ref), "primitive") + ref = self._external_ref(a.ref) name = a.ref else: ref = self._type_ref(a.type) if a.type else ir.Ref("string", "primitive") name = a.name or "" return ir.Attr(name, ref, a.use == "required", a.default, a.fixed, a.doc) + # ----- imported attributes --------------------------------------------- # + + def _external_ref(self, ref_name: str) -> ir.Ref: + """Resolve an imported attribute ref (xml:lang, xlink:type, ...) to the + type its declaration names. + + The pinned refs in _EXTERNAL_ATTR never look at the import: their + declared type is an XML-Schema BUILTIN with nothing to read. The rest + are declared in the imported xml/xlink schemas, which the XSD parser + has already read beside the main one. + """ + pinned = _EXTERNAL_ATTR.get(ref_name) + if pinned is not None: + return ir.Ref(pinned, "primitive") + candidates = self.external_attrs.get(ref_name.split(":")[-1], []) + if len(candidates) != 1: + raise ValueError( + f"external attribute ref {ref_name!r} resolves to " + f"{len(candidates)} declarations in the imported schemas" + ) + namespace, decl = candidates[0] + return self._external_decl_ref(namespace, decl, ref_name) + + def _external_decl_ref(self, namespace: str, decl: xsd.Attribute, ref_name: str) -> ir.Ref: + """The type an imported attribute declaration carries: a named type + from its own schema (xlink:typeType), or its inline type hoisted under + the qualified ref name (xml:space).""" + if decl.type: + return self._external_type_ref(namespace, decl.type) + if decl.inline_type is not None: + name = ref_name.replace(":", "-") + self._external_value(name, decl.inline_type, namespace) + return ir.Ref(name, "value") + return ir.Ref("string", "primitive") + + def _external_type_ref(self, namespace: str, type_name: str) -> ir.Ref: + ext = self.schema.imported[namespace] + name = type_name.split(":")[-1] + st = ext.simple_types.get(name) + if st is None: + return ir.Ref(_primitive(type_name), "primitive") + self._external_value(name, st, namespace) + return ir.Ref(name, "value") + + def _external_value(self, name: str, st: xsd.SimpleType, namespace: str) -> None: + """Lower (once) a value type declared in an imported schema.""" + if name not in self.external_values: + self.external_values[name] = self._value_type( + st, self.schema.imported[namespace].simple_types, name=name + ) + # ----- complex types --------------------------------------------------- # def _complex_type(self, name: str, ct: xsd.ComplexType) -> ir.ComplexType: diff --git a/gen/plates/build.py b/gen/plates/build.py index c07dbeb4f..105edf63a 100644 --- a/gen/plates/build.py +++ b/gen/plates/build.py @@ -64,6 +64,11 @@ # repairs them, a non-validating one ignores the flag. _PRIM_NAME_TOKEN = {"id", "idref", "idrefs", "nmtoken", "nmtokens"} +# The primitives a validating target maps to a wrapper class rather than a raw +# target string: the name tokens plus the language tag (a builtin lexical +# space, like them, but not a name). +_PRIM_WRAPPER = _PRIM_NAME_TOKEN | {"language"} + def wrap_doc(doc: str | None, width: int) -> list[str]: """Greedy word-wrap of raw doc text at `width` (the wrapped TEXT width; @@ -512,6 +517,7 @@ def _plate_ref(self, ref: ir.Ref) -> PlateRef: if ref.name in _PRIM_NUMERIC else "primitive-string", name_token=ref.name in _PRIM_NAME_TOKEN, + wrapper=ref.name in _PRIM_WRAPPER, unique_id=ref.name == "id", ) if ref.category == "value": diff --git a/gen/plates/model.py b/gen/plates/model.py index 838668ce4..9198d82e0 100644 --- a/gen/plates/model.py +++ b/gen/plates/model.py @@ -59,6 +59,11 @@ class PlateRef: # lexically narrower than a free string. A validating target renders a # repairing wrapper for it; others ignore the flag. Neutral schema fact. name_token: bool = False + # True when the target maps this primitive to a WRAPPER type whose + # value-style API (parse/toString) the rendered code uses instead of a raw + # target string. A superset of `name_token`: the identity tokens plus the + # language tag. Target binding, like `ident`. + wrapper: bool = False # True when the referent is the xs:ID primitive, whose value must be unique # within the document. Neutral schema fact. unique_id: bool = False diff --git a/gen/press/context.py b/gen/press/context.py index 9519b212a..231f3b309 100644 --- a/gen/press/context.py +++ b/gen/press/context.py @@ -226,6 +226,7 @@ def plate_context(plates: Plates, plate) -> dict: refs += [f.type_ref for f in items] ctx["any_decimal"] = any(r.kind == "primitive-decimal" for r in refs) ctx["any_name_token"] = any(r.name_token for r in refs) + ctx["any_language"] = any(r.wire == "language" for r in refs) ctx["any_min1"] = any(f.min1 for f in items) ctx["any_bounded"] = any(f.max is not None for f in items) ctx["any_vector"] = any(f.cardinality == "vector" for f in items) diff --git a/gen/schema/out/musicxml.schema.json b/gen/schema/out/musicxml.schema.json index 931025401..c70548a24 100644 --- a/gen/schema/out/musicxml.schema.json +++ b/gen/schema/out/musicxml.schema.json @@ -21,6 +21,9 @@ "minimum": 1, "maximum": 3 }, + "actuateType": { + "enum": ["onLoad", "onRequest", "other", "none"] + }, "arrow-direction": { "description": "The arrow-direction type represents the direction in which an arrow points, using Unicode arrow terminology.", "enum": ["left", "up", "right", "down", "northwest", "northeast", "southeast", "southwest", "left right", "up down", "northwest southeast", "northeast southwest", "other"] @@ -401,6 +404,9 @@ "description": "The show-tuplet type indicates whether to show a part of a tuplet relating to the tuplet-actual element, both the tuplet-actual and tuplet-normal elements, or neither.", "enum": ["actual", "both", "none"] }, + "showType": { + "enum": ["new", "replace", "embed", "other", "none"] + }, "smufl-accidental-glyph-name": { "description": "The smufl-accidental-glyph-name type is used to reference a specific Standard Music Font Layout (SMuFL) accidental character. The value is a SMuFL canonical glyph name that starts with one of the strings used at the start of glyph names for SMuFL accidentals.", "type": "string", @@ -587,6 +593,9 @@ "description": "The two-note-turn type describes the ending notes of trills and mordents for playback, relative to the current note.", "enum": ["whole", "half", "none"] }, + "typeType": { + "enum": ["simple", "extended", "title", "resource", "locator", "arc"] + }, "up-down": { "description": "The up-down type is used for the direction of arrows and other pointed symbols like vertical accents, indicating which way the tip is pointing.", "enum": ["up", "down"] @@ -619,6 +628,9 @@ "description": "The wood-value type represents pictograms for wood percussion instruments. The maraca and maracas values distinguish the one- and two-maraca versions of the pictogram.", "enum": ["bamboo scraper", "board clapper", "cabasa", "castanets", "castanets with handle", "claves", "football rattle", "guiro", "log drum", "maraca", "maracas", "quijada", "rainstick", "ratchet", "reco-reco", "sandpaper blocks", "slit drum", "temple block", "vibraslap", "whip", "wood block"] }, + "xml-space": { + "enum": ["default", "preserve"] + }, "yes-no": { "description": "The yes-no type is used for boolean-like attributes. We cannot use W3C XML Schema booleans due to their restrictions on expression of boolean values.", "enum": ["yes", "no"] @@ -701,7 +713,7 @@ "properties": { "smufl": { "$ref": "#/$defs/smufl-accidental-glyph-name" }, "xml:lang": { "type": "string" }, - "xml:space": { "type": "string" }, + "xml:space": { "$ref": "#/$defs/xml-space" }, "justify": { "$ref": "#/$defs/left-center-right" }, "default-x": { "$ref": "#/$defs/tenths" }, "default-y": { "$ref": "#/$defs/tenths" }, @@ -1096,7 +1108,7 @@ "additionalProperties": false, "properties": { "xml:lang": { "type": "string" }, - "xml:space": { "type": "string" }, + "xml:space": { "$ref": "#/$defs/xml-space" }, "justify": { "$ref": "#/$defs/left-center-right" }, "default-x": { "$ref": "#/$defs/tenths" }, "default-y": { "$ref": "#/$defs/tenths" }, @@ -1766,7 +1778,7 @@ "additionalProperties": false, "properties": { "xml:lang": { "type": "string" }, - "xml:space": { "type": "string" }, + "xml:space": { "$ref": "#/$defs/xml-space" }, "justify": { "$ref": "#/$defs/left-center-right" }, "default-x": { "$ref": "#/$defs/tenths" }, "default-y": { "$ref": "#/$defs/tenths" }, @@ -1816,11 +1828,11 @@ "properties": { "name": { "type": "string" }, "xlink:href": { "type": "string" }, - "xlink:type": { "type": "string" }, + "xlink:type": { "$ref": "#/$defs/typeType" }, "xlink:role": { "type": "string" }, "xlink:title": { "type": "string" }, - "xlink:show": { "type": "string" }, - "xlink:actuate": { "type": "string" }, + "xlink:show": { "$ref": "#/$defs/showType" }, + "xlink:actuate": { "$ref": "#/$defs/actuateType" }, "element": { "type": "string" }, "position": { "type": "integer" }, "default-x": { "$ref": "#/$defs/tenths" }, @@ -4022,11 +4034,11 @@ "additionalProperties": false, "properties": { "xlink:href": { "type": "string" }, - "xlink:type": { "type": "string" }, + "xlink:type": { "$ref": "#/$defs/typeType" }, "xlink:role": { "type": "string" }, "xlink:title": { "type": "string" }, - "xlink:show": { "type": "string" }, - "xlink:actuate": { "type": "string" } + "xlink:show": { "$ref": "#/$defs/showType" }, + "xlink:actuate": { "$ref": "#/$defs/actuateType" } } }, "part-group": { @@ -4053,11 +4065,11 @@ "additionalProperties": false, "properties": { "xlink:href": { "type": "string" }, - "xlink:type": { "type": "string" }, + "xlink:type": { "$ref": "#/$defs/typeType" }, "xlink:role": { "type": "string" }, "xlink:title": { "type": "string" }, - "xlink:show": { "type": "string" }, - "xlink:actuate": { "type": "string" }, + "xlink:show": { "$ref": "#/$defs/showType" }, + "xlink:actuate": { "$ref": "#/$defs/actuateType" }, "instrument-link": { "type": "array", "items": { "$ref": "#/$defs/instrument-link" } }, "group-link": { "type": "array", "items": { "type": "string" } } } diff --git a/gen/test/c/config.toml b/gen/test/c/config.toml index 0aa3b8f0d..1a04ff2ba 100644 --- a/gen/test/c/config.toml +++ b/gen/test/c/config.toml @@ -41,6 +41,7 @@ token = "char *" nmtoken = "char *" id = "char *" idref = "char *" +language = "char *" date = "char *" decimal = "double" integer = "long" diff --git a/gen/test/c/mx/mx_accidental_text.c b/gen/test/c/mx/mx_accidental_text.c index 4bdadc827..9712d7e08 100644 --- a/gen/test/c/mx/mx_accidental_text.c +++ b/gen/test/c/mx/mx_accidental_text.c @@ -28,7 +28,7 @@ MxAccidentalText *mx_accidental_text_parse(xmlNodePtr el) { m->xml_lang = mx_strdup(s); } else if (strcmp(aname, "xml:space") == 0) { m->has_xml_space = true; - m->xml_space = mx_strdup(s); + m->xml_space = mx_xml_space_parse(s); } else if (strcmp(aname, "justify") == 0) { m->has_justify = true; m->justify = mx_left_center_right_parse(s); @@ -131,7 +131,7 @@ xmlNodePtr mx_accidental_text_serialize(const MxAccidentalText *m, xmlNodePtr pa xmlSetProp(el, BAD_CAST "xml:lang", BAD_CAST m->xml_lang); } if (m->has_xml_space) { - xmlSetProp(el, BAD_CAST "xml:space", BAD_CAST m->xml_space); + xmlSetProp(el, BAD_CAST "xml:space", BAD_CAST mx_xml_space_to_string(m->xml_space)); } if (m->has_justify) { xmlSetProp(el, BAD_CAST "justify", BAD_CAST mx_left_center_right_to_string(m->justify)); @@ -225,8 +225,6 @@ void mx_accidental_text_free(MxAccidentalText *m) { free(m->smufl); if (m->has_xml_lang) free(m->xml_lang); - if (m->has_xml_space) - free(m->xml_space); if (m->has_font_family) free(m->font_family); if (m->has_font_size) diff --git a/gen/test/c/mx/mx_accidental_text.h b/gen/test/c/mx/mx_accidental_text.h index 5616a5d3d..6c67a0d28 100644 --- a/gen/test/c/mx/mx_accidental_text.h +++ b/gen/test/c/mx/mx_accidental_text.h @@ -21,6 +21,7 @@ #include "mx_tenths.h" #include "mx_text_direction.h" #include "mx_valign.h" +#include "mx_xml_space.h" /* * The accidental-text type represents an element with an accidental value and text-formatting @@ -32,7 +33,7 @@ typedef struct { bool has_xml_lang; char *xml_lang; /* attribute xml:lang */ bool has_xml_space; - char *xml_space; /* attribute xml:space */ + MxXMLSpace xml_space; /* attribute xml:space */ bool has_justify; MxLeftCenterRight justify; /* attribute justify */ bool has_default_x; diff --git a/gen/test/c/mx/mx_actuate_type.c b/gen/test/c/mx/mx_actuate_type.c new file mode 100644 index 000000000..8d821ad46 --- /dev/null +++ b/gen/test/c/mx/mx_actuate_type.c @@ -0,0 +1,37 @@ +/* Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. */ + +#include "mx_actuate_type.h" + +#include + +static const char *const mx_actuate_type_values[] = { + "onLoad", + "onRequest", + "other", + "none", +}; + +bool mx_actuate_type_try_parse(const char *s, MxActuateType *out) { + if (!s) + s = ""; + for (size_t i = 0; i < sizeof(mx_actuate_type_values) / sizeof(mx_actuate_type_values[0]); i++) { + if (strcmp(s, mx_actuate_type_values[i]) == 0) { + *out = (MxActuateType)i; + return true; + } + } + *out = (MxActuateType)0; + return false; +} + +MxActuateType mx_actuate_type_parse(const char *s) { + MxActuateType v; + mx_actuate_type_try_parse(s, &v); + return v; +} + +const char *mx_actuate_type_to_string(MxActuateType v) { + if ((size_t)v >= sizeof(mx_actuate_type_values) / sizeof(mx_actuate_type_values[0])) + return mx_actuate_type_values[0]; + return mx_actuate_type_values[v]; +} diff --git a/gen/test/c/mx/mx_actuate_type.h b/gen/test/c/mx/mx_actuate_type.h new file mode 100644 index 000000000..843622e3b --- /dev/null +++ b/gen/test/c/mx/mx_actuate_type.h @@ -0,0 +1,21 @@ +/* Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. */ + +#ifndef MX_ACTUATE_TYPE_H_INCLUDED +#define MX_ACTUATE_TYPE_H_INCLUDED + +#include + +typedef enum { + MX_ACTUATE_TYPE_ON_LOAD = 0, + MX_ACTUATE_TYPE_ON_REQUEST = 1, + MX_ACTUATE_TYPE_OTHER = 2, + MX_ACTUATE_TYPE_NONE = 3 +} MxActuateType; + +bool mx_actuate_type_try_parse(const char *s, MxActuateType *out); +/* Lenient: unknown input falls back to the first variant. */ +MxActuateType mx_actuate_type_parse(const char *s); +/* Returns the wire literal (static storage; do not free). */ +const char *mx_actuate_type_to_string(MxActuateType v); + +#endif /* MX_ACTUATE_TYPE_H_INCLUDED */ diff --git a/gen/test/c/mx/mx_formatted_text.c b/gen/test/c/mx/mx_formatted_text.c index deff066b2..170b30d7b 100644 --- a/gen/test/c/mx/mx_formatted_text.c +++ b/gen/test/c/mx/mx_formatted_text.c @@ -25,7 +25,7 @@ MxFormattedText *mx_formatted_text_parse(xmlNodePtr el) { m->xml_lang = mx_strdup(s); } else if (strcmp(aname, "xml:space") == 0) { m->has_xml_space = true; - m->xml_space = mx_strdup(s); + m->xml_space = mx_xml_space_parse(s); } else if (strcmp(aname, "justify") == 0) { m->has_justify = true; m->justify = mx_left_center_right_parse(s); @@ -125,7 +125,7 @@ xmlNodePtr mx_formatted_text_serialize(const MxFormattedText *m, xmlNodePtr pare xmlSetProp(el, BAD_CAST "xml:lang", BAD_CAST m->xml_lang); } if (m->has_xml_space) { - xmlSetProp(el, BAD_CAST "xml:space", BAD_CAST m->xml_space); + xmlSetProp(el, BAD_CAST "xml:space", BAD_CAST mx_xml_space_to_string(m->xml_space)); } if (m->has_justify) { xmlSetProp(el, BAD_CAST "justify", BAD_CAST mx_left_center_right_to_string(m->justify)); @@ -217,8 +217,6 @@ void mx_formatted_text_free(MxFormattedText *m) { return; if (m->has_xml_lang) free(m->xml_lang); - if (m->has_xml_space) - free(m->xml_space); if (m->has_font_family) free(m->font_family); if (m->has_font_size) diff --git a/gen/test/c/mx/mx_formatted_text.h b/gen/test/c/mx/mx_formatted_text.h index 1289b9da5..81ca9fed9 100644 --- a/gen/test/c/mx/mx_formatted_text.h +++ b/gen/test/c/mx/mx_formatted_text.h @@ -19,6 +19,7 @@ #include "mx_tenths.h" #include "mx_text_direction.h" #include "mx_valign.h" +#include "mx_xml_space.h" /* * The formatted-text type represents a text element with text-formatting attributes. @@ -27,7 +28,7 @@ typedef struct { bool has_xml_lang; char *xml_lang; /* attribute xml:lang */ bool has_xml_space; - char *xml_space; /* attribute xml:space */ + MxXMLSpace xml_space; /* attribute xml:space */ bool has_justify; MxLeftCenterRight justify; /* attribute justify */ bool has_default_x; diff --git a/gen/test/c/mx/mx_formatted_text_id.c b/gen/test/c/mx/mx_formatted_text_id.c index 973a3701a..1cf526bdc 100644 --- a/gen/test/c/mx/mx_formatted_text_id.c +++ b/gen/test/c/mx/mx_formatted_text_id.c @@ -25,7 +25,7 @@ MxFormattedTextID *mx_formatted_text_id_parse(xmlNodePtr el) { m->xml_lang = mx_strdup(s); } else if (strcmp(aname, "xml:space") == 0) { m->has_xml_space = true; - m->xml_space = mx_strdup(s); + m->xml_space = mx_xml_space_parse(s); } else if (strcmp(aname, "justify") == 0) { m->has_justify = true; m->justify = mx_left_center_right_parse(s); @@ -128,7 +128,7 @@ xmlNodePtr mx_formatted_text_id_serialize(const MxFormattedTextID *m, xmlNodePtr xmlSetProp(el, BAD_CAST "xml:lang", BAD_CAST m->xml_lang); } if (m->has_xml_space) { - xmlSetProp(el, BAD_CAST "xml:space", BAD_CAST m->xml_space); + xmlSetProp(el, BAD_CAST "xml:space", BAD_CAST mx_xml_space_to_string(m->xml_space)); } if (m->has_justify) { xmlSetProp(el, BAD_CAST "justify", BAD_CAST mx_left_center_right_to_string(m->justify)); @@ -223,8 +223,6 @@ void mx_formatted_text_id_free(MxFormattedTextID *m) { return; if (m->has_xml_lang) free(m->xml_lang); - if (m->has_xml_space) - free(m->xml_space); if (m->has_font_family) free(m->font_family); if (m->has_font_size) diff --git a/gen/test/c/mx/mx_formatted_text_id.h b/gen/test/c/mx/mx_formatted_text_id.h index 140bfd109..ea9fd90cc 100644 --- a/gen/test/c/mx/mx_formatted_text_id.h +++ b/gen/test/c/mx/mx_formatted_text_id.h @@ -19,6 +19,7 @@ #include "mx_tenths.h" #include "mx_text_direction.h" #include "mx_valign.h" +#include "mx_xml_space.h" /* * The formatted-text-id type represents a text element with text-formatting and id attributes. @@ -27,7 +28,7 @@ typedef struct { bool has_xml_lang; char *xml_lang; /* attribute xml:lang */ bool has_xml_space; - char *xml_space; /* attribute xml:space */ + MxXMLSpace xml_space; /* attribute xml:space */ bool has_justify; MxLeftCenterRight justify; /* attribute justify */ bool has_default_x; diff --git a/gen/test/c/mx/mx_link.c b/gen/test/c/mx/mx_link.c index 17e217edb..81afdb4d4 100644 --- a/gen/test/c/mx/mx_link.c +++ b/gen/test/c/mx/mx_link.c @@ -28,7 +28,7 @@ MxLink *mx_link_parse(xmlNodePtr el) { m->xlink_href = mx_strdup(s); } else if (strcmp(aname, "xlink:type") == 0) { m->has_xlink_type = true; - m->xlink_type = mx_strdup(s); + m->xlink_type = mx_type_type_parse(s); } else if (strcmp(aname, "xlink:role") == 0) { m->has_xlink_role = true; m->xlink_role = mx_strdup(s); @@ -37,10 +37,10 @@ MxLink *mx_link_parse(xmlNodePtr el) { m->xlink_title = mx_strdup(s); } else if (strcmp(aname, "xlink:show") == 0) { m->has_xlink_show = true; - m->xlink_show = mx_strdup(s); + m->xlink_show = mx_show_type_parse(s); } else if (strcmp(aname, "xlink:actuate") == 0) { m->has_xlink_actuate = true; - m->xlink_actuate = mx_strdup(s); + m->xlink_actuate = mx_actuate_type_parse(s); } else if (strcmp(aname, "element") == 0) { m->has_element = true; m->element = mx_strdup(s); @@ -94,7 +94,7 @@ xmlNodePtr mx_link_serialize(const MxLink *m, xmlNodePtr parent, const char *tag xmlSetProp(el, BAD_CAST "xlink:href", BAD_CAST m->xlink_href); } if (m->has_xlink_type) { - xmlSetProp(el, BAD_CAST "xlink:type", BAD_CAST m->xlink_type); + xmlSetProp(el, BAD_CAST "xlink:type", BAD_CAST mx_type_type_to_string(m->xlink_type)); } if (m->has_xlink_role) { xmlSetProp(el, BAD_CAST "xlink:role", BAD_CAST m->xlink_role); @@ -103,10 +103,10 @@ xmlNodePtr mx_link_serialize(const MxLink *m, xmlNodePtr parent, const char *tag xmlSetProp(el, BAD_CAST "xlink:title", BAD_CAST m->xlink_title); } if (m->has_xlink_show) { - xmlSetProp(el, BAD_CAST "xlink:show", BAD_CAST m->xlink_show); + xmlSetProp(el, BAD_CAST "xlink:show", BAD_CAST mx_show_type_to_string(m->xlink_show)); } if (m->has_xlink_actuate) { - xmlSetProp(el, BAD_CAST "xlink:actuate", BAD_CAST m->xlink_actuate); + xmlSetProp(el, BAD_CAST "xlink:actuate", BAD_CAST mx_actuate_type_to_string(m->xlink_actuate)); } if (m->has_element) { xmlSetProp(el, BAD_CAST "element", BAD_CAST m->element); @@ -146,16 +146,10 @@ void mx_link_free(MxLink *m) { free(m->name); if (m->has_xlink_href) free(m->xlink_href); - if (m->has_xlink_type) - free(m->xlink_type); if (m->has_xlink_role) free(m->xlink_role); if (m->has_xlink_title) free(m->xlink_title); - if (m->has_xlink_show) - free(m->xlink_show); - if (m->has_xlink_actuate) - free(m->xlink_actuate); if (m->has_element) free(m->element); free(m); diff --git a/gen/test/c/mx/mx_link.h b/gen/test/c/mx/mx_link.h index 7245ec075..bb26329af 100644 --- a/gen/test/c/mx/mx_link.h +++ b/gen/test/c/mx/mx_link.h @@ -6,7 +6,10 @@ #include #include #include +#include "mx_actuate_type.h" +#include "mx_show_type.h" #include "mx_tenths.h" +#include "mx_type_type.h" /* * The link type serves as an outgoing simple XLink. It is also used to connect a MusicXML score @@ -19,15 +22,15 @@ typedef struct { bool has_xlink_href; char *xlink_href; /* attribute xlink:href */ bool has_xlink_type; - char *xlink_type; /* attribute xlink:type */ + MxTypeType xlink_type; /* attribute xlink:type */ bool has_xlink_role; char *xlink_role; /* attribute xlink:role */ bool has_xlink_title; char *xlink_title; /* attribute xlink:title */ bool has_xlink_show; - char *xlink_show; /* attribute xlink:show */ + MxShowType xlink_show; /* attribute xlink:show */ bool has_xlink_actuate; - char *xlink_actuate; /* attribute xlink:actuate */ + MxActuateType xlink_actuate; /* attribute xlink:actuate */ bool has_element; char *element; /* attribute element */ bool has_position; diff --git a/gen/test/c/mx/mx_opus.c b/gen/test/c/mx/mx_opus.c index 058865b2c..2c3e96419 100644 --- a/gen/test/c/mx/mx_opus.c +++ b/gen/test/c/mx/mx_opus.c @@ -25,7 +25,7 @@ MxOpus *mx_opus_parse(xmlNodePtr el) { m->xlink_href = mx_strdup(s); } else if (strcmp(aname, "xlink:type") == 0) { m->has_xlink_type = true; - m->xlink_type = mx_strdup(s); + m->xlink_type = mx_type_type_parse(s); } else if (strcmp(aname, "xlink:role") == 0) { m->has_xlink_role = true; m->xlink_role = mx_strdup(s); @@ -34,10 +34,10 @@ MxOpus *mx_opus_parse(xmlNodePtr el) { m->xlink_title = mx_strdup(s); } else if (strcmp(aname, "xlink:show") == 0) { m->has_xlink_show = true; - m->xlink_show = mx_strdup(s); + m->xlink_show = mx_show_type_parse(s); } else if (strcmp(aname, "xlink:actuate") == 0) { m->has_xlink_actuate = true; - m->xlink_actuate = mx_strdup(s); + m->xlink_actuate = mx_actuate_type_parse(s); } else { mx_error_set("unknown attribute \"%s\" on <%s>", aname, (const char *)el->name); @@ -70,7 +70,7 @@ xmlNodePtr mx_opus_serialize(const MxOpus *m, xmlNodePtr parent, const char *tag xmlSetProp(el, BAD_CAST "xlink:href", BAD_CAST m->xlink_href); } if (m->has_xlink_type) { - xmlSetProp(el, BAD_CAST "xlink:type", BAD_CAST m->xlink_type); + xmlSetProp(el, BAD_CAST "xlink:type", BAD_CAST mx_type_type_to_string(m->xlink_type)); } if (m->has_xlink_role) { xmlSetProp(el, BAD_CAST "xlink:role", BAD_CAST m->xlink_role); @@ -79,10 +79,10 @@ xmlNodePtr mx_opus_serialize(const MxOpus *m, xmlNodePtr parent, const char *tag xmlSetProp(el, BAD_CAST "xlink:title", BAD_CAST m->xlink_title); } if (m->has_xlink_show) { - xmlSetProp(el, BAD_CAST "xlink:show", BAD_CAST m->xlink_show); + xmlSetProp(el, BAD_CAST "xlink:show", BAD_CAST mx_show_type_to_string(m->xlink_show)); } if (m->has_xlink_actuate) { - xmlSetProp(el, BAD_CAST "xlink:actuate", BAD_CAST m->xlink_actuate); + xmlSetProp(el, BAD_CAST "xlink:actuate", BAD_CAST mx_actuate_type_to_string(m->xlink_actuate)); } return el; } @@ -92,15 +92,9 @@ void mx_opus_free(MxOpus *m) { return; if (m->has_xlink_href) free(m->xlink_href); - if (m->has_xlink_type) - free(m->xlink_type); if (m->has_xlink_role) free(m->xlink_role); if (m->has_xlink_title) free(m->xlink_title); - if (m->has_xlink_show) - free(m->xlink_show); - if (m->has_xlink_actuate) - free(m->xlink_actuate); free(m); } diff --git a/gen/test/c/mx/mx_opus.h b/gen/test/c/mx/mx_opus.h index 2dff1cafe..e9b6093be 100644 --- a/gen/test/c/mx/mx_opus.h +++ b/gen/test/c/mx/mx_opus.h @@ -6,6 +6,9 @@ #include #include #include +#include "mx_actuate_type.h" +#include "mx_show_type.h" +#include "mx_type_type.h" /* * The opus type represents a link to a MusicXML opus document that composes multiple MusicXML @@ -15,15 +18,15 @@ typedef struct { bool has_xlink_href; char *xlink_href; /* attribute xlink:href */ bool has_xlink_type; - char *xlink_type; /* attribute xlink:type */ + MxTypeType xlink_type; /* attribute xlink:type */ bool has_xlink_role; char *xlink_role; /* attribute xlink:role */ bool has_xlink_title; char *xlink_title; /* attribute xlink:title */ bool has_xlink_show; - char *xlink_show; /* attribute xlink:show */ + MxShowType xlink_show; /* attribute xlink:show */ bool has_xlink_actuate; - char *xlink_actuate; /* attribute xlink:actuate */ + MxActuateType xlink_actuate; /* attribute xlink:actuate */ } MxOpus; /* NULL on error; the message is in mx_error(). */ diff --git a/gen/test/c/mx/mx_show_type.c b/gen/test/c/mx/mx_show_type.c new file mode 100644 index 000000000..879d1b26d --- /dev/null +++ b/gen/test/c/mx/mx_show_type.c @@ -0,0 +1,38 @@ +/* Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. */ + +#include "mx_show_type.h" + +#include + +static const char *const mx_show_type_values[] = { + "new", + "replace", + "embed", + "other", + "none", +}; + +bool mx_show_type_try_parse(const char *s, MxShowType *out) { + if (!s) + s = ""; + for (size_t i = 0; i < sizeof(mx_show_type_values) / sizeof(mx_show_type_values[0]); i++) { + if (strcmp(s, mx_show_type_values[i]) == 0) { + *out = (MxShowType)i; + return true; + } + } + *out = (MxShowType)0; + return false; +} + +MxShowType mx_show_type_parse(const char *s) { + MxShowType v; + mx_show_type_try_parse(s, &v); + return v; +} + +const char *mx_show_type_to_string(MxShowType v) { + if ((size_t)v >= sizeof(mx_show_type_values) / sizeof(mx_show_type_values[0])) + return mx_show_type_values[0]; + return mx_show_type_values[v]; +} diff --git a/gen/test/c/mx/mx_show_type.h b/gen/test/c/mx/mx_show_type.h new file mode 100644 index 000000000..cb72687e8 --- /dev/null +++ b/gen/test/c/mx/mx_show_type.h @@ -0,0 +1,22 @@ +/* Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. */ + +#ifndef MX_SHOW_TYPE_H_INCLUDED +#define MX_SHOW_TYPE_H_INCLUDED + +#include + +typedef enum { + MX_SHOW_TYPE_NEW = 0, + MX_SHOW_TYPE_REPLACE = 1, + MX_SHOW_TYPE_EMBED = 2, + MX_SHOW_TYPE_OTHER = 3, + MX_SHOW_TYPE_NONE = 4 +} MxShowType; + +bool mx_show_type_try_parse(const char *s, MxShowType *out); +/* Lenient: unknown input falls back to the first variant. */ +MxShowType mx_show_type_parse(const char *s); +/* Returns the wire literal (static storage; do not free). */ +const char *mx_show_type_to_string(MxShowType v); + +#endif /* MX_SHOW_TYPE_H_INCLUDED */ diff --git a/gen/test/c/mx/mx_type_type.c b/gen/test/c/mx/mx_type_type.c new file mode 100644 index 000000000..c2ce57d15 --- /dev/null +++ b/gen/test/c/mx/mx_type_type.c @@ -0,0 +1,39 @@ +/* Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. */ + +#include "mx_type_type.h" + +#include + +static const char *const mx_type_type_values[] = { + "simple", + "extended", + "title", + "resource", + "locator", + "arc", +}; + +bool mx_type_type_try_parse(const char *s, MxTypeType *out) { + if (!s) + s = ""; + for (size_t i = 0; i < sizeof(mx_type_type_values) / sizeof(mx_type_type_values[0]); i++) { + if (strcmp(s, mx_type_type_values[i]) == 0) { + *out = (MxTypeType)i; + return true; + } + } + *out = (MxTypeType)0; + return false; +} + +MxTypeType mx_type_type_parse(const char *s) { + MxTypeType v; + mx_type_type_try_parse(s, &v); + return v; +} + +const char *mx_type_type_to_string(MxTypeType v) { + if ((size_t)v >= sizeof(mx_type_type_values) / sizeof(mx_type_type_values[0])) + return mx_type_type_values[0]; + return mx_type_type_values[v]; +} diff --git a/gen/test/c/mx/mx_type_type.h b/gen/test/c/mx/mx_type_type.h new file mode 100644 index 000000000..75a255fc8 --- /dev/null +++ b/gen/test/c/mx/mx_type_type.h @@ -0,0 +1,23 @@ +/* Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. */ + +#ifndef MX_TYPE_TYPE_H_INCLUDED +#define MX_TYPE_TYPE_H_INCLUDED + +#include + +typedef enum { + MX_TYPE_TYPE_SIMPLE = 0, + MX_TYPE_TYPE_EXTENDED = 1, + MX_TYPE_TYPE_TITLE = 2, + MX_TYPE_TYPE_RESOURCE = 3, + MX_TYPE_TYPE_LOCATOR = 4, + MX_TYPE_TYPE_ARC = 5 +} MxTypeType; + +bool mx_type_type_try_parse(const char *s, MxTypeType *out); +/* Lenient: unknown input falls back to the first variant. */ +MxTypeType mx_type_type_parse(const char *s); +/* Returns the wire literal (static storage; do not free). */ +const char *mx_type_type_to_string(MxTypeType v); + +#endif /* MX_TYPE_TYPE_H_INCLUDED */ diff --git a/gen/test/c/mx/mx_xml_space.c b/gen/test/c/mx/mx_xml_space.c new file mode 100644 index 000000000..adf9e11e6 --- /dev/null +++ b/gen/test/c/mx/mx_xml_space.c @@ -0,0 +1,35 @@ +/* Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. */ + +#include "mx_xml_space.h" + +#include + +static const char *const mx_xml_space_values[] = { + "default", + "preserve", +}; + +bool mx_xml_space_try_parse(const char *s, MxXMLSpace *out) { + if (!s) + s = ""; + for (size_t i = 0; i < sizeof(mx_xml_space_values) / sizeof(mx_xml_space_values[0]); i++) { + if (strcmp(s, mx_xml_space_values[i]) == 0) { + *out = (MxXMLSpace)i; + return true; + } + } + *out = (MxXMLSpace)0; + return false; +} + +MxXMLSpace mx_xml_space_parse(const char *s) { + MxXMLSpace v; + mx_xml_space_try_parse(s, &v); + return v; +} + +const char *mx_xml_space_to_string(MxXMLSpace v) { + if ((size_t)v >= sizeof(mx_xml_space_values) / sizeof(mx_xml_space_values[0])) + return mx_xml_space_values[0]; + return mx_xml_space_values[v]; +} diff --git a/gen/test/c/mx/mx_xml_space.h b/gen/test/c/mx/mx_xml_space.h new file mode 100644 index 000000000..8111b166c --- /dev/null +++ b/gen/test/c/mx/mx_xml_space.h @@ -0,0 +1,19 @@ +/* Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. */ + +#ifndef MX_XML_SPACE_H_INCLUDED +#define MX_XML_SPACE_H_INCLUDED + +#include + +typedef enum { + MX_XML_SPACE_DEFAULT = 0, + MX_XML_SPACE_PRESERVE = 1 +} MxXMLSpace; + +bool mx_xml_space_try_parse(const char *s, MxXMLSpace *out); +/* Lenient: unknown input falls back to the first variant. */ +MxXMLSpace mx_xml_space_parse(const char *s); +/* Returns the wire literal (static storage; do not free). */ +const char *mx_xml_space_to_string(MxXMLSpace v); + +#endif /* MX_XML_SPACE_H_INCLUDED */ diff --git a/gen/test/c/mx/sources.cmake b/gen/test/c/mx/sources.cmake index be1d3dc40..575949ca0 100644 --- a/gen/test/c/mx/sources.cmake +++ b/gen/test/c/mx/sources.cmake @@ -9,6 +9,7 @@ set(MX_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/mx_accord.c ${CMAKE_CURRENT_LIST_DIR}/mx_accordion_middle.c ${CMAKE_CURRENT_LIST_DIR}/mx_accordion_registration.c + ${CMAKE_CURRENT_LIST_DIR}/mx_actuate_type.c ${CMAKE_CURRENT_LIST_DIR}/mx_appearance.c ${CMAKE_CURRENT_LIST_DIR}/mx_arpeggiate.c ${CMAKE_CURRENT_LIST_DIR}/mx_arrow.c @@ -249,6 +250,7 @@ set(MX_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/mx_semitones.c ${CMAKE_CURRENT_LIST_DIR}/mx_show_frets.c ${CMAKE_CURRENT_LIST_DIR}/mx_show_tuplet.c + ${CMAKE_CURRENT_LIST_DIR}/mx_show_type.c ${CMAKE_CURRENT_LIST_DIR}/mx_slash.c ${CMAKE_CURRENT_LIST_DIR}/mx_slide.c ${CMAKE_CURRENT_LIST_DIR}/mx_slur.c @@ -322,6 +324,7 @@ set(MX_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/mx_tuplet_portion.c ${CMAKE_CURRENT_LIST_DIR}/mx_tuplet_type.c ${CMAKE_CURRENT_LIST_DIR}/mx_two_note_turn.c + ${CMAKE_CURRENT_LIST_DIR}/mx_type_type.c ${CMAKE_CURRENT_LIST_DIR}/mx_typed_text.c ${CMAKE_CURRENT_LIST_DIR}/mx_unpitched.c ${CMAKE_CURRENT_LIST_DIR}/mx_up_down.c @@ -336,6 +339,7 @@ set(MX_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/mx_winged.c ${CMAKE_CURRENT_LIST_DIR}/mx_wood.c ${CMAKE_CURRENT_LIST_DIR}/mx_work.c + ${CMAKE_CURRENT_LIST_DIR}/mx_xml_space.c ${CMAKE_CURRENT_LIST_DIR}/mx_yes_no.c ${CMAKE_CURRENT_LIST_DIR}/mx_yes_no_number.c ${CMAKE_CURRENT_LIST_DIR}/mx_yyyy_mm_dd.c diff --git a/gen/test/go/config.toml b/gen/test/go/config.toml index 4593202fd..c08858605 100644 --- a/gen/test/go/config.toml +++ b/gen/test/go/config.toml @@ -36,6 +36,7 @@ token = "string" nmtoken = "string" id = "string" idref = "string" +language = "string" date = "string" decimal = "float64" integer = "int" diff --git a/gen/test/go/mx/accidental_text.go b/gen/test/go/mx/accidental_text.go index 60260754e..e79f8a9d8 100644 --- a/gen/test/go/mx/accidental_text.go +++ b/gen/test/go/mx/accidental_text.go @@ -12,7 +12,7 @@ import ( type AccidentalText struct { SMUFL *SMUFLAccidentalGlyphName // attribute "smufl" XMLLang *string // attribute "xml:lang" - XMLSpace *string // attribute "xml:space" + XMLSpace *XMLSpace // attribute "xml:space" Justify *LeftCenterRight // attribute "justify" DefaultX *Tenths // attribute "default-x" DefaultY *Tenths // attribute "default-y" @@ -50,7 +50,7 @@ func parseAccidentalText(el *etree.Element) (*AccidentalText, error) { v := a.Value m.XMLLang = &v case "xml:space": - v := a.Value + v := ParseXMLSpace(a.Value) m.XMLSpace = &v case "justify": v := ParseLeftCenterRight(a.Value) @@ -133,7 +133,7 @@ func serializeAccidentalText(m *AccidentalText, parent *etree.Element, tag strin el.CreateAttr("xml:lang", (*m.XMLLang)) } if m.XMLSpace != nil { - el.CreateAttr("xml:space", (*m.XMLSpace)) + el.CreateAttr("xml:space", (*m.XMLSpace).String()) } if m.Justify != nil { el.CreateAttr("justify", (*m.Justify).String()) diff --git a/gen/test/go/mx/actuate_type.go b/gen/test/go/mx/actuate_type.go new file mode 100644 index 000000000..766a76fe7 --- /dev/null +++ b/gen/test/go/mx/actuate_type.go @@ -0,0 +1,49 @@ +// Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. + +package mx + +type ActuateType int + +const ( + ActuateTypeOnLoad ActuateType = iota + ActuateTypeOnRequest + ActuateTypeOther + ActuateTypeNone +) + +// actuateTypeValues lists the wire literals by variant ordinal. +var actuateTypeValues = []string{ + "onLoad", + "onRequest", + "other", + "none", +} + +var actuateTypeIndex = map[string]ActuateType{ + "onLoad": ActuateTypeOnLoad, + "onRequest": ActuateTypeOnRequest, + "other": ActuateTypeOther, + "none": ActuateTypeNone, +} + +// TryParseActuateType matches s against the wire literals. +func TryParseActuateType(s string) (ActuateType, bool) { + v, ok := actuateTypeIndex[s] + return v, ok +} + +// ParseActuateType is lenient: unknown input falls back to the first variant. +func ParseActuateType(s string) ActuateType { + if v, ok := actuateTypeIndex[s]; ok { + return v + } + return ActuateTypeOnLoad +} + +// String returns the wire literal. +func (v ActuateType) String() string { + if int(v) < 0 || int(v) >= len(actuateTypeValues) { + return actuateTypeValues[0] + } + return actuateTypeValues[v] +} diff --git a/gen/test/go/mx/formatted_text.go b/gen/test/go/mx/formatted_text.go index e4a8335bd..276b48a72 100644 --- a/gen/test/go/mx/formatted_text.go +++ b/gen/test/go/mx/formatted_text.go @@ -10,7 +10,7 @@ import ( // The formatted-text type represents a text element with text-formatting attributes. type FormattedText struct { XMLLang *string // attribute "xml:lang" - XMLSpace *string // attribute "xml:space" + XMLSpace *XMLSpace // attribute "xml:space" Justify *LeftCenterRight // attribute "justify" DefaultX *Tenths // attribute "default-x" DefaultY *Tenths // attribute "default-y" @@ -45,7 +45,7 @@ func parseFormattedText(el *etree.Element) (*FormattedText, error) { v := a.Value m.XMLLang = &v case "xml:space": - v := a.Value + v := ParseXMLSpace(a.Value) m.XMLSpace = &v case "justify": v := ParseLeftCenterRight(a.Value) @@ -125,7 +125,7 @@ func serializeFormattedText(m *FormattedText, parent *etree.Element, tag string) el.CreateAttr("xml:lang", (*m.XMLLang)) } if m.XMLSpace != nil { - el.CreateAttr("xml:space", (*m.XMLSpace)) + el.CreateAttr("xml:space", (*m.XMLSpace).String()) } if m.Justify != nil { el.CreateAttr("justify", (*m.Justify).String()) diff --git a/gen/test/go/mx/formatted_text_id.go b/gen/test/go/mx/formatted_text_id.go index 2040e0efb..30dc446f4 100644 --- a/gen/test/go/mx/formatted_text_id.go +++ b/gen/test/go/mx/formatted_text_id.go @@ -10,7 +10,7 @@ import ( // The formatted-text-id type represents a text element with text-formatting and id attributes. type FormattedTextID struct { XMLLang *string // attribute "xml:lang" - XMLSpace *string // attribute "xml:space" + XMLSpace *XMLSpace // attribute "xml:space" Justify *LeftCenterRight // attribute "justify" DefaultX *Tenths // attribute "default-x" DefaultY *Tenths // attribute "default-y" @@ -46,7 +46,7 @@ func parseFormattedTextID(el *etree.Element) (*FormattedTextID, error) { v := a.Value m.XMLLang = &v case "xml:space": - v := a.Value + v := ParseXMLSpace(a.Value) m.XMLSpace = &v case "justify": v := ParseLeftCenterRight(a.Value) @@ -129,7 +129,7 @@ func serializeFormattedTextID(m *FormattedTextID, parent *etree.Element, tag str el.CreateAttr("xml:lang", (*m.XMLLang)) } if m.XMLSpace != nil { - el.CreateAttr("xml:space", (*m.XMLSpace)) + el.CreateAttr("xml:space", (*m.XMLSpace).String()) } if m.Justify != nil { el.CreateAttr("justify", (*m.Justify).String()) diff --git a/gen/test/go/mx/link.go b/gen/test/go/mx/link.go index b063b324f..0b2da8100 100644 --- a/gen/test/go/mx/link.go +++ b/gen/test/go/mx/link.go @@ -11,19 +11,19 @@ import ( // with a MusicXML opus. If a relative link is used within a document that is part of a compressed // MusicXML file, the link is relative to the root folder of the zip file. type Link struct { - Name *string // attribute "name" - XlinkHref *string // attribute "xlink:href" - XlinkType *string // attribute "xlink:type" - XlinkRole *string // attribute "xlink:role" - XlinkTitle *string // attribute "xlink:title" - XlinkShow *string // attribute "xlink:show" - XlinkActuate *string // attribute "xlink:actuate" - Element *string // attribute "element" - Position *int // attribute "position" - DefaultX *Tenths // attribute "default-x" - DefaultY *Tenths // attribute "default-y" - RelativeX *Tenths // attribute "relative-x" - RelativeY *Tenths // attribute "relative-y" + Name *string // attribute "name" + XlinkHref *string // attribute "xlink:href" + XlinkType *TypeType // attribute "xlink:type" + XlinkRole *string // attribute "xlink:role" + XlinkTitle *string // attribute "xlink:title" + XlinkShow *ShowType // attribute "xlink:show" + XlinkActuate *ActuateType // attribute "xlink:actuate" + Element *string // attribute "element" + Position *int // attribute "position" + DefaultX *Tenths // attribute "default-x" + DefaultY *Tenths // attribute "default-y" + RelativeX *Tenths // attribute "relative-x" + RelativeY *Tenths // attribute "relative-y" } func parseLink(el *etree.Element) (*Link, error) { @@ -40,7 +40,7 @@ func parseLink(el *etree.Element) (*Link, error) { v := a.Value m.XlinkHref = &v case "xlink:type": - v := a.Value + v := ParseTypeType(a.Value) m.XlinkType = &v case "xlink:role": v := a.Value @@ -49,10 +49,10 @@ func parseLink(el *etree.Element) (*Link, error) { v := a.Value m.XlinkTitle = &v case "xlink:show": - v := a.Value + v := ParseShowType(a.Value) m.XlinkShow = &v case "xlink:actuate": - v := a.Value + v := ParseActuateType(a.Value) m.XlinkActuate = &v case "element": v := a.Value @@ -91,7 +91,7 @@ func serializeLink(m *Link, parent *etree.Element, tag string) { el.CreateAttr("xlink:href", (*m.XlinkHref)) } if m.XlinkType != nil { - el.CreateAttr("xlink:type", (*m.XlinkType)) + el.CreateAttr("xlink:type", (*m.XlinkType).String()) } if m.XlinkRole != nil { el.CreateAttr("xlink:role", (*m.XlinkRole)) @@ -100,10 +100,10 @@ func serializeLink(m *Link, parent *etree.Element, tag string) { el.CreateAttr("xlink:title", (*m.XlinkTitle)) } if m.XlinkShow != nil { - el.CreateAttr("xlink:show", (*m.XlinkShow)) + el.CreateAttr("xlink:show", (*m.XlinkShow).String()) } if m.XlinkActuate != nil { - el.CreateAttr("xlink:actuate", (*m.XlinkActuate)) + el.CreateAttr("xlink:actuate", (*m.XlinkActuate).String()) } if m.Element != nil { el.CreateAttr("element", (*m.Element)) diff --git a/gen/test/go/mx/opus.go b/gen/test/go/mx/opus.go index 323b5199d..3615922d0 100644 --- a/gen/test/go/mx/opus.go +++ b/gen/test/go/mx/opus.go @@ -10,12 +10,12 @@ import ( // The opus type represents a link to a MusicXML opus document that composes multiple MusicXML // scores into a collection. type Opus struct { - XlinkHref *string // attribute "xlink:href" - XlinkType *string // attribute "xlink:type" - XlinkRole *string // attribute "xlink:role" - XlinkTitle *string // attribute "xlink:title" - XlinkShow *string // attribute "xlink:show" - XlinkActuate *string // attribute "xlink:actuate" + XlinkHref *string // attribute "xlink:href" + XlinkType *TypeType // attribute "xlink:type" + XlinkRole *string // attribute "xlink:role" + XlinkTitle *string // attribute "xlink:title" + XlinkShow *ShowType // attribute "xlink:show" + XlinkActuate *ActuateType // attribute "xlink:actuate" } func parseOpus(el *etree.Element) (*Opus, error) { @@ -29,7 +29,7 @@ func parseOpus(el *etree.Element) (*Opus, error) { v := a.Value m.XlinkHref = &v case "xlink:type": - v := a.Value + v := ParseTypeType(a.Value) m.XlinkType = &v case "xlink:role": v := a.Value @@ -38,10 +38,10 @@ func parseOpus(el *etree.Element) (*Opus, error) { v := a.Value m.XlinkTitle = &v case "xlink:show": - v := a.Value + v := ParseShowType(a.Value) m.XlinkShow = &v case "xlink:actuate": - v := a.Value + v := ParseActuateType(a.Value) m.XlinkActuate = &v default: return nil, fmt.Errorf("unknown attribute %q on <%s>", a.FullKey(), el.Tag) @@ -59,7 +59,7 @@ func serializeOpus(m *Opus, parent *etree.Element, tag string) { el.CreateAttr("xlink:href", (*m.XlinkHref)) } if m.XlinkType != nil { - el.CreateAttr("xlink:type", (*m.XlinkType)) + el.CreateAttr("xlink:type", (*m.XlinkType).String()) } if m.XlinkRole != nil { el.CreateAttr("xlink:role", (*m.XlinkRole)) @@ -68,9 +68,9 @@ func serializeOpus(m *Opus, parent *etree.Element, tag string) { el.CreateAttr("xlink:title", (*m.XlinkTitle)) } if m.XlinkShow != nil { - el.CreateAttr("xlink:show", (*m.XlinkShow)) + el.CreateAttr("xlink:show", (*m.XlinkShow).String()) } if m.XlinkActuate != nil { - el.CreateAttr("xlink:actuate", (*m.XlinkActuate)) + el.CreateAttr("xlink:actuate", (*m.XlinkActuate).String()) } } diff --git a/gen/test/go/mx/show_type.go b/gen/test/go/mx/show_type.go new file mode 100644 index 000000000..94d8a7902 --- /dev/null +++ b/gen/test/go/mx/show_type.go @@ -0,0 +1,52 @@ +// Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. + +package mx + +type ShowType int + +const ( + ShowTypeNew ShowType = iota + ShowTypeReplace + ShowTypeEmbed + ShowTypeOther + ShowTypeNone +) + +// showTypeValues lists the wire literals by variant ordinal. +var showTypeValues = []string{ + "new", + "replace", + "embed", + "other", + "none", +} + +var showTypeIndex = map[string]ShowType{ + "new": ShowTypeNew, + "replace": ShowTypeReplace, + "embed": ShowTypeEmbed, + "other": ShowTypeOther, + "none": ShowTypeNone, +} + +// TryParseShowType matches s against the wire literals. +func TryParseShowType(s string) (ShowType, bool) { + v, ok := showTypeIndex[s] + return v, ok +} + +// ParseShowType is lenient: unknown input falls back to the first variant. +func ParseShowType(s string) ShowType { + if v, ok := showTypeIndex[s]; ok { + return v + } + return ShowTypeNew +} + +// String returns the wire literal. +func (v ShowType) String() string { + if int(v) < 0 || int(v) >= len(showTypeValues) { + return showTypeValues[0] + } + return showTypeValues[v] +} diff --git a/gen/test/go/mx/type_type.go b/gen/test/go/mx/type_type.go new file mode 100644 index 000000000..e4fa0a154 --- /dev/null +++ b/gen/test/go/mx/type_type.go @@ -0,0 +1,55 @@ +// Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. + +package mx + +type TypeType int + +const ( + TypeTypeSimple TypeType = iota + TypeTypeExtended + TypeTypeTitle + TypeTypeResource + TypeTypeLocator + TypeTypeArc +) + +// typeTypeValues lists the wire literals by variant ordinal. +var typeTypeValues = []string{ + "simple", + "extended", + "title", + "resource", + "locator", + "arc", +} + +var typeTypeIndex = map[string]TypeType{ + "simple": TypeTypeSimple, + "extended": TypeTypeExtended, + "title": TypeTypeTitle, + "resource": TypeTypeResource, + "locator": TypeTypeLocator, + "arc": TypeTypeArc, +} + +// TryParseTypeType matches s against the wire literals. +func TryParseTypeType(s string) (TypeType, bool) { + v, ok := typeTypeIndex[s] + return v, ok +} + +// ParseTypeType is lenient: unknown input falls back to the first variant. +func ParseTypeType(s string) TypeType { + if v, ok := typeTypeIndex[s]; ok { + return v + } + return TypeTypeSimple +} + +// String returns the wire literal. +func (v TypeType) String() string { + if int(v) < 0 || int(v) >= len(typeTypeValues) { + return typeTypeValues[0] + } + return typeTypeValues[v] +} diff --git a/gen/test/go/mx/xml_space.go b/gen/test/go/mx/xml_space.go new file mode 100644 index 000000000..9deb0c99a --- /dev/null +++ b/gen/test/go/mx/xml_space.go @@ -0,0 +1,43 @@ +// Code generated by gen from musicxml-3.1-8bbe8e5. DO NOT EDIT. + +package mx + +type XMLSpace int + +const ( + XMLSpaceDefault XMLSpace = iota + XMLSpacePreserve +) + +// xmlSpaceValues lists the wire literals by variant ordinal. +var xmlSpaceValues = []string{ + "default", + "preserve", +} + +var xmlSpaceIndex = map[string]XMLSpace{ + "default": XMLSpaceDefault, + "preserve": XMLSpacePreserve, +} + +// TryParseXMLSpace matches s against the wire literals. +func TryParseXMLSpace(s string) (XMLSpace, bool) { + v, ok := xmlSpaceIndex[s] + return v, ok +} + +// ParseXMLSpace is lenient: unknown input falls back to the first variant. +func ParseXMLSpace(s string) XMLSpace { + if v, ok := xmlSpaceIndex[s]; ok { + return v + } + return XMLSpaceDefault +} + +// String returns the wire literal. +func (v XMLSpace) String() string { + if int(v) < 0 || int(v) >= len(xmlSpaceValues) { + return xmlSpaceValues[0] + } + return xmlSpaceValues[v] +} diff --git a/gen/tests/test_ir.py b/gen/tests/test_ir.py index e816cd3d5..531935436 100644 --- a/gen/tests/test_ir.py +++ b/gen/tests/test_ir.py @@ -171,6 +171,62 @@ def test_dead_dropped_live_kept(self): self.assertIn("measure-text", names) +class ImportedVocabulary(unittest.TestCase): + """The imported xml/xlink schema declarations: the closed vocabularies + those schemas declare arrive as ordinary value types with the schema's own + members, the refs the IR pins to an XSD BUILTIN (xml:lang, the anyURI + links) stay primitives, and an imported type nothing references never + enters the IR.""" + + def test_enums_carry_the_schema_members(self): + for xsd in XSDS: + with self.subTest(xsd=xsd.name): + m = build_ir(parse(xsd), xsd.stem) + by_name = {v.name: v for v in m.value_types} + self.assertEqual( + by_name["typeType"].values, + ["simple", "extended", "title", "resource", "locator", "arc"], + ) + self.assertEqual( + by_name["showType"].values, ["new", "replace", "embed", "other", "none"] + ) + self.assertEqual( + by_name["actuateType"].values, ["onLoad", "onRequest", "other", "none"] + ) + self.assertEqual(by_name["xml-space"].values, ["default", "preserve"]) + + def test_attribute_refs_point_at_the_imported_types(self): + m = build_ir(parse(XSD_40), "musicxml-4.0") + # The refs are declared on the attribute groups the formatted-text + # family shares, so gather from both. + refs = {a.name: a.type for c in m.complex_types for a in c.attributes} + refs.update({a.name: a.type for ag in m.attribute_groups for a in ag.attributes}) + self.assertEqual(refs["xml:lang"], ir.Ref("language", "primitive")) + self.assertEqual(refs["xml:space"], ir.Ref("xml-space", "value")) + self.assertEqual(refs["xlink:type"], ir.Ref("typeType", "value")) + # xs:anyURI accepts nearly any string, so the link refs stay primitive. + self.assertEqual(refs["xlink:href"], ir.Ref("string", "primitive")) + self.assertEqual(refs["xlink:role"], ir.Ref("string", "primitive")) + self.assertEqual(refs["xlink:title"], ir.Ref("string", "primitive")) + + def test_pinned_refs_leave_the_builtin_table(self): + m = build_ir(parse(XSD_40), "musicxml-4.0") + for resolved in ("xml:space", "xlink:type", "xlink:show", "xlink:actuate"): + self.assertNotIn(resolved, m.builtins) + # The pinned refs name an XSD builtin rather than a declaration; the + # two builtins they reach carry the IR's lexical facts. + self.assertEqual(m.builtins["xml:lang"], "language") + self.assertEqual(m.builtins["xlink:href"], "string") + self.assertEqual(m.builtins["xs:language"], "language") + self.assertEqual(m.builtins["xs:NCName"], "nmtoken") + + def test_unreferenced_imported_types_do_not_enter_the_ir(self): + m = build_ir(parse(XSD_40), "musicxml-4.0") + names = {v.name for v in m.value_types} + for unused in ("hrefType", "roleType", "arcroleType", "labelType", "fromType", "toType"): + self.assertNotIn(unused, names) + + class ResolverIntegrity(unittest.TestCase): """The resolution layer must produce a self-contained, resolvable view for every complex type: groups fully spliced, attributes deduped and resolvable, diff --git a/gen/xsd/model.py b/gen/xsd/model.py index fff8bae0a..b86b8a371 100644 --- a/gen/xsd/model.py +++ b/gen/xsd/model.py @@ -225,5 +225,13 @@ class Schema: complex_types: dict[str, ComplexType] groups: dict[str, Group] attribute_groups: dict[str, AttributeGroup] + # The top-level xs:attribute declarations. The imported xml/xlink schemas + # declare their attributes this way (xml:lang, xlink:type, ...), so this + # is where an external attribute ref's type lives. + attributes: dict[str, Attribute] elements: list[TopLevelElement] imports: list[tuple[str, str]] # (namespace, schemaLocation) + # The imported schemas that sit beside this one, keyed by namespace. An + # import by URL (xlink.xsd -> the XML namespace) or a missing file is not + # resolved; a reference that needs one fails loud when it is lowered. + imported: dict[str, Schema] = field(default_factory=dict) diff --git a/gen/xsd/parser.py b/gen/xsd/parser.py index 30799f176..b883b05a7 100644 --- a/gen/xsd/parser.py +++ b/gen/xsd/parser.py @@ -2,8 +2,10 @@ Uses the stdlib ElementTree. The parser is intentionally strict about the MusicXML subset: it understands the element kinds the schema actually uses and -ignores annotations and XML comments. It does not resolve imported schemas -(xml.xsd, xlink.xsd); attribute refs into those namespaces are kept verbatim. +ignores annotations and XML comments. An imported schema that sits beside the +main one (xml.xsd, xlink.xsd) is parsed too, so attribute refs into those +namespaces can be resolved by the IR; an import that names no local file (a +URL, as xlink.xsd's own xml.xsd import does) is left unresolved. """ from __future__ import annotations @@ -39,10 +41,39 @@ def parse(path: str | Path) -> Schema: + schema = _parse_file(path) + _resolve_imports(schema, Path(path)) + return schema + + +def _parse_file(path: str | Path) -> Schema: + """Parse one schema document, without resolving its imports.""" root = ET.parse(path).getroot() return _Parser().schema(root) +def _parse_imported_file(path: str | Path) -> Schema: + """Parse one imported schema document, reading only its declarations.""" + root = ET.parse(path).getroot() + return _ImportedParser().schema(root) + + +def _resolve_imports(schema: Schema, path: Path) -> None: + """Parse the imported schemas that sit beside `path`. + + One level only: the declarations the IR lowers (the xml/xlink attribute + declarations) name types from their own file, so following an import's + own imports buys nothing. A schemaLocation that is a URL, or a path with + no file behind it, stays unresolved and surfaces if a reference needs it. + """ + for namespace, location in schema.imports: + if not location or "://" in location: + continue + candidate = path.parent / location + if candidate.is_file(): + schema.imported[namespace] = _parse_imported_file(candidate) + + def _local(tag: object) -> str: """Local name of an element tag; '' for comments/PIs (non-str tags).""" return tag.rsplit("}", 1)[-1] if isinstance(tag, str) else "" @@ -74,14 +105,7 @@ def _max_occurs(elem: ET.Element) -> int: class _Parser: def schema(self, root: ET.Element) -> Schema: - schema = Schema( - simple_types={}, - complex_types={}, - groups={}, - attribute_groups={}, - elements=[], - imports=[], - ) + schema = _empty_schema() for child in _kids(root): kind = _local(child.tag) if kind == "import": @@ -100,6 +124,9 @@ def schema(self, root: ET.Element) -> Schema: elif kind == "attributeGroup": ag = self.attribute_group(child) schema.attribute_groups[ag.name] = ag + elif kind == "attribute": + a = self.attribute(child) + schema.attributes[a.name] = a elif kind == "element": schema.elements.append(self.top_level_element(child)) return schema @@ -256,3 +283,41 @@ def _find_particle(self, container: ET.Element): def top_level_element(self, elem: ET.Element) -> TopLevelElement: inline = next((self.complex_type(c) for c in _kids(elem) if _local(c.tag) == "complexType"), None) return TopLevelElement(elem.get("name", ""), elem.get("type"), inline, _doc(elem)) + + +class _ImportedParser(_Parser): + """Read an imported schema's attribute declarations and the simple types + they name -- nothing else. + + The imported xml/xlink schemas declare far more than their attributes + (xlink.xsd's groups use xs:any, which the MusicXML subset has no model + for). The IR resolves external attribute refs to those declarations, so + the narrower read keeps the imported documents inside the same subset the + main schema stays in. + """ + + def schema(self, root: ET.Element) -> Schema: + """The imported schema's attribute and simple-type declarations.""" + schema = _empty_schema() + for child in _kids(root): + kind = _local(child.tag) + if kind == "simpleType": + st = self.simple_type(child) + schema.simple_types[st.name] = st + elif kind == "attribute": + a = self.attribute(child) + schema.attributes[a.name] = a + return schema + + +def _empty_schema() -> Schema: + """A schema with no declarations, ready to be filled in.""" + return Schema( + simple_types={}, + complex_types={}, + groups={}, + attribute_groups={}, + attributes={}, + elements=[], + imports=[], + ) diff --git a/src/private/mx/core/Language.cpp b/src/private/mx/core/Language.cpp new file mode 100644 index 000000000..4802a3d43 --- /dev/null +++ b/src/private/mx/core/Language.cpp @@ -0,0 +1,111 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +#include "mx/core/Language.h" + +#include +#include + +namespace mx::core +{ + +// The pattern is ASCII (the builtin xs:language datatype); a non-ASCII letter +// is a grammar violation, not something to widen the check for. +bool languageIsLetter(char c) noexcept +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +bool languageIsSubtagChar(char c) noexcept +{ + return languageIsLetter(c) || (c >= '0' && c <= '9'); +} + +// The xs:language pattern: 1-8 letters, then zero or more '-' followed by 1-8 +// letters or digits. +bool languageMatchesPattern(std::string_view text) noexcept +{ + if (text.empty()) + { + return false; + } + std::size_t i = 0; + std::size_t run = 0; + while (i < text.size() && languageIsLetter(text[i])) + { + ++i; + ++run; + } + if (run == 0 || run > 8) + { + return false; + } + while (i < text.size()) + { + if (text[i] != '-') + { + return false; + } + ++i; + run = 0; + while (i < text.size() && languageIsSubtagChar(text[i])) + { + ++i; + ++run; + } + if (run == 0 || run > 8) + { + return false; + } + } + return true; +} + +Language::Language() = default; + +Language::Language(std::string value) : m_value{std::move(value)} +{ + repair(); +} + +void Language::setValue(std::string value) +{ + m_value = std::move(value); + repair(); +} + +void Language::repair() +{ + // Surrounding XML whitespace is not a repair (the contract the identity + // wrappers share), so normalize it away before judging the tag. + m_value = std::string{trimWhitespace(m_value)}; + if (!m_value.empty() && !languageMatchesPattern(m_value)) + { + m_value.clear(); + } +} + +bool Language::tryParse(std::string_view text, Language &out) +{ + if (!text.empty() && !languageMatchesPattern(text)) + { + return false; + } + out = Language{std::string{text}}; + return true; +} + +Language Language::parse(std::string_view text) +{ + return Language{std::string{text}}; +} + +Language Language::parse(std::string_view text, ValueParseOutcome &outcome) +{ + Language strict; + outcome = tryParse(trimWhitespace(text), strict) ? ValueParseOutcome::valid : ValueParseOutcome::invalid; + return parse(text); +} + +} // namespace mx::core \ No newline at end of file diff --git a/src/private/mx/core/Language.h b/src/private/mx/core/Language.h new file mode 100644 index 000000000..abb2e3d02 --- /dev/null +++ b/src/private/mx/core/Language.h @@ -0,0 +1,62 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +// Hand-written runtime for the generated mx::core model; never regenerated. + +#pragma once + +#include "mx/core/Lexical.h" + +#include +#include + +namespace mx::core +{ + +/// The XML namespace's language tag (`xml:lang`): the builtin xs:language +/// pattern -- a letter-led run of 1-8 ASCII letters followed by +/// hyphen-separated subtags of 1-8 ASCII letters or digits -- or the empty +/// string, the "un-declaration" the attribute's schema unions in. The empty +/// tag is also the natural zero and the repair: a tag the grammar forbids +/// reads as "no language stated" rather than as a plausible but wrong tag +/// (surrounding XML whitespace is normalized away first, not a repair). So a +/// lexically invalid tag cannot exist through the public API. +class Language final +{ + public: + Language(); + + explicit Language(std::string value); + + const std::string &value() const noexcept + { + return m_value; + } + + void setValue(std::string value); + + std::string toString() const + { + return m_value; + } + + /// Strict: the language pattern, or empty. + static bool tryParse(std::string_view text, Language &out); + + /// Lenient: an out-of-grammar tag becomes the empty tag. + static Language parse(std::string_view text); + + /// Lenient, and says whether the text had to be repaired. Surrounding + /// XML whitespace is not a repair. + static Language parse(std::string_view text, ValueParseOutcome &outcome); + + bool operator==(const Language &other) const noexcept = default; + + private: + void repair(); + + std::string m_value; +}; + +} // namespace mx::core \ No newline at end of file diff --git a/src/private/mx/core/generated/AccidentalText.cpp b/src/private/mx/core/generated/AccidentalText.cpp index 3cd3ef165..426fd1050 100644 --- a/src/private/mx/core/generated/AccidentalText.cpp +++ b/src/private/mx/core/generated/AccidentalText.cpp @@ -21,22 +21,22 @@ void AccidentalText::setSmufl(std::optional value) m_smufl = std::move(value); } -const std::optional &AccidentalText::xmlLang() const noexcept +const std::optional &AccidentalText::xmlLang() const noexcept { return m_xmlLang; } -void AccidentalText::setXMLLang(std::optional value) +void AccidentalText::setXMLLang(std::optional value) { m_xmlLang = std::move(value); } -const std::optional &AccidentalText::xmlSpace() const noexcept +const std::optional &AccidentalText::xmlSpace() const noexcept { return m_xmlSpace; } -void AccidentalText::setXMLSpace(std::optional value) +void AccidentalText::setXMLSpace(std::optional value) { m_xmlSpace = std::move(value); } @@ -267,11 +267,11 @@ AccidentalText parseAccidentalText(pugi::xml_node el, const ParseContext &contex } else if (aname == "xml:lang") { - out.setXMLLang(std::string{a.value()}); + out.setXMLLang(parseValue(a.value(), context, el, "xml:lang")); } else if (aname == "xml:space") { - out.setXMLSpace(std::string{a.value()}); + out.setXMLSpace(parseValue(a.value(), context, el, "xml:space")); } else if (aname == "justify") { @@ -380,11 +380,11 @@ void serializeAccidentalText(const AccidentalText &v, pugi::xml_node parent, con } if (v.xmlLang()) { - setAttribute(el, "xml:lang", *v.xmlLang()); + setAttribute(el, "xml:lang", v.xmlLang()->toString()); } if (v.xmlSpace()) { - setAttribute(el, "xml:space", *v.xmlSpace()); + setAttribute(el, "xml:space", v.xmlSpace()->toString()); } if (v.justify()) { diff --git a/src/private/mx/core/generated/AccidentalText.h b/src/private/mx/core/generated/AccidentalText.h index 041e1301d..af1c83e4a 100644 --- a/src/private/mx/core/generated/AccidentalText.h +++ b/src/private/mx/core/generated/AccidentalText.h @@ -2,6 +2,7 @@ #pragma once +#include "mx/core/Language.h" #include "mx/core/generated/AccidentalValue.h" #include "mx/core/generated/Color.h" #include "mx/core/generated/EnclosureShape.h" @@ -17,6 +18,7 @@ #include "mx/core/generated/Tenths.h" #include "mx/core/generated/TextDirection.h" #include "mx/core/generated/Valign.h" +#include "mx/core/generated/XMLSpace.h" #include #include @@ -38,10 +40,10 @@ class AccidentalText final public: const std::optional &smufl() const noexcept; void setSmufl(std::optional value); - const std::optional &xmlLang() const noexcept; - void setXMLLang(std::optional value); - const std::optional &xmlSpace() const noexcept; - void setXMLSpace(std::optional value); + const std::optional &xmlLang() const noexcept; + void setXMLLang(std::optional value); + const std::optional &xmlSpace() const noexcept; + void setXMLSpace(std::optional value); const std::optional &justify() const noexcept; void setJustify(std::optional value); const std::optional &defaultX() const noexcept; @@ -88,8 +90,8 @@ class AccidentalText final private: std::optional m_smufl; - std::optional m_xmlLang; - std::optional m_xmlSpace; + std::optional m_xmlLang; + std::optional m_xmlSpace; std::optional m_justify; std::optional m_defaultX; std::optional m_defaultY; diff --git a/src/private/mx/core/generated/ActuateType.cpp b/src/private/mx/core/generated/ActuateType.cpp new file mode 100644 index 000000000..571a0f349 --- /dev/null +++ b/src/private/mx/core/generated/ActuateType.cpp @@ -0,0 +1,69 @@ +// Code generated by gen from musicxml-4.0-ed15c23. DO NOT EDIT. + +#include "mx/core/generated/ActuateType.h" + +#include +#include + +namespace mx::core +{ + +constexpr std::string_view kActuateTypeWire[] = { + "onLoad", + "onRequest", + "other", + "none", +}; + +ActuateType ActuateType::onLoad() noexcept +{ + return ActuateType{Tag::onLoad}; +} + +ActuateType ActuateType::onRequest() noexcept +{ + return ActuateType{Tag::onRequest}; +} + +ActuateType ActuateType::other() noexcept +{ + return ActuateType{Tag::other}; +} + +ActuateType ActuateType::none() noexcept +{ + return ActuateType{Tag::none}; +} + +std::string_view ActuateType::toString() const noexcept +{ + return kActuateTypeWire[static_cast(m_tag)]; +} + +bool ActuateType::tryParse(std::string_view text, ActuateType &out) noexcept +{ + for (std::size_t i = 0; i < std::size(kActuateTypeWire); ++i) + { + if (kActuateTypeWire[i] == text) + { + out = ActuateType{static_cast(i)}; + return true; + } + } + return false; +} + +ActuateType ActuateType::parse(std::string_view text) noexcept +{ + ValueParseOutcome outcome = ValueParseOutcome::valid; + return parse(text, outcome); +} + +ActuateType ActuateType::parse(std::string_view text, ValueParseOutcome &outcome) noexcept +{ + ActuateType v; + outcome = tryParse(text, v) ? ValueParseOutcome::valid : ValueParseOutcome::invalid; + return v; +} + +} // namespace mx::core diff --git a/src/private/mx/core/generated/ActuateType.h b/src/private/mx/core/generated/ActuateType.h new file mode 100644 index 000000000..f4ef4b119 --- /dev/null +++ b/src/private/mx/core/generated/ActuateType.h @@ -0,0 +1,61 @@ +// Code generated by gen from musicxml-4.0-ed15c23. DO NOT EDIT. + +#pragma once + +#include "mx/core/Lexical.h" + +#include + +namespace mx::core +{ + +/// Closed vocabulary: the named factories are the only constructors, so an +/// out-of-vocabulary value is unrepresentable through the public API. +class ActuateType final +{ + public: + enum class Tag + { + onLoad, + onRequest, + other, + none, + }; + + /// The natural zero: the first variant. + ActuateType() noexcept = default; + + static ActuateType onLoad() noexcept; + static ActuateType onRequest() noexcept; + static ActuateType other() noexcept; + static ActuateType none() noexcept; + + Tag tag() const noexcept + { + return m_tag; + } + + /// The wire literal (static storage). + std::string_view toString() const noexcept; + + /// Strict: matches a wire literal exactly. + static bool tryParse(std::string_view text, ActuateType &out) noexcept; + + /// Lenient: an unrecognized literal falls back to the first variant + /// (the import leniency policy; never produces an invalid value). + static ActuateType parse(std::string_view text) noexcept; + + /// Lenient, and says whether the literal was recognized. + static ActuateType parse(std::string_view text, ValueParseOutcome &outcome) noexcept; + + bool operator==(const ActuateType &other) const noexcept = default; + + private: + explicit ActuateType(Tag tag) noexcept : m_tag{tag} + { + } + + Tag m_tag{Tag::onLoad}; +}; + +} // namespace mx::core diff --git a/src/private/mx/core/generated/Directive.cpp b/src/private/mx/core/generated/Directive.cpp index 0f2da367d..5c8bf22fa 100644 --- a/src/private/mx/core/generated/Directive.cpp +++ b/src/private/mx/core/generated/Directive.cpp @@ -11,12 +11,12 @@ namespace mx::core { -const std::optional &Directive::xmlLang() const noexcept +const std::optional &Directive::xmlLang() const noexcept { return m_xmlLang; } -void Directive::setXMLLang(std::optional value) +void Directive::setXMLLang(std::optional value) { m_xmlLang = std::move(value); } @@ -133,7 +133,7 @@ Directive parseDirective(pugi::xml_node el, const ParseContext &context) } if (aname == "xml:lang") { - out.setXMLLang(std::string{a.value()}); + out.setXMLLang(parseValue(a.value(), context, el, "xml:lang")); } else if (aname == "default-x") { @@ -194,7 +194,7 @@ void serializeDirective(const Directive &v, pugi::xml_node parent, const char *t pugi::xml_node el = parent.append_child(tag); if (v.xmlLang()) { - setAttribute(el, "xml:lang", *v.xmlLang()); + setAttribute(el, "xml:lang", v.xmlLang()->toString()); } if (v.defaultX()) { diff --git a/src/private/mx/core/generated/Directive.h b/src/private/mx/core/generated/Directive.h index ad1da1ef7..198e3ec77 100644 --- a/src/private/mx/core/generated/Directive.h +++ b/src/private/mx/core/generated/Directive.h @@ -2,6 +2,7 @@ #pragma once +#include "mx/core/Language.h" #include "mx/core/generated/Color.h" #include "mx/core/generated/FontFamily.h" #include "mx/core/generated/FontSize.h" @@ -25,8 +26,8 @@ class ParseContext; class Directive final { public: - const std::optional &xmlLang() const noexcept; - void setXMLLang(std::optional value); + const std::optional &xmlLang() const noexcept; + void setXMLLang(std::optional value); const std::optional &defaultX() const noexcept; void setDefaultX(std::optional value); const std::optional &defaultY() const noexcept; @@ -50,7 +51,7 @@ class Directive final bool operator==(const Directive &other) const = default; private: - std::optional m_xmlLang; + std::optional m_xmlLang; std::optional m_defaultX; std::optional m_defaultY; std::optional m_relativeX; diff --git a/src/private/mx/core/generated/FormattedText.cpp b/src/private/mx/core/generated/FormattedText.cpp index 68898015c..c449b73a4 100644 --- a/src/private/mx/core/generated/FormattedText.cpp +++ b/src/private/mx/core/generated/FormattedText.cpp @@ -11,22 +11,22 @@ namespace mx::core { -const std::optional &FormattedText::xmlLang() const noexcept +const std::optional &FormattedText::xmlLang() const noexcept { return m_xmlLang; } -void FormattedText::setXMLLang(std::optional value) +void FormattedText::setXMLLang(std::optional value) { m_xmlLang = std::move(value); } -const std::optional &FormattedText::xmlSpace() const noexcept +const std::optional &FormattedText::xmlSpace() const noexcept { return m_xmlSpace; } -void FormattedText::setXMLSpace(std::optional value) +void FormattedText::setXMLSpace(std::optional value) { m_xmlSpace = std::move(value); } @@ -253,11 +253,11 @@ FormattedText parseFormattedText(pugi::xml_node el, const ParseContext &context) } if (aname == "xml:lang") { - out.setXMLLang(std::string{a.value()}); + out.setXMLLang(parseValue(a.value(), context, el, "xml:lang")); } else if (aname == "xml:space") { - out.setXMLSpace(std::string{a.value()}); + out.setXMLSpace(parseValue(a.value(), context, el, "xml:space")); } else if (aname == "justify") { @@ -362,11 +362,11 @@ void serializeFormattedText(const FormattedText &v, pugi::xml_node parent, const pugi::xml_node el = parent.append_child(tag); if (v.xmlLang()) { - setAttribute(el, "xml:lang", *v.xmlLang()); + setAttribute(el, "xml:lang", v.xmlLang()->toString()); } if (v.xmlSpace()) { - setAttribute(el, "xml:space", *v.xmlSpace()); + setAttribute(el, "xml:space", v.xmlSpace()->toString()); } if (v.justify()) { diff --git a/src/private/mx/core/generated/FormattedText.h b/src/private/mx/core/generated/FormattedText.h index 576513849..bd49a7362 100644 --- a/src/private/mx/core/generated/FormattedText.h +++ b/src/private/mx/core/generated/FormattedText.h @@ -2,6 +2,7 @@ #pragma once +#include "mx/core/Language.h" #include "mx/core/generated/Color.h" #include "mx/core/generated/EnclosureShape.h" #include "mx/core/generated/FontFamily.h" @@ -15,6 +16,7 @@ #include "mx/core/generated/Tenths.h" #include "mx/core/generated/TextDirection.h" #include "mx/core/generated/Valign.h" +#include "mx/core/generated/XMLSpace.h" #include #include @@ -33,10 +35,10 @@ class ParseContext; class FormattedText final { public: - const std::optional &xmlLang() const noexcept; - void setXMLLang(std::optional value); - const std::optional &xmlSpace() const noexcept; - void setXMLSpace(std::optional value); + const std::optional &xmlLang() const noexcept; + void setXMLLang(std::optional value); + const std::optional &xmlSpace() const noexcept; + void setXMLSpace(std::optional value); const std::optional &justify() const noexcept; void setJustify(std::optional value); const std::optional &defaultX() const noexcept; @@ -82,8 +84,8 @@ class FormattedText final bool operator==(const FormattedText &other) const = default; private: - std::optional m_xmlLang; - std::optional m_xmlSpace; + std::optional m_xmlLang; + std::optional m_xmlSpace; std::optional m_justify; std::optional m_defaultX; std::optional m_defaultY; diff --git a/src/private/mx/core/generated/FormattedTextID.cpp b/src/private/mx/core/generated/FormattedTextID.cpp index e9427bc93..22948f467 100644 --- a/src/private/mx/core/generated/FormattedTextID.cpp +++ b/src/private/mx/core/generated/FormattedTextID.cpp @@ -11,22 +11,22 @@ namespace mx::core { -const std::optional &FormattedTextID::xmlLang() const noexcept +const std::optional &FormattedTextID::xmlLang() const noexcept { return m_xmlLang; } -void FormattedTextID::setXMLLang(std::optional value) +void FormattedTextID::setXMLLang(std::optional value) { m_xmlLang = std::move(value); } -const std::optional &FormattedTextID::xmlSpace() const noexcept +const std::optional &FormattedTextID::xmlSpace() const noexcept { return m_xmlSpace; } -void FormattedTextID::setXMLSpace(std::optional value) +void FormattedTextID::setXMLSpace(std::optional value) { m_xmlSpace = std::move(value); } @@ -263,11 +263,11 @@ FormattedTextID parseFormattedTextID(pugi::xml_node el, const ParseContext &cont } if (aname == "xml:lang") { - out.setXMLLang(std::string{a.value()}); + out.setXMLLang(parseValue(a.value(), context, el, "xml:lang")); } else if (aname == "xml:space") { - out.setXMLSpace(std::string{a.value()}); + out.setXMLSpace(parseValue(a.value(), context, el, "xml:space")); } else if (aname == "justify") { @@ -376,11 +376,11 @@ void serializeFormattedTextID(const FormattedTextID &v, pugi::xml_node parent, c pugi::xml_node el = parent.append_child(tag); if (v.xmlLang()) { - setAttribute(el, "xml:lang", *v.xmlLang()); + setAttribute(el, "xml:lang", v.xmlLang()->toString()); } if (v.xmlSpace()) { - setAttribute(el, "xml:space", *v.xmlSpace()); + setAttribute(el, "xml:space", v.xmlSpace()->toString()); } if (v.justify()) { diff --git a/src/private/mx/core/generated/FormattedTextID.h b/src/private/mx/core/generated/FormattedTextID.h index 4cb107b6c..adf22a9d2 100644 --- a/src/private/mx/core/generated/FormattedTextID.h +++ b/src/private/mx/core/generated/FormattedTextID.h @@ -2,6 +2,7 @@ #pragma once +#include "mx/core/Language.h" #include "mx/core/NameToken.h" #include "mx/core/Token.h" #include "mx/core/generated/Color.h" @@ -17,6 +18,7 @@ #include "mx/core/generated/Tenths.h" #include "mx/core/generated/TextDirection.h" #include "mx/core/generated/Valign.h" +#include "mx/core/generated/XMLSpace.h" #include #include @@ -35,10 +37,10 @@ class ParseContext; class FormattedTextID final { public: - const std::optional &xmlLang() const noexcept; - void setXMLLang(std::optional value); - const std::optional &xmlSpace() const noexcept; - void setXMLSpace(std::optional value); + const std::optional &xmlLang() const noexcept; + void setXMLLang(std::optional value); + const std::optional &xmlSpace() const noexcept; + void setXMLSpace(std::optional value); const std::optional &justify() const noexcept; void setJustify(std::optional value); const std::optional &defaultX() const noexcept; @@ -86,8 +88,8 @@ class FormattedTextID final bool operator==(const FormattedTextID &other) const = default; private: - std::optional m_xmlLang; - std::optional m_xmlSpace; + std::optional m_xmlLang; + std::optional m_xmlSpace; std::optional m_justify; std::optional m_defaultX; std::optional m_defaultY; diff --git a/src/private/mx/core/generated/Link.cpp b/src/private/mx/core/generated/Link.cpp index f3788a04c..01432ea32 100644 --- a/src/private/mx/core/generated/Link.cpp +++ b/src/private/mx/core/generated/Link.cpp @@ -31,12 +31,12 @@ void Link::setXlinkHref(std::string value) m_xlinkHref = std::move(value); } -const std::optional &Link::xlinkType() const noexcept +const std::optional &Link::xlinkType() const noexcept { return m_xlinkType; } -void Link::setXlinkType(std::optional value) +void Link::setXlinkType(std::optional value) { m_xlinkType = std::move(value); } @@ -61,22 +61,22 @@ void Link::setXlinkTitle(std::optional value) m_xlinkTitle = std::move(value); } -const std::optional &Link::xlinkShow() const noexcept +const std::optional &Link::xlinkShow() const noexcept { return m_xlinkShow; } -void Link::setXlinkShow(std::optional value) +void Link::setXlinkShow(std::optional value) { m_xlinkShow = std::move(value); } -const std::optional &Link::xlinkActuate() const noexcept +const std::optional &Link::xlinkActuate() const noexcept { return m_xlinkActuate; } -void Link::setXlinkActuate(std::optional value) +void Link::setXlinkActuate(std::optional value) { m_xlinkActuate = std::move(value); } @@ -163,7 +163,7 @@ Link parseLink(pugi::xml_node el, const ParseContext &context) } else if (aname == "xlink:type") { - out.setXlinkType(std::string{a.value()}); + out.setXlinkType(parseValue(a.value(), context, el, "xlink:type")); } else if (aname == "xlink:role") { @@ -175,11 +175,11 @@ Link parseLink(pugi::xml_node el, const ParseContext &context) } else if (aname == "xlink:show") { - out.setXlinkShow(std::string{a.value()}); + out.setXlinkShow(parseValue(a.value(), context, el, "xlink:show")); } else if (aname == "xlink:actuate") { - out.setXlinkActuate(std::string{a.value()}); + out.setXlinkActuate(parseValue(a.value(), context, el, "xlink:actuate")); } else if (aname == "element") { @@ -238,7 +238,7 @@ void serializeLink(const Link &v, pugi::xml_node parent, const char *tag) setAttribute(el, "xlink:href", v.xlinkHref()); if (v.xlinkType()) { - setAttribute(el, "xlink:type", *v.xlinkType()); + setAttribute(el, "xlink:type", v.xlinkType()->toString()); } if (v.xlinkRole()) { @@ -250,11 +250,11 @@ void serializeLink(const Link &v, pugi::xml_node parent, const char *tag) } if (v.xlinkShow()) { - setAttribute(el, "xlink:show", *v.xlinkShow()); + setAttribute(el, "xlink:show", v.xlinkShow()->toString()); } if (v.xlinkActuate()) { - setAttribute(el, "xlink:actuate", *v.xlinkActuate()); + setAttribute(el, "xlink:actuate", v.xlinkActuate()->toString()); } if (v.element()) { diff --git a/src/private/mx/core/generated/Link.h b/src/private/mx/core/generated/Link.h index 94db05a76..56db0a323 100644 --- a/src/private/mx/core/generated/Link.h +++ b/src/private/mx/core/generated/Link.h @@ -4,7 +4,10 @@ #include "mx/core/NameToken.h" #include "mx/core/Token.h" +#include "mx/core/generated/ActuateType.h" +#include "mx/core/generated/ShowType.h" #include "mx/core/generated/Tenths.h" +#include "mx/core/generated/TypeType.h" #include #include @@ -29,16 +32,16 @@ class Link final void setName(std::optional value); const std::string &xlinkHref() const noexcept; void setXlinkHref(std::string value); - const std::optional &xlinkType() const noexcept; - void setXlinkType(std::optional value); + const std::optional &xlinkType() const noexcept; + void setXlinkType(std::optional value); const std::optional &xlinkRole() const noexcept; void setXlinkRole(std::optional value); const std::optional &xlinkTitle() const noexcept; void setXlinkTitle(std::optional value); - const std::optional &xlinkShow() const noexcept; - void setXlinkShow(std::optional value); - const std::optional &xlinkActuate() const noexcept; - void setXlinkActuate(std::optional value); + const std::optional &xlinkShow() const noexcept; + void setXlinkShow(std::optional value); + const std::optional &xlinkActuate() const noexcept; + void setXlinkActuate(std::optional value); const std::optional &element() const noexcept; void setElement(std::optional value); const std::optional &position() const noexcept; @@ -56,11 +59,11 @@ class Link final private: std::optional m_name; std::string m_xlinkHref{}; - std::optional m_xlinkType; + std::optional m_xlinkType; std::optional m_xlinkRole; std::optional m_xlinkTitle; - std::optional m_xlinkShow; - std::optional m_xlinkActuate; + std::optional m_xlinkShow; + std::optional m_xlinkActuate; std::optional m_element; std::optional m_position; std::optional m_defaultX; diff --git a/src/private/mx/core/generated/LyricLanguage.cpp b/src/private/mx/core/generated/LyricLanguage.cpp index 1707a5131..7eaf4e86f 100644 --- a/src/private/mx/core/generated/LyricLanguage.cpp +++ b/src/private/mx/core/generated/LyricLanguage.cpp @@ -31,12 +31,12 @@ void LyricLanguage::setName(std::optional value) m_name = std::move(value); } -const std::string &LyricLanguage::xmlLang() const noexcept +const Language &LyricLanguage::xmlLang() const noexcept { return m_xmlLang; } -void LyricLanguage::setXMLLang(std::string value) +void LyricLanguage::setXMLLang(Language value) { m_xmlLang = std::move(value); } @@ -63,7 +63,7 @@ LyricLanguage parseLyricLanguage(pugi::xml_node el, const ParseContext &context) else if (aname == "xml:lang") { seen_xmlLang = true; - out.setXMLLang(std::string{a.value()}); + out.setXMLLang(parseValue(a.value(), context, el, "xml:lang")); } else { @@ -73,7 +73,7 @@ LyricLanguage parseLyricLanguage(pugi::xml_node el, const ParseContext &context) if (!seen_xmlLang) { // Configured import repair (plan ยง2.4): inject the default. - out.setXMLLang(std::string("und")); + out.setXMLLang(Language::parse("und")); reportAttributeDefaulted(context, el, "xml:lang", "und"); } parseLyricLanguageContent(out, el, context); @@ -101,7 +101,7 @@ void serializeLyricLanguage(const LyricLanguage &v, pugi::xml_node parent, const { setAttribute(el, "name", *v.name()); } - setAttribute(el, "xml:lang", v.xmlLang()); + setAttribute(el, "xml:lang", v.xmlLang().toString()); serializeLyricLanguageContent(v, el); } diff --git a/src/private/mx/core/generated/LyricLanguage.h b/src/private/mx/core/generated/LyricLanguage.h index 1fd1f968d..2ea278eef 100644 --- a/src/private/mx/core/generated/LyricLanguage.h +++ b/src/private/mx/core/generated/LyricLanguage.h @@ -2,6 +2,7 @@ #pragma once +#include "mx/core/Language.h" #include "mx/core/NameToken.h" #include "mx/core/Token.h" @@ -26,14 +27,14 @@ class LyricLanguage final void setNumber(std::optional value); const std::optional &name() const noexcept; void setName(std::optional value); - const std::string &xmlLang() const noexcept; - void setXMLLang(std::string value); + const Language &xmlLang() const noexcept; + void setXMLLang(Language value); bool operator==(const LyricLanguage &other) const = default; private: std::optional m_number; std::optional m_name; - std::string m_xmlLang{}; + Language m_xmlLang{}; }; LyricLanguage parseLyricLanguage(pugi::xml_node el, const ParseContext &context); diff --git a/src/private/mx/core/generated/Opus.cpp b/src/private/mx/core/generated/Opus.cpp index 2f331312e..0c2527ef7 100644 --- a/src/private/mx/core/generated/Opus.cpp +++ b/src/private/mx/core/generated/Opus.cpp @@ -21,12 +21,12 @@ void Opus::setXlinkHref(std::string value) m_xlinkHref = std::move(value); } -const std::optional &Opus::xlinkType() const noexcept +const std::optional &Opus::xlinkType() const noexcept { return m_xlinkType; } -void Opus::setXlinkType(std::optional value) +void Opus::setXlinkType(std::optional value) { m_xlinkType = std::move(value); } @@ -51,22 +51,22 @@ void Opus::setXlinkTitle(std::optional value) m_xlinkTitle = std::move(value); } -const std::optional &Opus::xlinkShow() const noexcept +const std::optional &Opus::xlinkShow() const noexcept { return m_xlinkShow; } -void Opus::setXlinkShow(std::optional value) +void Opus::setXlinkShow(std::optional value) { m_xlinkShow = std::move(value); } -const std::optional &Opus::xlinkActuate() const noexcept +const std::optional &Opus::xlinkActuate() const noexcept { return m_xlinkActuate; } -void Opus::setXlinkActuate(std::optional value) +void Opus::setXlinkActuate(std::optional value) { m_xlinkActuate = std::move(value); } @@ -89,7 +89,7 @@ Opus parseOpus(pugi::xml_node el, const ParseContext &context) } else if (aname == "xlink:type") { - out.setXlinkType(std::string{a.value()}); + out.setXlinkType(parseValue(a.value(), context, el, "xlink:type")); } else if (aname == "xlink:role") { @@ -101,11 +101,11 @@ Opus parseOpus(pugi::xml_node el, const ParseContext &context) } else if (aname == "xlink:show") { - out.setXlinkShow(std::string{a.value()}); + out.setXlinkShow(parseValue(a.value(), context, el, "xlink:show")); } else if (aname == "xlink:actuate") { - out.setXlinkActuate(std::string{a.value()}); + out.setXlinkActuate(parseValue(a.value(), context, el, "xlink:actuate")); } else { @@ -136,7 +136,7 @@ void serializeOpus(const Opus &v, pugi::xml_node parent, const char *tag) setAttribute(el, "xlink:href", v.xlinkHref()); if (v.xlinkType()) { - setAttribute(el, "xlink:type", *v.xlinkType()); + setAttribute(el, "xlink:type", v.xlinkType()->toString()); } if (v.xlinkRole()) { @@ -148,11 +148,11 @@ void serializeOpus(const Opus &v, pugi::xml_node parent, const char *tag) } if (v.xlinkShow()) { - setAttribute(el, "xlink:show", *v.xlinkShow()); + setAttribute(el, "xlink:show", v.xlinkShow()->toString()); } if (v.xlinkActuate()) { - setAttribute(el, "xlink:actuate", *v.xlinkActuate()); + setAttribute(el, "xlink:actuate", v.xlinkActuate()->toString()); } serializeOpusContent(v, el); } diff --git a/src/private/mx/core/generated/Opus.h b/src/private/mx/core/generated/Opus.h index f0d909290..97b61cf8c 100644 --- a/src/private/mx/core/generated/Opus.h +++ b/src/private/mx/core/generated/Opus.h @@ -2,6 +2,10 @@ #pragma once +#include "mx/core/generated/ActuateType.h" +#include "mx/core/generated/ShowType.h" +#include "mx/core/generated/TypeType.h" + #include #include @@ -22,25 +26,25 @@ class Opus final public: const std::string &xlinkHref() const noexcept; void setXlinkHref(std::string value); - const std::optional &xlinkType() const noexcept; - void setXlinkType(std::optional value); + const std::optional &xlinkType() const noexcept; + void setXlinkType(std::optional value); const std::optional &xlinkRole() const noexcept; void setXlinkRole(std::optional value); const std::optional &xlinkTitle() const noexcept; void setXlinkTitle(std::optional value); - const std::optional &xlinkShow() const noexcept; - void setXlinkShow(std::optional value); - const std::optional &xlinkActuate() const noexcept; - void setXlinkActuate(std::optional value); + const std::optional &xlinkShow() const noexcept; + void setXlinkShow(std::optional value); + const std::optional &xlinkActuate() const noexcept; + void setXlinkActuate(std::optional value); bool operator==(const Opus &other) const = default; private: std::string m_xlinkHref{}; - std::optional m_xlinkType; + std::optional m_xlinkType; std::optional m_xlinkRole; std::optional m_xlinkTitle; - std::optional m_xlinkShow; - std::optional m_xlinkActuate; + std::optional m_xlinkShow; + std::optional m_xlinkActuate; }; Opus parseOpus(pugi::xml_node el, const ParseContext &context); diff --git a/src/private/mx/core/generated/PartLink.cpp b/src/private/mx/core/generated/PartLink.cpp index be001b290..5ca3cc2cb 100644 --- a/src/private/mx/core/generated/PartLink.cpp +++ b/src/private/mx/core/generated/PartLink.cpp @@ -21,12 +21,12 @@ void PartLink::setXlinkHref(std::string value) m_xlinkHref = std::move(value); } -const std::optional &PartLink::xlinkType() const noexcept +const std::optional &PartLink::xlinkType() const noexcept { return m_xlinkType; } -void PartLink::setXlinkType(std::optional value) +void PartLink::setXlinkType(std::optional value) { m_xlinkType = std::move(value); } @@ -51,22 +51,22 @@ void PartLink::setXlinkTitle(std::optional value) m_xlinkTitle = std::move(value); } -const std::optional &PartLink::xlinkShow() const noexcept +const std::optional &PartLink::xlinkShow() const noexcept { return m_xlinkShow; } -void PartLink::setXlinkShow(std::optional value) +void PartLink::setXlinkShow(std::optional value) { m_xlinkShow = std::move(value); } -const std::optional &PartLink::xlinkActuate() const noexcept +const std::optional &PartLink::xlinkActuate() const noexcept { return m_xlinkActuate; } -void PartLink::setXlinkActuate(std::optional value) +void PartLink::setXlinkActuate(std::optional value) { m_xlinkActuate = std::move(value); } @@ -119,7 +119,7 @@ PartLink parsePartLink(pugi::xml_node el, const ParseContext &context) } else if (aname == "xlink:type") { - out.setXlinkType(std::string{a.value()}); + out.setXlinkType(parseValue(a.value(), context, el, "xlink:type")); } else if (aname == "xlink:role") { @@ -131,11 +131,11 @@ PartLink parsePartLink(pugi::xml_node el, const ParseContext &context) } else if (aname == "xlink:show") { - out.setXlinkShow(std::string{a.value()}); + out.setXlinkShow(parseValue(a.value(), context, el, "xlink:show")); } else if (aname == "xlink:actuate") { - out.setXlinkActuate(std::string{a.value()}); + out.setXlinkActuate(parseValue(a.value(), context, el, "xlink:actuate")); } else { @@ -179,7 +179,7 @@ void serializePartLink(const PartLink &v, pugi::xml_node parent, const char *tag setAttribute(el, "xlink:href", v.xlinkHref()); if (v.xlinkType()) { - setAttribute(el, "xlink:type", *v.xlinkType()); + setAttribute(el, "xlink:type", v.xlinkType()->toString()); } if (v.xlinkRole()) { @@ -191,11 +191,11 @@ void serializePartLink(const PartLink &v, pugi::xml_node parent, const char *tag } if (v.xlinkShow()) { - setAttribute(el, "xlink:show", *v.xlinkShow()); + setAttribute(el, "xlink:show", v.xlinkShow()->toString()); } if (v.xlinkActuate()) { - setAttribute(el, "xlink:actuate", *v.xlinkActuate()); + setAttribute(el, "xlink:actuate", v.xlinkActuate()->toString()); } serializePartLinkContent(v, el); } diff --git a/src/private/mx/core/generated/PartLink.h b/src/private/mx/core/generated/PartLink.h index 3b87682db..f861e2dbd 100644 --- a/src/private/mx/core/generated/PartLink.h +++ b/src/private/mx/core/generated/PartLink.h @@ -2,7 +2,10 @@ #pragma once +#include "mx/core/generated/ActuateType.h" #include "mx/core/generated/InstrumentLink.h" +#include "mx/core/generated/ShowType.h" +#include "mx/core/generated/TypeType.h" #include #include @@ -30,16 +33,16 @@ class PartLink final public: const std::string &xlinkHref() const noexcept; void setXlinkHref(std::string value); - const std::optional &xlinkType() const noexcept; - void setXlinkType(std::optional value); + const std::optional &xlinkType() const noexcept; + void setXlinkType(std::optional value); const std::optional &xlinkRole() const noexcept; void setXlinkRole(std::optional value); const std::optional &xlinkTitle() const noexcept; void setXlinkTitle(std::optional value); - const std::optional &xlinkShow() const noexcept; - void setXlinkShow(std::optional value); - const std::optional &xlinkActuate() const noexcept; - void setXlinkActuate(std::optional value); + const std::optional &xlinkShow() const noexcept; + void setXlinkShow(std::optional value); + const std::optional &xlinkActuate() const noexcept; + void setXlinkActuate(std::optional value); std::span instrumentLink() const noexcept; void addInstrumentLink(InstrumentLink value); void setInstrumentLink(std::vector value); @@ -50,11 +53,11 @@ class PartLink final private: std::string m_xlinkHref{}; - std::optional m_xlinkType; + std::optional m_xlinkType; std::optional m_xlinkRole; std::optional m_xlinkTitle; - std::optional m_xlinkShow; - std::optional m_xlinkActuate; + std::optional m_xlinkShow; + std::optional m_xlinkActuate; std::vector m_instrumentLink; std::vector m_groupLink; }; diff --git a/src/private/mx/core/generated/ShowType.cpp b/src/private/mx/core/generated/ShowType.cpp new file mode 100644 index 000000000..4a9ca6d01 --- /dev/null +++ b/src/private/mx/core/generated/ShowType.cpp @@ -0,0 +1,71 @@ +// Code generated by gen from musicxml-4.0-ed15c23. DO NOT EDIT. + +#include "mx/core/generated/ShowType.h" + +#include +#include + +namespace mx::core +{ + +constexpr std::string_view kShowTypeWire[] = { + "new", "replace", "embed", "other", "none", +}; + +ShowType ShowType::new_() noexcept +{ + return ShowType{Tag::new_}; +} + +ShowType ShowType::replace() noexcept +{ + return ShowType{Tag::replace}; +} + +ShowType ShowType::embed() noexcept +{ + return ShowType{Tag::embed}; +} + +ShowType ShowType::other() noexcept +{ + return ShowType{Tag::other}; +} + +ShowType ShowType::none() noexcept +{ + return ShowType{Tag::none}; +} + +std::string_view ShowType::toString() const noexcept +{ + return kShowTypeWire[static_cast(m_tag)]; +} + +bool ShowType::tryParse(std::string_view text, ShowType &out) noexcept +{ + for (std::size_t i = 0; i < std::size(kShowTypeWire); ++i) + { + if (kShowTypeWire[i] == text) + { + out = ShowType{static_cast(i)}; + return true; + } + } + return false; +} + +ShowType ShowType::parse(std::string_view text) noexcept +{ + ValueParseOutcome outcome = ValueParseOutcome::valid; + return parse(text, outcome); +} + +ShowType ShowType::parse(std::string_view text, ValueParseOutcome &outcome) noexcept +{ + ShowType v; + outcome = tryParse(text, v) ? ValueParseOutcome::valid : ValueParseOutcome::invalid; + return v; +} + +} // namespace mx::core diff --git a/src/private/mx/core/generated/ShowType.h b/src/private/mx/core/generated/ShowType.h new file mode 100644 index 000000000..db95de6bb --- /dev/null +++ b/src/private/mx/core/generated/ShowType.h @@ -0,0 +1,63 @@ +// Code generated by gen from musicxml-4.0-ed15c23. DO NOT EDIT. + +#pragma once + +#include "mx/core/Lexical.h" + +#include + +namespace mx::core +{ + +/// Closed vocabulary: the named factories are the only constructors, so an +/// out-of-vocabulary value is unrepresentable through the public API. +class ShowType final +{ + public: + enum class Tag + { + new_, + replace, + embed, + other, + none, + }; + + /// The natural zero: the first variant. + ShowType() noexcept = default; + + static ShowType new_() noexcept; + static ShowType replace() noexcept; + static ShowType embed() noexcept; + static ShowType other() noexcept; + static ShowType none() noexcept; + + Tag tag() const noexcept + { + return m_tag; + } + + /// The wire literal (static storage). + std::string_view toString() const noexcept; + + /// Strict: matches a wire literal exactly. + static bool tryParse(std::string_view text, ShowType &out) noexcept; + + /// Lenient: an unrecognized literal falls back to the first variant + /// (the import leniency policy; never produces an invalid value). + static ShowType parse(std::string_view text) noexcept; + + /// Lenient, and says whether the literal was recognized. + static ShowType parse(std::string_view text, ValueParseOutcome &outcome) noexcept; + + bool operator==(const ShowType &other) const noexcept = default; + + private: + explicit ShowType(Tag tag) noexcept : m_tag{tag} + { + } + + Tag m_tag{Tag::new_}; +}; + +} // namespace mx::core diff --git a/src/private/mx/core/generated/TextElementData.cpp b/src/private/mx/core/generated/TextElementData.cpp index 38b6b754f..8c14ad3ec 100644 --- a/src/private/mx/core/generated/TextElementData.cpp +++ b/src/private/mx/core/generated/TextElementData.cpp @@ -11,12 +11,12 @@ namespace mx::core { -const std::optional &TextElementData::xmlLang() const noexcept +const std::optional &TextElementData::xmlLang() const noexcept { return m_xmlLang; } -void TextElementData::setXMLLang(std::optional value) +void TextElementData::setXMLLang(std::optional value) { m_xmlLang = std::move(value); } @@ -153,7 +153,7 @@ TextElementData parseTextElementData(pugi::xml_node el, const ParseContext &cont } if (aname == "xml:lang") { - out.setXMLLang(std::string{a.value()}); + out.setXMLLang(parseValue(a.value(), context, el, "xml:lang")); } else if (aname == "font-family") { @@ -222,7 +222,7 @@ void serializeTextElementData(const TextElementData &v, pugi::xml_node parent, c pugi::xml_node el = parent.append_child(tag); if (v.xmlLang()) { - setAttribute(el, "xml:lang", *v.xmlLang()); + setAttribute(el, "xml:lang", v.xmlLang()->toString()); } if (v.fontFamily()) { diff --git a/src/private/mx/core/generated/TextElementData.h b/src/private/mx/core/generated/TextElementData.h index 1d5bd6f1c..e647b5159 100644 --- a/src/private/mx/core/generated/TextElementData.h +++ b/src/private/mx/core/generated/TextElementData.h @@ -2,6 +2,7 @@ #pragma once +#include "mx/core/Language.h" #include "mx/core/generated/Color.h" #include "mx/core/generated/FontFamily.h" #include "mx/core/generated/FontSize.h" @@ -31,8 +32,8 @@ class ParseContext; class TextElementData final { public: - const std::optional &xmlLang() const noexcept; - void setXMLLang(std::optional value); + const std::optional &xmlLang() const noexcept; + void setXMLLang(std::optional value); const std::optional &fontFamily() const noexcept; void setFontFamily(std::optional value); const std::optional &fontStyle() const noexcept; @@ -60,7 +61,7 @@ class TextElementData final bool operator==(const TextElementData &other) const = default; private: - std::optional m_xmlLang; + std::optional m_xmlLang; std::optional m_fontFamily; std::optional m_fontStyle; std::optional m_fontSize; diff --git a/src/private/mx/core/generated/TypeType.cpp b/src/private/mx/core/generated/TypeType.cpp new file mode 100644 index 000000000..dbf8fc969 --- /dev/null +++ b/src/private/mx/core/generated/TypeType.cpp @@ -0,0 +1,76 @@ +// Code generated by gen from musicxml-4.0-ed15c23. DO NOT EDIT. + +#include "mx/core/generated/TypeType.h" + +#include +#include + +namespace mx::core +{ + +constexpr std::string_view kTypeTypeWire[] = { + "simple", "extended", "title", "resource", "locator", "arc", +}; + +TypeType TypeType::simple() noexcept +{ + return TypeType{Tag::simple}; +} + +TypeType TypeType::extended() noexcept +{ + return TypeType{Tag::extended}; +} + +TypeType TypeType::title() noexcept +{ + return TypeType{Tag::title}; +} + +TypeType TypeType::resource() noexcept +{ + return TypeType{Tag::resource}; +} + +TypeType TypeType::locator() noexcept +{ + return TypeType{Tag::locator}; +} + +TypeType TypeType::arc() noexcept +{ + return TypeType{Tag::arc}; +} + +std::string_view TypeType::toString() const noexcept +{ + return kTypeTypeWire[static_cast(m_tag)]; +} + +bool TypeType::tryParse(std::string_view text, TypeType &out) noexcept +{ + for (std::size_t i = 0; i < std::size(kTypeTypeWire); ++i) + { + if (kTypeTypeWire[i] == text) + { + out = TypeType{static_cast(i)}; + return true; + } + } + return false; +} + +TypeType TypeType::parse(std::string_view text) noexcept +{ + ValueParseOutcome outcome = ValueParseOutcome::valid; + return parse(text, outcome); +} + +TypeType TypeType::parse(std::string_view text, ValueParseOutcome &outcome) noexcept +{ + TypeType v; + outcome = tryParse(text, v) ? ValueParseOutcome::valid : ValueParseOutcome::invalid; + return v; +} + +} // namespace mx::core diff --git a/src/private/mx/core/generated/TypeType.h b/src/private/mx/core/generated/TypeType.h new file mode 100644 index 000000000..397421d4f --- /dev/null +++ b/src/private/mx/core/generated/TypeType.h @@ -0,0 +1,65 @@ +// Code generated by gen from musicxml-4.0-ed15c23. DO NOT EDIT. + +#pragma once + +#include "mx/core/Lexical.h" + +#include + +namespace mx::core +{ + +/// Closed vocabulary: the named factories are the only constructors, so an +/// out-of-vocabulary value is unrepresentable through the public API. +class TypeType final +{ + public: + enum class Tag + { + simple, + extended, + title, + resource, + locator, + arc, + }; + + /// The natural zero: the first variant. + TypeType() noexcept = default; + + static TypeType simple() noexcept; + static TypeType extended() noexcept; + static TypeType title() noexcept; + static TypeType resource() noexcept; + static TypeType locator() noexcept; + static TypeType arc() noexcept; + + Tag tag() const noexcept + { + return m_tag; + } + + /// The wire literal (static storage). + std::string_view toString() const noexcept; + + /// Strict: matches a wire literal exactly. + static bool tryParse(std::string_view text, TypeType &out) noexcept; + + /// Lenient: an unrecognized literal falls back to the first variant + /// (the import leniency policy; never produces an invalid value). + static TypeType parse(std::string_view text) noexcept; + + /// Lenient, and says whether the literal was recognized. + static TypeType parse(std::string_view text, ValueParseOutcome &outcome) noexcept; + + bool operator==(const TypeType &other) const noexcept = default; + + private: + explicit TypeType(Tag tag) noexcept : m_tag{tag} + { + } + + Tag m_tag{Tag::simple}; +}; + +} // namespace mx::core diff --git a/src/private/mx/core/generated/XMLSpace.cpp b/src/private/mx/core/generated/XMLSpace.cpp new file mode 100644 index 000000000..87a1829ed --- /dev/null +++ b/src/private/mx/core/generated/XMLSpace.cpp @@ -0,0 +1,57 @@ +// Code generated by gen from musicxml-4.0-ed15c23. DO NOT EDIT. + +#include "mx/core/generated/XMLSpace.h" + +#include +#include + +namespace mx::core +{ + +constexpr std::string_view kXMLSpaceWire[] = { + "default", + "preserve", +}; + +XMLSpace XMLSpace::default_() noexcept +{ + return XMLSpace{Tag::default_}; +} + +XMLSpace XMLSpace::preserve() noexcept +{ + return XMLSpace{Tag::preserve}; +} + +std::string_view XMLSpace::toString() const noexcept +{ + return kXMLSpaceWire[static_cast(m_tag)]; +} + +bool XMLSpace::tryParse(std::string_view text, XMLSpace &out) noexcept +{ + for (std::size_t i = 0; i < std::size(kXMLSpaceWire); ++i) + { + if (kXMLSpaceWire[i] == text) + { + out = XMLSpace{static_cast(i)}; + return true; + } + } + return false; +} + +XMLSpace XMLSpace::parse(std::string_view text) noexcept +{ + ValueParseOutcome outcome = ValueParseOutcome::valid; + return parse(text, outcome); +} + +XMLSpace XMLSpace::parse(std::string_view text, ValueParseOutcome &outcome) noexcept +{ + XMLSpace v; + outcome = tryParse(text, v) ? ValueParseOutcome::valid : ValueParseOutcome::invalid; + return v; +} + +} // namespace mx::core diff --git a/src/private/mx/core/generated/XMLSpace.h b/src/private/mx/core/generated/XMLSpace.h new file mode 100644 index 000000000..e4ecf2cf9 --- /dev/null +++ b/src/private/mx/core/generated/XMLSpace.h @@ -0,0 +1,57 @@ +// Code generated by gen from musicxml-4.0-ed15c23. DO NOT EDIT. + +#pragma once + +#include "mx/core/Lexical.h" + +#include + +namespace mx::core +{ + +/// Closed vocabulary: the named factories are the only constructors, so an +/// out-of-vocabulary value is unrepresentable through the public API. +class XMLSpace final +{ + public: + enum class Tag + { + default_, + preserve, + }; + + /// The natural zero: the first variant. + XMLSpace() noexcept = default; + + static XMLSpace default_() noexcept; + static XMLSpace preserve() noexcept; + + Tag tag() const noexcept + { + return m_tag; + } + + /// The wire literal (static storage). + std::string_view toString() const noexcept; + + /// Strict: matches a wire literal exactly. + static bool tryParse(std::string_view text, XMLSpace &out) noexcept; + + /// Lenient: an unrecognized literal falls back to the first variant + /// (the import leniency policy; never produces an invalid value). + static XMLSpace parse(std::string_view text) noexcept; + + /// Lenient, and says whether the literal was recognized. + static XMLSpace parse(std::string_view text, ValueParseOutcome &outcome) noexcept; + + bool operator==(const XMLSpace &other) const noexcept = default; + + private: + explicit XMLSpace(Tag tag) noexcept : m_tag{tag} + { + } + + Tag m_tag{Tag::default_}; +}; + +} // namespace mx::core diff --git a/src/private/mx/core/generated/sources.cmake b/src/private/mx/core/generated/sources.cmake index 2ea5632b5..88b726204 100644 --- a/src/private/mx/core/generated/sources.cmake +++ b/src/private/mx/core/generated/sources.cmake @@ -8,6 +8,7 @@ set(MX_CORE_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Accord.cpp ${CMAKE_CURRENT_LIST_DIR}/AccordionMiddle.cpp ${CMAKE_CURRENT_LIST_DIR}/AccordionRegistration.cpp + ${CMAKE_CURRENT_LIST_DIR}/ActuateType.cpp ${CMAKE_CURRENT_LIST_DIR}/AllMarginsGroup.cpp ${CMAKE_CURRENT_LIST_DIR}/Appearance.cpp ${CMAKE_CURRENT_LIST_DIR}/Arpeggiate.cpp @@ -336,6 +337,7 @@ set(MX_CORE_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Semitones.cpp ${CMAKE_CURRENT_LIST_DIR}/ShowFrets.cpp ${CMAKE_CURRENT_LIST_DIR}/ShowTuplet.cpp + ${CMAKE_CURRENT_LIST_DIR}/ShowType.cpp ${CMAKE_CURRENT_LIST_DIR}/Slash.cpp ${CMAKE_CURRENT_LIST_DIR}/SlashGroup.cpp ${CMAKE_CURRENT_LIST_DIR}/SlashGroupGroup.cpp @@ -433,6 +435,7 @@ set(MX_CORE_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/TupletPortion.cpp ${CMAKE_CURRENT_LIST_DIR}/TupletType.cpp ${CMAKE_CURRENT_LIST_DIR}/TwoNoteTurn.cpp + ${CMAKE_CURRENT_LIST_DIR}/TypeType.cpp ${CMAKE_CURRENT_LIST_DIR}/TypedText.cpp ${CMAKE_CURRENT_LIST_DIR}/Unpitched.cpp ${CMAKE_CURRENT_LIST_DIR}/UpDown.cpp @@ -451,6 +454,7 @@ set(MX_CORE_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Wood.cpp ${CMAKE_CURRENT_LIST_DIR}/WoodValue.cpp ${CMAKE_CURRENT_LIST_DIR}/Work.cpp + ${CMAKE_CURRENT_LIST_DIR}/XMLSpace.cpp ${CMAKE_CURRENT_LIST_DIR}/YesNo.cpp ${CMAKE_CURRENT_LIST_DIR}/YesNoNumber.cpp ${CMAKE_CURRENT_LIST_DIR}/YyyyMmDd.cpp @@ -462,6 +466,7 @@ set(MX_CORE_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Accord.h ${CMAKE_CURRENT_LIST_DIR}/AccordionMiddle.h ${CMAKE_CURRENT_LIST_DIR}/AccordionRegistration.h + ${CMAKE_CURRENT_LIST_DIR}/ActuateType.h ${CMAKE_CURRENT_LIST_DIR}/AllMarginsGroup.h ${CMAKE_CURRENT_LIST_DIR}/Appearance.h ${CMAKE_CURRENT_LIST_DIR}/Arpeggiate.h @@ -790,6 +795,7 @@ set(MX_CORE_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Semitones.h ${CMAKE_CURRENT_LIST_DIR}/ShowFrets.h ${CMAKE_CURRENT_LIST_DIR}/ShowTuplet.h + ${CMAKE_CURRENT_LIST_DIR}/ShowType.h ${CMAKE_CURRENT_LIST_DIR}/Slash.h ${CMAKE_CURRENT_LIST_DIR}/SlashGroup.h ${CMAKE_CURRENT_LIST_DIR}/SlashGroupGroup.h @@ -887,6 +893,7 @@ set(MX_CORE_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/TupletPortion.h ${CMAKE_CURRENT_LIST_DIR}/TupletType.h ${CMAKE_CURRENT_LIST_DIR}/TwoNoteTurn.h + ${CMAKE_CURRENT_LIST_DIR}/TypeType.h ${CMAKE_CURRENT_LIST_DIR}/TypedText.h ${CMAKE_CURRENT_LIST_DIR}/Unpitched.h ${CMAKE_CURRENT_LIST_DIR}/UpDown.h @@ -906,6 +913,7 @@ set(MX_CORE_GENERATED_SOURCES ${CMAKE_CURRENT_LIST_DIR}/Wood.h ${CMAKE_CURRENT_LIST_DIR}/WoodValue.h ${CMAKE_CURRENT_LIST_DIR}/Work.h + ${CMAKE_CURRENT_LIST_DIR}/XMLSpace.h ${CMAKE_CURRENT_LIST_DIR}/YesNo.h ${CMAKE_CURRENT_LIST_DIR}/YesNoNumber.h ${CMAKE_CURRENT_LIST_DIR}/YyyyMmDd.h diff --git a/src/private/mxtest/core/DocumentTest.cpp b/src/private/mxtest/core/DocumentTest.cpp index b21da66e8..038f43f48 100644 --- a/src/private/mxtest/core/DocumentTest.cpp +++ b/src/private/mxtest/core/DocumentTest.cpp @@ -18,7 +18,10 @@ #include "pugixml.hpp" +#include +#include #include +#include using namespace mx::core; @@ -110,6 +113,40 @@ TEST(RejectsMissingRequiredAttribute, Document) CHECK(e.path.find("score-part") != std::string::npos); } +TEST(RepairsOutOfVocabularyXmlAndXlinkAttributes, Document) +{ + // #398: xml:lang, xml:space and the xlink attributes are constrained now. + // An out-of-vocabulary value is repaired to the type's natural value and + // reported, the same leniency every other value type applies -- a + // document-level REFUSAL would turn a 3.x file into an unparseable one. + std::vector diagnostics; + pugi::xml_document doc; + CHECK(doc.load_string("" + "" + "" + "Hello" + "" + "M" + "")); + const ParseContext context{[&diagnostics](const Diagnostic &diagnostic) { diagnostics.push_back(diagnostic); }}; + auto result = parse(doc, context); + CHECK(result.ok()); + + const auto reported = [&diagnostics](const std::string &message) { + return std::any_of(diagnostics.begin(), diagnostics.end(), + [&message](const Diagnostic &diagnostic) { return diagnostic.message == message; }); + }; + CHECK_EQUAL(std::size_t{4}, diagnostics.size()); + CHECK(reported("invalid value \"bogus\" in attribute \"xlink:type\"; using \"simple\"")); + CHECK(reported("invalid value \"sometimes\" in attribute \"xlink:show\"; using \"new\"")); + CHECK(reported("invalid value \"not a language\" in attribute \"xml:lang\"; using \"\"")); + CHECK(reported("invalid value \"sometimes\" in attribute \"xml:space\"; using \"default\"")); + for (const Diagnostic &diagnostic : diagnostics) + { + CHECK(DiagnosticCode::invalidValue == diagnostic.code); + } +} + TEST(RejectsNewerVersion, Document) { pugi::xml_document doc; diff --git a/src/private/mxtest/core/ImportDiagnosticsTest.cpp b/src/private/mxtest/core/ImportDiagnosticsTest.cpp index fb31b3fa9..8b509b84e 100644 --- a/src/private/mxtest/core/ImportDiagnosticsTest.cpp +++ b/src/private/mxtest/core/ImportDiagnosticsTest.cpp @@ -104,7 +104,7 @@ TEST(MissingLyricLanguageIsDefaultedAndReported, ImportDiagnostics) const auto &defaults = parsed.document.asScorePartwise().scoreHeader().defaults(); REQUIRE(defaults.has_value()); REQUIRE(defaults->lyricLanguage().size() == 1); - CHECK_EQUAL(std::string{"und"}, defaults->lyricLanguage().front().xmlLang()); + CHECK_EQUAL(std::string{"und"}, defaults->lyricLanguage().front().xmlLang().value()); REQUIRE(parsed.diagnostics.size() == 1); const Diagnostic &diagnostic = parsed.diagnostics.front(); diff --git a/src/private/mxtest/core/ValueTest.cpp b/src/private/mxtest/core/ValueTest.cpp index 2b2a7b090..1b61933d8 100644 --- a/src/private/mxtest/core/ValueTest.cpp +++ b/src/private/mxtest/core/ValueTest.cpp @@ -9,10 +9,12 @@ #include "cpul/cpulTestHarness.h" #include "mx/core/Decimal.h" +#include "mx/core/Language.h" #include "mx/core/Lexical.h" #include "mx/core/NameToken.h" #include "mx/core/Token.h" #include "mx/core/generated/AboveBelow.h" +#include "mx/core/generated/ActuateType.h" #include "mx/core/generated/Color.h" #include "mx/core/generated/CommaSeparatedText.h" #include "mx/core/generated/EndingNumber.h" @@ -26,10 +28,13 @@ #include "mx/core/generated/PositiveDivisions.h" #include "mx/core/generated/PositiveIntegerOrEmpty.h" #include "mx/core/generated/RotationDegrees.h" +#include "mx/core/generated/ShowType.h" #include "mx/core/generated/SmuflAccidentalGlyphName.h" #include "mx/core/generated/SmuflCodaGlyphName.h" #include "mx/core/generated/SmuflWavyLineGlyphName.h" #include "mx/core/generated/TimeOnly.h" +#include "mx/core/generated/TypeType.h" +#include "mx/core/generated/XMLSpace.h" #include "mx/core/generated/YyyyMmDd.h" #include @@ -280,6 +285,62 @@ TEST(TokenRepairsToNCName, Values) CHECK(ValueParseOutcome::invalid == outcome); } +TEST(ImportedXmlAndXlinkVocabulary, Values) +{ + // The xml:space and xlink vocabularies come from the imported schemas + // (docs/xml.xsd, docs/xlink.xsd), so the enums carry exactly the members + // those schemas declare -- and an out-of-vocabulary value repairs to the + // natural zero the way every other MusicXML enum does. + CHECK_EQUAL(std::string_view{"default"}, XMLSpace{}.toString()); + CHECK_EQUAL(std::string_view{"preserve"}, XMLSpace::preserve().toString()); + CHECK_EQUAL(std::string_view{"simple"}, TypeType{}.toString()); + CHECK_EQUAL(std::string_view{"arc"}, TypeType::arc().toString()); + CHECK_EQUAL(std::string_view{"new"}, ShowType{}.toString()); + CHECK_EQUAL(std::string_view{"embed"}, ShowType::embed().toString()); + CHECK_EQUAL(std::string_view{"onLoad"}, ActuateType{}.toString()); + CHECK_EQUAL(std::string_view{"onRequest"}, ActuateType::onRequest().toString()); + ValueParseOutcome outcome = ValueParseOutcome::valid; + CHECK_EQUAL(std::string_view{"preserve"}, XMLSpace::parse("preserve", outcome).toString()); + CHECK(ValueParseOutcome::valid == outcome); + CHECK(TypeType::parse("sometimes", outcome) == TypeType{}); + CHECK(ValueParseOutcome::invalid == outcome); + TypeType strict; + CHECK(!TypeType::tryParse("sometimes", strict)); +} + +TEST(LanguageKeepsTheBuiltinPatternOrEmpty, Values) +{ + // xml:lang is the builtin xs:language pattern: a letter-led run of 1-8 + // letters, then hyphen-separated subtags of 1-8 letters or digits. The + // empty string is legal -- the "un-declaration" xml.xsd unions in -- and + // it is both the natural zero and the repair, so an out-of-grammar tag + // is representable only as "no language stated". + CHECK_EQUAL(std::string{""}, Language{}.toString()); + CHECK_EQUAL(std::string{"en"}, Language{"en"}.toString()); + CHECK_EQUAL(std::string{"en-US"}, Language{"en-US"}.toString()); + CHECK_EQUAL(std::string{"und"}, Language{"und"}.toString()); + CHECK_EQUAL(std::string{""}, Language{"not a language"}.toString()); + CHECK_EQUAL(std::string{""}, Language{"abcdefghijkl"}.toString()); // subtag over 8 + CHECK_EQUAL(std::string{""}, Language{"-en"}.toString()); // empty first subtag + CHECK_EQUAL(std::string{""}, Language{"1en"}.toString()); // digit-led + CHECK_EQUAL(std::string{""}, Language{"en_US"}.toString()); // not a tag separator + Language parsed; + CHECK(Language::tryParse("ja", parsed)); + CHECK(Language::tryParse("", parsed)); // the un-declaration + CHECK(!Language::tryParse("not a language", parsed)); + CHECK(!Language::tryParse("en-US-x-123456789", parsed)); // subtag over 8 + ValueParseOutcome outcome = ValueParseOutcome::invalid; + CHECK_EQUAL(std::string{"fr"}, Language::parse(" fr ", outcome).toString()); + CHECK(ValueParseOutcome::valid == outcome); // surrounding whitespace is not a repair + CHECK_EQUAL(std::string{""}, Language::parse("not a language", outcome).toString()); + CHECK(ValueParseOutcome::invalid == outcome); + Language tag; + tag.setValue("de-CH"); + CHECK_EQUAL(std::string{"de-CH"}, tag.toString()); + tag.setValue("12"); + CHECK_EQUAL(std::string{""}, tag.toString()); +} + TEST(NameTokenRepairsToNmtoken, Values) { // xs:NMTOKEN is wider than an NCName: a leading digit is allowed