From 0fd9565f76cda9cbc2c889091d3c4cb350ab2930 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Thu, 8 Oct 2026 17:49:56 +1300 Subject: [PATCH 1/2] New version. --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 6bf14c909..49a3b77d5 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.20261007.0 +1.20261008.0 From 41ea1dd0c14e6ea10150497a72a654b6a0bee741 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Thu, 8 Oct 2026 17:59:31 +1300 Subject: [PATCH 2/2] Added support for OMEX files that don't specify a master file. --- src/sed/seddocument.cpp | 11 ++- src/support/combine/combinearchive.cpp | 89 ++++++++++++++++++ src/support/combine/combinearchive.h | 2 + src/support/combine/combinearchive_p.h | 4 + tests/api/sed/basictests.cpp | 72 +++++++++++++- tests/bindings/javascript/sed.basic.test.js | 85 ++++++++++++++++- tests/bindings/python/test_sed_basic.py | 88 ++++++++++++++++- tests/res/api/sed/no_master_file.omex | Bin 2989 -> 0 bytes .../no_master_file_with_one_cellml_file.omex | Bin 0 -> 813 bytes .../no_master_file_with_one_sedml_file.omex | Bin 0 -> 2455 bytes ...ter_file_with_one_unknown_cellml_file.omex | Bin 0 -> 424 bytes ...ster_file_with_one_unknown_sedml_file.omex | Bin 0 -> 954 bytes ...master_file_with_several_cellml_files.omex | Bin 0 -> 1346 bytes ..._master_file_with_several_sedml_files.omex | Bin 0 -> 4099 bytes 14 files changed, 340 insertions(+), 11 deletions(-) delete mode 100644 tests/res/api/sed/no_master_file.omex create mode 100644 tests/res/api/sed/no_master_file_with_one_cellml_file.omex create mode 100644 tests/res/api/sed/no_master_file_with_one_sedml_file.omex create mode 100644 tests/res/api/sed/no_master_file_with_one_unknown_cellml_file.omex create mode 100644 tests/res/api/sed/no_master_file_with_one_unknown_sedml_file.omex create mode 100644 tests/res/api/sed/no_master_file_with_several_cellml_files.omex create mode 100644 tests/res/api/sed/no_master_file_with_several_sedml_files.omex diff --git a/src/sed/seddocument.cpp b/src/sed/seddocument.cpp index 6ae4bcef3..d6042f6ca 100644 --- a/src/sed/seddocument.cpp +++ b/src/sed/seddocument.cpp @@ -90,10 +90,17 @@ void SedDocument::Impl::initialiseFromSedmlFile(const SedDocumentPtr &pOwner, co void SedDocument::Impl::initialiseFromCombineArchive(const SedDocumentPtr &pOwner, const FilePtr &pFile) { - const auto &masterFile {pFile->pimpl()->mCombineArchive->masterFile()}; + const auto &combineArchive {pFile->pimpl()->mCombineArchive}; + const auto &masterFile {combineArchive->masterFile()}; if (masterFile == nullptr) { - addError("A simulation experiment description cannot be created using a COMBINE archive with no master file."); + if (combineArchive->sedmlFileCount() > 1) { + addError("A simulation experiment description cannot be created using a COMBINE archive with no master file and several SED-ML files."); + } else if (combineArchive->cellmlFileCount() > 1) { + addError("A simulation experiment description cannot be created using a COMBINE archive with no master file, no SED-ML file, and several CellML files."); + } else { + addError("A simulation experiment description cannot be created using a COMBINE archive with no master file."); + } } else { auto masterFileType {masterFile->type()}; diff --git a/src/support/combine/combinearchive.cpp b/src/support/combine/combinearchive.cpp index a629b89e3..a48d15a98 100644 --- a/src/support/combine/combinearchive.cpp +++ b/src/support/combine/combinearchive.cpp @@ -22,8 +22,30 @@ limitations under the License. #include "combine/combinearchive.h" #include "omex/CaContent.h" +#include + namespace libOpenCOR { +namespace { + +bool isCellmlFormat(const std::string &pFormat) +{ + // Note: we accept versioned formats (e.g., cellml.1.0 or cellml.2.0). + + static const std::regex CELLML_FORMAT_REGEX {R"(^http://identifiers\.org/combine\.specifications/cellml(\..*)?$)"}; + + return std::regex_match(pFormat, CELLML_FORMAT_REGEX); +} + +bool isSedmlFormat(const std::string &pFormat) +{ + static const std::string SEDML_FORMAT {"http://identifiers.org/combine.specifications/sed-ml"}; + + return pFormat == SEDML_FORMAT; +} + +} // namespace + CombineArchive::Impl::Impl(const FilePtr &pFile, libcombine::CombineArchive *pArchive, UnsignedChars &&pArchiveContents) : mArchiveContents(std::move(pArchiveContents)) , mArchive(pArchive) @@ -37,6 +59,9 @@ CombineArchive::Impl::Impl(const FilePtr &pFile, libcombine::CombineArchive *pAr mFiles.reserve(fileCount); mFileNames.reserve(fileCount); + FilePtr cellmlFile; + FilePtr sedmlFile; + for (int i {0}; i < mArchive->getNumEntries(); ++i) { const auto *entry {mArchive->getEntry(i)}; auto location {entry->getLocation()}; @@ -50,6 +75,50 @@ CombineArchive::Impl::Impl(const FilePtr &pFile, libcombine::CombineArchive *pAr if (entry->getMaster()) { mMasterFile = file; } + + // Determine the type of the file using its contents or, if they are not recognised (e.g., malformed XML), using + // the format specified in the manifest. This means that a broken SED-ML file is still considered to be a SED-ML + // file and, as such, cannot be silently ignored in favour of a CellML file. + + auto fileType {file->type()}; + + if (fileType == File::Type::UNKNOWN_FILE) { + const auto &format {entry->getFormat()}; + + if (isCellmlFormat(format)) { + fileType = File::Type::CELLML_FILE; + } else if (isSedmlFormat(format)) { + fileType = File::Type::SEDML_FILE; + } + } + + if (fileType == File::Type::CELLML_FILE) { + if (mCellmlFileCount == 0) { + cellmlFile = file; + } + + ++mCellmlFileCount; + } else if (fileType == File::Type::SEDML_FILE) { + if (mSedmlFileCount == 0) { + sedmlFile = file; + } + + ++mSedmlFileCount; + } + } + + // Determine the master file, if none was specified. + // Note: the master attribute is optional, so if no master file was specified then we consider the SED-ML file to be + // the master file, if there is only one SED-ML file, or the CellML file to be the master file, if there is no + // SED-ML file and only one CellML file. Otherwise, it is up to the user (or a tool like Web OpenCOR) to decide + // which file should be used. + + if (mMasterFile == nullptr) { + if (mSedmlFileCount == 1) { + mMasterFile = sedmlFile; + } else if ((mSedmlFileCount == 0) && (mCellmlFileCount == 1)) { + mMasterFile = cellmlFile; + } } } @@ -63,6 +132,16 @@ const FilePtr &CombineArchive::Impl::masterFile() const return mMasterFile; } +size_t CombineArchive::Impl::cellmlFileCount() const +{ + return mCellmlFileCount; +} + +size_t CombineArchive::Impl::sedmlFileCount() const +{ + return mSedmlFileCount; +} + bool CombineArchive::Impl::hasFiles() const { return mFiles.empty(); @@ -176,6 +255,16 @@ const FilePtr &CombineArchive::masterFile() const return pimpl()->masterFile(); } +size_t CombineArchive::cellmlFileCount() const +{ + return pimpl()->cellmlFileCount(); +} + +size_t CombineArchive::sedmlFileCount() const +{ + return pimpl()->sedmlFileCount(); +} + bool CombineArchive::hasFiles() const { return !pimpl()->hasFiles(); diff --git a/src/support/combine/combinearchive.h b/src/support/combine/combinearchive.h index 7c1cc09c1..6e18485ae 100644 --- a/src/support/combine/combinearchive.h +++ b/src/support/combine/combinearchive.h @@ -44,6 +44,8 @@ class LIBOPENCOR_UNIT_TESTING_EXPORT CombineArchive: public Logger static CombineArchivePtr create(const FilePtr &pFile); const FilePtr &masterFile() const; + size_t cellmlFileCount() const; + size_t sedmlFileCount() const; bool hasFiles() const; size_t fileCount() const; diff --git a/src/support/combine/combinearchive_p.h b/src/support/combine/combinearchive_p.h index e9ff1cb6b..520eae970 100644 --- a/src/support/combine/combinearchive_p.h +++ b/src/support/combine/combinearchive_p.h @@ -32,11 +32,15 @@ class CombineArchive::Impl: public Logger::Impl std::vector mFiles; std::vector mFileNames; FilePtr mMasterFile; + size_t mCellmlFileCount {0}; + size_t mSedmlFileCount {0}; explicit Impl(const FilePtr &pFile, libcombine::CombineArchive *pArchive, UnsignedChars &&pArchiveContents); ~Impl() override; const FilePtr &masterFile() const; + size_t cellmlFileCount() const; + size_t sedmlFileCount() const; bool hasFiles() const; size_t fileCount() const; const Strings &fileNames() const; diff --git a/tests/api/sed/basictests.cpp b/tests/api/sed/basictests.cpp index 6bbd9d87e..f109aeed3 100644 --- a/tests/api/sed/basictests.cpp +++ b/tests/api/sed/basictests.cpp @@ -107,13 +107,79 @@ TEST(BasicSedTest, combineArchiveWithNoManifestFile) EXPECT_EQ_ISSUES(document, EXPECTED_ISSUES); } -TEST(BasicSedTest, combineArchiveWithNoMasterFile) +TEST(BasicSedTest, combineArchiveWithNoMasterFileAndOneCellmlFile) +{ + auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("api/sed/no_master_file_with_one_cellml_file.omex"))}; + auto document {libOpenCOR::SedDocument::create(file)}; + + EXPECT_FALSE(document->hasIssues()); + EXPECT_EQ(std::dynamic_pointer_cast(document->simulations()[0])->outputEndTime(), 1000.0); + + auto instance {document->instantiate()}; + + instance->run(); + + EXPECT_FALSE(instance->hasIssues()); +} + +TEST(BasicSedTest, combineArchiveWithNoMasterFileAndOneUnknownCellmlFile) { static const libOpenCOR::ExpectedIssues EXPECTED_ISSUES {{ - {libOpenCOR::Issue::Type::ERROR, "A simulation experiment description cannot be created using a COMBINE archive with no master file."}, + {libOpenCOR::Issue::Type::ERROR, "A simulation experiment description cannot be created using a COMBINE archive with an unknown master file (only CellML and SED-ML master files are supported)."}, + }}; + + auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("api/sed/no_master_file_with_one_unknown_cellml_file.omex"))}; + auto document {libOpenCOR::SedDocument::create(file)}; + + EXPECT_EQ_ISSUES(document, EXPECTED_ISSUES); +} + +TEST(BasicSedTest, combineArchiveWithNoMasterFileAndSeveralCellmlFiles) +{ + static const libOpenCOR::ExpectedIssues EXPECTED_ISSUES {{ + {libOpenCOR::Issue::Type::ERROR, "A simulation experiment description cannot be created using a COMBINE archive with no master file, no SED-ML file, and several CellML files."}, + }}; + + auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("api/sed/no_master_file_with_several_cellml_files.omex"))}; + auto document {libOpenCOR::SedDocument::create(file)}; + + EXPECT_EQ_ISSUES(document, EXPECTED_ISSUES); +} + +TEST(BasicSedTest, combineArchiveWithNoMasterFileAndOneSedmlFile) +{ + auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("api/sed/no_master_file_with_one_sedml_file.omex"))}; + auto document {libOpenCOR::SedDocument::create(file)}; + + EXPECT_FALSE(document->hasIssues()); + EXPECT_EQ(std::dynamic_pointer_cast(document->simulations()[0])->outputEndTime(), 50.0); + + auto instance {document->instantiate()}; + + instance->run(); + + EXPECT_FALSE(instance->hasIssues()); +} + +TEST(BasicSedTest, combineArchiveWithNoMasterFileAndOneUnknownSedmlFile) +{ + static const libOpenCOR::ExpectedIssues EXPECTED_ISSUES {{ + {libOpenCOR::Issue::Type::ERROR, "A simulation experiment description cannot be created using a COMBINE archive with an unknown master file (only CellML and SED-ML master files are supported)."}, + }}; + + auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("api/sed/no_master_file_with_one_unknown_sedml_file.omex"))}; + auto document {libOpenCOR::SedDocument::create(file)}; + + EXPECT_EQ_ISSUES(document, EXPECTED_ISSUES); +} + +TEST(BasicSedTest, combineArchiveWithNoMasterFileAndSeveralSedmlFiles) +{ + static const libOpenCOR::ExpectedIssues EXPECTED_ISSUES {{ + {libOpenCOR::Issue::Type::ERROR, "A simulation experiment description cannot be created using a COMBINE archive with no master file and several SED-ML files."}, }}; - auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("api/sed/no_master_file.omex"))}; + auto file {libOpenCOR::File::create(libOpenCOR::resourcePath("api/sed/no_master_file_with_several_sedml_files.omex"))}; auto document {libOpenCOR::SedDocument::create(file)}; EXPECT_EQ_ISSUES(document, EXPECTED_ISSUES); diff --git a/tests/bindings/javascript/sed.basic.test.js b/tests/bindings/javascript/sed.basic.test.js index 69818b174..bc3a1bbb1 100644 --- a/tests/bindings/javascript/sed.basic.test.js +++ b/tests/bindings/javascript/sed.basic.test.js @@ -129,8 +129,25 @@ test.describe('Sed basic tests', () => { ]); }); - test('COMBINE archive with no master file', () => { - const file = new loc.File(utils.resourcePath('api/sed/no_master_file.omex')); + test('COMBINE archive with no master file and one CellML file', () => { + const file = new loc.File(utils.resourcePath('api/sed/no_master_file_with_one_cellml_file.omex')); + + file.setContents(utils.fileContents(file.path)); + + const document = new loc.SedDocument(file); + + assert.strictEqual(document.hasIssues, false); + assert.strictEqual(document.simulations.get(0).outputEndTime, 1000); + + const instance = document.instantiate(); + + instance.run(); + + assert.strictEqual(instance.hasIssues, false); + }); + + test('COMBINE archive with no master file and one unknown CellML file', () => { + const file = new loc.File(utils.resourcePath('api/sed/no_master_file_with_one_unknown_cellml_file.omex')); file.setContents(utils.fileContents(file.path)); @@ -139,7 +156,69 @@ test.describe('Sed basic tests', () => { assertIssues(loc, document, [ [ loc.Issue.Type.ERROR, - 'A simulation experiment description cannot be created using a COMBINE archive with no master file.' + 'A simulation experiment description cannot be created using a COMBINE archive with an unknown master file (only CellML and SED-ML master files are supported).' + ] + ]); + }); + + test('COMBINE archive with no master file and several CellML files', () => { + const file = new loc.File(utils.resourcePath('api/sed/no_master_file_with_several_cellml_files.omex')); + + file.setContents(utils.fileContents(file.path)); + + const document = new loc.SedDocument(file); + + assertIssues(loc, document, [ + [ + loc.Issue.Type.ERROR, + 'A simulation experiment description cannot be created using a COMBINE archive with no master file, no SED-ML file, and several CellML files.' + ] + ]); + }); + + test('COMBINE archive with no master file and one SED-ML file', () => { + const file = new loc.File(utils.resourcePath('api/sed/no_master_file_with_one_sedml_file.omex')); + + file.setContents(utils.fileContents(file.path)); + + const document = new loc.SedDocument(file); + + assert.strictEqual(document.hasIssues, false); + assert.strictEqual(document.simulations.get(0).outputEndTime, 50); + + const instance = document.instantiate(); + + instance.run(); + + assert.strictEqual(instance.hasIssues, false); + }); + + test('COMBINE archive with no master file and one unknown SED-ML file', () => { + const file = new loc.File(utils.resourcePath('api/sed/no_master_file_with_one_unknown_sedml_file.omex')); + + file.setContents(utils.fileContents(file.path)); + + const document = new loc.SedDocument(file); + + assertIssues(loc, document, [ + [ + loc.Issue.Type.ERROR, + 'A simulation experiment description cannot be created using a COMBINE archive with an unknown master file (only CellML and SED-ML master files are supported).' + ] + ]); + }); + + test('COMBINE archive with no master file and several SED-ML files', () => { + const file = new loc.File(utils.resourcePath('api/sed/no_master_file_with_several_sedml_files.omex')); + + file.setContents(utils.fileContents(file.path)); + + const document = new loc.SedDocument(file); + + assertIssues(loc, document, [ + [ + loc.Issue.Type.ERROR, + 'A simulation experiment description cannot be created using a COMBINE archive with no master file and several SED-ML files.' ] ]); }); diff --git a/tests/bindings/python/test_sed_basic.py b/tests/bindings/python/test_sed_basic.py index 38a4b6c2b..b5606d24b 100644 --- a/tests/bindings/python/test_sed_basic.py +++ b/tests/bindings/python/test_sed_basic.py @@ -109,15 +109,97 @@ def test_combine_archive_with_no_manifest_file(): assert_issues(document, expected_issues) -def test_combine_archive_with_no_master_file(): +def test_combine_archive_with_no_master_file_and_one_cellml_file(): + file = loc.File( + utils.resource_path("api/sed/no_master_file_with_one_cellml_file.omex") + ) + document = loc.SedDocument(file) + + assert not document.has_issues + assert document.simulations[0].output_end_time == 1000.0 + + instance = document.instantiate() + + instance.run() + + assert not instance.has_issues + + +def test_combine_archive_with_no_master_file_and_one_unknown_cellml_file(): expected_issues = [ [ loc.Issue.Type.Error, - "A simulation experiment description cannot be created using a COMBINE archive with no master file.", + "A simulation experiment description cannot be created using a COMBINE archive with an unknown master file (only CellML and SED-ML master files are supported).", + ], + ] + + file = loc.File( + utils.resource_path("api/sed/no_master_file_with_one_unknown_cellml_file.omex") + ) + document = loc.SedDocument(file) + + assert_issues(document, expected_issues) + + +def test_combine_archive_with_no_master_file_and_several_cellml_files(): + expected_issues = [ + [ + loc.Issue.Type.Error, + "A simulation experiment description cannot be created using a COMBINE archive with no master file, no SED-ML file, and several CellML files.", + ], + ] + + file = loc.File( + utils.resource_path("api/sed/no_master_file_with_several_cellml_files.omex") + ) + document = loc.SedDocument(file) + + assert_issues(document, expected_issues) + + +def test_combine_archive_with_no_master_file_and_one_sedml_file(): + file = loc.File( + utils.resource_path("api/sed/no_master_file_with_one_sedml_file.omex") + ) + document = loc.SedDocument(file) + + assert not document.has_issues + assert document.simulations[0].output_end_time == 50.0 + + instance = document.instantiate() + + instance.run() + + assert not instance.has_issues + + +def test_combine_archive_with_no_master_file_and_one_unknown_sedml_file(): + expected_issues = [ + [ + loc.Issue.Type.Error, + "A simulation experiment description cannot be created using a COMBINE archive with an unknown master file (only CellML and SED-ML master files are supported).", + ], + ] + + file = loc.File( + utils.resource_path("api/sed/no_master_file_with_one_unknown_sedml_file.omex") + ) + document = loc.SedDocument(file) + + assert_issues(document, expected_issues) + + +def test_combine_archive_with_no_master_file_and_several_sedml_files(): + expected_issues = [ + [ + loc.Issue.Type.Error, + "A simulation experiment description cannot be created using a COMBINE archive with no master file and several SED-ML files.", ], ] - file = loc.File(utils.resource_path("api/sed/no_master_file.omex")) + file = loc.File( + utils.resource_path("api/sed/no_master_file_with_several_sedml_files.omex") + ) document = loc.SedDocument(file) assert_issues(document, expected_issues) diff --git a/tests/res/api/sed/no_master_file.omex b/tests/res/api/sed/no_master_file.omex deleted file mode 100644 index 33efbd231facd6a2c79a285e0aaf71854a692e52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2989 zcmbW32{0RI8^?nn+8}NdRH9TIb(U(Wqw0u=yN*WPao<-_w#3mmN(4X;D4U zqVD@D7A;!GQtQ?uy2*a)TeIJ{Gv9vCJMYXp^UnO|eV_j`^Z%Kk7=YXW5Ouky?pgx= zDAWl6fE@q>IAguNeZ1`zWd3!yYQYL%9u<7%GV;OQRi3&-!cGF{fcta+096D4n6Vu2 z!^v?seYJvwzB^YTGdT#D4IQ2iwL4+qGb{|QD7!^5C{9LA%_oq$VhH;$H*Rj!*&8gh z$8h?kRX~I80yF#t!|)$ow0LI#H~gy8Odsj%$$wxJITgz-c1X0s=S3PT*yJ5x;N2Gh z=#jV!B5_25m4N>taK!)HAhv&?zzej}nPHq8bS*I9-tq}^yv=QiNOmc(BJQH~!ECA{ z93v^G^HU!F!Lz-XL>%@^!lE;KmrJTC1n$y;qO`76R^ls4QI(z^%%Hkm!);ggcpOJ zW47QVSVEU*`k1(G71Os?^53=YE`rwx3X$+6_s< zx-w#xl<7Xc6MFi(7Jn-!-fJSF;b*MG4B1(ux_oGQ5qmaVzp-+q&D_|$zIcuKR-?uZ zFSDK#-_0|grO7m$_8qeS+Pda9^nNu)&ka0vE8vDa!`ykra3$cX@4eI}fC-8j6kzqd zHJ54@&0dp#8;_1%u{bh5=sWggdalOHB3Ou9lz8Zt}IYK%;<=WJkb^V zf>R}3SaNI81T=z%&#XUV)3GV;ttYMl1X&c%9=s7;)|CqPV_q_tt)38`G7zZxX+auC zzH_s*jY7wlOB|gdIAyM*XgpM@nWz&-28Pj1Av+9>l^c>*Eq5oZ~vJ}$TD zsCkBt%ZIM%c9=j{=>+BSgS6 zT6U|^n|Z9xq%4>bdGXNK?W_AIcV8V-jBAS(=#RJQp>O|92$JeBf2%Pjvm9z=x~vjt4$^j;@*t5F&m*HpNvZXi1nOb zD0Y46wbmMPhoWF+zO!F;eMDVpbI6QKqi3|O1JxHXvBN&0MBy7(qMW{g{yN&Ghw2-7 zY>1AG9$ma^nKxT|FxNGmIG^a7D$!7l?ak=8^MdX&g0%GJM9)%6KYRg{P>DMAJhCg} ziZ+3RRQp)ReKc>nw1x6s5z4WVz%`Df)U|MBXgiY~R@{Bq85d>@Hikd3qb46pBg{m@53?Bb! z2Hp*eREfM-TH?Xh-XK3ngeMwV`YC=!GK`yeB>8lJRNwe&ahPOvN$RyVyFkvl&#m>m zvWv=Y9ITj2$?FYKndcv$Ih2+qm6c!FBRd;L!8Sq(H#g`_O%Fg< zym?T`D~F}T;ddu@r0nIB5bIL}r$wQ{_}g1slnYP%4btQi?CSMgV_jE!%bK$Jv%+d~ zenKM6-?pniv|AS-5^ONDtO?xhSq6nPA2+P zkJ~LsZm9OLR$1Ti(W>;@4+D7FOKOh6{$c z|^eA%-rk4=WXTv7S-UHz%oo^#SXi#A)FC3)9?Mbr3t z%l)fMBL%aP>B+7AI+q`gp<=LvOqT!C zk2}ISjW(;1#hxHK*w3evb2pcz2e^2}Pe{abn>Mb9&CHD4cEp20SYgrll zzwJ)t%70LnvjdN_n}d_XuV_QdU)e43Ceic+eg<)f09}9(B=$gnA5WhOhijRf(eI## z@oJjC#zRO3{R3ktX+$S-tOqGABXQOUjYLW#ud;!dBvPVs(wQEHeEdeF8~U9RpuIb> zw$vxmo@s)jqvvM$6A+IR1Pu_6swjqb`iHo9T$~0W$A^gqAxBjd^O^3L`2U0?$F*tW zc-$4*I386|3T$5xq4JZF?K9~|JP#^6eAN&2uNLF)Xxi3i46ew4Wp*z A-T(jq diff --git a/tests/res/api/sed/no_master_file_with_one_cellml_file.omex b/tests/res/api/sed/no_master_file_with_one_cellml_file.omex new file mode 100644 index 0000000000000000000000000000000000000000..a575d6136ac3d0d49c45aef225a0074762bd25fb GIT binary patch literal 813 zcmWIWW@Zs#U|`^2aO&}hEezj%cn%{2gEtog13yqSIW;FIHz(dm55l+`c0Rw!KxE(V zaDlQ1Cad&rUSPOacIkFm99Ky0C3&7zE9(DxE$#Nc^{8$7$;Jua_r9-wzct?L@zF-1 z{Qgx^#nV_O7sym}Jl%63XEMwC{Pq2T8$8?%pRy{+HH)bJn-tzLx5nQna_*04#Z#6H zzL#4pm0BiE2$wGS$8x#g&xO=;=ND#kygV`4e@<~!QQM3cT)`cY?V2_G+C0WhRuMn$ z^(87sYZ$auC)i#&raTSCdI7#fY-7W*Rv`g@*ZtV_3X1x*6OosRLnXV z7SVV;o7p{arp!$qO^f3zI~r?d-#p-#zmnb9lx^G1m|%{b=eFkZM7mWTy|~&r`k~0? zxxVq&EpB}+?Mh8!jec&%`0Rn~jzgO|>Wf>muUBoqld5@lZLu717k%6S*#e;9a0cz`)ea=@C*V)@Y?EH1QRKSVndvnb6wbM*> zy$id&SKp7D^@=&bn~_O`8FvZ=`V9;kfF8w2ujrc5Lj|IhfuVu%Bv3nY7zKEdb7h3FX$jU{$oz0%|w2VuhTTP-y3n;`|s-B z?mS^S_So_7q028TV3XjOWtECtNOav{C^Z9;5rK79h;|=I0!pQ?i}cRPQxV81;AWbR zNz5T7Hql$-NOSToZSnil1PwXgh0IwTOw6M%Aa2!08YWV}nCkh6hj=t4-$8NM0g`6A zosyz?PX#1P}~umxKEge5Q(7-Ft7j61s@k4okRZ)7VzPv*}RZO%ilY#L2BN@^(^ zIXiw?3o&O{Rc)(7=JE zN8)JL!tPoo51=5bdvNoG!n~z=6iH;xX0l=gHfFP@{N9WPg?lBYxP>nupUWKHA!74J zPst-d@NyB;w>FE&)j{~za35SqY8`820^>WR+c!ys0XRQ$7?2BR+r$p`^cJ<(HMytPjbjx^c1sd`nLnVfQ zWYKM+Tc}x1)tQ2t2EwaS{w@@-=;6`Fqo>uhiccJ;Z)(Fk1^s;wWtCQ6 zF5zAgCeYH{`3ig0A{$F3|CE}_Ym^s5Eh4tK*at%zH;Mk!Glf1+19;65SNJ*(j-Ni4 zoE+$zrz8f=V#BNsGl_vt`NcP-|on3Qjy@(kQ^)7bb@q9ZF(52IpaBN+n;K|8@=PX4Vt?WWPZ=~l7M?3(Rt9G5rk}n&D!B=6MEn#WA z(wuJJ_Qz&-%En}I?OtG&eecq*8V|EH87ls!NE~cYbYe*5T*@*5e?ly8Hz6<_^$D>8 z%`CMh5Sld?V0hYbj-AwQB*R6&tg;;QSl3Q-ro6Cff@I|nhqE=dn9}Cpv*pZEYR{Rf8wapU+D3yd{qVi|)#p~^NCz-WqXk!G9LKCShN%wVk6-m0 z3%N?El@kIgX^E<>hA|kR=IemV(?FHfk_X1T-8M!w6qw0Q;I07(4XA%0oKgwsnB z1trt)%^4E;=1`gDXdhrQa$r2tZRg3L0T{TfaFmy)W|vKnv|GyIk_=FzWUBosKlu%c`*w>R?^UQJ2lhbPohU z4TZkPq1}44kwB*q4^U^lBsy%vRQ^&4dzjq5BBxl4Q1s$D%!nlmW)@v_Dyxm9*=}mw zls|Y45sGt`N#Q>8?O|1Qd+Vn**%XzG_(#1JYo!t~=_X!k>Bb2N{|+aYgpmM2E01i2 zQLw5(TPZCe_Qq(FEvZyQS5LTh^o%2@zPBa_>}na++JD!A@K&apgL5A-7+a4>A^Hdr z=JW;LzuEinc@^yvIXPe?y6$hH>IB!zq@uKce3p1H%DVpULW`rFV{IW%I(a48ES;0Qg-(*Xd2 N+gZI`dqF>6{{WgUQ{4an literal 0 HcmV?d00001 diff --git a/tests/res/api/sed/no_master_file_with_one_unknown_cellml_file.omex b/tests/res/api/sed/no_master_file_with_one_unknown_cellml_file.omex new file mode 100644 index 0000000000000000000000000000000000000000..e22caa02db8552dfb36ec49563b795e59be795f5 GIT binary patch literal 424 zcmWIWW@Zs#U|`^2=$`Bm8~=5^uL6)K2gKY!oR*oBs+XLalargneeS$(sK!}sFW+20#M8;4Bvft4kH7DH_#A%pgGB@IXSsG@kV+O#@(>< z`Ar5Q`+kQDlszz6rFZiJ!@aUgx69(VLUJ$3^Q>A?|JQ42xA(0_ZPQOSPWZm}ef9gT z@m`OQHVWnUuaYXB#xl7;rlRBNo&!0PS>ETb?+@JI;cobpRY|T{MD^dK@Q%4P{zj2= ze?%*uvSjeR++wNJGHF7%biqHC%LRWfq@Fv!Fq`A$iOK$Rild6!X1w4E?ucyHtl`(@ zF>bPo_;IfKYgnhw7oI`hSKrzsZthKqEqJ(zxW@}@H>X59z8maVv+Rr!$jXj`ghpMA1c zpIxJ3*2%Dl#_QS4?uj#HZt`ea9ADYdSTp2~HJ2ySt@7x_ z)y~lmMK;g%jlXVj>uYIOY8q?wb2G+g4`g>7+SE~B+?sv8YWtm3&AV%p?boloc>77J zZl%Z0)h~|(`v-4Z`i8xJr+s;L&}pv!!IO9O>up!gyBPob@SFUL4_`I9r}NyZFDy4? zzHe?^Y0B~{Z${rfw1`~CwDI>YU<4ilMkWuC&P~kAOiL{;(W}VKnLBYK-ysD7hwpW) ziHTY(CL1ojd*@Dxna6rBrt)P1i|gN;mKMAWQY(76ce;%4cfY_r31=EFtXg|dgYm^m z{)+9ditMgcs}>dB*zLg)R;#dj-C8NH4|bM3%ukeVG~PVru6e%Ma3Q-+B8SdZlY70( zOSn0#><@mvvHQ^495v^;bzC>Yn*1Mga$i=u^yiG1?oo&2qSW5_S+4H|l$0+l-(hy3 z;Ov*8L(V;#aphI}*1SEl<=Ot3QN2IoYolu!1H2iTM3`|Wd!R?bpaJM>Gz&7J26TWoD;r3b83@+{>90U}1_l7V4R@OW literal 0 HcmV?d00001 diff --git a/tests/res/api/sed/no_master_file_with_several_cellml_files.omex b/tests/res/api/sed/no_master_file_with_several_cellml_files.omex new file mode 100644 index 0000000000000000000000000000000000000000..c5f8fad13da9013da693aac9387f9124d2a3d31c GIT binary patch literal 1346 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 P0BEBwZnSF~;{fnaL5)|J literal 0 HcmV?d00001