Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.20261007.0
1.20261008.0
11 changes: 9 additions & 2 deletions src/sed/seddocument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()};

Expand Down
89 changes: 89 additions & 0 deletions src/support/combine/combinearchive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,30 @@ limitations under the License.
#include "combine/combinearchive.h"
#include "omex/CaContent.h"

#include <regex>

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)
Expand All @@ -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()};
Expand All @@ -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;
}
}
}

Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions src/support/combine/combinearchive.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/support/combine/combinearchive_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ class CombineArchive::Impl: public Logger::Impl
std::vector<FilePtr> mFiles;
std::vector<std::string> 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;
Expand Down
72 changes: 69 additions & 3 deletions tests/api/sed/basictests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<libOpenCOR::SedUniformTimeCourse>(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<libOpenCOR::SedUniformTimeCourse>(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);
Expand Down
85 changes: 82 additions & 3 deletions tests/bindings/javascript/sed.basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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.'
]
]);
});
Expand Down
Loading
Loading