experiment: model MusicXML ids with an api::Id type - #409
Open
webern wants to merge 2 commits into
Open
Conversation
An id attribute is not any old string. It has to follow the XML name rules, and until now mx enforced that only at the last moment, inside mx::impl, by handing the text to core::Token on the way out. The caller never learned that their id had changed. Replace std::optional<std::string> id with std::optional<api::Id> on all 41 api id members. api::Id holds a core::Token behind a pimpl, so the text is scrubbed once, as the Id is built, and mx::impl passes the token itself to mx::core. No second conversion is possible. Callers who need to know whether their text was legal compare Id::value() with what they passed in. Id copies, assigns, compares, sorts, and hashes like a plain value. std::hash is specialized for it, so an Id works as a key in std::map and std::unordered_map. Copies share one immutable token instead of allocating, and a moved-from Id still holds a token, so reading one is harmless. IdAccess (private to mx) is the only door between Id and its token. The public header names no core type. The id explanation moves from ApiCommon.h to Id.h, where the type it describes lives. SegnoData and CodaData keep their legacy std::string id plus isIdSpecified; those belong with the rest of issue 249.
These two were the last api types spelling an id as a std::string plus an isIdSpecified flag. Every other id in mx::api is now a std::optional<api::Id>, so leaving these on the old pattern would mean two ways to say one thing. Replace the pair with std::optional<Id>, drop isIdSpecified from the equality block, and route the reader and writer through getId and setId like the rest. The api-level direction test now covers the <segno> and <coda> ids alongside the other direction-type ids.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges into #401, not into
main. This is the "what would it look like" experiment for replacing the barestd::optional<std::string> idwith a type.The problem
An id attribute is not any old string. MusicXML requires it to be an XML name: a letter or an underscore first, then letters, digits, dots, hyphens, or underscores.
On #401 as it stands, mx enforces that at the last possible moment.
mx::implhands the caller's text tocore::Tokenon the way out,core::Tokenscrubs it, and the file gets an id the caller never asked for and never hears about:The change
Every id member in
mx::apibecomes astd::optional<Id>.api::Idholds acore::Tokenbehind a pimpl. That is the point of the design: the text is scrubbed once, when theIdis built, and from then onmx::implhandsmx::corethe token itself. Acore::Tokencannot hold a malformed id, so there is no second conversion left to make.Id.cppis now the only place in mx where an api id string becomes a token.The public header names no core type.
IdAccess, insrc/private/mx/api/IdAccess.h, is the only door between anIdand its token, and it is not part of the public api.What a caller sees
Scrubbing is silent, so
Iddocuments the check rather than inventing an error channel:Uniqueness is unchanged. It is a property of the whole score rather than of one name, so
Idcannot check it (#397).Value semantics
Idcopies, assigns, compares, sorts, and hashes like any other value.std::hashis specialized for it, so anIdworks as a key in bothstd::mapandstd::unordered_map. Copies share one immutable token instead of allocating another, and a moved-fromIdstill holds a token, so reading one is harmless rather than undefined.Scope
All 43 id members are converted, so the api has one way to say this and not three:
std::optional<std::string>(DampData,EyeglassesData,HarpPedalsData,ImageData,MarkDataChoice,OtherDirectionData,PercussionData,PrincipalVoiceData,ScordaturaData,StaffDivideData,StringMuteData,TempoData,AccordionRegistrationData);SegnoDataandCodaData, which held astd::string idplus anisIdSpecifiedflag. Both flags are gone, along with their equality-block lines.The reader and writer sites that hand-rolled the id attribute now go through
getId/setIdalongside the rest.PartData::uniqueIdandInstrumentData::uniqueIdare left alone. They are the required part-id join between<score-part>and<part>, not theoptional-unique-idattribute this change is about.Two things worth a look
Id{""}is"X". There is no nearest legal id for empty text, so it getscore::Token's natural zero. To write no id attribute, leave thestd::optionalempty. This is not new behavior — feat: expose musicxml id attributes in mx::api #401 already writesid="X"for an unusable string — but the type makes it visible.id="3 mice"reaches the api asmice. By doctrine that is right (an invalid document should be hard to express, and this is documented normalization), but it is a real change and worth naming.Gates
Run natively with
MX_RUNNING_IN_DOCKER=1; the Docker daemon is not available in this environment.make fmt-checkmake api-testmake core-roundtrip-testmake api-roundtripBATCH_SIZE=0)make wasm-testwas not run locally (no Emscripten here); CI covers it.New tests in
IdAttributeApiTest.cppcover scrubbing, the compare-your-input check, a scrubbed id surviving a round trip, and the value semantics including both map kinds. The existingsegnoAndCodaRoundTriptest inDirectionWriterTest.cppcompares whole objects, so it covers the two converted ids through the equality block.