Support the .mdl model format, in both of its on-disk flavours - #2
Merged
Conversation
`ingest`, `loadDirectory` and the model source adder now take a `.mdl` as readily as a `.slx` and open it to the same tree: the same blocks, parameter usages, config sets, model references, and model workspace. A `.mdl` is two formats wearing one extension, and both are read: - The MODERN flavour is an OPC *text* package — the identical part set a `.slx` zips, delimited by `__MWOPC_PART_BEGIN__` lines instead of archived, with binary parts base64'd. Decoding that framing hands the parts to `parseModelParts`, which is `parseSlx` minus the unzip, so one reader serves both containers instead of two that have to agree. - The CLASSIC flavour is the pre-R2012 nested-brace text that a model which was never migrated still has. It shares no framing with anything: blocks are brace-nested property lists, and the model workspace is a uuencoded `MatData` record holding a 1xN struct array of Name/Value pairs rather than the struct-of-variables an `.mxarray` part holds. `readMxArrayRecords` splits the record framing out of `parseMxArray` so both interpretations share it. `parseModel` sniffs which of the three it has been handed and is the export a consumer should reach for; `parseMdl` and `parseSlx` remain for a known format. The parameter-usage gate is now `isParamReference`, called by both parsers, so the classic flavour cannot drift from the `.slx` on which rows it surfaces. Three parity rules came out of holding it to that standard: `BlockType`, `Name` and `SID` are XML attributes in a `.slx` and properties in a `.mdl`, so they are skipped; `CopyOfModelName` is classic-only bookkeeping and is now in `NON_PARAM_PROPS`; and `BlockParameterDefaults` holds `Block` nodes with no `.slx` counterpart, so the walk starts from the model's `System` children. A model reference is named without an extension, so the entry takes the parent model's own — a legacy hierarchy is legacy throughout, and labelling a `.mdl` model's children `.slx` would link to nothing. Four divergences are real and are asserted rather than assumed: a classic file records no release and no UUID (both come back `''`), spells `lastModified` as MATLAB does rather than as ISO 8601, and has no archive, so `rawContents` and `zipEntries` are `null`. A fifth is MATLAB's limit and not the format's: exporting to R2011b drops the linked data dictionary, so the block row that links into it is a plain string there and a link target everywhere else. Verification, per the repo's rule that MATLAB is the only source of truth: `gen_mdl.m` writes a second parity corpus — one modern `.mdl`, the R2011b and R2017b classic exports of the same diagram, the `.slx` twin of each, an MCOS pair, and `mdl_truth.json` recorded from the MODEL rather than from any one file. `mdl.parity.test.ts` holds every flavour to that truth and to its twin, including a byte-level part comparison in which the modern `.mdl` and the `.slx` differ only in the two per-save parts — `simulink/modelWorkspace.mxarray` included, which is what proves the base64 decode. `mdlParser.test.ts` covers the framing and grammar against synthetic bytes. `drift.mjs` now regenerates both corpora.
A minor, not a patch: `.mdl` is a format the package could not read at all before, and it comes with two new entry points — `parseMdl` and the format-agnostic `parseModel`, which sniffs a `.slx` or either `.mdl` flavour from the bytes. Additive on every published path: `ingest`, `loadFromPath` and `loadDirectory` take a `.mdl` where they used to reject it, and nothing they already accepted behaves differently. Not a major, because nothing was removed or renamed. The one signature change that could break a compile is `ParsedSlx.rawContents` and `.zipEntries` becoming nullable — a classic `.mdl` is one flat text file with no parts to expose — and `ParsedSlx` is not re-exported from src/index.ts. The "exports" map publishes only `.` and `./node`, so no consumer can name that type without reaching around the map. npm run verify is green (3325 passed, 26 skipped; tarball dist-only, 419 files / 292.9 kB). The `.mdl` corpus in test/parity/artifacts/mdl/ was written by the same MATLAB as the rest of the parity corpus, 27.1.0.3353139 (R2027a Prerelease).
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.
Reads a
.mdlas readily as a.slx, and opens it to the same tree: the sameblocks, parameter usages, config sets, model references, and model workspace.
ingest,loadFromPath,loadDirectoryand the model source adder all takethe new extension;
.mdlgets the Simulink-model icon and class rather thanfalling through to the MAT default.
A
.mdlis two formats wearing one extensionBoth are read, because both are files a user can still have.
.slxzips, delimited by__MWOPC_PART_BEGIN__lines instead of archived,with binary parts base64'd. Decoding that framing hands the parts to
parseModelParts, which isparseSlxminus the unzip, so one reader servesboth containers rather than two that have to be kept in agreement.
was never migrated still has. It shares no framing with anything: blocks are
brace-nested property lists, and the model workspace is a uuencoded
MatDatarecord holding a 1xN struct array of Name/Value pairs rather than the
struct-of-variables an
.mxarraypart holds.readMxArrayRecordssplits therecord framing out of
parseMxArrayso both interpretations share it.parseModelsniffs which of the three it was handed and is the export to reachfor;
parseMdlandparseSlxremain for a known format.Keeping the two formats from drifting
The parameter-usage gate is now the shared
isParamReference, called by bothparsers, so the classic flavour cannot disagree with the
.slxabout which rowsit surfaces. Three rules came out of holding it to that standard:
BlockType,NameandSIDare XML attributes in a.slxand ordinaryproperties in a
.mdl, so they are skipped;CopyOfModelNameis classic-only bookkeeping and is now inNON_PARAM_PROPS— leaving it in gave the classic file an extra
Child / CopyOfModelNamerowthe
.slxof the same model did not have;BlockParameterDefaultsholdsBlocknodes with no.slxcounterpart, sothe walk starts from the model's
Systemchildren.A model reference is named without an extension, so the entry takes the parent
model's own: a legacy hierarchy is legacy throughout, and labelling a
.mdlmodel's children
.slxwould link to nothing.Verification
MATLAB stays the only source of truth.
test/parity/matlab/gen_mdl.mwrites asecond parity corpus — one modern
.mdl, the R2011b and R2017b classic exportsof the same diagram, the
.slxtwin of each, an MCOS pair, andmdl_truth.jsonrecorded from the model rather than from any one file, so every flavour is
held to the same expectation. Written by 27.1.0.3353139 (R2027a Prerelease), the
same MATLAB as the rest of the corpus.
mdl.parity.test.ts(156 tests) checks each flavour against that truth andagainst its twin, including a byte-level part comparison in which the modern
.mdland the.slxdiffer only in the two per-save parts —simulink/modelWorkspace.mxarrayincluded, which is what proves the base64decode rather than sampling a value out of it.
mdlParser.test.ts(32 tests)covers the framing and the grammar against synthetic bytes.
drift.mjsnowregenerates both corpora.
Four divergences are real, and are asserted rather than assumed: a classic file
records no release and no UUID (both come back
''), spellslastModifiedasMATLAB does rather than as ISO 8601, and has no archive, so
rawContentsandzipEntriesarenull. A fifth is MATLAB's limit and not the format's —exporting to R2011b drops the linked data dictionary, so the block row that
links into it is a plain string there and a link target in every other flavour.
Version
1.1.0. Additive on every published path; nothing removed or renamed. The one
signature change that could break a compile is
ParsedSlx.rawContentsand.zipEntriesbecoming nullable, andParsedSlxis not re-exported fromsrc/index.ts— theexportsmap publishes only.and./node.npm run verifyis green: 3325 passed, 26 skipped, tarball dist-only (419files / 292.9 kB).
.mdlis read-only, like.slx,.matand.prj; there isno write-back path and none is claimed.