@particle-academy/holy-sheet 2.7.0 → 2.9.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 +40 -0
- package/dist/index.cjs +732 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -4
- package/dist/index.d.ts +86 -4
- package/dist/index.js +732 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -77,9 +77,9 @@ var Inference = {
|
|
|
77
77
|
return { header: headerName, type: "auto" };
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
|
-
function nonNullSample(
|
|
80
|
+
function nonNullSample(values2) {
|
|
81
81
|
const out = [];
|
|
82
|
-
for (const v of
|
|
82
|
+
for (const v of values2) {
|
|
83
83
|
if (v === null || v === void 0) continue;
|
|
84
84
|
out.push(v);
|
|
85
85
|
if (out.length >= SAMPLE_SIZE) break;
|
|
@@ -269,10 +269,10 @@ var CellAddress = {
|
|
|
269
269
|
|
|
270
270
|
// src/workbook/cell.ts
|
|
271
271
|
var Cell = class {
|
|
272
|
-
constructor(address, value,
|
|
272
|
+
constructor(address, value, formula2 = null, format = null, comment = null, cachedValue = null) {
|
|
273
273
|
this.address = address;
|
|
274
274
|
this.value = value;
|
|
275
|
-
this.formula =
|
|
275
|
+
this.formula = formula2;
|
|
276
276
|
this.format = format;
|
|
277
277
|
this.comment = comment;
|
|
278
278
|
this.cachedValue = cachedValue;
|
|
@@ -320,8 +320,8 @@ var ColumnWidths = {
|
|
|
320
320
|
|
|
321
321
|
// src/workbook/cell-comment.ts
|
|
322
322
|
var CellComment = class {
|
|
323
|
-
constructor(
|
|
324
|
-
this.text =
|
|
323
|
+
constructor(text2, author = null, color2 = null) {
|
|
324
|
+
this.text = text2;
|
|
325
325
|
this.author = author;
|
|
326
326
|
this.color = color2;
|
|
327
327
|
}
|
|
@@ -415,8 +415,8 @@ var MergedRegion = class {
|
|
|
415
415
|
};
|
|
416
416
|
|
|
417
417
|
// src/workbook/sheet.ts
|
|
418
|
-
var Sheet = class {
|
|
419
|
-
constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0, rowHeights = /* @__PURE__ */ new Map(), print = null) {
|
|
418
|
+
var Sheet = class _Sheet {
|
|
419
|
+
constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0, rowHeights = /* @__PURE__ */ new Map(), print = null, charts = []) {
|
|
420
420
|
this.name = name;
|
|
421
421
|
this.cells = cells;
|
|
422
422
|
this.mergedRegions = mergedRegions;
|
|
@@ -425,6 +425,10 @@ var Sheet = class {
|
|
|
425
425
|
this.frozenCols = frozenCols;
|
|
426
426
|
this.rowHeights = rowHeights;
|
|
427
427
|
this.print = print;
|
|
428
|
+
this.charts = charts;
|
|
429
|
+
}
|
|
430
|
+
withCharts(charts) {
|
|
431
|
+
return new _Sheet(this.name, this.cells, this.mergedRegions, this.columnWidths, this.frozenRows, this.frozenCols, this.rowHeights, this.print, charts);
|
|
428
432
|
}
|
|
429
433
|
/** rowNumber → (colLetter → Cell), sorted by ascending row number. */
|
|
430
434
|
rows() {
|
|
@@ -455,6 +459,59 @@ var Sheet = class {
|
|
|
455
459
|
}
|
|
456
460
|
};
|
|
457
461
|
|
|
462
|
+
// src/workbook/chart-range.ts
|
|
463
|
+
var ChartRange = class _ChartRange {
|
|
464
|
+
constructor(sheet, c1, r1, c2, r2) {
|
|
465
|
+
this.sheet = sheet;
|
|
466
|
+
this.c1 = c1;
|
|
467
|
+
this.r1 = r1;
|
|
468
|
+
this.c2 = c2;
|
|
469
|
+
this.r2 = r2;
|
|
470
|
+
}
|
|
471
|
+
static parse(ref, defaultSheet) {
|
|
472
|
+
if (typeof ref !== "string") return null;
|
|
473
|
+
let text2 = ref.trim();
|
|
474
|
+
let sheet = defaultSheet;
|
|
475
|
+
const bang = text2.lastIndexOf("!");
|
|
476
|
+
if (bang >= 0) {
|
|
477
|
+
let sheetPart = text2.slice(0, bang);
|
|
478
|
+
text2 = text2.slice(bang + 1);
|
|
479
|
+
if (sheetPart.length >= 2 && sheetPart.startsWith("'") && sheetPart.endsWith("'")) {
|
|
480
|
+
sheetPart = sheetPart.slice(1, -1).replace(/''/g, "'");
|
|
481
|
+
}
|
|
482
|
+
if (sheetPart === "") return null;
|
|
483
|
+
sheet = sheetPart;
|
|
484
|
+
}
|
|
485
|
+
const ends = text2.replace(/\$/g, "").split(":");
|
|
486
|
+
if (ends.length > 2) return null;
|
|
487
|
+
const a = CellAddress.parse(ends[0]);
|
|
488
|
+
const b = CellAddress.parse(ends[1] ?? ends[0]);
|
|
489
|
+
if (a === null || b === null || a[1] < 1 || b[1] < 1) return null;
|
|
490
|
+
return new _ChartRange(sheet, Math.min(a[0], b[0]), Math.min(a[1], b[1]), Math.max(a[0], b[0]), Math.max(a[1], b[1]));
|
|
491
|
+
}
|
|
492
|
+
/** 'Q3 Revenue'!$B$5:$B$7 */
|
|
493
|
+
formula() {
|
|
494
|
+
const from = "$" + CellAddress.letter(this.c1) + "$" + this.r1;
|
|
495
|
+
const to = "$" + CellAddress.letter(this.c2) + "$" + this.r2;
|
|
496
|
+
return "'" + this.sheet.replace(/'/g, "''") + "'!" + from + (from === to ? "" : ":" + to);
|
|
497
|
+
}
|
|
498
|
+
/** "B5:B7" on its own sheet, "'Data'!B5:B7" on another. */
|
|
499
|
+
toSchema(ownSheet) {
|
|
500
|
+
const from = CellAddress.letter(this.c1) + this.r1;
|
|
501
|
+
const to = CellAddress.letter(this.c2) + this.r2;
|
|
502
|
+
const ref = from === to ? from : from + ":" + to;
|
|
503
|
+
return this.sheet === ownSheet ? ref : "'" + this.sheet.replace(/'/g, "''") + "'!" + ref;
|
|
504
|
+
}
|
|
505
|
+
/** A1 addresses, row by row. */
|
|
506
|
+
addresses() {
|
|
507
|
+
const out = [];
|
|
508
|
+
for (let r = this.r1; r <= this.r2; r++) {
|
|
509
|
+
for (let c = this.c1; c <= this.c2; c++) out.push(CellAddress.letter(c) + r);
|
|
510
|
+
}
|
|
511
|
+
return out;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
458
515
|
// src/workbook/workbook.ts
|
|
459
516
|
var Workbook = class {
|
|
460
517
|
constructor(sheets, meta = {}) {
|
|
@@ -667,20 +724,21 @@ var Normalizer = class {
|
|
|
667
724
|
toInt(sheet.frozenRows ?? 0),
|
|
668
725
|
toInt(sheet.frozenCols ?? 0),
|
|
669
726
|
normalizeRowHeights(sheet.rowHeights),
|
|
670
|
-
normalizePrint(sheet.print)
|
|
727
|
+
normalizePrint(sheet.print),
|
|
728
|
+
normalizeCharts(sheet.charts, name, cells)
|
|
671
729
|
);
|
|
672
730
|
}
|
|
673
731
|
normalizeCellMap(map, sheetName2) {
|
|
674
732
|
const cells = /* @__PURE__ */ new Map();
|
|
675
733
|
for (const [address, cellData] of Object.entries(map)) {
|
|
676
734
|
if (!isCellSpec(cellData)) {
|
|
677
|
-
const [bare,
|
|
735
|
+
const [bare, formula2] = promoteFormula(cellData);
|
|
678
736
|
cells.set(
|
|
679
737
|
address,
|
|
680
738
|
new Cell(
|
|
681
739
|
address,
|
|
682
|
-
|
|
683
|
-
|
|
740
|
+
formula2 !== null ? null : this.coerceValue(bare, null, address, sheetName2),
|
|
741
|
+
formula2
|
|
684
742
|
)
|
|
685
743
|
);
|
|
686
744
|
continue;
|
|
@@ -796,11 +854,11 @@ var Normalizer = class {
|
|
|
796
854
|
value.computedValue ?? null
|
|
797
855
|
);
|
|
798
856
|
}
|
|
799
|
-
const [bare,
|
|
857
|
+
const [bare, formula2] = promoteFormula(value);
|
|
800
858
|
return new Cell(
|
|
801
859
|
address,
|
|
802
|
-
|
|
803
|
-
|
|
860
|
+
formula2 !== null ? null : this.coerceValue(bare, columnFormat, address, sheetName2),
|
|
861
|
+
formula2,
|
|
804
862
|
columnFormat
|
|
805
863
|
);
|
|
806
864
|
}
|
|
@@ -858,6 +916,72 @@ function toInt(v) {
|
|
|
858
916
|
const n = Math.trunc(Number(v));
|
|
859
917
|
return Number.isFinite(n) ? n : 0;
|
|
860
918
|
}
|
|
919
|
+
function normalizePoints(points2, count) {
|
|
920
|
+
if (!isPlainObject(points2) && !Array.isArray(points2)) return [];
|
|
921
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
922
|
+
for (const p of Object.values(points2)) {
|
|
923
|
+
const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
|
|
924
|
+
const index = rec ? rec.index : void 0;
|
|
925
|
+
const color2 = rec ? rec.color : void 0;
|
|
926
|
+
const m = typeof color2 === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(color2) : null;
|
|
927
|
+
if (typeof index === "number" && Number.isInteger(index) && index >= 0 && index < count && m) {
|
|
928
|
+
byIndex.set(index, m[1].toUpperCase());
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index, color2]) => ({ index, color: color2 }));
|
|
932
|
+
}
|
|
933
|
+
var CHART_TYPES = ["column", "bar", "line", "pie"];
|
|
934
|
+
function normalizeCharts(charts, sheetName2, cells) {
|
|
935
|
+
if (!isPlainObject(charts) && !Array.isArray(charts)) return [];
|
|
936
|
+
let lastCol = -1;
|
|
937
|
+
for (const address of cells.keys()) {
|
|
938
|
+
const parsed = CellAddress.parse(address);
|
|
939
|
+
if (parsed !== null) lastCol = Math.max(lastCol, parsed[0]);
|
|
940
|
+
}
|
|
941
|
+
const out = [];
|
|
942
|
+
for (const chart of Object.values(charts)) {
|
|
943
|
+
if (!isPlainObject(chart) || !CHART_TYPES.includes(chart.type)) continue;
|
|
944
|
+
const series = [];
|
|
945
|
+
const rawSeries = isPlainObject(chart.series) || Array.isArray(chart.series) ? Object.values(chart.series) : [];
|
|
946
|
+
for (const s of rawSeries) {
|
|
947
|
+
const values2 = isPlainObject(s) || Array.isArray(s) ? ChartRange.parse(s.values, sheetName2) : null;
|
|
948
|
+
if (values2 === null) continue;
|
|
949
|
+
const rec = s;
|
|
950
|
+
const m = typeof rec.color === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(rec.color) : null;
|
|
951
|
+
series.push({
|
|
952
|
+
name: typeof rec.name === "string" ? rec.name : null,
|
|
953
|
+
values: values2,
|
|
954
|
+
color: m ? m[1].toUpperCase() : null,
|
|
955
|
+
points: normalizePoints(rec.points, values2.addresses().length)
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
if (series.length === 0) continue;
|
|
959
|
+
const stack = out.length;
|
|
960
|
+
let from = [lastCol + 2, 1 + 18 * stack];
|
|
961
|
+
let to = [from[0] + 8, from[1] + 16];
|
|
962
|
+
if (isPlainObject(chart.position) || Array.isArray(chart.position)) {
|
|
963
|
+
const p = chart.position;
|
|
964
|
+
const a = typeof p.from === "string" ? CellAddress.parse(p.from) : null;
|
|
965
|
+
const b = typeof p.to === "string" ? CellAddress.parse(p.to) : null;
|
|
966
|
+
if (a !== null && b !== null && b[0] > a[0] && b[1] > a[1]) {
|
|
967
|
+
from = [a[0], a[1] - 1];
|
|
968
|
+
to = [b[0], b[1] - 1];
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
out.push({
|
|
972
|
+
type: chart.type,
|
|
973
|
+
title: typeof chart.title === "string" && chart.title !== "" ? chart.title : null,
|
|
974
|
+
categories: ChartRange.parse(chart.categories, sheetName2),
|
|
975
|
+
series,
|
|
976
|
+
from,
|
|
977
|
+
to,
|
|
978
|
+
legend: typeof chart.legend === "boolean" ? chart.legend : null,
|
|
979
|
+
dataLabels: chart.dataLabels === true,
|
|
980
|
+
format: typeof chart.format === "string" && chart.format !== "" ? chart.format : null
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
return out;
|
|
984
|
+
}
|
|
861
985
|
function phpNumeric(v) {
|
|
862
986
|
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
863
987
|
return isNumericString(v) ? numericStringToNumber(v) : null;
|
|
@@ -876,6 +1000,7 @@ function normalizePrint(print) {
|
|
|
876
1000
|
const p = print;
|
|
877
1001
|
const orientation = p.orientation === "portrait" || p.orientation === "landscape" ? p.orientation : null;
|
|
878
1002
|
const fitToWidth = p.fitToWidth === true;
|
|
1003
|
+
const fitToPage = p.fitToPage === true;
|
|
879
1004
|
let footer = null;
|
|
880
1005
|
if (isPlainObject(p.footer) || Array.isArray(p.footer)) {
|
|
881
1006
|
const f = p.footer;
|
|
@@ -883,8 +1008,8 @@ function normalizePrint(print) {
|
|
|
883
1008
|
footer = { left: slot("left"), center: slot("center"), right: slot("right") };
|
|
884
1009
|
if (footer.left === "" && footer.center === "" && footer.right === "") footer = null;
|
|
885
1010
|
}
|
|
886
|
-
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
887
|
-
return { orientation, fitToWidth, footer };
|
|
1011
|
+
if (orientation === null && !fitToWidth && !fitToPage && footer === null) return null;
|
|
1012
|
+
return { orientation, fitToWidth, fitToPage, footer };
|
|
888
1013
|
}
|
|
889
1014
|
|
|
890
1015
|
// src/schema/formula-linter.ts
|
|
@@ -948,9 +1073,9 @@ var FormulaLinter = class {
|
|
|
948
1073
|
isError(v) {
|
|
949
1074
|
return typeof v === "string" && v.length > 0 && v[0] === "#";
|
|
950
1075
|
}
|
|
951
|
-
evaluate(
|
|
1076
|
+
evaluate(formula2, defaultSheet, stack) {
|
|
952
1077
|
try {
|
|
953
|
-
const c = { tokens: this.tokenize(
|
|
1078
|
+
const c = { tokens: this.tokenize(formula2), pos: 0 };
|
|
954
1079
|
const result = this.parseExpr(c, defaultSheet, stack);
|
|
955
1080
|
if (c.pos < c.tokens.length) return ERR_NAME;
|
|
956
1081
|
return result;
|
|
@@ -1211,13 +1336,13 @@ var FormulaLinter = class {
|
|
|
1211
1336
|
const row1 = Math.min(a[1], b[1]);
|
|
1212
1337
|
const col2 = Math.max(a[0], b[0]);
|
|
1213
1338
|
const row2 = Math.max(a[1], b[1]);
|
|
1214
|
-
const
|
|
1339
|
+
const values2 = [];
|
|
1215
1340
|
for (let r = row1; r <= row2; r++) {
|
|
1216
1341
|
for (let col = col1; col <= col2; col++) {
|
|
1217
|
-
|
|
1342
|
+
values2.push(this.resolveCell(sheet, CellAddress.letter(col) + r, stack));
|
|
1218
1343
|
}
|
|
1219
1344
|
}
|
|
1220
|
-
return
|
|
1345
|
+
return values2;
|
|
1221
1346
|
}
|
|
1222
1347
|
expectOp(c, op) {
|
|
1223
1348
|
const t = c.tokens[c.pos];
|
|
@@ -1349,12 +1474,12 @@ var FormulaLinter = class {
|
|
|
1349
1474
|
return truthy ? args[1] : args[2] ?? false;
|
|
1350
1475
|
}
|
|
1351
1476
|
// ---- Hints ----
|
|
1352
|
-
hint(error2,
|
|
1477
|
+
hint(error2, formula2, sheet) {
|
|
1353
1478
|
switch (error2) {
|
|
1354
1479
|
case ERR_VALUE:
|
|
1355
|
-
return this.hintValue(
|
|
1480
|
+
return this.hintValue(formula2, sheet);
|
|
1356
1481
|
case ERR_REF:
|
|
1357
|
-
return this.hintRef(
|
|
1482
|
+
return this.hintRef(formula2);
|
|
1358
1483
|
case ERR_NAME:
|
|
1359
1484
|
return "The formula references an unknown function or has a syntax error. Holy Sheet supports: SUM, AVERAGE, COUNT, COUNTA, MIN, MAX, IF, ROUND, ABS, LEN, UPPER, LOWER, CONCAT.";
|
|
1360
1485
|
case ERR_DIV0:
|
|
@@ -1365,8 +1490,8 @@ var FormulaLinter = class {
|
|
|
1365
1490
|
return "Formula evaluation failed.";
|
|
1366
1491
|
}
|
|
1367
1492
|
}
|
|
1368
|
-
hintRef(
|
|
1369
|
-
for (const m of
|
|
1493
|
+
hintRef(formula2) {
|
|
1494
|
+
for (const m of formula2.matchAll(new RegExp(SHEET_QUALIFIER, "g"))) {
|
|
1370
1495
|
const name = this.cleanSheetName(m[1]);
|
|
1371
1496
|
if (!this.sheetNames.has(name.toLowerCase())) {
|
|
1372
1497
|
return `The formula refers to a sheet named '${name}', and this workbook has no such sheet. Its sheets are: ${[...this.sheetNames.values()].join(", ")}. Quote a name that contains spaces or punctuation: 'My Sheet'!A1.`;
|
|
@@ -1374,10 +1499,10 @@ var FormulaLinter = class {
|
|
|
1374
1499
|
}
|
|
1375
1500
|
return "A cell reference points to a cell that doesn't exist in the workbook. Check column letters and row numbers.";
|
|
1376
1501
|
}
|
|
1377
|
-
hintValue(
|
|
1502
|
+
hintValue(formula2, sheet) {
|
|
1378
1503
|
const re = new RegExp(`(?:${SHEET_QUALIFIER})?\\$?([A-Z]+)\\$?(\\d+)`, "gi");
|
|
1379
1504
|
const offenders = [];
|
|
1380
|
-
for (const m of
|
|
1505
|
+
for (const m of formula2.matchAll(re)) {
|
|
1381
1506
|
const sheetName2 = m[1] ? this.sheetNames.get(this.cleanSheetName(m[1]).toLowerCase()) ?? this.cleanSheetName(m[1]) : sheet;
|
|
1382
1507
|
const a1 = m[2].toUpperCase() + m[3];
|
|
1383
1508
|
const key = sheetName2 + "!" + a1;
|
|
@@ -1500,11 +1625,11 @@ var Repairer = class {
|
|
|
1500
1625
|
sheet.columns.forEach((col, colIdx) => {
|
|
1501
1626
|
if (!isPlainObject(col)) return;
|
|
1502
1627
|
if (col.type !== void 0 && col.type !== "auto") return;
|
|
1503
|
-
const
|
|
1628
|
+
const values2 = [];
|
|
1504
1629
|
for (const row of sheet.rows) {
|
|
1505
|
-
if (Array.isArray(row) && colIdx < row.length)
|
|
1630
|
+
if (Array.isArray(row) && colIdx < row.length) values2.push(row[colIdx]);
|
|
1506
1631
|
}
|
|
1507
|
-
const nonNull =
|
|
1632
|
+
const nonNull = values2.filter((v) => v !== null && v !== void 0);
|
|
1508
1633
|
if (nonNull.length === 0) return;
|
|
1509
1634
|
const allDateLike = nonNull.every((v) => typeof v === "string" && ISO_DATE2.test(v));
|
|
1510
1635
|
if (allDateLike) {
|
|
@@ -1767,6 +1892,9 @@ var Validator = class {
|
|
|
1767
1892
|
}
|
|
1768
1893
|
}
|
|
1769
1894
|
}
|
|
1895
|
+
if (sheet.charts != null) {
|
|
1896
|
+
errors.push(...validateCharts(sheet.charts, path, typeof sheet.name === "string" ? sheet.name : ""));
|
|
1897
|
+
}
|
|
1770
1898
|
if (sheet.print != null) {
|
|
1771
1899
|
const print = sheet.print;
|
|
1772
1900
|
if (!isPlainObject(print) && !Array.isArray(print)) {
|
|
@@ -1796,6 +1924,15 @@ var Validator = class {
|
|
|
1796
1924
|
"true scales the sheet to one page wide when printed."
|
|
1797
1925
|
));
|
|
1798
1926
|
}
|
|
1927
|
+
if (print.fitToPage != null && typeof print.fitToPage !== "boolean") {
|
|
1928
|
+
errors.push(error(
|
|
1929
|
+
`${path}.print.fitToPage`,
|
|
1930
|
+
"boolean",
|
|
1931
|
+
typeOf(print.fitToPage),
|
|
1932
|
+
print.fitToPage,
|
|
1933
|
+
"true scales the sheet onto ONE page, wide and tall: a one-page summary with a chart prints whole."
|
|
1934
|
+
));
|
|
1935
|
+
}
|
|
1799
1936
|
if (print.footer != null) {
|
|
1800
1937
|
if (!isPlainObject(print.footer) && !Array.isArray(print.footer)) {
|
|
1801
1938
|
errors.push(error(
|
|
@@ -1867,6 +2004,149 @@ var Validator = class {
|
|
|
1867
2004
|
return errors;
|
|
1868
2005
|
}
|
|
1869
2006
|
};
|
|
2007
|
+
function validatePoints(points2, at2, values2) {
|
|
2008
|
+
if (!Array.isArray(points2)) {
|
|
2009
|
+
return [
|
|
2010
|
+
error(
|
|
2011
|
+
at2,
|
|
2012
|
+
"a list of points",
|
|
2013
|
+
typeOf(points2),
|
|
2014
|
+
points2,
|
|
2015
|
+
'Colour single points: [{"index": 2, "color": "#C2410C"}], index 0-based within values.'
|
|
2016
|
+
)
|
|
2017
|
+
];
|
|
2018
|
+
}
|
|
2019
|
+
const count = values2 === null ? 0 : values2.addresses().length;
|
|
2020
|
+
const errors = [];
|
|
2021
|
+
points2.forEach((p, k) => {
|
|
2022
|
+
const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
|
|
2023
|
+
const index = rec ? rec.index ?? null : null;
|
|
2024
|
+
const color2 = rec ? rec.color ?? null : null;
|
|
2025
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= count) {
|
|
2026
|
+
errors.push(error(
|
|
2027
|
+
`${at2}[${k}].index`,
|
|
2028
|
+
"an index within values (0 to " + Math.max(0, count - 1) + ")",
|
|
2029
|
+
typeOf(index),
|
|
2030
|
+
index,
|
|
2031
|
+
'The 0-based position of the point in its values range: "B5:B7" has points 0, 1 and 2.'
|
|
2032
|
+
));
|
|
2033
|
+
}
|
|
2034
|
+
if (typeof color2 !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(color2)) {
|
|
2035
|
+
errors.push(error(`${at2}[${k}].color`, "#RRGGBB", typeOf(color2), color2, 'A hex colour, like "#C2410C".'));
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
return errors;
|
|
2039
|
+
}
|
|
2040
|
+
function validateCharts(charts, path, sheetName2) {
|
|
2041
|
+
if (!Array.isArray(charts)) {
|
|
2042
|
+
return [
|
|
2043
|
+
error(
|
|
2044
|
+
`${path}.charts`,
|
|
2045
|
+
"a list of charts",
|
|
2046
|
+
typeOf(charts),
|
|
2047
|
+
charts,
|
|
2048
|
+
'charts is a list: [{"type": "column", "categories": "A5:A7", "series": [{"name": "Revenue", "values": "B5:B7"}]}].'
|
|
2049
|
+
)
|
|
2050
|
+
];
|
|
2051
|
+
}
|
|
2052
|
+
const errors = [];
|
|
2053
|
+
charts.forEach((chart, i) => {
|
|
2054
|
+
const at2 = `${path}.charts[${i}]`;
|
|
2055
|
+
if (!isPlainObject(chart) && !Array.isArray(chart)) {
|
|
2056
|
+
errors.push(error(at2, "object", typeOf(chart), chart, "A chart is an object with a type and series."));
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
const c = chart;
|
|
2060
|
+
if (!CHART_TYPES.includes(c.type)) {
|
|
2061
|
+
errors.push(error(
|
|
2062
|
+
`${at2}.type`,
|
|
2063
|
+
"one of: " + CHART_TYPES.join(", "),
|
|
2064
|
+
typeOf(c.type ?? null),
|
|
2065
|
+
c.type ?? null,
|
|
2066
|
+
"column compares values across categories; bar does the same horizontally (long labels); line shows change over time; pie shows parts of one whole."
|
|
2067
|
+
));
|
|
2068
|
+
}
|
|
2069
|
+
const series = c.series;
|
|
2070
|
+
if (!Array.isArray(series) || series.length === 0) {
|
|
2071
|
+
errors.push(error(
|
|
2072
|
+
`${at2}.series`,
|
|
2073
|
+
"a non-empty list of series",
|
|
2074
|
+
typeOf(series ?? null),
|
|
2075
|
+
series ?? null,
|
|
2076
|
+
'Each series names the range it plots: [{"name": "Revenue", "values": "B5:B7"}].'
|
|
2077
|
+
));
|
|
2078
|
+
} else {
|
|
2079
|
+
series.forEach((s, j) => {
|
|
2080
|
+
const isObj = isPlainObject(s) || Array.isArray(s);
|
|
2081
|
+
const values2 = isObj ? s.values ?? null : s;
|
|
2082
|
+
if (!isObj || ChartRange.parse(values2, sheetName2) === null) {
|
|
2083
|
+
errors.push(error(
|
|
2084
|
+
`${at2}.series[${j}].values`,
|
|
2085
|
+
'a cell range like "B5:B7"',
|
|
2086
|
+
typeOf(values2),
|
|
2087
|
+
values2,
|
|
2088
|
+
`A range of cells: "B5:B7" on this sheet, or "'Other sheet'!B5:B7".`
|
|
2089
|
+
));
|
|
2090
|
+
} else {
|
|
2091
|
+
const color2 = s.color;
|
|
2092
|
+
if (color2 != null && (typeof color2 !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(color2))) {
|
|
2093
|
+
errors.push(error(`${at2}.series[${j}].color`, "#RRGGBB", typeOf(color2), color2, 'A hex colour, like "#1F3A5F".'));
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
if (isObj && s.points != null) {
|
|
2097
|
+
errors.push(...validatePoints(s.points, `${at2}.series[${j}].points`, ChartRange.parse(values2, sheetName2)));
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
if (c.categories != null && ChartRange.parse(c.categories, sheetName2) === null) {
|
|
2102
|
+
errors.push(error(
|
|
2103
|
+
`${at2}.categories`,
|
|
2104
|
+
'a cell range like "A5:A7"',
|
|
2105
|
+
typeOf(c.categories),
|
|
2106
|
+
c.categories,
|
|
2107
|
+
'The labels along the axis, as a range: "A5:A7".'
|
|
2108
|
+
));
|
|
2109
|
+
}
|
|
2110
|
+
if (c.position != null) {
|
|
2111
|
+
const p = c.position;
|
|
2112
|
+
const isObj = isPlainObject(p) || Array.isArray(p);
|
|
2113
|
+
const a = isObj && typeof p.from === "string" ? CellAddress.parse(p.from) : null;
|
|
2114
|
+
const b = isObj && typeof p.to === "string" ? CellAddress.parse(p.to) : null;
|
|
2115
|
+
if (a === null || b === null || b[0] <= a[0] || b[1] <= a[1]) {
|
|
2116
|
+
errors.push(error(
|
|
2117
|
+
`${at2}.position`,
|
|
2118
|
+
'{"from": top-left cell, "to": bottom-right cell}',
|
|
2119
|
+
typeOf(p),
|
|
2120
|
+
p,
|
|
2121
|
+
'Two cells, the second below and to the right of the first: {"from": "E2", "to": "L18"} is 7 columns by 16 rows.'
|
|
2122
|
+
));
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
for (const key of ["title", "format"]) {
|
|
2126
|
+
if (c[key] != null && typeof c[key] !== "string") {
|
|
2127
|
+
errors.push(error(
|
|
2128
|
+
`${at2}.${key}`,
|
|
2129
|
+
"string",
|
|
2130
|
+
typeOf(c[key]),
|
|
2131
|
+
c[key],
|
|
2132
|
+
key === "format" ? 'An Excel number format for the axis and labels, like "$#,##0" or "0.0%".' : "The chart title."
|
|
2133
|
+
));
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
for (const key of ["dataLabels", "legend"]) {
|
|
2137
|
+
if (c[key] != null && typeof c[key] !== "boolean") {
|
|
2138
|
+
errors.push(error(
|
|
2139
|
+
`${at2}.${key}`,
|
|
2140
|
+
"boolean",
|
|
2141
|
+
typeOf(c[key]),
|
|
2142
|
+
c[key],
|
|
2143
|
+
key === "dataLabels" ? "true prints each value on the chart." : "Defaults to on for several series or a pie."
|
|
2144
|
+
));
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
return errors;
|
|
2149
|
+
}
|
|
1870
2150
|
function error(path, expected, got, value, hint) {
|
|
1871
2151
|
return { path, expected, got, value, hint };
|
|
1872
2152
|
}
|
|
@@ -2383,15 +2663,15 @@ var FormatSniffer = {
|
|
|
2383
2663
|
};
|
|
2384
2664
|
|
|
2385
2665
|
// src/reader/ods/ods-formula.ts
|
|
2386
|
-
function odsFormulaToA1(
|
|
2666
|
+
function odsFormulaToA1(formula2) {
|
|
2387
2667
|
let grammar = "of";
|
|
2388
|
-
const m = /^(of|oooc|msoxl):/i.exec(
|
|
2668
|
+
const m = /^(of|oooc|msoxl):/i.exec(formula2);
|
|
2389
2669
|
if (m) {
|
|
2390
2670
|
grammar = m[1].toLowerCase();
|
|
2391
|
-
|
|
2671
|
+
formula2 = formula2.slice(m[0].length);
|
|
2392
2672
|
}
|
|
2393
|
-
if (
|
|
2394
|
-
return grammar === "msoxl" ?
|
|
2673
|
+
if (formula2.startsWith("=")) formula2 = formula2.slice(1);
|
|
2674
|
+
return grammar === "msoxl" ? formula2 : translate(formula2);
|
|
2395
2675
|
}
|
|
2396
2676
|
function translate(s) {
|
|
2397
2677
|
let out = "";
|
|
@@ -2740,8 +3020,8 @@ var OdsStyles = class {
|
|
|
2740
3020
|
switch (el2.name) {
|
|
2741
3021
|
case "number-style": {
|
|
2742
3022
|
if (has2("scientific-number") || has2("fraction")) return null;
|
|
2743
|
-
for (const
|
|
2744
|
-
const iso = currencyCode(ownText(
|
|
3023
|
+
for (const text2 of parts.filter((p) => p.name === "text")) {
|
|
3024
|
+
const iso = currencyCode(ownText(text2).replace(/[-()"\u00A0]/g, ""));
|
|
2745
3025
|
if (iso !== null) return { displayFormat: "currency", decimals: decimals ?? 0, currency: iso };
|
|
2746
3026
|
}
|
|
2747
3027
|
if (!number) return null;
|
|
@@ -2920,6 +3200,7 @@ function sheetToSchema(sheet) {
|
|
|
2920
3200
|
const print = {};
|
|
2921
3201
|
if (sheet.print.orientation !== null) print["orientation"] = sheet.print.orientation;
|
|
2922
3202
|
if (sheet.print.fitToWidth) print["fitToWidth"] = true;
|
|
3203
|
+
if (sheet.print.fitToPage) print["fitToPage"] = true;
|
|
2923
3204
|
if (sheet.print.footer !== null) {
|
|
2924
3205
|
const footer = {};
|
|
2925
3206
|
for (const k of ["left", "center", "right"]) if (sheet.print.footer[k] !== "") footer[k] = sheet.print.footer[k];
|
|
@@ -2927,6 +3208,30 @@ function sheetToSchema(sheet) {
|
|
|
2927
3208
|
}
|
|
2928
3209
|
out["print"] = print;
|
|
2929
3210
|
}
|
|
3211
|
+
if (sheet.charts.length > 0) {
|
|
3212
|
+
out["charts"] = sheet.charts.map((c) => chartToSchema(c, sheet.name));
|
|
3213
|
+
}
|
|
3214
|
+
return out;
|
|
3215
|
+
}
|
|
3216
|
+
function chartToSchema(chart, sheetName2) {
|
|
3217
|
+
const out = { type: chart.type };
|
|
3218
|
+
if (chart.title !== null) out["title"] = chart.title;
|
|
3219
|
+
if (chart.categories !== null) out["categories"] = chart.categories.toSchema(sheetName2);
|
|
3220
|
+
out["series"] = chart.series.map((s) => {
|
|
3221
|
+
const series = {};
|
|
3222
|
+
if (s.name !== null) series["name"] = s.name;
|
|
3223
|
+
series["values"] = s.values.toSchema(sheetName2);
|
|
3224
|
+
if (s.color !== null) series["color"] = "#" + s.color;
|
|
3225
|
+
if (s.points.length > 0) series["points"] = s.points.map((p) => ({ index: p.index, color: "#" + p.color }));
|
|
3226
|
+
return series;
|
|
3227
|
+
});
|
|
3228
|
+
out["position"] = {
|
|
3229
|
+
from: CellAddress.letter(chart.from[0]) + (chart.from[1] + 1),
|
|
3230
|
+
to: CellAddress.letter(chart.to[0]) + (chart.to[1] + 1)
|
|
3231
|
+
};
|
|
3232
|
+
if (chart.dataLabels) out["dataLabels"] = true;
|
|
3233
|
+
if (chart.legend !== null) out["legend"] = chart.legend;
|
|
3234
|
+
if (chart.format !== null) out["format"] = chart.format;
|
|
2930
3235
|
return out;
|
|
2931
3236
|
}
|
|
2932
3237
|
function cellToSchema(cell) {
|
|
@@ -3034,10 +3339,10 @@ function parseXml(src, options = {}) {
|
|
|
3034
3339
|
if (namespaces) root.content = [];
|
|
3035
3340
|
const stack = [root];
|
|
3036
3341
|
const scopes = [{ xml: XML_NS }];
|
|
3037
|
-
const appendText = (
|
|
3342
|
+
const appendText = (text2) => {
|
|
3038
3343
|
const top = stack[stack.length - 1];
|
|
3039
|
-
top.text +=
|
|
3040
|
-
if (namespaces &&
|
|
3344
|
+
top.text += text2;
|
|
3345
|
+
if (namespaces && text2 !== "") top.content.push(text2);
|
|
3041
3346
|
};
|
|
3042
3347
|
while (i < n) {
|
|
3043
3348
|
if (src[i] === "<") {
|
|
@@ -3258,7 +3563,7 @@ function hasContent(cell) {
|
|
|
3258
3563
|
}
|
|
3259
3564
|
function readCell(cell) {
|
|
3260
3565
|
const type = attr(cell, NS.OFFICE, "value-type") ?? null;
|
|
3261
|
-
const
|
|
3566
|
+
const text2 = paragraphs(cell);
|
|
3262
3567
|
let value = null;
|
|
3263
3568
|
let seconds = null;
|
|
3264
3569
|
let dateHasTime = false;
|
|
@@ -3287,20 +3592,20 @@ function readCell(cell) {
|
|
|
3287
3592
|
case "string": {
|
|
3288
3593
|
const error2 = attr(cell, NS.CALCEXT, "value-type") === "error";
|
|
3289
3594
|
const stored = attr(cell, NS.OFFICE, "string-value");
|
|
3290
|
-
value = stored !== void 0 && !error2 ? stored :
|
|
3595
|
+
value = stored !== void 0 && !error2 ? stored : text2 ?? "";
|
|
3291
3596
|
break;
|
|
3292
3597
|
}
|
|
3293
3598
|
default:
|
|
3294
|
-
value =
|
|
3599
|
+
value = text2 !== null && text2 !== "" ? text2 : null;
|
|
3295
3600
|
}
|
|
3296
|
-
let
|
|
3601
|
+
let formula2 = null;
|
|
3297
3602
|
let cached = null;
|
|
3298
3603
|
const formulaAttr = attr(cell, NS.TABLE, "formula");
|
|
3299
3604
|
if (formulaAttr !== void 0) {
|
|
3300
3605
|
const translated = odsFormulaToA1(formulaAttr);
|
|
3301
3606
|
const literalBoolean = type === "boolean" && /^[ \t\n\r\f\v]*(TRUE|FALSE)\(\)[ \t\n\r\f\v]*$/i.test(translated);
|
|
3302
3607
|
if (!literalBoolean) {
|
|
3303
|
-
|
|
3608
|
+
formula2 = translated;
|
|
3304
3609
|
cached = seconds !== null ? serial(seconds) : value;
|
|
3305
3610
|
value = null;
|
|
3306
3611
|
seconds = null;
|
|
@@ -3316,7 +3621,7 @@ function readCell(cell) {
|
|
|
3316
3621
|
const currency = attr(cell, NS.OFFICE, "currency");
|
|
3317
3622
|
return {
|
|
3318
3623
|
value,
|
|
3319
|
-
formula,
|
|
3624
|
+
formula: formula2,
|
|
3320
3625
|
cached,
|
|
3321
3626
|
type,
|
|
3322
3627
|
currency: currency !== void 0 ? phpTrim(currency) : null,
|
|
@@ -3385,6 +3690,139 @@ function readMeta(xml) {
|
|
|
3385
3690
|
return out;
|
|
3386
3691
|
}
|
|
3387
3692
|
|
|
3693
|
+
// src/reader/chart-parser.ts
|
|
3694
|
+
function descendants(node, name) {
|
|
3695
|
+
const out = [];
|
|
3696
|
+
const stack = node ? [...node.children].reverse() : [];
|
|
3697
|
+
while (stack.length > 0) {
|
|
3698
|
+
const n = stack.pop();
|
|
3699
|
+
if (n.name === name) out.push(n);
|
|
3700
|
+
for (let i = n.children.length - 1; i >= 0; i--) stack.push(n.children[i]);
|
|
3701
|
+
}
|
|
3702
|
+
return out;
|
|
3703
|
+
}
|
|
3704
|
+
function load(xml) {
|
|
3705
|
+
if (/<!DOCTYPE/i.test(xml)) return null;
|
|
3706
|
+
return parseXml(xml);
|
|
3707
|
+
}
|
|
3708
|
+
function wrap2(root) {
|
|
3709
|
+
return { name: "#document", attrs: {}, children: [root], text: "" };
|
|
3710
|
+
}
|
|
3711
|
+
var ChartParser = {
|
|
3712
|
+
anchors(drawingXml) {
|
|
3713
|
+
const root = load(drawingXml);
|
|
3714
|
+
if (!root) return [];
|
|
3715
|
+
const out = [];
|
|
3716
|
+
for (const anchor of descendants(wrap2(root), "twoCellAnchor")) {
|
|
3717
|
+
const from = marker(anchor, "from");
|
|
3718
|
+
const to = marker(anchor, "to");
|
|
3719
|
+
const chart = descendants(anchor, "chart")[0];
|
|
3720
|
+
const rel = chart ? at(chart, "id") ?? "" : "";
|
|
3721
|
+
if (from !== null && to !== null && rel !== "") out.push({ from, to, rel });
|
|
3722
|
+
}
|
|
3723
|
+
return out;
|
|
3724
|
+
},
|
|
3725
|
+
chart(chartXml, sheetName2, from, to) {
|
|
3726
|
+
const root = load(chartXml);
|
|
3727
|
+
if (!root) return null;
|
|
3728
|
+
const doc = wrap2(root);
|
|
3729
|
+
let plot;
|
|
3730
|
+
let type = null;
|
|
3731
|
+
for (const tag of ["barChart", "lineChart", "pieChart"]) {
|
|
3732
|
+
const found = descendants(doc, tag)[0];
|
|
3733
|
+
if (found) {
|
|
3734
|
+
plot = found;
|
|
3735
|
+
type = tag === "lineChart" ? "line" : tag === "pieChart" ? "pie" : at(el(found, "barDir"), "val") === "bar" ? "bar" : "column";
|
|
3736
|
+
break;
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
if (!plot || type === null) return null;
|
|
3740
|
+
let categories = null;
|
|
3741
|
+
const series = [];
|
|
3742
|
+
for (const ser of els(plot, "ser")) {
|
|
3743
|
+
const valRef = formula(ser, "val");
|
|
3744
|
+
const values2 = valRef !== null ? ChartRange.parse(valRef, sheetName2) : null;
|
|
3745
|
+
if (values2 === null) continue;
|
|
3746
|
+
if (categories === null) {
|
|
3747
|
+
const catRef = formula(ser, "cat");
|
|
3748
|
+
categories = catRef !== null ? ChartRange.parse(catRef, sheetName2) : null;
|
|
3749
|
+
}
|
|
3750
|
+
series.push({ name: seriesName(ser), values: values2, color: seriesColor(ser), points: points(ser) });
|
|
3751
|
+
}
|
|
3752
|
+
if (series.length === 0) return null;
|
|
3753
|
+
const labels = el(plot, "dLbls");
|
|
3754
|
+
const dataLabels2 = labels !== void 0 && at(el(labels, "showVal"), "val") === "1";
|
|
3755
|
+
let format = null;
|
|
3756
|
+
for (const host of [labels, descendants(doc, "valAx")[0]]) {
|
|
3757
|
+
const numFmt = host ? el(host, "numFmt") : void 0;
|
|
3758
|
+
if (numFmt && at(numFmt, "sourceLinked") === "0" && (at(numFmt, "formatCode") ?? "") !== "") {
|
|
3759
|
+
format = at(numFmt, "formatCode");
|
|
3760
|
+
break;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
let title = null;
|
|
3764
|
+
const titleEl = descendants(doc, "title")[0];
|
|
3765
|
+
if (titleEl) {
|
|
3766
|
+
const text2 = descendants(titleEl, "t").map((t) => t.text).join("");
|
|
3767
|
+
title = text2 !== "" ? text2 : null;
|
|
3768
|
+
}
|
|
3769
|
+
return {
|
|
3770
|
+
type,
|
|
3771
|
+
title,
|
|
3772
|
+
categories,
|
|
3773
|
+
series,
|
|
3774
|
+
from,
|
|
3775
|
+
to,
|
|
3776
|
+
legend: descendants(doc, "legend").length > 0 ? true : null,
|
|
3777
|
+
dataLabels: dataLabels2,
|
|
3778
|
+
format
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
};
|
|
3782
|
+
function marker(anchor, tag) {
|
|
3783
|
+
const m = el(anchor, tag);
|
|
3784
|
+
if (!m) return null;
|
|
3785
|
+
const col = descendants(m, "col")[0]?.text.trim() ?? "";
|
|
3786
|
+
const row = descendants(m, "row")[0]?.text.trim() ?? "";
|
|
3787
|
+
return /^\d+$/.test(col) && /^\d+$/.test(row) ? [parseInt(col, 10), parseInt(row, 10)] : null;
|
|
3788
|
+
}
|
|
3789
|
+
function formula(ser, name) {
|
|
3790
|
+
const host = el(ser, name);
|
|
3791
|
+
if (!host) return null;
|
|
3792
|
+
for (const ref of ["numRef", "strRef"]) {
|
|
3793
|
+
const f = el(el(host, ref), "f");
|
|
3794
|
+
if (f && f.text.trim() !== "") return f.text.trim();
|
|
3795
|
+
}
|
|
3796
|
+
return null;
|
|
3797
|
+
}
|
|
3798
|
+
function seriesName(ser) {
|
|
3799
|
+
const tx = el(ser, "tx");
|
|
3800
|
+
if (!tx) return null;
|
|
3801
|
+
const v = el(tx, "v");
|
|
3802
|
+
if (v) return v.text;
|
|
3803
|
+
const cached = descendants(tx, "v")[0];
|
|
3804
|
+
return cached ? cached.text : null;
|
|
3805
|
+
}
|
|
3806
|
+
function points(ser) {
|
|
3807
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
3808
|
+
for (const dPt of els(ser, "dPt")) {
|
|
3809
|
+
const idx = at(el(dPt, "idx"), "val");
|
|
3810
|
+
const clr = descendants(dPt, "srgbClr")[0];
|
|
3811
|
+
const color2 = (clr ? at(clr, "val") ?? "" : "").toUpperCase();
|
|
3812
|
+
if (idx !== void 0 && /^[0-9]+$/.test(idx) && /^[0-9A-F]{6}$/.test(color2)) {
|
|
3813
|
+
byIndex.set(parseInt(idx, 10), color2);
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index, color2]) => ({ index, color: color2 }));
|
|
3817
|
+
}
|
|
3818
|
+
function seriesColor(ser) {
|
|
3819
|
+
const spPr = el(ser, "spPr");
|
|
3820
|
+
if (!spPr) return null;
|
|
3821
|
+
const clr = descendants(spPr, "srgbClr")[0];
|
|
3822
|
+
const val = (clr ? at(clr, "val") ?? "" : "").toUpperCase();
|
|
3823
|
+
return /^[0-9A-F]{6}$/.test(val) ? val : null;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3388
3826
|
// src/reader/comments-parser.ts
|
|
3389
3827
|
var CommentsParser = {
|
|
3390
3828
|
parse(commentsXml) {
|
|
@@ -3572,10 +4010,11 @@ function parsePrint(xml) {
|
|
|
3572
4010
|
const o = at(setup, "orientation");
|
|
3573
4011
|
const orientation = o === "portrait" || o === "landscape" ? o : null;
|
|
3574
4012
|
const fitToWidth = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && ["0", ""].includes(at(setup, "fitToHeight") ?? "");
|
|
4013
|
+
const fitToPageOnly = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && at(setup, "fitToHeight") === "1";
|
|
3575
4014
|
const oddFooter = el(el(xml, "headerFooter"), "oddFooter");
|
|
3576
4015
|
const footer = oddFooter !== void 0 ? parseFooterCodes(oddFooter.text) : null;
|
|
3577
|
-
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
3578
|
-
return { orientation, fitToWidth, footer };
|
|
4016
|
+
if (orientation === null && !fitToWidth && !fitToPageOnly && footer === null) return null;
|
|
4017
|
+
return { orientation, fitToWidth, fitToPage: fitToPageOnly, footer };
|
|
3579
4018
|
}
|
|
3580
4019
|
function parseFooterCodes(codes) {
|
|
3581
4020
|
const slots = { left: "", center: "", right: "" };
|
|
@@ -3616,13 +4055,13 @@ function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
|
3616
4055
|
const type = c.attrs["t"] ?? "n";
|
|
3617
4056
|
const styleIdx = c.attrs["s"] !== void 0 ? parseInt(c.attrs["s"], 10) : 0;
|
|
3618
4057
|
const format = stylesIndex[styleIdx] ?? null;
|
|
3619
|
-
let
|
|
4058
|
+
let formula2 = null;
|
|
3620
4059
|
let cachedValue = null;
|
|
3621
4060
|
let value = null;
|
|
3622
4061
|
const f = el(c, "f");
|
|
3623
4062
|
const v = el(c, "v");
|
|
3624
4063
|
if (f) {
|
|
3625
|
-
|
|
4064
|
+
formula2 = f.text;
|
|
3626
4065
|
cachedValue = v ? coerceValue(v.text, type) : null;
|
|
3627
4066
|
} else if (type === "inlineStr" && el(c, "is") && el(el(c, "is"), "t")) {
|
|
3628
4067
|
value = el(el(c, "is"), "t").text;
|
|
@@ -3633,7 +4072,7 @@ function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
|
3633
4072
|
value = coerceValue(v.text, type);
|
|
3634
4073
|
}
|
|
3635
4074
|
const comment = comments[address] ?? null;
|
|
3636
|
-
cells.set(address, new Cell(address, value,
|
|
4075
|
+
cells.set(address, new Cell(address, value, formula2, format, comment, cachedValue));
|
|
3637
4076
|
}
|
|
3638
4077
|
}
|
|
3639
4078
|
return cells;
|
|
@@ -3724,7 +4163,26 @@ var XlsxReader = class {
|
|
|
3724
4163
|
if (commentsXml) comments = { ...comments, ...CommentsParser.parse(commentsXml) };
|
|
3725
4164
|
}
|
|
3726
4165
|
}
|
|
3727
|
-
|
|
4166
|
+
let sheet = WorksheetParser.parse(worksheetXml, name, stylesIndex, comments, sharedStrings);
|
|
4167
|
+
if (sheetRelsXml) {
|
|
4168
|
+
const charts = [];
|
|
4169
|
+
for (const dr of Object.values(RelsParser.byType(RelsParser.parse(sheetRelsXml), "/drawing"))) {
|
|
4170
|
+
const drawingPath = resolveRelativePath(sheetPath, dr.Target);
|
|
4171
|
+
const slash = drawingPath.lastIndexOf("/");
|
|
4172
|
+
const drawingXml = getText(drawingPath);
|
|
4173
|
+
const drawingRelsXml = getText(drawingPath.slice(0, slash) + "/_rels/" + drawingPath.slice(slash + 1) + ".rels");
|
|
4174
|
+
if (drawingXml === null || drawingRelsXml === null) continue;
|
|
4175
|
+
const drawingRels = RelsParser.parse(drawingRelsXml);
|
|
4176
|
+
for (const anchor of ChartParser.anchors(drawingXml)) {
|
|
4177
|
+
const target2 = drawingRels[anchor.rel]?.Target;
|
|
4178
|
+
const chartXml = typeof target2 === "string" ? getText(resolveRelativePath(drawingPath, target2)) : null;
|
|
4179
|
+
const chart = chartXml !== null ? ChartParser.chart(chartXml, name, anchor.from, anchor.to) : null;
|
|
4180
|
+
if (chart !== null) charts.push(chart);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
if (charts.length > 0) sheet = sheet.withCharts(charts);
|
|
4184
|
+
}
|
|
4185
|
+
sheets.push(sheet);
|
|
3728
4186
|
i++;
|
|
3729
4187
|
}
|
|
3730
4188
|
const meta = this.parseDocProps(getText("docProps/core.xml"));
|
|
@@ -3978,6 +4436,119 @@ function hexToArgb(hex2) {
|
|
|
3978
4436
|
return "FF000000";
|
|
3979
4437
|
}
|
|
3980
4438
|
|
|
4439
|
+
// src/writer/chart-xml.ts
|
|
4440
|
+
var NS_C = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
4441
|
+
var NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
4442
|
+
var NS_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
4443
|
+
var ChartXml = {
|
|
4444
|
+
drawing(charts, firstChartNum) {
|
|
4445
|
+
let anchors = "";
|
|
4446
|
+
charts.forEach((chart, k) => {
|
|
4447
|
+
const id = k + 2;
|
|
4448
|
+
const num = firstChartNum + k;
|
|
4449
|
+
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>`;
|
|
4450
|
+
});
|
|
4451
|
+
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>";
|
|
4452
|
+
},
|
|
4453
|
+
drawingRels(charts, firstChartNum) {
|
|
4454
|
+
let rels = "";
|
|
4455
|
+
charts.forEach((_, k) => {
|
|
4456
|
+
rels += `<Relationship Id="rId${k + 1}" Type="${NS_R}/chart" Target="../charts/chart${firstChartNum + k}.xml"/>`;
|
|
4457
|
+
});
|
|
4458
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
4459
|
+
},
|
|
4460
|
+
chart(chart, workbook) {
|
|
4461
|
+
const type = chart.type;
|
|
4462
|
+
let series = "";
|
|
4463
|
+
chart.series.forEach((s, i) => {
|
|
4464
|
+
series += seriesXml(i, s, chart, workbook);
|
|
4465
|
+
});
|
|
4466
|
+
const labels = chart.dataLabels ? dataLabels(type, chart.format) : "";
|
|
4467
|
+
let plot;
|
|
4468
|
+
if (type === "line") {
|
|
4469
|
+
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>';
|
|
4470
|
+
} else if (type === "pie") {
|
|
4471
|
+
plot = '<c:pieChart><c:varyColors val="1"/>' + series + labels + '<c:firstSliceAng val="0"/></c:pieChart>';
|
|
4472
|
+
} else {
|
|
4473
|
+
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>';
|
|
4474
|
+
}
|
|
4475
|
+
const axesXml = type === "pie" ? "" : axes(type === "bar", chart.format);
|
|
4476
|
+
const legend = chart.legend ?? (chart.series.length > 1 || type === "pie");
|
|
4477
|
+
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"/>';
|
|
4478
|
+
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>";
|
|
4479
|
+
}
|
|
4480
|
+
};
|
|
4481
|
+
function marker2(tag, at2) {
|
|
4482
|
+
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}>`;
|
|
4483
|
+
}
|
|
4484
|
+
function seriesXml(i, s, chart, workbook) {
|
|
4485
|
+
const type = chart.type;
|
|
4486
|
+
const name = s.name !== null ? `<c:tx><c:v>${text(s.name)}</c:v></c:tx>` : "";
|
|
4487
|
+
let fill = "";
|
|
4488
|
+
if (s.color !== null && type !== "pie") {
|
|
4489
|
+
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>`;
|
|
4490
|
+
}
|
|
4491
|
+
const invert = type === "column" || type === "bar" ? '<c:invertIfNegative val="0"/>' : "";
|
|
4492
|
+
const mark = type === "line" ? '<c:marker><c:symbol val="circle"/><c:size val="6"/></c:marker>' : "";
|
|
4493
|
+
const cat = chart.categories !== null ? "<c:cat>" + strRef(chart.categories, workbook) + "</c:cat>" : "";
|
|
4494
|
+
const smooth = type === "line" ? '<c:smooth val="0"/>' : "";
|
|
4495
|
+
let points2 = "";
|
|
4496
|
+
for (const p of s.points) {
|
|
4497
|
+
const solid = `<a:solidFill><a:srgbClr val="${p.color}"/></a:solidFill>`;
|
|
4498
|
+
points2 += `<c:dPt><c:idx val="${p.index}"/>` + (type === "line" ? `<c:marker><c:symbol val="circle"/><c:size val="6"/><c:spPr>${solid}<a:ln>${solid}</a:ln></c:spPr></c:marker><c:bubble3D val="0"/>` : type === "pie" ? `<c:bubble3D val="0"/><c:spPr>${solid}</c:spPr>` : `<c:invertIfNegative val="0"/><c:bubble3D val="0"/><c:spPr>${solid}</c:spPr>`) + "</c:dPt>";
|
|
4499
|
+
}
|
|
4500
|
+
return `<c:ser><c:idx val="${i}"/><c:order val="${i}"/>` + name + fill + invert + mark + points2 + cat + "<c:val>" + numRef(s.values, workbook) + "</c:val>" + smooth + "</c:ser>";
|
|
4501
|
+
}
|
|
4502
|
+
function strRef(range, workbook) {
|
|
4503
|
+
let pts = "";
|
|
4504
|
+
const vals = values(range, workbook);
|
|
4505
|
+
vals.forEach((v, idx) => {
|
|
4506
|
+
if (v !== null) pts += `<c:pt idx="${idx}"><c:v>${text(category(v))}</c:v></c:pt>`;
|
|
4507
|
+
});
|
|
4508
|
+
return `<c:strRef><c:f>${text(range.formula())}</c:f><c:strCache><c:ptCount val="${vals.length}"/>` + pts + "</c:strCache></c:strRef>";
|
|
4509
|
+
}
|
|
4510
|
+
function numRef(range, workbook) {
|
|
4511
|
+
let pts = "";
|
|
4512
|
+
const vals = values(range, workbook);
|
|
4513
|
+
vals.forEach((v, idx) => {
|
|
4514
|
+
if (typeof v === "number") pts += `<c:pt idx="${idx}"><c:v>${formatFloat(v)}</c:v></c:pt>`;
|
|
4515
|
+
});
|
|
4516
|
+
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>";
|
|
4517
|
+
}
|
|
4518
|
+
function values(range, workbook) {
|
|
4519
|
+
const sheet = workbook.sheets.find((s) => s.name === range.sheet);
|
|
4520
|
+
return range.addresses().map((address) => {
|
|
4521
|
+
const cell = sheet?.cells.get(address);
|
|
4522
|
+
if (cell === void 0) return null;
|
|
4523
|
+
const v = cell.formula !== null ? cell.cachedValue : cell.value;
|
|
4524
|
+
return v === void 0 ? null : v;
|
|
4525
|
+
});
|
|
4526
|
+
}
|
|
4527
|
+
function category(v) {
|
|
4528
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
4529
|
+
if (typeof v === "number") return formatFloat(v);
|
|
4530
|
+
return v;
|
|
4531
|
+
}
|
|
4532
|
+
function dataLabels(type, format) {
|
|
4533
|
+
const pos = type === "line" ? "t" : type === "pie" ? "bestFit" : "outEnd";
|
|
4534
|
+
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>`;
|
|
4535
|
+
}
|
|
4536
|
+
function axes(horizontal, format) {
|
|
4537
|
+
const catPos = horizontal ? "l" : "b";
|
|
4538
|
+
const valPos = horizontal ? "b" : "l";
|
|
4539
|
+
const numFmt = format !== null ? `<c:numFmt formatCode="${attr2(format)}" sourceLinked="0"/>` : '<c:numFmt formatCode="General" sourceLinked="1"/>';
|
|
4540
|
+
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>`;
|
|
4541
|
+
}
|
|
4542
|
+
function textProps(color2, size) {
|
|
4543
|
+
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>`;
|
|
4544
|
+
}
|
|
4545
|
+
function text(s) {
|
|
4546
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4547
|
+
}
|
|
4548
|
+
function attr2(s) {
|
|
4549
|
+
return text(s).replace(/"/g, """).replace(/'/g, "'");
|
|
4550
|
+
}
|
|
4551
|
+
|
|
3981
4552
|
// src/writer/xlsx-writer.ts
|
|
3982
4553
|
var encoder2 = new TextEncoder();
|
|
3983
4554
|
function footerCodes(footer) {
|
|
@@ -3989,6 +4560,9 @@ function footerCodes(footer) {
|
|
|
3989
4560
|
}
|
|
3990
4561
|
return codes;
|
|
3991
4562
|
}
|
|
4563
|
+
function drawingRelId(comments) {
|
|
4564
|
+
return comments ? "rId3" : "rId1";
|
|
4565
|
+
}
|
|
3992
4566
|
function decimal(value) {
|
|
3993
4567
|
const out = value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
3994
4568
|
return out === "" || out === "-0" ? "0" : out;
|
|
@@ -4012,14 +4586,29 @@ var XlsxWriter = class {
|
|
|
4012
4586
|
add("xl/styles.xml", styles.toXml());
|
|
4013
4587
|
add("docProps/core.xml", this.coreXml(workbook));
|
|
4014
4588
|
add("docProps/app.xml", this.appXml());
|
|
4589
|
+
const firstChart = [];
|
|
4590
|
+
let next = 1;
|
|
4591
|
+
workbook.sheets.forEach((sheet, i) => {
|
|
4592
|
+
firstChart[i] = next;
|
|
4593
|
+
next += sheet.charts.length;
|
|
4594
|
+
});
|
|
4015
4595
|
workbook.sheets.forEach((sheet, i) => {
|
|
4016
4596
|
const n = i + 1;
|
|
4017
4597
|
add(`xl/worksheets/sheet${n}.xml`, sheetXmls[i]);
|
|
4598
|
+
if (sheet.hasComments() || sheet.charts.length > 0) {
|
|
4599
|
+
add(`xl/worksheets/_rels/sheet${n}.xml.rels`, this.sheetRelsXml(n, sheet.hasComments(), sheet.charts.length > 0));
|
|
4600
|
+
}
|
|
4018
4601
|
if (sheet.hasComments()) {
|
|
4019
|
-
add(`xl/worksheets/_rels/sheet${n}.xml.rels`, this.sheetRelsXml(n));
|
|
4020
4602
|
add(`xl/comments${n}.xml`, this.commentsXml(sheet));
|
|
4021
4603
|
add(`xl/drawings/vmlDrawing${n}.vml`, this.vmlDrawingXml(sheet));
|
|
4022
4604
|
}
|
|
4605
|
+
if (sheet.charts.length > 0) {
|
|
4606
|
+
add(`xl/drawings/drawing${n}.xml`, ChartXml.drawing(sheet.charts, firstChart[i]));
|
|
4607
|
+
add(`xl/drawings/_rels/drawing${n}.xml.rels`, ChartXml.drawingRels(sheet.charts, firstChart[i]));
|
|
4608
|
+
sheet.charts.forEach((chart, k) => {
|
|
4609
|
+
add(`xl/charts/chart${firstChart[i] + k}.xml`, ChartXml.chart(chart, workbook));
|
|
4610
|
+
});
|
|
4611
|
+
}
|
|
4023
4612
|
});
|
|
4024
4613
|
return zipSync(files);
|
|
4025
4614
|
}
|
|
@@ -4033,6 +4622,16 @@ var XlsxWriter = class {
|
|
|
4033
4622
|
const n = i + 1;
|
|
4034
4623
|
overrides += `<Override PartName="/xl/comments${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>`;
|
|
4035
4624
|
}
|
|
4625
|
+
let chartNum = 1;
|
|
4626
|
+
workbook.sheets.forEach((sheet, i) => {
|
|
4627
|
+
if (sheet.charts.length === 0) return;
|
|
4628
|
+
const n = i + 1;
|
|
4629
|
+
overrides += `<Override PartName="/xl/drawings/drawing${n}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/>`;
|
|
4630
|
+
for (let k = 0; k < sheet.charts.length; k++) {
|
|
4631
|
+
overrides += `<Override PartName="/xl/charts/chart${chartNum}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>`;
|
|
4632
|
+
chartNum++;
|
|
4633
|
+
}
|
|
4634
|
+
});
|
|
4036
4635
|
let defaults = '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/>';
|
|
4037
4636
|
if (sheetsWithComments.length > 0) {
|
|
4038
4637
|
defaults += '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>';
|
|
@@ -4061,8 +4660,16 @@ var XlsxWriter = class {
|
|
|
4061
4660
|
rels += `<Relationship Id="rId${workbook.sheets.length + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
|
|
4062
4661
|
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
4063
4662
|
}
|
|
4064
|
-
|
|
4065
|
-
|
|
4663
|
+
/** Comments keep rId1 (vml) and rId2 (comments); the chart drawing takes the next id. */
|
|
4664
|
+
sheetRelsXml(sheetNum, comments, charts) {
|
|
4665
|
+
let rels = "";
|
|
4666
|
+
if (comments) {
|
|
4667
|
+
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"/>`;
|
|
4668
|
+
}
|
|
4669
|
+
if (charts) {
|
|
4670
|
+
rels += `<Relationship Id="${drawingRelId(comments)}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" Target="../drawings/drawing${sheetNum}.xml"/>`;
|
|
4671
|
+
}
|
|
4672
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rels + "</Relationships>";
|
|
4066
4673
|
}
|
|
4067
4674
|
sheetXml(sheet, styles) {
|
|
4068
4675
|
let sheetViewsXml = "";
|
|
@@ -4104,13 +4711,15 @@ var XlsxWriter = class {
|
|
|
4104
4711
|
}
|
|
4105
4712
|
const legacyDrawing = sheet.hasComments() ? '<legacyDrawing r:id="rId1"/>' : "";
|
|
4106
4713
|
const print = sheet.print;
|
|
4107
|
-
const
|
|
4714
|
+
const fits = (print?.fitToWidth ?? false) || (print?.fitToPage ?? false);
|
|
4715
|
+
const sheetPrXml = fits ? '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr>' : "";
|
|
4108
4716
|
let pageSetupXml = "";
|
|
4109
|
-
if (print !== null && (print.orientation !== null ||
|
|
4110
|
-
pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") +
|
|
4717
|
+
if (print !== null && (print.orientation !== null || fits)) {
|
|
4718
|
+
pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + // fitToPage: one page wide AND tall. fitToWidth: one wide, as tall as it needs.
|
|
4719
|
+
(print.fitToPage ? ' fitToWidth="1" fitToHeight="1"' : print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
|
|
4111
4720
|
}
|
|
4112
4721
|
const headerFooterXml = print?.footer ? `<headerFooter><oddFooter>${this.escape(footerCodes(print.footer))}</oddFooter></headerFooter>` : "";
|
|
4113
|
-
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + sheetPrXml + sheetViewsXml + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + pageSetupXml + headerFooterXml + legacyDrawing + "</worksheet>";
|
|
4722
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + sheetPrXml + sheetViewsXml + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + pageSetupXml + headerFooterXml + (sheet.charts.length > 0 ? `<drawing r:id="${drawingRelId(sheet.hasComments())}"/>` : "") + legacyDrawing + "</worksheet>";
|
|
4114
4723
|
}
|
|
4115
4724
|
cellXml(cell, styles) {
|
|
4116
4725
|
const ref = cell.address;
|
|
@@ -4150,8 +4759,8 @@ var XlsxWriter = class {
|
|
|
4150
4759
|
const author = entry.comment.author ?? "Author";
|
|
4151
4760
|
if (!authors.includes(author)) authors.push(author);
|
|
4152
4761
|
const authorIdx = authors.indexOf(author);
|
|
4153
|
-
const
|
|
4154
|
-
commentList += `<comment ref="${entry.address}" authorId="${authorIdx}"><text><r><t xml:space="preserve">${
|
|
4762
|
+
const text2 = this.escape(entry.comment.text);
|
|
4763
|
+
commentList += `<comment ref="${entry.address}" authorId="${authorIdx}"><text><r><t xml:space="preserve">${text2}</t></r></text></comment>`;
|
|
4155
4764
|
}
|
|
4156
4765
|
let authorsXml = "<authors>";
|
|
4157
4766
|
for (const a of authors) authorsXml += `<author>${this.escape(a)}</author>`;
|
|
@@ -5031,19 +5640,19 @@ function canonEntries(root) {
|
|
|
5031
5640
|
continue;
|
|
5032
5641
|
}
|
|
5033
5642
|
stack.pop();
|
|
5034
|
-
const
|
|
5643
|
+
const text2 = top.list ? "[" + top.parts.join(",") + "]" : "{" + top.parts.join(",") + "}";
|
|
5035
5644
|
const parent = stack[stack.length - 1];
|
|
5036
5645
|
if (parent === void 0) {
|
|
5037
|
-
return
|
|
5646
|
+
return text2;
|
|
5038
5647
|
}
|
|
5039
|
-
parent.parts.push(member(parent, top.key,
|
|
5648
|
+
parent.parts.push(member(parent, top.key, text2));
|
|
5040
5649
|
}
|
|
5041
5650
|
}
|
|
5042
5651
|
function entriesFor(value) {
|
|
5043
5652
|
return Array.isArray(value) ? Array.from(value, (item, i) => [String(i), item]) : entriesOf(value);
|
|
5044
5653
|
}
|
|
5045
|
-
function member(frame, key,
|
|
5046
|
-
return frame.list ?
|
|
5654
|
+
function member(frame, key, text2) {
|
|
5655
|
+
return frame.list ? text2 : quote(key) + ":" + text2;
|
|
5047
5656
|
}
|
|
5048
5657
|
function scalar(value) {
|
|
5049
5658
|
switch (typeof value) {
|
|
@@ -5061,11 +5670,11 @@ function scalar(value) {
|
|
|
5061
5670
|
return JSON.stringify(value) ?? "null";
|
|
5062
5671
|
}
|
|
5063
5672
|
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
5064
|
-
function quote(
|
|
5065
|
-
if (LONE_SURROGATE.test(
|
|
5673
|
+
function quote(text2) {
|
|
5674
|
+
if (LONE_SURROGATE.test(text2)) {
|
|
5066
5675
|
throw unencodable("Single unpaired UTF-16 surrogate");
|
|
5067
5676
|
}
|
|
5068
|
-
return JSON.stringify(
|
|
5677
|
+
return JSON.stringify(text2);
|
|
5069
5678
|
}
|
|
5070
5679
|
function unencodable(reason) {
|
|
5071
5680
|
return new TypeError(`[holy-sheet] cannot compare a value JSON cannot hold: ${reason}`);
|
|
@@ -5234,6 +5843,56 @@ var holy_sheet_schema_default = {
|
|
|
5234
5843
|
propertyNames: { pattern: "^[1-9][0-9]*$" },
|
|
5235
5844
|
additionalProperties: { type: "number", minimum: 0 }
|
|
5236
5845
|
},
|
|
5846
|
+
charts: {
|
|
5847
|
+
type: "array",
|
|
5848
|
+
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.",
|
|
5849
|
+
items: {
|
|
5850
|
+
type: "object",
|
|
5851
|
+
additionalProperties: false,
|
|
5852
|
+
required: ["type", "series"],
|
|
5853
|
+
properties: {
|
|
5854
|
+
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)." },
|
|
5855
|
+
title: { type: "string" },
|
|
5856
|
+
categories: { type: "string", description: `The labels along the axis (or the pie's slices), as a range: "A5:A7". Another sheet: "'Data'!A5:A7".` },
|
|
5857
|
+
series: {
|
|
5858
|
+
type: "array",
|
|
5859
|
+
minItems: 1,
|
|
5860
|
+
items: {
|
|
5861
|
+
type: "object",
|
|
5862
|
+
additionalProperties: false,
|
|
5863
|
+
required: ["values"],
|
|
5864
|
+
properties: {
|
|
5865
|
+
name: { type: "string", description: "Shown in the legend." },
|
|
5866
|
+
values: { type: "string", description: 'The numbers, as a range the same length as categories: "B5:B7".' },
|
|
5867
|
+
color: { type: "string", description: "#RRGGBB. Ignored for a pie, whose slices take a palette." },
|
|
5868
|
+
points: {
|
|
5869
|
+
type: "array",
|
|
5870
|
+
description: 'Colour single points to make the finding stand out: the one bar or slice the title is about. index is 0-based within values: [{"index": 2, "color": "#C2410C"}].',
|
|
5871
|
+
items: {
|
|
5872
|
+
type: "object",
|
|
5873
|
+
additionalProperties: false,
|
|
5874
|
+
required: ["index", "color"],
|
|
5875
|
+
properties: {
|
|
5876
|
+
index: { type: "integer", minimum: 0 },
|
|
5877
|
+
color: { type: "string", description: "#RRGGBB" }
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
},
|
|
5884
|
+
position: {
|
|
5885
|
+
type: "object",
|
|
5886
|
+
additionalProperties: false,
|
|
5887
|
+
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.',
|
|
5888
|
+
properties: { from: { type: "string" }, to: { type: "string" } }
|
|
5889
|
+
},
|
|
5890
|
+
dataLabels: { type: "boolean", description: "Print each value on the chart. Usually right for a board chart." },
|
|
5891
|
+
legend: { type: "boolean", description: "Defaults to on for several series or a pie, off for one series." },
|
|
5892
|
+
format: { type: "string", description: 'Excel number format for the value axis and labels: "$#,##0", "0.0%", "$#,##0,K".' }
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5895
|
+
},
|
|
5237
5896
|
print: {
|
|
5238
5897
|
type: "object",
|
|
5239
5898
|
additionalProperties: false,
|
|
@@ -5241,6 +5900,7 @@ var holy_sheet_schema_default = {
|
|
|
5241
5900
|
properties: {
|
|
5242
5901
|
orientation: { type: "string", enum: ["portrait", "landscape"] },
|
|
5243
5902
|
fitToWidth: { type: "boolean", description: "Scale the sheet to one page wide (as many pages tall as it needs)." },
|
|
5903
|
+
fitToPage: { type: "boolean", description: "Scale the sheet onto ONE page, wide and tall. Use it for a one-page summary with a chart, so the page break never cuts the chart." },
|
|
5244
5904
|
footer: {
|
|
5245
5905
|
type: "object",
|
|
5246
5906
|
additionalProperties: false,
|
|
@@ -5460,7 +6120,7 @@ var holy_sheet_schema_default = {
|
|
|
5460
6120
|
};
|
|
5461
6121
|
|
|
5462
6122
|
// src/agent.ts
|
|
5463
|
-
var VERSION = "2.
|
|
6123
|
+
var VERSION = "2.9.0";
|
|
5464
6124
|
function toU8(input) {
|
|
5465
6125
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
5466
6126
|
}
|