linny-r 1.4.0 → 1.4.2

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.
@@ -96,6 +96,7 @@ class LinnyRModel {
96
96
  this.grid_pixels = 20;
97
97
  this.align_to_grid = true;
98
98
  this.infer_cost_prices = false;
99
+ this.report_results = false;
99
100
  this.show_block_arrows = true;
100
101
  this.last_zoom_factor = 1;
101
102
 
@@ -2486,6 +2487,7 @@ class LinnyRModel {
2486
2487
  this.decimal_comma = nodeParameterValue(node, 'decimal-comma') === '1';
2487
2488
  this.align_to_grid = nodeParameterValue(node, 'align-to-grid') === '1';
2488
2489
  this.infer_cost_prices = nodeParameterValue(node, 'cost-prices') === '1';
2490
+ this.report_results = nodeParameterValue(node, 'report-results') === '1';
2489
2491
  this.show_block_arrows = nodeParameterValue(node, 'block-arrows') === '1';
2490
2492
  this.name = xmlDecoded(nodeContentByTag(node, 'name'));
2491
2493
  this.author = xmlDecoded(nodeContentByTag(node, 'author'));
@@ -2828,6 +2830,7 @@ class LinnyRModel {
2828
2830
  if(this.decimal_comma) p += ' decimal-comma="1"';
2829
2831
  if(this.align_to_grid) p += ' align-to-grid="1"';
2830
2832
  if(this.infer_cost_prices) p += ' cost-prices="1"';
2833
+ if(this.report_results) p += ' report-results="1"';
2831
2834
  if(this.show_block_arrows) p += ' block-arrows="1"';
2832
2835
  let xml = this.xml_header + ['<model', p, '><name>', xmlEncoded(this.name),
2833
2836
  '</name><author>', xmlEncoded(this.author),
@@ -2937,24 +2940,72 @@ class LinnyRModel {
2937
2940
  }
2938
2941
 
2939
2942
  get outputData() {
2940
- // Returns model results [data, statistics] in tab-separated format
2941
- // First create list of distinct variables used in charts
2942
- const vbls = [];
2943
+ // Returns model results [data, statistics] in tab-separated format.
2944
+ const
2945
+ vbls = [],
2946
+ names = [],
2947
+ scale_re = /\s+\(x[0-9\.\,]+\)$/;
2948
+ // First create list of distinct variables used in charts.
2949
+ // NOTE: Also include those that are not "visible" in a chart.
2943
2950
  for(let i = 0; i < this.charts.length; i++) {
2944
2951
  const c = this.charts[i];
2945
2952
  for(let j = 0; j < c.variables.length; j++) {
2946
- addDistinct(c.variables[j], vbls);
2953
+ let v = c.variables[j],
2954
+ vn = v.displayName;
2955
+ // If variable is scaled, do not include it as such, but include
2956
+ // a new unscaled chart variable.
2957
+ if(vn.match(scale_re)) {
2958
+ vn = vn.replace(scale_re, '');
2959
+ // Add only if (now unscaled) variable has not been added already.
2960
+ if(names.indexOf(vn) < 0) {
2961
+ // NOTE: Chart variable object is used ony as adummy, so NULL
2962
+ // can be used as its "owner chart".
2963
+ const cv = new ChartVariable(null);
2964
+ cv.setProperties(v.object, v.attribute, false, '#000000');
2965
+ vbls.push(cv);
2966
+ names.push(uvn);
2967
+ }
2968
+ } else if(names.indexOf(vn) < 0) {
2969
+ // Keep track of the dataset and dataset modifier variables,
2970
+ // so they will not be added in the next FOR loop.
2971
+ vbls.push(v);
2972
+ names.push(vn);
2973
+ }
2947
2974
  }
2948
2975
  }
2949
- // Create a new chart (without adding it to this model)
2976
+ // Add new variables for each outcome dataset and each equation that
2977
+ // is not a chart variable.
2978
+ for(let id in this.datasets) if(this.datasets.hasOwnProperty(id)) {
2979
+ const
2980
+ ds = this.datasets[id],
2981
+ eq = (ds === this.equations_dataset);
2982
+ if(ds.outcome || eq) {
2983
+ for(let ms in ds.modifiers) if(ds.modifiers.hasOwnProperty(ms)) {
2984
+ const
2985
+ dm = ds.modifiers[ms],
2986
+ n = dm.displayName;
2987
+ // Do not add if already in the list.
2988
+ if(names.indexOf(n) < 0) {
2989
+ // Here, too, NULL can be used as "owner chart".
2990
+ const cv = new ChartVariable(null);
2991
+ cv.setProperties(ds, dm.selector, false, '#000000');
2992
+ vbls.push(cv);
2993
+ }
2994
+ }
2995
+ }
2996
+ }
2997
+ // Sort variables by their name.
2998
+ vbls.sort((a, b) => UI.compareFullNames(a.displayName, b.displayName));
2999
+ // Create a new chart as dummy, so without adding it to this model.
2950
3000
  const c = new Chart();
2951
3001
  for(let i = 0; i < vbls.length; i++) {
2952
3002
  const v = vbls[i];
2953
3003
  c.addVariable(v.object.displayName, v.attribute);
2954
3004
  }
2955
- c.draw();
3005
+ // NOTE: Call `draw` with FALSE to prevent display in the chart manager.
3006
+ c.draw(false);
3007
+ // After drawing, all variables and their statistics have been computed.
2956
3008
  return [c.dataAsString, c.statisticsAsString];
2957
- // @@@TO DO: also add statistics on "outcome" datasets
2958
3009
  }
2959
3010
 
2960
3011
  get listOfAllSelectors() {
@@ -4738,7 +4789,7 @@ class Note extends ObjectWithXYWH {
4738
4789
  get numberContext() {
4739
4790
  // Returns the string to be used to evaluate #. For notes this is
4740
4791
  // their note number if specified, otherwise the number context of a
4741
- // nearby enode, and otherwise the number context of their cluster.
4792
+ // nearby node, and otherwise the number context of their cluster.
4742
4793
  let n = this.number;
4743
4794
  if(n) return n;
4744
4795
  n = this.nearbyNode;
@@ -5203,16 +5254,8 @@ class NodeBox extends ObjectWithXYWH {
5203
5254
 
5204
5255
  get numberContext() {
5205
5256
  // Returns the string to be used to evaluate #, so for clusters,
5206
- // processes and products this is the string of trailing digits
5207
- // (or empty if none) of the node name, or if that does not end on
5208
- // a number, the trailing digits of the first prefix (from right to
5209
- // left) that does end on a number
5210
- const sn = UI.prefixesAndName(this.name);
5211
- let nc = endsWithDigits(sn.pop());
5212
- while(!nc && sn.length > 0) {
5213
- nc = endsWithDigits(sn.pop());
5214
- }
5215
- return nc;
5257
+ // processes and products this is their "tail number".
5258
+ return UI.tailNumber(this.name);
5216
5259
  }
5217
5260
 
5218
5261
  get similarNumberedEntities() {
@@ -5283,14 +5326,14 @@ class NodeBox extends ObjectWithXYWH {
5283
5326
  MODEL.replaceEntityInExpressions(old_name, this.displayName);
5284
5327
  MODEL.inferIgnoredEntities();
5285
5328
  // NOTE: Renaming may affect the node's display size.
5286
- if(this.resize()) this.drawWithLinks();
5329
+ if(this.resize()) UI.drawSelection(MODEL);
5287
5330
  // NOTE: Only TRUE indicates a successful (cosmetic) name change.
5288
5331
  return true;
5289
5332
  }
5290
5333
 
5291
5334
  resize() {
5292
- // Resizes this node; returns TRUE iff size has changed
5293
- // So keep track of original width and height
5335
+ // Resizes this node; returns TRUE iff size has changed.
5336
+ // Therefore, keep track of original width and height.
5294
5337
  const
5295
5338
  ow = this.width,
5296
5339
  oh = this.height,
@@ -5346,12 +5389,13 @@ class NodeBox extends ObjectWithXYWH {
5346
5389
  }
5347
5390
 
5348
5391
  drawWithLinks() {
5349
- // TO DO: also draw relevant arrows when this is a cluster
5392
+ // TO DO: Also draw relevant arrows when this is a cluster.
5350
5393
  UI.drawObject(this);
5351
5394
  if(this instanceof Cluster) return;
5352
- // draw ALL arrows associated with this node
5395
+ // Draw ALL arrows associated with this node.
5353
5396
  const fc = this.cluster;
5354
- // make list of arrows that represent a link related to this node
5397
+ fc.categorizeEntities();
5398
+ // Make list of arrows that represent a link related to this node.
5355
5399
  let a,
5356
5400
  alist = [];
5357
5401
  for(let j = 0; j < fc.arrows.length; j++) {
@@ -5367,7 +5411,7 @@ class NodeBox extends ObjectWithXYWH {
5367
5411
  }
5368
5412
  }
5369
5413
  }
5370
- // draw all arrows in this list
5414
+ // Draw all arrows in this list.
5371
5415
  for(let i = 0; i < alist.length; i++) UI.drawObject(alist[i]);
5372
5416
  }
5373
5417
 
@@ -7328,7 +7372,7 @@ class Process extends Node {
7328
7372
  // without comments or their (X, Y) position
7329
7373
  n = MODEL.black_box_entities[id];
7330
7374
  // `n` is just the name, so remove the actor name if it was added
7331
- if(an) n = n.substr(0, n.lastIndexOf(an));
7375
+ if(an) n = n.substring(0, n.lastIndexOf(an));
7332
7376
  col = true;
7333
7377
  cmnts = '';
7334
7378
  x = 0;
@@ -8271,17 +8315,10 @@ class DatasetModifier {
8271
8315
  // NOTE: If the selector contains wildcards, return "?" to indicate
8272
8316
  // that the value of # cannot be inferred at compile time.
8273
8317
  if(this.hasWildcards) return '?';
8274
- // Otherwise, # is the string of digits at the end of the selector.
8275
- // NOTE: equation names are like entity names, so treat them as such,
8276
- // i.e., also check for prefixes that end on digits.
8277
- const sn = UI.prefixesAndName(this.name);
8278
- let nc = endsWithDigits(sn.pop());
8279
- while(!nc && sn.length > 0) {
8280
- nc = endsWithDigits(sn.pop());
8281
- }
8282
- // NOTE: if the selector has no tail number, return the number context
8283
- // of the dataset of this modifier.
8284
- return nc || this.dataset.numberContext;
8318
+ // Otherwise, return the "tail number" of the selector, or if the
8319
+ // selector has no tail number, return the number context of the
8320
+ // dataset of this modifier.
8321
+ return UI.tailnumber(this.name) || this.dataset.numberContext;
8285
8322
  }
8286
8323
 
8287
8324
  match(s) {
@@ -8366,14 +8403,8 @@ class Dataset {
8366
8403
 
8367
8404
  get numberContext() {
8368
8405
  // Returns the string to be used to evaluate #
8369
- // Like for nodes, this is the string of digits at the end of the
8370
- // dataset name (if any) or an empty string (to denote undefined)
8371
- const sn = UI.prefixesAndName(this.name);
8372
- let nc = endsWithDigits(sn.pop());
8373
- while(!nc && sn.length > 0) {
8374
- nc = endsWithDigits(sn.pop());
8375
- }
8376
- return nc;
8406
+ // Like for nodes, this is the "tail number" of the dataset name.
8407
+ return UI.tailNumber(this.name);
8377
8408
  }
8378
8409
 
8379
8410
  get selectorList() {
@@ -8847,10 +8878,11 @@ class ChartVariable {
8847
8878
  this.non_zero_tally = 0;
8848
8879
  this.exceptions = 0;
8849
8880
  this.bin_tallies = [];
8881
+ this.wildcard_index = false;
8850
8882
  }
8851
8883
 
8852
8884
  setProperties(obj, attr, stck, clr, sf=1, lw=1, vis=true) {
8853
- // Sets the defining properties for this chart variable
8885
+ // Sets the defining properties for this chart variable.
8854
8886
  this.object = obj;
8855
8887
  this.attribute = attr;
8856
8888
  this.stacked = stck;
@@ -8862,12 +8894,18 @@ class ChartVariable {
8862
8894
 
8863
8895
  get displayName() {
8864
8896
  // Returns the name of the Linny-R entity and its attribute, followed
8865
- // by its scale factor unless it equals 1 (no scaling)
8897
+ // by its scale factor unless it equals 1 (no scaling).
8866
8898
  const sf = (this.scale_factor === 1 ? '' :
8867
8899
  ` (x${VM.sig4Dig(this.scale_factor)})`);
8868
- // NOTE: display name of equation is just the equations dataset modifier
8869
- if(this.object === MODEL.equations_dataset) return this.attribute + sf;
8870
- // NOTE: do not display vertical bar if no attribute is specified
8900
+ // NOTE: Display name of equation is just the equations dataset modifier.
8901
+ if(this.object === MODEL.equations_dataset) {
8902
+ let eqn = this.attribute;
8903
+ if(this.wildcard_index !== false) {
8904
+ eqn = eqn.replace('??', this.wildcard_index);
8905
+ }
8906
+ return eqn + sf;
8907
+ }
8908
+ // NOTE: Do not display the vertical bar if no attribute is specified.
8871
8909
  if(!this.attribute) return this.object.displayName + sf;
8872
8910
  return this.object.displayName + UI.OA_SEPARATOR + this.attribute + sf;
8873
8911
  }
@@ -8937,7 +8975,7 @@ class ChartVariable {
8937
8975
  }
8938
8976
 
8939
8977
  computeVector() {
8940
- // Compute vector for this variable (using run results if specified)
8978
+ // Compute vector for this variable (using run results if specified).
8941
8979
  let xrun = null,
8942
8980
  rr = null,
8943
8981
  ri = this.chart.run_index;
@@ -8952,10 +8990,10 @@ class ChartVariable {
8952
8990
  this.vector.length = 0;
8953
8991
  }
8954
8992
  }
8955
- // Compute vector and statistics only if vector is still empty
8993
+ // Compute vector and statistics only if vector is still empty.
8956
8994
  if(this.vector.length > 0) return;
8957
- // NOTE: expression vectors start at t = 0 with initial values that should
8958
- // not be included in statistics
8995
+ // NOTE: expression vectors start at t = 0 with initial values that
8996
+ // should not be included in statistics.
8959
8997
  let v,
8960
8998
  av = null,
8961
8999
  t_end;
@@ -8966,22 +9004,23 @@ class ChartVariable {
8966
9004
  this.non_zero_tally = 0;
8967
9005
  this.exceptions = 0;
8968
9006
  if(rr) {
8969
- // Use run results (time scaled) as "actual vector" `av` for this variable
9007
+ // Use run results (time scaled) as "actual vector" `av` for this
9008
+ // variable.
8970
9009
  const tsteps = Math.ceil(this.chart.time_horizon / this.chart.time_scale);
8971
9010
  av = [];
8972
- // NOTE: scaleData expects "pure" data, so slice off v[0]
9011
+ // NOTE: `scaleDataToVector` expects "pure" data, so slice off v[0].
8973
9012
  VM.scaleDataToVector(rr.vector.slice(1), av, xrun.time_step_duration,
8974
9013
  this.chart.time_scale, tsteps, 1);
8975
9014
  t_end = tsteps;
8976
9015
  } else {
8977
9016
  // Get the variable's own value (number, vector or expression)
8978
- // Special case: when an experiment is running, variables that depict a
8979
- // dataset with no explicit modifier must recompute the vector using the
8980
- // current experiment run combination
9017
+ // Special case: when an experiment is running, variables that
9018
+ // depict a dataset with no explicit modifier must recompute the
9019
+ // vector using the current experiment run combination.
8981
9020
  if(MODEL.running_experiment &&
8982
9021
  this.object instanceof Dataset && !this.attribute) {
8983
9022
  // Check if dataset modifiers match the combination of selectors
8984
- // for the active run
9023
+ // for the active run.
8985
9024
  const mm = this.object.matchingModifiers(
8986
9025
  MODEL.running_experiment.activeCombination);
8987
9026
  // If so, use the first (the list should contain at most 1 selector)
@@ -9000,21 +9039,24 @@ class ChartVariable {
9000
9039
  }
9001
9040
  t_end = MODEL.end_period - MODEL.start_period + 1;
9002
9041
  }
9003
- // NOTE: when a chart combines run results with dataset vectors, the latter
9004
- // may be longer than the # of time steps displayed in the chart
9042
+ // NOTE: when a chart combines run results with dataset vectors, the
9043
+ // latter may be longer than the # of time steps displayed in the chart.
9005
9044
  t_end = Math.min(t_end, this.chart.total_time_steps);
9006
9045
  this.N = t_end;
9007
9046
  for(let t = 0; t <= t_end; t++) {
9008
- // Get the result, store it, and incorporate it in statistics
9047
+ // Get the result, store it, and incorporate it in statistics.
9009
9048
  if(!av) {
9010
9049
  // Undefined attribute => zero (no error)
9011
9050
  v = 0;
9012
9051
  } else if(Array.isArray(av)) {
9013
- // Attribute value is a vector -- may be shorter than t => then use 0
9052
+ // Attribute value is a vector.
9053
+ // NOTE: This vector may be shorter than t; then use 0.
9014
9054
  v = (t < av.length ? av[t] : 0);
9015
9055
  } else if(av instanceof Expression) {
9016
- // Attribute value is an expression
9017
- v = av.result(t);
9056
+ // Attribute value is an expression. If this chart variable has
9057
+ // its wildcard vector index set, evaluate the expression with
9058
+ // this index as context number.
9059
+ v = av.result(t, this.wildcard_index);
9018
9060
  } else {
9019
9061
  // Attribute value must be a number
9020
9062
  v = av;
@@ -9183,7 +9225,7 @@ class Chart {
9183
9225
 
9184
9226
  variableIndexByName(n) {
9185
9227
  for(let i = 0; i < this.variables.length; i++) {
9186
- if(this.variables[i].displayName == n) return i;
9228
+ if(this.variables[i].displayName === n) return i;
9187
9229
  }
9188
9230
  return -1;
9189
9231
  }
@@ -9208,20 +9250,39 @@ class Chart {
9208
9250
  if(n === UI.EQUATIONS_DATASET_NAME) {
9209
9251
  // For equations only the attribute (modifier selector)
9210
9252
  dn = a;
9253
+ n = a;
9211
9254
  } else if(!a) {
9212
9255
  // If no attribute specified (=> dataset) only the entity name
9213
9256
  dn = n;
9214
9257
  }
9215
9258
  let vi = this.variableIndexByName(dn);
9216
9259
  if(vi >= 0) return vi;
9217
- // check whether name refers to a Linny-R entity defined by the model
9260
+ // Check whether name refers to a Linny-R entity defined by the model.
9218
9261
  let obj = MODEL.objectByName(n);
9219
9262
  if(obj === null) {
9220
9263
  UI.warn(`Unknown entity "${n}"`);
9221
9264
  return null;
9222
- } else {
9223
- // no attribute specified? then assume default
9265
+ }
9266
+ const eq = obj instanceof DatasetModifier;
9267
+ if(!eq) {
9268
+ // No equation and no attribute specified? Then assume default.
9224
9269
  if(a === '') a = obj.defaultAttribute;
9270
+ } else if(n.indexOf('??') >= 0) {
9271
+ // Special case: for wildcard equations, add dummy variables
9272
+ // for each vector in the wildcard vector set of the equation
9273
+ // expression.
9274
+ const
9275
+ vlist = [],
9276
+ clr = this.nextAvailableDefaultColor,
9277
+ indices = Object.keys(obj.expression.wildcard_vectors);
9278
+ for(let i = 0; i < indices.length; i++) {
9279
+ const v = new ChartVariable(this);
9280
+ v.setProperties(MODEL.equations_dataset, dn, false, clr);
9281
+ v.wildcard_index = parseInt(indices[i]);
9282
+ this.variables.push(v);
9283
+ vlist.push(v);
9284
+ }
9285
+ return vlist;
9225
9286
  }
9226
9287
  const v = new ChartVariable(this);
9227
9288
  v.setProperties(obj, a, false, this.nextAvailableDefaultColor, 1, 1);
@@ -9314,7 +9375,7 @@ class Chart {
9314
9375
  return VM.sig2Dig(s / 8760) + 'y';
9315
9376
  }
9316
9377
 
9317
- draw() {
9378
+ draw(display=true) {
9318
9379
  // NOTE: The SVG drawing area is fixed to be 500 pixels high, so that
9319
9380
  // when saved as a file, it will (at 300 dpi) be about 2 inches high.
9320
9381
  // Its width will equal its hight times the W/H-ratio of the chart
@@ -9489,6 +9550,10 @@ class Chart {
9489
9550
  }
9490
9551
  }
9491
9552
 
9553
+ // Now all vectors have been computed. If `display` is FALSE, this
9554
+ // indicates that data is used only to save model results.
9555
+ if(!display) return;
9556
+
9492
9557
  // Define the bins when drawing as histogram
9493
9558
  if(this.histogram) {
9494
9559
  this.value_range = maxv - minv;
@@ -124,9 +124,9 @@ function msecToTime(msec) {
124
124
  const ts = new Date(msec).toISOString().slice(11, -1).split('.');
125
125
  let hms = ts[0], ms = ts[1];
126
126
  // Trim zero hours and minutes
127
- while(hms.startsWith('00:')) hms = hms.substr(3);
127
+ while(hms.startsWith('00:')) hms = hms.substring(3);
128
128
  // Trim leading zero on first number
129
- if(hms.startsWith('00')) hms = hms.substr(1);
129
+ if(hms.startsWith('00')) hms = hms.substring(1);
130
130
  // Trim msec when minutes > 0
131
131
  if(hms.indexOf(':') > 0) return hms;
132
132
  // If < 1 second, return as milliseconds
@@ -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;
@@ -165,7 +173,7 @@ function uniformDecimals(data) {
165
173
  ss = v.split('e');
166
174
  x = ss[1];
167
175
  if(x.length < maxe) {
168
- x = x[0] + '0' + x.substr(1);
176
+ x = x[0] + '0' + x.substring(1);
169
177
  }
170
178
  data[i] = ss[0] + 'e' + x;
171
179
  } else if(maxi > 3) {
@@ -470,6 +478,21 @@ function matchingNumber(m, s) {
470
478
  return (n == m ? n : false);
471
479
  }
472
480
 
481
+ function compareWithTailNumbers(s1, s2) {
482
+ // Returns 0 on equal, an integer < 0 if `s1` comes before `s2`, and
483
+ // an integer > 0 if `s2` comes before `s1`.
484
+ if(s1 === s2) return 0;
485
+ let tn1 = endsWithDigits(s1),
486
+ tn2 = endsWithDigits(s2);
487
+ if(tn1) s1 = s1.slice(0, -tn1.length);
488
+ if(tn2) s2 = s2.slice(0, -tn2.length);
489
+ let c = ciCompare(s1, s2);
490
+ if(c !== 0 || !(tn1 || tn2)) return c;
491
+ if(tn1 && tn2) return parseInt(tn1) - parseInt(tn2);
492
+ if(tn2) return -1;
493
+ return 1;
494
+ }
495
+
473
496
  function compareSelectors(s1, s2) {
474
497
  // Dataset selectors comparison is case-insensitive, and puts wildcards
475
498
  // last, where * comes later than ?
@@ -484,7 +507,7 @@ function compareSelectors(s1, s2) {
484
507
  star2 = s2.indexOf('*');
485
508
  if(star1 >= 0) {
486
509
  if(star2 < 0) return 1;
487
- return s1.localeCompare(s2);
510
+ return ciCompare(s1, s2);
488
511
  }
489
512
  if(star2 >= 0) return -1;
490
513
  // Replace ? by | because | has a higher ASCII value than all other chars
@@ -517,7 +540,7 @@ function compareSelectors(s1, s2) {
517
540
  while(i >= 0 && s_1[i] === '-') i--;
518
541
  // If trailing minuses, replace by as many spaces and add an exclamation point
519
542
  if(i < n - 1) {
520
- s_1 = s_1.substr(0, i);
543
+ s_1 = s_1.substring(0, i);
521
544
  while(s_1.length < n) s_1 += ' ';
522
545
  s_1 += '!';
523
546
  }
@@ -526,7 +549,7 @@ function compareSelectors(s1, s2) {
526
549
  i = n - 1;
527
550
  while(i >= 0 && s_2[i] === '-') i--;
528
551
  if(i < n - 1) {
529
- s_2 = s_2.substr(0, i);
552
+ s_2 = s_2.substring(0, i);
530
553
  while(s_2.length < n) s_2 += ' ';
531
554
  s_2 += '!';
532
555
  }
@@ -627,13 +650,11 @@ function customizeXML(str) {
627
650
  // for example to rename entities in one go -- USE WITH CARE!
628
651
  // First modify `str` -- by default, do nothing
629
652
 
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');
653
+ /*
654
+ if(str.indexOf('<author>XXX</author>') >= 0) {
655
+ str = str.replace(/<url>NL\/(\w+)\.csv<\/url>/g, '<url></url>');
635
656
  }
636
-
657
+ */
637
658
 
638
659
  // Finally, return the modified string
639
660
  return str;
@@ -808,8 +829,9 @@ function stringToFloatArray(s) {
808
829
  a = [];
809
830
  while(i <= s.length) {
810
831
  const
811
- h = s.substr(i - 8, 8),
812
- r = h.substr(6, 2) + h.substr(4, 2) + h.substr(2, 2) + h.substr(0, 2);
832
+ h = s.substring(i - 8, i),
833
+ r = h.substring(6, 2) + h.substring(4, 2) +
834
+ h.substring(2, 2) + h.substring(0, 2);
813
835
  a.push(hexToFloat(r));
814
836
  i += 8;
815
837
  }
@@ -824,7 +846,7 @@ function hexToBytes(hex) {
824
846
  // Converts a hex string to a Uint8Array
825
847
  const bytes = [];
826
848
  for(let i = 0; i < hex.length; i += 2) {
827
- bytes.push(parseInt(hex.substr(i, 2), 16));
849
+ bytes.push(parseInt(hex.substring(i, i + 2), 16));
828
850
  }
829
851
  return new Uint8Array(bytes);
830
852
  }
@@ -911,6 +933,7 @@ if(NODE) module.exports = {
911
933
  rangeToList: rangeToList,
912
934
  dateToString: dateToString,
913
935
  msecToTime: msecToTime,
936
+ compactClockTime: compactClockTime,
914
937
  uniformDecimals: uniformDecimals,
915
938
  ellipsedText: ellipsedText,
916
939
  earlierVersion: earlierVersion,