linny-r 1.3.3 → 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.
@@ -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: if a pattern starts with equals sign = then `str` must
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 = match && str === pm.substring(1);
345
+ match = (str === pm.substring(1));
342
346
  } else if(pm.startsWith('~')) {
343
- match = match && str.startsWith(pm.substring(1));
347
+ match = str.startsWith(pm.substring(1));
344
348
  } else {
345
- match = match && str.indexOf(pm) >= 0;
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
- for(let j = 0; j < p.min.length; j++) {
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 = match && str !== pm.substring(1);
373
+ match = (str !== pm.substring(1));
352
374
  } else if(pm.startsWith('~')) {
353
- match = match && !str.startsWith(pm.substring(1));
375
+ match = !str.startsWith(pm.substring(1));
354
376
  } else {
355
- match = match && str.indexOf(pm) < 0;
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(/\&amp;/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);