|
111 | 111 | state = add(state, 'publication', p.key, f, struct()); |
112 | 112 | end |
113 | 113 |
|
| 114 | + % On a schema with one entity class (2026-10-08) there is no web_resource: |
| 115 | + % a URL is an address for the thing it names. A resource the dataset is |
| 116 | + % `stored_at` becomes one of the dataset's own identifiers; any other one is |
| 117 | + % an entity of the `type` the spec gives it (software, publication, ...). |
| 118 | + merged = ndi.setup.V2.mergedEntities(); |
| 119 | + storedAt = asCell(getOr(getOr(spec, 'dataset', struct()), 'stored_at', {})); |
| 120 | + state.storedIds = {}; |
114 | 121 | for e = entries(spec, 'web_resources') |
115 | 122 | w = e{1}; |
116 | | - state = checkKnown(state, 'web_resource', w, {'name', 'identifiers', 'host'}); |
117 | | - state = add(state, 'web_resource', w.key, putIds(struct('name', w.name), w), struct()); |
118 | | - if isfield(w, 'host') |
119 | | - state = relate(state, w.key, w.host, 'hosted_by', {'web_resource'}, {'organization'}); |
| 123 | + if ~merged |
| 124 | + state = checkKnown(state, 'web_resource', w, {'name', 'identifiers', 'host'}, ... |
| 125 | + struct('type', '')); |
| 126 | + state = add(state, 'web_resource', w.key, putIds(struct('name', w.name), w), struct()); |
| 127 | + if isfield(w, 'host') |
| 128 | + state = relate(state, w.key, w.host, 'hosted_by', {'web_resource'}, {'organization'}); |
| 129 | + end |
| 130 | + elseif any(strcmp(w.key, storedAt)) |
| 131 | + state = checkKnown(state, 'web_resource', w, {'name', 'identifiers'}, ... |
| 132 | + struct('host', 'a stored copy is an identifier of the dataset; its host is not recorded', ... |
| 133 | + 'type', '')); |
| 134 | + state.storedIds = [state.storedIds, storedIdentifiers(w)]; |
| 135 | + state.ids(w.key) = ''; |
| 136 | + state.kinds(w.key) = 'web_resource'; |
| 137 | + else |
| 138 | + state = checkKnown(state, 'web_resource', w, {'name', 'identifiers', 'type'}, ... |
| 139 | + struct('host', 'the schema in use has no relation from a resource to its host')); |
| 140 | + if ~isfield(w, 'type') |
| 141 | + error('ndi:setup:V2:noType', ['web resource `%s` needs a `type` ' ... |
| 142 | + '(software, publication, protocol, ...) on a schema with one entity class.'], w.key); |
| 143 | + end |
| 144 | + state = add(state, 'web_resource', w.key, putIds(struct('name', w.name), w), ... |
| 145 | + struct(), '', char(w.type)); |
120 | 146 | end |
121 | 147 | end |
122 | 148 |
|
|
158 | 184 | if ~isempty(desc) |
159 | 185 | f.description = desc; |
160 | 186 | end |
| 187 | + if ndi.setup.V2.mergedEntities() && isfield(s, 'node') ... |
| 188 | + && startsWith(char(s.node), 'WBStrain:') |
| 189 | + % the strain's WormBase id is one of its identifiers (one entity class; |
| 190 | + % `wormbase:WBStrain00000001`, V_eta_entity_composition_plan.md sec. 6), |
| 191 | + % since a subject is `instance_of` the strain rather than asserting it |
| 192 | + wb = struct('scheme', 'WormBase', 'value', strrep(char(s.node), ':', '')); |
| 193 | + s.identifiers = [asCell(getOr(s, 'identifiers', {})), {wb}]; |
| 194 | + end |
161 | 195 | f = putIds(f, s); |
162 | 196 | edges = struct(); |
163 | 197 | if isfield(s, 'source') |
|
253 | 287 | if subjectHasName |
254 | 288 | f = putIf(f, 'name', i, 'name'); |
255 | 289 | end |
256 | | - state = add(state, 'subject', i.key, f, struct()); |
| 290 | + state = add(state, 'subject', i.key, f, struct(), '', 'device'); |
257 | 291 | if isfield(i, 'product') |
258 | 292 | state = relate(state, i.key, i.product, 'instance_of', {'subject'}, {'product'}); |
259 | 293 | end |
|
276 | 310 | f = putIf(f, fn{1}, d, fn{1}); |
277 | 311 | end |
278 | 312 | f = putIds(f, d); |
| 313 | + if isfield(state, 'storedIds') && ~isempty(state.storedIds) |
| 314 | + % a stored copy is one of the dataset's addresses (one entity class only) |
| 315 | + extra = cellfun(@(v) struct('scheme', 'URL', 'value', v), state.storedIds, ... |
| 316 | + 'UniformOutput', false); |
| 317 | + if isfield(f, 'global_identifier') |
| 318 | + extra = [num2cell(f.global_identifier(:)'), extra]; |
| 319 | + end |
| 320 | + f.global_identifier = did2.build.list(extra{:}); |
| 321 | + end |
279 | 322 | key = '#dataset'; |
280 | 323 | state = add(state, 'dataset', key, f, struct(), options.DatasetId); |
281 | 324 |
|
|
288 | 331 | end |
289 | 332 | state = relate(state, key, a.person, 'has_author', {'dataset'}, {'person'}, extra); |
290 | 333 | end |
291 | | - for pair = {'funding', 'funded_by', {'funding'}; 'cites', 'cites', {'publication'}; ... |
292 | | - 'stored_at', 'stored_at', {'web_resource'}; 'documented_by', 'documented_by', {'web_resource'}}' |
| 334 | + pairs = {'funding', 'funded_by', {'funding'}; 'cites', 'cites', {'publication'}; ... |
| 335 | + 'stored_at', 'stored_at', {'web_resource'}; 'documented_by', 'documented_by', {'web_resource'}}'; |
| 336 | + if ndi.setup.V2.mergedEntities() |
| 337 | + pairs = pairs(:, ~strcmp(pairs(1, :), 'stored_at')); % now the dataset's identifiers |
| 338 | + end |
| 339 | + for pair = pairs |
293 | 340 | for t = asCell(getOr(d, pair{1}, {})) |
294 | 341 | state = relate(state, key, t{1}, pair{2}, {'dataset'}, pair{3}); |
295 | 342 | end |
|
436 | 483 | id = state.ids(key); |
437 | 484 | end |
438 | 485 |
|
439 | | -function state = add(state, className, key, fields, edges, id) |
| 486 | +function state = add(state, className, key, fields, edges, id, type) |
| 487 | +% TYPE: the entity type, for a subject (or web resource) on a schema with one |
| 488 | +% entity class; ndi.setup.V2.entityDocuments. |
440 | 489 | if nargin < 6 |
441 | 490 | id = ''; |
442 | 491 | end |
| 492 | +if nargin < 7 |
| 493 | + type = ''; |
| 494 | +end |
443 | 495 | if isKey(state.ids, key) |
444 | 496 | error('ndi:setup:V2:duplicateKey', 'The spec defines `%s` twice.', key); |
445 | 497 | end |
446 | | -doc = did2.build.document(className, fields, 'SessionId', state.sid, ... |
447 | | - 'Edges', edges, 'Id', id, 'Validate', state.validate); |
448 | | -state.docs{end+1} = doc; |
449 | | -state.ids(key) = doc.base.id; |
| 498 | +entityKinds = {'organization', 'person', 'funding', 'publication', 'web_resource', ... |
| 499 | + 'software', 'product', 'strain', 'subject', 'dataset', 'study'}; |
| 500 | +if any(strcmp(className, entityKinds)) |
| 501 | + buildAs = className; |
| 502 | + if strcmp(className, 'web_resource') && ~isempty(type) |
| 503 | + buildAs = type; % on a schema with one entity class, what the resource is |
| 504 | + end |
| 505 | + docs = ndi.setup.V2.entityDocuments(buildAs, fields, edges, 'SessionId', state.sid, ... |
| 506 | + 'Id', id, 'Validate', state.validate, 'Type', type); |
| 507 | +else |
| 508 | + docs = {did2.build.document(className, fields, 'SessionId', state.sid, ... |
| 509 | + 'Edges', edges, 'Id', id, 'Validate', state.validate)}; |
| 510 | +end |
| 511 | +state.docs = [state.docs, docs]; |
| 512 | +state.ids(key) = docs{1}.base.id; |
450 | 513 | state.kinds(key) = className; |
451 | 514 | end |
452 | 515 |
|
| 516 | +function ids = storedIdentifiers(w) |
| 517 | +% A stored copy's addresses: an NDI Cloud dataset page is `ndicloud:<id>`. |
| 518 | +ids = {}; |
| 519 | +for x = asCell(getOr(w, 'identifiers', {})) |
| 520 | + v = char(x{1}.value); |
| 521 | + tok = regexp(v, 'ndi-cloud\.com/datasets/([0-9a-f]{24})', 'tokens', 'once'); |
| 522 | + if ~isempty(tok) |
| 523 | + ids{end+1} = ['ndicloud:' tok{1}]; %#ok<AGROW> |
| 524 | + else |
| 525 | + ids{end+1} = v; %#ok<AGROW> |
| 526 | + end |
| 527 | +end |
| 528 | +end |
| 529 | + |
453 | 530 | function state = relate(state, childKey, parentKey, relation, childKinds, parentKinds, extra) |
454 | 531 | if nargin < 7 |
455 | 532 | extra = struct(); |
|
0 commit comments