@particle-academy/holy-sheet 2.5.0 → 2.7.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 +560 -0
- package/dist/index.cjs +350 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -7
- package/dist/index.d.ts +30 -7
- package/dist/index.js +350 -67
- package/dist/index.js.map +1 -1
- package/package.json +67 -66
package/dist/index.js
CHANGED
|
@@ -416,13 +416,15 @@ var MergedRegion = class {
|
|
|
416
416
|
|
|
417
417
|
// src/workbook/sheet.ts
|
|
418
418
|
var Sheet = class {
|
|
419
|
-
constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0) {
|
|
419
|
+
constructor(name, cells = /* @__PURE__ */ new Map(), mergedRegions = [], columnWidths = /* @__PURE__ */ new Map(), frozenRows = 0, frozenCols = 0, rowHeights = /* @__PURE__ */ new Map(), print = null) {
|
|
420
420
|
this.name = name;
|
|
421
421
|
this.cells = cells;
|
|
422
422
|
this.mergedRegions = mergedRegions;
|
|
423
423
|
this.columnWidths = columnWidths;
|
|
424
424
|
this.frozenRows = frozenRows;
|
|
425
425
|
this.frozenCols = frozenCols;
|
|
426
|
+
this.rowHeights = rowHeights;
|
|
427
|
+
this.print = print;
|
|
426
428
|
}
|
|
427
429
|
/** rowNumber → (colLetter → Cell), sorted by ascending row number. */
|
|
428
430
|
rows() {
|
|
@@ -493,6 +495,54 @@ var DateConverter = {
|
|
|
493
495
|
}
|
|
494
496
|
};
|
|
495
497
|
|
|
498
|
+
// src/exceptions.ts
|
|
499
|
+
var SchemaException = class _SchemaException extends Error {
|
|
500
|
+
constructor(errors, message) {
|
|
501
|
+
super(message ?? summarize(errors));
|
|
502
|
+
this.name = "SchemaException";
|
|
503
|
+
this.errors = errors;
|
|
504
|
+
Object.setPrototypeOf(this, _SchemaException.prototype);
|
|
505
|
+
}
|
|
506
|
+
static fromErrors(errors) {
|
|
507
|
+
return new _SchemaException(errors);
|
|
508
|
+
}
|
|
509
|
+
getErrors() {
|
|
510
|
+
return this.errors;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
function summarize(errors) {
|
|
514
|
+
if (errors.length === 1) {
|
|
515
|
+
const e = errors[0];
|
|
516
|
+
return `[holy-sheet] schema invalid at ${e.path}: expected ${e.expected}, got ${e.got}`;
|
|
517
|
+
}
|
|
518
|
+
const first2 = errors[0];
|
|
519
|
+
const rest = errors.length - 1;
|
|
520
|
+
return `[holy-sheet] schema invalid at ${first2.path}: expected ${first2.expected}, got ${first2.got} (+${rest} more)`;
|
|
521
|
+
}
|
|
522
|
+
var UnsupportedFormatException = class _UnsupportedFormatException extends Error {
|
|
523
|
+
constructor(message, path = null, mimetype = null) {
|
|
524
|
+
super(message);
|
|
525
|
+
this.name = "UnsupportedFormatException";
|
|
526
|
+
this.path = path;
|
|
527
|
+
this.mimetype = mimetype;
|
|
528
|
+
Object.setPrototypeOf(this, _UnsupportedFormatException.prototype);
|
|
529
|
+
}
|
|
530
|
+
static notAZip(path = null) {
|
|
531
|
+
return new _UnsupportedFormatException(
|
|
532
|
+
`[holy-sheet] cannot read ${path ?? "this file"}: it is not a zip archive, and xlsx and ods both are`,
|
|
533
|
+
path
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
static unknownPackage(path, mimetype) {
|
|
537
|
+
const what = mimetype !== null && mimetype !== "" ? `its mimetype is ${mimetype}` : "it has neither xl/workbook.xml nor an OpenDocument mimetype";
|
|
538
|
+
return new _UnsupportedFormatException(
|
|
539
|
+
`[holy-sheet] cannot read ${path ?? "this file"}: ${what}. Supported: xlsx, ods`,
|
|
540
|
+
path,
|
|
541
|
+
mimetype !== "" ? mimetype : null
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
496
546
|
// src/schema/theme.ts
|
|
497
547
|
var Theme = class {
|
|
498
548
|
constructor(key) {
|
|
@@ -570,7 +620,7 @@ var Normalizer = class {
|
|
|
570
620
|
const columnFormat = columnFormats.get(c) ?? null;
|
|
571
621
|
const rowBand = theme.dataFormat(r);
|
|
572
622
|
const merged = rowBand !== null ? columnFormat ? columnFormat.mergeWith(rowBand) : rowBand : columnFormat;
|
|
573
|
-
cells.set(address, this.buildCell(address, value, merged));
|
|
623
|
+
cells.set(address, this.buildCell(address, value, merged, name));
|
|
574
624
|
});
|
|
575
625
|
});
|
|
576
626
|
if (sheet.totals && isPlainObject(sheet.totals) && Object.keys(sheet.totals).length > 0 && rows.length > 0 && columns.length > 0) {
|
|
@@ -594,7 +644,7 @@ var Normalizer = class {
|
|
|
594
644
|
}
|
|
595
645
|
}
|
|
596
646
|
if (sheet.cells != null) {
|
|
597
|
-
for (const [address, cell] of this.normalizeCellMap(sheet.cells)) {
|
|
647
|
+
for (const [address, cell] of this.normalizeCellMap(sheet.cells, name)) {
|
|
598
648
|
const beneath = cells.get(address);
|
|
599
649
|
cells.set(
|
|
600
650
|
address,
|
|
@@ -615,17 +665,23 @@ var Normalizer = class {
|
|
|
615
665
|
this.normalizeMerges(sheet.mergedRegions ?? []),
|
|
616
666
|
this.normalizeWidths(sheet),
|
|
617
667
|
toInt(sheet.frozenRows ?? 0),
|
|
618
|
-
toInt(sheet.frozenCols ?? 0)
|
|
668
|
+
toInt(sheet.frozenCols ?? 0),
|
|
669
|
+
normalizeRowHeights(sheet.rowHeights),
|
|
670
|
+
normalizePrint(sheet.print)
|
|
619
671
|
);
|
|
620
672
|
}
|
|
621
|
-
normalizeCellMap(map) {
|
|
673
|
+
normalizeCellMap(map, sheetName2) {
|
|
622
674
|
const cells = /* @__PURE__ */ new Map();
|
|
623
675
|
for (const [address, cellData] of Object.entries(map)) {
|
|
624
|
-
if (!
|
|
676
|
+
if (!isCellSpec(cellData)) {
|
|
625
677
|
const [bare, formula] = promoteFormula(cellData);
|
|
626
678
|
cells.set(
|
|
627
679
|
address,
|
|
628
|
-
new Cell(
|
|
680
|
+
new Cell(
|
|
681
|
+
address,
|
|
682
|
+
formula !== null ? null : this.coerceValue(bare, null, address, sheetName2),
|
|
683
|
+
formula
|
|
684
|
+
)
|
|
629
685
|
);
|
|
630
686
|
continue;
|
|
631
687
|
}
|
|
@@ -640,7 +696,7 @@ var Normalizer = class {
|
|
|
640
696
|
address,
|
|
641
697
|
new Cell(
|
|
642
698
|
address,
|
|
643
|
-
this.coerceValue(rawValue, format),
|
|
699
|
+
this.coerceValue(rawValue, format, address, sheetName2),
|
|
644
700
|
cellData?.formula ?? null,
|
|
645
701
|
format,
|
|
646
702
|
comment,
|
|
@@ -721,8 +777,8 @@ var Normalizer = class {
|
|
|
721
777
|
return null;
|
|
722
778
|
}
|
|
723
779
|
}
|
|
724
|
-
buildCell(address, value, columnFormat) {
|
|
725
|
-
if (
|
|
780
|
+
buildCell(address, value, columnFormat, sheetName2) {
|
|
781
|
+
if (isCellSpec(value)) {
|
|
726
782
|
const cellFormat = isPlainObject(value.format) ? CellFormat.fromInput(value.format) : null;
|
|
727
783
|
const merged = columnFormat ? columnFormat.mergeWith(cellFormat) : cellFormat;
|
|
728
784
|
const comment = isPlainObject(value.comment) ? new CellComment(
|
|
@@ -733,7 +789,7 @@ var Normalizer = class {
|
|
|
733
789
|
const rawValue = value.value ?? null;
|
|
734
790
|
return new Cell(
|
|
735
791
|
address,
|
|
736
|
-
this.coerceValue(rawValue, merged),
|
|
792
|
+
this.coerceValue(rawValue, merged, address, sheetName2),
|
|
737
793
|
value.formula ?? null,
|
|
738
794
|
merged,
|
|
739
795
|
comment,
|
|
@@ -743,12 +799,12 @@ var Normalizer = class {
|
|
|
743
799
|
const [bare, formula] = promoteFormula(value);
|
|
744
800
|
return new Cell(
|
|
745
801
|
address,
|
|
746
|
-
formula !== null ? null : this.coerceValue(bare, columnFormat),
|
|
802
|
+
formula !== null ? null : this.coerceValue(bare, columnFormat, address, sheetName2),
|
|
747
803
|
formula,
|
|
748
804
|
columnFormat
|
|
749
805
|
);
|
|
750
806
|
}
|
|
751
|
-
coerceValue(value, format) {
|
|
807
|
+
coerceValue(value, format, address = "?", sheetName2) {
|
|
752
808
|
if (value === null || value === void 0) return null;
|
|
753
809
|
const df = format?.displayFormat;
|
|
754
810
|
if (df === "date" || df === "datetime") {
|
|
@@ -762,12 +818,36 @@ var Normalizer = class {
|
|
|
762
818
|
if (value instanceof Date) {
|
|
763
819
|
return DateConverter.toSerial(value, true);
|
|
764
820
|
}
|
|
765
|
-
if (typeof value
|
|
821
|
+
if (typeof value === "bigint") {
|
|
822
|
+
return String(value);
|
|
823
|
+
}
|
|
824
|
+
if (typeof value === "object" && !Array.isArray(value) && hasOwnToString(value)) {
|
|
766
825
|
return String(value);
|
|
767
826
|
}
|
|
827
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
828
|
+
const where = sheetName2 === void 0 ? address : `${sheetName2}!${address}`;
|
|
829
|
+
const got = typeOf(value);
|
|
830
|
+
throw SchemaException.fromErrors([
|
|
831
|
+
{
|
|
832
|
+
path: where,
|
|
833
|
+
expected: "a string, number, bool, null, Date, or an object with its own toString",
|
|
834
|
+
got,
|
|
835
|
+
value,
|
|
836
|
+
hint: Array.isArray(value) ? 'This cell holds an array. The usual cause is a `rows` entry nested one level too deep: `rows: [[["x"]]]` makes cell A1 an array, where `rows: [["x"]]` makes it the string "x". For a cell with formatting use the object form: {value: "x", format: {...}}.' : "Give the cell a scalar or a Date, or define toString() on the object so the conversion is yours rather than implicit."
|
|
837
|
+
}
|
|
838
|
+
]);
|
|
839
|
+
}
|
|
768
840
|
return value;
|
|
769
841
|
}
|
|
770
842
|
};
|
|
843
|
+
function isCellSpec(value) {
|
|
844
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
845
|
+
return Object.getPrototypeOf(value) === Object.prototype;
|
|
846
|
+
}
|
|
847
|
+
function hasOwnToString(value) {
|
|
848
|
+
const fn = value.toString;
|
|
849
|
+
return typeof fn === "function" && fn !== Object.prototype.toString;
|
|
850
|
+
}
|
|
771
851
|
function promoteFormula(value) {
|
|
772
852
|
if (typeof value === "string" && value.length > 1 && value[0] === "=") {
|
|
773
853
|
return [null, value.slice(1)];
|
|
@@ -778,6 +858,34 @@ function toInt(v) {
|
|
|
778
858
|
const n = Math.trunc(Number(v));
|
|
779
859
|
return Number.isFinite(n) ? n : 0;
|
|
780
860
|
}
|
|
861
|
+
function phpNumeric(v) {
|
|
862
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
863
|
+
return isNumericString(v) ? numericStringToNumber(v) : null;
|
|
864
|
+
}
|
|
865
|
+
function normalizeRowHeights(map) {
|
|
866
|
+
const out = /* @__PURE__ */ new Map();
|
|
867
|
+
if (!isPlainObject(map) && !Array.isArray(map)) return out;
|
|
868
|
+
for (const [row, px] of Object.entries(map)) {
|
|
869
|
+
const n = phpNumeric(px);
|
|
870
|
+
if (/^[1-9]\d{0,6}$/.test(row) && n !== null && n >= 0) out.set(parseInt(row, 10), n);
|
|
871
|
+
}
|
|
872
|
+
return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
|
|
873
|
+
}
|
|
874
|
+
function normalizePrint(print) {
|
|
875
|
+
if (!isPlainObject(print) && !Array.isArray(print)) return null;
|
|
876
|
+
const p = print;
|
|
877
|
+
const orientation = p.orientation === "portrait" || p.orientation === "landscape" ? p.orientation : null;
|
|
878
|
+
const fitToWidth = p.fitToWidth === true;
|
|
879
|
+
let footer = null;
|
|
880
|
+
if (isPlainObject(p.footer) || Array.isArray(p.footer)) {
|
|
881
|
+
const f = p.footer;
|
|
882
|
+
const slot = (k) => typeof f[k] === "string" ? f[k] : "";
|
|
883
|
+
footer = { left: slot("left"), center: slot("center"), right: slot("right") };
|
|
884
|
+
if (footer.left === "" && footer.center === "" && footer.right === "") footer = null;
|
|
885
|
+
}
|
|
886
|
+
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
887
|
+
return { orientation, fitToWidth, footer };
|
|
888
|
+
}
|
|
781
889
|
|
|
782
890
|
// src/schema/formula-linter.ts
|
|
783
891
|
var ERR_VALUE = "#VALUE!";
|
|
@@ -1299,54 +1407,6 @@ function phpRound(x, n) {
|
|
|
1299
1407
|
return Math.sign(x) * Math.round(Math.abs(x) * factor) / factor;
|
|
1300
1408
|
}
|
|
1301
1409
|
|
|
1302
|
-
// src/exceptions.ts
|
|
1303
|
-
var SchemaException = class _SchemaException extends Error {
|
|
1304
|
-
constructor(errors, message) {
|
|
1305
|
-
super(message ?? summarize(errors));
|
|
1306
|
-
this.name = "SchemaException";
|
|
1307
|
-
this.errors = errors;
|
|
1308
|
-
Object.setPrototypeOf(this, _SchemaException.prototype);
|
|
1309
|
-
}
|
|
1310
|
-
static fromErrors(errors) {
|
|
1311
|
-
return new _SchemaException(errors);
|
|
1312
|
-
}
|
|
1313
|
-
getErrors() {
|
|
1314
|
-
return this.errors;
|
|
1315
|
-
}
|
|
1316
|
-
};
|
|
1317
|
-
function summarize(errors) {
|
|
1318
|
-
if (errors.length === 1) {
|
|
1319
|
-
const e = errors[0];
|
|
1320
|
-
return `[holy-sheet] schema invalid at ${e.path}: expected ${e.expected}, got ${e.got}`;
|
|
1321
|
-
}
|
|
1322
|
-
const first2 = errors[0];
|
|
1323
|
-
const rest = errors.length - 1;
|
|
1324
|
-
return `[holy-sheet] schema invalid at ${first2.path}: expected ${first2.expected}, got ${first2.got} (+${rest} more)`;
|
|
1325
|
-
}
|
|
1326
|
-
var UnsupportedFormatException = class _UnsupportedFormatException extends Error {
|
|
1327
|
-
constructor(message, path = null, mimetype = null) {
|
|
1328
|
-
super(message);
|
|
1329
|
-
this.name = "UnsupportedFormatException";
|
|
1330
|
-
this.path = path;
|
|
1331
|
-
this.mimetype = mimetype;
|
|
1332
|
-
Object.setPrototypeOf(this, _UnsupportedFormatException.prototype);
|
|
1333
|
-
}
|
|
1334
|
-
static notAZip(path = null) {
|
|
1335
|
-
return new _UnsupportedFormatException(
|
|
1336
|
-
`[holy-sheet] cannot read ${path ?? "this file"}: it is not a zip archive, and xlsx and ods both are`,
|
|
1337
|
-
path
|
|
1338
|
-
);
|
|
1339
|
-
}
|
|
1340
|
-
static unknownPackage(path, mimetype) {
|
|
1341
|
-
const what = mimetype !== null && mimetype !== "" ? `its mimetype is ${mimetype}` : "it has neither xl/workbook.xml nor an OpenDocument mimetype";
|
|
1342
|
-
return new _UnsupportedFormatException(
|
|
1343
|
-
`[holy-sheet] cannot read ${path ?? "this file"}: ${what}. Supported: xlsx, ods`,
|
|
1344
|
-
path,
|
|
1345
|
-
mimetype !== "" ? mimetype : null
|
|
1346
|
-
);
|
|
1347
|
-
}
|
|
1348
|
-
};
|
|
1349
|
-
|
|
1350
1410
|
// src/schema/repairer.ts
|
|
1351
1411
|
var VALID_THEMES = ["default", "minimal", "plain", "business"];
|
|
1352
1412
|
var ISO_DATE2 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
@@ -1593,7 +1653,21 @@ var Validator = class {
|
|
|
1593
1653
|
"Each row is an array of cell values, in column order."
|
|
1594
1654
|
)
|
|
1595
1655
|
);
|
|
1656
|
+
return;
|
|
1596
1657
|
}
|
|
1658
|
+
row.forEach((cell, k) => {
|
|
1659
|
+
if (Array.isArray(cell)) {
|
|
1660
|
+
errors.push(
|
|
1661
|
+
error(
|
|
1662
|
+
`${path}.rows[${j}][${k}]`,
|
|
1663
|
+
"a cell value or a cell object",
|
|
1664
|
+
"array",
|
|
1665
|
+
cell,
|
|
1666
|
+
'This cell is an array, which usually means the row is nested one level too deep: `rows: [[["x"]]]` makes cell A1 an array, where `rows: [["x"]]` makes it "x". For a cell with formatting use the object form: {value: "x", format: {...}}.'
|
|
1667
|
+
)
|
|
1668
|
+
);
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1597
1671
|
});
|
|
1598
1672
|
}
|
|
1599
1673
|
}
|
|
@@ -1659,6 +1733,95 @@ var Validator = class {
|
|
|
1659
1733
|
}
|
|
1660
1734
|
}
|
|
1661
1735
|
}
|
|
1736
|
+
if (sheet.rowHeights != null) {
|
|
1737
|
+
if (!isPlainObject(sheet.rowHeights) && !Array.isArray(sheet.rowHeights)) {
|
|
1738
|
+
errors.push(
|
|
1739
|
+
error(
|
|
1740
|
+
`${path}.rowHeights`,
|
|
1741
|
+
"object keyed by 1-based row number",
|
|
1742
|
+
typeOf(sheet.rowHeights),
|
|
1743
|
+
sheet.rowHeights,
|
|
1744
|
+
'Row heights map a row NUMBER to pixels: {"1": 32, "4": 28}.'
|
|
1745
|
+
)
|
|
1746
|
+
);
|
|
1747
|
+
} else {
|
|
1748
|
+
for (const [row, px] of Object.entries(sheet.rowHeights)) {
|
|
1749
|
+
const n = typeof px === "number" ? px : isNumericString(px) ? numericStringToNumber(px) : null;
|
|
1750
|
+
if (!/^[1-9]\d{0,6}$/.test(row)) {
|
|
1751
|
+
errors.push(error(
|
|
1752
|
+
`${path}.rowHeights.${row}`,
|
|
1753
|
+
"a 1-based row number",
|
|
1754
|
+
typeOf(row),
|
|
1755
|
+
row,
|
|
1756
|
+
'Keys are row numbers as Excel shows them: "1" is the first row.'
|
|
1757
|
+
));
|
|
1758
|
+
} else if (n === null || !Number.isFinite(n) || n < 0) {
|
|
1759
|
+
errors.push(error(
|
|
1760
|
+
`${path}.rowHeights.${row}`,
|
|
1761
|
+
"a non-negative number of pixels",
|
|
1762
|
+
typeOf(px),
|
|
1763
|
+
px,
|
|
1764
|
+
"A height is a number of pixels, like 28 (20 is a default row)."
|
|
1765
|
+
));
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
if (sheet.print != null) {
|
|
1771
|
+
const print = sheet.print;
|
|
1772
|
+
if (!isPlainObject(print) && !Array.isArray(print)) {
|
|
1773
|
+
errors.push(error(
|
|
1774
|
+
`${path}.print`,
|
|
1775
|
+
"object {orientation?, fitToWidth?, footer?}",
|
|
1776
|
+
typeOf(print),
|
|
1777
|
+
print,
|
|
1778
|
+
'Print setup: {"orientation": "landscape", "fitToWidth": true, "footer": {"right": "Page {page} of {pages}"}}.'
|
|
1779
|
+
));
|
|
1780
|
+
} else {
|
|
1781
|
+
if (print.orientation != null && print.orientation !== "portrait" && print.orientation !== "landscape") {
|
|
1782
|
+
errors.push(error(
|
|
1783
|
+
`${path}.print.orientation`,
|
|
1784
|
+
"one of: portrait, landscape",
|
|
1785
|
+
typeOf(print.orientation),
|
|
1786
|
+
print.orientation,
|
|
1787
|
+
"Use landscape for a sheet wider than it is tall."
|
|
1788
|
+
));
|
|
1789
|
+
}
|
|
1790
|
+
if (print.fitToWidth != null && typeof print.fitToWidth !== "boolean") {
|
|
1791
|
+
errors.push(error(
|
|
1792
|
+
`${path}.print.fitToWidth`,
|
|
1793
|
+
"boolean",
|
|
1794
|
+
typeOf(print.fitToWidth),
|
|
1795
|
+
print.fitToWidth,
|
|
1796
|
+
"true scales the sheet to one page wide when printed."
|
|
1797
|
+
));
|
|
1798
|
+
}
|
|
1799
|
+
if (print.footer != null) {
|
|
1800
|
+
if (!isPlainObject(print.footer) && !Array.isArray(print.footer)) {
|
|
1801
|
+
errors.push(error(
|
|
1802
|
+
`${path}.print.footer`,
|
|
1803
|
+
"object {left?, center?, right?}",
|
|
1804
|
+
typeOf(print.footer),
|
|
1805
|
+
print.footer,
|
|
1806
|
+
"A footer has up to three slots of text."
|
|
1807
|
+
));
|
|
1808
|
+
} else {
|
|
1809
|
+
const footer = print.footer;
|
|
1810
|
+
for (const slot of ["left", "center", "right"]) {
|
|
1811
|
+
if (footer[slot] != null && typeof footer[slot] !== "string") {
|
|
1812
|
+
errors.push(error(
|
|
1813
|
+
`${path}.print.footer.${slot}`,
|
|
1814
|
+
"string",
|
|
1815
|
+
typeOf(footer[slot]),
|
|
1816
|
+
footer[slot],
|
|
1817
|
+
"Footer text; {page} and {pages} print the page number and count."
|
|
1818
|
+
));
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1662
1825
|
return errors;
|
|
1663
1826
|
}
|
|
1664
1827
|
validateColumn(col, path) {
|
|
@@ -2748,6 +2911,22 @@ function sheetToSchema(sheet) {
|
|
|
2748
2911
|
}
|
|
2749
2912
|
if (sheet.frozenRows > 0) out["frozenRows"] = sheet.frozenRows;
|
|
2750
2913
|
if (sheet.frozenCols > 0) out["frozenCols"] = sheet.frozenCols;
|
|
2914
|
+
if (sheet.rowHeights.size > 0) {
|
|
2915
|
+
const heights = {};
|
|
2916
|
+
for (const [k, v] of sheet.rowHeights) heights[k] = v;
|
|
2917
|
+
out["rowHeights"] = heights;
|
|
2918
|
+
}
|
|
2919
|
+
if (sheet.print !== null) {
|
|
2920
|
+
const print = {};
|
|
2921
|
+
if (sheet.print.orientation !== null) print["orientation"] = sheet.print.orientation;
|
|
2922
|
+
if (sheet.print.fitToWidth) print["fitToWidth"] = true;
|
|
2923
|
+
if (sheet.print.footer !== null) {
|
|
2924
|
+
const footer = {};
|
|
2925
|
+
for (const k of ["left", "center", "right"]) if (sheet.print.footer[k] !== "") footer[k] = sheet.print.footer[k];
|
|
2926
|
+
print["footer"] = footer;
|
|
2927
|
+
}
|
|
2928
|
+
out["print"] = print;
|
|
2929
|
+
}
|
|
2751
2930
|
return out;
|
|
2752
2931
|
}
|
|
2753
2932
|
function cellToSchema(cell) {
|
|
@@ -3370,9 +3549,62 @@ var WorksheetParser = {
|
|
|
3370
3549
|
const merges = parseMerges(xml);
|
|
3371
3550
|
const columnWidths = parseColumnWidths(xml);
|
|
3372
3551
|
const [frozenRows, frozenCols] = parseFrozen(xml);
|
|
3373
|
-
return new Sheet(name, cells, merges, columnWidths, frozenRows, frozenCols);
|
|
3552
|
+
return new Sheet(name, cells, merges, columnWidths, frozenRows, frozenCols, parseRowHeights(xml), parsePrint(xml));
|
|
3374
3553
|
}
|
|
3375
3554
|
};
|
|
3555
|
+
function parseRowHeights(xml) {
|
|
3556
|
+
const out = /* @__PURE__ */ new Map();
|
|
3557
|
+
const sheetData = el(xml, "sheetData");
|
|
3558
|
+
if (!sheetData) return out;
|
|
3559
|
+
for (const row of els(sheetData, "row")) {
|
|
3560
|
+
const r = parseInt(at(row, "r") ?? "0", 10);
|
|
3561
|
+
const custom = at(row, "customHeight");
|
|
3562
|
+
const ht = at(row, "ht");
|
|
3563
|
+
if (r > 0 && ht !== void 0 && (custom === "1" || custom === "true")) {
|
|
3564
|
+
out.set(r, Math.round(parseFloat(ht) / 0.75 * 100) / 100);
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
return out;
|
|
3568
|
+
}
|
|
3569
|
+
function parsePrint(xml) {
|
|
3570
|
+
const fitToPage = ["1", "true"].includes(at(el(el(xml, "sheetPr"), "pageSetUpPr"), "fitToPage") ?? "");
|
|
3571
|
+
const setup = el(xml, "pageSetup");
|
|
3572
|
+
const o = at(setup, "orientation");
|
|
3573
|
+
const orientation = o === "portrait" || o === "landscape" ? o : null;
|
|
3574
|
+
const fitToWidth = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && ["0", ""].includes(at(setup, "fitToHeight") ?? "");
|
|
3575
|
+
const oddFooter = el(el(xml, "headerFooter"), "oddFooter");
|
|
3576
|
+
const footer = oddFooter !== void 0 ? parseFooterCodes(oddFooter.text) : null;
|
|
3577
|
+
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
3578
|
+
return { orientation, fitToWidth, footer };
|
|
3579
|
+
}
|
|
3580
|
+
function parseFooterCodes(codes) {
|
|
3581
|
+
const slots = { left: "", center: "", right: "" };
|
|
3582
|
+
let current = "center";
|
|
3583
|
+
const len = codes.length;
|
|
3584
|
+
for (let i = 0; i < len; i++) {
|
|
3585
|
+
const ch = codes[i];
|
|
3586
|
+
if (ch !== "&" || i + 1 >= len) {
|
|
3587
|
+
slots[current] += ch;
|
|
3588
|
+
continue;
|
|
3589
|
+
}
|
|
3590
|
+
const next = codes[++i];
|
|
3591
|
+
if (next === "&") slots[current] += "&";
|
|
3592
|
+
else if (next === "L") current = "left";
|
|
3593
|
+
else if (next === "C") current = "center";
|
|
3594
|
+
else if (next === "R") current = "right";
|
|
3595
|
+
else if (next === "P") slots[current] += "{page}";
|
|
3596
|
+
else if (next === "N") slots[current] += "{pages}";
|
|
3597
|
+
else if (next === '"') {
|
|
3598
|
+
const close = codes.indexOf('"', i + 1);
|
|
3599
|
+
i = close === -1 ? len : close;
|
|
3600
|
+
} else if (/[0-9]/.test(next)) {
|
|
3601
|
+
while (i + 1 < len && /[0-9]/.test(codes[i + 1])) i++;
|
|
3602
|
+
} else if (next === "K") {
|
|
3603
|
+
i = Math.min(len - 1, i + 6);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
return slots.left === "" && slots.center === "" && slots.right === "" ? null : slots;
|
|
3607
|
+
}
|
|
3376
3608
|
function parseCells(xml, stylesIndex, comments, sharedStrings) {
|
|
3377
3609
|
const cells = /* @__PURE__ */ new Map();
|
|
3378
3610
|
const sheetData = el(xml, "sheetData");
|
|
@@ -3748,6 +3980,19 @@ function hexToArgb(hex2) {
|
|
|
3748
3980
|
|
|
3749
3981
|
// src/writer/xlsx-writer.ts
|
|
3750
3982
|
var encoder2 = new TextEncoder();
|
|
3983
|
+
function footerCodes(footer) {
|
|
3984
|
+
let codes = "";
|
|
3985
|
+
for (const [slot, code] of [["left", "&L"], ["center", "&C"], ["right", "&R"]]) {
|
|
3986
|
+
if (footer[slot] !== "") {
|
|
3987
|
+
codes += code + footer[slot].replace(/&/g, "&&").replace(/\{pages\}/g, "&N").replace(/\{page\}/g, "&P");
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
return codes;
|
|
3991
|
+
}
|
|
3992
|
+
function decimal(value) {
|
|
3993
|
+
const out = value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
3994
|
+
return out === "" || out === "-0" ? "0" : out;
|
|
3995
|
+
}
|
|
3751
3996
|
var XlsxWriter = class {
|
|
3752
3997
|
toBytes(workbook) {
|
|
3753
3998
|
const styles = new StylesRegistry();
|
|
@@ -3836,14 +4081,20 @@ var XlsxWriter = class {
|
|
|
3836
4081
|
}
|
|
3837
4082
|
colsXml += "</cols>";
|
|
3838
4083
|
}
|
|
4084
|
+
const rows = sheet.rows();
|
|
4085
|
+
for (const rowIndex of sheet.rowHeights.keys()) {
|
|
4086
|
+
if (!rows.has(rowIndex)) rows.set(rowIndex, /* @__PURE__ */ new Map());
|
|
4087
|
+
}
|
|
3839
4088
|
let rowsXml = "";
|
|
3840
|
-
for (const [rowIndex, row] of
|
|
4089
|
+
for (const [rowIndex, row] of [...rows.entries()].sort((a, b) => a[0] - b[0])) {
|
|
3841
4090
|
let cellsXml = "";
|
|
3842
4091
|
const sortedCols = [...row.keys()].sort();
|
|
3843
4092
|
for (const col of sortedCols) {
|
|
3844
4093
|
cellsXml += this.cellXml(row.get(col), styles);
|
|
3845
4094
|
}
|
|
3846
|
-
|
|
4095
|
+
const height = sheet.rowHeights.get(rowIndex);
|
|
4096
|
+
const ht = height !== void 0 ? ` ht="${decimal(height * 0.75)}" customHeight="1"` : "";
|
|
4097
|
+
rowsXml += cellsXml === "" ? `<row r="${rowIndex}"${ht}/>` : `<row r="${rowIndex}"${ht}>${cellsXml}</row>`;
|
|
3847
4098
|
}
|
|
3848
4099
|
let mergesXml = "";
|
|
3849
4100
|
if (sheet.mergedRegions.length > 0) {
|
|
@@ -3852,7 +4103,14 @@ var XlsxWriter = class {
|
|
|
3852
4103
|
mergesXml += "</mergeCells>";
|
|
3853
4104
|
}
|
|
3854
4105
|
const legacyDrawing = sheet.hasComments() ? '<legacyDrawing r:id="rId1"/>' : "";
|
|
3855
|
-
|
|
4106
|
+
const print = sheet.print;
|
|
4107
|
+
const sheetPrXml = print?.fitToWidth ? '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr>' : "";
|
|
4108
|
+
let pageSetupXml = "";
|
|
4109
|
+
if (print !== null && (print.orientation !== null || print.fitToWidth)) {
|
|
4110
|
+
pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + (print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
|
|
4111
|
+
}
|
|
4112
|
+
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>";
|
|
3856
4114
|
}
|
|
3857
4115
|
cellXml(cell, styles) {
|
|
3858
4116
|
const ref = cell.address;
|
|
@@ -4969,6 +5227,31 @@ var holy_sheet_schema_default = {
|
|
|
4969
5227
|
type: "integer",
|
|
4970
5228
|
minimum: 0,
|
|
4971
5229
|
description: "How many columns stay visible while the sheet scrolls sideways. Set to 1 when the first column is the label for the row."
|
|
5230
|
+
},
|
|
5231
|
+
rowHeights: {
|
|
5232
|
+
type: "object",
|
|
5233
|
+
description: 'Row heights in pixels, keyed by 1-based row NUMBER as Excel shows it: {"1": 32, "4": 28}. A default row is 20; give a title row or a header row room (28-36) instead of packing text against its fill.',
|
|
5234
|
+
propertyNames: { pattern: "^[1-9][0-9]*$" },
|
|
5235
|
+
additionalProperties: { type: "number", minimum: 0 }
|
|
5236
|
+
},
|
|
5237
|
+
print: {
|
|
5238
|
+
type: "object",
|
|
5239
|
+
additionalProperties: false,
|
|
5240
|
+
description: "How the sheet prints. A sheet wider than a page splits its columns across pages unless fitToWidth is set.",
|
|
5241
|
+
properties: {
|
|
5242
|
+
orientation: { type: "string", enum: ["portrait", "landscape"] },
|
|
5243
|
+
fitToWidth: { type: "boolean", description: "Scale the sheet to one page wide (as many pages tall as it needs)." },
|
|
5244
|
+
footer: {
|
|
5245
|
+
type: "object",
|
|
5246
|
+
additionalProperties: false,
|
|
5247
|
+
description: 'A printed footer: up to three slots. {page} and {pages} print the page number and count: {"left": "Q3 Revenue Review", "right": "Page {page} of {pages}"}.',
|
|
5248
|
+
properties: {
|
|
5249
|
+
left: { type: "string" },
|
|
5250
|
+
center: { type: "string" },
|
|
5251
|
+
right: { type: "string" }
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
4972
5255
|
}
|
|
4973
5256
|
}
|
|
4974
5257
|
},
|
|
@@ -5177,7 +5460,7 @@ var holy_sheet_schema_default = {
|
|
|
5177
5460
|
};
|
|
5178
5461
|
|
|
5179
5462
|
// src/agent.ts
|
|
5180
|
-
var VERSION = "2.
|
|
5463
|
+
var VERSION = "2.7.0";
|
|
5181
5464
|
function toU8(input) {
|
|
5182
5465
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
5183
5466
|
}
|