linny-r 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/static/index.html +6 -12
- package/static/linny-r.css +28 -2
- package/static/scripts/linny-r-ctrl.js +40 -8
- package/static/scripts/linny-r-gui.js +124 -65
- package/static/scripts/linny-r-model.js +474 -143
- package/static/scripts/linny-r-utils.js +134 -12
- package/static/scripts/linny-r-vm.js +806 -390
@@ -327,44 +327,149 @@ function patternList(str) {
|
|
327
327
|
}
|
328
328
|
|
329
329
|
function patternMatch(str, patterns) {
|
330
|
-
// Returns TRUE when `str` matches the &|^-pattern
|
331
|
-
// NOTE:
|
330
|
+
// Returns TRUE when `str` matches the &|^-pattern.
|
331
|
+
// NOTE: If a pattern starts with equals sign = then `str` must
|
332
332
|
// equal the rest of the pattern to match; if it starts with a tilde
|
333
|
-
// ~ then `str` must start with the rest of the pattern to match
|
333
|
+
// ~ then `str` must start with the rest of the pattern to match.
|
334
334
|
for(let i = 0; i < patterns.length; i++) {
|
335
335
|
const p = patterns[i];
|
336
|
+
// NOTE: `p` is an OR sub-pattern that tests for a set of "plus"
|
337
|
+
// sub-sub-patterns (all of which should match) and a set of "min"
|
338
|
+
// sub-sub-patters (all should NOT match)
|
336
339
|
let pm,
|
340
|
+
re,
|
337
341
|
match = true;
|
338
|
-
for(let j = 0; j < p.plus.length; j++) {
|
342
|
+
for(let j = 0; match && j < p.plus.length; j++) {
|
339
343
|
pm = p.plus[j];
|
340
344
|
if(pm.startsWith('=')) {
|
341
|
-
match =
|
345
|
+
match = (str === pm.substring(1));
|
342
346
|
} else if(pm.startsWith('~')) {
|
343
|
-
match =
|
347
|
+
match = str.startsWith(pm.substring(1));
|
344
348
|
} else {
|
345
|
-
match =
|
349
|
+
match = (str.indexOf(pm) >= 0);
|
350
|
+
}
|
351
|
+
// If no match, check whether pattern contains wildcards
|
352
|
+
if(!match && pm.indexOf('#') >= 0) {
|
353
|
+
// If so, rematch using regular expression that tests for a
|
354
|
+
// number or a ?? wildcard
|
355
|
+
let res = pm.split('#');
|
356
|
+
for(let i = 0; i < res.length; i++) {
|
357
|
+
res[i] = escapeRegex(res[i]);
|
358
|
+
}
|
359
|
+
res = res.join('(\\d+|\\?\\?)');
|
360
|
+
if(pm.startsWith('=')) {
|
361
|
+
res = '^' + res + '$';
|
362
|
+
} else if(pm.startsWith('~')) {
|
363
|
+
res = '^' + res;
|
364
|
+
}
|
365
|
+
re = new RegExp(res, 'g');
|
366
|
+
match = re.test(str);
|
346
367
|
}
|
347
368
|
}
|
348
|
-
|
369
|
+
// Any "min" match indicates NO match for this sub-pattern,
|
370
|
+
for(let j = 0; match && j < p.min.length; j++) {
|
349
371
|
pm = p.min[j];
|
350
372
|
if(pm.startsWith('=')) {
|
351
|
-
match =
|
373
|
+
match = (str !== pm.substring(1));
|
352
374
|
} else if(pm.startsWith('~')) {
|
353
|
-
match =
|
375
|
+
match = !str.startsWith(pm.substring(1));
|
354
376
|
} else {
|
355
|
-
match =
|
377
|
+
match = (str.indexOf(pm) < 0);
|
378
|
+
}
|
379
|
+
// If still matching, check whether pattern contains wildcards
|
380
|
+
if(match && pm.indexOf('#') >= 0) {
|
381
|
+
// If so, now "negatively" rematch using regular expressions
|
382
|
+
let res = pm.split('#');
|
383
|
+
for(let i = 0; i < res.length; i++) {
|
384
|
+
res[i] = escapeRegex(res[i]);
|
385
|
+
}
|
386
|
+
res = res.join('(\\d+|\\?\\?)');
|
387
|
+
if(pm.startsWith('=')) {
|
388
|
+
res = '^' + res + '$';
|
389
|
+
} else if(pm.startsWith('~')) {
|
390
|
+
res = '^' + res;
|
391
|
+
}
|
392
|
+
re = new RegExp(res, 'g');
|
393
|
+
match = !re.test(str);
|
356
394
|
}
|
357
395
|
}
|
396
|
+
// Iterating through OR list, so any match indicates TRUE
|
358
397
|
if(match) return true;
|
359
398
|
}
|
360
399
|
return false;
|
361
400
|
}
|
362
401
|
|
402
|
+
function matchingWildcardNumber(str, patterns) {
|
403
|
+
// Returns the number that, when substituted for #, caused `str` to
|
404
|
+
// match with the pattern list `patterns`, or FALSE if no such number
|
405
|
+
// exists.
|
406
|
+
// First get the list of all groups of consecutive digits in `str`.
|
407
|
+
let nlist = str.match(/(\d+)/g);
|
408
|
+
// If none, then evidently no number caused the match.
|
409
|
+
if(!nlist) return false;
|
410
|
+
// Now for each number check whether `str` still matches when the
|
411
|
+
// number is replaced by the wildcard #.
|
412
|
+
const mlist = [];
|
413
|
+
for(let i = 0; i < nlist.length; i++) {
|
414
|
+
const
|
415
|
+
rstr = str.replaceAll(nlist[i], '#'),
|
416
|
+
pm = patternMatch(rstr, patterns);
|
417
|
+
// If it still matches, add it to the list.
|
418
|
+
if(pm) addDistinct(nlist[i], mlist);
|
419
|
+
}
|
420
|
+
// NOTE This is only a quick and dirty heuristic. For intricate patterns
|
421
|
+
// there may be mutliple matches, and there may be false positives for
|
422
|
+
// patterns like "abc1#2" (so a hashtag with adjacent digits) but for
|
423
|
+
// now this is good enough.
|
424
|
+
if(mlist.length) return mlist[0];
|
425
|
+
return false;
|
426
|
+
}
|
427
|
+
|
363
428
|
function escapeRegex(str) {
|
364
429
|
// Returns `str` with its RegEx special characters escaped
|
365
430
|
return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
366
431
|
}
|
367
432
|
|
433
|
+
function wildcardMatchRegex(name, equation=false) {
|
434
|
+
// Returns a RegEx object that will match wildcards in an entity name
|
435
|
+
// with an integer number (\d+), or NULL if `name` does not contain
|
436
|
+
// wildcards
|
437
|
+
// By default, # denotes a wildcard, but this may be changed to ??
|
438
|
+
// when an equation name is parsed
|
439
|
+
const sl = name.split(equation ? '??' : '#');
|
440
|
+
if(sl.length < 2) return null;
|
441
|
+
for(let i = 0; i < sl.length; i++) {
|
442
|
+
sl[i] = escapeRegex(sl[i]);
|
443
|
+
}
|
444
|
+
// NOTE: match against integer numbers, but also match for ?? because
|
445
|
+
// wildcard equation entities also match!
|
446
|
+
return new RegExp(`^${sl.join('(\\d+|\\?\\?)')}$`, 'gi');
|
447
|
+
}
|
448
|
+
|
449
|
+
function wildcardFormat(name, modifier=false) {
|
450
|
+
// Returns string with CSS classes if it contains wildcards
|
451
|
+
// NOTE: modifiers can contain * and single ? as wildcards;
|
452
|
+
// equation names can contain at most one ?? as wildcard
|
453
|
+
const re = (modifier ? /(\?+|\*)/ : /(\?\?)/g);
|
454
|
+
return name.replace(re, '<span class="wildcard">$1</span>');
|
455
|
+
}
|
456
|
+
|
457
|
+
function matchingNumber(m, s) {
|
458
|
+
// Returns an integer value if string `m` matches selector pattern `s`
|
459
|
+
// (where asterisks match 0 or more characters, and question marks 1
|
460
|
+
// character) and the matching parts jointly denote an integer.
|
461
|
+
let raw = s.replace(/\*/g, '(.*)').replace(/\?/g, '(.)'),
|
462
|
+
match = m.match(new RegExp(`^${raw}$`)),
|
463
|
+
n = '';
|
464
|
+
if(match) {
|
465
|
+
// Concatenate all matching characters (need not be digits)
|
466
|
+
m = match.slice(1).join('');
|
467
|
+
n = parseInt(m);
|
468
|
+
}
|
469
|
+
// Return number only if when match is parsed as integer
|
470
|
+
return (n == m ? n : false);
|
471
|
+
}
|
472
|
+
|
368
473
|
function compareSelectors(s1, s2) {
|
369
474
|
// Dataset selectors comparison is case-insensitive, and puts wildcards
|
370
475
|
// last, where * comes later than ?
|
@@ -517,6 +622,23 @@ function xmlDecoded(str) {
|
|
517
622
|
).replace(/\&/g, '&').replace(/\$\$\\n/g, '\n\n');
|
518
623
|
}
|
519
624
|
|
625
|
+
function customizeXML(str) {
|
626
|
+
// NOTE: this function can be customized to pre-process a model file,
|
627
|
+
// for example to rename entities in one go -- USE WITH CARE!
|
628
|
+
// First modify `str` -- by default, do nothing
|
629
|
+
|
630
|
+
|
631
|
+
if(str.indexOf('<author>Emma van Kleef</author>') >= 0) {
|
632
|
+
str = str.replace(/is-information="1"><name>Line (\d+): switch</g,
|
633
|
+
'is-information="1" no-slack="1"><name>Line $1: switch<');
|
634
|
+
str = str.replace(/: zon/g, ': solar');
|
635
|
+
}
|
636
|
+
|
637
|
+
|
638
|
+
// Finally, return the modified string
|
639
|
+
return str;
|
640
|
+
}
|
641
|
+
|
520
642
|
function cleanXML(node) {
|
521
643
|
// Removes all unnamed text nodes and comment nodes from the XML
|
522
644
|
// subtree under node
|
@@ -536,7 +658,7 @@ function cleanXML(node) {
|
|
536
658
|
function parseXML(xml) {
|
537
659
|
// Parses string `xml` into an XML document, and returns its root node
|
538
660
|
// (or null if errors)
|
539
|
-
xml = XML_PARSER.parseFromString(xml, 'application/xml');
|
661
|
+
xml = XML_PARSER.parseFromString(customizeXML(xml), 'application/xml');
|
540
662
|
const
|
541
663
|
de = xml.documentElement,
|
542
664
|
pe = de.getElementsByTagName('parsererror').item(0);
|