@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.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,14 +415,20 @@ 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) {
|
|
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;
|
|
423
423
|
this.columnWidths = columnWidths;
|
|
424
424
|
this.frozenRows = frozenRows;
|
|
425
425
|
this.frozenCols = frozenCols;
|
|
426
|
+
this.rowHeights = rowHeights;
|
|
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);
|
|
426
432
|
}
|
|
427
433
|
/** rowNumber → (colLetter → Cell), sorted by ascending row number. */
|
|
428
434
|
rows() {
|
|
@@ -453,6 +459,59 @@ var Sheet = class {
|
|
|
453
459
|
}
|
|
454
460
|
};
|
|
455
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
|
+
|
|
456
515
|
// src/workbook/workbook.ts
|
|
457
516
|
var Workbook = class {
|
|
458
517
|
constructor(sheets, meta = {}) {
|
|
@@ -663,20 +722,23 @@ var Normalizer = class {
|
|
|
663
722
|
this.normalizeMerges(sheet.mergedRegions ?? []),
|
|
664
723
|
this.normalizeWidths(sheet),
|
|
665
724
|
toInt(sheet.frozenRows ?? 0),
|
|
666
|
-
toInt(sheet.frozenCols ?? 0)
|
|
725
|
+
toInt(sheet.frozenCols ?? 0),
|
|
726
|
+
normalizeRowHeights(sheet.rowHeights),
|
|
727
|
+
normalizePrint(sheet.print),
|
|
728
|
+
normalizeCharts(sheet.charts, name, cells)
|
|
667
729
|
);
|
|
668
730
|
}
|
|
669
731
|
normalizeCellMap(map, sheetName2) {
|
|
670
732
|
const cells = /* @__PURE__ */ new Map();
|
|
671
733
|
for (const [address, cellData] of Object.entries(map)) {
|
|
672
734
|
if (!isCellSpec(cellData)) {
|
|
673
|
-
const [bare,
|
|
735
|
+
const [bare, formula2] = promoteFormula(cellData);
|
|
674
736
|
cells.set(
|
|
675
737
|
address,
|
|
676
738
|
new Cell(
|
|
677
739
|
address,
|
|
678
|
-
|
|
679
|
-
|
|
740
|
+
formula2 !== null ? null : this.coerceValue(bare, null, address, sheetName2),
|
|
741
|
+
formula2
|
|
680
742
|
)
|
|
681
743
|
);
|
|
682
744
|
continue;
|
|
@@ -792,11 +854,11 @@ var Normalizer = class {
|
|
|
792
854
|
value.computedValue ?? null
|
|
793
855
|
);
|
|
794
856
|
}
|
|
795
|
-
const [bare,
|
|
857
|
+
const [bare, formula2] = promoteFormula(value);
|
|
796
858
|
return new Cell(
|
|
797
859
|
address,
|
|
798
|
-
|
|
799
|
-
|
|
860
|
+
formula2 !== null ? null : this.coerceValue(bare, columnFormat, address, sheetName2),
|
|
861
|
+
formula2,
|
|
800
862
|
columnFormat
|
|
801
863
|
);
|
|
802
864
|
}
|
|
@@ -854,6 +916,81 @@ function toInt(v) {
|
|
|
854
916
|
const n = Math.trunc(Number(v));
|
|
855
917
|
return Number.isFinite(n) ? n : 0;
|
|
856
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
|
+
}
|
|
966
|
+
function phpNumeric(v) {
|
|
967
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
968
|
+
return isNumericString(v) ? numericStringToNumber(v) : null;
|
|
969
|
+
}
|
|
970
|
+
function normalizeRowHeights(map) {
|
|
971
|
+
const out = /* @__PURE__ */ new Map();
|
|
972
|
+
if (!isPlainObject(map) && !Array.isArray(map)) return out;
|
|
973
|
+
for (const [row, px] of Object.entries(map)) {
|
|
974
|
+
const n = phpNumeric(px);
|
|
975
|
+
if (/^[1-9]\d{0,6}$/.test(row) && n !== null && n >= 0) out.set(parseInt(row, 10), n);
|
|
976
|
+
}
|
|
977
|
+
return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
|
|
978
|
+
}
|
|
979
|
+
function normalizePrint(print) {
|
|
980
|
+
if (!isPlainObject(print) && !Array.isArray(print)) return null;
|
|
981
|
+
const p = print;
|
|
982
|
+
const orientation = p.orientation === "portrait" || p.orientation === "landscape" ? p.orientation : null;
|
|
983
|
+
const fitToWidth = p.fitToWidth === true;
|
|
984
|
+
let footer = null;
|
|
985
|
+
if (isPlainObject(p.footer) || Array.isArray(p.footer)) {
|
|
986
|
+
const f = p.footer;
|
|
987
|
+
const slot = (k) => typeof f[k] === "string" ? f[k] : "";
|
|
988
|
+
footer = { left: slot("left"), center: slot("center"), right: slot("right") };
|
|
989
|
+
if (footer.left === "" && footer.center === "" && footer.right === "") footer = null;
|
|
990
|
+
}
|
|
991
|
+
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
992
|
+
return { orientation, fitToWidth, footer };
|
|
993
|
+
}
|
|
857
994
|
|
|
858
995
|
// src/schema/formula-linter.ts
|
|
859
996
|
var ERR_VALUE = "#VALUE!";
|
|
@@ -916,9 +1053,9 @@ var FormulaLinter = class {
|
|
|
916
1053
|
isError(v) {
|
|
917
1054
|
return typeof v === "string" && v.length > 0 && v[0] === "#";
|
|
918
1055
|
}
|
|
919
|
-
evaluate(
|
|
1056
|
+
evaluate(formula2, defaultSheet, stack) {
|
|
920
1057
|
try {
|
|
921
|
-
const c = { tokens: this.tokenize(
|
|
1058
|
+
const c = { tokens: this.tokenize(formula2), pos: 0 };
|
|
922
1059
|
const result = this.parseExpr(c, defaultSheet, stack);
|
|
923
1060
|
if (c.pos < c.tokens.length) return ERR_NAME;
|
|
924
1061
|
return result;
|
|
@@ -1179,13 +1316,13 @@ var FormulaLinter = class {
|
|
|
1179
1316
|
const row1 = Math.min(a[1], b[1]);
|
|
1180
1317
|
const col2 = Math.max(a[0], b[0]);
|
|
1181
1318
|
const row2 = Math.max(a[1], b[1]);
|
|
1182
|
-
const
|
|
1319
|
+
const values2 = [];
|
|
1183
1320
|
for (let r = row1; r <= row2; r++) {
|
|
1184
1321
|
for (let col = col1; col <= col2; col++) {
|
|
1185
|
-
|
|
1322
|
+
values2.push(this.resolveCell(sheet, CellAddress.letter(col) + r, stack));
|
|
1186
1323
|
}
|
|
1187
1324
|
}
|
|
1188
|
-
return
|
|
1325
|
+
return values2;
|
|
1189
1326
|
}
|
|
1190
1327
|
expectOp(c, op) {
|
|
1191
1328
|
const t = c.tokens[c.pos];
|
|
@@ -1317,12 +1454,12 @@ var FormulaLinter = class {
|
|
|
1317
1454
|
return truthy ? args[1] : args[2] ?? false;
|
|
1318
1455
|
}
|
|
1319
1456
|
// ---- Hints ----
|
|
1320
|
-
hint(error2,
|
|
1457
|
+
hint(error2, formula2, sheet) {
|
|
1321
1458
|
switch (error2) {
|
|
1322
1459
|
case ERR_VALUE:
|
|
1323
|
-
return this.hintValue(
|
|
1460
|
+
return this.hintValue(formula2, sheet);
|
|
1324
1461
|
case ERR_REF:
|
|
1325
|
-
return this.hintRef(
|
|
1462
|
+
return this.hintRef(formula2);
|
|
1326
1463
|
case ERR_NAME:
|
|
1327
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.";
|
|
1328
1465
|
case ERR_DIV0:
|
|
@@ -1333,8 +1470,8 @@ var FormulaLinter = class {
|
|
|
1333
1470
|
return "Formula evaluation failed.";
|
|
1334
1471
|
}
|
|
1335
1472
|
}
|
|
1336
|
-
hintRef(
|
|
1337
|
-
for (const m of
|
|
1473
|
+
hintRef(formula2) {
|
|
1474
|
+
for (const m of formula2.matchAll(new RegExp(SHEET_QUALIFIER, "g"))) {
|
|
1338
1475
|
const name = this.cleanSheetName(m[1]);
|
|
1339
1476
|
if (!this.sheetNames.has(name.toLowerCase())) {
|
|
1340
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.`;
|
|
@@ -1342,10 +1479,10 @@ var FormulaLinter = class {
|
|
|
1342
1479
|
}
|
|
1343
1480
|
return "A cell reference points to a cell that doesn't exist in the workbook. Check column letters and row numbers.";
|
|
1344
1481
|
}
|
|
1345
|
-
hintValue(
|
|
1482
|
+
hintValue(formula2, sheet) {
|
|
1346
1483
|
const re = new RegExp(`(?:${SHEET_QUALIFIER})?\\$?([A-Z]+)\\$?(\\d+)`, "gi");
|
|
1347
1484
|
const offenders = [];
|
|
1348
|
-
for (const m of
|
|
1485
|
+
for (const m of formula2.matchAll(re)) {
|
|
1349
1486
|
const sheetName2 = m[1] ? this.sheetNames.get(this.cleanSheetName(m[1]).toLowerCase()) ?? this.cleanSheetName(m[1]) : sheet;
|
|
1350
1487
|
const a1 = m[2].toUpperCase() + m[3];
|
|
1351
1488
|
const key = sheetName2 + "!" + a1;
|
|
@@ -1468,11 +1605,11 @@ var Repairer = class {
|
|
|
1468
1605
|
sheet.columns.forEach((col, colIdx) => {
|
|
1469
1606
|
if (!isPlainObject(col)) return;
|
|
1470
1607
|
if (col.type !== void 0 && col.type !== "auto") return;
|
|
1471
|
-
const
|
|
1608
|
+
const values2 = [];
|
|
1472
1609
|
for (const row of sheet.rows) {
|
|
1473
|
-
if (Array.isArray(row) && colIdx < row.length)
|
|
1610
|
+
if (Array.isArray(row) && colIdx < row.length) values2.push(row[colIdx]);
|
|
1474
1611
|
}
|
|
1475
|
-
const nonNull =
|
|
1612
|
+
const nonNull = values2.filter((v) => v !== null && v !== void 0);
|
|
1476
1613
|
if (nonNull.length === 0) return;
|
|
1477
1614
|
const allDateLike = nonNull.every((v) => typeof v === "string" && ISO_DATE2.test(v));
|
|
1478
1615
|
if (allDateLike) {
|
|
@@ -1701,6 +1838,98 @@ var Validator = class {
|
|
|
1701
1838
|
}
|
|
1702
1839
|
}
|
|
1703
1840
|
}
|
|
1841
|
+
if (sheet.rowHeights != null) {
|
|
1842
|
+
if (!isPlainObject(sheet.rowHeights) && !Array.isArray(sheet.rowHeights)) {
|
|
1843
|
+
errors.push(
|
|
1844
|
+
error(
|
|
1845
|
+
`${path}.rowHeights`,
|
|
1846
|
+
"object keyed by 1-based row number",
|
|
1847
|
+
typeOf(sheet.rowHeights),
|
|
1848
|
+
sheet.rowHeights,
|
|
1849
|
+
'Row heights map a row NUMBER to pixels: {"1": 32, "4": 28}.'
|
|
1850
|
+
)
|
|
1851
|
+
);
|
|
1852
|
+
} else {
|
|
1853
|
+
for (const [row, px] of Object.entries(sheet.rowHeights)) {
|
|
1854
|
+
const n = typeof px === "number" ? px : isNumericString(px) ? numericStringToNumber(px) : null;
|
|
1855
|
+
if (!/^[1-9]\d{0,6}$/.test(row)) {
|
|
1856
|
+
errors.push(error(
|
|
1857
|
+
`${path}.rowHeights.${row}`,
|
|
1858
|
+
"a 1-based row number",
|
|
1859
|
+
typeOf(row),
|
|
1860
|
+
row,
|
|
1861
|
+
'Keys are row numbers as Excel shows them: "1" is the first row.'
|
|
1862
|
+
));
|
|
1863
|
+
} else if (n === null || !Number.isFinite(n) || n < 0) {
|
|
1864
|
+
errors.push(error(
|
|
1865
|
+
`${path}.rowHeights.${row}`,
|
|
1866
|
+
"a non-negative number of pixels",
|
|
1867
|
+
typeOf(px),
|
|
1868
|
+
px,
|
|
1869
|
+
"A height is a number of pixels, like 28 (20 is a default row)."
|
|
1870
|
+
));
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (sheet.charts != null) {
|
|
1876
|
+
errors.push(...validateCharts(sheet.charts, path, typeof sheet.name === "string" ? sheet.name : ""));
|
|
1877
|
+
}
|
|
1878
|
+
if (sheet.print != null) {
|
|
1879
|
+
const print = sheet.print;
|
|
1880
|
+
if (!isPlainObject(print) && !Array.isArray(print)) {
|
|
1881
|
+
errors.push(error(
|
|
1882
|
+
`${path}.print`,
|
|
1883
|
+
"object {orientation?, fitToWidth?, footer?}",
|
|
1884
|
+
typeOf(print),
|
|
1885
|
+
print,
|
|
1886
|
+
'Print setup: {"orientation": "landscape", "fitToWidth": true, "footer": {"right": "Page {page} of {pages}"}}.'
|
|
1887
|
+
));
|
|
1888
|
+
} else {
|
|
1889
|
+
if (print.orientation != null && print.orientation !== "portrait" && print.orientation !== "landscape") {
|
|
1890
|
+
errors.push(error(
|
|
1891
|
+
`${path}.print.orientation`,
|
|
1892
|
+
"one of: portrait, landscape",
|
|
1893
|
+
typeOf(print.orientation),
|
|
1894
|
+
print.orientation,
|
|
1895
|
+
"Use landscape for a sheet wider than it is tall."
|
|
1896
|
+
));
|
|
1897
|
+
}
|
|
1898
|
+
if (print.fitToWidth != null && typeof print.fitToWidth !== "boolean") {
|
|
1899
|
+
errors.push(error(
|
|
1900
|
+
`${path}.print.fitToWidth`,
|
|
1901
|
+
"boolean",
|
|
1902
|
+
typeOf(print.fitToWidth),
|
|
1903
|
+
print.fitToWidth,
|
|
1904
|
+
"true scales the sheet to one page wide when printed."
|
|
1905
|
+
));
|
|
1906
|
+
}
|
|
1907
|
+
if (print.footer != null) {
|
|
1908
|
+
if (!isPlainObject(print.footer) && !Array.isArray(print.footer)) {
|
|
1909
|
+
errors.push(error(
|
|
1910
|
+
`${path}.print.footer`,
|
|
1911
|
+
"object {left?, center?, right?}",
|
|
1912
|
+
typeOf(print.footer),
|
|
1913
|
+
print.footer,
|
|
1914
|
+
"A footer has up to three slots of text."
|
|
1915
|
+
));
|
|
1916
|
+
} else {
|
|
1917
|
+
const footer = print.footer;
|
|
1918
|
+
for (const slot of ["left", "center", "right"]) {
|
|
1919
|
+
if (footer[slot] != null && typeof footer[slot] !== "string") {
|
|
1920
|
+
errors.push(error(
|
|
1921
|
+
`${path}.print.footer.${slot}`,
|
|
1922
|
+
"string",
|
|
1923
|
+
typeOf(footer[slot]),
|
|
1924
|
+
footer[slot],
|
|
1925
|
+
"Footer text; {page} and {pages} print the page number and count."
|
|
1926
|
+
));
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1704
1933
|
return errors;
|
|
1705
1934
|
}
|
|
1706
1935
|
validateColumn(col, path) {
|
|
@@ -1746,6 +1975,113 @@ var Validator = class {
|
|
|
1746
1975
|
return errors;
|
|
1747
1976
|
}
|
|
1748
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
|
+
}
|
|
1749
2085
|
function error(path, expected, got, value, hint) {
|
|
1750
2086
|
return { path, expected, got, value, hint };
|
|
1751
2087
|
}
|
|
@@ -2262,15 +2598,15 @@ var FormatSniffer = {
|
|
|
2262
2598
|
};
|
|
2263
2599
|
|
|
2264
2600
|
// src/reader/ods/ods-formula.ts
|
|
2265
|
-
function odsFormulaToA1(
|
|
2601
|
+
function odsFormulaToA1(formula2) {
|
|
2266
2602
|
let grammar = "of";
|
|
2267
|
-
const m = /^(of|oooc|msoxl):/i.exec(
|
|
2603
|
+
const m = /^(of|oooc|msoxl):/i.exec(formula2);
|
|
2268
2604
|
if (m) {
|
|
2269
2605
|
grammar = m[1].toLowerCase();
|
|
2270
|
-
|
|
2606
|
+
formula2 = formula2.slice(m[0].length);
|
|
2271
2607
|
}
|
|
2272
|
-
if (
|
|
2273
|
-
return grammar === "msoxl" ?
|
|
2608
|
+
if (formula2.startsWith("=")) formula2 = formula2.slice(1);
|
|
2609
|
+
return grammar === "msoxl" ? formula2 : translate(formula2);
|
|
2274
2610
|
}
|
|
2275
2611
|
function translate(s) {
|
|
2276
2612
|
let out = "";
|
|
@@ -2619,8 +2955,8 @@ var OdsStyles = class {
|
|
|
2619
2955
|
switch (el2.name) {
|
|
2620
2956
|
case "number-style": {
|
|
2621
2957
|
if (has2("scientific-number") || has2("fraction")) return null;
|
|
2622
|
-
for (const
|
|
2623
|
-
const iso = currencyCode(ownText(
|
|
2958
|
+
for (const text2 of parts.filter((p) => p.name === "text")) {
|
|
2959
|
+
const iso = currencyCode(ownText(text2).replace(/[-()"\u00A0]/g, ""));
|
|
2624
2960
|
if (iso !== null) return { displayFormat: "currency", decimals: decimals ?? 0, currency: iso };
|
|
2625
2961
|
}
|
|
2626
2962
|
if (!number) return null;
|
|
@@ -2790,6 +3126,45 @@ function sheetToSchema(sheet) {
|
|
|
2790
3126
|
}
|
|
2791
3127
|
if (sheet.frozenRows > 0) out["frozenRows"] = sheet.frozenRows;
|
|
2792
3128
|
if (sheet.frozenCols > 0) out["frozenCols"] = sheet.frozenCols;
|
|
3129
|
+
if (sheet.rowHeights.size > 0) {
|
|
3130
|
+
const heights = {};
|
|
3131
|
+
for (const [k, v] of sheet.rowHeights) heights[k] = v;
|
|
3132
|
+
out["rowHeights"] = heights;
|
|
3133
|
+
}
|
|
3134
|
+
if (sheet.print !== null) {
|
|
3135
|
+
const print = {};
|
|
3136
|
+
if (sheet.print.orientation !== null) print["orientation"] = sheet.print.orientation;
|
|
3137
|
+
if (sheet.print.fitToWidth) print["fitToWidth"] = true;
|
|
3138
|
+
if (sheet.print.footer !== null) {
|
|
3139
|
+
const footer = {};
|
|
3140
|
+
for (const k of ["left", "center", "right"]) if (sheet.print.footer[k] !== "") footer[k] = sheet.print.footer[k];
|
|
3141
|
+
print["footer"] = footer;
|
|
3142
|
+
}
|
|
3143
|
+
out["print"] = print;
|
|
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;
|
|
2793
3168
|
return out;
|
|
2794
3169
|
}
|
|
2795
3170
|
function cellToSchema(cell) {
|
|
@@ -2897,10 +3272,10 @@ function parseXml(src, options = {}) {
|
|
|
2897
3272
|
if (namespaces) root.content = [];
|
|
2898
3273
|
const stack = [root];
|
|
2899
3274
|
const scopes = [{ xml: XML_NS }];
|
|
2900
|
-
const appendText = (
|
|
3275
|
+
const appendText = (text2) => {
|
|
2901
3276
|
const top = stack[stack.length - 1];
|
|
2902
|
-
top.text +=
|
|
2903
|
-
if (namespaces &&
|
|
3277
|
+
top.text += text2;
|
|
3278
|
+
if (namespaces && text2 !== "") top.content.push(text2);
|
|
2904
3279
|
};
|
|
2905
3280
|
while (i < n) {
|
|
2906
3281
|
if (src[i] === "<") {
|
|
@@ -3121,7 +3496,7 @@ function hasContent(cell) {
|
|
|
3121
3496
|
}
|
|
3122
3497
|
function readCell(cell) {
|
|
3123
3498
|
const type = attr(cell, NS.OFFICE, "value-type") ?? null;
|
|
3124
|
-
const
|
|
3499
|
+
const text2 = paragraphs(cell);
|
|
3125
3500
|
let value = null;
|
|
3126
3501
|
let seconds = null;
|
|
3127
3502
|
let dateHasTime = false;
|
|
@@ -3150,20 +3525,20 @@ function readCell(cell) {
|
|
|
3150
3525
|
case "string": {
|
|
3151
3526
|
const error2 = attr(cell, NS.CALCEXT, "value-type") === "error";
|
|
3152
3527
|
const stored = attr(cell, NS.OFFICE, "string-value");
|
|
3153
|
-
value = stored !== void 0 && !error2 ? stored :
|
|
3528
|
+
value = stored !== void 0 && !error2 ? stored : text2 ?? "";
|
|
3154
3529
|
break;
|
|
3155
3530
|
}
|
|
3156
3531
|
default:
|
|
3157
|
-
value =
|
|
3532
|
+
value = text2 !== null && text2 !== "" ? text2 : null;
|
|
3158
3533
|
}
|
|
3159
|
-
let
|
|
3534
|
+
let formula2 = null;
|
|
3160
3535
|
let cached = null;
|
|
3161
3536
|
const formulaAttr = attr(cell, NS.TABLE, "formula");
|
|
3162
3537
|
if (formulaAttr !== void 0) {
|
|
3163
3538
|
const translated = odsFormulaToA1(formulaAttr);
|
|
3164
3539
|
const literalBoolean = type === "boolean" && /^[ \t\n\r\f\v]*(TRUE|FALSE)\(\)[ \t\n\r\f\v]*$/i.test(translated);
|
|
3165
3540
|
if (!literalBoolean) {
|
|
3166
|
-
|
|
3541
|
+
formula2 = translated;
|
|
3167
3542
|
cached = seconds !== null ? serial(seconds) : value;
|
|
3168
3543
|
value = null;
|
|
3169
3544
|
seconds = null;
|
|
@@ -3179,7 +3554,7 @@ function readCell(cell) {
|
|
|
3179
3554
|
const currency = attr(cell, NS.OFFICE, "currency");
|
|
3180
3555
|
return {
|
|
3181
3556
|
value,
|
|
3182
|
-
formula,
|
|
3557
|
+
formula: formula2,
|
|
3183
3558
|
cached,
|
|
3184
3559
|
type,
|
|
3185
3560
|
currency: currency !== void 0 ? phpTrim(currency) : null,
|
|
@@ -3248,6 +3623,127 @@ function readMeta(xml) {
|
|
|
3248
3623
|
return out;
|
|
3249
3624
|
}
|
|
3250
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
|
+
|
|
3251
3747
|
// src/reader/comments-parser.ts
|
|
3252
3748
|
var CommentsParser = {
|
|
3253
3749
|
parse(commentsXml) {
|
|
@@ -3412,9 +3908,62 @@ var WorksheetParser = {
|
|
|
3412
3908
|
const merges = parseMerges(xml);
|
|
3413
3909
|
const columnWidths = parseColumnWidths(xml);
|
|
3414
3910
|
const [frozenRows, frozenCols] = parseFrozen(xml);
|
|
3415
|
-
return new Sheet(name, cells, merges, columnWidths, frozenRows, frozenCols);
|
|
3911
|
+
return new Sheet(name, cells, merges, columnWidths, frozenRows, frozenCols, parseRowHeights(xml), parsePrint(xml));
|
|
3416
3912
|
}
|
|
3417
3913
|
};
|
|
3914
|
+
function parseRowHeights(xml) {
|
|
3915
|
+
const out = /* @__PURE__ */ new Map();
|
|
3916
|
+
const sheetData = el(xml, "sheetData");
|
|
3917
|
+
if (!sheetData) return out;
|
|
3918
|
+
for (const row of els(sheetData, "row")) {
|
|
3919
|
+
const r = parseInt(at(row, "r") ?? "0", 10);
|
|
3920
|
+
const custom = at(row, "customHeight");
|
|
3921
|
+
const ht = at(row, "ht");
|
|
3922
|
+
if (r > 0 && ht !== void 0 && (custom === "1" || custom === "true")) {
|
|
3923
|
+
out.set(r, Math.round(parseFloat(ht) / 0.75 * 100) / 100);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
return out;
|
|
3927
|
+
}
|
|
3928
|
+
function parsePrint(xml) {
|
|
3929
|
+
const fitToPage = ["1", "true"].includes(at(el(el(xml, "sheetPr"), "pageSetUpPr"), "fitToPage") ?? "");
|
|
3930
|
+
const setup = el(xml, "pageSetup");
|
|
3931
|
+
const o = at(setup, "orientation");
|
|
3932
|
+
const orientation = o === "portrait" || o === "landscape" ? o : null;
|
|
3933
|
+
const fitToWidth = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && ["0", ""].includes(at(setup, "fitToHeight") ?? "");
|
|
3934
|
+
const oddFooter = el(el(xml, "headerFooter"), "oddFooter");
|
|
3935
|
+
const footer = oddFooter !== void 0 ? parseFooterCodes(oddFooter.text) : null;
|
|
3936
|
+
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
3937
|
+
return { orientation, fitToWidth, footer };
|
|
3938
|
+
}
|
|
3939
|
+
function parseFooterCodes(codes) {
|
|
3940
|
+
const slots = { left: "", center: "", right: "" };
|
|
3941
|
+
let current = "center";
|
|
3942
|
+
const len = codes.length;
|
|
3943
|
+
for (let i = 0; i < len; i++) {
|
|
3944
|
+
const ch = codes[i];
|
|
3945
|
+
if (ch !== "&" || i + 1 >= len) {
|
|
3946
|
+
slots[current] += ch;
|
|
3947
|
+
continue;
|
|
3948
|
+
}
|
|
3949
|
+
const next = codes[++i];
|
|
3950
|
+
if (next === "&") slots[current] += "&";
|
|
3951
|
+
else if (next === "L") current = "left";
|
|
3952
|
+
else if (next === "C") current = "center";
|
|
3953
|
+
else if (next === "R") current = "right";
|
|
3954
|
+
else if (next === "P") slots[current] += "{page}";
|
|
3955
|
+
else if (next === "N") slots[current] += "{pages}";
|
|
3956
|
+
else if (next === '"') {
|
|
3957
|
+
const close = codes.indexOf('"', i + 1);
|
|
3958
|
+
i = close === -1 ? len : close;
|
|
3959
|
+
} else if (/[0-9]/.test(next)) {
|
|
3960
|
+
while (i + 1 < len && /[0-9]/.test(codes[i + 1])) i++;
|
|
3961
|
+
} else if (next === "K") {
|
|
3962
|
+
i = Math.min(len - 1, i + 6);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
return slots.left === "" && slots.center === "" && slots.right === "" ? null : slots;
|
|
3966
|
+
}
|
|
3418
3967
|
function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
3419
3968
|
const cells = /* @__PURE__ */ new Map();
|
|
3420
3969
|
const sheetData = el(xml, "sheetData");
|
|
@@ -3426,13 +3975,13 @@ function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
|
3426
3975
|
const type = c.attrs["t"] ?? "n";
|
|
3427
3976
|
const styleIdx = c.attrs["s"] !== void 0 ? parseInt(c.attrs["s"], 10) : 0;
|
|
3428
3977
|
const format = stylesIndex[styleIdx] ?? null;
|
|
3429
|
-
let
|
|
3978
|
+
let formula2 = null;
|
|
3430
3979
|
let cachedValue = null;
|
|
3431
3980
|
let value = null;
|
|
3432
3981
|
const f = el(c, "f");
|
|
3433
3982
|
const v = el(c, "v");
|
|
3434
3983
|
if (f) {
|
|
3435
|
-
|
|
3984
|
+
formula2 = f.text;
|
|
3436
3985
|
cachedValue = v ? coerceValue(v.text, type) : null;
|
|
3437
3986
|
} else if (type === "inlineStr" && el(c, "is") && el(el(c, "is"), "t")) {
|
|
3438
3987
|
value = el(el(c, "is"), "t").text;
|
|
@@ -3443,7 +3992,7 @@ function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
|
3443
3992
|
value = coerceValue(v.text, type);
|
|
3444
3993
|
}
|
|
3445
3994
|
const comment = comments[address] ?? null;
|
|
3446
|
-
cells.set(address, new Cell(address, value,
|
|
3995
|
+
cells.set(address, new Cell(address, value, formula2, format, comment, cachedValue));
|
|
3447
3996
|
}
|
|
3448
3997
|
}
|
|
3449
3998
|
return cells;
|
|
@@ -3534,7 +4083,26 @@ var XlsxReader = class {
|
|
|
3534
4083
|
if (commentsXml) comments = { ...comments, ...CommentsParser.parse(commentsXml) };
|
|
3535
4084
|
}
|
|
3536
4085
|
}
|
|
3537
|
-
|
|
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);
|
|
3538
4106
|
i++;
|
|
3539
4107
|
}
|
|
3540
4108
|
const meta = this.parseDocProps(getText("docProps/core.xml"));
|
|
@@ -3788,8 +4356,132 @@ function hexToArgb(hex2) {
|
|
|
3788
4356
|
return "FF000000";
|
|
3789
4357
|
}
|
|
3790
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
|
+
|
|
3791
4467
|
// src/writer/xlsx-writer.ts
|
|
3792
4468
|
var encoder2 = new TextEncoder();
|
|
4469
|
+
function footerCodes(footer) {
|
|
4470
|
+
let codes = "";
|
|
4471
|
+
for (const [slot, code] of [["left", "&L"], ["center", "&C"], ["right", "&R"]]) {
|
|
4472
|
+
if (footer[slot] !== "") {
|
|
4473
|
+
codes += code + footer[slot].replace(/&/g, "&&").replace(/\{pages\}/g, "&N").replace(/\{page\}/g, "&P");
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
return codes;
|
|
4477
|
+
}
|
|
4478
|
+
function drawingRelId(comments) {
|
|
4479
|
+
return comments ? "rId3" : "rId1";
|
|
4480
|
+
}
|
|
4481
|
+
function decimal(value) {
|
|
4482
|
+
const out = value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
4483
|
+
return out === "" || out === "-0" ? "0" : out;
|
|
4484
|
+
}
|
|
3793
4485
|
var XlsxWriter = class {
|
|
3794
4486
|
toBytes(workbook) {
|
|
3795
4487
|
const styles = new StylesRegistry();
|
|
@@ -3809,14 +4501,29 @@ var XlsxWriter = class {
|
|
|
3809
4501
|
add("xl/styles.xml", styles.toXml());
|
|
3810
4502
|
add("docProps/core.xml", this.coreXml(workbook));
|
|
3811
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
|
+
});
|
|
3812
4510
|
workbook.sheets.forEach((sheet, i) => {
|
|
3813
4511
|
const n = i + 1;
|
|
3814
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
|
+
}
|
|
3815
4516
|
if (sheet.hasComments()) {
|
|
3816
|
-
add(`xl/worksheets/_rels/sheet${n}.xml.rels`, this.sheetRelsXml(n));
|
|
3817
4517
|
add(`xl/comments${n}.xml`, this.commentsXml(sheet));
|
|
3818
4518
|
add(`xl/drawings/vmlDrawing${n}.vml`, this.vmlDrawingXml(sheet));
|
|
3819
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
|
+
}
|
|
3820
4527
|
});
|
|
3821
4528
|
return zipSync(files);
|
|
3822
4529
|
}
|
|
@@ -3830,6 +4537,16 @@ var XlsxWriter = class {
|
|
|
3830
4537
|
const n = i + 1;
|
|
3831
4538
|
overrides += `<Override PartName="/xl/comments${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>`;
|
|
3832
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
|
+
});
|
|
3833
4550
|
let defaults = '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/>';
|
|
3834
4551
|
if (sheetsWithComments.length > 0) {
|
|
3835
4552
|
defaults += '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>';
|
|
@@ -3858,8 +4575,16 @@ var XlsxWriter = class {
|
|
|
3858
4575
|
rels += `<Relationship Id="rId${workbook.sheets.length + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
|
|
3859
4576
|
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
3860
4577
|
}
|
|
3861
|
-
|
|
3862
|
-
|
|
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>";
|
|
3863
4588
|
}
|
|
3864
4589
|
sheetXml(sheet, styles) {
|
|
3865
4590
|
let sheetViewsXml = "";
|
|
@@ -3878,14 +4603,20 @@ var XlsxWriter = class {
|
|
|
3878
4603
|
}
|
|
3879
4604
|
colsXml += "</cols>";
|
|
3880
4605
|
}
|
|
4606
|
+
const rows = sheet.rows();
|
|
4607
|
+
for (const rowIndex of sheet.rowHeights.keys()) {
|
|
4608
|
+
if (!rows.has(rowIndex)) rows.set(rowIndex, /* @__PURE__ */ new Map());
|
|
4609
|
+
}
|
|
3881
4610
|
let rowsXml = "";
|
|
3882
|
-
for (const [rowIndex, row] of
|
|
4611
|
+
for (const [rowIndex, row] of [...rows.entries()].sort((a, b) => a[0] - b[0])) {
|
|
3883
4612
|
let cellsXml = "";
|
|
3884
4613
|
const sortedCols = [...row.keys()].sort();
|
|
3885
4614
|
for (const col of sortedCols) {
|
|
3886
4615
|
cellsXml += this.cellXml(row.get(col), styles);
|
|
3887
4616
|
}
|
|
3888
|
-
|
|
4617
|
+
const height = sheet.rowHeights.get(rowIndex);
|
|
4618
|
+
const ht = height !== void 0 ? ` ht="${decimal(height * 0.75)}" customHeight="1"` : "";
|
|
4619
|
+
rowsXml += cellsXml === "" ? `<row r="${rowIndex}"${ht}/>` : `<row r="${rowIndex}"${ht}>${cellsXml}</row>`;
|
|
3889
4620
|
}
|
|
3890
4621
|
let mergesXml = "";
|
|
3891
4622
|
if (sheet.mergedRegions.length > 0) {
|
|
@@ -3894,7 +4625,14 @@ var XlsxWriter = class {
|
|
|
3894
4625
|
mergesXml += "</mergeCells>";
|
|
3895
4626
|
}
|
|
3896
4627
|
const legacyDrawing = sheet.hasComments() ? '<legacyDrawing r:id="rId1"/>' : "";
|
|
3897
|
-
|
|
4628
|
+
const print = sheet.print;
|
|
4629
|
+
const sheetPrXml = print?.fitToWidth ? '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr>' : "";
|
|
4630
|
+
let pageSetupXml = "";
|
|
4631
|
+
if (print !== null && (print.orientation !== null || print.fitToWidth)) {
|
|
4632
|
+
pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + (print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
|
|
4633
|
+
}
|
|
4634
|
+
const headerFooterXml = print?.footer ? `<headerFooter><oddFooter>${this.escape(footerCodes(print.footer))}</oddFooter></headerFooter>` : "";
|
|
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>";
|
|
3898
4636
|
}
|
|
3899
4637
|
cellXml(cell, styles) {
|
|
3900
4638
|
const ref = cell.address;
|
|
@@ -3934,8 +4672,8 @@ var XlsxWriter = class {
|
|
|
3934
4672
|
const author = entry.comment.author ?? "Author";
|
|
3935
4673
|
if (!authors.includes(author)) authors.push(author);
|
|
3936
4674
|
const authorIdx = authors.indexOf(author);
|
|
3937
|
-
const
|
|
3938
|
-
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>`;
|
|
3939
4677
|
}
|
|
3940
4678
|
let authorsXml = "<authors>";
|
|
3941
4679
|
for (const a of authors) authorsXml += `<author>${this.escape(a)}</author>`;
|
|
@@ -4815,19 +5553,19 @@ function canonEntries(root) {
|
|
|
4815
5553
|
continue;
|
|
4816
5554
|
}
|
|
4817
5555
|
stack.pop();
|
|
4818
|
-
const
|
|
5556
|
+
const text2 = top.list ? "[" + top.parts.join(",") + "]" : "{" + top.parts.join(",") + "}";
|
|
4819
5557
|
const parent = stack[stack.length - 1];
|
|
4820
5558
|
if (parent === void 0) {
|
|
4821
|
-
return
|
|
5559
|
+
return text2;
|
|
4822
5560
|
}
|
|
4823
|
-
parent.parts.push(member(parent, top.key,
|
|
5561
|
+
parent.parts.push(member(parent, top.key, text2));
|
|
4824
5562
|
}
|
|
4825
5563
|
}
|
|
4826
5564
|
function entriesFor(value) {
|
|
4827
5565
|
return Array.isArray(value) ? Array.from(value, (item, i) => [String(i), item]) : entriesOf(value);
|
|
4828
5566
|
}
|
|
4829
|
-
function member(frame, key,
|
|
4830
|
-
return frame.list ?
|
|
5567
|
+
function member(frame, key, text2) {
|
|
5568
|
+
return frame.list ? text2 : quote(key) + ":" + text2;
|
|
4831
5569
|
}
|
|
4832
5570
|
function scalar(value) {
|
|
4833
5571
|
switch (typeof value) {
|
|
@@ -4845,11 +5583,11 @@ function scalar(value) {
|
|
|
4845
5583
|
return JSON.stringify(value) ?? "null";
|
|
4846
5584
|
}
|
|
4847
5585
|
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
4848
|
-
function quote(
|
|
4849
|
-
if (LONE_SURROGATE.test(
|
|
5586
|
+
function quote(text2) {
|
|
5587
|
+
if (LONE_SURROGATE.test(text2)) {
|
|
4850
5588
|
throw unencodable("Single unpaired UTF-16 surrogate");
|
|
4851
5589
|
}
|
|
4852
|
-
return JSON.stringify(
|
|
5590
|
+
return JSON.stringify(text2);
|
|
4853
5591
|
}
|
|
4854
5592
|
function unencodable(reason) {
|
|
4855
5593
|
return new TypeError(`[holy-sheet] cannot compare a value JSON cannot hold: ${reason}`);
|
|
@@ -5011,6 +5749,68 @@ var holy_sheet_schema_default = {
|
|
|
5011
5749
|
type: "integer",
|
|
5012
5750
|
minimum: 0,
|
|
5013
5751
|
description: "How many columns stay visible while the sheet scrolls sideways. Set to 1 when the first column is the label for the row."
|
|
5752
|
+
},
|
|
5753
|
+
rowHeights: {
|
|
5754
|
+
type: "object",
|
|
5755
|
+
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.',
|
|
5756
|
+
propertyNames: { pattern: "^[1-9][0-9]*$" },
|
|
5757
|
+
additionalProperties: { type: "number", minimum: 0 }
|
|
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
|
+
},
|
|
5796
|
+
print: {
|
|
5797
|
+
type: "object",
|
|
5798
|
+
additionalProperties: false,
|
|
5799
|
+
description: "How the sheet prints. A sheet wider than a page splits its columns across pages unless fitToWidth is set.",
|
|
5800
|
+
properties: {
|
|
5801
|
+
orientation: { type: "string", enum: ["portrait", "landscape"] },
|
|
5802
|
+
fitToWidth: { type: "boolean", description: "Scale the sheet to one page wide (as many pages tall as it needs)." },
|
|
5803
|
+
footer: {
|
|
5804
|
+
type: "object",
|
|
5805
|
+
additionalProperties: false,
|
|
5806
|
+
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}"}.',
|
|
5807
|
+
properties: {
|
|
5808
|
+
left: { type: "string" },
|
|
5809
|
+
center: { type: "string" },
|
|
5810
|
+
right: { type: "string" }
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5014
5814
|
}
|
|
5015
5815
|
}
|
|
5016
5816
|
},
|
|
@@ -5219,7 +6019,7 @@ var holy_sheet_schema_default = {
|
|
|
5219
6019
|
};
|
|
5220
6020
|
|
|
5221
6021
|
// src/agent.ts
|
|
5222
|
-
var VERSION = "2.
|
|
6022
|
+
var VERSION = "2.8.0";
|
|
5223
6023
|
function toU8(input) {
|
|
5224
6024
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
5225
6025
|
}
|