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
72 changes: 68 additions & 4 deletions src/io/readYAMLmodel.m
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
end

section = 0;
readList = '';
metMiriams=cell(100000,3); metMirNo=1;
rxnMiriams=cell(100000,3); rxnMirNo=1;
geneMiriams=cell(100000,3); genMirNo=1;
Expand Down Expand Up @@ -346,6 +347,12 @@

% import metabolites:
if section == 2
% The lines of a notes map are indented below the entry's own
% fields; their keys are not entry fields, whatever their name.
if strcmp(readList, 'notes') && ~isempty(regexp(tline_raw, '^ {6,}', 'once'))
model = appendNote(model, 'metNotes', tline_raw, tline_key, tline_value, pos);
continue
end
switch tline_key
case 'id'
pos = pos + 1;
Expand All @@ -364,8 +371,14 @@
model = readFieldValue(model, 'metCharges', tline_value, pos);
readList=''; miriamKey='';
case 'notes'
model = readFieldValue(model, 'metNotes', tline_value, pos);
readList=''; miriamKey='';
% A notes map (other tools' keys, as cobrapy and
% raven-toolbox write them) is read into the note text.
if isempty(tline_value) || strcmp(tline_value, '!!omap')
readList = 'notes';
else
model = readFieldValue(model, 'metNotes', tline_value, pos);
readList=''; miriamKey='';
end
case 'inchis'
model = readFieldValue(model, 'inchis', tline_value, pos);
readList=''; miriamKey='';
Expand Down Expand Up @@ -421,6 +434,12 @@

% import reactions:
if section == 3
% The lines of a notes map are indented below the entry's own
% fields; their keys are not entry fields, whatever their name.
if strcmp(readList, 'notes') && ~isempty(regexp(tline_raw, '^ {6,}', 'once'))
model = appendNote(model, 'rxnNotes', tline_raw, tline_key, tline_value, pos);
continue
end
switch tline_key
case 'id'
pos = pos + 1;
Expand All @@ -445,8 +464,12 @@
% `notes` is the canonical reaction-side key; `rxnNotes`
% is the legacy RAVEN MATLAB key, kept here for
% backward-compatible reads.
model = readFieldValue(model, 'rxnNotes', tline_value, pos);
readList=''; miriamKey='';
if isempty(tline_value) || strcmp(tline_value, '!!omap')
readList = 'notes';
else
model = readFieldValue(model, 'rxnNotes', tline_value, pos);
readList=''; miriamKey='';
end
case 'rxnFrom'
model = readFieldValue(model, 'rxnFrom', tline_value, pos);
readList=''; miriamKey='';
Expand Down Expand Up @@ -878,6 +901,47 @@
model.(fieldName)(pos,1) = {value};
end

function model = appendNote(model, fieldName, raw, key, value, pos)
% One line of a notes map: a 'note' entry adds its value (RAVEN's own note
% text), another entry adds 'key: value', a list item adds its value to the
% entry above it. Entries are separated by '; ', list items by ', '. A
% quoted list item is read whole, as the key detection would split it at
% a ': ' inside the quotes.
item = regexp(raw, '^ +- ([''"])(.*)\1$', 'tokens', 'once');
if ~isempty(item)
key = '';
value = item{2};
end
if strcmp(value, '!!omap')
value = '';
end
text = '';
if numel(model.(fieldName)) >= pos
text = model.(fieldName){pos};
end
if isempty(key)
if isempty(text) || endsWith(text, ':')
text = strtrim([text ' ' value]);
else
text = [text ', ' value];
end
else
if isempty(value)
entry = [key ':'];
elseif strcmp(key, 'note')
entry = value;
else
entry = [key ': ' value];
end
if isempty(text)
text = entry;
else
text = [text '; ' entry];
end
end
model = readFieldValue(model, fieldName, text, pos);
end

function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber)
if isempty(key)
key=miriamKey;
Expand Down
39 changes: 39 additions & 0 deletions tests/function_tests/tIO.m
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,45 @@ function writeReadYAMLPreservesUnknownAnnotationFields(testCase)
testCase.verifyEqual(m2.annotation.geckopyVersion, '0.2.1');
end

function readYAMLmodelReadsNotesMaps(testCase)
% cobrapy and raven-toolbox write other tools' notes as a map
% under "notes"; its entries are read into the note text.
m = testCase.model;
m.metNotes = repmat({''}, numel(m.mets), 1);
m.rxnNotes = repmat({''}, numel(m.rxns), 1);
m.metNotes{1} = 'METPLACEHOLDER';
m.rxnNotes{1} = 'RXNPLACEHOLDER';
f = [tempname '.yml'];
testCase.addTeardown(@() delete(f));
evalc('writeYAMLmodel(m, f);');
txt = fileread(f);
% The metabolite's map has a key named like an entry field and a
% quoted list item with ': ', and is followed by an annotation.
txt = regexprep(txt, '( *)- notes: METPLACEHOLDER', ...
['$1- notes: !!omap\n$1 - enzyme_usage: Enzyme-usage pseudometabolite\n' ...
'$1 - name: other\n$1 - refs:\n$1 - "PMID: 1"\n$1 - b\n' ...
'$1- annotation: !!omap\n$1 - sbo: SBO:0000252']);
% The reaction's map comes before other fields, has keys named
% like entry fields and a list value.
txt = regexprep(txt, '( *)- notes: RXNPLACEHOLDER', ...
['$1- notes: !!omap\n$1 - geckopy_warning: no match for g1\n$1 - id: R99\n' ...
'$1 - name: other\n$1 - refs:\n$1 - a\n$1 - b\n$1 - note: curated\n' ...
'$1- rxnFrom: test']);
fid = fopen(f, 'w'); fwrite(fid, txt); fclose(fid);
evalc('m2 = readYAMLmodel(f);');
testCase.verifyEqual(m2.metNotes{1}, ...
'enzyme_usage: Enzyme-usage pseudometabolite; name: other; refs: PMID: 1, b');
testCase.verifyEqual(m2.metNames, m.metNames);
testCase.verifyTrue(any(strcmp(m2.metMiriams{1}.name, 'sbo')));
testCase.verifyEqual(m2.rxnNotes{1}, ...
'geckopy_warning: no match for g1; id: R99; name: other; refs: a, b; curated');
testCase.verifyEqual(m2.rxns, m.rxns);
testCase.verifyEqual(m2.rxnNames, m.rxnNames);
testCase.verifyEqual(m2.rxnFrom{1}, 'test');
testCase.verifyEqual(numel(m2.mets), numel(m.mets));
testCase.verifyEqual(numel(m2.rxns), numel(m.rxns));
end

function exportForGitWritesRepo(testCase)
d = [tempname filesep];
mkdir(d);
Expand Down
Loading