sdocs-dev 1.15.0 → 1.19.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.
- package/bin/sdocs-dev.js +52 -17
- package/lib/agent-block.js +270 -50
- package/lib/agent-files.js +312 -74
- package/lib/cells-verify.js +1 -0
- package/lib/cloud-bindings.js +85 -0
- package/lib/cloud-commands.js +826 -0
- package/lib/cloud-credentials.js +380 -0
- package/lib/commands.js +46 -5
- package/lib/help-text.js +301 -70
- package/lib/io.js +53 -5
- package/lib/library-commands.js +6 -15
- package/lib/library-scan.js +16 -6
- package/lib/library-server.js +4 -12
- package/lib/setup.js +182 -191
- package/lib/short-link.js +38 -1
- package/lib/slides-verify.js +109 -0
- package/package.json +1 -1
- package/shared/sdocs-cells-formula.js +547 -73
- package/shared/sdocs-cells.js +164 -18
- package/shared/sdocs-shapes.js +1044 -0
- package/shared/sdocs-slide-resolve.js +258 -0
- package/shared/sdocs-slide-stdlib.js +180 -0
- package/shared/sdocs-styles.js +1 -1
package/shared/sdocs-cells.js
CHANGED
|
@@ -140,11 +140,11 @@
|
|
|
140
140
|
|
|
141
141
|
// Peel leading directive lines (in any order, machine or author):
|
|
142
142
|
// sdoc-cells: source=... range=... error=... name=... (baked metadata)
|
|
143
|
-
// format: A=$
|
|
143
|
+
// format: A=$ 2=% C3=plain (column, row, cell formats)
|
|
144
144
|
// `name` is the tab name. Authored as the fence info string (```cells
|
|
145
145
|
// Sales); the renderer + CLI normalise that into this directive so the
|
|
146
146
|
// name has one home in the model.
|
|
147
|
-
var source, range, formats, name, workbook;
|
|
147
|
+
var source, range, formats, rowFormats, cellFormats, defaultFormat, name, workbook;
|
|
148
148
|
var lines = trimmed.split('\n');
|
|
149
149
|
var idx = 0;
|
|
150
150
|
var FORMAT_RE = /^format:\s*(.*)$/i;
|
|
@@ -159,15 +159,22 @@
|
|
|
159
159
|
if (meta.error) return { empty: false, error: meta.error, source: source, name: name, workbook: workbook };
|
|
160
160
|
idx++; continue;
|
|
161
161
|
}
|
|
162
|
-
if (fm) {
|
|
162
|
+
if (fm) {
|
|
163
|
+
var rules = parseFormatRules(fm[1]);
|
|
164
|
+
formats = mergeFormats(formats, rules.columns);
|
|
165
|
+
rowFormats = mergeFormats(rowFormats, rules.rows);
|
|
166
|
+
cellFormats = mergeFormats(cellFormats, rules.cells);
|
|
167
|
+
if (rules.defaultFormat) defaultFormat = rules.defaultFormat;
|
|
168
|
+
idx++; continue;
|
|
169
|
+
}
|
|
163
170
|
break;
|
|
164
171
|
}
|
|
165
172
|
var body = lines.slice(idx).join('\n').replace(/\s+$/, '');
|
|
166
173
|
|
|
167
174
|
// Unresolved reference (after directives): the CLI never baked it.
|
|
168
175
|
var ref = body.match(REFERENCE_RE);
|
|
169
|
-
if (ref) return { empty: false, unresolved: ref[1], formats: formats, name: name, workbook: workbook };
|
|
170
|
-
if (body === '') return { rows: 0, cols: 0, cells: [], empty: true, source: source, range: range, formats: formats, name: name, workbook: workbook };
|
|
176
|
+
if (ref) return { empty: false, unresolved: ref[1], formats: formats, rowFormats: rowFormats, cellFormats: cellFormats, defaultFormat: defaultFormat, name: name, workbook: workbook };
|
|
177
|
+
if (body === '') return { rows: 0, cols: 0, cells: [], empty: true, source: source, range: range, formats: formats, rowFormats: rowFormats, cellFormats: cellFormats, defaultFormat: defaultFormat, name: name, workbook: workbook };
|
|
171
178
|
|
|
172
179
|
var raw = parseCsv(body);
|
|
173
180
|
var cols = 0;
|
|
@@ -183,7 +190,7 @@
|
|
|
183
190
|
}
|
|
184
191
|
cells.push(out);
|
|
185
192
|
}
|
|
186
|
-
return { rows: raw.length, cols: cols, cells: cells, empty: false, source: source, range: range, formats: formats, name: name, workbook: workbook };
|
|
193
|
+
return { rows: raw.length, cols: cols, cells: cells, empty: false, source: source, range: range, formats: formats, rowFormats: rowFormats, cellFormats: cellFormats, defaultFormat: defaultFormat, name: name, workbook: workbook };
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
// Serialize a 2D array of raw cell strings back to CSV (RFC 4180 quoting:
|
|
@@ -261,6 +268,9 @@
|
|
|
261
268
|
function sortKey(cell, fxCell) {
|
|
262
269
|
if (fxCell && fxCell.kind === 'number') return { rank: 0, v: fxCell.value };
|
|
263
270
|
if (fxCell && fxCell.kind === 'error') return { rank: 1, v: String(fxCell.code || '').toLowerCase() };
|
|
271
|
+
if (fxCell && (fxCell.kind === 'text' || fxCell.kind === 'boolean')) {
|
|
272
|
+
return { rank: 1, v: String(fxCell.value).toLowerCase() };
|
|
273
|
+
}
|
|
264
274
|
if (!cell || cell.type === 'empty') return { rank: 2, v: 0 };
|
|
265
275
|
if (cell.type === 'number') return { rank: 0, v: cell.value };
|
|
266
276
|
return { rank: 1, v: String(cell.value).toLowerCase() };
|
|
@@ -341,7 +351,10 @@
|
|
|
341
351
|
var t = String(tok).trim();
|
|
342
352
|
var decimals = null;
|
|
343
353
|
var dm = t.match(/\.(\d+)$/);
|
|
344
|
-
if (dm) {
|
|
354
|
+
if (dm) {
|
|
355
|
+
decimals = Math.min(30, parseInt(dm[1], 10));
|
|
356
|
+
t = t.slice(0, t.length - dm[0].length);
|
|
357
|
+
}
|
|
345
358
|
var lc = t.toLowerCase();
|
|
346
359
|
if (t === '$' || lc === 'usd' || lc === 'currency') return { kind: 'currency', symbol: '$', decimals: decimals == null ? 2 : decimals };
|
|
347
360
|
if (t === '£' || lc === 'gbp') return { kind: 'currency', symbol: '£', decimals: decimals == null ? 2 : decimals };
|
|
@@ -356,15 +369,138 @@
|
|
|
356
369
|
// Parse a per-column format spec like "A=plain B=$ C=%.1" into a map
|
|
357
370
|
// { colIndex: fmt }. Keys are column letters; unknown tokens are skipped.
|
|
358
371
|
function parseFormats(spec) {
|
|
372
|
+
return parseFormatRules(spec).columns;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function mergeFormats(base, extra) {
|
|
376
|
+
var out = base || {};
|
|
377
|
+
Object.keys(extra || {}).forEach(function (key) { out[key] = extra[key]; });
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Parse one compact format directive. Targets mirror spreadsheet addresses:
|
|
382
|
+
// *=.2 whole sheet
|
|
383
|
+
// B=$ column B
|
|
384
|
+
// 4=% row 4
|
|
385
|
+
// C7=plain cell C7
|
|
386
|
+
// Specific targets override broader ones when resolveFormat() is called.
|
|
387
|
+
function parseFormatRules(spec) {
|
|
359
388
|
var out = {};
|
|
360
|
-
var
|
|
389
|
+
var rows = {};
|
|
390
|
+
var cells = {};
|
|
391
|
+
var defaultFormat = null;
|
|
392
|
+
// Both sides must end at whitespace boundaries. Without these anchors a
|
|
393
|
+
// typo such as A-2=% could be recovered as the unrelated valid rule 2=%.
|
|
394
|
+
var re = /(^|\s)(\*|[A-Za-z]+[1-9]\d*|[A-Za-z]+|[1-9]\d*)\s*=\s*(\S+)(?=\s|$)/g;
|
|
361
395
|
var m;
|
|
362
|
-
while ((m = re.exec(spec))) {
|
|
363
|
-
var
|
|
364
|
-
var fmt = parseFmtToken(m[
|
|
365
|
-
if (
|
|
396
|
+
while ((m = re.exec(String(spec)))) {
|
|
397
|
+
var target = m[2];
|
|
398
|
+
var fmt = parseFmtToken(m[3]);
|
|
399
|
+
if (!fmt) continue;
|
|
400
|
+
if (target === '*') { defaultFormat = fmt; continue; }
|
|
401
|
+
if (/^[1-9]\d*$/.test(target)) { rows[parseInt(target, 10) - 1] = fmt; continue; }
|
|
402
|
+
var cell = target.match(/^([A-Za-z]+)([1-9]\d*)$/);
|
|
403
|
+
if (cell) {
|
|
404
|
+
var cellCol = colIndex(cell[1]);
|
|
405
|
+
if (cellCol >= 0) cells[(parseInt(cell[2], 10) - 1) + ':' + cellCol] = fmt;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
var col = colIndex(target);
|
|
409
|
+
if (col >= 0) out[col] = fmt;
|
|
366
410
|
}
|
|
367
|
-
return out;
|
|
411
|
+
return { columns: out, rows: rows, cells: cells, defaultFormat: defaultFormat };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Resolve display/export formatting with predictable spreadsheet precedence:
|
|
415
|
+
// built-in two-decimal maximum < whole sheet < column < row < cell.
|
|
416
|
+
function resolveFormat(model, row, col) {
|
|
417
|
+
var fmt = { kind: 'number', decimals: 2, trim: true };
|
|
418
|
+
if (model && model.defaultFormat) fmt = model.defaultFormat;
|
|
419
|
+
if (model && model.formats && model.formats[col]) fmt = model.formats[col];
|
|
420
|
+
if (model && model.rowFormats && model.rowFormats[row]) fmt = model.rowFormats[row];
|
|
421
|
+
if (model && model.cellFormats && model.cellFormats[row + ':' + col]) fmt = model.cellFormats[row + ':' + col];
|
|
422
|
+
return fmt;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Parse a decimal string without passing through binary floating point.
|
|
426
|
+
// Formula values may arrive in exponent form; literal cells never do.
|
|
427
|
+
function decimalParts(raw) {
|
|
428
|
+
var s = String(raw == null ? '' : raw).trim();
|
|
429
|
+
var negative = s.charAt(0) === '-';
|
|
430
|
+
if (negative || s.charAt(0) === '+') s = s.slice(1);
|
|
431
|
+
var m = s.match(/^(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/);
|
|
432
|
+
if (!m) return null;
|
|
433
|
+
var integer = m[1];
|
|
434
|
+
var fraction = m[2] || '';
|
|
435
|
+
var exponent = m[3] ? parseInt(m[3], 10) : 0;
|
|
436
|
+
if (exponent) {
|
|
437
|
+
var digits = integer + fraction;
|
|
438
|
+
var point = integer.length + exponent;
|
|
439
|
+
if (point <= 0) {
|
|
440
|
+
integer = '0';
|
|
441
|
+
fraction = new Array(-point + 1).join('0') + digits;
|
|
442
|
+
} else if (point >= digits.length) {
|
|
443
|
+
integer = digits + new Array(point - digits.length + 1).join('0');
|
|
444
|
+
fraction = '';
|
|
445
|
+
} else {
|
|
446
|
+
integer = digits.slice(0, point);
|
|
447
|
+
fraction = digits.slice(point);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
integer = integer.replace(/^0+(?=\d)/, '');
|
|
451
|
+
return { negative: negative, integer: integer || '0', fraction: fraction };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function incrementDigits(digits) {
|
|
455
|
+
var chars = digits.split('');
|
|
456
|
+
for (var i = chars.length - 1; i >= 0; i--) {
|
|
457
|
+
if (chars[i] !== '9') {
|
|
458
|
+
chars[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1);
|
|
459
|
+
return chars.join('');
|
|
460
|
+
}
|
|
461
|
+
chars[i] = '0';
|
|
462
|
+
}
|
|
463
|
+
return '1' + chars.join('');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Round half away from zero, matching spreadsheet display semantics. The
|
|
467
|
+
// returned string is exact even for integers beyond Number.MAX_SAFE_INTEGER.
|
|
468
|
+
function roundDecimalString(raw, decimals, trim) {
|
|
469
|
+
var parts = decimalParts(raw);
|
|
470
|
+
if (!parts) return null;
|
|
471
|
+
var d = Math.max(0, Math.min(30, decimals));
|
|
472
|
+
var fraction = parts.fraction.slice(0, d);
|
|
473
|
+
while (fraction.length < d) fraction += '0';
|
|
474
|
+
var digits = parts.integer + fraction;
|
|
475
|
+
if ((parts.fraction.charAt(d) || '0') >= '5') digits = incrementDigits(digits);
|
|
476
|
+
if (digits.length <= d) digits = new Array(d - digits.length + 2).join('0') + digits;
|
|
477
|
+
var split = digits.length - d;
|
|
478
|
+
var integer = d ? digits.slice(0, split) : digits;
|
|
479
|
+
fraction = d ? digits.slice(split) : '';
|
|
480
|
+
integer = integer.replace(/^0+(?=\d)/, '') || '0';
|
|
481
|
+
if (trim) fraction = fraction.replace(/0+$/, '');
|
|
482
|
+
var zero = /^0+$/.test(integer) && (!fraction || /^0+$/.test(fraction));
|
|
483
|
+
return (parts.negative && !zero ? '-' : '') + integer + (fraction ? '.' + fraction : '');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function shiftDecimalString(raw, places) {
|
|
487
|
+
var parts = decimalParts(raw);
|
|
488
|
+
if (!parts) return null;
|
|
489
|
+
var digits = parts.integer + parts.fraction;
|
|
490
|
+
var point = parts.integer.length + places;
|
|
491
|
+
var out;
|
|
492
|
+
if (point <= 0) out = '0.' + new Array(-point + 1).join('0') + digits;
|
|
493
|
+
else if (point >= digits.length) out = digits + new Array(point - digits.length + 1).join('0');
|
|
494
|
+
else out = digits.slice(0, point) + '.' + digits.slice(point);
|
|
495
|
+
return (parts.negative ? '-' : '') + out;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Excel calculates to 15 significant decimal digits. Formula results arrive
|
|
499
|
+
// here as IEEE Numbers, whose shortest JS string can expose a 16th or 17th
|
|
500
|
+
// binary-conversion digit when a user requests a high-precision format.
|
|
501
|
+
function displayRaw(cell) {
|
|
502
|
+
if (!cell.computed || !isFinite(cell.value) || cell.value === 0) return cell.raw;
|
|
503
|
+
return Number(cell.value).toPrecision(15);
|
|
368
504
|
}
|
|
369
505
|
|
|
370
506
|
// Format a numeric cell's display per a column format. Returns null for
|
|
@@ -372,18 +508,26 @@
|
|
|
372
508
|
// the model's raw is untouched, so copy / export emit the original.
|
|
373
509
|
function formatValue(cell, fmt) {
|
|
374
510
|
if (!cell || cell.type !== 'number') return null;
|
|
375
|
-
var
|
|
511
|
+
var raw = displayRaw(cell);
|
|
376
512
|
if (!fmt || fmt.kind === 'number') {
|
|
377
|
-
|
|
513
|
+
var decimals = !fmt ? 2 : fmt.decimals;
|
|
514
|
+
var numberText = decimals != null
|
|
515
|
+
? roundDecimalString(raw, decimals, !fmt || fmt.trim)
|
|
516
|
+
: raw;
|
|
517
|
+
return formatNumber(numberText);
|
|
378
518
|
}
|
|
379
519
|
if (fmt.kind === 'plain') return cell.raw;
|
|
380
520
|
if (fmt.kind === 'currency') {
|
|
381
521
|
var d = fmt.decimals == null ? 2 : fmt.decimals;
|
|
382
|
-
|
|
522
|
+
var money = roundDecimalString(raw, d, false);
|
|
523
|
+
var moneyNegative = money && money.charAt(0) === '-';
|
|
524
|
+
if (moneyNegative) money = money.slice(1);
|
|
525
|
+
return (moneyNegative ? '-' : '') + fmt.symbol + formatNumber(money);
|
|
383
526
|
}
|
|
384
527
|
if (fmt.kind === 'percent') {
|
|
385
|
-
var
|
|
386
|
-
var str = fmt.decimals
|
|
528
|
+
var shifted = shiftDecimalString(raw, 2);
|
|
529
|
+
var str = roundDecimalString(shifted, fmt.decimals == null ? 2 : fmt.decimals,
|
|
530
|
+
fmt.decimals == null);
|
|
387
531
|
return formatNumber(str) + '%';
|
|
388
532
|
}
|
|
389
533
|
return formatNumber(cell.raw);
|
|
@@ -399,6 +543,8 @@
|
|
|
399
543
|
exports.formatNumber = formatNumber;
|
|
400
544
|
exports.colIndex = colIndex;
|
|
401
545
|
exports.parseFormats = parseFormats;
|
|
546
|
+
exports.parseFormatRules = parseFormatRules;
|
|
547
|
+
exports.resolveFormat = resolveFormat;
|
|
402
548
|
exports.formatValue = formatValue;
|
|
403
549
|
exports.looksLikeHeader = looksLikeHeader;
|
|
404
550
|
exports.sortRows = sortRows;
|