@particle-academy/holy-sheet 2.4.3 → 2.6.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 +550 -0
- package/README.md +3 -3
- package/dist/index.cjs +145 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +145 -62
- package/dist/index.js.map +1 -1
- package/package.json +67 -66
package/dist/index.cjs
CHANGED
|
@@ -495,6 +495,54 @@ var DateConverter = {
|
|
|
495
495
|
}
|
|
496
496
|
};
|
|
497
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
|
+
|
|
498
546
|
// src/schema/theme.ts
|
|
499
547
|
var Theme = class {
|
|
500
548
|
constructor(key) {
|
|
@@ -572,7 +620,7 @@ var Normalizer = class {
|
|
|
572
620
|
const columnFormat = columnFormats.get(c) ?? null;
|
|
573
621
|
const rowBand = theme.dataFormat(r);
|
|
574
622
|
const merged = rowBand !== null ? columnFormat ? columnFormat.mergeWith(rowBand) : rowBand : columnFormat;
|
|
575
|
-
cells.set(address, this.buildCell(address, value, merged));
|
|
623
|
+
cells.set(address, this.buildCell(address, value, merged, name));
|
|
576
624
|
});
|
|
577
625
|
});
|
|
578
626
|
if (sheet.totals && isPlainObject(sheet.totals) && Object.keys(sheet.totals).length > 0 && rows.length > 0 && columns.length > 0) {
|
|
@@ -596,7 +644,7 @@ var Normalizer = class {
|
|
|
596
644
|
}
|
|
597
645
|
}
|
|
598
646
|
if (sheet.cells != null) {
|
|
599
|
-
for (const [address, cell] of this.normalizeCellMap(sheet.cells)) {
|
|
647
|
+
for (const [address, cell] of this.normalizeCellMap(sheet.cells, name)) {
|
|
600
648
|
const beneath = cells.get(address);
|
|
601
649
|
cells.set(
|
|
602
650
|
address,
|
|
@@ -615,19 +663,23 @@ var Normalizer = class {
|
|
|
615
663
|
name,
|
|
616
664
|
cells,
|
|
617
665
|
this.normalizeMerges(sheet.mergedRegions ?? []),
|
|
618
|
-
this.
|
|
666
|
+
this.normalizeWidths(sheet),
|
|
619
667
|
toInt(sheet.frozenRows ?? 0),
|
|
620
668
|
toInt(sheet.frozenCols ?? 0)
|
|
621
669
|
);
|
|
622
670
|
}
|
|
623
|
-
normalizeCellMap(map) {
|
|
671
|
+
normalizeCellMap(map, sheetName2) {
|
|
624
672
|
const cells = /* @__PURE__ */ new Map();
|
|
625
673
|
for (const [address, cellData] of Object.entries(map)) {
|
|
626
|
-
if (!
|
|
674
|
+
if (!isCellSpec(cellData)) {
|
|
627
675
|
const [bare, formula] = promoteFormula(cellData);
|
|
628
676
|
cells.set(
|
|
629
677
|
address,
|
|
630
|
-
new Cell(
|
|
678
|
+
new Cell(
|
|
679
|
+
address,
|
|
680
|
+
formula !== null ? null : this.coerceValue(bare, null, address, sheetName2),
|
|
681
|
+
formula
|
|
682
|
+
)
|
|
631
683
|
);
|
|
632
684
|
continue;
|
|
633
685
|
}
|
|
@@ -642,7 +694,7 @@ var Normalizer = class {
|
|
|
642
694
|
address,
|
|
643
695
|
new Cell(
|
|
644
696
|
address,
|
|
645
|
-
this.coerceValue(rawValue, format),
|
|
697
|
+
this.coerceValue(rawValue, format, address, sheetName2),
|
|
646
698
|
cellData?.formula ?? null,
|
|
647
699
|
format,
|
|
648
700
|
comment,
|
|
@@ -661,6 +713,36 @@ var Normalizer = class {
|
|
|
661
713
|
}
|
|
662
714
|
return out;
|
|
663
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* The two ways a width can be stated, folded into one map.
|
|
718
|
+
*
|
|
719
|
+
* `columns[].width` widens the column at that POSITION; the sheet-level
|
|
720
|
+
* `columnWidths` map keys the same columns by 0-based index. Both have been
|
|
721
|
+
* described in the shared `skills/holy-sheet.schema.json` ("Same as
|
|
722
|
+
* columnWidths but per-column") for as long as the field has existed, and only
|
|
723
|
+
* the map was ever read — so a width written the way the schema documents it
|
|
724
|
+
* emitted no `<cols>` element at all, with no error and no repair note
|
|
725
|
+
* (holy-sheet#8, fixed in PHP 2.4.0; this is the twin).
|
|
726
|
+
*
|
|
727
|
+
* The MAP IS APPLIED LAST and wins, matching PHP. It is the mechanism that
|
|
728
|
+
* already worked, so anyone who reached for it to work around this bug must not
|
|
729
|
+
* now find a leftover `width` quietly overriding it.
|
|
730
|
+
*/
|
|
731
|
+
normalizeWidths(sheet) {
|
|
732
|
+
const out = /* @__PURE__ */ new Map();
|
|
733
|
+
for (const [colIdx, columnDef] of (sheet.columns ?? []).entries()) {
|
|
734
|
+
if (columnDef === null || typeof columnDef !== "object") continue;
|
|
735
|
+
if (columnDef.width === void 0) continue;
|
|
736
|
+
const index = ColumnWidths.index(colIdx);
|
|
737
|
+
const width = ColumnWidths.width(columnDef.width);
|
|
738
|
+
if (index === null || width === null) continue;
|
|
739
|
+
out.set(index, width);
|
|
740
|
+
}
|
|
741
|
+
for (const [index, width] of this.normalizeColumnWidths(sheet.columnWidths ?? {})) {
|
|
742
|
+
out.set(index, width);
|
|
743
|
+
}
|
|
744
|
+
return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
|
|
745
|
+
}
|
|
664
746
|
normalizeColumnWidths(widths) {
|
|
665
747
|
const out = /* @__PURE__ */ new Map();
|
|
666
748
|
for (const [key, px] of Object.entries(widths)) {
|
|
@@ -693,8 +775,8 @@ var Normalizer = class {
|
|
|
693
775
|
return null;
|
|
694
776
|
}
|
|
695
777
|
}
|
|
696
|
-
buildCell(address, value, columnFormat) {
|
|
697
|
-
if (
|
|
778
|
+
buildCell(address, value, columnFormat, sheetName2) {
|
|
779
|
+
if (isCellSpec(value)) {
|
|
698
780
|
const cellFormat = isPlainObject(value.format) ? CellFormat.fromInput(value.format) : null;
|
|
699
781
|
const merged = columnFormat ? columnFormat.mergeWith(cellFormat) : cellFormat;
|
|
700
782
|
const comment = isPlainObject(value.comment) ? new CellComment(
|
|
@@ -705,7 +787,7 @@ var Normalizer = class {
|
|
|
705
787
|
const rawValue = value.value ?? null;
|
|
706
788
|
return new Cell(
|
|
707
789
|
address,
|
|
708
|
-
this.coerceValue(rawValue, merged),
|
|
790
|
+
this.coerceValue(rawValue, merged, address, sheetName2),
|
|
709
791
|
value.formula ?? null,
|
|
710
792
|
merged,
|
|
711
793
|
comment,
|
|
@@ -715,12 +797,12 @@ var Normalizer = class {
|
|
|
715
797
|
const [bare, formula] = promoteFormula(value);
|
|
716
798
|
return new Cell(
|
|
717
799
|
address,
|
|
718
|
-
formula !== null ? null : this.coerceValue(bare, columnFormat),
|
|
800
|
+
formula !== null ? null : this.coerceValue(bare, columnFormat, address, sheetName2),
|
|
719
801
|
formula,
|
|
720
802
|
columnFormat
|
|
721
803
|
);
|
|
722
804
|
}
|
|
723
|
-
coerceValue(value, format) {
|
|
805
|
+
coerceValue(value, format, address = "?", sheetName2) {
|
|
724
806
|
if (value === null || value === void 0) return null;
|
|
725
807
|
const df = format?.displayFormat;
|
|
726
808
|
if (df === "date" || df === "datetime") {
|
|
@@ -734,12 +816,36 @@ var Normalizer = class {
|
|
|
734
816
|
if (value instanceof Date) {
|
|
735
817
|
return DateConverter.toSerial(value, true);
|
|
736
818
|
}
|
|
737
|
-
if (typeof value
|
|
819
|
+
if (typeof value === "bigint") {
|
|
738
820
|
return String(value);
|
|
739
821
|
}
|
|
822
|
+
if (typeof value === "object" && !Array.isArray(value) && hasOwnToString(value)) {
|
|
823
|
+
return String(value);
|
|
824
|
+
}
|
|
825
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
826
|
+
const where = sheetName2 === void 0 ? address : `${sheetName2}!${address}`;
|
|
827
|
+
const got = typeOf(value);
|
|
828
|
+
throw SchemaException.fromErrors([
|
|
829
|
+
{
|
|
830
|
+
path: where,
|
|
831
|
+
expected: "a string, number, bool, null, Date, or an object with its own toString",
|
|
832
|
+
got,
|
|
833
|
+
value,
|
|
834
|
+
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."
|
|
835
|
+
}
|
|
836
|
+
]);
|
|
837
|
+
}
|
|
740
838
|
return value;
|
|
741
839
|
}
|
|
742
840
|
};
|
|
841
|
+
function isCellSpec(value) {
|
|
842
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
843
|
+
return Object.getPrototypeOf(value) === Object.prototype;
|
|
844
|
+
}
|
|
845
|
+
function hasOwnToString(value) {
|
|
846
|
+
const fn = value.toString;
|
|
847
|
+
return typeof fn === "function" && fn !== Object.prototype.toString;
|
|
848
|
+
}
|
|
743
849
|
function promoteFormula(value) {
|
|
744
850
|
if (typeof value === "string" && value.length > 1 && value[0] === "=") {
|
|
745
851
|
return [null, value.slice(1)];
|
|
@@ -1271,54 +1377,6 @@ function phpRound(x, n) {
|
|
|
1271
1377
|
return Math.sign(x) * Math.round(Math.abs(x) * factor) / factor;
|
|
1272
1378
|
}
|
|
1273
1379
|
|
|
1274
|
-
// src/exceptions.ts
|
|
1275
|
-
var SchemaException = class _SchemaException extends Error {
|
|
1276
|
-
constructor(errors, message) {
|
|
1277
|
-
super(message ?? summarize(errors));
|
|
1278
|
-
this.name = "SchemaException";
|
|
1279
|
-
this.errors = errors;
|
|
1280
|
-
Object.setPrototypeOf(this, _SchemaException.prototype);
|
|
1281
|
-
}
|
|
1282
|
-
static fromErrors(errors) {
|
|
1283
|
-
return new _SchemaException(errors);
|
|
1284
|
-
}
|
|
1285
|
-
getErrors() {
|
|
1286
|
-
return this.errors;
|
|
1287
|
-
}
|
|
1288
|
-
};
|
|
1289
|
-
function summarize(errors) {
|
|
1290
|
-
if (errors.length === 1) {
|
|
1291
|
-
const e = errors[0];
|
|
1292
|
-
return `[holy-sheet] schema invalid at ${e.path}: expected ${e.expected}, got ${e.got}`;
|
|
1293
|
-
}
|
|
1294
|
-
const first2 = errors[0];
|
|
1295
|
-
const rest = errors.length - 1;
|
|
1296
|
-
return `[holy-sheet] schema invalid at ${first2.path}: expected ${first2.expected}, got ${first2.got} (+${rest} more)`;
|
|
1297
|
-
}
|
|
1298
|
-
var UnsupportedFormatException = class _UnsupportedFormatException extends Error {
|
|
1299
|
-
constructor(message, path = null, mimetype = null) {
|
|
1300
|
-
super(message);
|
|
1301
|
-
this.name = "UnsupportedFormatException";
|
|
1302
|
-
this.path = path;
|
|
1303
|
-
this.mimetype = mimetype;
|
|
1304
|
-
Object.setPrototypeOf(this, _UnsupportedFormatException.prototype);
|
|
1305
|
-
}
|
|
1306
|
-
static notAZip(path = null) {
|
|
1307
|
-
return new _UnsupportedFormatException(
|
|
1308
|
-
`[holy-sheet] cannot read ${path ?? "this file"}: it is not a zip archive, and xlsx and ods both are`,
|
|
1309
|
-
path
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1312
|
-
static unknownPackage(path, mimetype) {
|
|
1313
|
-
const what = mimetype !== null && mimetype !== "" ? `its mimetype is ${mimetype}` : "it has neither xl/workbook.xml nor an OpenDocument mimetype";
|
|
1314
|
-
return new _UnsupportedFormatException(
|
|
1315
|
-
`[holy-sheet] cannot read ${path ?? "this file"}: ${what}. Supported: xlsx, ods`,
|
|
1316
|
-
path,
|
|
1317
|
-
mimetype !== "" ? mimetype : null
|
|
1318
|
-
);
|
|
1319
|
-
}
|
|
1320
|
-
};
|
|
1321
|
-
|
|
1322
1380
|
// src/schema/repairer.ts
|
|
1323
1381
|
var VALID_THEMES = ["default", "minimal", "plain", "business"];
|
|
1324
1382
|
var ISO_DATE2 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
@@ -1565,7 +1623,21 @@ var Validator = class {
|
|
|
1565
1623
|
"Each row is an array of cell values, in column order."
|
|
1566
1624
|
)
|
|
1567
1625
|
);
|
|
1626
|
+
return;
|
|
1568
1627
|
}
|
|
1628
|
+
row.forEach((cell, k) => {
|
|
1629
|
+
if (Array.isArray(cell)) {
|
|
1630
|
+
errors.push(
|
|
1631
|
+
error(
|
|
1632
|
+
`${path}.rows[${j}][${k}]`,
|
|
1633
|
+
"a cell value or a cell object",
|
|
1634
|
+
"array",
|
|
1635
|
+
cell,
|
|
1636
|
+
'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: {...}}.'
|
|
1637
|
+
)
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1569
1641
|
});
|
|
1570
1642
|
}
|
|
1571
1643
|
}
|
|
@@ -1662,6 +1734,17 @@ var Validator = class {
|
|
|
1662
1734
|
)
|
|
1663
1735
|
);
|
|
1664
1736
|
}
|
|
1737
|
+
if (col.width !== void 0 && ColumnWidths.width(col.width) === null) {
|
|
1738
|
+
errors.push(
|
|
1739
|
+
error(
|
|
1740
|
+
`${path}.width`,
|
|
1741
|
+
"a non-negative number of pixels",
|
|
1742
|
+
typeOf(col.width),
|
|
1743
|
+
col.width,
|
|
1744
|
+
"A column width is a number of pixels, like 120. The sheet-level columnWidths map takes precedence over it."
|
|
1745
|
+
)
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1665
1748
|
return errors;
|
|
1666
1749
|
}
|
|
1667
1750
|
};
|
|
@@ -5138,7 +5221,7 @@ var holy_sheet_schema_default = {
|
|
|
5138
5221
|
};
|
|
5139
5222
|
|
|
5140
5223
|
// src/agent.ts
|
|
5141
|
-
var VERSION = "2.
|
|
5224
|
+
var VERSION = "2.6.0";
|
|
5142
5225
|
function toU8(input) {
|
|
5143
5226
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
5144
5227
|
}
|