spm_provenance.m 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113
  1. classdef spm_provenance < handle
  2. % Provenance using PROV Data Model
  3. % http://www.w3.org/TR/prov-dm/
  4. %
  5. % p = spm_provenance;
  6. % p.get_default_namespace
  7. % p.set_default_namespace(uri)
  8. % p.add_namespace(prefix,uri)
  9. % p.get_namespace(prefix)
  10. % p.remove_namespace(prefix)
  11. % p.entity(id,attributes)
  12. % p.activity(id,startTime,endTime,attributes)
  13. % p.agent(id,attributes)
  14. % p.wasGeneratedBy(id,entity,activity,time,attributes)
  15. % p.used(id,activity,entity,time,attributes)
  16. % p.wasInformedBy(id,informed,informant,attributes)
  17. % p.wasStartedBy(id,activity,trigger,starter,time,attributes)
  18. % p.wasEndedBy(id,activity,trigger,ender,time,attributes)
  19. % p.wasInvalidatedBy(id,entity,activity,time,attributes)
  20. % p.wasDerivedFrom(id,generatedEntity,usedEntity,activity,generation,usage,attributes)
  21. % p.revision(id,generatedEntity,usedEntity,activity,generation,usage,attributes)
  22. % p.quotation(id,generatedEntity,usedEntity,activity,generation,usage,attributes)
  23. % p.primarySource(id,generatedEntity,usedEntity,activity,generation,usage,attributes)
  24. % p.wasAttributedTo(id,entity,agent,attributes)
  25. % p.wasAssociatedWith(id,activity,agent,plan,attributes)
  26. % p.actedOnBehalfOf(id,delegate,responsible,activity,attributes)
  27. % p.wasInfluencedBy(id,influencee,influencer,attributes)
  28. % p.alternateOf(alternate1,alternate2)
  29. % p.specializationOf(specificEntity,generalEntity)
  30. % p.collection(id,attributes)
  31. % p.emptyCollection(id,attributes)
  32. % p.hadMember(collection,entity)
  33. % p.bundle(id,b)
  34. %__________________________________________________________________________
  35. % Copyright (C) 2013-2017 Wellcome Trust Centre for Neuroimaging
  36. % Guillaume Flandin
  37. % $Id: spm_provenance.m 7057 2017-04-13 16:45:49Z guillaume $
  38. %-Properties
  39. %==========================================================================
  40. properties (SetAccess='private', GetAccess='private')
  41. namespace = struct('prefix','','uri','');
  42. stack = {};
  43. end
  44. %-Constructor
  45. %==========================================================================
  46. methods
  47. function obj = spm_provenance
  48. add_namespace(obj,'prov','http://www.w3.org/ns/prov#');
  49. add_namespace(obj,'xsd','http://www.w3.org/2001/XMLSchema#');
  50. end
  51. end
  52. %-Public methods
  53. %==========================================================================
  54. methods (Access='public')
  55. %-Namespaces
  56. %----------------------------------------------------------------------
  57. function uri = get_default_namespace(obj)
  58. uri = obj.namespace(1).uri;
  59. end
  60. function set_default_namespace(obj,uri)
  61. obj.namespace(1).uri = uri;
  62. end
  63. function ns = add_namespace(obj,prefix,uri)
  64. n = ismember({obj.namespace.prefix},prefix);
  65. if any(n(1:min(numel(n),3)))
  66. error('Namespace MUST NOT declare prefixes prov and xsd or be empty.');
  67. end
  68. if any(n), n = find(n);
  69. else n = numel(obj.namespace) + 1; end
  70. obj.namespace(n).prefix = prefix;
  71. obj.namespace(n).uri = uri;
  72. if nargout
  73. ns = @(local_name) [prefix ':' local_name];
  74. end
  75. end
  76. function [uri,ns] = get_namespace(obj,prefix)
  77. if nargin == 1, uri = obj.namespace; return; end
  78. n = ismember({obj.namespace.prefix},prefix);
  79. if ~any(n), uri = '';
  80. else uri = obj.namespace(n).uri; end
  81. if nargout > 1
  82. ns = @(local_name) [prefix ':' local_name];
  83. end
  84. end
  85. function remove_namespace(obj,prefix)
  86. n = ismember({obj.namespace.prefix},prefix);
  87. if any(n)
  88. obj.namespace(n) = [];
  89. else
  90. warning('Prefix ''%s'' not found.',prefix);
  91. end
  92. end
  93. %-Components
  94. %----------------------------------------------------------------------
  95. %function entity(obj,id,attributes)
  96. function entity(obj,varargin)
  97. parseArg(obj,'entity',varargin{:});
  98. end
  99. %function activity(obj,id,startTime,endTime,attributes)
  100. function activity(obj,varargin)
  101. parseArg(obj,'activity',varargin{:});
  102. end
  103. %function agent(obj,id,attributes)
  104. function agent(obj,varargin)
  105. parseArg(obj,'agent',varargin{:});
  106. end
  107. %-Relations
  108. %----------------------------------------------------------------------
  109. %function wasGeneratedBy(obj,id,entity,activity,time,attributes)
  110. function wasGeneratedBy(obj,varargin)
  111. parseArg(obj,'wasGeneratedBy',varargin{:});
  112. end
  113. %function used(obj,id,activity,entity,time,attributes)
  114. function used(obj,varargin)
  115. parseArg(obj,'used',varargin{:});
  116. end
  117. %function wasInformedBy(obj,id,informed,informant,attributes)
  118. function wasInformedBy(obj,varargin)
  119. parseArg(obj,'wasInformedBy',varargin{:});
  120. end
  121. %function wasStartedBy(obj,id,activity,trigger,starter,time,attributes)
  122. function wasStartedBy(obj,varargin)
  123. parseArg(obj,'wasStartedBy',varargin{:});
  124. end
  125. %function wasEndedBy(obj,id,activity,trigger,ender,time,attributes)
  126. function wasEndedBy(obj,varargin)
  127. parseArg(obj,'wasEndedBy',varargin{:});
  128. end
  129. %function wasInvalidatedBy(obj,id,entity,activity,time,attributes)
  130. function wasInvalidatedBy(obj,varargin)
  131. parseArg(obj,'wasInvalidatedBy',varargin{:});
  132. end
  133. %function wasDerivedFrom(obj,id,generatedEntity,usedEntity,activity,generation,usage,attributes)
  134. function wasDerivedFrom(obj,varargin)
  135. parseArg(obj,'wasDerivedFrom',varargin{:});
  136. end
  137. %function revision(obj,id,generatedEntity,usedEntity,activity,generation,usage,attributes)
  138. function revision(obj,varargin)
  139. attr = {'prov:type','prov:Revision'};
  140. [arg,attributes] = addAttr(varargin,attr);
  141. wasDerivedFrom(obj,arg{:},attributes);
  142. end
  143. %function quotation(obj,id,generatedEntity,usedEntity,activity,generation,usage,attributes)
  144. function quotation(obj,varargin)
  145. attr = {'prov:type','prov:Quotation'};
  146. [arg,attributes] = addAttr(varargin,attr);
  147. wasDerivedFrom(obj,arg{:},attributes);
  148. end
  149. %function primarySource(obj,id,generatedEntity,usedEntity,activity,generation,usage,attributes)
  150. function primarySource(obj,varargin)
  151. attr = {'prov:type','prov:primarySource'};
  152. [arg,attributes] = addAttr(varargin,attr);
  153. wasDerivedFrom(obj,arg{:},attributes);
  154. end
  155. %function wasAttributedTo(obj,id,entity,agent,attributes)
  156. function wasAttributedTo(obj,varargin)
  157. parseArg(obj,'wasAttributedTo',varargin{:});
  158. end
  159. %function wasAssociatedWith(obj,id,activity,agent,plan,attributes)
  160. function wasAssociatedWith(obj,varargin)
  161. parseArg(obj,'wasAssociatedWith',varargin{:});
  162. end
  163. %function actedOnBehalfOf(obj,id,delegate,responsible,activity,attributes)
  164. function actedOnBehalfOf(obj,varargin)
  165. parseArg(obj,'actedOnBehalfOf',varargin{:});
  166. end
  167. %function wasInfluencedBy(obj,id,influencee,influencer,attributes)
  168. function wasInfluencedBy(obj,varargin)
  169. parseArg(obj,'wasInfluencedBy',varargin{:});
  170. end
  171. %function alternateOf(obj,alternate1,alternate2)
  172. function alternateOf(obj,alternate1,alternate2)
  173. addItem(obj,'alternateOf','',alternate1,alternate2,{});
  174. end
  175. %function specializationOf(obj,specificEntity,generalEntity)
  176. function specializationOf(obj,specificEntity,generalEntity)
  177. addItem(obj,'specializationOf','',specificEntity,generalEntity,{});
  178. end
  179. %function collection(obj,id,attributes)
  180. function collection(obj,varargin)
  181. attr = {'prov:type','prov:Collection'};
  182. [arg,attributes] = addAttr(varargin,attr);
  183. entity(obj,arg{:},attributes);
  184. end
  185. %function emptyCollection(obj,id,attributes)
  186. function emptyCollection(obj,varargin)
  187. attr = {'prov:type','prov:emptyCollection'};
  188. [arg,attributes] = addAttr(varargin,attr);
  189. entity(obj,arg{:},attributes);
  190. end
  191. %function hadMember(obj,collection,entity)
  192. function hadMember(obj,collection,entity)
  193. addItem(obj,'hadMember','',collection,entity,{});
  194. end
  195. %function bundle(obj,id,p)
  196. function varargout = bundle(obj,id,p)
  197. if nargin < 3, p = eval(class(obj)); end
  198. addItem(obj,'bundle',id,p);
  199. if nargin < 3, varargout = {p}; end
  200. end
  201. %-Serialization
  202. %----------------------------------------------------------------------
  203. function varargout = serialize(obj,fmt)
  204. if nargin < 2, fmt = 'provn'; end
  205. [p,n,e] = fileparts(fmt);
  206. if ~isempty(e), fmt = e(2:end); end
  207. switch lower(fmt)
  208. case 'provn'
  209. %-PROV-N: the Provenance Notation
  210. % http://www.w3.org/TR/prov-n/
  211. s = sprintf('document\n');
  212. s = [s serialize_provn(obj)];
  213. s = [s sprintf('endDocument\n')];
  214. case 'json'
  215. %-PROV-JSON
  216. % http://www.w3.org/Submission/2013/SUBM-prov-json-20130424/
  217. s = sprintf('{\n');
  218. s = [s serialize_json(obj)];
  219. s = [s sprintf('}\n')];
  220. case 'jsonld'
  221. %-PROV-JSONLD
  222. % http://dl.acm.org/citation.cfm?id=2962062
  223. [s,b] = serialize_jsonld(obj);
  224. arr = ~isempty(b);
  225. while ~isempty(b)
  226. s = [s sprintf(',\n')];
  227. [s0,b] = serialize_jsonld(b{3},b{2});
  228. s = [s s0];
  229. end
  230. if arr, s = [sprintf('[\n') s sprintf('\n]\n')]; end
  231. case 'ttl'
  232. %-Turtle
  233. % http://www.w3.org/TR/turtle/
  234. %warning('Partially implemented.');
  235. s = serialize_ttl(obj);
  236. case 'dot'
  237. %-GraphViz
  238. % http://www.graphviz.org/
  239. %warning('Partially implemented.');
  240. s = sprintf('digraph "PROV" { size="16,12"; rankdir="BT";\n');
  241. s = [s serialize_dot(obj)];
  242. s = [s sprintf('}\n')];
  243. %matlab.internal.strfun.dot2fig(s);
  244. case {'pdf','svg','png'}
  245. tmp = tempname;
  246. dotfile = [tmp '.dot'];
  247. if isempty(e), outfile = [tmp '.' fmt];
  248. else outfile = fullfile(p,[n e]); end
  249. serialize(obj,dotfile);
  250. dotexe = 'dot';
  251. system(['"' dotexe '" -T' fmt ' -Gdpi=350 -o "' outfile '" "' dotfile '"']);
  252. delete(dotfile);
  253. open(outfile);
  254. return;
  255. otherwise
  256. error('Unknown format "%s".',fmt);
  257. end
  258. if ~isempty(e)
  259. filename = fullfile(p,[n e]);
  260. fid = fopen(filename,'wt');
  261. if fid == -1, error('Cannot write "%s%".',filename); end
  262. fprintf(fid,'%s',s);
  263. fclose(fid);
  264. else
  265. varargout = {s};
  266. end
  267. end
  268. end
  269. %-Private methods
  270. %==========================================================================
  271. methods (Access='private')
  272. function [id,identifier,arg,attributes] = parseArg(obj,comp,varargin)
  273. if isempty(varargin), error('Invalid syntax.'); end
  274. if isstruct(varargin{1})
  275. id = varargin{1}.id;
  276. varargin = varargin(2:end);
  277. else
  278. id = '';
  279. end
  280. identifier = varargin{1};
  281. if iscell(varargin{end})
  282. attributes = attrstr(varargin{end});
  283. varargin = varargin(1:end-1);
  284. else
  285. attributes = {};
  286. end
  287. arg = varargin(2:end);
  288. l = list_expressions;
  289. i = ismember(l(:,1),comp);
  290. argconv = l{i,4};
  291. if ~ismember(comp,{'entity','activity','agent'})
  292. argconv = argconv(2:end);
  293. end
  294. if numel(arg) > numel(argconv)
  295. error('Too many input arguments.');
  296. end
  297. for j=1:numel(argconv)
  298. if numel(arg) < j, arg{j} = '-';
  299. else arg{j} = argconv{j}(arg{j}); end
  300. end
  301. if ismember(comp,{'entity','activity','agent'})
  302. if ~isempty(id), error('Invalid syntax.'); end
  303. addItem(obj,comp,identifier,arg{:},attributes);
  304. else
  305. addItem(obj,comp,id,identifier,arg{:},attributes);
  306. end
  307. end
  308. function addItem(obj,varargin)
  309. n = numel(obj.stack) + 1;
  310. obj.stack{n} = varargin;
  311. end
  312. function str = serialize_provn(obj,step)
  313. if nargin < 2, step = 1; end
  314. o = blanks(2*step);
  315. str = '';
  316. %-Namespace
  317. if ~isempty(obj.namespace(1).uri)
  318. str = [str sprintf([o 'default <%s>\n'],obj.namespace(1).uri)];
  319. end
  320. for i=4:numel(obj.namespace)
  321. str = [str sprintf([o 'prefix %s <%s>\n'],...
  322. obj.namespace(i).prefix, obj.namespace(i).uri)];
  323. end
  324. if ~isempty(obj.namespace(1).uri) || numel(obj.namespace) > 3
  325. str = [str sprintf('\n')];
  326. end
  327. for i=1:numel(obj.stack)
  328. %-Components
  329. if ismember(obj.stack{i}{1},{'entity','agent'})
  330. str = [str sprintf([o '%s(%s'],obj.stack{i}{1:2})];
  331. elseif ismember(obj.stack{i}{1},{'activity'})
  332. if isequal(obj.stack{i}{4},'-') && isequal(obj.stack{i}{3},'-')
  333. str = [str sprintf([o '%s(%s'],obj.stack{i}{1:2})];
  334. elseif isequal(obj.stack{i}{4},'-')
  335. str = [str sprintf([o '%s(%s, %s'],obj.stack{i}{1:3})];
  336. else
  337. str = [str sprintf([o '%s(%s, %s, %s'],obj.stack{i}{1:4})];
  338. end
  339. elseif ismember(obj.stack{i}{1},{'bundle'})
  340. str = [str sprintf([o 'bundle %s\n'],obj.stack{i}{2})];
  341. str = [str serialize_provn(obj.stack{i}{3},2)];
  342. str = [str sprintf([o 'endBundle\n'])];
  343. else
  344. str = [str sprintf([o '%s('],obj.stack{i}{1})];
  345. if ~isempty(obj.stack{i}{2})
  346. str = [str sprintf('%s; ',obj.stack{i}{2})];
  347. end
  348. k = find(cellfun(@(x) ~isequal(x,'-'),obj.stack{i}(3:end-1)));
  349. if false %isempty(k)
  350. k = 0; % remove optional '-'
  351. else
  352. k = numel(obj.stack{i})-1;
  353. end
  354. for j=3:k
  355. str = [str sprintf('%s',obj.stack{i}{j})];
  356. if j~=k, str = [str sprintf(', ')]; end
  357. end
  358. end
  359. %-Attributes
  360. if ~ismember(obj.stack{i}{1},{'alternateOf','specializationOf','hadMember','bundle'})
  361. attr = obj.stack{i}{end};
  362. if ~isempty(attr)
  363. str = [str sprintf([',\n' o o '['])];
  364. for j=1:2:numel(attr)
  365. attribute = attr{j};
  366. literal = attr{j+1};
  367. if iscell(literal)
  368. literal = sprintf('"%s" %%%% %s',literal{:});
  369. else
  370. if ~isempty(parseQN(literal,'prefix'))
  371. s = '''';
  372. else
  373. s = '"';
  374. end
  375. literal = sprintf([s '%s' s],literal);
  376. end
  377. str = [str sprintf('%s = %s',attribute,literal)];
  378. if j~=numel(attr)-1, str = [str sprintf([',\n' o o])]; end
  379. end
  380. str = [str sprintf(']')];
  381. end
  382. end
  383. if ~ismember(obj.stack{i}{1},{'bundle'}), str = [str sprintf(')\n')]; end
  384. end
  385. end
  386. function str = serialize_json(obj,step)
  387. if nargin < 2, step = 1; end
  388. o = blanks(2*step);
  389. str = '';
  390. %-Namespace
  391. str = [str sprintf([o '"prefix": {\n'])];
  392. if ~isempty(obj.namespace(1).uri)
  393. str = [str sprintf([o o '"default": "%s"'],obj.namespace(1).uri)];
  394. end
  395. for i=4:numel(obj.namespace)
  396. str = [str sprintf([o o '"%s": "%s"'],...
  397. obj.namespace(i).prefix,obj.namespace(i).uri)];
  398. if i~=numel(obj.namespace), str = [str sprintf(',')]; end
  399. str = [str sprintf('\n')];
  400. end
  401. str = [str sprintf([o '}'])];
  402. %-Expressions
  403. s = sortprov(obj);
  404. for i=1:numel(s)
  405. if ~isempty(s(i).idx) && ~isequal(s(i).expr,'bundle')
  406. str = [str sprintf(',\n')];
  407. str = [str sprintf([o '"%s": {\n'],s(i).expr)];
  408. for j=s(i).idx
  409. id = obj.stack{j}{2};
  410. if isempty(id)
  411. id = ['_:' s(i).short int2str(j)]; % change counter to start from 1
  412. end
  413. str = [str sprintf([o o '"%s": {\n'],id)];
  414. l = find(cellfun(@(x) ~isequal(x,'-'),obj.stack{j}(3:end-1)));
  415. attr = obj.stack{j}{end};
  416. for k=1:numel(l)
  417. str = [str sprintf([o o o '"prov:%s": "%s"'],s(i).props{k},obj.stack{j}{k+2})];
  418. if k~=numel(l) || ~isempty(attr), str = [str sprintf(',')]; end
  419. str = [str sprintf('\n')];
  420. end
  421. A = reshape(attr,2,[])';
  422. while size(A,1) ~= 0
  423. attribute = A{1,1};
  424. l = 1;
  425. for k=2:size(A,1)
  426. if strcmp(A{k,1},attribute)
  427. l = [l k];
  428. end
  429. end
  430. for k=1:numel(l)
  431. literal{k} = A{k,2};
  432. datatype{k} = 'xsd:string';
  433. if iscell(literal{k})
  434. datatype{k} = literal{k}{2};
  435. literal{k} = literal{k}{1};
  436. else
  437. if isequal(attribute,'prov:type') || strncmp(literal{k},'prov:',5)
  438. datatype{k} = 'xsd:QName';
  439. end
  440. end
  441. end
  442. str = [str sprintf([o o o '"%s":'],attribute)];
  443. if numel(l) == 1
  444. str = [str sprintf(' {\n')];
  445. else
  446. str = [str sprintf(' [\n')];
  447. end
  448. for k=1:numel(l)
  449. if numel(l) ~= 1
  450. str = [str sprintf([o o o o '{\n'])];
  451. end
  452. str = [str sprintf([o o o o '"$": "%s",\n'],literal{k})];
  453. str = [str sprintf([o o o o '"type": "%s"\n'],datatype{k})];
  454. if numel(l) ~= 1
  455. str = [str sprintf([o o o o '}'])];
  456. if k~=numel(l), str = [str sprintf(',')]; end
  457. str = [str sprintf('\n')];
  458. end
  459. end
  460. if numel(l) == 1
  461. str = [str sprintf([o o o '}'])];
  462. else
  463. str = [str sprintf([o o o ']'])];
  464. end
  465. A(l,:) = [];
  466. if size(A,1) ~= 0, str = [str sprintf(',')]; end
  467. str = [str sprintf('\n')];
  468. end
  469. str = [str sprintf([o o '}'])];
  470. if j~=s(i).idx(end), str = [str sprintf(',')]; end
  471. str = [str sprintf('\n')];
  472. end
  473. str = [str sprintf([o '}'])];
  474. end
  475. end
  476. %-Bundles
  477. if ~isempty(s(end).idx) %% assumes bundle is last in the list...
  478. str = [str sprintf(',\n')];
  479. str = [str sprintf([o '"bundle": {\n'])];
  480. for i=1:numel(s(end).idx)
  481. str = [str sprintf([o o '"%s": {\n'],obj.stack{s(end).idx(i)}{2})];
  482. str = [str serialize_json(obj.stack{s(end).idx(i)}{3},step+1)];
  483. str = [str sprintf([o o '}'])];
  484. if i~=numel(s(end).idx), str = [str sprintf(',')]; end
  485. str = [str sprintf('\n')];
  486. end
  487. str = [str sprintf([o '}'])];
  488. end
  489. str = [str sprintf('\n')];
  490. end
  491. function [str,b] = serialize_jsonld(obj,bundle_id,step)
  492. if nargin < 2, bundle_id = ''; end
  493. if nargin < 3, step = 1; end
  494. o = blanks(2*step);
  495. str = sprintf('{\n');
  496. b = [];
  497. provo = prov_o;
  498. %-Namespace
  499. str = [str sprintf([o '"@context": [\n'])];
  500. str = [str sprintf([o o '"https://provenance.ecs.soton.ac.uk/prov.jsonld"'])];
  501. ns = obj.namespace;
  502. if ~isempty(bundle_id)
  503. ns(end+1) = struct('prefix','rdfs','uri','http://www.w3.org/2000/01/rdf-schema#');
  504. end
  505. if ~isempty(ns(1).uri) || numel(ns) > 3
  506. str = [str sprintf(',\n') o o sprintf('{\n')];
  507. else
  508. str = [str sprintf('\n')];
  509. end
  510. if ~isempty(ns(1).uri)
  511. str = [str sprintf([o o o '"@base": "%s"'],ns(1).uri)];
  512. if numel(ns) > 3
  513. str = [str sprintf(',')];
  514. end
  515. str = [str sprintf('\n')];
  516. end
  517. for i=4:numel(ns)
  518. str = [str sprintf([o o o '"%s": "%s"'],...
  519. ns(i).prefix, ns(i).uri)];
  520. if i~=numel(ns)
  521. str = [str sprintf(',')];
  522. end
  523. str = [str sprintf('\n')];
  524. end
  525. if ~isempty(ns(1).uri) || numel(ns) > 3
  526. str = [str o o sprintf('}\n')];
  527. end
  528. str = [str sprintf([o ']'])];
  529. %-Identifiant (for Bundle)
  530. if ~isempty(bundle_id)
  531. str = [str sprintf(',\n') o sprintf('"@id": "%s"',bundle_id)];
  532. end
  533. %-Graph
  534. str = [str sprintf(',\n') o '"@graph": [' sprintf('\n')];
  535. for i=1:numel(obj.stack)
  536. node = {};
  537. ont = provo(ismember(provo(:,1),obj.stack{i}{1}),:);
  538. if ismember(obj.stack{i}{1},{'specializationOf','alternateOf','hadMember'})
  539. node{1,1} = '@id';
  540. node{1,2} = obj.stack{i}{3};
  541. node{2,1} = obj.stack{i}{1};
  542. node{2,2} = obj.stack{i}{4};
  543. elseif ismember(obj.stack{i}{1},{'bundle'})
  544. if ~isempty(b)
  545. warning('Only a single bundle is allowed.');
  546. end
  547. b = obj.stack{i};
  548. continue;
  549. else
  550. attr = obj.stack{i}{end};
  551. % identifier
  552. if ~isempty(obj.stack{i}{2})
  553. node{end+1,1} = '@id';
  554. node{end,2} = obj.stack{i}{2};
  555. end
  556. % type
  557. node{end+1,1} = '@type';
  558. node{end,2} = ont(2);
  559. idx = [];
  560. for j=1:2:numel(attr)
  561. if strcmp(attr{j},'prov:type') && ~isempty(parseQN(attr{j+1}))
  562. node{end,2}{end+1} = attr{j+1};
  563. idx = [idx j j+1];
  564. end
  565. end
  566. if numel(node{end,2}) == 1, node{end,2} = char(node{end,2}); end
  567. attr(idx) = [];
  568. % PROV attributes
  569. for j=1:numel(ont{3})
  570. if ~isequal(obj.stack{i}{j+2},'-')
  571. node{end+1,1} = ont{3}{j};
  572. node{end,2} = obj.stack{i}{j+2};
  573. end
  574. end
  575. % additional attributes
  576. for j=1:2:numel(attr)
  577. if isempty(attr{j}), continue; end
  578. switch attr{j}
  579. case 'prov:location'
  580. attr{j} = 'prov:atLocation';
  581. case 'prov:label'
  582. attr{j} = 'rdfs:label';
  583. % remove xsd:string
  584. if iscell(attr{j+1})
  585. attr{j+1} = attr{j+1}{1};
  586. end
  587. case 'prov:hadRole'
  588. end
  589. node{end+1,1} = attr{j};
  590. node{end,2} = attr{j+1};
  591. if iscell(node{end,2})
  592. node{end,2} = {struct(...
  593. 'type',node{end,2}{2},...
  594. 'value',node{end,2}{1})};
  595. elseif ischar(node{end,2}) && ~isempty(parseQN(node{end,2}))
  596. node{end,2} = {struct(...
  597. 'id', node{end,2})};
  598. end
  599. for k=(j+2):2:numel(attr)
  600. if strcmp(node{end,1},attr{k})
  601. attr{k} = '';
  602. v = attr{k+1};
  603. if iscell(v)
  604. v = struct('type',v{2},'value',v{1});
  605. elseif ischar(v) && ~isempty(parseQN(v))
  606. v = struct('id',v);
  607. end
  608. if ischar(node{end,2})
  609. node{end,2} = {node{end,2},v};
  610. else
  611. node{end,2}{end+1} = v;
  612. end
  613. end
  614. end
  615. end
  616. end
  617. % remove trailing colon
  618. for n=1:numel(node)
  619. if iscell(node{n})
  620. for k=1:numel(node{n})
  621. if isstruct(node{n}{k})
  622. if isfield(node{n}{k},'value') && node{n}{k}.value(end) == ':'
  623. node{n}{k}.value = node{n}{k}.value(1:end-1);
  624. elseif isfield(node{n}{k},'id') && node{n}{k}.id(end) == ':'
  625. node{n}{k}.id = node{n}{k}.id(1:end-1);
  626. end
  627. else
  628. if node{n}{k}(end) == ':'
  629. node{n}{k} = node{n}{k}(1:end-1);
  630. end
  631. end
  632. end
  633. else
  634. if node{n}(end) == ':'
  635. node{n} = node{n}(1:end-1);
  636. end
  637. end
  638. end
  639. % serialize node
  640. str = [str o o sprintf('{\n')];
  641. for j=1:size(node,1)
  642. str = [str o o o sprintf('"%s": ',node{j,1})];
  643. if ischar(node{j,2})
  644. str = [str sprintf('"%s"',node{j,2})];
  645. else
  646. if iscell(node{j,2}) && numel(node{j,2})>1, str = [str '[']; end
  647. for k=1:numel(node{j,2})
  648. if iscell(node{j,2}) && ischar(node{j,2}{k})
  649. str = [str sprintf('"%s"',node{j,2}{k})];
  650. else
  651. fn = fieldnames(node{j,2}{k});
  652. str = [str '{'];
  653. for l=1:numel(fn)
  654. str = [str sprintf('"@%s": "%s"',...
  655. fn{l},node{j,2}{k}.(fn{l}))];
  656. if l<numel(fn)
  657. str = [str sprintf(', ')];
  658. end
  659. end
  660. str = [str '}'];
  661. end
  662. if k<numel(node{j,2})
  663. str = [str sprintf(',')];
  664. end
  665. end
  666. if iscell(node{j,2}) && numel(node{j,2})>1, str = [str ']']; end
  667. end
  668. if j<size(node,1)
  669. str = [str sprintf(',')];
  670. end
  671. str = [str sprintf('\n')];
  672. end
  673. str = [str o o sprintf('}')];
  674. if i<numel(obj.stack) && ~(i==numel(obj.stack)-1 && strcmp(obj.stack{i+1}{1},'bundle'))
  675. str = [str ','];
  676. end
  677. str = [str sprintf('\n')];
  678. end
  679. str = [str o sprintf(']')];
  680. str = [str sprintf('\n')];
  681. str = [str sprintf('}')];
  682. end
  683. function str = serialize_ttl(obj,step)
  684. if nargin < 2, step = 1; end
  685. o = blanks(2*step);
  686. str = '';
  687. %-Namespace
  688. %if step == 1 % if step > 1, only write user defined namespaces
  689. ns = obj.namespace;
  690. if step == 1
  691. ns(end+1) = struct('prefix','rdfs','uri','http://www.w3.org/2000/01/rdf-schema#');
  692. if ~isempty(ns(1).uri)
  693. str = [str sprintf('@prefix : <%s> .\n',ns(1).uri)];
  694. end
  695. end
  696. for i=2:numel(ns)
  697. str = [str sprintf('@prefix %s: <%s> .\n',ns(i).prefix,ns(i).uri)];
  698. end
  699. if ~isempty(ns(1).uri) || numel(ns) > 3
  700. str = [str sprintf('\n')];
  701. end
  702. %end
  703. %-Expressions
  704. % optional entries for activity and relations are not saved
  705. for i=1:numel(obj.stack)
  706. if ismember(obj.stack{i}{1},{'entity','activity','agent'})
  707. str = [str sprintf('%s\n',obj.stack{i}{2})];
  708. attr = obj.stack{i}{end};
  709. k = ismember(attr(1:2:end),'prov:type');
  710. a_type = [{['prov:' obj.stack{i}{1}]} attr{2*find(k)}];
  711. a_type{1}(6) = upper(a_type{1}(6));
  712. str = [str sprintf([o 'a'])];
  713. for j=1:numel(a_type)
  714. if ~isempty(parseQN(a_type{j},'prefix'))
  715. str = [str sprintf(' %s',a_type{j})];
  716. else
  717. str = [str sprintf(' "%s"^^xsd:string',a_type{j})];
  718. end
  719. if j~=numel(a_type), str = [str sprintf(',')]; end
  720. end
  721. if ~isempty(attr)
  722. str = [str sprintf(' ; \n')];
  723. for j=1:2:numel(attr)
  724. attribute = attr{j};
  725. literal = attr{j+1};
  726. if strcmp(attribute,'prov:type'), continue; end
  727. if strcmp(attribute,'prov:label')
  728. attribute = 'rdfs:label';
  729. if iscell(literal), literal = literal{1}; end
  730. end
  731. if strcmp(attribute,'prov:location')
  732. attribute = 'prov:atLocation';
  733. end
  734. if iscell(literal)
  735. %if ~strcmp(literal{2},'xsd:string')
  736. literal = sprintf('"%s"^^%s',literal{:});
  737. %else
  738. % literal = sprintf('"%s"',literal{1});
  739. %end
  740. else
  741. if ~isempty(parseQN(literal,'prefix'))
  742. s = '';
  743. else
  744. s = '"';
  745. end
  746. literal = sprintf([s '%s' s],literal);
  747. end
  748. str = [str sprintf([o '%s %s'],attribute,literal)];
  749. if j~=numel(attr)-1, str = [str sprintf(' ;\n')]; end
  750. end
  751. end
  752. str = [str sprintf(' .\n\n')];
  753. elseif ismember(obj.stack{i}{1},{'bundle'})
  754. str = [str serialize_ttl(obj.stack{i}{3},step+1)];
  755. else
  756. if strcmp(obj.stack{i}{1},'wasGeneratedBy') && ~strcmp(obj.stack{i}{5},'-')
  757. % temporary hack until full PROV-N to PROV-O mapping...
  758. tag = ['_:blank' int2str(i)];
  759. str = [str sprintf('%s a prov:Generation .\n\n',tag)];
  760. str = [str sprintf('%s prov:qualifiedGeneration %s .\n\n',obj.stack{i}{3},tag)];
  761. str = [str sprintf('%s prov:atTime "%s"^^xsd:dateTime .\n\n',tag,obj.stack{i}{5})];
  762. else
  763. str = [str sprintf('%s prov:%s %s .\n\n',obj.stack{i}{[3 1 4]})];
  764. end
  765. end
  766. end
  767. end
  768. function str = serialize_dot(obj,annn)
  769. s = sortprov(obj);
  770. str = '';
  771. expr = {'entity','activity','agent'};
  772. dot_style.entity = {'style','filled','shape','ellipse','color','#808080','fillcolor','#FFFC87','sides','4'};
  773. dot_style.activity = {'style','filled','shape','polygon','color','#0000FF','fillcolor','#9FB1FC','sides','4'};
  774. dot_style.agent = {'style','filled','shape','house', 'color','#000000','fillcolor','#FED37F','sides','4'};
  775. dot_style.default = {'labeldistance','1.5','rotation','20','labelfontsize','8','labelangle','60.0'};
  776. dot_style.wasGeneratedBy = [dot_style.default ,'color','darkgreen','fontcolor','darkgreen'];
  777. dot_style.used = [dot_style.default,'color','red4','fontcolor','red'];
  778. dot_style.wasAttributedTo = [dot_style.default,'color','#FED37F'];
  779. dot_style.wasAssociatedWith = [dot_style.default,'color','#FED37F'];
  780. dot_style.actedOnBehalfOf = [dot_style.default,'color','#FED37F'];
  781. dot_style.wasInfluencedBy = [dot_style.default,'color','grey'];
  782. dot_style.atLocation = [dot_style.default,'color','blue','fontcolor','blue'];
  783. dot_style.annotationLink = {'style','dashed','color','#C0C0C0','arrowhead','none'};
  784. dot_style.annotation = {'shape','note','color','gray','fontcolor','black','fontsize','10'};
  785. strannlab = '<<TABLE cellpadding="0" border="0">%s</TABLE>>';
  786. strtr = '<TR><TD align="left">%s</TD><TD align="left">%s</TD></TR>';
  787. if nargin < 2, annn = 0; end
  788. for i=1:numel(s)
  789. if ~isempty(s(i).idx)
  790. if ismember(s(i).expr,expr)
  791. idx = ismember(expr,s(i).expr);
  792. for j=s(i).idx
  793. label = getattr(obj.stack{j}{end},'prov:label');
  794. if ~isempty(label)
  795. if iscell(label)
  796. label = label{1};
  797. end
  798. else
  799. label = parseQN(obj.stack{j}{2},'local');
  800. end
  801. url = get_url(obj,obj.stack{j}{2});
  802. str = [str sprintf('n%s ',get_valid_identifier(url))];
  803. str = [str dotlist([dot_style.(s(i).expr),'label',label,'URL',url])];
  804. attr = obj.stack{j}{end};
  805. if ~isempty(attr)
  806. url_ann = sprintf('http://annot/ann%d',annn);
  807. attrlist = '';
  808. for k=1:2:numel(attr)
  809. attribute = attr{k};
  810. literal = attr{k+1};
  811. if iscell(literal)
  812. literal = literal{1};
  813. end
  814. attrlist = [attrlist sprintf(strtr,attribute,htmlesc(literal))]; %htmlesc
  815. end
  816. ann_label = sprintf(strannlab,attrlist);
  817. A = ['n' get_valid_identifier(url_ann)];
  818. str = [str A ' ' dotlist([dot_style.annotation,'label',ann_label])];
  819. B = ['n' get_valid_identifier(url)];
  820. str = [str sprintf('%s -> %s ',A,B)];
  821. str = [str dotlist(dot_style.annotationLink)];
  822. annn = annn + 1;
  823. end
  824. end
  825. % handle prov:location / prov:atLocation
  826. if i==1
  827. for j=s(i).idx
  828. val = getattr(obj.stack{j}{end},'prov:location');
  829. if ~isempty(val) && ~iscell(val)
  830. A = ['n' get_valid_identifier(get_url(obj,val))];
  831. B = ['n' get_valid_identifier(get_url(obj,obj.stack{j}{2}))];
  832. str = [str sprintf('%s -> %s ',A,B)];
  833. str = [str dotlist([dot_style.atLocation,'label','locationOf'])];
  834. end
  835. end
  836. end
  837. elseif ~ismember(s(i).expr,{'bundle','collection','emptyCollection','alternateOf','specializationOf'})
  838. for j=s(i).idx
  839. if isequal(obj.stack{j}{4},'-'), continue; end
  840. A = ['n' get_valid_identifier(get_url(obj,obj.stack{j}{3}))];
  841. B = ['n' get_valid_identifier(get_url(obj,obj.stack{j}{4}))];
  842. str = [str sprintf('%s -> %s ',A,B)];
  843. try
  844. str = [str dotlist([dot_style.(s(i).expr),'label',s(i).expr])];
  845. catch
  846. str = [str dotlist([dot_style.default,'label',s(i).expr])];
  847. end
  848. end
  849. elseif ismember(s(i).expr,{'bundle'})
  850. label = obj.stack{s(i).idx}{2};
  851. url = get_url(obj,obj.stack{s(i).idx}{2});
  852. str = [str sprintf('subgraph clustern%s {\n',get_valid_identifier(url))];
  853. str = [str sprintf(' label="%s";\n',label)];
  854. str = [str sprintf(' URL="%s";\n',url)];
  855. str = [str serialize_dot(obj.stack{s(i).idx}{3},annn)];
  856. str = [str sprintf('}\n')];
  857. else
  858. warning('"%s" not handled yet.',s(i).expr);
  859. end
  860. end
  861. end
  862. end
  863. function s = sortprov(obj)
  864. expr = list_expressions;
  865. l = cellfun(@(x) x{1},obj.stack,'UniformOutput',false);
  866. for i=1:size(expr,1)
  867. s(i).expr = expr{i,1};
  868. s(i).short = expr{i,2};
  869. s(i).props = expr{i,3};
  870. s(i).idx = find(ismember(l,expr{i,1}));
  871. end
  872. end
  873. function url = get_url(obj,id)
  874. url = [obj.get_namespace(parseQN(id,'prefix')) ...
  875. parseQN(id,'local')];
  876. end
  877. end
  878. end
  879. %-Helper functions
  880. %==========================================================================
  881. function [arg,attributes] = addAttr(vararg,attr)
  882. if iscell(vararg{end})
  883. arg = vararg(1:end-1);
  884. attributes = [vararg{end} attr{:}];
  885. else
  886. arg = vararg;
  887. attributes = attr;
  888. end
  889. end
  890. function varargout = parseQN(qn,ret)
  891. [t,r] = strtok(qn,':');
  892. if isempty(r), r = t; t = ''; else r(1) = []; end
  893. if ~isempty(strfind(r,' ')), t = ''; r = qn; end
  894. if nargin == 1, ret = 'all'; end
  895. switch lower(ret)
  896. case 'all'
  897. varargout = {t,r};
  898. case 'prefix'
  899. varargout = {t};
  900. case 'local'
  901. varargout = {r};
  902. otherwise
  903. error('Syntax error.');
  904. end
  905. end
  906. function val = getattr(attr,key)
  907. val = '';
  908. for i=1:2:numel(attr)
  909. if strcmp(attr{i},key)
  910. val = attr{i+1};
  911. return;
  912. end
  913. end
  914. end
  915. function attr = attrstr(attr)
  916. for i=2:2:numel(attr)
  917. if isnumeric(attr{i})
  918. if isinteger(attr{i})
  919. attr{i} = intstr(attr{i});
  920. else
  921. attr{i} = floatstr(attr{i});
  922. end
  923. elseif iscell(attr{i}) && iscell(attr{i}{1})
  924. if numel(attr{i}) == 1
  925. attr{i} = jsonesc(cell2str(attr{i}{1}));
  926. else
  927. attr{i}{1} = jsonesc(cell2str(attr{i}{1}));
  928. end
  929. elseif iscell(attr{i})
  930. if isinteger(attr{i}{1})
  931. attr{i}{1} = intstr(attr{i}{1});
  932. else
  933. attr{i}{1} = floatstr(attr{i}{1});
  934. end
  935. end
  936. end
  937. end
  938. function id = esc(id)
  939. c = '=''(),-:;[].';
  940. for i=1:numel(c)
  941. id = strrep(id,c(i),['\' c(i)]);
  942. end
  943. end
  944. function str = htmlesc(str)
  945. %-Escape
  946. % See http://www.w3.org/TR/html4/charset.html#h-5.3.2
  947. str = strrep(str,'&','&amp;');
  948. str = strrep(str,'<','&lt;');
  949. str = strrep(str,'>','&gt;');
  950. str = strrep(str,'"','&quot;');
  951. end
  952. function str = jsonesc(str)
  953. % See http://json.org/
  954. str = strrep(str,'"','\"');
  955. end
  956. function id = get_valid_identifier(id)
  957. c = '/:#-.';
  958. for i=1:numel(c)
  959. id = strrep(id,c(i),'_');
  960. end
  961. end
  962. function t = timestr(t)
  963. if isnumeric(t)
  964. t = datestr(t,'yyyy-mm-ddTHH:MM:SS');
  965. end
  966. end
  967. function i = intstr(i)
  968. if isnumeric(i)
  969. i = ['[' strrep(int2str(i),' ',', ') ']'];
  970. end
  971. end
  972. function f = floatstr(f)
  973. if isnumeric(f)
  974. if size(f,1) == 1
  975. f = strrep(mat2str(f),' ',', ');
  976. else
  977. ff = '[';
  978. for i=1:size(f,1)
  979. if i~=size(f,1), c=','; else c=''; end
  980. ff = [ff floatstr(f(i,:)) c];
  981. end
  982. ff = [ff ']'];
  983. f = ff;
  984. end
  985. end
  986. end
  987. function s = dotlist(l)
  988. s = '[';
  989. for i=1:2:numel(l)
  990. c = '"';
  991. if strncmp(l{i+1},'<<',2), c = ''; end
  992. s = [s sprintf('%s=%c%s%c',l{i},c,l{i+1},c)];
  993. if i~=numel(l)-1
  994. s = [s ','];
  995. end
  996. end
  997. s = [s ']' sprintf('\n')];
  998. end
  999. function s = cell2str(s)
  1000. s = jsonesc(s);
  1001. s = ['[' sprintf('"%s", ',s{:}) ']']; s(end-2:end-1) = [];
  1002. end
  1003. function l = list_expressions
  1004. % {expression, short_name, {property_names}, {convert_fcn}}
  1005. n = @(x) x;
  1006. l = {
  1007. 'entity', '', {}, {};...
  1008. 'activity', '', {'startTime','endTime'}, {@timestr,@timestr};...
  1009. 'agent', '', {}, {};...
  1010. 'wasGeneratedBy', 'wGB', {'entity','activity','time'}, {n,n,@timestr};...
  1011. 'used', 'u', {'activity','entity','time'}, {n,n,@timestr};...
  1012. 'wasInformedBy', 'wInfm', {'informed','informant'}, {n,n};...
  1013. 'wasStartedBy', 'wSB', {'activity','trigger','starter','time'}, {n,n,n,@timestr};...
  1014. 'wasEndedBy', 'wEB', {'activity','trigger','ender','time'}, {n,n,n,@timestr};...
  1015. 'wasInvalidatedBy', 'wIB', {'entity','activity','time'}, {n,n,@timestr};...
  1016. 'wasDerivedFrom', 'wDF', {'generatedEntity','usedEntity','activity','generation','usage'}, {n,n,n,n,n};...
  1017. 'wasAttributedTo', 'wAT', {'entity','agent'}, {n,n};...
  1018. 'wasAssociatedWith', 'wAW', {'activity','agent','plan'}, {n,n,n};...
  1019. 'actedOnBehalfOf', 'aOBO', {'delegate','responsible','activity'}, {n,n,n};...
  1020. 'wasInfluencedBy', 'wInf', {'influencee','influencer'}, {n,n};...
  1021. 'alternateOf', 'aO', {'alternate1','alternate2'}, {n,n};...
  1022. 'specializationOf', 'sO', {'specificEntity','generalEntity'}, {n,n};...
  1023. 'hadMember', 'hM', {'collection','entity'}, {n,n};...
  1024. 'bundle', '', {}, {};...
  1025. };
  1026. end
  1027. function m = prov_o
  1028. m = {
  1029. 'entity', 'prov:Entity', {}; ...
  1030. 'activity', 'prov:Activity', {'startedAtTime','endedAtTime'}; ...
  1031. 'agent', 'prov:Agent', {}; ...
  1032. 'wasGeneratedBy', 'prov:Generation', {'entity_generated','activity','atTime'}; ...
  1033. 'used', 'prov:Usage', {'activity_using','entity','atTime'}; ...
  1034. 'wasInformedBy', 'prov:Communication', {'informed','activity'}; ...
  1035. 'wasStartedBy', 'prov:Start', {'activity_started','entity','hadActivity','atTime'}; ...
  1036. 'wasEndedBy', 'prov:End', {'activity_ended','entity','hadActivity','atTime'}; ...
  1037. 'wasInvalidatedBy', 'prov:Invalidation', {'entity_invalidated','activity','atTime'}; ...
  1038. 'wasDerivedFrom', 'prov:Derivation', {'entity_derived','entity','hadActivity','hadGeneration','hadUsage'}; ...
  1039. 'wasAttributedTo', 'prov:Attribution', {'entity_attributed','agent'}; ...
  1040. 'wasAssociatedWith', 'prov:Association', {'activity_associated','agent','hadPlan'}; ...
  1041. 'actedOnBehalfOf', 'prov:Delegation', {'delegate','agent','hadActivity'}; ...
  1042. 'wasInfluencedBy', 'prov:Influence', {'influencee','influencer'}; ...
  1043. 'alternateOf', 'prov:alternateOf', {'alternate1','alternate2'}; ... % no @type
  1044. 'specializationOf', 'prov:specializationOf', {'specificEntity','generalEntity'}; ... % no @type
  1045. 'hadMember', 'prov:hadMember', {'collection','entity'}; ... % no @type
  1046. 'bundle', '', {}; ...
  1047. };
  1048. end