diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg index 1a4e5085..4e32f161 100644 --- a/.github/badges/tests.svg +++ b/.github/badges/tests.svg @@ -1 +1 @@ -teststests558 passed558 passed \ No newline at end of file +teststests568 passed568 passed \ No newline at end of file diff --git a/code/+openminds/@Collection/Collection.m b/code/+openminds/@Collection/Collection.m index 4e9a2f25..e7d3616a 100644 --- a/code/+openminds/@Collection/Collection.m +++ b/code/+openminds/@Collection/Collection.m @@ -36,6 +36,7 @@ % Todo: Validation. % - Linked subject states should have same subject +% - Add method to update links for specified instances % Need mechanism to check if embedded nodes are added to the collection @@ -51,7 +52,7 @@ NumNodes NumTypes end - + properties (SetAccess = protected) % Nodes - Dictionary storing instances as values with identifiers % as keys @@ -66,11 +67,11 @@ properties (SetAccess = protected) LinkResolver - + % MetadataStore - Optional metadata store for saving/loading MetadataStore openminds.interface.MetadataStore = openminds.internal.FileMetadataStore.empty end - + methods % Constructor function obj = Collection(instance, options) % Create an instance of an openMINDS collection @@ -97,7 +98,7 @@ % creates a collection with the specified metadata store. If no % instances are provided, the collection will automatically load % instances from the store. - + % collection = openminds.Collection(..., NameA, ValueA, ... ) % also specifies optional name value pairs when creating the % collection. @@ -106,7 +107,7 @@ % - Name : A name for the collection % - Description : A description of the collection % - MetadataStore : A metadata store for saving/loading instances - + arguments (Repeating) instance % openminds.abstract.Schema end @@ -126,13 +127,13 @@ obj.Nodes = containers.Map; obj.TypeMap = containers.Map; end - + obj.initializeFromInstances(instance) obj.Name = options.Name; obj.Description = options.Description; obj.MetadataStore = options.MetadataStore; - + % Auto-load from MetadataStore if provided and no instances given if isempty(instance) && ~isempty(obj.MetadataStore) obj.load(); @@ -148,7 +149,7 @@ numNodes = length(obj.Nodes); end end - + function numTypes = get.NumTypes(obj) if isa(obj.TypeMap, 'dictionary') numTypes = numEntries(obj.TypeMap); @@ -190,16 +191,19 @@ function add(obj, instance, options) end arguments options.AddSubNodesOnly = false; + options.AbortIfNodeExists = true; end for i = 1:numel(instance) thisInstance = instance{i}; for j = 1:numel(thisInstance) % If thisInstance is an array - obj.addNode(thisInstance(j), "AddSubNodesOnly", options.AddSubNodesOnly); + obj.addNode(thisInstance(j), ... + "AddSubNodesOnly", options.AddSubNodesOnly, ... + "AbortIfNodeExists", options.AbortIfNodeExists); end end end - + function tf = contains(obj, instance) % Todo:work for arrays tf = false; @@ -210,10 +214,10 @@ function add(obj, instance, options) end end end - + function remove(obj, instance) % remove - Remove metadata instance from the collection - + if isstring(instance) || ischar(instance) instanceId = instance; elseif openminds.utility.isInstance(instance) @@ -250,11 +254,11 @@ function remove(obj, instance) instance = instance{1}; end end - + function instances = getAll(obj) % getAll - Get all instances of collection instances = obj.Nodes.values(); - + % For older MATLAB releases, the instances might be nested a % cell array, need to unnest if that's the case: if iscell(instances{1}) @@ -269,11 +273,11 @@ function remove(obj, instance) end tf = false; - + if obj.NumNodes == 0 return end - + typeKeys = obj.TypeMap.keys; tf = any( endsWith(typeKeys, "."+type) ); %i.e ".Person" end @@ -293,10 +297,10 @@ function remove(obj, instance) if obj.NumNodes == 0 return end - + instanceKeys = obj.getInstanceKeysForType(type); if isempty(instanceKeys); return; end - + if isa(obj.Nodes, 'dictionary') instances = obj.Nodes(instanceKeys); else @@ -327,6 +331,10 @@ function updateLinks(obj) allInstances = [allInstances{:}]; end + if iscolumn(allInstances) + allInstances = reshape(allInstances, 1, []); + end + for instance = allInstances obj.addNode(instance{1}, ... 'AddSubNodesOnly', true, ... @@ -360,14 +368,14 @@ function updateLinks(obj) % ------ % % outputPaths (cell): A list of the file paths created. - + arguments obj openminds.Collection savePath (1,1) string = "" options.MetadataStore openminds.interface.MetadataStore = openminds.internal.FileMetadataStore.empty % options.SaveFormat = "jsonld" Implement if more formats are supported end - + % Update links before saving obj.updateLinks() instances = obj.getAll(); @@ -375,7 +383,7 @@ function updateLinks(obj) if savePath ~= "" tempStore = openminds.internal.store.createTemporaryStore(savePath); outputPaths = tempStore.save(instances); - + elseif ~isempty(options.MetadataStore) outputPaths = obj.MetadataStore.save(instances); @@ -387,7 +395,7 @@ function updateLinks(obj) error('openminds:Collection:NoSavePath', ... 'Either provide savePath or configure a MetadataStore'); end - + if ~nargout clear outputPaths end @@ -436,7 +444,7 @@ function load(obj, loadPath, options) error('openminds:Collection:PathNotFound', 'Path not found: %s', loadPath); end end - + for i = 1:numel(instances) if openminds.utility.isInstance(instances{i}) obj.addNode(instances{i}); @@ -469,13 +477,13 @@ function load(obj, loadPath, options) % -------- % collection : openminds.Collection % A new collection loaded with instances from the store - + arguments metadataStore (1,1) openminds.interface.MetadataStore options.Name (1,1) string = "" options.Description (1,1) string = "" end - + % Create collection with the metadata store collection = openminds.Collection('MetadataStore', metadataStore, ... 'Name', options.Name, 'Description', options.Description); @@ -493,14 +501,18 @@ function load(obj, loadPath, options) end wasAdded = false; - + if isempty(instance.id) instance.id = obj.getBlankNodeIdentifier(); end - % Do not add openminds controlled term instances - if startsWith(instance.id, "https://openminds.ebrains.eu/instances/") - return + % Do not add openminds controlled term instances if disabled in + % preferences + if startsWith(instance.id, "https://openminds.ebrains.eu/instances/") ... + || startsWith(instance.id, "https://openminds.om-i.org/instances/") + if ~openminds.getpref('AddControlledInstanceToCollection') + return + end end if obj.NumNodes > 0 @@ -511,7 +523,10 @@ function load(obj, loadPath, options) end end end - + + % Add subnodes first + obj.addSubNodes(instance) + if ~options.AddSubNodesOnly obj.Nodes(instance.id) = {instance}; wasAdded = true; @@ -529,13 +544,12 @@ function load(obj, loadPath, options) obj.TypeMap(instanceType) = {string(instance.id)}; end end - - obj.addSubNodes(instance) + if ~nargout clear wasAdded end end - + % Add sub node instances (linked types) to the Node container. function addSubNodes(obj, instance) % Add links. @@ -552,7 +566,7 @@ function addSubNodes(obj, instance) obj.addNode(embeddedInstances{i}, 'AddSubNodesOnly', true); end end - + function identifier = getBlankNodeIdentifier(obj) fmt = '_:%06d'; identifier = length(obj) + 1; @@ -567,19 +581,19 @@ function initializeFromInstances(obj, instance) isFilePath = @(x) (ischar(x) || isstring(x)) && isfile(x); isFolderPath = @(x) (ischar(x) || isstring(x)) && isfolder(x); isMetadata = @(x) openminds.utility.isInstance(x); - + % Initialize from file(s) if all( cellfun(isFilePath, instance) ) obj.load(instance{:}) - + % Initialize from folder elseif all( cellfun(isFolderPath, instance) ) obj.load(instance{:}) - + % Initialize from instance(s) elseif all( cellfun(isMetadata, instance) ) obj.add(instance{:}); - + else ME = MException(... 'OPENMINDS_MATLAB:Collection:InvalidInstanceSpecification', ... @@ -597,7 +611,7 @@ function initializeFromInstances(obj, instance) if obj.NumTypes > 0 typeKeys = obj.TypeMap.keys; - + isMatch = strcmp(typeKeys, instanceType.ClassName); if any(isMatch) if isa(obj.TypeMap, 'dictionary') @@ -615,9 +629,9 @@ function initializeFromInstances(obj, instance) instanceKeys = {}; return end - + existingKeys = obj.Nodes.keys(); - + % Sanity check, make sure all keys exist in Nodes dictionary assert( all( ismember( instanceKeys, existingKeys ) ), ... 'TypeMap has too many keys' ) diff --git a/code/+openminds/getpref.m b/code/+openminds/getpref.m index adf216da..f4152a68 100644 --- a/code/+openminds/getpref.m +++ b/code/+openminds/getpref.m @@ -20,6 +20,7 @@ [ ... "PropertyDisplayMode", ... "DocLinkTarget", ... + "AddControlledInstanceToCollection", ... "" ... ])... } = "" diff --git a/code/+openminds/instanceFromIRI.m b/code/+openminds/instanceFromIRI.m index 83ef6d99..f2a82985 100644 --- a/code/+openminds/instanceFromIRI.m +++ b/code/+openminds/instanceFromIRI.m @@ -35,7 +35,7 @@ end [typeEnum, instanceName] = openminds.utility.parseInstanceIRI(IRI); - + if contains(typeEnum.ClassName, "controlledterms") instance = feval(typeEnum.ClassName, IRI); else diff --git a/code/+openminds/setpref.m b/code/+openminds/setpref.m index a046de66..da26292d 100644 --- a/code/+openminds/setpref.m +++ b/code/+openminds/setpref.m @@ -18,7 +18,7 @@ end pref = openminds.utility.Preferences.getSingleton; - + prefNames = fieldnames(prefValues); for i = 1:numel(prefNames) preferenceName = prefNames{i}; diff --git a/code/+openminds/startup.m b/code/+openminds/startup.m index 53af3747..8dbc387e 100644 --- a/code/+openminds/startup.m +++ b/code/+openminds/startup.m @@ -3,7 +3,7 @@ function startup(version) % % This function ensures that only one version of openMINDS schema classes % are on MATLAB's search path. - + arguments version (1,1) openminds.internal.utility.VersionNumber ... {openminds.mustBeValidVersion(version)} = "latest" @@ -14,7 +14,7 @@ function startup(version) % NB: Assumes this function is located in code/+openminds: codePath = fileparts( fileparts( mfilename('fullpath') ) ); addpath( fullfile(codePath, 'internal') ) - + % Run internal function that correctly configures the search path openminds.selectOpenMindsVersion(version) fprintf(['Added classes for version "%s" of the openMINDS metadata model ' ... diff --git a/code/+openminds/toolboxversion.m b/code/+openminds/toolboxversion.m index 2471e19f..17dc4647 100644 --- a/code/+openminds/toolboxversion.m +++ b/code/+openminds/toolboxversion.m @@ -5,7 +5,7 @@ contentsFile = fullfile(rootPath, 'Contents.m'); fileStr = fileread(contentsFile); - + % First try to get a version with a sub-patch version number matchedStr = regexp(fileStr, 'Version \d*\.\d*\.\d*.\d*(?= )', 'match'); diff --git a/code/internal/+openminds/+utility/Preferences.m b/code/internal/+openminds/+utility/Preferences.m index 8b6ad1c0..968fa0b3 100644 --- a/code/internal/+openminds/+utility/Preferences.m +++ b/code/internal/+openminds/+utility/Preferences.m @@ -10,12 +10,16 @@ % - "help" : MATLAB docstrings % - "online" : Online ReadTheDocs % documentation +% AddControlledInstanceToCollection (logical): Whether to add +% openMINDS controlled instances to a +% collection properties (SetObservable) PropertyDisplayMode (1,1) string ... {mustBeMember(PropertyDisplayMode, ["all", "non-empty"])} = "all" DocLinkTarget (1,1) string ... {mustBeMember(DocLinkTarget, ["help", "online"])} = "online" + AddControlledInstanceToCollection (1,1) logical = true end properties (Constant, Access = private)