@particle-academy/holy-sheet 2.7.0 → 2.8.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.
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +624 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +73 -4
- package/dist/index.d.ts +73 -4
- package/dist/index.js +624 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -77,9 +77,9 @@ var Inference = {
|
|
|
77
77
|
return { header: headerName, type: "auto" };
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
|
-
function nonNullSample(
|
|
80
|
+
function nonNullSample(values2) {
|
|
81
81
|
const out = [];
|
|
82
|
-
for (const v of
|
|
82
|
+
for (const v of values2) {
|
|
83
83
|
if (v === null || v === void 0) continue;
|
|
84
84
|
out.push(v);
|
|
85
85
|
if (out.length >= SAMPLE_SIZE) break;
|
|
@@ -269,10 +269,10 @@ var CellAddress = {
|
|
|
269
269
|
|
|
270
270
|
// src/workbook/cell.ts
|
|
271
271
|
var Cell = class {
|
|
272
|
-
constructor(address, value,
|
|
272
|
+
constructor(address, value, formula2 = null, format = null, comment = null, cachedValue = null) {
|
|
273
273
|
this.address = address;
|
|
274
274
|
this.value = value;
|
|
275
|
-
this.formula =
|
|
275
|
+
this.formula = formula2;
|
|
276
276
|
this.format = format;
|
|
277
277
|
this.comment = comment;
|
|
278
278
|
this.cachedValue = cachedValue;
|
|
@@ -320,8 +320,8 @@ var ColumnWidths = {
|
|
|
320
320
|
|
|
321
321
|
// src/workbook/cell-comment.ts
|
|
322
322
|
var CellComment = class {
|
|
323
|
-
constructor(
|
|
324
|
-
this.text =
|
|
323
|
+
constructor(text2, author = null, color2 = null) {
|
|
324
|
+
this.text = text2;
|
|
325
325
|
this.author = author;
|
|
326
326
|
this.color = color2;
|
|
327
327
|
}
|
|
@@ -415,8 +415,8 @@ var MergedRegion = class {
|
|
|
415
415
|
};
|
|
416
416
|
|
|
417
417
|
// src/workbook/sheet.ts
|
|
418
|
-
var Sheet = class {
|
|
419
|
-
constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0, rowHeights = /* @__PURE__ */ new Map(), print = null) {
|
|
418
|
+
var Sheet = class _Sheet {
|
|
419
|
+
constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0, rowHeights = /* @__PURE__ */ new Map(), print = null, charts = []) {
|
|
420
420
|
this.name = name;
|
|
421
421
|
this.cells = cells;
|
|
422
422
|
this.mergedRegions = mergedRegions;
|
|
@@ -425,6 +425,10 @@ var Sheet = class {
|
|
|
425
425
|
this.frozenCols = frozenCols;
|
|
426
426
|
this.rowHeights = rowHeights;
|
|
427
427
|
this.print = print;
|
|
428
|
+
this.charts = charts;
|
|
429
|
+
}
|
|
430
|
+
withCharts(charts) {
|
|
431
|
+
return new _Sheet(this.name, this.cells, this.mergedRegions, this.columnWidths, this.frozenRows, this.frozenCols, this.rowHeights, this.print, charts);
|
|
428
432
|
}
|
|
429
433
|
/** rowNumber → (colLetter → Cell), sorted by ascending row number. */
|
|
430
434
|
rows() {
|
|
@@ -455,6 +459,59 @@ var Sheet = class {
|
|
|
455
459
|
}
|
|
456
460
|
};
|
|
457
461
|
|
|
462
|
+
// src/workbook/chart-range.ts
|
|
463
|
+
var ChartRange = class _ChartRange {
|
|
464
|
+
constructor(sheet, c1, r1, c2, r2) {
|
|
465
|
+
this.sheet = sheet;
|
|
466
|
+
this.c1 = c1;
|
|
467
|
+
this.r1 = r1;
|
|
468
|
+
this.c2 = c2;
|
|
469
|
+
this.r2 = r2;
|
|
470
|
+
}
|
|
471
|
+
static parse(ref, defaultSheet) {
|
|
472
|
+
if (typeof ref !== "string") return null;
|
|
473
|
+
let text2 = ref.trim();
|
|
474
|
+
let sheet = defaultSheet;
|
|
475
|
+
const bang = text2.lastIndexOf("!");
|
|
476
|
+
if (bang >= 0) {
|
|
477
|
+
let sheetPart = text2.slice(0, bang);
|
|
478
|
+
text2 = text2.slice(bang + 1);
|
|
479
|
+
if (sheetPart.length >= 2 && sheetPart.startsWith("'") && sheetPart.endsWith("'")) {
|
|
480
|
+
sheetPart = sheetPart.slice(1, -1).replace(/''/g, "'");
|
|
481
|
+
}
|
|
482
|
+
if (sheetPart === "") return null;
|
|
483
|
+
sheet = sheetPart;
|
|
484
|
+
}
|
|
485
|
+
const ends = text2.replace(/\$/g, "").split(":");
|
|
486
|
+
if (ends.length > 2) return null;
|
|
487
|
+
const a = CellAddress.parse(ends[0]);
|
|
488
|
+
const b = CellAddress.parse(ends[1] ?? ends[0]);
|
|
489
|
+
if (a === null || b === null || a[1] < 1 || b[1] < 1) return null;
|
|
490
|
+
return new _ChartRange(sheet, Math.min(a[0], b[0]), Math.min(a[1], b[1]), Math.max(a[0], b[0]), Math.max(a[1], b[1]));
|
|
491
|
+
}
|
|
492
|
+
/** 'Q3 Revenue'!$B$5:$B$7 */
|
|
493
|
+
formula() {
|
|
494
|
+
const from = "$" + CellAddress.letter(this.c1) + "$" + this.r1;
|
|
495
|
+
const to = "$" + CellAddress.letter(this.c2) + "$" + this.r2;
|
|
496
|
+
return "'" + this.sheet.replace(/'/g, "''") + "'!" + from + (from === to ? "" : ":" + to);
|
|
497
|
+
}
|
|
498
|
+
/** "B5:B7" on its own sheet, "'Data'!B5:B7" on another. */
|
|
499
|
+
toSchema(ownSheet) {
|
|
500
|
+
const from = CellAddress.letter(this.c1) + this.r1;
|
|
501
|
+
const to = CellAddress.letter(this.c2) + this.r2;
|
|
502
|
+
const ref = from === to ? from : from + ":" + to;
|
|
503
|
+
return this.sheet === ownSheet ? ref : "'" + this.sheet.replace(/'/g, "''") + "'!" + ref;
|
|
504
|
+
}
|
|
505
|
+
/** A1 addresses, row by row. */
|
|
506
|
+
addresses() {
|
|
507
|
+
const out = [];
|
|
508
|
+
for (let r = this.r1; r <= this.r2; r++) {
|
|
509
|
+
for (let c = this.c1; c <= this.c2; c++) out.push(CellAddress.letter(c) + r);
|
|
510
|
+
}
|
|
511
|
+
return out;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
458
515
|
// src/workbook/workbook.ts
|
|
459
516
|
var Workbook = class {
|
|
460
517
|
constructor(sheets, meta = {}) {
|
|
@@ -667,20 +724,21 @@ var Normalizer = class {
|
|
|
667
724
|
toInt(sheet.frozenRows ?? 0),
|
|
668
725
|
toInt(sheet.frozenCols ?? 0),
|
|
669
726
|
normalizeRowHeights(sheet.rowHeights),
|
|
670
|
-
normalizePrint(sheet.print)
|
|
727
|
+
normalizePrint(sheet.print),
|
|
728
|
+
normalizeCharts(sheet.charts, name, cells)
|
|
671
729
|
);
|
|
672
730
|
}
|
|
673
731
|
normalizeCellMap(map, sheetName2) {
|
|
674
732
|
const cells = /* @__PURE__ */ new Map();
|
|
675
733
|
for (const [address, cellData] of Object.entries(map)) {
|
|
676
734
|
if (!isCellSpec(cellData)) {
|
|
677
|
-
const [bare,
|
|
735
|
+
const [bare, formula2] = promoteFormula(cellData);
|
|
678
736
|
cells.set(
|
|
679
737
|
address,
|
|
680
738
|
new Cell(
|
|
681
739
|
address,
|
|
682
|
-
|
|
683
|
-
|
|
740
|
+
formula2 !== null ? null : this.coerceValue(bare, null, address, sheetName2),
|
|
741
|
+
formula2
|
|
684
742
|
)
|
|
685
743
|
);
|
|
686
744
|
continue;
|
|
@@ -796,11 +854,11 @@ var Normalizer = class {
|
|
|
796
854
|
value.computedValue ?? null
|
|
797
855
|
);
|
|
798
856
|
}
|
|
799
|
-
const [bare,
|
|
857
|
+
const [bare, formula2] = promoteFormula(value);
|
|
800
858
|
return new Cell(
|
|
801
859
|
address,
|
|
802
|
-
|
|
803
|
-
|
|
860
|
+
formula2 !== null ? null : this.coerceValue(bare, columnFormat, address, sheetName2),
|
|
861
|
+
formula2,
|
|
804
862
|
columnFormat
|
|
805
863
|
);
|
|
806
864
|
}
|
|
@@ -858,6 +916,53 @@ function toInt(v) {
|
|
|
858
916
|
const n = Math.trunc(Number(v));
|
|
859
917
|
return Number.isFinite(n) ? n : 0;
|
|
860
918
|
}
|
|
919
|
+
var CHART_TYPES = ["column", "bar", "line", "pie"];
|
|
920
|
+
function normalizeCharts(charts, sheetName2, cells) {
|
|
921
|
+
if (!isPlainObject(charts) && !Array.isArray(charts)) return [];
|
|
922
|
+
let lastCol = -1;
|
|
923
|
+
for (const address of cells.keys()) {
|
|
924
|
+
const parsed = CellAddress.parse(address);
|
|
925
|
+
if (parsed !== null) lastCol = Math.max(lastCol, parsed[0]);
|
|
926
|
+
}
|
|
927
|
+
const out = [];
|
|
928
|
+
for (const chart of Object.values(charts)) {
|
|
929
|
+
if (!isPlainObject(chart) || !CHART_TYPES.includes(chart.type)) continue;
|
|
930
|
+
const series = [];
|
|
931
|
+
const rawSeries = isPlainObject(chart.series) || Array.isArray(chart.series) ? Object.values(chart.series) : [];
|
|
932
|
+
for (const s of rawSeries) {
|
|
933
|
+
const values2 = isPlainObject(s) || Array.isArray(s) ? ChartRange.parse(s.values, sheetName2) : null;
|
|
934
|
+
if (values2 === null) continue;
|
|
935
|
+
const rec = s;
|
|
936
|
+
const m = typeof rec.color === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(rec.color) : null;
|
|
937
|
+
series.push({ name: typeof rec.name === "string" ? rec.name : null, values: values2, color: m ? m[1].toUpperCase() : null });
|
|
938
|
+
}
|
|
939
|
+
if (series.length === 0) continue;
|
|
940
|
+
const stack = out.length;
|
|
941
|
+
let from = [lastCol + 2, 1 + 18 * stack];
|
|
942
|
+
let to = [from[0] + 8, from[1] + 16];
|
|
943
|
+
if (isPlainObject(chart.position) || Array.isArray(chart.position)) {
|
|
944
|
+
const p = chart.position;
|
|
945
|
+
const a = typeof p.from === "string" ? CellAddress.parse(p.from) : null;
|
|
946
|
+
const b = typeof p.to === "string" ? CellAddress.parse(p.to) : null;
|
|
947
|
+
if (a !== null && b !== null && b[0] > a[0] && b[1] > a[1]) {
|
|
948
|
+
from = [a[0], a[1] - 1];
|
|
949
|
+
to = [b[0], b[1] - 1];
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
out.push({
|
|
953
|
+
type: chart.type,
|
|
954
|
+
title: typeof chart.title === "string" && chart.title !== "" ? chart.title : null,
|
|
955
|
+
categories: ChartRange.parse(chart.categories, sheetName2),
|
|
956
|
+
series,
|
|
957
|
+
from,
|
|
958
|
+
to,
|
|
959
|
+
legend: typeof chart.legend === "boolean" ? chart.legend : null,
|
|
960
|
+
dataLabels: chart.dataLabels === true,
|
|
961
|
+
format: typeof chart.format === "string" && chart.format !== "" ? chart.format : null
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return out;
|
|
965
|
+
}
|
|
861
966
|
function phpNumeric(v) {
|
|
862
967
|
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
863
968
|
return isNumericString(v) ? numericStringToNumber(v) : null;
|
|
@@ -948,9 +1053,9 @@ var FormulaLinter = class {
|
|
|
948
1053
|
isError(v) {
|
|
949
1054
|
return typeof v === "string" && v.length > 0 && v[0] === "#";
|
|
950
1055
|
}
|
|
951
|
-
evaluate(
|
|
1056
|
+
evaluate(formula2, defaultSheet, stack) {
|
|
952
1057
|
try {
|
|
953
|
-
const c = { tokens: this.tokenize(
|
|
1058
|
+
const c = { tokens: this.tokenize(formula2), pos: 0 };
|
|
954
1059
|
const result = this.parseExpr(c, defaultSheet, stack);
|
|
955
1060
|
if (c.pos < c.tokens.length) return ERR_NAME;
|
|
956
1061
|
return result;
|
|
@@ -1211,13 +1316,13 @@ var FormulaLinter = class {
|
|
|
1211
1316
|
const row1 = Math.min(a[1], b[1]);
|
|
1212
1317
|
const col2 = Math.max(a[0], b[0]);
|
|
1213
1318
|
const row2 = Math.max(a[1], b[1]);
|
|
1214
|
-
const
|
|
1319
|
+
const values2 = [];
|
|
1215
1320
|
for (let r = row1; r <= row2; r++) {
|
|
1216
1321
|
for (let col = col1; col <= col2; col++) {
|
|
1217
|
-
|
|
1322
|
+
values2.push(this.resolveCell(sheet, CellAddress.letter(col) + r, stack));
|
|
1218
1323
|
}
|
|
1219
1324
|
}
|
|
1220
|
-
return
|
|
1325
|
+
return values2;
|
|
1221
1326
|
}
|
|
1222
1327
|
expectOp(c, op) {
|
|
1223
1328
|
const t = c.tokens[c.pos];
|
|
@@ -1349,12 +1454,12 @@ var FormulaLinter = class {
|
|
|
1349
1454
|
return truthy ? args[1] : args[2] ?? false;
|
|
1350
1455
|
}
|
|
1351
1456
|
// ---- Hints ----
|
|
1352
|
-
hint(error2,
|
|
1457
|
+
hint(error2, formula2, sheet) {
|
|
1353
1458
|
switch (error2) {
|
|
1354
1459
|
case ERR_VALUE:
|
|
1355
|
-
return this.hintValue(
|
|
1460
|
+
return this.hintValue(formula2, sheet);
|
|
1356
1461
|
case ERR_REF:
|
|
1357
|
-
return this.hintRef(
|
|
1462
|
+
return this.hintRef(formula2);
|
|
1358
1463
|
case ERR_NAME:
|
|
1359
1464
|
return "The formula references an unknown function or has a syntax error. Holy Sheet supports: SUM, AVERAGE, COUNT, COUNTA, MIN, MAX, IF, ROUND, ABS, LEN, UPPER, LOWER, CONCAT.";
|
|
1360
1465
|
case ERR_DIV0:
|
|
@@ -1365,8 +1470,8 @@ var FormulaLinter = class {
|
|
|
1365
1470
|
return "Formula evaluation failed.";
|
|
1366
1471
|
}
|
|
1367
1472
|
}
|
|
1368
|
-
hintRef(
|
|
1369
|
-
for (const m of
|
|
1473
|
+
hintRef(formula2) {
|
|
1474
|
+
for (const m of formula2.matchAll(new RegExp(SHEET_QUALIFIER, "g"))) {
|
|
1370
1475
|
const name = this.cleanSheetName(m[1]);
|
|
1371
1476
|
if (!this.sheetNames.has(name.toLowerCase())) {
|
|
1372
1477
|
return `The formula refers to a sheet named '${name}', and this workbook has no such sheet. Its sheets are: ${[...this.sheetNames.values()].join(", ")}. Quote a name that contains spaces or punctuation: 'My Sheet'!A1.`;
|
|
@@ -1374,10 +1479,10 @@ var FormulaLinter = class {
|
|
|
1374
1479
|
}
|
|
1375
1480
|
return "A cell reference points to a cell that doesn't exist in the workbook. Check column letters and row numbers.";
|
|
1376
1481
|
}
|
|
1377
|
-
hintValue(
|
|
1482
|
+
hintValue(formula2, sheet) {
|
|
1378
1483
|
const re = new RegExp(`(?:${SHEET_QUALIFIER})?\\$?([A-Z]+)\\$?(\\d+)`, "gi");
|
|
1379
1484
|
const offenders = [];
|
|
1380
|
-
for (const m of
|
|
1485
|
+
for (const m of formula2.matchAll(re)) {
|
|
1381
1486
|
const sheetName2 = m[1] ? this.sheetNames.get(this.cleanSheetName(m[1]).toLowerCase()) ?? this.cleanSheetName(m[1]) : sheet;
|
|
1382
1487
|
const a1 = m[2].toUpperCase() + m[3];
|
|
1383
1488
|
const key = sheetName2 + "!" + a1;
|
|
@@ -1500,11 +1605,11 @@ var Repairer = class {
|
|
|
1500
1605
|
sheet.columns.forEach((col, colIdx) => {
|
|
1501
1606
|
if (!isPlainObject(col)) return;
|
|
1502
1607
|
if (col.type !== void 0 && col.type !== "auto") return;
|
|
1503
|
-
const
|
|
1608
|
+
const values2 = [];
|
|
1504
1609
|
for (const row of sheet.rows) {
|
|
1505
|
-
if (Array.isArray(row) && colIdx < row.length)
|
|
1610
|
+
if (Array.isArray(row) && colIdx < row.length) values2.push(row[colIdx]);
|
|
1506
1611
|
}
|
|
1507
|
-
const nonNull =
|
|
1612
|
+
const nonNull = values2.filter((v) => v !== null && v !== void 0);
|
|
1508
1613
|
if (nonNull.length === 0) return;
|
|
1509
1614
|
const allDateLike = nonNull.every((v) => typeof v === "string" && ISO_DATE2.test(v));
|
|
1510
1615
|
if (allDateLike) {
|
|
@@ -1767,6 +1872,9 @@ var Validator = class {
|
|
|
1767
1872
|
}
|
|
1768
1873
|
}
|
|
1769
1874
|
}
|
|
1875
|
+
if (sheet.charts != null) {
|
|
1876
|
+
errors.push(...validateCharts(sheet.charts, path, typeof sheet.name === "string" ? sheet.name : ""));
|
|
1877
|
+
}
|
|
1770
1878
|
if (sheet.print != null) {
|
|
1771
1879
|
const print = sheet.print;
|
|
1772
1880
|
if (!isPlainObject(print) && !Array.isArray(print)) {
|
|
@@ -1867,6 +1975,113 @@ var Validator = class {
|
|
|
1867
1975
|
return errors;
|
|
1868
1976
|
}
|
|
1869
1977
|
};
|
|
1978
|
+
function validateCharts(charts, path, sheetName2) {
|
|
1979
|
+
if (!Array.isArray(charts)) {
|
|
1980
|
+
return [
|
|
1981
|
+
error(
|
|
1982
|
+
`${path}.charts`,
|
|
1983
|
+
"a list of charts",
|
|
1984
|
+
typeOf(charts),
|
|
1985
|
+
charts,
|
|
1986
|
+
'charts is a list: [{"type": "column", "categories": "A5:A7", "series": [{"name": "Revenue", "values": "B5:B7"}]}].'
|
|
1987
|
+
)
|
|
1988
|
+
];
|
|
1989
|
+
}
|
|
1990
|
+
const errors = [];
|
|
1991
|
+
charts.forEach((chart, i) => {
|
|
1992
|
+
const at2 = `${path}.charts[${i}]`;
|
|
1993
|
+
if (!isPlainObject(chart) && !Array.isArray(chart)) {
|
|
1994
|
+
errors.push(error(at2, "object", typeOf(chart), chart, "A chart is an object with a type and series."));
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
const c = chart;
|
|
1998
|
+
if (!CHART_TYPES.includes(c.type)) {
|
|
1999
|
+
errors.push(error(
|
|
2000
|
+
`${at2}.type`,
|
|
2001
|
+
"one of: " + CHART_TYPES.join(", "),
|
|
2002
|
+
typeOf(c.type ?? null),
|
|
2003
|
+
c.type ?? null,
|
|
2004
|
+
"column compares values across categories; bar does the same horizontally (long labels); line shows change over time; pie shows parts of one whole."
|
|
2005
|
+
));
|
|
2006
|
+
}
|
|
2007
|
+
const series = c.series;
|
|
2008
|
+
if (!Array.isArray(series) || series.length === 0) {
|
|
2009
|
+
errors.push(error(
|
|
2010
|
+
`${at2}.series`,
|
|
2011
|
+
"a non-empty list of series",
|
|
2012
|
+
typeOf(series ?? null),
|
|
2013
|
+
series ?? null,
|
|
2014
|
+
'Each series names the range it plots: [{"name": "Revenue", "values": "B5:B7"}].'
|
|
2015
|
+
));
|
|
2016
|
+
} else {
|
|
2017
|
+
series.forEach((s, j) => {
|
|
2018
|
+
const isObj = isPlainObject(s) || Array.isArray(s);
|
|
2019
|
+
const values2 = isObj ? s.values ?? null : s;
|
|
2020
|
+
if (!isObj || ChartRange.parse(values2, sheetName2) === null) {
|
|
2021
|
+
errors.push(error(
|
|
2022
|
+
`${at2}.series[${j}].values`,
|
|
2023
|
+
'a cell range like "B5:B7"',
|
|
2024
|
+
typeOf(values2),
|
|
2025
|
+
values2,
|
|
2026
|
+
`A range of cells: "B5:B7" on this sheet, or "'Other sheet'!B5:B7".`
|
|
2027
|
+
));
|
|
2028
|
+
} else {
|
|
2029
|
+
const color2 = s.color;
|
|
2030
|
+
if (color2 != null && (typeof color2 !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(color2))) {
|
|
2031
|
+
errors.push(error(`${at2}.series[${j}].color`, "#RRGGBB", typeOf(color2), color2, 'A hex colour, like "#1F3A5F".'));
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
if (c.categories != null && ChartRange.parse(c.categories, sheetName2) === null) {
|
|
2037
|
+
errors.push(error(
|
|
2038
|
+
`${at2}.categories`,
|
|
2039
|
+
'a cell range like "A5:A7"',
|
|
2040
|
+
typeOf(c.categories),
|
|
2041
|
+
c.categories,
|
|
2042
|
+
'The labels along the axis, as a range: "A5:A7".'
|
|
2043
|
+
));
|
|
2044
|
+
}
|
|
2045
|
+
if (c.position != null) {
|
|
2046
|
+
const p = c.position;
|
|
2047
|
+
const isObj = isPlainObject(p) || Array.isArray(p);
|
|
2048
|
+
const a = isObj && typeof p.from === "string" ? CellAddress.parse(p.from) : null;
|
|
2049
|
+
const b = isObj && typeof p.to === "string" ? CellAddress.parse(p.to) : null;
|
|
2050
|
+
if (a === null || b === null || b[0] <= a[0] || b[1] <= a[1]) {
|
|
2051
|
+
errors.push(error(
|
|
2052
|
+
`${at2}.position`,
|
|
2053
|
+
'{"from": top-left cell, "to": bottom-right cell}',
|
|
2054
|
+
typeOf(p),
|
|
2055
|
+
p,
|
|
2056
|
+
'Two cells, the second below and to the right of the first: {"from": "E2", "to": "L18"} is 7 columns by 16 rows.'
|
|
2057
|
+
));
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
for (const key of ["title", "format"]) {
|
|
2061
|
+
if (c[key] != null && typeof c[key] !== "string") {
|
|
2062
|
+
errors.push(error(
|
|
2063
|
+
`${at2}.${key}`,
|
|
2064
|
+
"string",
|
|
2065
|
+
typeOf(c[key]),
|
|
2066
|
+
c[key],
|
|
2067
|
+
key === "format" ? 'An Excel number format for the axis and labels, like "$#,##0" or "0.0%".' : "The chart title."
|
|
2068
|
+
));
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
for (const key of ["dataLabels", "legend"]) {
|
|
2072
|
+
if (c[key] != null && typeof c[key] !== "boolean") {
|
|
2073
|
+
errors.push(error(
|
|
2074
|
+
`${at2}.${key}`,
|
|
2075
|
+
"boolean",
|
|
2076
|
+
typeOf(c[key]),
|
|
2077
|
+
c[key],
|
|
2078
|
+
key === "dataLabels" ? "true prints each value on the chart." : "Defaults to on for several series or a pie."
|
|
2079
|
+
));
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
return errors;
|
|
2084
|
+
}
|
|
1870
2085
|
function error(path, expected, got, value, hint) {
|
|
1871
2086
|
return { path, expected, got, value, hint };
|
|
1872
2087
|
}
|
|
@@ -2383,15 +2598,15 @@ var FormatSniffer = {
|
|
|
2383
2598
|
};
|
|
2384
2599
|
|
|
2385
2600
|
// src/reader/ods/ods-formula.ts
|
|
2386
|
-
function odsFormulaToA1(
|
|
2601
|
+
function odsFormulaToA1(formula2) {
|
|
2387
2602
|
let grammar = "of";
|
|
2388
|
-
const m = /^(of|oooc|msoxl):/i.exec(
|
|
2603
|
+
const m = /^(of|oooc|msoxl):/i.exec(formula2);
|
|
2389
2604
|
if (m) {
|
|
2390
2605
|
grammar = m[1].toLowerCase();
|
|
2391
|
-
|
|
2606
|
+
formula2 = formula2.slice(m[0].length);
|
|
2392
2607
|
}
|
|
2393
|
-
if (
|
|
2394
|
-
return grammar === "msoxl" ?
|
|
2608
|
+
if (formula2.startsWith("=")) formula2 = formula2.slice(1);
|
|
2609
|
+
return grammar === "msoxl" ? formula2 : translate(formula2);
|
|
2395
2610
|
}
|
|
2396
2611
|
function translate(s) {
|
|
2397
2612
|
let out = "";
|
|
@@ -2740,8 +2955,8 @@ var OdsStyles = class {
|
|
|
2740
2955
|
switch (el2.name) {
|
|
2741
2956
|
case "number-style": {
|
|
2742
2957
|
if (has2("scientific-number") || has2("fraction")) return null;
|
|
2743
|
-
for (const
|
|
2744
|
-
const iso = currencyCode(ownText(
|
|
2958
|
+
for (const text2 of parts.filter((p) => p.name === "text")) {
|
|
2959
|
+
const iso = currencyCode(ownText(text2).replace(/[-()"\u00A0]/g, ""));
|
|
2745
2960
|
if (iso !== null) return { displayFormat: "currency", decimals: decimals ?? 0, currency: iso };
|
|
2746
2961
|
}
|
|
2747
2962
|
if (!number) return null;
|
|
@@ -2927,6 +3142,29 @@ function sheetToSchema(sheet) {
|
|
|
2927
3142
|
}
|
|
2928
3143
|
out["print"] = print;
|
|
2929
3144
|
}
|
|
3145
|
+
if (sheet.charts.length > 0) {
|
|
3146
|
+
out["charts"] = sheet.charts.map((c) => chartToSchema(c, sheet.name));
|
|
3147
|
+
}
|
|
3148
|
+
return out;
|
|
3149
|
+
}
|
|
3150
|
+
function chartToSchema(chart, sheetName2) {
|
|
3151
|
+
const out = { type: chart.type };
|
|
3152
|
+
if (chart.title !== null) out["title"] = chart.title;
|
|
3153
|
+
if (chart.categories !== null) out["categories"] = chart.categories.toSchema(sheetName2);
|
|
3154
|
+
out["series"] = chart.series.map((s) => {
|
|
3155
|
+
const series = {};
|
|
3156
|
+
if (s.name !== null) series["name"] = s.name;
|
|
3157
|
+
series["values"] = s.values.toSchema(sheetName2);
|
|
3158
|
+
if (s.color !== null) series["color"] = "#" + s.color;
|
|
3159
|
+
return series;
|
|
3160
|
+
});
|
|
3161
|
+
out["position"] = {
|
|
3162
|
+
from: CellAddress.letter(chart.from[0]) + (chart.from[1] + 1),
|
|
3163
|
+
to: CellAddress.letter(chart.to[0]) + (chart.to[1] + 1)
|
|
3164
|
+
};
|
|
3165
|
+
if (chart.dataLabels) out["dataLabels"] = true;
|
|
3166
|
+
if (chart.legend !== null) out["legend"] = chart.legend;
|
|
3167
|
+
if (chart.format !== null) out["format"] = chart.format;
|
|
2930
3168
|
return out;
|
|
2931
3169
|
}
|
|
2932
3170
|
function cellToSchema(cell) {
|
|
@@ -3034,10 +3272,10 @@ function parseXml(src, options = {}) {
|
|
|
3034
3272
|
if (namespaces) root.content = [];
|
|
3035
3273
|
const stack = [root];
|
|
3036
3274
|
const scopes = [{ xml: XML_NS }];
|
|
3037
|
-
const appendText = (
|
|
3275
|
+
const appendText = (text2) => {
|
|
3038
3276
|
const top = stack[stack.length - 1];
|
|
3039
|
-
top.text +=
|
|
3040
|
-
if (namespaces &&
|
|
3277
|
+
top.text += text2;
|
|
3278
|
+
if (namespaces && text2 !== "") top.content.push(text2);
|
|
3041
3279
|
};
|
|
3042
3280
|
while (i < n) {
|
|
3043
3281
|
if (src[i] === "<") {
|
|
@@ -3258,7 +3496,7 @@ function hasContent(cell) {
|
|
|
3258
3496
|
}
|
|
3259
3497
|
function readCell(cell) {
|
|
3260
3498
|
const type = attr(cell, NS.OFFICE, "value-type") ?? null;
|
|
3261
|
-
const
|
|
3499
|
+
const text2 = paragraphs(cell);
|
|
3262
3500
|
let value = null;
|
|
3263
3501
|
let seconds = null;
|
|
3264
3502
|
let dateHasTime = false;
|
|
@@ -3287,20 +3525,20 @@ function readCell(cell) {
|
|
|
3287
3525
|
case "string": {
|
|
3288
3526
|
const error2 = attr(cell, NS.CALCEXT, "value-type") === "error";
|
|
3289
3527
|
const stored = attr(cell, NS.OFFICE, "string-value");
|
|
3290
|
-
value = stored !== void 0 && !error2 ? stored :
|
|
3528
|
+
value = stored !== void 0 && !error2 ? stored : text2 ?? "";
|
|
3291
3529
|
break;
|
|
3292
3530
|
}
|
|
3293
3531
|
default:
|
|
3294
|
-
value =
|
|
3532
|
+
value = text2 !== null && text2 !== "" ? text2 : null;
|
|
3295
3533
|
}
|
|
3296
|
-
let
|
|
3534
|
+
let formula2 = null;
|
|
3297
3535
|
let cached = null;
|
|
3298
3536
|
const formulaAttr = attr(cell, NS.TABLE, "formula");
|
|
3299
3537
|
if (formulaAttr !== void 0) {
|
|
3300
3538
|
const translated = odsFormulaToA1(formulaAttr);
|
|
3301
3539
|
const literalBoolean = type === "boolean" && /^[ \t\n\r\f\v]*(TRUE|FALSE)\(\)[ \t\n\r\f\v]*$/i.test(translated);
|
|
3302
3540
|
if (!literalBoolean) {
|
|
3303
|
-
|
|
3541
|
+
formula2 = translated;
|
|
3304
3542
|
cached = seconds !== null ? serial(seconds) : value;
|
|
3305
3543
|
value = null;
|
|
3306
3544
|
seconds = null;
|
|
@@ -3316,7 +3554,7 @@ function readCell(cell) {
|
|
|
3316
3554
|
const currency = attr(cell, NS.OFFICE, "currency");
|
|
3317
3555
|
return {
|
|
3318
3556
|
value,
|
|
3319
|
-
formula,
|
|
3557
|
+
formula: formula2,
|
|
3320
3558
|
cached,
|
|
3321
3559
|
type,
|
|
3322
3560
|
currency: currency !== void 0 ? phpTrim(currency) : null,
|
|
@@ -3385,6 +3623,127 @@ function readMeta(xml) {
|
|
|
3385
3623
|
return out;
|
|
3386
3624
|
}
|
|
3387
3625
|
|
|
3626
|
+
// src/reader/chart-parser.ts
|
|
3627
|
+
function descendants(node, name) {
|
|
3628
|
+
const out = [];
|
|
3629
|
+
const stack = node ? [...node.children].reverse() : [];
|
|
3630
|
+
while (stack.length > 0) {
|
|
3631
|
+
const n = stack.pop();
|
|
3632
|
+
if (n.name === name) out.push(n);
|
|
3633
|
+
for (let i = n.children.length - 1; i >= 0; i--) stack.push(n.children[i]);
|
|
3634
|
+
}
|
|
3635
|
+
return out;
|
|
3636
|
+
}
|
|
3637
|
+
function load(xml) {
|
|
3638
|
+
if (/<!DOCTYPE/i.test(xml)) return null;
|
|
3639
|
+
return parseXml(xml);
|
|
3640
|
+
}
|
|
3641
|
+
function wrap2(root) {
|
|
3642
|
+
return { name: "#document", attrs: {}, children: [root], text: "" };
|
|
3643
|
+
}
|
|
3644
|
+
var ChartParser = {
|
|
3645
|
+
anchors(drawingXml) {
|
|
3646
|
+
const root = load(drawingXml);
|
|
3647
|
+
if (!root) return [];
|
|
3648
|
+
const out = [];
|
|
3649
|
+
for (const anchor of descendants(wrap2(root), "twoCellAnchor")) {
|
|
3650
|
+
const from = marker(anchor, "from");
|
|
3651
|
+
const to = marker(anchor, "to");
|
|
3652
|
+
const chart = descendants(anchor, "chart")[0];
|
|
3653
|
+
const rel = chart ? at(chart, "id") ?? "" : "";
|
|
3654
|
+
if (from !== null && to !== null && rel !== "") out.push({ from, to, rel });
|
|
3655
|
+
}
|
|
3656
|
+
return out;
|
|
3657
|
+
},
|
|
3658
|
+
chart(chartXml, sheetName2, from, to) {
|
|
3659
|
+
const root = load(chartXml);
|
|
3660
|
+
if (!root) return null;
|
|
3661
|
+
const doc = wrap2(root);
|
|
3662
|
+
let plot;
|
|
3663
|
+
let type = null;
|
|
3664
|
+
for (const tag of ["barChart", "lineChart", "pieChart"]) {
|
|
3665
|
+
const found = descendants(doc, tag)[0];
|
|
3666
|
+
if (found) {
|
|
3667
|
+
plot = found;
|
|
3668
|
+
type = tag === "lineChart" ? "line" : tag === "pieChart" ? "pie" : at(el(found, "barDir"), "val") === "bar" ? "bar" : "column";
|
|
3669
|
+
break;
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
if (!plot || type === null) return null;
|
|
3673
|
+
let categories = null;
|
|
3674
|
+
const series = [];
|
|
3675
|
+
for (const ser of els(plot, "ser")) {
|
|
3676
|
+
const valRef = formula(ser, "val");
|
|
3677
|
+
const values2 = valRef !== null ? ChartRange.parse(valRef, sheetName2) : null;
|
|
3678
|
+
if (values2 === null) continue;
|
|
3679
|
+
if (categories === null) {
|
|
3680
|
+
const catRef = formula(ser, "cat");
|
|
3681
|
+
categories = catRef !== null ? ChartRange.parse(catRef, sheetName2) : null;
|
|
3682
|
+
}
|
|
3683
|
+
series.push({ name: seriesName(ser), values: values2, color: seriesColor(ser) });
|
|
3684
|
+
}
|
|
3685
|
+
if (series.length === 0) return null;
|
|
3686
|
+
const labels = el(plot, "dLbls");
|
|
3687
|
+
const dataLabels2 = labels !== void 0 && at(el(labels, "showVal"), "val") === "1";
|
|
3688
|
+
let format = null;
|
|
3689
|
+
for (const host of [labels, descendants(doc, "valAx")[0]]) {
|
|
3690
|
+
const numFmt = host ? el(host, "numFmt") : void 0;
|
|
3691
|
+
if (numFmt && at(numFmt, "sourceLinked") === "0" && (at(numFmt, "formatCode") ?? "") !== "") {
|
|
3692
|
+
format = at(numFmt, "formatCode");
|
|
3693
|
+
break;
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
let title = null;
|
|
3697
|
+
const titleEl = descendants(doc, "title")[0];
|
|
3698
|
+
if (titleEl) {
|
|
3699
|
+
const text2 = descendants(titleEl, "t").map((t) => t.text).join("");
|
|
3700
|
+
title = text2 !== "" ? text2 : null;
|
|
3701
|
+
}
|
|
3702
|
+
return {
|
|
3703
|
+
type,
|
|
3704
|
+
title,
|
|
3705
|
+
categories,
|
|
3706
|
+
series,
|
|
3707
|
+
from,
|
|
3708
|
+
to,
|
|
3709
|
+
legend: descendants(doc, "legend").length > 0 ? true : null,
|
|
3710
|
+
dataLabels: dataLabels2,
|
|
3711
|
+
format
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
};
|
|
3715
|
+
function marker(anchor, tag) {
|
|
3716
|
+
const m = el(anchor, tag);
|
|
3717
|
+
if (!m) return null;
|
|
3718
|
+
const col = descendants(m, "col")[0]?.text.trim() ?? "";
|
|
3719
|
+
const row = descendants(m, "row")[0]?.text.trim() ?? "";
|
|
3720
|
+
return /^\d+$/.test(col) && /^\d+$/.test(row) ? [parseInt(col, 10), parseInt(row, 10)] : null;
|
|
3721
|
+
}
|
|
3722
|
+
function formula(ser, name) {
|
|
3723
|
+
const host = el(ser, name);
|
|
3724
|
+
if (!host) return null;
|
|
3725
|
+
for (const ref of ["numRef", "strRef"]) {
|
|
3726
|
+
const f = el(el(host, ref), "f");
|
|
3727
|
+
if (f && f.text.trim() !== "") return f.text.trim();
|
|
3728
|
+
}
|
|
3729
|
+
return null;
|
|
3730
|
+
}
|
|
3731
|
+
function seriesName(ser) {
|
|
3732
|
+
const tx = el(ser, "tx");
|
|
3733
|
+
if (!tx) return null;
|
|
3734
|
+
const v = el(tx, "v");
|
|
3735
|
+
if (v) return v.text;
|
|
3736
|
+
const cached = descendants(tx, "v")[0];
|
|
3737
|
+
return cached ? cached.text : null;
|
|
3738
|
+
}
|
|
3739
|
+
function seriesColor(ser) {
|
|
3740
|
+
const spPr = el(ser, "spPr");
|
|
3741
|
+
if (!spPr) return null;
|
|
3742
|
+
const clr = descendants(spPr, "srgbClr")[0];
|
|
3743
|
+
const val = (clr ? at(clr, "val") ?? "" : "").toUpperCase();
|
|
3744
|
+
return /^[0-9A-F]{6}$/.test(val) ? val : null;
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3388
3747
|
// src/reader/comments-parser.ts
|
|
3389
3748
|
var CommentsParser = {
|
|
3390
3749
|
parse(commentsXml) {
|
|
@@ -3616,13 +3975,13 @@ function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
|
3616
3975
|
const type = c.attrs["t"] ?? "n";
|
|
3617
3976
|
const styleIdx = c.attrs["s"] !== void 0 ? parseInt(c.attrs["s"], 10) : 0;
|
|
3618
3977
|
const format = stylesIndex[styleIdx] ?? null;
|
|
3619
|
-
let
|
|
3978
|
+
let formula2 = null;
|
|
3620
3979
|
let cachedValue = null;
|
|
3621
3980
|
let value = null;
|
|
3622
3981
|
const f = el(c, "f");
|
|
3623
3982
|
const v = el(c, "v");
|
|
3624
3983
|
if (f) {
|
|
3625
|
-
|
|
3984
|
+
formula2 = f.text;
|
|
3626
3985
|
cachedValue = v ? coerceValue(v.text, type) : null;
|
|
3627
3986
|
} else if (type === "inlineStr" && el(c, "is") && el(el(c, "is"), "t")) {
|
|
3628
3987
|
value = el(el(c, "is"), "t").text;
|
|
@@ -3633,7 +3992,7 @@ function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
|
3633
3992
|
value = coerceValue(v.text, type);
|
|
3634
3993
|
}
|
|
3635
3994
|
const comment = comments[address] ?? null;
|
|
3636
|
-
cells.set(address, new Cell(address, value,
|
|
3995
|
+
cells.set(address, new Cell(address, value, formula2, format, comment, cachedValue));
|
|
3637
3996
|
}
|
|
3638
3997
|
}
|
|
3639
3998
|
return cells;
|
|
@@ -3724,7 +4083,26 @@ var XlsxReader = class {
|
|
|
3724
4083
|
if (commentsXml) comments = { ...comments, ...CommentsParser.parse(commentsXml) };
|
|
3725
4084
|
}
|
|
3726
4085
|
}
|
|
3727
|
-
|
|
4086
|
+
let sheet = WorksheetParser.parse(worksheetXml, name, stylesIndex, comments, sharedStrings);
|
|
4087
|
+
if (sheetRelsXml) {
|
|
4088
|
+
const charts = [];
|
|
4089
|
+
for (const dr of Object.values(RelsParser.byType(RelsParser.parse(sheetRelsXml), "/drawing"))) {
|
|
4090
|
+
const drawingPath = resolveRelativePath(sheetPath, dr.Target);
|
|
4091
|
+
const slash = drawingPath.lastIndexOf("/");
|
|
4092
|
+
const drawingXml = getText(drawingPath);
|
|
4093
|
+
const drawingRelsXml = getText(drawingPath.slice(0, slash) + "/_rels/" + drawingPath.slice(slash + 1) + ".rels");
|
|
4094
|
+
if (drawingXml === null || drawingRelsXml === null) continue;
|
|
4095
|
+
const drawingRels = RelsParser.parse(drawingRelsXml);
|
|
4096
|
+
for (const anchor of ChartParser.anchors(drawingXml)) {
|
|
4097
|
+
const target2 = drawingRels[anchor.rel]?.Target;
|
|
4098
|
+
const chartXml = typeof target2 === "string" ? getText(resolveRelativePath(drawingPath, target2)) : null;
|
|
4099
|
+
const chart = chartXml !== null ? ChartParser.chart(chartXml, name, anchor.from, anchor.to) : null;
|
|
4100
|
+
if (chart !== null) charts.push(chart);
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
if (charts.length > 0) sheet = sheet.withCharts(charts);
|
|
4104
|
+
}
|
|
4105
|
+
sheets.push(sheet);
|
|
3728
4106
|
i++;
|
|
3729
4107
|
}
|
|
3730
4108
|
const meta = this.parseDocProps(getText("docProps/core.xml"));
|
|
@@ -3978,6 +4356,114 @@ function hexToArgb(hex2) {
|
|
|
3978
4356
|
return "FF000000";
|
|
3979
4357
|
}
|
|
3980
4358
|
|
|
4359
|
+
// src/writer/chart-xml.ts
|
|
4360
|
+
var NS_C = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
4361
|
+
var NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
4362
|
+
var NS_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
4363
|
+
var ChartXml = {
|
|
4364
|
+
drawing(charts, firstChartNum) {
|
|
4365
|
+
let anchors = "";
|
|
4366
|
+
charts.forEach((chart, k) => {
|
|
4367
|
+
const id = k + 2;
|
|
4368
|
+
const num = firstChartNum + k;
|
|
4369
|
+
anchors += '<xdr:twoCellAnchor editAs="oneCell">' + marker2("from", chart.from) + marker2("to", chart.to) + `<xdr:graphicFrame macro=""><xdr:nvGraphicFramePr><xdr:cNvPr id="${id}" name="Chart ${num}"/><xdr:cNvGraphicFramePr/></xdr:nvGraphicFramePr><xdr:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/></xdr:xfrm><a:graphic><a:graphicData uri="${NS_C}"><c:chart xmlns:c="${NS_C}" xmlns:r="${NS_R}" r:id="rId${k + 1}"/></a:graphicData></a:graphic></xdr:graphicFrame><xdr:clientData/></xdr:twoCellAnchor>`;
|
|
4370
|
+
});
|
|
4371
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:a="${NS_A}">` + anchors + "</xdr:wsDr>";
|
|
4372
|
+
},
|
|
4373
|
+
drawingRels(charts, firstChartNum) {
|
|
4374
|
+
let rels = "";
|
|
4375
|
+
charts.forEach((_, k) => {
|
|
4376
|
+
rels += `<Relationship Id="rId${k + 1}" Type="${NS_R}/chart" Target="../charts/chart${firstChartNum + k}.xml"/>`;
|
|
4377
|
+
});
|
|
4378
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
4379
|
+
},
|
|
4380
|
+
chart(chart, workbook) {
|
|
4381
|
+
const type = chart.type;
|
|
4382
|
+
let series = "";
|
|
4383
|
+
chart.series.forEach((s, i) => {
|
|
4384
|
+
series += seriesXml(i, s, chart, workbook);
|
|
4385
|
+
});
|
|
4386
|
+
const labels = chart.dataLabels ? dataLabels(type, chart.format) : "";
|
|
4387
|
+
let plot;
|
|
4388
|
+
if (type === "line") {
|
|
4389
|
+
plot = '<c:lineChart><c:grouping val="standard"/><c:varyColors val="0"/>' + series + labels + '<c:marker val="1"/><c:axId val="10"/><c:axId val="20"/></c:lineChart>';
|
|
4390
|
+
} else if (type === "pie") {
|
|
4391
|
+
plot = '<c:pieChart><c:varyColors val="1"/>' + series + labels + '<c:firstSliceAng val="0"/></c:pieChart>';
|
|
4392
|
+
} else {
|
|
4393
|
+
plot = `<c:barChart><c:barDir val="${type === "bar" ? "bar" : "col"}"/><c:grouping val="clustered"/><c:varyColors val="0"/>` + series + labels + '<c:gapWidth val="80"/><c:axId val="10"/><c:axId val="20"/></c:barChart>';
|
|
4394
|
+
}
|
|
4395
|
+
const axesXml = type === "pie" ? "" : axes(type === "bar", chart.format);
|
|
4396
|
+
const legend = chart.legend ?? (chart.series.length > 1 || type === "pie");
|
|
4397
|
+
const title = chart.title !== null ? `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz="1200" b="1"/></a:pPr><a:r><a:rPr lang="en-US" sz="1200" b="1"/><a:t>${text(chart.title)}</a:t></a:r></a:p></c:rich></c:tx><c:overlay val="0"/></c:title><c:autoTitleDeleted val="0"/>` : '<c:autoTitleDeleted val="1"/>';
|
|
4398
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><c:chartSpace xmlns:c="${NS_C}" xmlns:a="${NS_A}" xmlns:r="${NS_R}"><c:roundedCorners val="0"/><c:chart>` + title + "<c:plotArea><c:layout/>" + plot + axesXml + "</c:plotArea>" + (legend ? '<c:legend><c:legendPos val="b"/><c:overlay val="0"/></c:legend>' : "") + '<c:plotVisOnly val="1"/><c:dispBlanksAs val="gap"/></c:chart><c:spPr><a:noFill/><a:ln><a:noFill/></a:ln></c:spPr>' + textProps("595959", 900) + "</c:chartSpace>";
|
|
4399
|
+
}
|
|
4400
|
+
};
|
|
4401
|
+
function marker2(tag, at2) {
|
|
4402
|
+
return `<xdr:${tag}><xdr:col>${at2[0]}</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>${at2[1]}</xdr:row><xdr:rowOff>0</xdr:rowOff></xdr:${tag}>`;
|
|
4403
|
+
}
|
|
4404
|
+
function seriesXml(i, s, chart, workbook) {
|
|
4405
|
+
const type = chart.type;
|
|
4406
|
+
const name = s.name !== null ? `<c:tx><c:v>${text(s.name)}</c:v></c:tx>` : "";
|
|
4407
|
+
let fill = "";
|
|
4408
|
+
if (s.color !== null && type !== "pie") {
|
|
4409
|
+
fill = type === "line" ? `<c:spPr><a:ln w="28575" cap="rnd"><a:solidFill><a:srgbClr val="${s.color}"/></a:solidFill><a:round/></a:ln></c:spPr>` : `<c:spPr><a:solidFill><a:srgbClr val="${s.color}"/></a:solidFill></c:spPr>`;
|
|
4410
|
+
}
|
|
4411
|
+
const invert = type === "column" || type === "bar" ? '<c:invertIfNegative val="0"/>' : "";
|
|
4412
|
+
const mark = type === "line" ? '<c:marker><c:symbol val="circle"/><c:size val="6"/></c:marker>' : "";
|
|
4413
|
+
const cat = chart.categories !== null ? "<c:cat>" + strRef(chart.categories, workbook) + "</c:cat>" : "";
|
|
4414
|
+
const smooth = type === "line" ? '<c:smooth val="0"/>' : "";
|
|
4415
|
+
return `<c:ser><c:idx val="${i}"/><c:order val="${i}"/>` + name + fill + invert + mark + cat + "<c:val>" + numRef(s.values, workbook) + "</c:val>" + smooth + "</c:ser>";
|
|
4416
|
+
}
|
|
4417
|
+
function strRef(range, workbook) {
|
|
4418
|
+
let pts = "";
|
|
4419
|
+
const vals = values(range, workbook);
|
|
4420
|
+
vals.forEach((v, idx) => {
|
|
4421
|
+
if (v !== null) pts += `<c:pt idx="${idx}"><c:v>${text(category(v))}</c:v></c:pt>`;
|
|
4422
|
+
});
|
|
4423
|
+
return `<c:strRef><c:f>${text(range.formula())}</c:f><c:strCache><c:ptCount val="${vals.length}"/>` + pts + "</c:strCache></c:strRef>";
|
|
4424
|
+
}
|
|
4425
|
+
function numRef(range, workbook) {
|
|
4426
|
+
let pts = "";
|
|
4427
|
+
const vals = values(range, workbook);
|
|
4428
|
+
vals.forEach((v, idx) => {
|
|
4429
|
+
if (typeof v === "number") pts += `<c:pt idx="${idx}"><c:v>${formatFloat(v)}</c:v></c:pt>`;
|
|
4430
|
+
});
|
|
4431
|
+
return `<c:numRef><c:f>${text(range.formula())}</c:f><c:numCache><c:formatCode>General</c:formatCode><c:ptCount val="${vals.length}"/>` + pts + "</c:numCache></c:numRef>";
|
|
4432
|
+
}
|
|
4433
|
+
function values(range, workbook) {
|
|
4434
|
+
const sheet = workbook.sheets.find((s) => s.name === range.sheet);
|
|
4435
|
+
return range.addresses().map((address) => {
|
|
4436
|
+
const cell = sheet?.cells.get(address);
|
|
4437
|
+
if (cell === void 0) return null;
|
|
4438
|
+
const v = cell.formula !== null ? cell.cachedValue : cell.value;
|
|
4439
|
+
return v === void 0 ? null : v;
|
|
4440
|
+
});
|
|
4441
|
+
}
|
|
4442
|
+
function category(v) {
|
|
4443
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
4444
|
+
if (typeof v === "number") return formatFloat(v);
|
|
4445
|
+
return v;
|
|
4446
|
+
}
|
|
4447
|
+
function dataLabels(type, format) {
|
|
4448
|
+
const pos = type === "line" ? "t" : type === "pie" ? "bestFit" : "outEnd";
|
|
4449
|
+
return "<c:dLbls>" + (format !== null ? `<c:numFmt formatCode="${attr2(format)}" sourceLinked="0"/>` : "") + "<c:spPr><a:noFill/><a:ln><a:noFill/></a:ln></c:spPr>" + textProps("404040", 900) + `<c:dLblPos val="${pos}"/><c:showLegendKey val="0"/><c:showVal val="1"/><c:showCatName val="0"/><c:showSerName val="0"/><c:showPercent val="0"/><c:showBubbleSize val="0"/></c:dLbls>`;
|
|
4450
|
+
}
|
|
4451
|
+
function axes(horizontal, format) {
|
|
4452
|
+
const catPos = horizontal ? "l" : "b";
|
|
4453
|
+
const valPos = horizontal ? "b" : "l";
|
|
4454
|
+
const numFmt = format !== null ? `<c:numFmt formatCode="${attr2(format)}" sourceLinked="0"/>` : '<c:numFmt formatCode="General" sourceLinked="1"/>';
|
|
4455
|
+
return `<c:catAx><c:axId val="10"/><c:scaling><c:orientation val="${horizontal ? "maxMin" : "minMax"}"/></c:scaling><c:delete val="0"/><c:axPos val="${catPos}"/><c:numFmt formatCode="General" sourceLinked="1"/><c:majorTickMark val="none"/><c:minorTickMark val="none"/><c:tickLblPos val="low"/><c:spPr><a:ln w="9525"><a:solidFill><a:srgbClr val="BFBFBF"/></a:solidFill></a:ln></c:spPr><c:crossAx val="20"/><c:crosses val="autoZero"/><c:auto val="1"/><c:lblAlgn val="ctr"/><c:lblOffset val="100"/><c:noMultiLvlLbl val="0"/></c:catAx><c:valAx><c:axId val="20"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="${valPos}"/><c:majorGridlines><c:spPr><a:ln w="9525"><a:solidFill><a:srgbClr val="E5E7EB"/></a:solidFill></a:ln></c:spPr></c:majorGridlines>` + numFmt + `<c:majorTickMark val="none"/><c:minorTickMark val="none"/><c:tickLblPos val="nextTo"/><c:spPr><a:ln><a:noFill/></a:ln></c:spPr><c:crossAx val="10"/><c:crosses val="${horizontal ? "max" : "autoZero"}"/><c:crossBetween val="between"/></c:valAx>`;
|
|
4456
|
+
}
|
|
4457
|
+
function textProps(color2, size) {
|
|
4458
|
+
return `<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz="${size}"><a:solidFill><a:srgbClr val="${color2}"/></a:solidFill></a:defRPr></a:pPr><a:endParaRPr lang="en-US"/></a:p></c:txPr>`;
|
|
4459
|
+
}
|
|
4460
|
+
function text(s) {
|
|
4461
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4462
|
+
}
|
|
4463
|
+
function attr2(s) {
|
|
4464
|
+
return text(s).replace(/"/g, """).replace(/'/g, "'");
|
|
4465
|
+
}
|
|
4466
|
+
|
|
3981
4467
|
// src/writer/xlsx-writer.ts
|
|
3982
4468
|
var encoder2 = new TextEncoder();
|
|
3983
4469
|
function footerCodes(footer) {
|
|
@@ -3989,6 +4475,9 @@ function footerCodes(footer) {
|
|
|
3989
4475
|
}
|
|
3990
4476
|
return codes;
|
|
3991
4477
|
}
|
|
4478
|
+
function drawingRelId(comments) {
|
|
4479
|
+
return comments ? "rId3" : "rId1";
|
|
4480
|
+
}
|
|
3992
4481
|
function decimal(value) {
|
|
3993
4482
|
const out = value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
3994
4483
|
return out === "" || out === "-0" ? "0" : out;
|
|
@@ -4012,14 +4501,29 @@ var XlsxWriter = class {
|
|
|
4012
4501
|
add("xl/styles.xml", styles.toXml());
|
|
4013
4502
|
add("docProps/core.xml", this.coreXml(workbook));
|
|
4014
4503
|
add("docProps/app.xml", this.appXml());
|
|
4504
|
+
const firstChart = [];
|
|
4505
|
+
let next = 1;
|
|
4506
|
+
workbook.sheets.forEach((sheet, i) => {
|
|
4507
|
+
firstChart[i] = next;
|
|
4508
|
+
next += sheet.charts.length;
|
|
4509
|
+
});
|
|
4015
4510
|
workbook.sheets.forEach((sheet, i) => {
|
|
4016
4511
|
const n = i + 1;
|
|
4017
4512
|
add(`xl/worksheets/sheet${n}.xml`, sheetXmls[i]);
|
|
4513
|
+
if (sheet.hasComments() || sheet.charts.length > 0) {
|
|
4514
|
+
add(`xl/worksheets/_rels/sheet${n}.xml.rels`, this.sheetRelsXml(n, sheet.hasComments(), sheet.charts.length > 0));
|
|
4515
|
+
}
|
|
4018
4516
|
if (sheet.hasComments()) {
|
|
4019
|
-
add(`xl/worksheets/_rels/sheet${n}.xml.rels`, this.sheetRelsXml(n));
|
|
4020
4517
|
add(`xl/comments${n}.xml`, this.commentsXml(sheet));
|
|
4021
4518
|
add(`xl/drawings/vmlDrawing${n}.vml`, this.vmlDrawingXml(sheet));
|
|
4022
4519
|
}
|
|
4520
|
+
if (sheet.charts.length > 0) {
|
|
4521
|
+
add(`xl/drawings/drawing${n}.xml`, ChartXml.drawing(sheet.charts, firstChart[i]));
|
|
4522
|
+
add(`xl/drawings/_rels/drawing${n}.xml.rels`, ChartXml.drawingRels(sheet.charts, firstChart[i]));
|
|
4523
|
+
sheet.charts.forEach((chart, k) => {
|
|
4524
|
+
add(`xl/charts/chart${firstChart[i] + k}.xml`, ChartXml.chart(chart, workbook));
|
|
4525
|
+
});
|
|
4526
|
+
}
|
|
4023
4527
|
});
|
|
4024
4528
|
return zipSync(files);
|
|
4025
4529
|
}
|
|
@@ -4033,6 +4537,16 @@ var XlsxWriter = class {
|
|
|
4033
4537
|
const n = i + 1;
|
|
4034
4538
|
overrides += `<Override PartName="/xl/comments${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>`;
|
|
4035
4539
|
}
|
|
4540
|
+
let chartNum = 1;
|
|
4541
|
+
workbook.sheets.forEach((sheet, i) => {
|
|
4542
|
+
if (sheet.charts.length === 0) return;
|
|
4543
|
+
const n = i + 1;
|
|
4544
|
+
overrides += `<Override PartName="/xl/drawings/drawing${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/>`;
|
|
4545
|
+
for (let k = 0; k < sheet.charts.length; k++) {
|
|
4546
|
+
overrides += `<Override PartName="/xl/charts/chart${chartNum}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>`;
|
|
4547
|
+
chartNum++;
|
|
4548
|
+
}
|
|
4549
|
+
});
|
|
4036
4550
|
let defaults = '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/>';
|
|
4037
4551
|
if (sheetsWithComments.length > 0) {
|
|
4038
4552
|
defaults += '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>';
|
|
@@ -4061,8 +4575,16 @@ var XlsxWriter = class {
|
|
|
4061
4575
|
rels += `<Relationship Id="rId${workbook.sheets.length + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
|
|
4062
4576
|
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
4063
4577
|
}
|
|
4064
|
-
|
|
4065
|
-
|
|
4578
|
+
/** Comments keep rId1 (vml) and rId2 (comments); the chart drawing takes the next id. */
|
|
4579
|
+
sheetRelsXml(sheetNum, comments, charts) {
|
|
4580
|
+
let rels = "";
|
|
4581
|
+
if (comments) {
|
|
4582
|
+
rels += `<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="../drawings/vmlDrawing${sheetNum}.vml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="../comments${sheetNum}.xml"/>`;
|
|
4583
|
+
}
|
|
4584
|
+
if (charts) {
|
|
4585
|
+
rels += `<Relationship Id="${drawingRelId(comments)}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" Target="../drawings/drawing${sheetNum}.xml"/>`;
|
|
4586
|
+
}
|
|
4587
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
4066
4588
|
}
|
|
4067
4589
|
sheetXml(sheet, styles) {
|
|
4068
4590
|
let sheetViewsXml = "";
|
|
@@ -4110,7 +4632,7 @@ var XlsxWriter = class {
|
|
|
4110
4632
|
pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + (print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
|
|
4111
4633
|
}
|
|
4112
4634
|
const headerFooterXml = print?.footer ? `<headerFooter><oddFooter>${this.escape(footerCodes(print.footer))}</oddFooter></headerFooter>` : "";
|
|
4113
|
-
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + sheetPrXml + sheetViewsXml + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + pageSetupXml + headerFooterXml + legacyDrawing + "</worksheet>";
|
|
4635
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + sheetPrXml + sheetViewsXml + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + pageSetupXml + headerFooterXml + (sheet.charts.length > 0 ? `<drawing r:id="${drawingRelId(sheet.hasComments())}"/>` : "") + legacyDrawing + "</worksheet>";
|
|
4114
4636
|
}
|
|
4115
4637
|
cellXml(cell, styles) {
|
|
4116
4638
|
const ref = cell.address;
|
|
@@ -4150,8 +4672,8 @@ var XlsxWriter = class {
|
|
|
4150
4672
|
const author = entry.comment.author ?? "Author";
|
|
4151
4673
|
if (!authors.includes(author)) authors.push(author);
|
|
4152
4674
|
const authorIdx = authors.indexOf(author);
|
|
4153
|
-
const
|
|
4154
|
-
commentList += `<comment ref="${entry.address}" authorId="${authorIdx}"><text><r><t xml:space="preserve">${
|
|
4675
|
+
const text2 = this.escape(entry.comment.text);
|
|
4676
|
+
commentList += `<comment ref="${entry.address}" authorId="${authorIdx}"><text><r><t xml:space="preserve">${text2}</t></r></text></comment>`;
|
|
4155
4677
|
}
|
|
4156
4678
|
let authorsXml = "<authors>";
|
|
4157
4679
|
for (const a of authors) authorsXml += `<author>${this.escape(a)}</author>`;
|
|
@@ -5031,19 +5553,19 @@ function canonEntries(root) {
|
|
|
5031
5553
|
continue;
|
|
5032
5554
|
}
|
|
5033
5555
|
stack.pop();
|
|
5034
|
-
const
|
|
5556
|
+
const text2 = top.list ? "[" + top.parts.join(",") + "]" : "{" + top.parts.join(",") + "}";
|
|
5035
5557
|
const parent = stack[stack.length - 1];
|
|
5036
5558
|
if (parent === void 0) {
|
|
5037
|
-
return
|
|
5559
|
+
return text2;
|
|
5038
5560
|
}
|
|
5039
|
-
parent.parts.push(member(parent, top.key,
|
|
5561
|
+
parent.parts.push(member(parent, top.key, text2));
|
|
5040
5562
|
}
|
|
5041
5563
|
}
|
|
5042
5564
|
function entriesFor(value) {
|
|
5043
5565
|
return Array.isArray(value) ? Array.from(value, (item, i) => [String(i), item]) : entriesOf(value);
|
|
5044
5566
|
}
|
|
5045
|
-
function member(frame, key,
|
|
5046
|
-
return frame.list ?
|
|
5567
|
+
function member(frame, key, text2) {
|
|
5568
|
+
return frame.list ? text2 : quote(key) + ":" + text2;
|
|
5047
5569
|
}
|
|
5048
5570
|
function scalar(value) {
|
|
5049
5571
|
switch (typeof value) {
|
|
@@ -5061,11 +5583,11 @@ function scalar(value) {
|
|
|
5061
5583
|
return JSON.stringify(value) ?? "null";
|
|
5062
5584
|
}
|
|
5063
5585
|
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
5064
|
-
function quote(
|
|
5065
|
-
if (LONE_SURROGATE.test(
|
|
5586
|
+
function quote(text2) {
|
|
5587
|
+
if (LONE_SURROGATE.test(text2)) {
|
|
5066
5588
|
throw unencodable("Single unpaired UTF-16 surrogate");
|
|
5067
5589
|
}
|
|
5068
|
-
return JSON.stringify(
|
|
5590
|
+
return JSON.stringify(text2);
|
|
5069
5591
|
}
|
|
5070
5592
|
function unencodable(reason) {
|
|
5071
5593
|
return new TypeError(`[holy-sheet] cannot compare a value JSON cannot hold: ${reason}`);
|
|
@@ -5234,6 +5756,43 @@ var holy_sheet_schema_default = {
|
|
|
5234
5756
|
propertyNames: { pattern: "^[1-9][0-9]*$" },
|
|
5235
5757
|
additionalProperties: { type: "number", minimum: 0 }
|
|
5236
5758
|
},
|
|
5759
|
+
charts: {
|
|
5760
|
+
type: "array",
|
|
5761
|
+
description: "Native Excel charts over CELL RANGES on this sheet: edit a figure and the chart follows. Use one whenever the sheet compares values -- a board reader sees the comparison before the numbers. Keep them quiet: a title that states the point, dataLabels with a format, one colour per series.",
|
|
5762
|
+
items: {
|
|
5763
|
+
type: "object",
|
|
5764
|
+
additionalProperties: false,
|
|
5765
|
+
required: ["type", "series"],
|
|
5766
|
+
properties: {
|
|
5767
|
+
type: { type: "string", enum: ["column", "bar", "line", "pie"], description: "column compares categories; bar does it horizontally (long labels, ranked lists); line shows change over time; pie shows parts of ONE whole (at most ~5 slices)." },
|
|
5768
|
+
title: { type: "string" },
|
|
5769
|
+
categories: { type: "string", description: `The labels along the axis (or the pie's slices), as a range: "A5:A7". Another sheet: "'Data'!A5:A7".` },
|
|
5770
|
+
series: {
|
|
5771
|
+
type: "array",
|
|
5772
|
+
minItems: 1,
|
|
5773
|
+
items: {
|
|
5774
|
+
type: "object",
|
|
5775
|
+
additionalProperties: false,
|
|
5776
|
+
required: ["values"],
|
|
5777
|
+
properties: {
|
|
5778
|
+
name: { type: "string", description: "Shown in the legend." },
|
|
5779
|
+
values: { type: "string", description: 'The numbers, as a range the same length as categories: "B5:B7".' },
|
|
5780
|
+
color: { type: "string", description: "#RRGGBB. Ignored for a pie, whose slices take a palette." }
|
|
5781
|
+
}
|
|
5782
|
+
}
|
|
5783
|
+
},
|
|
5784
|
+
position: {
|
|
5785
|
+
type: "object",
|
|
5786
|
+
additionalProperties: false,
|
|
5787
|
+
description: 'Where the chart sits: from its top-left cell to its bottom-right cell, {"from": "E2", "to": "L18"}. Omitted: to the right of the data, 8 columns by 16 rows.',
|
|
5788
|
+
properties: { from: { type: "string" }, to: { type: "string" } }
|
|
5789
|
+
},
|
|
5790
|
+
dataLabels: { type: "boolean", description: "Print each value on the chart. Usually right for a board chart." },
|
|
5791
|
+
legend: { type: "boolean", description: "Defaults to on for several series or a pie, off for one series." },
|
|
5792
|
+
format: { type: "string", description: 'Excel number format for the value axis and labels: "$#,##0", "0.0%", "$#,##0,K".' }
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
},
|
|
5237
5796
|
print: {
|
|
5238
5797
|
type: "object",
|
|
5239
5798
|
additionalProperties: false,
|
|
@@ -5460,7 +6019,7 @@ var holy_sheet_schema_default = {
|
|
|
5460
6019
|
};
|
|
5461
6020
|
|
|
5462
6021
|
// src/agent.ts
|
|
5463
|
-
var VERSION = "2.
|
|
6022
|
+
var VERSION = "2.8.0";
|
|
5464
6023
|
function toU8(input) {
|
|
5465
6024
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
5466
6025
|
}
|