|
178 | 178 | obj.schema = []; |
179 | 179 | obj.currentpath__ = char(36); |
180 | 180 | obj.root__ = obj; |
| 181 | + kindval = ''; |
181 | 182 | if (nargin >= 1) |
182 | 183 | if (~isempty(varargin)) |
183 | 184 | allflags = [varargin(1:2:end); varargin(2:2:end)]; |
|
188 | 189 | if (isfield(obj.flags__, 'schema')) |
189 | 190 | obj.schema = obj.flags__.schema; |
190 | 191 | end |
| 192 | + if (isfield(obj.flags__, 'kind')) |
| 193 | + kindval = obj.flags__.kind; |
| 194 | + end |
191 | 195 | end |
192 | 196 | if (ischar(val) && ~isempty(regexpi(val, '^https*://', 'once'))) |
193 | 197 | try |
|
207 | 211 | obj.data = val; |
208 | 212 | end |
209 | 213 | end |
| 214 | + % apply kind schema |
| 215 | + if ~isempty(kindval) |
| 216 | + kindschema = getkindschema_(kindval); |
| 217 | + if ~isempty(kindschema) |
| 218 | + obj.setschema(kindschema); |
| 219 | + elseif isempty(obj.schema) |
| 220 | + error('Unknown kind "%s" and no schema defined. Use: uuid, date, time, datetime, ipv4, ipv6, email, uri, posint, nonnegative', kindval); |
| 221 | + end |
| 222 | + obj.setattr(char(36), 'kind', kindval); |
| 223 | + end |
210 | 224 | end |
211 | 225 |
|
212 | 226 | % overloaded numel to prevent subsref from outputting many outputs |
|
237 | 251 | trackpath = obj.currentpath__; |
238 | 252 |
|
239 | 253 | if (oplen == 1 && strcmp(idxkey(1).type, '()') && isempty(idxkey(1).subs)) |
| 254 | + kindval = obj.getattr(char(36), 'kind'); |
| 255 | + if ~isempty(kindval) && isstruct(val) |
| 256 | + formatted = formatkind_(kindval, val); |
| 257 | + if ~isempty(formatted) |
| 258 | + varargout{1} = formatted; |
| 259 | + return |
| 260 | + end |
| 261 | + end |
240 | 262 | varargout{1} = val; |
241 | 263 | return |
242 | 264 | end |
| 265 | + |
243 | 266 | i = 1; |
244 | 267 | while i <= oplen |
245 | 268 | idx = idxkey(i); |
|
509 | 532 | fieldname = idxkey(1).subs; |
510 | 533 | % Skip if JSONPath |
511 | 534 | if (isempty(fieldname) || fieldname(1) ~= char(36)) |
| 535 | + % validate if kind is set |
| 536 | + kindval = obj.getattr(char(36), 'kind'); |
| 537 | + if ~isempty(obj.schema) && ~isempty(kindval) |
| 538 | + targetpath = [obj.currentpath__ '.' esckey_(fieldname)]; |
| 539 | + tempobj = jdict(); |
| 540 | + tempobj.schema = obj.schema; |
| 541 | + tempobj.currentpath__ = targetpath; |
| 542 | + le(tempobj, otherobj); |
| 543 | + end |
512 | 544 | if (isempty(obj.data)) |
513 | 545 | obj.data = struct(); |
514 | 546 | end |
|
536 | 568 |
|
537 | 569 | % Fast path: single numeric index like jd.(1) = value |
538 | 570 | if (oplen == 1 && strcmp(idxkey(1).type, '.') && isnumeric(idxkey(1).subs)) |
| 571 | + % validate if kind is set |
| 572 | + kindval = obj.getattr(char(36), 'kind'); |
| 573 | + if ~isempty(obj.schema) && ~isempty(kindval) |
| 574 | + targetpath = [obj.currentpath__ '[' num2str(idxkey(1).subs - 1) ']']; |
| 575 | + tempobj = jdict(); |
| 576 | + tempobj.schema = obj.schema; |
| 577 | + tempobj.currentpath__ = targetpath; |
| 578 | + le(tempobj, otherobj); |
| 579 | + end |
539 | 580 | newidx = idxkey(1).subs; |
540 | 581 | if isstruct(obj.data) && isstruct(otherobj) |
541 | 582 | fnames = fieldnames(obj.data); |
|
658 | 699 | end |
659 | 700 | end |
660 | 701 |
|
| 702 | + % check if kind-validation is needed |
| 703 | + kindval = obj.getattr(char(36), 'kind'); |
| 704 | + needvalidate = ~isempty(obj.schema) && ~isempty(kindval); |
| 705 | + |
661 | 706 | if (oplen >= 2 && ischar(idxkey(oplen - 1).subs) && strcmp(idxkey(oplen - 1).subs, 'v') && strcmp(idxkey(oplen).type, '()')) |
662 | 707 | % Handle .v(index) = value at any depth |
| 708 | + if needvalidate |
| 709 | + tempobj = jdict(); |
| 710 | + tempobj.schema = obj.schema; |
| 711 | + tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen); |
| 712 | + le(tempobj, otherobj); |
| 713 | + end |
663 | 714 | nextsubs = idxkey(oplen).subs; |
664 | 715 | if iscell(nextsubs) |
665 | 716 | nextsubs = nextsubs{1}; |
|
674 | 725 | end |
675 | 726 | opcell{oplen + 1} = opcell{oplen}; |
676 | 727 | elseif (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{oplen})) |
| 728 | + if needvalidate |
| 729 | + tempobj = jdict(); |
| 730 | + tempobj.schema = obj.schema; |
| 731 | + tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen); |
| 732 | + le(tempobj, otherobj); |
| 733 | + end |
677 | 734 | opcell{oplen}(idx.subs) = otherobj; |
678 | 735 | opcell{oplen + 1} = opcell{oplen}; |
679 | 736 | else |
| 737 | + if needvalidate |
| 738 | + tempobj = jdict(); |
| 739 | + tempobj.schema = obj.schema; |
| 740 | + tempobj.currentpath__ = buildpath_(obj.currentpath__, idxkey, oplen); |
| 741 | + le(tempobj, otherobj); |
| 742 | + end |
680 | 743 | if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36)) |
681 | 744 | opcell{oplen + 1} = obj.call_('jsonpath', opcell{oplen}, idx.subs, otherobj); |
682 | 745 | else |
|
1208 | 1271 | function escaped = esckey_(key) |
1209 | 1272 | escaped = regexprep(key, '(?<=[^\\]|^)\.', '\\.'); |
1210 | 1273 | end |
| 1274 | + |
| 1275 | +% predefined schemas for known kinds |
| 1276 | +function schema = getkindschema_(kind) |
| 1277 | + switch lower(kind) |
| 1278 | + case 'bytes' |
| 1279 | + schema = struct('type', 'array', 'items', struct('type', 'integer', 'minimum', 0, 'maximum', 255)); |
| 1280 | + case 'uuid' |
| 1281 | + schema = struct('type', 'object', ... |
| 1282 | + 'properties', struct( ... |
| 1283 | + 'time_low', struct('type', 'integer', 'minimum', 0, 'maximum', 4294967295), ... % 32-bit |
| 1284 | + 'time_mid', struct('type', 'integer', 'minimum', 0, 'maximum', 65535), ... % 16-bit |
| 1285 | + 'time_high', struct('type', 'integer', 'minimum', 0, 'maximum', 65535), ... % 16-bit |
| 1286 | + 'clock_seq', struct('type', 'integer', 'minimum', 0, 'maximum', 65535), ... % 16-bit |
| 1287 | + 'node', struct('type', 'integer', 'minimum', 0, 'maximum', 281474976710655)), ... % 48-bit |
| 1288 | + 'required', {{'time_low', 'time_mid', 'time_high', 'clock_seq', 'node'}}); |
| 1289 | + case 'date' |
| 1290 | + schema = struct('type', 'object', ... |
| 1291 | + 'properties', struct( ... |
| 1292 | + 'year', struct('type', 'integer', 'minimum', 1, 'maximum', 9999), ... |
| 1293 | + 'month', struct('type', 'integer', 'minimum', 1, 'maximum', 12), ... |
| 1294 | + 'day', struct('type', 'integer', 'minimum', 1, 'maximum', 31)), ... |
| 1295 | + 'required', {{'year', 'month', 'day'}}); |
| 1296 | + case 'time' |
| 1297 | + schema = struct('type', 'object', ... |
| 1298 | + 'properties', struct( ... |
| 1299 | + 'hour', struct('type', 'integer', 'minimum', 0, 'maximum', 23), ... |
| 1300 | + 'min', struct('type', 'integer', 'minimum', 0, 'maximum', 59), ... |
| 1301 | + 'sec', struct('type', 'number', 'minimum', 0, 'exclusiveMaximum', 60)), ... |
| 1302 | + 'required', {{'hour', 'min', 'sec'}}); |
| 1303 | + case 'datetime' |
| 1304 | + schema = struct('type', 'object', ... |
| 1305 | + 'properties', struct( ... |
| 1306 | + 'year', struct('type', 'integer', 'minimum', 1, 'maximum', 9999), ... |
| 1307 | + 'month', struct('type', 'integer', 'minimum', 1, 'maximum', 12), ... |
| 1308 | + 'day', struct('type', 'integer', 'minimum', 1, 'maximum', 31), ... |
| 1309 | + 'hour', struct('type', 'integer', 'minimum', 0, 'maximum', 23), ... |
| 1310 | + 'min', struct('type', 'integer', 'minimum', 0, 'maximum', 59), ... |
| 1311 | + 'sec', struct('type', 'number', 'minimum', 0, 'exclusiveMaximum', 60)), ... |
| 1312 | + 'required', {{'year', 'month', 'day', 'hour', 'min', 'sec'}}); |
| 1313 | + case 'email' |
| 1314 | + schema = struct('type', 'object', ... |
| 1315 | + 'properties', struct( ... |
| 1316 | + 'user', struct('type', 'string', 'minLength', 1), ... |
| 1317 | + 'domain', struct('type', 'string', 'pattern', '^[^@\s]+\.[^@\s]+$')), ... |
| 1318 | + 'required', {{'user', 'domain'}}); |
| 1319 | + case 'uri' |
| 1320 | + schema = struct('type', 'object', ... |
| 1321 | + 'properties', struct( ... |
| 1322 | + 'scheme', struct('type', 'string', 'pattern', '^[a-zA-Z][a-zA-Z0-9+.-]*$'), ... |
| 1323 | + 'host', struct('type', 'string', 'minLength', 1), ... |
| 1324 | + 'port', struct('type', 'integer', 'minimum', 0, 'maximum', 65535), ... |
| 1325 | + 'path', struct('type', 'string'), ... |
| 1326 | + 'query', struct('type', 'string'), ... |
| 1327 | + 'fragment', struct('type', 'string')), ... |
| 1328 | + 'required', {{'scheme', 'host'}}); |
| 1329 | + otherwise |
| 1330 | + schema = []; |
| 1331 | + end |
| 1332 | +end |
| 1333 | + |
| 1334 | +% format kind data as string |
| 1335 | +function str = formatkind_(kind, data) |
| 1336 | + str = []; |
| 1337 | + if ~isstruct(data) |
| 1338 | + return |
| 1339 | + end |
| 1340 | + try |
| 1341 | + switch lower(kind) |
| 1342 | + case 'bytes' |
| 1343 | + str = char(data); |
| 1344 | + case 'uuid' |
| 1345 | + str = sprintf('%08x-%04x-%04x-%04x-%012x', data.time_low, data.time_mid, data.time_high, data.clock_seq, data.node); |
| 1346 | + case 'date' |
| 1347 | + str = sprintf('%04d-%02d-%02d', data.year, data.month, data.day); |
| 1348 | + case 'time' |
| 1349 | + str = sprintf('%02d:%02d:%0*.*f', data.hour, data.min, 2 + (data.sec ~= floor(data.sec)) * 4, (data.sec ~= floor(data.sec)) * 3, data.sec); |
| 1350 | + case 'datetime' |
| 1351 | + str = sprintf('%04d-%02d-%02dT%02d:%02d:%0*.*f', data.year, data.month, data.day, data.hour, data.min, 2 + (data.sec ~= floor(data.sec)) * 4, (data.sec ~= floor(data.sec)) * 3, data.sec); |
| 1352 | + case 'email' |
| 1353 | + str = sprintf('%s@%s', data.user, data.domain); |
| 1354 | + case 'uri' |
| 1355 | + str = sprintf('%s://%s', data.scheme, data.host); |
| 1356 | + if isfield(data, 'port') && ~isempty(data.port) |
| 1357 | + str = [str sprintf(':%d', data.port)]; |
| 1358 | + end |
| 1359 | + if isfield(data, 'path') |
| 1360 | + str = [str data.path]; |
| 1361 | + end |
| 1362 | + if isfield(data, 'query') && ~isempty(data.query) |
| 1363 | + str = [str '?' data.query]; |
| 1364 | + end |
| 1365 | + if isfield(data, 'fragment') && ~isempty(data.fragment) |
| 1366 | + str = [str '#' data.fragment]; |
| 1367 | + end |
| 1368 | + end |
| 1369 | + catch |
| 1370 | + end |
| 1371 | +end |
| 1372 | + |
| 1373 | +% build JSONPath from idxkey array |
| 1374 | +function targetpath = buildpath_(basepath, idxkey, oplen) |
| 1375 | + targetpath = basepath; |
| 1376 | + for ii = 1:oplen |
| 1377 | + idx = idxkey(ii); |
| 1378 | + if strcmp(idx.type, '.') && ischar(idx.subs) && ~strcmp(idx.subs, 'v') |
| 1379 | + targetpath = [targetpath '.' esckey_(idx.subs)]; |
| 1380 | + elseif strcmp(idx.type, '()') && ~isempty(idx.subs) && isnumeric(idx.subs{1}) |
| 1381 | + targetpath = [targetpath '[' num2str(idx.subs{1} - 1) ']']; |
| 1382 | + end |
| 1383 | + end |
| 1384 | +end |
0 commit comments