mapshaper 0.5.107 → 0.5.110

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/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.103";
3
+ var VERSION = "0.5.110";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -101,6 +101,37 @@
101
101
  get trimQuotes () { return trimQuotes; }
102
102
  });
103
103
 
104
+ // This module provides a way for multiple jobs to run together asynchronously
105
+ // while keeping job-level context variables (like "defs") separate.
106
+
107
+ var stash = {};
108
+
109
+ function stashVar(key, val) {
110
+ if (key in stash) {
111
+ error('Tried to replace a stashed variable:', key);
112
+ }
113
+ stash[key] = val;
114
+ }
115
+
116
+ function getStashedVar(key) {
117
+ if (key in stash === false) {
118
+ return undefined; // to support running commands in tests
119
+ // error('Tried to read a nonexistent variable from the stash:', key);
120
+ }
121
+ return stash[key];
122
+ }
123
+
124
+ function clearStash() {
125
+ stash = {};
126
+ }
127
+
128
+ var Stash = /*#__PURE__*/Object.freeze({
129
+ __proto__: null,
130
+ stashVar: stashVar,
131
+ getStashedVar: getStashedVar,
132
+ clearStash: clearStash
133
+ });
134
+
104
135
  var Buffer = require('buffer').Buffer; // works with browserify
105
136
 
106
137
  var uniqCount = 0;
@@ -1133,62 +1164,6 @@
1133
1164
  return raw;
1134
1165
  }
1135
1166
 
1136
- var context = createContext(); // command context (persist for the current command cycle)
1137
-
1138
- function runningInBrowser() {
1139
- return typeof window !== 'undefined' && typeof window.document !== 'undefined';
1140
- }
1141
-
1142
- function getStateVar(key) {
1143
- return context[key];
1144
- }
1145
-
1146
- function setStateVar(key, val) {
1147
- context[key] = val;
1148
- }
1149
-
1150
- function createContext() {
1151
- return {
1152
- DEBUG: false,
1153
- QUIET: false,
1154
- VERBOSE: false,
1155
- defs: {},
1156
- input_files: []
1157
- };
1158
- }
1159
-
1160
- // Install a new set of context variables, clear them when an async callback is called.
1161
- // @cb callback function to wrap
1162
- // returns wrapped callback function
1163
- function createAsyncContext(cb) {
1164
- context = createContext();
1165
- return function() {
1166
- cb.apply(null, utils.toArray(arguments));
1167
- // clear context after cb(), so output/errors can be handled in current context
1168
- context = createContext();
1169
- };
1170
- }
1171
-
1172
- // Save the current context, restore it when an async callback is called
1173
- // @cb callback function to wrap
1174
- // returns wrapped callback function
1175
- function preserveContext(cb) {
1176
- var ctx = context;
1177
- return function() {
1178
- context = ctx;
1179
- cb.apply(null, utils.toArray(arguments));
1180
- };
1181
- }
1182
-
1183
- var State = /*#__PURE__*/Object.freeze({
1184
- __proto__: null,
1185
- runningInBrowser: runningInBrowser,
1186
- getStateVar: getStateVar,
1187
- setStateVar: setStateVar,
1188
- createAsyncContext: createAsyncContext,
1189
- preserveContext: preserveContext
1190
- });
1191
-
1192
1167
  var LOGGING = false;
1193
1168
  var STDOUT = false; // use stdout for status messages
1194
1169
  var _error, _stop, _message;
@@ -1268,13 +1243,13 @@
1268
1243
 
1269
1244
  function verbose() {
1270
1245
  // verbose can be set globally with the -verbose command or separately for each command
1271
- if (getStateVar('VERBOSE') || getStateVar('verbose')) {
1246
+ if (getStashedVar('VERBOSE')) {
1272
1247
  message.apply(null, arguments);
1273
1248
  }
1274
1249
  }
1275
1250
 
1276
1251
  function debug() {
1277
- if (getStateVar('DEBUG') || getStateVar('debug')) {
1252
+ if (getStashedVar('DEBUG')) {
1278
1253
  logArgs(arguments);
1279
1254
  }
1280
1255
  }
@@ -1354,7 +1329,7 @@
1354
1329
 
1355
1330
  function messageArgs(args) {
1356
1331
  var arr = utils.toArray(args);
1357
- var cmd = getStateVar('current_command');
1332
+ var cmd = getStashedVar('current_command');
1358
1333
  if (cmd && cmd != 'help') {
1359
1334
  arr.unshift('[' + cmd + ']');
1360
1335
  }
@@ -1362,7 +1337,7 @@
1362
1337
  }
1363
1338
 
1364
1339
  function logArgs(args) {
1365
- if (!LOGGING || getStateVar('QUIET') || !utils.isArrayLike(args)) return;
1340
+ if (!LOGGING || getStashedVar('QUIET') || !utils.isArrayLike(args)) return;
1366
1341
  var msg = formatLogArgs(args);
1367
1342
  if (STDOUT) console.log(msg);
1368
1343
  else console.error(msg);
@@ -1737,16 +1712,6 @@
1737
1712
 
1738
1713
  // Iterate over each [x,y] point in a layer
1739
1714
  // shapes: one layer's "shapes" array
1740
- function forEachPoint_(shapes, cb) {
1741
- var i, n, j, m, shp;
1742
- for (i=0, n=shapes.length; i<n; i++) {
1743
- shp = shapes[i];
1744
- for (j=0, m=shp ? shp.length : 0; j<m; j++) {
1745
- cb(shp[j], i);
1746
- }
1747
- }
1748
- }
1749
-
1750
1715
  function forEachPoint(shapes, cb) {
1751
1716
  var i, n, j, m, shp;
1752
1717
  for (i=0, n=shapes.length; i<n; i++) {
@@ -1763,7 +1728,6 @@
1763
1728
  getPointBounds: getPointBounds$1,
1764
1729
  getPointFeatureBounds: getPointFeatureBounds,
1765
1730
  getPointsInLayer: getPointsInLayer,
1766
- forEachPoint_: forEachPoint_,
1767
1731
  forEachPoint: forEachPoint
1768
1732
  });
1769
1733
 
@@ -4133,6 +4097,7 @@
4133
4097
  s = [],
4134
4098
  dataNulls = 0,
4135
4099
  rec;
4100
+
4136
4101
  for (var i=0, n=shapes.length; i<n; i++) {
4137
4102
  if (types[i] != geoType) continue;
4138
4103
  if (geoType) s.push(shapes[i]);
@@ -4143,7 +4108,7 @@
4143
4108
  return {
4144
4109
  geometry_type: geoType,
4145
4110
  shapes: s,
4146
- data: dataNulls < s.length ? new DataTable(p) : null
4111
+ data: dataNulls < p.length ? new DataTable(p) : null
4147
4112
  };
4148
4113
  });
4149
4114
  return layers;
@@ -4753,9 +4718,9 @@
4753
4718
  defn = projectionAliases[str]; // defn is a function
4754
4719
  } else if (looksLikeInitString(str)) {
4755
4720
  defn = '+init=' + str.toLowerCase();
4756
- } else if (str in getStateVar('defs')) {
4721
+ } else if (str in (getStashedVar('defs') || {})) {
4757
4722
  // a proj4 alias could be dynamically created in a -calc expression
4758
- defn = getStateVar('defs')[str];
4723
+ defn = getStashedVar('defs')[str];
4759
4724
  } else {
4760
4725
  defn = parseCustomProjection(str);
4761
4726
  }
@@ -7042,6 +7007,15 @@
7042
7007
  transformPoints: transformPoints
7043
7008
  });
7044
7009
 
7010
+ function runningInBrowser() {
7011
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
7012
+ }
7013
+
7014
+ var State = /*#__PURE__*/Object.freeze({
7015
+ __proto__: null,
7016
+ runningInBrowser: runningInBrowser
7017
+ });
7018
+
7045
7019
  function getPathSep(path) {
7046
7020
  // TODO: improve
7047
7021
  return path.indexOf('/') == -1 && path.indexOf('\\') != -1 ? '\\' : '/';
@@ -7150,7 +7124,8 @@
7150
7124
  } else if (fname == '/dev/stdin') {
7151
7125
  content = require('rw').readFileSync(fname);
7152
7126
  } else {
7153
- getStateVar('input_files').push(fname);
7127
+ // kludge to prevent overwriting of input files
7128
+ (getStashedVar('input_files') || []).push(fname);
7154
7129
  content = require('fs').readFileSync(fname);
7155
7130
  }
7156
7131
  if (encoding && Buffer.isBuffer(content)) {
@@ -7175,7 +7150,7 @@
7175
7150
  var fs = require('rw');
7176
7151
  cli.createDirIfNeeded(fname);
7177
7152
  if (cb) {
7178
- fs.writeFile(fname, content, preserveContext(cb));
7153
+ fs.writeFile(fname, content, cb);
7179
7154
  } else {
7180
7155
  fs.writeFileSync(fname, content);
7181
7156
  }
@@ -7270,7 +7245,7 @@
7270
7245
  return cli.writeFile('/dev/stdout', exports[0].content, cb);
7271
7246
  } else {
7272
7247
  var paths = getOutputPaths(utils.pluck(exports, 'filename'), opts);
7273
- var inputFiles = getStateVar('input_files');
7248
+ var inputFiles = getStashedVar('input_files');
7274
7249
  exports.forEach(function(obj, i) {
7275
7250
  var path = paths[i];
7276
7251
  if (obj.content instanceof ArrayBuffer) {
@@ -9957,11 +9932,6 @@
9957
9932
  ctx.$ = i >= 0 ? getFeatureById(i) : layerOnlyProxy;
9958
9933
  ctx._ = ctx; // provide access to functions when masked by variable names
9959
9934
  ctx.d = rec || null; // expose data properties a la d3 (also exposed as this.properties)
9960
- if (opts.record_alias) {
9961
- // this is used to expose source table record as "source" in -join calc=
9962
- // expresions.
9963
- ctx[opts.record_alias] = ctx.d;
9964
- }
9965
9935
  try {
9966
9936
  val = func.call(ctx.$, rec, ctx);
9967
9937
  } catch(e) {
@@ -9981,7 +9951,7 @@
9981
9951
  }
9982
9952
 
9983
9953
  function getExpressionContext(lyr, mixins, opts) {
9984
- var defs = getStateVar('defs');
9954
+ var defs = getStashedVar('defs');
9985
9955
  var env = getBaseContext();
9986
9956
  var ctx = {};
9987
9957
  var fields = lyr.data ? lyr.data.getFields() : [];
@@ -10170,7 +10140,7 @@
10170
10140
  }
10171
10141
  // Save any assigned variables to the defs object, so they will be available
10172
10142
  // for later -each expressions to use.
10173
- defs = getStateVar('defs');
10143
+ defs = getStashedVar('defs');
10174
10144
  compiled = compileCalcExpression(lyr, arcs, opts.expression);
10175
10145
  result = compiled(null, defs);
10176
10146
  message(msg + ": " + result);
@@ -13950,7 +13920,7 @@
13950
13920
 
13951
13921
  var propertiesBySymbolType = {
13952
13922
  polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
13953
- polyline: utils.arrayToIndex(commonProperties),
13923
+ polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin')),
13954
13924
  point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
13955
13925
  label: utils.arrayToIndex(commonProperties.concat(
13956
13926
  'fill,r,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
@@ -14038,7 +14008,6 @@
14038
14008
  function parseStyleExpression(strVal, lyr) {
14039
14009
  var func;
14040
14010
  try {
14041
- // func = compileValueExpression(strVal, lyr, null, {context: getStateVar('defs'), no_warn: true});
14042
14011
  func = compileValueExpression(strVal, lyr, null, {no_warn: true});
14043
14012
  func(0); // check for runtime errors (e.g. undefined variables)
14044
14013
  } catch(e) {
@@ -16820,7 +16789,7 @@ ${svg}
16820
16789
  function guessInputFileType(file) {
16821
16790
  var ext = getFileExtension(file || '').toLowerCase(),
16822
16791
  type = null;
16823
- if (ext == 'dbf' || ext == 'shp' || ext == 'prj' || ext == 'shx') {
16792
+ if (ext == 'dbf' || ext == 'shp' || ext == 'prj' || ext == 'shx' || ext == 'kml') {
16824
16793
  type = ext;
16825
16794
  } else if (/json$/.test(ext)) {
16826
16795
  type = 'json';
@@ -16833,7 +16802,8 @@ ${svg}
16833
16802
  function guessInputContentType(content) {
16834
16803
  var type = null;
16835
16804
  if (utils.isString(content)) {
16836
- type = stringLooksLikeJSON(content) ? 'json' : 'text';
16805
+ type = stringLooksLikeJSON(content) && 'json' ||
16806
+ stringLooksLikeKML(content) && 'kml' || 'text';
16837
16807
  } else if (utils.isObject(content) && content.type || utils.isArray(content)) {
16838
16808
  type = 'json';
16839
16809
  }
@@ -16844,11 +16814,15 @@ ${svg}
16844
16814
  return guessInputFileType(file) || guessInputContentType(content);
16845
16815
  }
16846
16816
 
16847
- //
16848
16817
  function stringLooksLikeJSON(str) {
16849
16818
  return /^\s*[{[]/.test(String(str));
16850
16819
  }
16851
16820
 
16821
+ function stringLooksLikeKML(str) {
16822
+ str = String(str);
16823
+ return str.includes('<kml ') && str.includes('xmlns="http://www.opengis.net/kml/');
16824
+ }
16825
+
16852
16826
  function couldBeDsvFile(name) {
16853
16827
  var ext = getFileExtension(name).toLowerCase();
16854
16828
  return /csv|tsv|txt$/.test(ext);
@@ -16893,6 +16867,7 @@ ${svg}
16893
16867
  guessInputContentType: guessInputContentType,
16894
16868
  guessInputType: guessInputType,
16895
16869
  stringLooksLikeJSON: stringLooksLikeJSON,
16870
+ stringLooksLikeKML: stringLooksLikeKML,
16896
16871
  couldBeDsvFile: couldBeDsvFile,
16897
16872
  isZipFile: isZipFile,
16898
16873
  isSupportedOutputFormat: isSupportedOutputFormat,
@@ -17371,8 +17346,8 @@ ${svg}
17371
17346
  DEFAULT_CACHE_LEN = opts && opts.cacheSize || 0x1000000, // 16MB
17372
17347
  DEFAULT_BUFFER_LEN = opts && opts.bufferSize || 0x40000, // 256K
17373
17348
  fd, cacheOffs, cache, binArr;
17374
-
17375
- getStateVar('input_files').push(path); // bit of a kludge
17349
+ // kludge to let us check if input files are being overwritten
17350
+ (getStashedVar('input_files') || []).push(path);
17376
17351
 
17377
17352
  // Double the default size of the Buffer returned by readSync()
17378
17353
  this.expandBuffer = function() {
@@ -23174,6 +23149,13 @@ ${svg}
23174
23149
  importJSON: importJSON
23175
23150
  });
23176
23151
 
23152
+ function importKML(str, opts) {
23153
+ var togeojson = require("@tmcw/togeojson");
23154
+ var Parser = typeof DOMParser == 'undefined' ? require("@xmldom/xmldom").DOMParser : DOMParser;
23155
+ var geojson = togeojson.kml(new Parser().parseFromString(str, "text/xml"));
23156
+ return importGeoJSON(geojson, opts || {});
23157
+ }
23158
+
23177
23159
  // Parse content of one or more input files and return a dataset
23178
23160
  // @obj: file data, indexed by file type
23179
23161
  // File data objects have two properties:
@@ -23210,6 +23192,11 @@ ${svg}
23210
23192
  fileFmt = 'prj';
23211
23193
  data = obj.prj;
23212
23194
  dataset = {layers: [], info: {prj: data.content}};
23195
+
23196
+ } else if (obj.kml) {
23197
+ fileFmt = 'kml';
23198
+ data = obj.kml;
23199
+ dataset = importKML(data.content, opts);
23213
23200
  }
23214
23201
 
23215
23202
  if (!dataset) {
@@ -23391,7 +23378,7 @@ ${svg}
23391
23378
  stop('Expected binary content, received a string');
23392
23379
  }
23393
23380
 
23394
- } else if (fileType) { // string type
23381
+ } else if (fileType) { // string type, e.g. kml, geojson
23395
23382
  content = cli.readFile(path, encoding || 'utf-8', cache);
23396
23383
 
23397
23384
  } else { // type can't be inferred from filename -- try reading as text
@@ -23680,6 +23667,49 @@ ${svg}
23680
23667
  getFormattedLayerList: getFormattedLayerList
23681
23668
  });
23682
23669
 
23670
+ function Job(catalog) {
23671
+ var currentCmd;
23672
+
23673
+ var job = {
23674
+ catalog: catalog || new Catalog(),
23675
+ defs: {},
23676
+ settings: {},
23677
+ input_files: []
23678
+ };
23679
+
23680
+ job.initSettings = function(o) {
23681
+ job.settings = o;
23682
+ stashVars(job, {});
23683
+ };
23684
+
23685
+ job.startCommand = function(cmd) {
23686
+ currentCmd = cmd;
23687
+ stashVars(job, cmd);
23688
+ };
23689
+
23690
+ // Rejected the idea of passing a command reference to compare with the initial command
23691
+ // (for error checking) ... the "-run" command inserts other commands before this call
23692
+ job.endCommand = function() {
23693
+ currentCmd = null;
23694
+ clearStash();
23695
+ };
23696
+
23697
+ job.resumeCommand = function() {
23698
+ stashVars(job, currentCmd);
23699
+ };
23700
+
23701
+ return job;
23702
+ }
23703
+
23704
+ function stashVars(job, cmd) {
23705
+ clearStash(); // prevent errors from overwriting stash
23706
+ stashVar('current_command', cmd.name);
23707
+ stashVar('DEBUG', job.settings.DEBUG || cmd.debug);
23708
+ stashVar('QUIET', job.settings.QUIET || cmd.quiet);
23709
+ stashVar('defs', job.defs);
23710
+ stashVar('input_files', job.input_files);
23711
+ }
23712
+
23683
23713
  const epsilon = 1.1102230246251565e-16;
23684
23714
  const splitter = 134217729;
23685
23715
  const resulterrbound = (3 + 8 * epsilon) * epsilon;
@@ -30423,7 +30453,7 @@ ${svg}
30423
30453
  if (isReservedName(opts.name)) {
30424
30454
  stop('"' + opts.name + '" is a reserved name');
30425
30455
  }
30426
- getStateVar('defs')[opts.name] = getColorizerFunction(opts);
30456
+ getStashedVar('defs')[opts.name] = getColorizerFunction(opts);
30427
30457
  };
30428
30458
 
30429
30459
  function isReservedName(name) {
@@ -30542,7 +30572,7 @@ ${svg}
30542
30572
 
30543
30573
  cmd.dashlines = function(lyr, dataset, opts) {
30544
30574
  var crs = getDatasetCRS(dataset);
30545
- var defs = getStateVar('defs');
30575
+ var defs = getStashedVar('defs');
30546
30576
  var exp = `this.geojson = splitFeature(this.geojson)`;
30547
30577
  requirePolylineLayer(lyr);
30548
30578
  defs.splitFeature = getSplitFeatureFunction(crs, opts);
@@ -30946,7 +30976,7 @@ ${svg}
30946
30976
  if (!opts.expression) {
30947
30977
  stop('Missing an assignment expression');
30948
30978
  }
30949
- var defs = getStateVar('defs');
30979
+ var defs = getStashedVar('defs');
30950
30980
  var compiled = compileFeatureExpression(opts.expression, {}, null, {no_warn: true});
30951
30981
  var result = compiled(null, defs);
30952
30982
  };
@@ -32218,8 +32248,6 @@ ${svg}
32218
32248
  if (opts && opts.where) {
32219
32249
  filter = compileValueExpression(opts.where, lyr, arcs);
32220
32250
  }
32221
- // 'defs' are now added to the context of all expressions
32222
- // compiled = compileFeatureExpression(exp, lyr, arcs, {context: getStateVar('defs')});
32223
32251
  compiled = compileFeatureExpression(exp, lyr, arcs, exprOpts);
32224
32252
  // call compiled expression with id of each record
32225
32253
  for (var i=0; i<n; i++) {
@@ -34628,59 +34656,52 @@ ${svg}
34628
34656
  };
34629
34657
  }
34630
34658
 
34631
- function resetControlFlow() {
34632
- setStateVar('control', null);
34659
+ function resetControlFlow(job) {
34660
+ job.control = null;
34633
34661
  }
34634
34662
 
34635
- function inControlBlock() {
34636
- var state = getState();
34637
- return !!state.inControlBlock;
34663
+ function inControlBlock(job) {
34664
+ return !!getState(job).inControlBlock;
34638
34665
  }
34639
34666
 
34640
- function enterActiveBranch() {
34641
- var state = getState();
34667
+ function enterActiveBranch(job) {
34668
+ var state = getState(job);
34642
34669
  state.inControlBlock = true;
34643
34670
  state.active = true;
34644
34671
  state.complete = true;
34645
34672
  }
34646
34673
 
34647
- function enterInactiveBranch() {
34648
- var state = getState();
34674
+ function enterInactiveBranch(job) {
34675
+ var state = getState(job);
34649
34676
  state.inControlBlock = true;
34650
34677
  state.active = false;
34651
34678
  }
34652
34679
 
34653
- function blockWasActive() {
34654
- return !!getState().complete;
34680
+ function blockWasActive(job) {
34681
+ return !!getState(job).complete;
34655
34682
  }
34656
34683
 
34657
- function inActiveBranch() {
34658
- return !!getState().active;
34684
+ function inActiveBranch(job) {
34685
+ return !!getState(job).active;
34659
34686
  }
34660
34687
 
34661
- function getState() {
34662
- var o = getStateVar('control') || setStateVar('control', {}) || getStateVar('control');
34663
- return o;
34688
+ function getState(job) {
34689
+ return job.control || (job.control = {});
34664
34690
  }
34665
34691
 
34666
- function compileIfCommandExpression(expr, catalog, target, opts) {
34667
- var ctx = {};
34668
-
34669
- if (target && target.layer) {
34670
- ctx = getLayerProxy(target.layer, target.dataset.arcs, opts);
34671
- }
34692
+ function compileIfCommandExpression(expr, catalog, targ, opts) {
34693
+ var ctx = getLayerProxy(targ.layer, targ.dataset.arcs, opts);
34694
+ var exprOpts = Object.assign({returns: true}, opts);
34695
+ var func = compileExpressionToFunction(expr, exprOpts);
34672
34696
 
34673
- ctx.file_exists = function(name) {
34674
- return cli.isFile(name);
34697
+ ctx.layer_exists = function(name) {
34698
+ return !!catalog.findSingleLayer(name);
34675
34699
  };
34676
34700
 
34677
- ctx.layer_exists = function(name) {
34678
- var lyr = catalog.findSingleLayer(name);
34679
- return !!lyr;
34701
+ ctx.file_exists = function(file) {
34702
+ return cli.isFile(file);
34680
34703
  };
34681
34704
 
34682
- var exprOpts = Object.assign({returns: true}, opts);
34683
- var func = compileExpressionToFunction(expr, exprOpts);
34684
34705
  return function() {
34685
34706
  try {
34686
34707
  return func.call(ctx, {}, ctx);
@@ -34689,45 +34710,44 @@ ${svg}
34689
34710
  stop(e.name, "in expression [" + expr + "]:", e.message);
34690
34711
  }
34691
34712
  };
34692
-
34693
34713
  }
34694
34714
 
34695
- function skipCommand(cmdName) {
34715
+ function skipCommand(cmdName, job) {
34696
34716
  // allow all control commands to run
34697
34717
  if (isControlFlowCommand(cmdName)) return false;
34698
- return inControlBlock() && !inActiveBranch();
34718
+ return inControlBlock(job) && !inActiveBranch(job);
34699
34719
  }
34700
34720
 
34701
- cmd.if = function(catalog, opts) {
34702
- if (inControlBlock()) {
34721
+ cmd.if = function(job, opts) {
34722
+ if (inControlBlock(job)) {
34703
34723
  stop('Nested -if commands are not supported.');
34704
34724
  }
34705
- evaluateIf(catalog, opts);
34725
+ evaluateIf(job, opts);
34706
34726
  };
34707
34727
 
34708
- cmd.elif = function(catalog, opts) {
34709
- if (!inControlBlock()) {
34728
+ cmd.elif = function(job, opts) {
34729
+ if (!inControlBlock(job)) {
34710
34730
  stop('-elif command must be preceded by an -if command.');
34711
34731
  }
34712
- evaluateIf(catalog, opts);
34732
+ evaluateIf(job, opts);
34713
34733
  };
34714
34734
 
34715
- cmd.else = function() {
34716
- if (!inControlBlock()) {
34735
+ cmd.else = function(job) {
34736
+ if (!inControlBlock(job)) {
34717
34737
  stop('-else command must be preceded by an -if command.');
34718
34738
  }
34719
- if (blockWasActive()) {
34720
- enterInactiveBranch();
34739
+ if (blockWasActive(job)) {
34740
+ enterInactiveBranch(job);
34721
34741
  } else {
34722
- enterActiveBranch();
34742
+ enterActiveBranch(job);
34723
34743
  }
34724
34744
  };
34725
34745
 
34726
- cmd.endif = function() {
34727
- if (!inControlBlock()) {
34746
+ cmd.endif = function(job) {
34747
+ if (!inControlBlock(job)) {
34728
34748
  stop('-endif command must be preceded by an -if command.');
34729
34749
  }
34730
- resetControlFlow();
34750
+ resetControlFlow(job);
34731
34751
  };
34732
34752
 
34733
34753
  function isControlFlowCommand(cmd) {
@@ -34748,11 +34768,11 @@ ${svg}
34748
34768
  return true;
34749
34769
  }
34750
34770
 
34751
- function evaluateIf(catalog, opts) {
34752
- if (!blockWasActive() && testLayer(catalog, opts)) {
34753
- enterActiveBranch();
34771
+ function evaluateIf(job, opts) {
34772
+ if (!blockWasActive(job) && testLayer(job.catalog, opts)) {
34773
+ enterActiveBranch(job);
34754
34774
  } else {
34755
- enterInactiveBranch();
34775
+ enterInactiveBranch(job);
34756
34776
  }
34757
34777
  }
34758
34778
 
@@ -34812,7 +34832,7 @@ ${svg}
34812
34832
  obj = content;
34813
34833
  }
34814
34834
 
34815
- utils.extend(getStateVar('defs'), obj);
34835
+ utils.extend(getStashedVar('defs'), obj);
34816
34836
  };
34817
34837
 
34818
34838
  var MAX_RULE_LEN = 50;
@@ -34871,7 +34891,7 @@ ${svg}
34871
34891
  null_shape_count: 0,
34872
34892
  null_data_count: lyr.data ? countNullRecords(lyr.data.getRecords()) : n
34873
34893
  };
34874
- if (lyr.shapes) {
34894
+ if (lyr.shapes && lyr.shapes.length > 0) {
34875
34895
  o.null_shape_count = countNullShapes(lyr.shapes);
34876
34896
  o.bbox =getLayerBounds(lyr, dataset.arcs).toArray();
34877
34897
  o.proj4 = getProjInfo(dataset);
@@ -37144,7 +37164,7 @@ ${svg}
37144
37164
  parseConsoleCommands: parseConsoleCommands
37145
37165
  });
37146
37166
 
37147
- cmd.run = function(targets, catalog, opts, cb) {
37167
+ cmd.run = function(job, targets, opts, cb) {
37148
37168
  var commandStr, commands;
37149
37169
  if (opts.include) {
37150
37170
  cmd.include({file: opts.include});
@@ -37155,7 +37175,7 @@ ${svg}
37155
37175
  commandStr = runGlobalExpression(opts.commands, targets);
37156
37176
  if (commandStr) {
37157
37177
  commands = parseCommands(commandStr);
37158
- runParsedCommands(commands, catalog, cb);
37178
+ runParsedCommands(commands, job, cb);
37159
37179
  } else {
37160
37180
  cb(null);
37161
37181
  }
@@ -37169,7 +37189,7 @@ ${svg}
37169
37189
  targetData = getRunCommandData(targets[0]);
37170
37190
  Object.defineProperty(ctx, 'target', {value: targetData});
37171
37191
  }
37172
- utils.extend(ctx, getStateVar('defs'));
37192
+ utils.extend(ctx, getStashedVar('defs'));
37173
37193
  try {
37174
37194
  output = Function('ctx', 'with(ctx) {return (' + expression + ');}').call({}, ctx);
37175
37195
  } catch(e) {
@@ -37188,7 +37208,7 @@ ${svg}
37188
37208
  }
37189
37209
 
37190
37210
  cmd.require = function(targets, opts) {
37191
- var defs = getStateVar('defs');
37211
+ var defs = getStashedVar('defs');
37192
37212
  var moduleFile, moduleName, mod;
37193
37213
  if (!opts.module) {
37194
37214
  stop("Missing module name or path to module");
@@ -39490,7 +39510,7 @@ ${svg}
39490
39510
  return [lyr1, lyr2];
39491
39511
  }
39492
39512
 
39493
- function runCommand(command, catalog, cb) {
39513
+ function runCommand(command, job, cb) {
39494
39514
  var name = command.name,
39495
39515
  opts = command.options,
39496
39516
  source,
@@ -39503,18 +39523,20 @@ ${svg}
39503
39523
  target,
39504
39524
  arcs;
39505
39525
 
39506
- if (skipCommand(name)) {
39526
+ if (skipCommand(name, job)) {
39507
39527
  return done(null);
39508
39528
  }
39509
39529
 
39530
+ if (!job) job = new Job();
39531
+ job.startCommand(command);
39532
+
39510
39533
  try { // catch errors from synchronous functions
39511
39534
 
39512
39535
  T$1.start();
39513
- if (!catalog) catalog = new Catalog();
39514
39536
 
39515
39537
  if (name == 'rename-layers') {
39516
39538
  // default target is all layers
39517
- targets = catalog.findCommandTargets(opts.target || '*');
39539
+ targets = job.catalog.findCommandTargets(opts.target || '*');
39518
39540
  targetLayers = targets.reduce(function(memo, obj) {
39519
39541
  return memo.concat(obj.layers);
39520
39542
  }, []);
@@ -39522,19 +39544,19 @@ ${svg}
39522
39544
  } else if (name == 'o') {
39523
39545
  // when combining GeoJSON layers, default is all layers
39524
39546
  // TODO: check that combine_layers is only used w/ GeoJSON output
39525
- targets = catalog.findCommandTargets(opts.target || opts.combine_layers && '*');
39547
+ targets = job.catalog.findCommandTargets(opts.target || opts.combine_layers && '*');
39526
39548
 
39527
39549
  } else if (name == 'rotate' || name == 'info' || name == 'proj' || name == 'drop' || name == 'target') {
39528
39550
  // these commands accept multiple target datasets
39529
- targets = catalog.findCommandTargets(opts.target);
39551
+ targets = job.catalog.findCommandTargets(opts.target);
39530
39552
 
39531
39553
  } else {
39532
- targets = catalog.findCommandTargets(opts.target);
39554
+ targets = job.catalog.findCommandTargets(opts.target);
39533
39555
 
39534
39556
  // special case to allow -merge-layers and -union to combine layers from multiple datasets
39535
39557
  // TODO: support multi-dataset targets for other commands
39536
39558
  if (targets.length > 1 && (name == 'merge-layers' || name == 'union')) {
39537
- targets = mergeCommandTargets(targets, catalog);
39559
+ targets = mergeCommandTargets(targets, job.catalog);
39538
39560
  }
39539
39561
 
39540
39562
  if (targets.length == 1) {
@@ -39542,7 +39564,7 @@ ${svg}
39542
39564
  arcs = targetDataset.arcs;
39543
39565
  targetLayers = targets[0].layers;
39544
39566
  // target= option sets default target
39545
- catalog.setDefaultTarget(targetLayers, targetDataset);
39567
+ job.catalog.setDefaultTarget(targetLayers, targetDataset);
39546
39568
 
39547
39569
  } else if (targets.length > 1) {
39548
39570
  stop("This command does not support targetting layers from different datasets");
@@ -39552,7 +39574,7 @@ ${svg}
39552
39574
  if (targets.length === 0) {
39553
39575
  if (opts.target) {
39554
39576
  stop(utils.format('Missing target: %s\nAvailable layers: %s',
39555
- opts.target, getFormattedLayerList(catalog)));
39577
+ opts.target, getFormattedLayerList(job.catalog)));
39556
39578
  }
39557
39579
  if (!(name == 'graticule' || name == 'i' || name == 'help' ||
39558
39580
  name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
@@ -39562,7 +39584,7 @@ ${svg}
39562
39584
  }
39563
39585
 
39564
39586
  if (opts.source) {
39565
- source = findCommandSource(convertSourceName(opts.source, targets), catalog, opts);
39587
+ source = findCommandSource(convertSourceName(opts.source, targets), job.catalog, opts);
39566
39588
  }
39567
39589
 
39568
39590
  if (name == 'affine') {
@@ -39616,7 +39638,7 @@ ${svg}
39616
39638
  outputLayers = applyCommandToEachLayer(cmd.dots, targetLayers, arcs, opts);
39617
39639
 
39618
39640
  } else if (name == 'drop') {
39619
- cmd.drop2(catalog, targets, opts);
39641
+ cmd.drop2(job.catalog, targets, opts);
39620
39642
  // cmd.drop(catalog, targetLayers, targetDataset, opts);
39621
39643
 
39622
39644
  } else if (name == 'each') {
@@ -39653,33 +39675,33 @@ ${svg}
39653
39675
  applyCommandToEachLayer(cmd.filterSlivers, targetLayers, targetDataset, opts);
39654
39676
 
39655
39677
  } else if (name == 'frame') {
39656
- cmd.frame(catalog, source, opts);
39678
+ cmd.frame(job.catalog, source, opts);
39657
39679
 
39658
39680
  } else if (name == 'fuzzy-join') {
39659
39681
  applyCommandToEachLayer(cmd.fuzzyJoin, targetLayers, arcs, source, opts);
39660
39682
 
39661
39683
  } else if (name == 'graticule') {
39662
- catalog.addDataset(cmd.graticule(targetDataset, opts));
39684
+ job.catalog.addDataset(cmd.graticule(targetDataset, opts));
39663
39685
 
39664
39686
  } else if (name == 'help') {
39665
39687
  // placing this here to handle errors from invalid command names
39666
39688
  getOptionParser().printHelp(command.options.command);
39667
39689
 
39668
39690
  } else if (name == 'i') {
39669
- if (opts.replace) catalog = new Catalog();
39691
+ if (opts.replace) job.catalog = new Catalog(); // is this what we want?
39670
39692
  targetDataset = cmd.importFiles(command.options);
39671
39693
  if (targetDataset) {
39672
- catalog.addDataset(targetDataset);
39694
+ job.catalog.addDataset(targetDataset);
39673
39695
  outputLayers = targetDataset.layers; // kludge to allow layer naming below
39674
39696
  }
39675
39697
 
39676
39698
  } else if (name == 'if' || name == 'elif') {
39677
39699
  // target = findSingleTargetLayer(opts.layer, targets[0], catalog);
39678
39700
  // cmd[name](target.layer, target.dataset, opts);
39679
- cmd[name](catalog, opts);
39701
+ cmd[name](job, opts);
39680
39702
 
39681
39703
  } else if (name == 'else' || name == 'endif') {
39682
- cmd[name]();
39704
+ cmd[name](job);
39683
39705
 
39684
39706
  } else if (name == 'ignore') {
39685
39707
  applyCommandToEachLayer(cmd.ignore, targetLayers, targetDataset, opts);
@@ -39719,14 +39741,16 @@ ${svg}
39719
39741
  outputFiles = exportTargetLayers(targets, opts);
39720
39742
  if (opts.final) {
39721
39743
  // don't propagate data if output is final
39722
- catalog = null;
39744
+ //// catalog = null;
39745
+ job.catalog = new Catalog();
39723
39746
  }
39724
- return writeFiles(outputFiles, opts, done);
39747
+ writeFiles(outputFiles, opts, done);
39748
+ return; // async command
39725
39749
 
39726
39750
  } else if (name == 'point-grid') {
39727
39751
  outputLayers = [cmd.pointGrid(targetDataset, opts)];
39728
39752
  if (!targetDataset) {
39729
- catalog.addDataset({layers: outputLayers});
39753
+ job.catalog.addDataset({layers: outputLayers});
39730
39754
  }
39731
39755
 
39732
39756
  } else if (name == 'point-to-grid') {
@@ -39746,10 +39770,11 @@ ${svg}
39746
39770
 
39747
39771
  } else if (name == 'proj') {
39748
39772
  initProjLibrary(opts, function() {
39773
+ job.resumeCommand();
39749
39774
  var err = null;
39750
39775
  try {
39751
39776
  targets.forEach(function(targ) {
39752
- cmd.proj(targ.dataset, catalog, opts);
39777
+ cmd.proj(targ.dataset, job.catalog, opts);
39753
39778
  });
39754
39779
  } catch(e) {
39755
39780
  err = e;
@@ -39760,7 +39785,7 @@ ${svg}
39760
39785
 
39761
39786
  } else if (name == 'rectangle') {
39762
39787
  if (source || opts.bbox || targets.length === 0) {
39763
- catalog.addDataset(cmd.rectangle(source, opts));
39788
+ job.catalog.addDataset(cmd.rectangle(source, opts));
39764
39789
  } else {
39765
39790
  outputLayers = cmd.rectangle2(targets[0], opts);
39766
39791
  }
@@ -39772,7 +39797,7 @@ ${svg}
39772
39797
  applyCommandToEachLayer(cmd.renameFields, targetLayers, opts.fields);
39773
39798
 
39774
39799
  } else if (name == 'rename-layers') {
39775
- cmd.renameLayers(targetLayers, opts.names, catalog);
39800
+ cmd.renameLayers(targetLayers, opts.names, job.catalog);
39776
39801
 
39777
39802
  } else if (name == 'require') {
39778
39803
  cmd.require(targets, opts);
@@ -39783,14 +39808,14 @@ ${svg}
39783
39808
  });
39784
39809
 
39785
39810
  } else if (name == 'run') {
39786
- cmd.run(targets, catalog, opts, done);
39787
- return;
39811
+ cmd.run(job, targets, opts, done);
39812
+ return; // async command
39788
39813
 
39789
39814
  } else if (name == 'scalebar') {
39790
- cmd.scalebar(catalog, opts);
39815
+ cmd.scalebar(job.catalog, opts);
39791
39816
 
39792
39817
  } else if (name == 'shape') {
39793
- catalog.addDataset(cmd.shape(targetDataset, opts));
39818
+ job.catalog.addDataset(cmd.shape(targetDataset, opts));
39794
39819
 
39795
39820
  } else if (name == 'shapes') {
39796
39821
  outputLayers = applyCommandToEachLayer(cmd.shapes, targetLayers, targetDataset, opts);
@@ -39830,7 +39855,7 @@ ${svg}
39830
39855
  outputLayers = applyCommandToEachLayer(cmd.subdivideLayer, targetLayers, arcs, opts.expression);
39831
39856
 
39832
39857
  } else if (name == 'target') {
39833
- cmd.target(catalog, opts);
39858
+ cmd.target(job.catalog, opts);
39834
39859
 
39835
39860
  } else if (name == 'union') {
39836
39861
  outputLayers = cmd.union(targetLayers, targetDataset, opts);
@@ -39840,7 +39865,7 @@ ${svg}
39840
39865
 
39841
39866
  } else {
39842
39867
  // throws error if command is not registered
39843
- cmd.runExternalCommand(command, catalog);
39868
+ cmd.runExternalCommand(command, job.catalog);
39844
39869
  }
39845
39870
 
39846
39871
  // apply name parameter
@@ -39852,12 +39877,12 @@ ${svg}
39852
39877
  }
39853
39878
 
39854
39879
  if (outputDataset) {
39855
- catalog.addDataset(outputDataset); // also sets default target
39880
+ job.catalog.addDataset(outputDataset); // also sets default target
39856
39881
  outputLayers = outputDataset.layers;
39857
39882
  if (targetLayers && !opts.no_replace) {
39858
39883
  // remove target layers from target dataset
39859
39884
  targetLayers.forEach(function(lyr) {
39860
- catalog.deleteLayer(lyr, targetDataset);
39885
+ job.catalog.deleteLayer(lyr, targetDataset);
39861
39886
  });
39862
39887
  }
39863
39888
  } else if (outputLayers && targetDataset && outputLayers != targetDataset.layers) {
@@ -39880,7 +39905,7 @@ ${svg}
39880
39905
  }
39881
39906
 
39882
39907
  if (opts.apart) {
39883
- catalog.setDefaultTargets(splitApartLayers( targetDataset, outputLayers).map(function(dataset) {
39908
+ job.catalog.setDefaultTargets(splitApartLayers( targetDataset, outputLayers).map(function(dataset) {
39884
39909
  return {
39885
39910
  dataset: dataset,
39886
39911
  layers: dataset.layers.concat()
@@ -39888,9 +39913,8 @@ ${svg}
39888
39913
  }));
39889
39914
  } else {
39890
39915
  // use command output as new default target
39891
- catalog.setDefaultTarget(outputLayers, targetDataset);
39916
+ job.catalog.setDefaultTarget(outputLayers, targetDataset);
39892
39917
  }
39893
-
39894
39918
  }
39895
39919
 
39896
39920
  // delete arcs if no longer needed (e.g. after -points command)
@@ -39902,11 +39926,13 @@ ${svg}
39902
39926
  return done(e);
39903
39927
  }
39904
39928
 
39929
+ // non-erroring synchronous commands are done
39905
39930
  done(null);
39906
39931
 
39907
39932
  function done(err) {
39933
+ job.endCommand();
39908
39934
  verbose('-', T$1.stop());
39909
- cb(err, err ? null : catalog);
39935
+ cb(err, err ? null : job);
39910
39936
  }
39911
39937
  }
39912
39938
 
@@ -39916,7 +39942,6 @@ ${svg}
39916
39942
  });
39917
39943
  }
39918
39944
 
39919
-
39920
39945
  // Apply a command to an array of target layers
39921
39946
  function applyCommandToEachLayer(func, targetLayers) {
39922
39947
  var args = utils.toArray(arguments).slice(2);
@@ -40050,7 +40075,7 @@ ${svg}
40050
40075
  }
40051
40076
 
40052
40077
  // add options to -i -o -join -clip -erase commands to bypass file i/o
40053
- // TODO: find a less kludgy solution, e.g. storing input data using setStateVar()
40078
+ // TODO: find a less kludgy solution
40054
40079
  commands.forEach(function(cmd) {
40055
40080
  if (commandTakesFileInput(cmd.name) && inputObj) {
40056
40081
  cmd.options.input = inputObj;
@@ -40096,8 +40121,8 @@ ${svg}
40096
40121
 
40097
40122
  // TODO: rewrite tests and remove this function
40098
40123
  function testCommands(argv, done) {
40099
- _runCommands(argv, {}, function(err, catalog) {
40100
- var targets = catalog ? catalog.getDefaultTargets() : [];
40124
+ _runCommands(argv, {}, function(err, job) {
40125
+ var targets = job ? job.catalog.getDefaultTargets() : [];
40101
40126
  var output;
40102
40127
  if (!err && targets.length > 0) {
40103
40128
  // returns dataset for compatibility with some older tests
@@ -40109,15 +40134,12 @@ ${svg}
40109
40134
 
40110
40135
  // Execute a sequence of parsed commands
40111
40136
  // @commands Array of parsed commands
40112
- // @catalog: Optional Catalog object containing previously imported data
40137
+ // @job: Optional Job object containing previously imported data
40113
40138
  // @cb: function(<error>, <catalog>)
40114
40139
  //
40115
- function runParsedCommands(commands, catalog, cb) {
40116
- if (!catalog) {
40117
- cb = createAsyncContext(cb); // use new context when creating new catalog
40118
- catalog = new Catalog();
40119
- } else if (catalog instanceof Catalog === false) {
40120
- error("Changed in v0.4: runParsedCommands() takes a Catalog object");
40140
+ function runParsedCommands(commands, job, cb) {
40141
+ if (!job) {
40142
+ job = new Job();
40121
40143
  }
40122
40144
 
40123
40145
  if (!utils.isArray(commands)) {
@@ -40127,7 +40149,7 @@ ${svg}
40127
40149
  if (commands.length === 0) {
40128
40150
  return done(new UserError("No commands to run"));
40129
40151
  }
40130
- commands = readAndRemoveSettings(commands);
40152
+ commands = readAndRemoveSettings(job, commands);
40131
40153
  if (!runningInBrowser()) {
40132
40154
  printStartupMessages();
40133
40155
  }
@@ -40144,25 +40166,22 @@ ${svg}
40144
40166
  var groups = divideImportCommand(commands);
40145
40167
  if (groups.length == 1) {
40146
40168
  // run a simple sequence of commands (input files are not batched)
40147
- return runParsedCommands2(commands, catalog, done);
40169
+ return runParsedCommands2(commands, job, done);
40148
40170
  }
40149
40171
 
40150
40172
  // run duplicated commands (i.e. batch mode)
40151
- utils.reduceAsync(groups, catalog, nextGroup, done);
40173
+ utils.reduceAsync(groups, job, nextGroup, done);
40152
40174
 
40153
- function nextGroup(catalog, commands, next) {
40154
- runParsedCommands2(commands, catalog, function(err, catalog) {
40175
+ function nextGroup(job, commands, next) {
40176
+ runParsedCommands2(commands, job, function(err, job) {
40155
40177
  err = filterError(err);
40156
- next(err, catalog);
40178
+ next(err, job);
40157
40179
  });
40158
40180
  }
40159
40181
 
40160
- function done(err, catalog) {
40182
+ function done(err, job) {
40161
40183
  err = filterError(err);
40162
- cb(err, catalog);
40163
- setStateVar('current_command', null);
40164
- setStateVar('verbose', false);
40165
- setStateVar('debug', false);
40184
+ cb(err, job);
40166
40185
  }
40167
40186
  }
40168
40187
 
@@ -40174,16 +40193,13 @@ ${svg}
40174
40193
  return err;
40175
40194
  }
40176
40195
 
40177
- function runParsedCommands2(commands, catalog, cb) {
40196
+ function runParsedCommands2(commands, job, cb) {
40178
40197
  // resetting closes any unterminated -if blocks from a previous command sequence
40179
- resetControlFlow();
40180
- utils.reduceAsync(commands, catalog, nextCommand, cb);
40198
+ resetControlFlow(job);
40199
+ utils.reduceAsync(commands, job, nextCommand, cb);
40181
40200
 
40182
- function nextCommand(catalog, cmd, next) {
40183
- setStateVar('current_command', cmd.name); // for log msgs
40184
- setStateVar('verbose', !!cmd.options.verbose);
40185
- setStateVar('debug', !!cmd.options.debug);
40186
- runCommand(cmd, catalog, next);
40201
+ function nextCommand(job, cmd, next) {
40202
+ runCommand(cmd, job, next);
40187
40203
  }
40188
40204
  }
40189
40205
 
@@ -40229,19 +40245,22 @@ ${svg}
40229
40245
  }
40230
40246
 
40231
40247
  // Some settings use command syntax and are parsed as commands.
40232
- function readAndRemoveSettings(commands) {
40233
- return commands.filter(function(cmd) {
40248
+ function readAndRemoveSettings(job, commands) {
40249
+ var settings = {VERBOSE: false, QUIET: false, DEBUG: false};
40250
+ var filtered = commands.filter(function(cmd) {
40234
40251
  if (cmd.name == 'verbose') {
40235
- setStateVar('VERBOSE', true);
40252
+ settings.VERBOSE = true;
40236
40253
  } else if (cmd.name == 'quiet') {
40237
- setStateVar('QUIET', true);
40254
+ settings.QUIET = true;
40238
40255
  } else if (cmd.name == 'debug') {
40239
- setStateVar('DEBUG', true);
40256
+ settings.DEBUG = true;
40240
40257
  } else {
40241
40258
  return true;
40242
40259
  }
40243
40260
  return false;
40244
40261
  });
40262
+ job.initSettings(settings);
40263
+ return filtered;
40245
40264
  }
40246
40265
 
40247
40266
  // Run informational commands and remove them from the array of parsed commands
@@ -40593,6 +40612,7 @@ ${svg}
40593
40612
  // to maintain compatibility with tests and to expose (some of) them to the GUI.
40594
40613
 
40595
40614
  Object.assign(internal, {
40615
+ Job,
40596
40616
  Dbf,
40597
40617
  DbfReader,
40598
40618
  DouglasPeucker,
@@ -40716,6 +40736,7 @@ ${svg}
40716
40736
  SourceUtils,
40717
40737
  Split,
40718
40738
  State,
40739
+ Stash,
40719
40740
  Stringify,
40720
40741
  Svg,
40721
40742
  SvgProperties,