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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/badges/code_issues.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion .github/badges/tests.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions code/+openminds/@Collection/Collection.m
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
properties (SetAccess = protected, Hidden)
TypeMap (1,1) dictionary
end

properties
LinkResolver
end

methods % Constructor
function obj = Collection(instance, options)
Expand Down Expand Up @@ -93,6 +97,7 @@
arguments
options.Name (1,1) string = ""
options.Description (1,1) string = ""
options.LinkResolver (1,:) = []
end

% Initialize nodes
Expand Down Expand Up @@ -130,6 +135,16 @@
function len = length(obj)
len = numEntries(obj.Nodes);
end

function tf = isKey(obj, identifier)
tf = false;

Check warning on line 140 in code/+openminds/@Collection/Collection.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/@Collection/Collection.m#L140

Added line #L140 was not covered by tests

if isConfigured(obj.Nodes)
if isKey(obj.Nodes, identifier)
tf = true;

Check warning on line 144 in code/+openminds/@Collection/Collection.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/@Collection/Collection.m#L142-L144

Added lines #L142 - L144 were not covered by tests
end
end
end

function add(obj, instance, options)
%add Add single or multiple instances to a collection.
Expand Down
2 changes: 1 addition & 1 deletion code/+openminds/@Collection/loadInstances.m
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
else
% Check if instance is a controlled instance
if startsWith(instanceId, "https://openminds.ebrains.eu/instances/")
resolvedInstances{j} = om.instance.getControlledInstance(instanceId);
resolvedInstances{j} = openminds.instanceFromIRI(instanceId);

Check warning on line 124 in code/+openminds/@Collection/loadInstances.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/@Collection/loadInstances.m#L124

Added line #L124 was not covered by tests
end
end
end
Expand Down
5 changes: 5 additions & 0 deletions code/+openminds/fromIdentifier.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function instance = fromIdentifier(atId)
[type, name] = openminds.utility.parseAtID(atId);
enumType = openminds.enum.Types(type);
instance = feval(enumType.ClassName, name);

Check warning on line 4 in code/+openminds/fromIdentifier.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/fromIdentifier.m#L2-L4

Added lines #L2 - L4 were not covered by tests
end
8 changes: 8 additions & 0 deletions code/+openminds/fromTypeName.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function instance = fromTypeName( typeNameURI, identifier )
arguments
typeNameURI
identifier = ''

Check warning on line 4 in code/+openminds/fromTypeName.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/fromTypeName.m#L4

Added line #L4 was not covered by tests
end
enumType = openminds.enum.Types.fromAtType(typeNameURI);
instance = feval(enumType.ClassName, 'id', identifier);

Check warning on line 7 in code/+openminds/fromTypeName.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/fromTypeName.m#L6-L7

Added lines #L6 - L7 were not covered by tests
end
32 changes: 21 additions & 11 deletions code/+openminds/getModelVersion.m
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
function versionNum = getModelVersion(outputType)

% Todo: save version number in prefs/singleton?

arguments
% outputType - char or VersionNumber. char is default to keep
% backwards compatibility. Todo: Deprecate char option
outputType (1,1) string ...
{mustBeMember(outputType, ["char", "VersionNumber"])} = "char"
end

typeFolder = fullfile(openminds.internal.rootpath, 'types/');
pathSplit = strsplit(path, pathsep);

matchedIdx = find(contains(pathSplit, typeFolder));
persistent lastTic cachedVersionNumber
if isempty(lastTic); lastTic = uint64(0); end

if numel(matchedIdx) > 1
warning('Multiple openMINDS model versions are present on the search path.');
if toc(lastTic) > 1
typeFolder = fullfile(openminds.internal.rootpath, 'types/');
pathSplit = strsplit(path, pathsep);

matchedIdx = find(contains(pathSplit, typeFolder));

if numel(matchedIdx) > 1
warning('Multiple openMINDS model versions are present on the search path.');

Check warning on line 22 in code/+openminds/getModelVersion.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/getModelVersion.m#L22

Added line #L22 was not covered by tests
end

versionName = strrep(pathSplit{matchedIdx(1)}, typeFolder, '');
cachedVersionNumber = openminds.internal.utility.VersionNumber(versionName);
cachedVersionNumber.Format = "vX.Y";
lastTic = tic();
end
versionNum = cachedVersionNumber;

versionNum = strrep(pathSplit{matchedIdx(1)}, typeFolder, '');

if outputType == "VersionNumber"
versionNum = openminds.internal.utility.VersionNumber(versionNum);
versionNum.Format = "vX.Y";
if outputType == "char"
versionNum = char(versionNum);
end
end
5 changes: 5 additions & 0 deletions code/+openminds/instanceFromIRI.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function instance = instanceFromIRI(iri)
[type, ~] = openminds.utility.parseAtID(iri);
typeEnum = openminds.enum.Types(type);
instance = feval(typeEnum.ClassName, iri);

Check warning on line 4 in code/+openminds/instanceFromIRI.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/instanceFromIRI.m#L2-L4

Added lines #L2 - L4 were not covered by tests
end
12 changes: 12 additions & 0 deletions code/+openminds/version.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function versionStr = version(version)
arguments
version (1,1) openminds.internal.utility.VersionNumber ...
{openminds.mustBeValidVersion(version)} = missing

Check warning on line 4 in code/+openminds/version.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/version.m#L4

Added line #L4 was not covered by tests
end
if ismissing(version)
versionStr = string(openminds.getModelVersion());

Check warning on line 7 in code/+openminds/version.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/version.m#L6-L7

Added lines #L6 - L7 were not covered by tests
else
openminds.selectOpenMindsVersion(version)
versionStr = string(version);

Check warning on line 10 in code/+openminds/version.m

View check run for this annotation

Codecov / codecov/patch

code/+openminds/version.m#L9-L10

Added lines #L9 - L10 were not covered by tests
end
end
31 changes: 24 additions & 7 deletions code/internal/+openminds/+abstract/ControlledTerm.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,21 @@
obj.deserializeFromName(varargin{1});
end
elseif nargin == 1 && isstruct( varargin{1} ) && isfield(varargin{1}, 'at_id')
obj.deserializeFromName(varargin{1}.at_id);
numInstances = numel(varargin{1});
if numInstances > 1
obj(numInstances) = feval(class(obj));

Check warning on line 67 in code/internal/+openminds/+abstract/ControlledTerm.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/ControlledTerm.m#L65-L67

Added lines #L65 - L67 were not covered by tests
end
for i = 1:numel(varargin{1})
obj(i).deserializeFromName(varargin{1}(i).at_id);

Check warning on line 70 in code/internal/+openminds/+abstract/ControlledTerm.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/ControlledTerm.m#L69-L70

Added lines #L69 - L70 were not covered by tests
end
else
[varargin, id] = obj.removeArg('id', varargin{:});
if ~isempty(id)
obj.id = id; % Assign provided id

Check warning on line 75 in code/internal/+openminds/+abstract/ControlledTerm.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/ControlledTerm.m#L73-L75

Added lines #L73 - L75 were not covered by tests
else
obj.id = obj.generateInstanceId();

Check warning on line 77 in code/internal/+openminds/+abstract/ControlledTerm.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/ControlledTerm.m#L77

Added line #L77 was not covered by tests
end
obj.assignPVPairs(varargin{:})
obj.id = obj.generateInstanceId();
end
end
end
Expand All @@ -87,13 +98,18 @@

import openminds.internal.getControlledInstance
import openminds.internal.utility.getSchemaName

instanceName = char(instanceName);
schemaName = getSchemaName(class(obj));

if openminds.utility.isSemanticInstanceName(instanceName)
[~, instanceName] = openminds.utility.parseAtID(instanceName);
if openminds.utility.isIRI(instanceName)
if openminds.utility.isSemanticInstanceName(instanceName)
[~, instanceName] = openminds.utility.parseAtID(instanceName);

Check warning on line 106 in code/internal/+openminds/+abstract/ControlledTerm.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/ControlledTerm.m#L105-L106

Added lines #L105 - L106 were not covered by tests
else
obj.id = instanceName;
return

Check warning on line 109 in code/internal/+openminds/+abstract/ControlledTerm.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/ControlledTerm.m#L108-L109

Added lines #L108 - L109 were not covered by tests
end
end

[instanceName, instanceNameOrig] = deal(instanceName);
if ~any(strcmp(obj.CONTROLLED_INSTANCES, instanceName))
% Try to make a valid name
Expand All @@ -112,7 +128,8 @@
return
end
else
error('No matching instances were found for name "%s"', instanceName)
warning('No matching instances were found for name "%s"', instanceName)
return

Check warning on line 132 in code/internal/+openminds/+abstract/ControlledTerm.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/ControlledTerm.m#L131-L132

Added lines #L131 - L132 were not covered by tests
% error('Deserialization from user instance is not implemented yet')
end
propNames = {'at_id', 'name', 'definition', 'description', 'interlexIdentifier', 'knowledgeSpaceLink', 'preferredOntologyIdentifier', 'synonym'};
Expand Down
31 changes: 23 additions & 8 deletions code/internal/+openminds/+abstract/Schema.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
classdef Schema < handle & openminds.internal.extern.uiw.mixin.AssignPVPairs & ...
openminds.internal.mixin.CustomInstanceDisplay & openminds.internal.mixin.StructAdapter
openminds.internal.mixin.CustomInstanceDisplay & ...
openminds.internal.mixin.StructAdapter
% Schema Abstract base class shared by all concrete Schema classes

% Todo:
Expand Down Expand Up @@ -29,7 +30,7 @@
LINKED_PROPERTIES struct %todo: name and classname, not openminds uri
EMBEDDED_PROPERTIES struct
end

events % Todo: Remove??
InstanceChanged
PropertyWithLinkedInstanceChanged
Expand Down Expand Up @@ -428,17 +429,27 @@
varargout = cell(1, numOutputs);
[varargout{:}] = builtin('subsref', obj, subs);
else
builtin('subsref', obj, subs)
obj = builtin('subsref', obj, subs);

Check warning on line 432 in code/internal/+openminds/+abstract/Schema.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/Schema.m#L432

Added line #L432 was not covered by tests

Check warning

Code scanning / Code Analyzer

Value assigned to variable might be unused. Warning

Value assigned to variable might be unused.
end
end
end

function n = numArgumentsFromSubscript(obj, s, indexingContext)
if (obj(1).isSubsForLinkedProperty(s) || obj(1).isSubsForEmbeddedProperty(s)) && numel(s) > 1

% if strcmp(s(1).type, '.') && strcmp(s(2).type, '()')
% %linkedTypeValues = builtin('subsref', obj, s(1:2));
% linkedTypeValues = obj.subsref(s(1:2));
%
% elseif strcmp(s(1).type, '.')
% linkedTypeValues = builtin('subsref', obj, s(1));
% end

linkedTypeValues = builtin('subsref', obj, s(1));
% if openminds.utility.isMixedInstance(linkedTypeValues)
% linkedTypeValues = {linkedTypeValues.Instance};
% end

if openminds.utility.isMixedInstance(linkedTypeValues)
linkedTypeValues = {linkedTypeValues.Instance};
end

if strcmp( s(2).type, '()' ) && iscell(linkedTypeValues)
s(2).type = '{}';
Expand Down Expand Up @@ -535,10 +546,14 @@
if isa(values, 'cell')
instanceType = cellfun(@(c) class(c), values, 'uni', false);
else
instanceType = class(values);
instanceType = string(class(values));

Check warning on line 549 in code/internal/+openminds/+abstract/Schema.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/Schema.m#L549

Added line #L549 was not covered by tests
end
if numel( unique(instanceType) ) == 1
outValues = [values{:}];
if iscell(values)
outValues = [values{:}];
else
outValues = values;

Check warning on line 555 in code/internal/+openminds/+abstract/Schema.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+abstract/Schema.m#L555

Added line #L555 was not covered by tests
end
else
outValues = feval(mixedTypeClassName, values);
end
Expand Down
11 changes: 10 additions & 1 deletion code/internal/+openminds/+internal/+abstract/LinkedCategory.m
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@
% resolved externally in order to put a real instance in place.
obj(i).Instance = struct;
obj(i).Instance.id = instance{i}.at_id;
elseif isstruct(instance{i}) && isfield(instance{i}, 'x_id')
% Variation of before
obj(i).Instance = struct;
obj(i).Instance.id = instance{i}.x_id;

Check warning on line 101 in code/internal/+openminds/+internal/+abstract/LinkedCategory.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+internal/+abstract/LinkedCategory.m#L100-L101

Added lines #L100 - L101 were not covered by tests
elseif isstruct(instance{i}) && isfield(instance{i}, 'at_type')
% Embedded type as structure
obj(i).Instance = openminds.fromTypeName(instance{i}.at_type);
obj(i).Instance = obj(i).Instance.fromStruct(instance{i});

Check warning on line 105 in code/internal/+openminds/+internal/+abstract/LinkedCategory.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+internal/+abstract/LinkedCategory.m#L104-L105

Added lines #L104 - L105 were not covered by tests

elseif isa(instance{i}, class(obj))
obj(i) = instance{i};
else
Expand Down Expand Up @@ -226,7 +235,7 @@
function displayScalarObject(obj)
% This should not happen...
disp(obj.Instance)
warning('Displaying scalar mixed type object')
warning('Displaying scalar mixed type object. This is a non-critical bug.')

Check warning on line 238 in code/internal/+openminds/+internal/+abstract/LinkedCategory.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+internal/+abstract/LinkedCategory.m#L238

Added line #L238 was not covered by tests
end

function displayNonScalarObject(obj)
Expand Down
Empty file.
3 changes: 2 additions & 1 deletion code/internal/+openminds/+internal/+utility/+json/encode.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

str = jsonencode(s, 'PrettyPrint', true);
% Todo: Use regexp to make sure we only replace json key names
str = strrep(str, '"x_', '"_');
%str = strrep(str, '"x_', '"_');
str = strrep(str, '"x_', '"@');
str = strrep(str, '"at_', '"@');
str = strrep(str, '[]', 'null');

Expand Down
11 changes: 7 additions & 4 deletions code/internal/+openminds/+internal/+utility/+string/type2class.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
function className = type2class(openmindsType)

typeSplit = strsplit(openmindsType, '/');

modelName = lower( typeSplit{end-1} );
schemaName = typeSplit{end};

className = sprintf('openminds.%s.%s', modelName, schemaName);

if startsWith(openmindsType, "https://openminds.om-i.org/types")
className = openminds.enum.Types(schemaName).ClassName;

Check warning on line 7 in code/internal/+openminds/+internal/+utility/+string/type2class.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+internal/+utility/+string/type2class.m#L6-L7

Added lines #L6 - L7 were not covered by tests
else
modelName = lower( typeSplit{end-1} );
className = sprintf('openminds.%s.%s', modelName, schemaName);

Check warning on line 10 in code/internal/+openminds/+internal/+utility/+string/type2class.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+internal/+utility/+string/type2class.m#L9-L10

Added lines #L9 - L10 were not covered by tests
end
end
46 changes: 46 additions & 0 deletions code/internal/+openminds/+utility/isIRI.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
function isValid = isIRI(value)
% isIRI Validates an Internationalized Resource Identifier (IRI)
% isValid = isIRI(value) returns true if the input string 'value'
% has the basic structure of an IRI as defined in RFC 3987 section 2.1,
% and false otherwise.
%
% This function uses a regular expression to check for:
% - A scheme, which must start with a letter and can include letters,
% digits, plus (+), hyphen (-), and period (.)
% - An optional authority preceded by "//" (which should not include
% the delimiters ?, or #)
% - A path (which is any sequence of characters except '?' and '#')
% - An optional query (preceded by '?')
% - An optional fragment (preceded by '#')
%
% Note: This validator provides a basic check and does not fully enforce
% all details of RFC 3987 (for example, the precise allowed Unicode
% ranges in each component).
%
% Example:
% validIRI = 'http://例え.テスト/path?query#fragment';
% isValid = openminds.utility.isIRI(validIRI);
%
% Author: ChatGPT o3-mini-high

% Regular expression pattern for a simplified IRI structure:
% - (?<scheme>[A-Za-z][A-Za-z0-9+\-.]*): --> scheme followed by ':'
% - (?://(?<authority>[^/?#]*))? --> optional "//" and authority
% - (?<path>[^?#]*) --> path (no '?' or '#')
% - (?:\?(?<query>[^#]*))? --> optional query starting with '?'
% - (?:#(?<fragment>.*))? --> optional fragment starting with '#'
%
% Note: Double backslashes (\\) are used in MATLAB string literals.
pattern = ['^(?<scheme>[A-Za-z][A-Za-z0-9+\\-.]*):', ...
'(?://(?<authority>[^/?#]*))?', ...
'(?<path>[^?#]*)', ...
'(?:\\?(?<query>[^#]*))?', ...
'(?:#(?<fragment>.*))?$', ...
];

% Use regexp with the 'names' option to capture components
tokens = regexp(value, pattern, 'names');

% The IRI is considered valid if the regex returns non-empty tokens.
isValid = ~isempty(tokens);
end
9 changes: 7 additions & 2 deletions code/internal/+openminds/+utility/isSemanticInstanceName.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@
%
% See also: matlab.net.URI, openminds.constant.BaseURI

tf = false;
if ~startsWith(name, "http")
return
end

URI = matlab.net.URI(name);

isValidUrl = sprintf("%s://%s", URI.Scheme, URI.Host) == ...
openminds.constant.BaseURI;
isValidUrl = sprintf("%s://%s", URI.Scheme, URI.Host) == openminds.constant.BaseURI("latest") ...
|| sprintf("%s://%s", URI.Scheme, URI.Host) == openminds.constant.BaseURI(3);

Check warning on line 36 in code/internal/+openminds/+utility/isSemanticInstanceName.m

View check run for this annotation

Codecov / codecov/patch

code/internal/+openminds/+utility/isSemanticInstanceName.m#L35-L36

Added lines #L35 - L36 were not covered by tests

URIPath = URI.Path;
URIPath(URIPath=="")=[];
Expand Down
Binary file added code/livescripts/basicNeuroscienceDataset.mlx
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,10 @@
obj@openminds.abstract.ControlledTerm(varargin{:})
end
end

methods (Static)
function instances = listInstances()
instances = openminds.controlledterms.ActionStatusType.CONTROLLED_INSTANCES';
end
end
end
6 changes: 6 additions & 0 deletions code/types/latest/+openminds/+controlledterms/AgeCategory.m
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,10 @@
obj@openminds.abstract.ControlledTerm(varargin{:})
end
end

methods (Static)
function instances = listInstances()
instances = openminds.controlledterms.AgeCategory.CONTROLLED_INSTANCES';
end
end
end
Loading
Loading