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