@particle-academy/holy-sheet 2.6.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 +33 -0
- package/dist/index.cjs +868 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -8
- package/dist/index.d.ts +100 -8
- package/dist/index.js +868 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -79,9 +79,9 @@ var Inference = {
|
|
|
79
79
|
return { header: headerName, type: "auto" };
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
|
-
function nonNullSample(
|
|
82
|
+
function nonNullSample(values2) {
|
|
83
83
|
const out = [];
|
|
84
|
-
for (const v of
|
|
84
|
+
for (const v of values2) {
|
|
85
85
|
if (v === null || v === void 0) continue;
|
|
86
86
|
out.push(v);
|
|
87
87
|
if (out.length >= SAMPLE_SIZE) break;
|
|
@@ -271,10 +271,10 @@ var CellAddress = {
|
|
|
271
271
|
|
|
272
272
|
// src/workbook/cell.ts
|
|
273
273
|
var Cell = class {
|
|
274
|
-
constructor(address, value,
|
|
274
|
+
constructor(address, value, formula2 = null, format = null, comment = null, cachedValue = null) {
|
|
275
275
|
this.address = address;
|
|
276
276
|
this.value = value;
|
|
277
|
-
this.formula =
|
|
277
|
+
this.formula = formula2;
|
|
278
278
|
this.format = format;
|
|
279
279
|
this.comment = comment;
|
|
280
280
|
this.cachedValue = cachedValue;
|
|
@@ -322,8 +322,8 @@ var ColumnWidths = {
|
|
|
322
322
|
|
|
323
323
|
// src/workbook/cell-comment.ts
|
|
324
324
|
var CellComment = class {
|
|
325
|
-
constructor(
|
|
326
|
-
this.text =
|
|
325
|
+
constructor(text2, author = null, color2 = null) {
|
|
326
|
+
this.text = text2;
|
|
327
327
|
this.author = author;
|
|
328
328
|
this.color = color2;
|
|
329
329
|
}
|
|
@@ -417,14 +417,20 @@ var MergedRegion = class {
|
|
|
417
417
|
};
|
|
418
418
|
|
|
419
419
|
// src/workbook/sheet.ts
|
|
420
|
-
var Sheet = class {
|
|
421
|
-
constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0) {
|
|
420
|
+
var Sheet = class _Sheet {
|
|
421
|
+
constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0, rowHeights = /* @__PURE__ */ new Map(), print = null, charts = []) {
|
|
422
422
|
this.name = name;
|
|
423
423
|
this.cells = cells;
|
|
424
424
|
this.mergedRegions = mergedRegions;
|
|
425
425
|
this.columnWidths = columnWidths;
|
|
426
426
|
this.frozenRows = frozenRows;
|
|
427
427
|
this.frozenCols = frozenCols;
|
|
428
|
+
this.rowHeights = rowHeights;
|
|
429
|
+
this.print = print;
|
|
430
|
+
this.charts = charts;
|
|
431
|
+
}
|
|
432
|
+
withCharts(charts) {
|
|
433
|
+
return new _Sheet(this.name, this.cells, this.mergedRegions, this.columnWidths, this.frozenRows, this.frozenCols, this.rowHeights, this.print, charts);
|
|
428
434
|
}
|
|
429
435
|
/** rowNumber → (colLetter → Cell), sorted by ascending row number. */
|
|
430
436
|
rows() {
|
|
@@ -455,6 +461,59 @@ var Sheet = class {
|
|
|
455
461
|
}
|
|
456
462
|
};
|
|
457
463
|
|
|
464
|
+
// src/workbook/chart-range.ts
|
|
465
|
+
var ChartRange = class _ChartRange {
|
|
466
|
+
constructor(sheet, c1, r1, c2, r2) {
|
|
467
|
+
this.sheet = sheet;
|
|
468
|
+
this.c1 = c1;
|
|
469
|
+
this.r1 = r1;
|
|
470
|
+
this.c2 = c2;
|
|
471
|
+
this.r2 = r2;
|
|
472
|
+
}
|
|
473
|
+
static parse(ref, defaultSheet) {
|
|
474
|
+
if (typeof ref !== "string") return null;
|
|
475
|
+
let text2 = ref.trim();
|
|
476
|
+
let sheet = defaultSheet;
|
|
477
|
+
const bang = text2.lastIndexOf("!");
|
|
478
|
+
if (bang >= 0) {
|
|
479
|
+
let sheetPart = text2.slice(0, bang);
|
|
480
|
+
text2 = text2.slice(bang + 1);
|
|
481
|
+
if (sheetPart.length >= 2 && sheetPart.startsWith("'") && sheetPart.endsWith("'")) {
|
|
482
|
+
sheetPart = sheetPart.slice(1, -1).replace(/''/g, "'");
|
|
483
|
+
}
|
|
484
|
+
if (sheetPart === "") return null;
|
|
485
|
+
sheet = sheetPart;
|
|
486
|
+
}
|
|
487
|
+
const ends = text2.replace(/\$/g, "").split(":");
|
|
488
|
+
if (ends.length > 2) return null;
|
|
489
|
+
const a = CellAddress.parse(ends[0]);
|
|
490
|
+
const b = CellAddress.parse(ends[1] ?? ends[0]);
|
|
491
|
+
if (a === null || b === null || a[1] < 1 || b[1] < 1) return null;
|
|
492
|
+
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]));
|
|
493
|
+
}
|
|
494
|
+
/** 'Q3 Revenue'!$B$5:$B$7 */
|
|
495
|
+
formula() {
|
|
496
|
+
const from = "$" + CellAddress.letter(this.c1) + "$" + this.r1;
|
|
497
|
+
const to = "$" + CellAddress.letter(this.c2) + "$" + this.r2;
|
|
498
|
+
return "'" + this.sheet.replace(/'/g, "''") + "'!" + from + (from === to ? "" : ":" + to);
|
|
499
|
+
}
|
|
500
|
+
/** "B5:B7" on its own sheet, "'Data'!B5:B7" on another. */
|
|
501
|
+
toSchema(ownSheet) {
|
|
502
|
+
const from = CellAddress.letter(this.c1) + this.r1;
|
|
503
|
+
const to = CellAddress.letter(this.c2) + this.r2;
|
|
504
|
+
const ref = from === to ? from : from + ":" + to;
|
|
505
|
+
return this.sheet === ownSheet ? ref : "'" + this.sheet.replace(/'/g, "''") + "'!" + ref;
|
|
506
|
+
}
|
|
507
|
+
/** A1 addresses, row by row. */
|
|
508
|
+
addresses() {
|
|
509
|
+
const out = [];
|
|
510
|
+
for (let r = this.r1; r <= this.r2; r++) {
|
|
511
|
+
for (let c = this.c1; c <= this.c2; c++) out.push(CellAddress.letter(c) + r);
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
458
517
|
// src/workbook/workbook.ts
|
|
459
518
|
var Workbook = class {
|
|
460
519
|
constructor(sheets, meta = {}) {
|
|
@@ -665,20 +724,23 @@ var Normalizer = class {
|
|
|
665
724
|
this.normalizeMerges(sheet.mergedRegions ?? []),
|
|
666
725
|
this.normalizeWidths(sheet),
|
|
667
726
|
toInt(sheet.frozenRows ?? 0),
|
|
668
|
-
toInt(sheet.frozenCols ?? 0)
|
|
727
|
+
toInt(sheet.frozenCols ?? 0),
|
|
728
|
+
normalizeRowHeights(sheet.rowHeights),
|
|
729
|
+
normalizePrint(sheet.print),
|
|
730
|
+
normalizeCharts(sheet.charts, name, cells)
|
|
669
731
|
);
|
|
670
732
|
}
|
|
671
733
|
normalizeCellMap(map, sheetName2) {
|
|
672
734
|
const cells = /* @__PURE__ */ new Map();
|
|
673
735
|
for (const [address, cellData] of Object.entries(map)) {
|
|
674
736
|
if (!isCellSpec(cellData)) {
|
|
675
|
-
const [bare,
|
|
737
|
+
const [bare, formula2] = promoteFormula(cellData);
|
|
676
738
|
cells.set(
|
|
677
739
|
address,
|
|
678
740
|
new Cell(
|
|
679
741
|
address,
|
|
680
|
-
|
|
681
|
-
|
|
742
|
+
formula2 !== null ? null : this.coerceValue(bare, null, address, sheetName2),
|
|
743
|
+
formula2
|
|
682
744
|
)
|
|
683
745
|
);
|
|
684
746
|
continue;
|
|
@@ -794,11 +856,11 @@ var Normalizer = class {
|
|
|
794
856
|
value.computedValue ?? null
|
|
795
857
|
);
|
|
796
858
|
}
|
|
797
|
-
const [bare,
|
|
859
|
+
const [bare, formula2] = promoteFormula(value);
|
|
798
860
|
return new Cell(
|
|
799
861
|
address,
|
|
800
|
-
|
|
801
|
-
|
|
862
|
+
formula2 !== null ? null : this.coerceValue(bare, columnFormat, address, sheetName2),
|
|
863
|
+
formula2,
|
|
802
864
|
columnFormat
|
|
803
865
|
);
|
|
804
866
|
}
|
|
@@ -856,6 +918,81 @@ function toInt(v) {
|
|
|
856
918
|
const n = Math.trunc(Number(v));
|
|
857
919
|
return Number.isFinite(n) ? n : 0;
|
|
858
920
|
}
|
|
921
|
+
var CHART_TYPES = ["column", "bar", "line", "pie"];
|
|
922
|
+
function normalizeCharts(charts, sheetName2, cells) {
|
|
923
|
+
if (!isPlainObject(charts) && !Array.isArray(charts)) return [];
|
|
924
|
+
let lastCol = -1;
|
|
925
|
+
for (const address of cells.keys()) {
|
|
926
|
+
const parsed = CellAddress.parse(address);
|
|
927
|
+
if (parsed !== null) lastCol = Math.max(lastCol, parsed[0]);
|
|
928
|
+
}
|
|
929
|
+
const out = [];
|
|
930
|
+
for (const chart of Object.values(charts)) {
|
|
931
|
+
if (!isPlainObject(chart) || !CHART_TYPES.includes(chart.type)) continue;
|
|
932
|
+
const series = [];
|
|
933
|
+
const rawSeries = isPlainObject(chart.series) || Array.isArray(chart.series) ? Object.values(chart.series) : [];
|
|
934
|
+
for (const s of rawSeries) {
|
|
935
|
+
const values2 = isPlainObject(s) || Array.isArray(s) ? ChartRange.parse(s.values, sheetName2) : null;
|
|
936
|
+
if (values2 === null) continue;
|
|
937
|
+
const rec = s;
|
|
938
|
+
const m = typeof rec.color === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(rec.color) : null;
|
|
939
|
+
series.push({ name: typeof rec.name === "string" ? rec.name : null, values: values2, color: m ? m[1].toUpperCase() : null });
|
|
940
|
+
}
|
|
941
|
+
if (series.length === 0) continue;
|
|
942
|
+
const stack = out.length;
|
|
943
|
+
let from = [lastCol + 2, 1 + 18 * stack];
|
|
944
|
+
let to = [from[0] + 8, from[1] + 16];
|
|
945
|
+
if (isPlainObject(chart.position) || Array.isArray(chart.position)) {
|
|
946
|
+
const p = chart.position;
|
|
947
|
+
const a = typeof p.from === "string" ? CellAddress.parse(p.from) : null;
|
|
948
|
+
const b = typeof p.to === "string" ? CellAddress.parse(p.to) : null;
|
|
949
|
+
if (a !== null && b !== null && b[0] > a[0] && b[1] > a[1]) {
|
|
950
|
+
from = [a[0], a[1] - 1];
|
|
951
|
+
to = [b[0], b[1] - 1];
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
out.push({
|
|
955
|
+
type: chart.type,
|
|
956
|
+
title: typeof chart.title === "string" && chart.title !== "" ? chart.title : null,
|
|
957
|
+
categories: ChartRange.parse(chart.categories, sheetName2),
|
|
958
|
+
series,
|
|
959
|
+
from,
|
|
960
|
+
to,
|
|
961
|
+
legend: typeof chart.legend === "boolean" ? chart.legend : null,
|
|
962
|
+
dataLabels: chart.dataLabels === true,
|
|
963
|
+
format: typeof chart.format === "string" && chart.format !== "" ? chart.format : null
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
return out;
|
|
967
|
+
}
|
|
968
|
+
function phpNumeric(v) {
|
|
969
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
970
|
+
return isNumericString(v) ? numericStringToNumber(v) : null;
|
|
971
|
+
}
|
|
972
|
+
function normalizeRowHeights(map) {
|
|
973
|
+
const out = /* @__PURE__ */ new Map();
|
|
974
|
+
if (!isPlainObject(map) && !Array.isArray(map)) return out;
|
|
975
|
+
for (const [row, px] of Object.entries(map)) {
|
|
976
|
+
const n = phpNumeric(px);
|
|
977
|
+
if (/^[1-9]\d{0,6}$/.test(row) && n !== null && n >= 0) out.set(parseInt(row, 10), n);
|
|
978
|
+
}
|
|
979
|
+
return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
|
|
980
|
+
}
|
|
981
|
+
function normalizePrint(print) {
|
|
982
|
+
if (!isPlainObject(print) && !Array.isArray(print)) return null;
|
|
983
|
+
const p = print;
|
|
984
|
+
const orientation = p.orientation === "portrait" || p.orientation === "landscape" ? p.orientation : null;
|
|
985
|
+
const fitToWidth = p.fitToWidth === true;
|
|
986
|
+
let footer = null;
|
|
987
|
+
if (isPlainObject(p.footer) || Array.isArray(p.footer)) {
|
|
988
|
+
const f = p.footer;
|
|
989
|
+
const slot = (k) => typeof f[k] === "string" ? f[k] : "";
|
|
990
|
+
footer = { left: slot("left"), center: slot("center"), right: slot("right") };
|
|
991
|
+
if (footer.left === "" && footer.center === "" && footer.right === "") footer = null;
|
|
992
|
+
}
|
|
993
|
+
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
994
|
+
return { orientation, fitToWidth, footer };
|
|
995
|
+
}
|
|
859
996
|
|
|
860
997
|
// src/schema/formula-linter.ts
|
|
861
998
|
var ERR_VALUE = "#VALUE!";
|
|
@@ -918,9 +1055,9 @@ var FormulaLinter = class {
|
|
|
918
1055
|
isError(v) {
|
|
919
1056
|
return typeof v === "string" && v.length > 0 && v[0] === "#";
|
|
920
1057
|
}
|
|
921
|
-
evaluate(
|
|
1058
|
+
evaluate(formula2, defaultSheet, stack) {
|
|
922
1059
|
try {
|
|
923
|
-
const c = { tokens: this.tokenize(
|
|
1060
|
+
const c = { tokens: this.tokenize(formula2), pos: 0 };
|
|
924
1061
|
const result = this.parseExpr(c, defaultSheet, stack);
|
|
925
1062
|
if (c.pos < c.tokens.length) return ERR_NAME;
|
|
926
1063
|
return result;
|
|
@@ -1181,13 +1318,13 @@ var FormulaLinter = class {
|
|
|
1181
1318
|
const row1 = Math.min(a[1], b[1]);
|
|
1182
1319
|
const col2 = Math.max(a[0], b[0]);
|
|
1183
1320
|
const row2 = Math.max(a[1], b[1]);
|
|
1184
|
-
const
|
|
1321
|
+
const values2 = [];
|
|
1185
1322
|
for (let r = row1; r <= row2; r++) {
|
|
1186
1323
|
for (let col = col1; col <= col2; col++) {
|
|
1187
|
-
|
|
1324
|
+
values2.push(this.resolveCell(sheet, CellAddress.letter(col) + r, stack));
|
|
1188
1325
|
}
|
|
1189
1326
|
}
|
|
1190
|
-
return
|
|
1327
|
+
return values2;
|
|
1191
1328
|
}
|
|
1192
1329
|
expectOp(c, op) {
|
|
1193
1330
|
const t = c.tokens[c.pos];
|
|
@@ -1319,12 +1456,12 @@ var FormulaLinter = class {
|
|
|
1319
1456
|
return truthy ? args[1] : args[2] ?? false;
|
|
1320
1457
|
}
|
|
1321
1458
|
// ---- Hints ----
|
|
1322
|
-
hint(error2,
|
|
1459
|
+
hint(error2, formula2, sheet) {
|
|
1323
1460
|
switch (error2) {
|
|
1324
1461
|
case ERR_VALUE:
|
|
1325
|
-
return this.hintValue(
|
|
1462
|
+
return this.hintValue(formula2, sheet);
|
|
1326
1463
|
case ERR_REF:
|
|
1327
|
-
return this.hintRef(
|
|
1464
|
+
return this.hintRef(formula2);
|
|
1328
1465
|
case ERR_NAME:
|
|
1329
1466
|
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.";
|
|
1330
1467
|
case ERR_DIV0:
|
|
@@ -1335,8 +1472,8 @@ var FormulaLinter = class {
|
|
|
1335
1472
|
return "Formula evaluation failed.";
|
|
1336
1473
|
}
|
|
1337
1474
|
}
|
|
1338
|
-
hintRef(
|
|
1339
|
-
for (const m of
|
|
1475
|
+
hintRef(formula2) {
|
|
1476
|
+
for (const m of formula2.matchAll(new RegExp(SHEET_QUALIFIER, "g"))) {
|
|
1340
1477
|
const name = this.cleanSheetName(m[1]);
|
|
1341
1478
|
if (!this.sheetNames.has(name.toLowerCase())) {
|
|
1342
1479
|
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.`;
|
|
@@ -1344,10 +1481,10 @@ var FormulaLinter = class {
|
|
|
1344
1481
|
}
|
|
1345
1482
|
return "A cell reference points to a cell that doesn't exist in the workbook. Check column letters and row numbers.";
|
|
1346
1483
|
}
|
|
1347
|
-
hintValue(
|
|
1484
|
+
hintValue(formula2, sheet) {
|
|
1348
1485
|
const re = new RegExp(`(?:${SHEET_QUALIFIER})?\\$?([A-Z]+)\\$?(\\d+)`, "gi");
|
|
1349
1486
|
const offenders = [];
|
|
1350
|
-
for (const m of
|
|
1487
|
+
for (const m of formula2.matchAll(re)) {
|
|
1351
1488
|
const sheetName2 = m[1] ? this.sheetNames.get(this.cleanSheetName(m[1]).toLowerCase()) ?? this.cleanSheetName(m[1]) : sheet;
|
|
1352
1489
|
const a1 = m[2].toUpperCase() + m[3];
|
|
1353
1490
|
const key = sheetName2 + "!" + a1;
|
|
@@ -1470,11 +1607,11 @@ var Repairer = class {
|
|
|
1470
1607
|
sheet.columns.forEach((col, colIdx) => {
|
|
1471
1608
|
if (!isPlainObject(col)) return;
|
|
1472
1609
|
if (col.type !== void 0 && col.type !== "auto") return;
|
|
1473
|
-
const
|
|
1610
|
+
const values2 = [];
|
|
1474
1611
|
for (const row of sheet.rows) {
|
|
1475
|
-
if (Array.isArray(row) && colIdx < row.length)
|
|
1612
|
+
if (Array.isArray(row) && colIdx < row.length) values2.push(row[colIdx]);
|
|
1476
1613
|
}
|
|
1477
|
-
const nonNull =
|
|
1614
|
+
const nonNull = values2.filter((v) => v !== null && v !== void 0);
|
|
1478
1615
|
if (nonNull.length === 0) return;
|
|
1479
1616
|
const allDateLike = nonNull.every((v) => typeof v === "string" && ISO_DATE2.test(v));
|
|
1480
1617
|
if (allDateLike) {
|
|
@@ -1703,6 +1840,98 @@ var Validator = class {
|
|
|
1703
1840
|
}
|
|
1704
1841
|
}
|
|
1705
1842
|
}
|
|
1843
|
+
if (sheet.rowHeights != null) {
|
|
1844
|
+
if (!isPlainObject(sheet.rowHeights) && !Array.isArray(sheet.rowHeights)) {
|
|
1845
|
+
errors.push(
|
|
1846
|
+
error(
|
|
1847
|
+
`${path}.rowHeights`,
|
|
1848
|
+
"object keyed by 1-based row number",
|
|
1849
|
+
typeOf(sheet.rowHeights),
|
|
1850
|
+
sheet.rowHeights,
|
|
1851
|
+
'Row heights map a row NUMBER to pixels: {"1": 32, "4": 28}.'
|
|
1852
|
+
)
|
|
1853
|
+
);
|
|
1854
|
+
} else {
|
|
1855
|
+
for (const [row, px] of Object.entries(sheet.rowHeights)) {
|
|
1856
|
+
const n = typeof px === "number" ? px : isNumericString(px) ? numericStringToNumber(px) : null;
|
|
1857
|
+
if (!/^[1-9]\d{0,6}$/.test(row)) {
|
|
1858
|
+
errors.push(error(
|
|
1859
|
+
`${path}.rowHeights.${row}`,
|
|
1860
|
+
"a 1-based row number",
|
|
1861
|
+
typeOf(row),
|
|
1862
|
+
row,
|
|
1863
|
+
'Keys are row numbers as Excel shows them: "1" is the first row.'
|
|
1864
|
+
));
|
|
1865
|
+
} else if (n === null || !Number.isFinite(n) || n < 0) {
|
|
1866
|
+
errors.push(error(
|
|
1867
|
+
`${path}.rowHeights.${row}`,
|
|
1868
|
+
"a non-negative number of pixels",
|
|
1869
|
+
typeOf(px),
|
|
1870
|
+
px,
|
|
1871
|
+
"A height is a number of pixels, like 28 (20 is a default row)."
|
|
1872
|
+
));
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
if (sheet.charts != null) {
|
|
1878
|
+
errors.push(...validateCharts(sheet.charts, path, typeof sheet.name === "string" ? sheet.name : ""));
|
|
1879
|
+
}
|
|
1880
|
+
if (sheet.print != null) {
|
|
1881
|
+
const print = sheet.print;
|
|
1882
|
+
if (!isPlainObject(print) && !Array.isArray(print)) {
|
|
1883
|
+
errors.push(error(
|
|
1884
|
+
`${path}.print`,
|
|
1885
|
+
"object {orientation?, fitToWidth?, footer?}",
|
|
1886
|
+
typeOf(print),
|
|
1887
|
+
print,
|
|
1888
|
+
'Print setup: {"orientation": "landscape", "fitToWidth": true, "footer": {"right": "Page {page} of {pages}"}}.'
|
|
1889
|
+
));
|
|
1890
|
+
} else {
|
|
1891
|
+
if (print.orientation != null && print.orientation !== "portrait" && print.orientation !== "landscape") {
|
|
1892
|
+
errors.push(error(
|
|
1893
|
+
`${path}.print.orientation`,
|
|
1894
|
+
"one of: portrait, landscape",
|
|
1895
|
+
typeOf(print.orientation),
|
|
1896
|
+
print.orientation,
|
|
1897
|
+
"Use landscape for a sheet wider than it is tall."
|
|
1898
|
+
));
|
|
1899
|
+
}
|
|
1900
|
+
if (print.fitToWidth != null && typeof print.fitToWidth !== "boolean") {
|
|
1901
|
+
errors.push(error(
|
|
1902
|
+
`${path}.print.fitToWidth`,
|
|
1903
|
+
"boolean",
|
|
1904
|
+
typeOf(print.fitToWidth),
|
|
1905
|
+
print.fitToWidth,
|
|
1906
|
+
"true scales the sheet to one page wide when printed."
|
|
1907
|
+
));
|
|
1908
|
+
}
|
|
1909
|
+
if (print.footer != null) {
|
|
1910
|
+
if (!isPlainObject(print.footer) && !Array.isArray(print.footer)) {
|
|
1911
|
+
errors.push(error(
|
|
1912
|
+
`${path}.print.footer`,
|
|
1913
|
+
"object {left?, center?, right?}",
|
|
1914
|
+
typeOf(print.footer),
|
|
1915
|
+
print.footer,
|
|
1916
|
+
"A footer has up to three slots of text."
|
|
1917
|
+
));
|
|
1918
|
+
} else {
|
|
1919
|
+
const footer = print.footer;
|
|
1920
|
+
for (const slot of ["left", "center", "right"]) {
|
|
1921
|
+
if (footer[slot] != null && typeof footer[slot] !== "string") {
|
|
1922
|
+
errors.push(error(
|
|
1923
|
+
`${path}.print.footer.${slot}`,
|
|
1924
|
+
"string",
|
|
1925
|
+
typeOf(footer[slot]),
|
|
1926
|
+
footer[slot],
|
|
1927
|
+
"Footer text; {page} and {pages} print the page number and count."
|
|
1928
|
+
));
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1706
1935
|
return errors;
|
|
1707
1936
|
}
|
|
1708
1937
|
validateColumn(col, path) {
|
|
@@ -1748,6 +1977,113 @@ var Validator = class {
|
|
|
1748
1977
|
return errors;
|
|
1749
1978
|
}
|
|
1750
1979
|
};
|
|
1980
|
+
function validateCharts(charts, path, sheetName2) {
|
|
1981
|
+
if (!Array.isArray(charts)) {
|
|
1982
|
+
return [
|
|
1983
|
+
error(
|
|
1984
|
+
`${path}.charts`,
|
|
1985
|
+
"a list of charts",
|
|
1986
|
+
typeOf(charts),
|
|
1987
|
+
charts,
|
|
1988
|
+
'charts is a list: [{"type": "column", "categories": "A5:A7", "series": [{"name": "Revenue", "values": "B5:B7"}]}].'
|
|
1989
|
+
)
|
|
1990
|
+
];
|
|
1991
|
+
}
|
|
1992
|
+
const errors = [];
|
|
1993
|
+
charts.forEach((chart, i) => {
|
|
1994
|
+
const at2 = `${path}.charts[${i}]`;
|
|
1995
|
+
if (!isPlainObject(chart) && !Array.isArray(chart)) {
|
|
1996
|
+
errors.push(error(at2, "object", typeOf(chart), chart, "A chart is an object with a type and series."));
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
const c = chart;
|
|
2000
|
+
if (!CHART_TYPES.includes(c.type)) {
|
|
2001
|
+
errors.push(error(
|
|
2002
|
+
`${at2}.type`,
|
|
2003
|
+
"one of: " + CHART_TYPES.join(", "),
|
|
2004
|
+
typeOf(c.type ?? null),
|
|
2005
|
+
c.type ?? null,
|
|
2006
|
+
"column compares values across categories; bar does the same horizontally (long labels); line shows change over time; pie shows parts of one whole."
|
|
2007
|
+
));
|
|
2008
|
+
}
|
|
2009
|
+
const series = c.series;
|
|
2010
|
+
if (!Array.isArray(series) || series.length === 0) {
|
|
2011
|
+
errors.push(error(
|
|
2012
|
+
`${at2}.series`,
|
|
2013
|
+
"a non-empty list of series",
|
|
2014
|
+
typeOf(series ?? null),
|
|
2015
|
+
series ?? null,
|
|
2016
|
+
'Each series names the range it plots: [{"name": "Revenue", "values": "B5:B7"}].'
|
|
2017
|
+
));
|
|
2018
|
+
} else {
|
|
2019
|
+
series.forEach((s, j) => {
|
|
2020
|
+
const isObj = isPlainObject(s) || Array.isArray(s);
|
|
2021
|
+
const values2 = isObj ? s.values ?? null : s;
|
|
2022
|
+
if (!isObj || ChartRange.parse(values2, sheetName2) === null) {
|
|
2023
|
+
errors.push(error(
|
|
2024
|
+
`${at2}.series[${j}].values`,
|
|
2025
|
+
'a cell range like "B5:B7"',
|
|
2026
|
+
typeOf(values2),
|
|
2027
|
+
values2,
|
|
2028
|
+
`A range of cells: "B5:B7" on this sheet, or "'Other sheet'!B5:B7".`
|
|
2029
|
+
));
|
|
2030
|
+
} else {
|
|
2031
|
+
const color2 = s.color;
|
|
2032
|
+
if (color2 != null && (typeof color2 !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(color2))) {
|
|
2033
|
+
errors.push(error(`${at2}.series[${j}].color`, "#RRGGBB", typeOf(color2), color2, 'A hex colour, like "#1F3A5F".'));
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
if (c.categories != null && ChartRange.parse(c.categories, sheetName2) === null) {
|
|
2039
|
+
errors.push(error(
|
|
2040
|
+
`${at2}.categories`,
|
|
2041
|
+
'a cell range like "A5:A7"',
|
|
2042
|
+
typeOf(c.categories),
|
|
2043
|
+
c.categories,
|
|
2044
|
+
'The labels along the axis, as a range: "A5:A7".'
|
|
2045
|
+
));
|
|
2046
|
+
}
|
|
2047
|
+
if (c.position != null) {
|
|
2048
|
+
const p = c.position;
|
|
2049
|
+
const isObj = isPlainObject(p) || Array.isArray(p);
|
|
2050
|
+
const a = isObj && typeof p.from === "string" ? CellAddress.parse(p.from) : null;
|
|
2051
|
+
const b = isObj && typeof p.to === "string" ? CellAddress.parse(p.to) : null;
|
|
2052
|
+
if (a === null || b === null || b[0] <= a[0] || b[1] <= a[1]) {
|
|
2053
|
+
errors.push(error(
|
|
2054
|
+
`${at2}.position`,
|
|
2055
|
+
'{"from": top-left cell, "to": bottom-right cell}',
|
|
2056
|
+
typeOf(p),
|
|
2057
|
+
p,
|
|
2058
|
+
'Two cells, the second below and to the right of the first: {"from": "E2", "to": "L18"} is 7 columns by 16 rows.'
|
|
2059
|
+
));
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
for (const key of ["title", "format"]) {
|
|
2063
|
+
if (c[key] != null && typeof c[key] !== "string") {
|
|
2064
|
+
errors.push(error(
|
|
2065
|
+
`${at2}.${key}`,
|
|
2066
|
+
"string",
|
|
2067
|
+
typeOf(c[key]),
|
|
2068
|
+
c[key],
|
|
2069
|
+
key === "format" ? 'An Excel number format for the axis and labels, like "$#,##0" or "0.0%".' : "The chart title."
|
|
2070
|
+
));
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
for (const key of ["dataLabels", "legend"]) {
|
|
2074
|
+
if (c[key] != null && typeof c[key] !== "boolean") {
|
|
2075
|
+
errors.push(error(
|
|
2076
|
+
`${at2}.${key}`,
|
|
2077
|
+
"boolean",
|
|
2078
|
+
typeOf(c[key]),
|
|
2079
|
+
c[key],
|
|
2080
|
+
key === "dataLabels" ? "true prints each value on the chart." : "Defaults to on for several series or a pie."
|
|
2081
|
+
));
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
return errors;
|
|
2086
|
+
}
|
|
1751
2087
|
function error(path, expected, got, value, hint) {
|
|
1752
2088
|
return { path, expected, got, value, hint };
|
|
1753
2089
|
}
|
|
@@ -2264,15 +2600,15 @@ var FormatSniffer = {
|
|
|
2264
2600
|
};
|
|
2265
2601
|
|
|
2266
2602
|
// src/reader/ods/ods-formula.ts
|
|
2267
|
-
function odsFormulaToA1(
|
|
2603
|
+
function odsFormulaToA1(formula2) {
|
|
2268
2604
|
let grammar = "of";
|
|
2269
|
-
const m = /^(of|oooc|msoxl):/i.exec(
|
|
2605
|
+
const m = /^(of|oooc|msoxl):/i.exec(formula2);
|
|
2270
2606
|
if (m) {
|
|
2271
2607
|
grammar = m[1].toLowerCase();
|
|
2272
|
-
|
|
2608
|
+
formula2 = formula2.slice(m[0].length);
|
|
2273
2609
|
}
|
|
2274
|
-
if (
|
|
2275
|
-
return grammar === "msoxl" ?
|
|
2610
|
+
if (formula2.startsWith("=")) formula2 = formula2.slice(1);
|
|
2611
|
+
return grammar === "msoxl" ? formula2 : translate(formula2);
|
|
2276
2612
|
}
|
|
2277
2613
|
function translate(s) {
|
|
2278
2614
|
let out = "";
|
|
@@ -2621,8 +2957,8 @@ var OdsStyles = class {
|
|
|
2621
2957
|
switch (el2.name) {
|
|
2622
2958
|
case "number-style": {
|
|
2623
2959
|
if (has2("scientific-number") || has2("fraction")) return null;
|
|
2624
|
-
for (const
|
|
2625
|
-
const iso = currencyCode(ownText(
|
|
2960
|
+
for (const text2 of parts.filter((p) => p.name === "text")) {
|
|
2961
|
+
const iso = currencyCode(ownText(text2).replace(/[-()"\u00A0]/g, ""));
|
|
2626
2962
|
if (iso !== null) return { displayFormat: "currency", decimals: decimals ?? 0, currency: iso };
|
|
2627
2963
|
}
|
|
2628
2964
|
if (!number) return null;
|
|
@@ -2792,6 +3128,45 @@ function sheetToSchema(sheet) {
|
|
|
2792
3128
|
}
|
|
2793
3129
|
if (sheet.frozenRows > 0) out["frozenRows"] = sheet.frozenRows;
|
|
2794
3130
|
if (sheet.frozenCols > 0) out["frozenCols"] = sheet.frozenCols;
|
|
3131
|
+
if (sheet.rowHeights.size > 0) {
|
|
3132
|
+
const heights = {};
|
|
3133
|
+
for (const [k, v] of sheet.rowHeights) heights[k] = v;
|
|
3134
|
+
out["rowHeights"] = heights;
|
|
3135
|
+
}
|
|
3136
|
+
if (sheet.print !== null) {
|
|
3137
|
+
const print = {};
|
|
3138
|
+
if (sheet.print.orientation !== null) print["orientation"] = sheet.print.orientation;
|
|
3139
|
+
if (sheet.print.fitToWidth) print["fitToWidth"] = true;
|
|
3140
|
+
if (sheet.print.footer !== null) {
|
|
3141
|
+
const footer = {};
|
|
3142
|
+
for (const k of ["left", "center", "right"]) if (sheet.print.footer[k] !== "") footer[k] = sheet.print.footer[k];
|
|
3143
|
+
print["footer"] = footer;
|
|
3144
|
+
}
|
|
3145
|
+
out["print"] = print;
|
|
3146
|
+
}
|
|
3147
|
+
if (sheet.charts.length > 0) {
|
|
3148
|
+
out["charts"] = sheet.charts.map((c) => chartToSchema(c, sheet.name));
|
|
3149
|
+
}
|
|
3150
|
+
return out;
|
|
3151
|
+
}
|
|
3152
|
+
function chartToSchema(chart, sheetName2) {
|
|
3153
|
+
const out = { type: chart.type };
|
|
3154
|
+
if (chart.title !== null) out["title"] = chart.title;
|
|
3155
|
+
if (chart.categories !== null) out["categories"] = chart.categories.toSchema(sheetName2);
|
|
3156
|
+
out["series"] = chart.series.map((s) => {
|
|
3157
|
+
const series = {};
|
|
3158
|
+
if (s.name !== null) series["name"] = s.name;
|
|
3159
|
+
series["values"] = s.values.toSchema(sheetName2);
|
|
3160
|
+
if (s.color !== null) series["color"] = "#" + s.color;
|
|
3161
|
+
return series;
|
|
3162
|
+
});
|
|
3163
|
+
out["position"] = {
|
|
3164
|
+
from: CellAddress.letter(chart.from[0]) + (chart.from[1] + 1),
|
|
3165
|
+
to: CellAddress.letter(chart.to[0]) + (chart.to[1] + 1)
|
|
3166
|
+
};
|
|
3167
|
+
if (chart.dataLabels) out["dataLabels"] = true;
|
|
3168
|
+
if (chart.legend !== null) out["legend"] = chart.legend;
|
|
3169
|
+
if (chart.format !== null) out["format"] = chart.format;
|
|
2795
3170
|
return out;
|
|
2796
3171
|
}
|
|
2797
3172
|
function cellToSchema(cell) {
|
|
@@ -2899,10 +3274,10 @@ function parseXml(src, options = {}) {
|
|
|
2899
3274
|
if (namespaces) root.content = [];
|
|
2900
3275
|
const stack = [root];
|
|
2901
3276
|
const scopes = [{ xml: XML_NS }];
|
|
2902
|
-
const appendText = (
|
|
3277
|
+
const appendText = (text2) => {
|
|
2903
3278
|
const top = stack[stack.length - 1];
|
|
2904
|
-
top.text +=
|
|
2905
|
-
if (namespaces &&
|
|
3279
|
+
top.text += text2;
|
|
3280
|
+
if (namespaces && text2 !== "") top.content.push(text2);
|
|
2906
3281
|
};
|
|
2907
3282
|
while (i < n) {
|
|
2908
3283
|
if (src[i] === "<") {
|
|
@@ -3123,7 +3498,7 @@ function hasContent(cell) {
|
|
|
3123
3498
|
}
|
|
3124
3499
|
function readCell(cell) {
|
|
3125
3500
|
const type = attr(cell, NS.OFFICE, "value-type") ?? null;
|
|
3126
|
-
const
|
|
3501
|
+
const text2 = paragraphs(cell);
|
|
3127
3502
|
let value = null;
|
|
3128
3503
|
let seconds = null;
|
|
3129
3504
|
let dateHasTime = false;
|
|
@@ -3152,20 +3527,20 @@ function readCell(cell) {
|
|
|
3152
3527
|
case "string": {
|
|
3153
3528
|
const error2 = attr(cell, NS.CALCEXT, "value-type") === "error";
|
|
3154
3529
|
const stored = attr(cell, NS.OFFICE, "string-value");
|
|
3155
|
-
value = stored !== void 0 && !error2 ? stored :
|
|
3530
|
+
value = stored !== void 0 && !error2 ? stored : text2 ?? "";
|
|
3156
3531
|
break;
|
|
3157
3532
|
}
|
|
3158
3533
|
default:
|
|
3159
|
-
value =
|
|
3534
|
+
value = text2 !== null && text2 !== "" ? text2 : null;
|
|
3160
3535
|
}
|
|
3161
|
-
let
|
|
3536
|
+
let formula2 = null;
|
|
3162
3537
|
let cached = null;
|
|
3163
3538
|
const formulaAttr = attr(cell, NS.TABLE, "formula");
|
|
3164
3539
|
if (formulaAttr !== void 0) {
|
|
3165
3540
|
const translated = odsFormulaToA1(formulaAttr);
|
|
3166
3541
|
const literalBoolean = type === "boolean" && /^[ \t\n\r\f\v]*(TRUE|FALSE)\(\)[ \t\n\r\f\v]*$/i.test(translated);
|
|
3167
3542
|
if (!literalBoolean) {
|
|
3168
|
-
|
|
3543
|
+
formula2 = translated;
|
|
3169
3544
|
cached = seconds !== null ? serial(seconds) : value;
|
|
3170
3545
|
value = null;
|
|
3171
3546
|
seconds = null;
|
|
@@ -3181,7 +3556,7 @@ function readCell(cell) {
|
|
|
3181
3556
|
const currency = attr(cell, NS.OFFICE, "currency");
|
|
3182
3557
|
return {
|
|
3183
3558
|
value,
|
|
3184
|
-
formula,
|
|
3559
|
+
formula: formula2,
|
|
3185
3560
|
cached,
|
|
3186
3561
|
type,
|
|
3187
3562
|
currency: currency !== void 0 ? phpTrim(currency) : null,
|
|
@@ -3250,6 +3625,127 @@ function readMeta(xml) {
|
|
|
3250
3625
|
return out;
|
|
3251
3626
|
}
|
|
3252
3627
|
|
|
3628
|
+
// src/reader/chart-parser.ts
|
|
3629
|
+
function descendants(node, name) {
|
|
3630
|
+
const out = [];
|
|
3631
|
+
const stack = node ? [...node.children].reverse() : [];
|
|
3632
|
+
while (stack.length > 0) {
|
|
3633
|
+
const n = stack.pop();
|
|
3634
|
+
if (n.name === name) out.push(n);
|
|
3635
|
+
for (let i = n.children.length - 1; i >= 0; i--) stack.push(n.children[i]);
|
|
3636
|
+
}
|
|
3637
|
+
return out;
|
|
3638
|
+
}
|
|
3639
|
+
function load(xml) {
|
|
3640
|
+
if (/<!DOCTYPE/i.test(xml)) return null;
|
|
3641
|
+
return parseXml(xml);
|
|
3642
|
+
}
|
|
3643
|
+
function wrap2(root) {
|
|
3644
|
+
return { name: "#document", attrs: {}, children: [root], text: "" };
|
|
3645
|
+
}
|
|
3646
|
+
var ChartParser = {
|
|
3647
|
+
anchors(drawingXml) {
|
|
3648
|
+
const root = load(drawingXml);
|
|
3649
|
+
if (!root) return [];
|
|
3650
|
+
const out = [];
|
|
3651
|
+
for (const anchor of descendants(wrap2(root), "twoCellAnchor")) {
|
|
3652
|
+
const from = marker(anchor, "from");
|
|
3653
|
+
const to = marker(anchor, "to");
|
|
3654
|
+
const chart = descendants(anchor, "chart")[0];
|
|
3655
|
+
const rel = chart ? at(chart, "id") ?? "" : "";
|
|
3656
|
+
if (from !== null && to !== null && rel !== "") out.push({ from, to, rel });
|
|
3657
|
+
}
|
|
3658
|
+
return out;
|
|
3659
|
+
},
|
|
3660
|
+
chart(chartXml, sheetName2, from, to) {
|
|
3661
|
+
const root = load(chartXml);
|
|
3662
|
+
if (!root) return null;
|
|
3663
|
+
const doc = wrap2(root);
|
|
3664
|
+
let plot;
|
|
3665
|
+
let type = null;
|
|
3666
|
+
for (const tag of ["barChart", "lineChart", "pieChart"]) {
|
|
3667
|
+
const found = descendants(doc, tag)[0];
|
|
3668
|
+
if (found) {
|
|
3669
|
+
plot = found;
|
|
3670
|
+
type = tag === "lineChart" ? "line" : tag === "pieChart" ? "pie" : at(el(found, "barDir"), "val") === "bar" ? "bar" : "column";
|
|
3671
|
+
break;
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
if (!plot || type === null) return null;
|
|
3675
|
+
let categories = null;
|
|
3676
|
+
const series = [];
|
|
3677
|
+
for (const ser of els(plot, "ser")) {
|
|
3678
|
+
const valRef = formula(ser, "val");
|
|
3679
|
+
const values2 = valRef !== null ? ChartRange.parse(valRef, sheetName2) : null;
|
|
3680
|
+
if (values2 === null) continue;
|
|
3681
|
+
if (categories === null) {
|
|
3682
|
+
const catRef = formula(ser, "cat");
|
|
3683
|
+
categories = catRef !== null ? ChartRange.parse(catRef, sheetName2) : null;
|
|
3684
|
+
}
|
|
3685
|
+
series.push({ name: seriesName(ser), values: values2, color: seriesColor(ser) });
|
|
3686
|
+
}
|
|
3687
|
+
if (series.length === 0) return null;
|
|
3688
|
+
const labels = el(plot, "dLbls");
|
|
3689
|
+
const dataLabels2 = labels !== void 0 && at(el(labels, "showVal"), "val") === "1";
|
|
3690
|
+
let format = null;
|
|
3691
|
+
for (const host of [labels, descendants(doc, "valAx")[0]]) {
|
|
3692
|
+
const numFmt = host ? el(host, "numFmt") : void 0;
|
|
3693
|
+
if (numFmt && at(numFmt, "sourceLinked") === "0" && (at(numFmt, "formatCode") ?? "") !== "") {
|
|
3694
|
+
format = at(numFmt, "formatCode");
|
|
3695
|
+
break;
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
let title = null;
|
|
3699
|
+
const titleEl = descendants(doc, "title")[0];
|
|
3700
|
+
if (titleEl) {
|
|
3701
|
+
const text2 = descendants(titleEl, "t").map((t) => t.text).join("");
|
|
3702
|
+
title = text2 !== "" ? text2 : null;
|
|
3703
|
+
}
|
|
3704
|
+
return {
|
|
3705
|
+
type,
|
|
3706
|
+
title,
|
|
3707
|
+
categories,
|
|
3708
|
+
series,
|
|
3709
|
+
from,
|
|
3710
|
+
to,
|
|
3711
|
+
legend: descendants(doc, "legend").length > 0 ? true : null,
|
|
3712
|
+
dataLabels: dataLabels2,
|
|
3713
|
+
format
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
};
|
|
3717
|
+
function marker(anchor, tag) {
|
|
3718
|
+
const m = el(anchor, tag);
|
|
3719
|
+
if (!m) return null;
|
|
3720
|
+
const col = descendants(m, "col")[0]?.text.trim() ?? "";
|
|
3721
|
+
const row = descendants(m, "row")[0]?.text.trim() ?? "";
|
|
3722
|
+
return /^\d+$/.test(col) && /^\d+$/.test(row) ? [parseInt(col, 10), parseInt(row, 10)] : null;
|
|
3723
|
+
}
|
|
3724
|
+
function formula(ser, name) {
|
|
3725
|
+
const host = el(ser, name);
|
|
3726
|
+
if (!host) return null;
|
|
3727
|
+
for (const ref of ["numRef", "strRef"]) {
|
|
3728
|
+
const f = el(el(host, ref), "f");
|
|
3729
|
+
if (f && f.text.trim() !== "") return f.text.trim();
|
|
3730
|
+
}
|
|
3731
|
+
return null;
|
|
3732
|
+
}
|
|
3733
|
+
function seriesName(ser) {
|
|
3734
|
+
const tx = el(ser, "tx");
|
|
3735
|
+
if (!tx) return null;
|
|
3736
|
+
const v = el(tx, "v");
|
|
3737
|
+
if (v) return v.text;
|
|
3738
|
+
const cached = descendants(tx, "v")[0];
|
|
3739
|
+
return cached ? cached.text : null;
|
|
3740
|
+
}
|
|
3741
|
+
function seriesColor(ser) {
|
|
3742
|
+
const spPr = el(ser, "spPr");
|
|
3743
|
+
if (!spPr) return null;
|
|
3744
|
+
const clr = descendants(spPr, "srgbClr")[0];
|
|
3745
|
+
const val = (clr ? at(clr, "val") ?? "" : "").toUpperCase();
|
|
3746
|
+
return /^[0-9A-F]{6}$/.test(val) ? val : null;
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3253
3749
|
// src/reader/comments-parser.ts
|
|
3254
3750
|
var CommentsParser = {
|
|
3255
3751
|
parse(commentsXml) {
|
|
@@ -3414,9 +3910,62 @@ var WorksheetParser = {
|
|
|
3414
3910
|
const merges = parseMerges(xml);
|
|
3415
3911
|
const columnWidths = parseColumnWidths(xml);
|
|
3416
3912
|
const [frozenRows, frozenCols] = parseFrozen(xml);
|
|
3417
|
-
return new Sheet(name, cells, merges, columnWidths, frozenRows, frozenCols);
|
|
3913
|
+
return new Sheet(name, cells, merges, columnWidths, frozenRows, frozenCols, parseRowHeights(xml), parsePrint(xml));
|
|
3418
3914
|
}
|
|
3419
3915
|
};
|
|
3916
|
+
function parseRowHeights(xml) {
|
|
3917
|
+
const out = /* @__PURE__ */ new Map();
|
|
3918
|
+
const sheetData = el(xml, "sheetData");
|
|
3919
|
+
if (!sheetData) return out;
|
|
3920
|
+
for (const row of els(sheetData, "row")) {
|
|
3921
|
+
const r = parseInt(at(row, "r") ?? "0", 10);
|
|
3922
|
+
const custom = at(row, "customHeight");
|
|
3923
|
+
const ht = at(row, "ht");
|
|
3924
|
+
if (r > 0 && ht !== void 0 && (custom === "1" || custom === "true")) {
|
|
3925
|
+
out.set(r, Math.round(parseFloat(ht) / 0.75 * 100) / 100);
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
return out;
|
|
3929
|
+
}
|
|
3930
|
+
function parsePrint(xml) {
|
|
3931
|
+
const fitToPage = ["1", "true"].includes(at(el(el(xml, "sheetPr"), "pageSetUpPr"), "fitToPage") ?? "");
|
|
3932
|
+
const setup = el(xml, "pageSetup");
|
|
3933
|
+
const o = at(setup, "orientation");
|
|
3934
|
+
const orientation = o === "portrait" || o === "landscape" ? o : null;
|
|
3935
|
+
const fitToWidth = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && ["0", ""].includes(at(setup, "fitToHeight") ?? "");
|
|
3936
|
+
const oddFooter = el(el(xml, "headerFooter"), "oddFooter");
|
|
3937
|
+
const footer = oddFooter !== void 0 ? parseFooterCodes(oddFooter.text) : null;
|
|
3938
|
+
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
3939
|
+
return { orientation, fitToWidth, footer };
|
|
3940
|
+
}
|
|
3941
|
+
function parseFooterCodes(codes) {
|
|
3942
|
+
const slots = { left: "", center: "", right: "" };
|
|
3943
|
+
let current = "center";
|
|
3944
|
+
const len = codes.length;
|
|
3945
|
+
for (let i = 0; i < len; i++) {
|
|
3946
|
+
const ch = codes[i];
|
|
3947
|
+
if (ch !== "&" || i + 1 >= len) {
|
|
3948
|
+
slots[current] += ch;
|
|
3949
|
+
continue;
|
|
3950
|
+
}
|
|
3951
|
+
const next = codes[++i];
|
|
3952
|
+
if (next === "&") slots[current] += "&";
|
|
3953
|
+
else if (next === "L") current = "left";
|
|
3954
|
+
else if (next === "C") current = "center";
|
|
3955
|
+
else if (next === "R") current = "right";
|
|
3956
|
+
else if (next === "P") slots[current] += "{page}";
|
|
3957
|
+
else if (next === "N") slots[current] += "{pages}";
|
|
3958
|
+
else if (next === '"') {
|
|
3959
|
+
const close = codes.indexOf('"', i + 1);
|
|
3960
|
+
i = close === -1 ? len : close;
|
|
3961
|
+
} else if (/[0-9]/.test(next)) {
|
|
3962
|
+
while (i + 1 < len && /[0-9]/.test(codes[i + 1])) i++;
|
|
3963
|
+
} else if (next === "K") {
|
|
3964
|
+
i = Math.min(len - 1, i + 6);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
return slots.left === "" && slots.center === "" && slots.right === "" ? null : slots;
|
|
3968
|
+
}
|
|
3420
3969
|
function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
3421
3970
|
const cells = /* @__PURE__ */ new Map();
|
|
3422
3971
|
const sheetData = el(xml, "sheetData");
|
|
@@ -3428,13 +3977,13 @@ function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
|
3428
3977
|
const type = c.attrs["t"] ?? "n";
|
|
3429
3978
|
const styleIdx = c.attrs["s"] !== void 0 ? parseInt(c.attrs["s"], 10) : 0;
|
|
3430
3979
|
const format = stylesIndex[styleIdx] ?? null;
|
|
3431
|
-
let
|
|
3980
|
+
let formula2 = null;
|
|
3432
3981
|
let cachedValue = null;
|
|
3433
3982
|
let value = null;
|
|
3434
3983
|
const f = el(c, "f");
|
|
3435
3984
|
const v = el(c, "v");
|
|
3436
3985
|
if (f) {
|
|
3437
|
-
|
|
3986
|
+
formula2 = f.text;
|
|
3438
3987
|
cachedValue = v ? coerceValue(v.text, type) : null;
|
|
3439
3988
|
} else if (type === "inlineStr" && el(c, "is") && el(el(c, "is"), "t")) {
|
|
3440
3989
|
value = el(el(c, "is"), "t").text;
|
|
@@ -3445,7 +3994,7 @@ function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
|
3445
3994
|
value = coerceValue(v.text, type);
|
|
3446
3995
|
}
|
|
3447
3996
|
const comment = comments[address] ?? null;
|
|
3448
|
-
cells.set(address, new Cell(address, value,
|
|
3997
|
+
cells.set(address, new Cell(address, value, formula2, format, comment, cachedValue));
|
|
3449
3998
|
}
|
|
3450
3999
|
}
|
|
3451
4000
|
return cells;
|
|
@@ -3536,7 +4085,26 @@ var XlsxReader = class {
|
|
|
3536
4085
|
if (commentsXml) comments = { ...comments, ...CommentsParser.parse(commentsXml) };
|
|
3537
4086
|
}
|
|
3538
4087
|
}
|
|
3539
|
-
|
|
4088
|
+
let sheet = WorksheetParser.parse(worksheetXml, name, stylesIndex, comments, sharedStrings);
|
|
4089
|
+
if (sheetRelsXml) {
|
|
4090
|
+
const charts = [];
|
|
4091
|
+
for (const dr of Object.values(RelsParser.byType(RelsParser.parse(sheetRelsXml), "/drawing"))) {
|
|
4092
|
+
const drawingPath = resolveRelativePath(sheetPath, dr.Target);
|
|
4093
|
+
const slash = drawingPath.lastIndexOf("/");
|
|
4094
|
+
const drawingXml = getText(drawingPath);
|
|
4095
|
+
const drawingRelsXml = getText(drawingPath.slice(0, slash) + "/_rels/" + drawingPath.slice(slash + 1) + ".rels");
|
|
4096
|
+
if (drawingXml === null || drawingRelsXml === null) continue;
|
|
4097
|
+
const drawingRels = RelsParser.parse(drawingRelsXml);
|
|
4098
|
+
for (const anchor of ChartParser.anchors(drawingXml)) {
|
|
4099
|
+
const target2 = drawingRels[anchor.rel]?.Target;
|
|
4100
|
+
const chartXml = typeof target2 === "string" ? getText(resolveRelativePath(drawingPath, target2)) : null;
|
|
4101
|
+
const chart = chartXml !== null ? ChartParser.chart(chartXml, name, anchor.from, anchor.to) : null;
|
|
4102
|
+
if (chart !== null) charts.push(chart);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
if (charts.length > 0) sheet = sheet.withCharts(charts);
|
|
4106
|
+
}
|
|
4107
|
+
sheets.push(sheet);
|
|
3540
4108
|
i++;
|
|
3541
4109
|
}
|
|
3542
4110
|
const meta = this.parseDocProps(getText("docProps/core.xml"));
|
|
@@ -3790,8 +4358,132 @@ function hexToArgb(hex2) {
|
|
|
3790
4358
|
return "FF000000";
|
|
3791
4359
|
}
|
|
3792
4360
|
|
|
4361
|
+
// src/writer/chart-xml.ts
|
|
4362
|
+
var NS_C = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
4363
|
+
var NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
4364
|
+
var NS_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
4365
|
+
var ChartXml = {
|
|
4366
|
+
drawing(charts, firstChartNum) {
|
|
4367
|
+
let anchors = "";
|
|
4368
|
+
charts.forEach((chart, k) => {
|
|
4369
|
+
const id = k + 2;
|
|
4370
|
+
const num = firstChartNum + k;
|
|
4371
|
+
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>`;
|
|
4372
|
+
});
|
|
4373
|
+
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>";
|
|
4374
|
+
},
|
|
4375
|
+
drawingRels(charts, firstChartNum) {
|
|
4376
|
+
let rels = "";
|
|
4377
|
+
charts.forEach((_, k) => {
|
|
4378
|
+
rels += `<Relationship Id="rId${k + 1}" Type="${NS_R}/chart" Target="../charts/chart${firstChartNum + k}.xml"/>`;
|
|
4379
|
+
});
|
|
4380
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
4381
|
+
},
|
|
4382
|
+
chart(chart, workbook) {
|
|
4383
|
+
const type = chart.type;
|
|
4384
|
+
let series = "";
|
|
4385
|
+
chart.series.forEach((s, i) => {
|
|
4386
|
+
series += seriesXml(i, s, chart, workbook);
|
|
4387
|
+
});
|
|
4388
|
+
const labels = chart.dataLabels ? dataLabels(type, chart.format) : "";
|
|
4389
|
+
let plot;
|
|
4390
|
+
if (type === "line") {
|
|
4391
|
+
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>';
|
|
4392
|
+
} else if (type === "pie") {
|
|
4393
|
+
plot = '<c:pieChart><c:varyColors val="1"/>' + series + labels + '<c:firstSliceAng val="0"/></c:pieChart>';
|
|
4394
|
+
} else {
|
|
4395
|
+
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>';
|
|
4396
|
+
}
|
|
4397
|
+
const axesXml = type === "pie" ? "" : axes(type === "bar", chart.format);
|
|
4398
|
+
const legend = chart.legend ?? (chart.series.length > 1 || type === "pie");
|
|
4399
|
+
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"/>';
|
|
4400
|
+
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>";
|
|
4401
|
+
}
|
|
4402
|
+
};
|
|
4403
|
+
function marker2(tag, at2) {
|
|
4404
|
+
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}>`;
|
|
4405
|
+
}
|
|
4406
|
+
function seriesXml(i, s, chart, workbook) {
|
|
4407
|
+
const type = chart.type;
|
|
4408
|
+
const name = s.name !== null ? `<c:tx><c:v>${text(s.name)}</c:v></c:tx>` : "";
|
|
4409
|
+
let fill = "";
|
|
4410
|
+
if (s.color !== null && type !== "pie") {
|
|
4411
|
+
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>`;
|
|
4412
|
+
}
|
|
4413
|
+
const invert = type === "column" || type === "bar" ? '<c:invertIfNegative val="0"/>' : "";
|
|
4414
|
+
const mark = type === "line" ? '<c:marker><c:symbol val="circle"/><c:size val="6"/></c:marker>' : "";
|
|
4415
|
+
const cat = chart.categories !== null ? "<c:cat>" + strRef(chart.categories, workbook) + "</c:cat>" : "";
|
|
4416
|
+
const smooth = type === "line" ? '<c:smooth val="0"/>' : "";
|
|
4417
|
+
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>";
|
|
4418
|
+
}
|
|
4419
|
+
function strRef(range, workbook) {
|
|
4420
|
+
let pts = "";
|
|
4421
|
+
const vals = values(range, workbook);
|
|
4422
|
+
vals.forEach((v, idx) => {
|
|
4423
|
+
if (v !== null) pts += `<c:pt idx="${idx}"><c:v>${text(category(v))}</c:v></c:pt>`;
|
|
4424
|
+
});
|
|
4425
|
+
return `<c:strRef><c:f>${text(range.formula())}</c:f><c:strCache><c:ptCount val="${vals.length}"/>` + pts + "</c:strCache></c:strRef>";
|
|
4426
|
+
}
|
|
4427
|
+
function numRef(range, workbook) {
|
|
4428
|
+
let pts = "";
|
|
4429
|
+
const vals = values(range, workbook);
|
|
4430
|
+
vals.forEach((v, idx) => {
|
|
4431
|
+
if (typeof v === "number") pts += `<c:pt idx="${idx}"><c:v>${formatFloat(v)}</c:v></c:pt>`;
|
|
4432
|
+
});
|
|
4433
|
+
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>";
|
|
4434
|
+
}
|
|
4435
|
+
function values(range, workbook) {
|
|
4436
|
+
const sheet = workbook.sheets.find((s) => s.name === range.sheet);
|
|
4437
|
+
return range.addresses().map((address) => {
|
|
4438
|
+
const cell = sheet?.cells.get(address);
|
|
4439
|
+
if (cell === void 0) return null;
|
|
4440
|
+
const v = cell.formula !== null ? cell.cachedValue : cell.value;
|
|
4441
|
+
return v === void 0 ? null : v;
|
|
4442
|
+
});
|
|
4443
|
+
}
|
|
4444
|
+
function category(v) {
|
|
4445
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
4446
|
+
if (typeof v === "number") return formatFloat(v);
|
|
4447
|
+
return v;
|
|
4448
|
+
}
|
|
4449
|
+
function dataLabels(type, format) {
|
|
4450
|
+
const pos = type === "line" ? "t" : type === "pie" ? "bestFit" : "outEnd";
|
|
4451
|
+
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>`;
|
|
4452
|
+
}
|
|
4453
|
+
function axes(horizontal, format) {
|
|
4454
|
+
const catPos = horizontal ? "l" : "b";
|
|
4455
|
+
const valPos = horizontal ? "b" : "l";
|
|
4456
|
+
const numFmt = format !== null ? `<c:numFmt formatCode="${attr2(format)}" sourceLinked="0"/>` : '<c:numFmt formatCode="General" sourceLinked="1"/>';
|
|
4457
|
+
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>`;
|
|
4458
|
+
}
|
|
4459
|
+
function textProps(color2, size) {
|
|
4460
|
+
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>`;
|
|
4461
|
+
}
|
|
4462
|
+
function text(s) {
|
|
4463
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4464
|
+
}
|
|
4465
|
+
function attr2(s) {
|
|
4466
|
+
return text(s).replace(/"/g, """).replace(/'/g, "'");
|
|
4467
|
+
}
|
|
4468
|
+
|
|
3793
4469
|
// src/writer/xlsx-writer.ts
|
|
3794
4470
|
var encoder2 = new TextEncoder();
|
|
4471
|
+
function footerCodes(footer) {
|
|
4472
|
+
let codes = "";
|
|
4473
|
+
for (const [slot, code] of [["left", "&L"], ["center", "&C"], ["right", "&R"]]) {
|
|
4474
|
+
if (footer[slot] !== "") {
|
|
4475
|
+
codes += code + footer[slot].replace(/&/g, "&&").replace(/\{pages\}/g, "&N").replace(/\{page\}/g, "&P");
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
return codes;
|
|
4479
|
+
}
|
|
4480
|
+
function drawingRelId(comments) {
|
|
4481
|
+
return comments ? "rId3" : "rId1";
|
|
4482
|
+
}
|
|
4483
|
+
function decimal(value) {
|
|
4484
|
+
const out = value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
4485
|
+
return out === "" || out === "-0" ? "0" : out;
|
|
4486
|
+
}
|
|
3795
4487
|
var XlsxWriter = class {
|
|
3796
4488
|
toBytes(workbook) {
|
|
3797
4489
|
const styles = new StylesRegistry();
|
|
@@ -3811,14 +4503,29 @@ var XlsxWriter = class {
|
|
|
3811
4503
|
add("xl/styles.xml", styles.toXml());
|
|
3812
4504
|
add("docProps/core.xml", this.coreXml(workbook));
|
|
3813
4505
|
add("docProps/app.xml", this.appXml());
|
|
4506
|
+
const firstChart = [];
|
|
4507
|
+
let next = 1;
|
|
4508
|
+
workbook.sheets.forEach((sheet, i) => {
|
|
4509
|
+
firstChart[i] = next;
|
|
4510
|
+
next += sheet.charts.length;
|
|
4511
|
+
});
|
|
3814
4512
|
workbook.sheets.forEach((sheet, i) => {
|
|
3815
4513
|
const n = i + 1;
|
|
3816
4514
|
add(`xl/worksheets/sheet${n}.xml`, sheetXmls[i]);
|
|
4515
|
+
if (sheet.hasComments() || sheet.charts.length > 0) {
|
|
4516
|
+
add(`xl/worksheets/_rels/sheet${n}.xml.rels`, this.sheetRelsXml(n, sheet.hasComments(), sheet.charts.length > 0));
|
|
4517
|
+
}
|
|
3817
4518
|
if (sheet.hasComments()) {
|
|
3818
|
-
add(`xl/worksheets/_rels/sheet${n}.xml.rels`, this.sheetRelsXml(n));
|
|
3819
4519
|
add(`xl/comments${n}.xml`, this.commentsXml(sheet));
|
|
3820
4520
|
add(`xl/drawings/vmlDrawing${n}.vml`, this.vmlDrawingXml(sheet));
|
|
3821
4521
|
}
|
|
4522
|
+
if (sheet.charts.length > 0) {
|
|
4523
|
+
add(`xl/drawings/drawing${n}.xml`, ChartXml.drawing(sheet.charts, firstChart[i]));
|
|
4524
|
+
add(`xl/drawings/_rels/drawing${n}.xml.rels`, ChartXml.drawingRels(sheet.charts, firstChart[i]));
|
|
4525
|
+
sheet.charts.forEach((chart, k) => {
|
|
4526
|
+
add(`xl/charts/chart${firstChart[i] + k}.xml`, ChartXml.chart(chart, workbook));
|
|
4527
|
+
});
|
|
4528
|
+
}
|
|
3822
4529
|
});
|
|
3823
4530
|
return zipSync(files);
|
|
3824
4531
|
}
|
|
@@ -3832,6 +4539,16 @@ var XlsxWriter = class {
|
|
|
3832
4539
|
const n = i + 1;
|
|
3833
4540
|
overrides += `<Override PartName="/xl/comments${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>`;
|
|
3834
4541
|
}
|
|
4542
|
+
let chartNum = 1;
|
|
4543
|
+
workbook.sheets.forEach((sheet, i) => {
|
|
4544
|
+
if (sheet.charts.length === 0) return;
|
|
4545
|
+
const n = i + 1;
|
|
4546
|
+
overrides += `<Override PartName="/xl/drawings/drawing${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/>`;
|
|
4547
|
+
for (let k = 0; k < sheet.charts.length; k++) {
|
|
4548
|
+
overrides += `<Override PartName="/xl/charts/chart${chartNum}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>`;
|
|
4549
|
+
chartNum++;
|
|
4550
|
+
}
|
|
4551
|
+
});
|
|
3835
4552
|
let defaults = '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/>';
|
|
3836
4553
|
if (sheetsWithComments.length > 0) {
|
|
3837
4554
|
defaults += '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>';
|
|
@@ -3860,8 +4577,16 @@ var XlsxWriter = class {
|
|
|
3860
4577
|
rels += `<Relationship Id="rId${workbook.sheets.length + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
|
|
3861
4578
|
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
3862
4579
|
}
|
|
3863
|
-
|
|
3864
|
-
|
|
4580
|
+
/** Comments keep rId1 (vml) and rId2 (comments); the chart drawing takes the next id. */
|
|
4581
|
+
sheetRelsXml(sheetNum, comments, charts) {
|
|
4582
|
+
let rels = "";
|
|
4583
|
+
if (comments) {
|
|
4584
|
+
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"/>`;
|
|
4585
|
+
}
|
|
4586
|
+
if (charts) {
|
|
4587
|
+
rels += `<Relationship Id="${drawingRelId(comments)}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" Target="../drawings/drawing${sheetNum}.xml"/>`;
|
|
4588
|
+
}
|
|
4589
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
3865
4590
|
}
|
|
3866
4591
|
sheetXml(sheet, styles) {
|
|
3867
4592
|
let sheetViewsXml = "";
|
|
@@ -3880,14 +4605,20 @@ var XlsxWriter = class {
|
|
|
3880
4605
|
}
|
|
3881
4606
|
colsXml += "</cols>";
|
|
3882
4607
|
}
|
|
4608
|
+
const rows = sheet.rows();
|
|
4609
|
+
for (const rowIndex of sheet.rowHeights.keys()) {
|
|
4610
|
+
if (!rows.has(rowIndex)) rows.set(rowIndex, /* @__PURE__ */ new Map());
|
|
4611
|
+
}
|
|
3883
4612
|
let rowsXml = "";
|
|
3884
|
-
for (const [rowIndex, row] of
|
|
4613
|
+
for (const [rowIndex, row] of [...rows.entries()].sort((a, b) => a[0] - b[0])) {
|
|
3885
4614
|
let cellsXml = "";
|
|
3886
4615
|
const sortedCols = [...row.keys()].sort();
|
|
3887
4616
|
for (const col of sortedCols) {
|
|
3888
4617
|
cellsXml += this.cellXml(row.get(col), styles);
|
|
3889
4618
|
}
|
|
3890
|
-
|
|
4619
|
+
const height = sheet.rowHeights.get(rowIndex);
|
|
4620
|
+
const ht = height !== void 0 ? ` ht="${decimal(height * 0.75)}" customHeight="1"` : "";
|
|
4621
|
+
rowsXml += cellsXml === "" ? `<row r="${rowIndex}"${ht}/>` : `<row r="${rowIndex}"${ht}>${cellsXml}</row>`;
|
|
3891
4622
|
}
|
|
3892
4623
|
let mergesXml = "";
|
|
3893
4624
|
if (sheet.mergedRegions.length > 0) {
|
|
@@ -3896,7 +4627,14 @@ var XlsxWriter = class {
|
|
|
3896
4627
|
mergesXml += "</mergeCells>";
|
|
3897
4628
|
}
|
|
3898
4629
|
const legacyDrawing = sheet.hasComments() ? '<legacyDrawing r:id="rId1"/>' : "";
|
|
3899
|
-
|
|
4630
|
+
const print = sheet.print;
|
|
4631
|
+
const sheetPrXml = print?.fitToWidth ? '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr>' : "";
|
|
4632
|
+
let pageSetupXml = "";
|
|
4633
|
+
if (print !== null && (print.orientation !== null || print.fitToWidth)) {
|
|
4634
|
+
pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + (print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
|
|
4635
|
+
}
|
|
4636
|
+
const headerFooterXml = print?.footer ? `<headerFooter><oddFooter>${this.escape(footerCodes(print.footer))}</oddFooter></headerFooter>` : "";
|
|
4637
|
+
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>";
|
|
3900
4638
|
}
|
|
3901
4639
|
cellXml(cell, styles) {
|
|
3902
4640
|
const ref = cell.address;
|
|
@@ -3936,8 +4674,8 @@ var XlsxWriter = class {
|
|
|
3936
4674
|
const author = entry.comment.author ?? "Author";
|
|
3937
4675
|
if (!authors.includes(author)) authors.push(author);
|
|
3938
4676
|
const authorIdx = authors.indexOf(author);
|
|
3939
|
-
const
|
|
3940
|
-
commentList += `<comment ref="${entry.address}" authorId="${authorIdx}"><text><r><t xml:space="preserve">${
|
|
4677
|
+
const text2 = this.escape(entry.comment.text);
|
|
4678
|
+
commentList += `<comment ref="${entry.address}" authorId="${authorIdx}"><text><r><t xml:space="preserve">${text2}</t></r></text></comment>`;
|
|
3941
4679
|
}
|
|
3942
4680
|
let authorsXml = "<authors>";
|
|
3943
4681
|
for (const a of authors) authorsXml += `<author>${this.escape(a)}</author>`;
|
|
@@ -4817,19 +5555,19 @@ function canonEntries(root) {
|
|
|
4817
5555
|
continue;
|
|
4818
5556
|
}
|
|
4819
5557
|
stack.pop();
|
|
4820
|
-
const
|
|
5558
|
+
const text2 = top.list ? "[" + top.parts.join(",") + "]" : "{" + top.parts.join(",") + "}";
|
|
4821
5559
|
const parent = stack[stack.length - 1];
|
|
4822
5560
|
if (parent === void 0) {
|
|
4823
|
-
return
|
|
5561
|
+
return text2;
|
|
4824
5562
|
}
|
|
4825
|
-
parent.parts.push(member(parent, top.key,
|
|
5563
|
+
parent.parts.push(member(parent, top.key, text2));
|
|
4826
5564
|
}
|
|
4827
5565
|
}
|
|
4828
5566
|
function entriesFor(value) {
|
|
4829
5567
|
return Array.isArray(value) ? Array.from(value, (item, i) => [String(i), item]) : entriesOf(value);
|
|
4830
5568
|
}
|
|
4831
|
-
function member(frame, key,
|
|
4832
|
-
return frame.list ?
|
|
5569
|
+
function member(frame, key, text2) {
|
|
5570
|
+
return frame.list ? text2 : quote(key) + ":" + text2;
|
|
4833
5571
|
}
|
|
4834
5572
|
function scalar(value) {
|
|
4835
5573
|
switch (typeof value) {
|
|
@@ -4847,11 +5585,11 @@ function scalar(value) {
|
|
|
4847
5585
|
return JSON.stringify(value) ?? "null";
|
|
4848
5586
|
}
|
|
4849
5587
|
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
4850
|
-
function quote(
|
|
4851
|
-
if (LONE_SURROGATE.test(
|
|
5588
|
+
function quote(text2) {
|
|
5589
|
+
if (LONE_SURROGATE.test(text2)) {
|
|
4852
5590
|
throw unencodable("Single unpaired UTF-16 surrogate");
|
|
4853
5591
|
}
|
|
4854
|
-
return JSON.stringify(
|
|
5592
|
+
return JSON.stringify(text2);
|
|
4855
5593
|
}
|
|
4856
5594
|
function unencodable(reason) {
|
|
4857
5595
|
return new TypeError(`[holy-sheet] cannot compare a value JSON cannot hold: ${reason}`);
|
|
@@ -5013,6 +5751,68 @@ var holy_sheet_schema_default = {
|
|
|
5013
5751
|
type: "integer",
|
|
5014
5752
|
minimum: 0,
|
|
5015
5753
|
description: "How many columns stay visible while the sheet scrolls sideways. Set to 1 when the first column is the label for the row."
|
|
5754
|
+
},
|
|
5755
|
+
rowHeights: {
|
|
5756
|
+
type: "object",
|
|
5757
|
+
description: 'Row heights in pixels, keyed by 1-based row NUMBER as Excel shows it: {"1": 32, "4": 28}. A default row is 20; give a title row or a header row room (28-36) instead of packing text against its fill.',
|
|
5758
|
+
propertyNames: { pattern: "^[1-9][0-9]*$" },
|
|
5759
|
+
additionalProperties: { type: "number", minimum: 0 }
|
|
5760
|
+
},
|
|
5761
|
+
charts: {
|
|
5762
|
+
type: "array",
|
|
5763
|
+
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.",
|
|
5764
|
+
items: {
|
|
5765
|
+
type: "object",
|
|
5766
|
+
additionalProperties: false,
|
|
5767
|
+
required: ["type", "series"],
|
|
5768
|
+
properties: {
|
|
5769
|
+
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)." },
|
|
5770
|
+
title: { type: "string" },
|
|
5771
|
+
categories: { type: "string", description: `The labels along the axis (or the pie's slices), as a range: "A5:A7". Another sheet: "'Data'!A5:A7".` },
|
|
5772
|
+
series: {
|
|
5773
|
+
type: "array",
|
|
5774
|
+
minItems: 1,
|
|
5775
|
+
items: {
|
|
5776
|
+
type: "object",
|
|
5777
|
+
additionalProperties: false,
|
|
5778
|
+
required: ["values"],
|
|
5779
|
+
properties: {
|
|
5780
|
+
name: { type: "string", description: "Shown in the legend." },
|
|
5781
|
+
values: { type: "string", description: 'The numbers, as a range the same length as categories: "B5:B7".' },
|
|
5782
|
+
color: { type: "string", description: "#RRGGBB. Ignored for a pie, whose slices take a palette." }
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5785
|
+
},
|
|
5786
|
+
position: {
|
|
5787
|
+
type: "object",
|
|
5788
|
+
additionalProperties: false,
|
|
5789
|
+
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.',
|
|
5790
|
+
properties: { from: { type: "string" }, to: { type: "string" } }
|
|
5791
|
+
},
|
|
5792
|
+
dataLabels: { type: "boolean", description: "Print each value on the chart. Usually right for a board chart." },
|
|
5793
|
+
legend: { type: "boolean", description: "Defaults to on for several series or a pie, off for one series." },
|
|
5794
|
+
format: { type: "string", description: 'Excel number format for the value axis and labels: "$#,##0", "0.0%", "$#,##0,K".' }
|
|
5795
|
+
}
|
|
5796
|
+
}
|
|
5797
|
+
},
|
|
5798
|
+
print: {
|
|
5799
|
+
type: "object",
|
|
5800
|
+
additionalProperties: false,
|
|
5801
|
+
description: "How the sheet prints. A sheet wider than a page splits its columns across pages unless fitToWidth is set.",
|
|
5802
|
+
properties: {
|
|
5803
|
+
orientation: { type: "string", enum: ["portrait", "landscape"] },
|
|
5804
|
+
fitToWidth: { type: "boolean", description: "Scale the sheet to one page wide (as many pages tall as it needs)." },
|
|
5805
|
+
footer: {
|
|
5806
|
+
type: "object",
|
|
5807
|
+
additionalProperties: false,
|
|
5808
|
+
description: 'A printed footer: up to three slots. {page} and {pages} print the page number and count: {"left": "Q3 Revenue Review", "right": "Page {page} of {pages}"}.',
|
|
5809
|
+
properties: {
|
|
5810
|
+
left: { type: "string" },
|
|
5811
|
+
center: { type: "string" },
|
|
5812
|
+
right: { type: "string" }
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5815
|
+
}
|
|
5016
5816
|
}
|
|
5017
5817
|
}
|
|
5018
5818
|
},
|
|
@@ -5221,7 +6021,7 @@ var holy_sheet_schema_default = {
|
|
|
5221
6021
|
};
|
|
5222
6022
|
|
|
5223
6023
|
// src/agent.ts
|
|
5224
|
-
var VERSION = "2.
|
|
6024
|
+
var VERSION = "2.8.0";
|
|
5225
6025
|
function toU8(input) {
|
|
5226
6026
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
5227
6027
|
}
|