linny-r 1.3.4 → 1.4.1

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.
@@ -135,6 +135,14 @@ function msecToTime(msec) {
135
135
  return hms + '.' + ms.slice(0, 1) + ' sec';
136
136
  }
137
137
 
138
+ function compactClockTime() {
139
+ // Returns current time (no date) in 6 digits hhmmss.
140
+ const d = new Date();
141
+ return d.getHours().toString().padStart(2, '0') +
142
+ d.getMinutes().toString().padStart(2, '0') +
143
+ d.getSeconds().toString().padStart(2, '0');
144
+ }
145
+
138
146
  function uniformDecimals(data) {
139
147
  // Formats the numbers in the array `data` so that they have uniform decimals
140
148
  // NOTE: (1) this routine assumes that all number strings have sig4Dig format;
@@ -327,44 +335,149 @@ function patternList(str) {
327
335
  }
328
336
 
329
337
  function patternMatch(str, patterns) {
330
- // Returns TRUE when `str` matches the &|^-pattern
331
- // NOTE: if a pattern starts with equals sign = then `str` must
338
+ // Returns TRUE when `str` matches the &|^-pattern.
339
+ // NOTE: If a pattern starts with equals sign = then `str` must
332
340
  // 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
341
+ // ~ then `str` must start with the rest of the pattern to match.
334
342
  for(let i = 0; i < patterns.length; i++) {
335
343
  const p = patterns[i];
344
+ // NOTE: `p` is an OR sub-pattern that tests for a set of "plus"
345
+ // sub-sub-patterns (all of which should match) and a set of "min"
346
+ // sub-sub-patters (all should NOT match)
336
347
  let pm,
348
+ re,
337
349
  match = true;
338
- for(let j = 0; j < p.plus.length; j++) {
350
+ for(let j = 0; match && j < p.plus.length; j++) {
339
351
  pm = p.plus[j];
340
352
  if(pm.startsWith('=')) {
341
- match = match && str === pm.substring(1);
353
+ match = (str === pm.substring(1));
342
354
  } else if(pm.startsWith('~')) {
343
- match = match && str.startsWith(pm.substring(1));
355
+ match = str.startsWith(pm.substring(1));
344
356
  } else {
345
- match = match && str.indexOf(pm) >= 0;
357
+ match = (str.indexOf(pm) >= 0);
358
+ }
359
+ // If no match, check whether pattern contains wildcards
360
+ if(!match && pm.indexOf('#') >= 0) {
361
+ // If so, rematch using regular expression that tests for a
362
+ // number or a ?? wildcard
363
+ let res = pm.split('#');
364
+ for(let i = 0; i < res.length; i++) {
365
+ res[i] = escapeRegex(res[i]);
366
+ }
367
+ res = res.join('(\\d+|\\?\\?)');
368
+ if(pm.startsWith('=')) {
369
+ res = '^' + res + '$';
370
+ } else if(pm.startsWith('~')) {
371
+ res = '^' + res;
372
+ }
373
+ re = new RegExp(res, 'g');
374
+ match = re.test(str);
346
375
  }
347
376
  }
348
- for(let j = 0; j < p.min.length; j++) {
377
+ // Any "min" match indicates NO match for this sub-pattern,
378
+ for(let j = 0; match && j < p.min.length; j++) {
349
379
  pm = p.min[j];
350
380
  if(pm.startsWith('=')) {
351
- match = match && str !== pm.substring(1);
381
+ match = (str !== pm.substring(1));
352
382
  } else if(pm.startsWith('~')) {
353
- match = match && !str.startsWith(pm.substring(1));
383
+ match = !str.startsWith(pm.substring(1));
354
384
  } else {
355
- match = match && str.indexOf(pm) < 0;
385
+ match = (str.indexOf(pm) < 0);
386
+ }
387
+ // If still matching, check whether pattern contains wildcards
388
+ if(match && pm.indexOf('#') >= 0) {
389
+ // If so, now "negatively" rematch using regular expressions
390
+ let res = pm.split('#');
391
+ for(let i = 0; i < res.length; i++) {
392
+ res[i] = escapeRegex(res[i]);
393
+ }
394
+ res = res.join('(\\d+|\\?\\?)');
395
+ if(pm.startsWith('=')) {
396
+ res = '^' + res + '$';
397
+ } else if(pm.startsWith('~')) {
398
+ res = '^' + res;
399
+ }
400
+ re = new RegExp(res, 'g');
401
+ match = !re.test(str);
356
402
  }
357
403
  }
404
+ // Iterating through OR list, so any match indicates TRUE
358
405
  if(match) return true;
359
406
  }
360
407
  return false;
361
408
  }
362
409
 
410
+ function matchingWildcardNumber(str, patterns) {
411
+ // Returns the number that, when substituted for #, caused `str` to
412
+ // match with the pattern list `patterns`, or FALSE if no such number
413
+ // exists.
414
+ // First get the list of all groups of consecutive digits in `str`.
415
+ let nlist = str.match(/(\d+)/g);
416
+ // If none, then evidently no number caused the match.
417
+ if(!nlist) return false;
418
+ // Now for each number check whether `str` still matches when the
419
+ // number is replaced by the wildcard #.
420
+ const mlist = [];
421
+ for(let i = 0; i < nlist.length; i++) {
422
+ const
423
+ rstr = str.replaceAll(nlist[i], '#'),
424
+ pm = patternMatch(rstr, patterns);
425
+ // If it still matches, add it to the list.
426
+ if(pm) addDistinct(nlist[i], mlist);
427
+ }
428
+ // NOTE This is only a quick and dirty heuristic. For intricate patterns
429
+ // there may be mutliple matches, and there may be false positives for
430
+ // patterns like "abc1#2" (so a hashtag with adjacent digits) but for
431
+ // now this is good enough.
432
+ if(mlist.length) return mlist[0];
433
+ return false;
434
+ }
435
+
363
436
  function escapeRegex(str) {
364
437
  // Returns `str` with its RegEx special characters escaped
365
438
  return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
366
439
  }
367
440
 
441
+ function wildcardMatchRegex(name, equation=false) {
442
+ // Returns a RegEx object that will match wildcards in an entity name
443
+ // with an integer number (\d+), or NULL if `name` does not contain
444
+ // wildcards
445
+ // By default, # denotes a wildcard, but this may be changed to ??
446
+ // when an equation name is parsed
447
+ const sl = name.split(equation ? '??' : '#');
448
+ if(sl.length < 2) return null;
449
+ for(let i = 0; i < sl.length; i++) {
450
+ sl[i] = escapeRegex(sl[i]);
451
+ }
452
+ // NOTE: match against integer numbers, but also match for ?? because
453
+ // wildcard equation entities also match!
454
+ return new RegExp(`^${sl.join('(\\d+|\\?\\?)')}$`, 'gi');
455
+ }
456
+
457
+ function wildcardFormat(name, modifier=false) {
458
+ // Returns string with CSS classes if it contains wildcards
459
+ // NOTE: modifiers can contain * and single ? as wildcards;
460
+ // equation names can contain at most one ?? as wildcard
461
+ const re = (modifier ? /(\?+|\*)/ : /(\?\?)/g);
462
+ return name.replace(re, '<span class="wildcard">$1</span>');
463
+ }
464
+
465
+ function matchingNumber(m, s) {
466
+ // Returns an integer value if string `m` matches selector pattern `s`
467
+ // (where asterisks match 0 or more characters, and question marks 1
468
+ // character) and the matching parts jointly denote an integer.
469
+ let raw = s.replace(/\*/g, '(.*)').replace(/\?/g, '(.)'),
470
+ match = m.match(new RegExp(`^${raw}$`)),
471
+ n = '';
472
+ if(match) {
473
+ // Concatenate all matching characters (need not be digits)
474
+ m = match.slice(1).join('');
475
+ n = parseInt(m);
476
+ }
477
+ // Return number only if when match is parsed as integer
478
+ return (n == m ? n : false);
479
+ }
480
+
368
481
  function compareSelectors(s1, s2) {
369
482
  // Dataset selectors comparison is case-insensitive, and puts wildcards
370
483
  // last, where * comes later than ?
@@ -517,6 +630,21 @@ function xmlDecoded(str) {
517
630
  ).replace(/\&amp;/g, '&').replace(/\$\$\\n/g, '\n\n');
518
631
  }
519
632
 
633
+ function customizeXML(str) {
634
+ // NOTE: this function can be customized to pre-process a model file,
635
+ // for example to rename entities in one go -- USE WITH CARE!
636
+ // First modify `str` -- by default, do nothing
637
+
638
+ /*
639
+ if(str.indexOf('<author>XXX</author>') >= 0) {
640
+ str = str.replace(/<url>NL\/(\w+)\.csv<\/url>/g, '<url></url>');
641
+ }
642
+ */
643
+
644
+ // Finally, return the modified string
645
+ return str;
646
+ }
647
+
520
648
  function cleanXML(node) {
521
649
  // Removes all unnamed text nodes and comment nodes from the XML
522
650
  // subtree under node
@@ -536,7 +664,7 @@ function cleanXML(node) {
536
664
  function parseXML(xml) {
537
665
  // Parses string `xml` into an XML document, and returns its root node
538
666
  // (or null if errors)
539
- xml = XML_PARSER.parseFromString(xml, 'application/xml');
667
+ xml = XML_PARSER.parseFromString(customizeXML(xml), 'application/xml');
540
668
  const
541
669
  de = xml.documentElement,
542
670
  pe = de.getElementsByTagName('parsererror').item(0);
@@ -789,6 +917,7 @@ if(NODE) module.exports = {
789
917
  rangeToList: rangeToList,
790
918
  dateToString: dateToString,
791
919
  msecToTime: msecToTime,
920
+ compactClockTime: compactClockTime,
792
921
  uniformDecimals: uniformDecimals,
793
922
  ellipsedText: ellipsedText,
794
923
  earlierVersion: earlierVersion,