@particle-academy/holy-sheet 2.4.0 → 2.4.2
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/dist/index.cjs +234 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +234 -121
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -223,7 +223,7 @@ interface SetMetaOp {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/** This package's own version, pinned to package.json by `version.test.ts`. */
|
|
226
|
-
declare const VERSION = "2.4.
|
|
226
|
+
declare const VERSION = "2.4.2";
|
|
227
227
|
type Any$6 = any;
|
|
228
228
|
/**
|
|
229
229
|
* Agent — the structured-tool surface for Holy Sheet. Mirrors PHP `Agent`.
|
|
@@ -295,7 +295,7 @@ type Any$5 = any;
|
|
|
295
295
|
* over `Agent`. Provided for parity / DI ergonomics; most callers use `Agent`.
|
|
296
296
|
*/
|
|
297
297
|
declare class HolySheet {
|
|
298
|
-
static readonly VERSION = "2.4.
|
|
298
|
+
static readonly VERSION = "2.4.2";
|
|
299
299
|
validate(schema: Any$5): ValidationError[];
|
|
300
300
|
toBytes(schema: Any$5): Uint8Array;
|
|
301
301
|
write(schema: Any$5, path: string): Promise<WriteResult>;
|
|
@@ -702,6 +702,21 @@ declare const SheetDiff: {
|
|
|
702
702
|
* example `columnWidths` decoded from `{"1":140,"0":120}`) as a map, not a
|
|
703
703
|
* list, and so as different from the same widths in order. A JS object has
|
|
704
704
|
* no such order to compare.
|
|
705
|
+
*
|
|
706
|
+
* Throws a `TypeError` on a value JSON cannot hold, as PHP 2.3.2 throws
|
|
707
|
+
* `JsonException`, rather than calling two such values the same:
|
|
708
|
+
*
|
|
709
|
+
* - a non-finite number (PHP: NAN, INF);
|
|
710
|
+
* - a lone UTF-16 surrogate in a string or a key, which is this runtime's
|
|
711
|
+
* invalid UTF-8 (a JS string cannot hold invalid UTF-8, and PHP's own
|
|
712
|
+
* `json_decode` refuses a lone surrogate);
|
|
713
|
+
* - a function or a symbol, which `JSON.stringify` would silently drop
|
|
714
|
+
* (a bigint already throws there);
|
|
715
|
+
* - nesting deeper than PHP's `json_encode` depth of 4096, where every object
|
|
716
|
+
* and array is a level, an empty one included. The walk is iterative, so
|
|
717
|
+
* this is PHP's limit and not the call stack's.
|
|
718
|
+
*
|
|
719
|
+
* `undefined` stays what JSON makes it: an absent key, and `null` in a list.
|
|
705
720
|
*/
|
|
706
721
|
same(a: unknown, b: unknown): boolean;
|
|
707
722
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -223,7 +223,7 @@ interface SetMetaOp {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/** This package's own version, pinned to package.json by `version.test.ts`. */
|
|
226
|
-
declare const VERSION = "2.4.
|
|
226
|
+
declare const VERSION = "2.4.2";
|
|
227
227
|
type Any$6 = any;
|
|
228
228
|
/**
|
|
229
229
|
* Agent — the structured-tool surface for Holy Sheet. Mirrors PHP `Agent`.
|
|
@@ -295,7 +295,7 @@ type Any$5 = any;
|
|
|
295
295
|
* over `Agent`. Provided for parity / DI ergonomics; most callers use `Agent`.
|
|
296
296
|
*/
|
|
297
297
|
declare class HolySheet {
|
|
298
|
-
static readonly VERSION = "2.4.
|
|
298
|
+
static readonly VERSION = "2.4.2";
|
|
299
299
|
validate(schema: Any$5): ValidationError[];
|
|
300
300
|
toBytes(schema: Any$5): Uint8Array;
|
|
301
301
|
write(schema: Any$5, path: string): Promise<WriteResult>;
|
|
@@ -702,6 +702,21 @@ declare const SheetDiff: {
|
|
|
702
702
|
* example `columnWidths` decoded from `{"1":140,"0":120}`) as a map, not a
|
|
703
703
|
* list, and so as different from the same widths in order. A JS object has
|
|
704
704
|
* no such order to compare.
|
|
705
|
+
*
|
|
706
|
+
* Throws a `TypeError` on a value JSON cannot hold, as PHP 2.3.2 throws
|
|
707
|
+
* `JsonException`, rather than calling two such values the same:
|
|
708
|
+
*
|
|
709
|
+
* - a non-finite number (PHP: NAN, INF);
|
|
710
|
+
* - a lone UTF-16 surrogate in a string or a key, which is this runtime's
|
|
711
|
+
* invalid UTF-8 (a JS string cannot hold invalid UTF-8, and PHP's own
|
|
712
|
+
* `json_decode` refuses a lone surrogate);
|
|
713
|
+
* - a function or a symbol, which `JSON.stringify` would silently drop
|
|
714
|
+
* (a bigint already throws there);
|
|
715
|
+
* - nesting deeper than PHP's `json_encode` depth of 4096, where every object
|
|
716
|
+
* and array is a level, an empty one included. The walk is iterative, so
|
|
717
|
+
* this is PHP's limit and not the call stack's.
|
|
718
|
+
*
|
|
719
|
+
* `undefined` stays what JSON makes it: an absent key, and `null` in a list.
|
|
705
720
|
*/
|
|
706
721
|
same(a: unknown, b: unknown): boolean;
|
|
707
722
|
/**
|
package/dist/index.js
CHANGED
|
@@ -175,7 +175,7 @@ var CsvBuilder = {
|
|
|
175
175
|
return ArrayBuilder.build(dataRows, headers, sheetName2, options);
|
|
176
176
|
}
|
|
177
177
|
};
|
|
178
|
-
function parseRows(csv, delimiter,
|
|
178
|
+
function parseRows(csv, delimiter, quote2) {
|
|
179
179
|
const rows = [];
|
|
180
180
|
let field = "";
|
|
181
181
|
let row = [];
|
|
@@ -195,9 +195,9 @@ function parseRows(csv, delimiter, quote) {
|
|
|
195
195
|
for (let i = 0; i < csv.length; i++) {
|
|
196
196
|
const ch = csv[i];
|
|
197
197
|
if (inQuotes) {
|
|
198
|
-
if (ch ===
|
|
199
|
-
if (csv[i + 1] ===
|
|
200
|
-
field +=
|
|
198
|
+
if (ch === quote2) {
|
|
199
|
+
if (csv[i + 1] === quote2) {
|
|
200
|
+
field += quote2;
|
|
201
201
|
i++;
|
|
202
202
|
} else {
|
|
203
203
|
inQuotes = false;
|
|
@@ -207,7 +207,7 @@ function parseRows(csv, delimiter, quote) {
|
|
|
207
207
|
}
|
|
208
208
|
continue;
|
|
209
209
|
}
|
|
210
|
-
if (ch ===
|
|
210
|
+
if (ch === quote2) {
|
|
211
211
|
inQuotes = true;
|
|
212
212
|
started = true;
|
|
213
213
|
continue;
|
|
@@ -2680,13 +2680,13 @@ function unescapeXml(s) {
|
|
|
2680
2680
|
});
|
|
2681
2681
|
}
|
|
2682
2682
|
function findTagEnd(src, from) {
|
|
2683
|
-
let
|
|
2683
|
+
let quote2 = "";
|
|
2684
2684
|
for (let i = from; i < src.length; i++) {
|
|
2685
2685
|
const ch = src[i];
|
|
2686
|
-
if (
|
|
2687
|
-
if (ch ===
|
|
2686
|
+
if (quote2) {
|
|
2687
|
+
if (ch === quote2) quote2 = "";
|
|
2688
2688
|
} else if (ch === '"' || ch === "'") {
|
|
2689
|
-
|
|
2689
|
+
quote2 = ch;
|
|
2690
2690
|
} else if (ch === ">") {
|
|
2691
2691
|
return i;
|
|
2692
2692
|
}
|
|
@@ -3834,16 +3834,132 @@ function phpString(value) {
|
|
|
3834
3834
|
if (isArr(value)) return "Array";
|
|
3835
3835
|
return String(value);
|
|
3836
3836
|
}
|
|
3837
|
+
function phpTrim2(value) {
|
|
3838
|
+
return value.replace(/^[ \t\n\r\x00\x0B]+|[ \t\n\r\x00\x0B]+$/g, "");
|
|
3839
|
+
}
|
|
3840
|
+
function phpInteger(value) {
|
|
3841
|
+
if (typeof value === "number") {
|
|
3842
|
+
return Number.isInteger(value) && Math.abs(value) <= 2 ** 63 ? value || 0 : null;
|
|
3843
|
+
}
|
|
3844
|
+
return typeof value === "string" && /^[0-9]+$/.test(value) ? phpInt2(value) : null;
|
|
3845
|
+
}
|
|
3846
|
+
function isIndexKey(key) {
|
|
3847
|
+
return /^[0-9]+$/.test(key) || /^-[1-9][0-9]*$/.test(key) && BigInt(key) >= -(2n ** 63n);
|
|
3848
|
+
}
|
|
3837
3849
|
function asciiUpper(value) {
|
|
3838
3850
|
return value.replace(/[a-z]+/g, (s) => s.toUpperCase());
|
|
3839
3851
|
}
|
|
3840
3852
|
function parseAddress(address) {
|
|
3841
|
-
const m = /^([A-Z]+)(\d+)$/.exec(asciiUpper(address
|
|
3853
|
+
const m = /^([A-Z]+)(\d+)$/.exec(asciiUpper(phpTrim2(address)));
|
|
3842
3854
|
if (!m) return null;
|
|
3843
3855
|
return [CellAddress.index(m[1]), parseInt(m[2], 10)];
|
|
3844
3856
|
}
|
|
3845
3857
|
var letter = CellAddress.letter;
|
|
3846
3858
|
|
|
3859
|
+
// src/ops/sheet-op-schema.ts
|
|
3860
|
+
var SheetOpSchema = {
|
|
3861
|
+
/** Every op type, in the order the variants are listed. */
|
|
3862
|
+
TYPES: [
|
|
3863
|
+
"set_cell",
|
|
3864
|
+
"set_range",
|
|
3865
|
+
"set_workbook",
|
|
3866
|
+
"clear_cell",
|
|
3867
|
+
"insert_rows",
|
|
3868
|
+
"delete_rows",
|
|
3869
|
+
"insert_columns",
|
|
3870
|
+
"delete_columns",
|
|
3871
|
+
"add_sheet",
|
|
3872
|
+
"remove_sheet",
|
|
3873
|
+
"rename_sheet",
|
|
3874
|
+
"move_sheet",
|
|
3875
|
+
"replace_sheet",
|
|
3876
|
+
"set_merged_regions",
|
|
3877
|
+
"set_column_widths",
|
|
3878
|
+
"set_frozen",
|
|
3879
|
+
"set_meta"
|
|
3880
|
+
],
|
|
3881
|
+
/** A fresh object on every call, so a caller editing it changes nothing here. */
|
|
3882
|
+
jsonSchema() {
|
|
3883
|
+
const address = () => ({ type: "string", pattern: "^[A-Za-z]+[0-9]+$" });
|
|
3884
|
+
const sheet = () => ({ type: "string", minLength: 1 });
|
|
3885
|
+
const count = () => ({ type: "integer", minimum: 1 });
|
|
3886
|
+
const position2 = () => ({ type: "integer", minimum: 1 });
|
|
3887
|
+
const object = () => ({ type: "object" });
|
|
3888
|
+
const nullableObject = () => ({ type: ["object", "null"] });
|
|
3889
|
+
const value = () => ({ type: ["string", "number", "boolean", "null"] });
|
|
3890
|
+
const variants = [
|
|
3891
|
+
variant(
|
|
3892
|
+
"set_cell",
|
|
3893
|
+
{ sheet: sheet(), address: address(), value: value(), formula: { type: "string" }, computedValue: value(), format: nullableObject(), comment: nullableObject() },
|
|
3894
|
+
["sheet", "address"],
|
|
3895
|
+
"Write one cell. Omitting formula or computedValue clears it; omitting format or comment keeps it; null clears it."
|
|
3896
|
+
),
|
|
3897
|
+
variant(
|
|
3898
|
+
"set_range",
|
|
3899
|
+
{ sheet: sheet(), start: address(), end: address(), values: { type: "array", items: { type: "array", items: value() } } },
|
|
3900
|
+
["sheet", "start", "values"],
|
|
3901
|
+
"Write a block of values row-major from start, each as a set_cell with no formula."
|
|
3902
|
+
),
|
|
3903
|
+
variant("set_workbook", { data: object() }, ["data"], "Replace the whole workbook schema."),
|
|
3904
|
+
variant("clear_cell", { sheet: sheet(), address: address() }, ["sheet", "address"], "Remove one cell."),
|
|
3905
|
+
variant("insert_rows", { sheet: sheet(), at: position2(), count: count() }, ["sheet", "at", "count"], "Insert rows before 1-based row `at`; cells, merges below move down."),
|
|
3906
|
+
variant("delete_rows", { sheet: sheet(), at: position2(), count: count() }, ["sheet", "at", "count"], "Delete rows starting at 1-based row `at`; cells, merges below move up."),
|
|
3907
|
+
variant("insert_columns", { sheet: sheet(), at: position2(), count: count() }, ["sheet", "at", "count"], "Insert columns before 1-based column `at` (A = 1); cells, merges and widths move right."),
|
|
3908
|
+
variant("delete_columns", { sheet: sheet(), at: position2(), count: count() }, ["sheet", "at", "count"], "Delete columns starting at 1-based column `at`; cells, merges and widths move left."),
|
|
3909
|
+
variant("add_sheet", { index: { type: "integer", minimum: 0 }, sheet: object() }, ["index", "sheet"], "Insert a sheet at a 0-based position."),
|
|
3910
|
+
variant("remove_sheet", { sheet: sheet() }, ["sheet"], "Remove a sheet by name."),
|
|
3911
|
+
variant("rename_sheet", { sheet: sheet(), name: sheet() }, ["sheet", "name"], "Rename a sheet."),
|
|
3912
|
+
variant("move_sheet", { sheet: sheet(), toIndex: { type: "integer", minimum: 0 } }, ["sheet", "toIndex"], "Move a sheet to a 0-based position."),
|
|
3913
|
+
variant("replace_sheet", { sheet: sheet(), data: object() }, ["sheet", "data"], "Replace one sheet whole."),
|
|
3914
|
+
variant(
|
|
3915
|
+
"set_merged_regions",
|
|
3916
|
+
{ sheet: sheet(), mergedRegions: { type: "array", items: { type: "object", required: ["start", "end"], properties: { start: address(), end: address() } } } },
|
|
3917
|
+
["sheet", "mergedRegions"],
|
|
3918
|
+
"Set every merged region of a sheet."
|
|
3919
|
+
),
|
|
3920
|
+
// A LIST too, as PHP 2.3.2 declares. PHP encodes a map whose keys run
|
|
3921
|
+
// 0..n-1 as a JSON list, so widths for columns A, B and C arrive as
|
|
3922
|
+
// `[120, 80, 140]` and no widths as `[]`; this port passes such a list
|
|
3923
|
+
// through a diff unchanged. A list's position is the column index.
|
|
3924
|
+
variant(
|
|
3925
|
+
"set_column_widths",
|
|
3926
|
+
{
|
|
3927
|
+
sheet: sheet(),
|
|
3928
|
+
columnWidths: {
|
|
3929
|
+
type: ["object", "array"],
|
|
3930
|
+
items: { type: "number", minimum: 0 },
|
|
3931
|
+
additionalProperties: { type: "number", minimum: 0 }
|
|
3932
|
+
}
|
|
3933
|
+
},
|
|
3934
|
+
["sheet", "columnWidths"],
|
|
3935
|
+
"Set every column width of a sheet (0-based column index to pixels; a list is indexed by position); empty removes them."
|
|
3936
|
+
),
|
|
3937
|
+
variant(
|
|
3938
|
+
"set_frozen",
|
|
3939
|
+
{ sheet: sheet(), rows: { type: "integer", minimum: 0 }, cols: { type: "integer", minimum: 0 } },
|
|
3940
|
+
["sheet", "rows", "cols"],
|
|
3941
|
+
"Set frozen rows and columns."
|
|
3942
|
+
),
|
|
3943
|
+
variant("set_meta", { meta: nullableObject() }, ["meta"], "Replace the workbook meta, or remove it with null.")
|
|
3944
|
+
];
|
|
3945
|
+
return {
|
|
3946
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
3947
|
+
title: "Holy Sheet op",
|
|
3948
|
+
description: "One op from Agent::diff, applied by Agent::reduce.",
|
|
3949
|
+
oneOf: variants
|
|
3950
|
+
};
|
|
3951
|
+
}
|
|
3952
|
+
};
|
|
3953
|
+
function variant(type, properties, required, description) {
|
|
3954
|
+
return {
|
|
3955
|
+
type: "object",
|
|
3956
|
+
description,
|
|
3957
|
+
required: ["type", ...required],
|
|
3958
|
+
additionalProperties: false,
|
|
3959
|
+
properties: { type: { const: type }, ...properties }
|
|
3960
|
+
};
|
|
3961
|
+
}
|
|
3962
|
+
|
|
3847
3963
|
// src/ops/sheet-reducer.ts
|
|
3848
3964
|
var SheetReducer = {
|
|
3849
3965
|
/** Sheet keys the granular ops address. A sheet with any other key is authored form. */
|
|
@@ -3856,6 +3972,9 @@ var SheetReducer = {
|
|
|
3856
3972
|
},
|
|
3857
3973
|
apply(schema, op) {
|
|
3858
3974
|
const type = get(op, "type");
|
|
3975
|
+
if (typeof type !== "string" || !SheetOpSchema.TYPES.includes(type)) {
|
|
3976
|
+
return schema;
|
|
3977
|
+
}
|
|
3859
3978
|
if (type === "set_workbook") {
|
|
3860
3979
|
return isArr(get(op, "data")) ? op.data : schema;
|
|
3861
3980
|
}
|
|
@@ -3873,14 +3992,22 @@ var SheetReducer = {
|
|
|
3873
3992
|
if (!isArr(get(op, "sheet"))) {
|
|
3874
3993
|
return schema;
|
|
3875
3994
|
}
|
|
3876
|
-
const index =
|
|
3877
|
-
|
|
3995
|
+
const index = has(op, "index") ? phpInteger(op.index) : sheets.length;
|
|
3996
|
+
if (index === null) {
|
|
3997
|
+
return schema;
|
|
3998
|
+
}
|
|
3999
|
+
sheets.splice(Math.max(0, Math.min(sheets.length, index)), 0, op.sheet);
|
|
3878
4000
|
return { ...schema, sheets };
|
|
3879
4001
|
}
|
|
3880
4002
|
const at2 = find(sheets, phpString(get(op, "sheet") ?? ""));
|
|
3881
4003
|
if (at2 === null) {
|
|
3882
4004
|
return schema;
|
|
3883
4005
|
}
|
|
4006
|
+
for (const field of POSITION_FIELDS) {
|
|
4007
|
+
if (has(op, field) && phpInteger(op[field]) === null) {
|
|
4008
|
+
return schema;
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
3884
4011
|
switch (type) {
|
|
3885
4012
|
case "remove_sheet":
|
|
3886
4013
|
sheets.splice(at2, 1);
|
|
@@ -3890,7 +4017,7 @@ var SheetReducer = {
|
|
|
3890
4017
|
break;
|
|
3891
4018
|
case "move_sheet": {
|
|
3892
4019
|
const [moved] = sheets.splice(at2, 1);
|
|
3893
|
-
const to = Math.max(0, Math.min(sheets.length,
|
|
4020
|
+
const to = Math.max(0, Math.min(sheets.length, phpInteger(get(op, "toIndex") ?? at2) ?? at2));
|
|
3894
4021
|
sheets.splice(to, 0, moved);
|
|
3895
4022
|
break;
|
|
3896
4023
|
}
|
|
@@ -3906,8 +4033,8 @@ var SheetReducer = {
|
|
|
3906
4033
|
sheets[at2] = setOrUnset(sheets[at2], "columnWidths", get(op, "columnWidths") ?? [], EMPTY);
|
|
3907
4034
|
break;
|
|
3908
4035
|
case "set_frozen":
|
|
3909
|
-
sheets[at2] = setOrUnset(sheets[at2], "frozenRows",
|
|
3910
|
-
sheets[at2] = setOrUnset(sheets[at2], "frozenCols",
|
|
4036
|
+
sheets[at2] = setOrUnset(sheets[at2], "frozenRows", phpInteger(get(op, "rows") ?? 0) ?? 0, 0);
|
|
4037
|
+
sheets[at2] = setOrUnset(sheets[at2], "frozenCols", phpInteger(get(op, "cols") ?? 0) ?? 0, 0);
|
|
3911
4038
|
break;
|
|
3912
4039
|
case "set_cell":
|
|
3913
4040
|
sheets[at2] = setCell(sheets[at2], op);
|
|
@@ -3916,7 +4043,7 @@ var SheetReducer = {
|
|
|
3916
4043
|
sheets[at2] = setRange(sheets[at2], op);
|
|
3917
4044
|
break;
|
|
3918
4045
|
case "clear_cell": {
|
|
3919
|
-
const address = asciiUpper(phpString(get(op, "address") ?? ""));
|
|
4046
|
+
const address = asciiUpper(phpTrim2(phpString(get(op, "address") ?? "")));
|
|
3920
4047
|
const cells = sheets[at2].cells;
|
|
3921
4048
|
if (isArr(cells) && !Array.isArray(cells) && has(cells, address)) {
|
|
3922
4049
|
const copy = { ...cells };
|
|
@@ -3926,16 +4053,16 @@ var SheetReducer = {
|
|
|
3926
4053
|
break;
|
|
3927
4054
|
}
|
|
3928
4055
|
case "insert_rows":
|
|
3929
|
-
sheets[at2] = shiftRows(sheets[at2],
|
|
4056
|
+
sheets[at2] = shiftRows(sheets[at2], position(op, "at"), Math.max(0, position(op, "count")));
|
|
3930
4057
|
break;
|
|
3931
4058
|
case "delete_rows":
|
|
3932
|
-
sheets[at2] = shiftRows(sheets[at2],
|
|
4059
|
+
sheets[at2] = shiftRows(sheets[at2], position(op, "at"), -Math.max(0, position(op, "count")));
|
|
3933
4060
|
break;
|
|
3934
4061
|
case "insert_columns":
|
|
3935
|
-
sheets[at2] = shiftColumns(sheets[at2],
|
|
4062
|
+
sheets[at2] = shiftColumns(sheets[at2], position(op, "at"), Math.max(0, position(op, "count")));
|
|
3936
4063
|
break;
|
|
3937
4064
|
case "delete_columns":
|
|
3938
|
-
sheets[at2] = shiftColumns(sheets[at2],
|
|
4065
|
+
sheets[at2] = shiftColumns(sheets[at2], position(op, "at"), -Math.max(0, position(op, "count")));
|
|
3939
4066
|
break;
|
|
3940
4067
|
default:
|
|
3941
4068
|
return schema;
|
|
@@ -3943,6 +4070,10 @@ var SheetReducer = {
|
|
|
3943
4070
|
return { ...schema, sheets };
|
|
3944
4071
|
}
|
|
3945
4072
|
};
|
|
4073
|
+
var POSITION_FIELDS = ["index", "toIndex", "rows", "cols", "at", "count"];
|
|
4074
|
+
function position(op, key) {
|
|
4075
|
+
return phpInteger(get(op, key) ?? 0) ?? 0;
|
|
4076
|
+
}
|
|
3946
4077
|
var EMPTY = /* @__PURE__ */ Symbol("empty");
|
|
3947
4078
|
function find(sheets, name) {
|
|
3948
4079
|
for (let i = 0; i < sheets.length; i++) {
|
|
@@ -3962,7 +4093,7 @@ function setOrUnset(sheet, key, value, empty) {
|
|
|
3962
4093
|
return out;
|
|
3963
4094
|
}
|
|
3964
4095
|
function setCell(sheet, op) {
|
|
3965
|
-
const address = asciiUpper(phpString(get(op, "address") ?? ""));
|
|
4096
|
+
const address = asciiUpper(phpTrim2(phpString(get(op, "address") ?? "")));
|
|
3966
4097
|
if (parseAddress(address) === null) {
|
|
3967
4098
|
return sheet;
|
|
3968
4099
|
}
|
|
@@ -4042,6 +4173,9 @@ function shiftColumns(sheet, at2, delta) {
|
|
|
4042
4173
|
if (isArr(get(sheet, "columnWidths"))) {
|
|
4043
4174
|
const widths = /* @__PURE__ */ new Map();
|
|
4044
4175
|
for (const [index, width] of entriesOf(sheet.columnWidths)) {
|
|
4176
|
+
if (!isIndexKey(index)) {
|
|
4177
|
+
continue;
|
|
4178
|
+
}
|
|
4045
4179
|
const i = phpInt2(index);
|
|
4046
4180
|
const number = i + 1;
|
|
4047
4181
|
if (number < at2) {
|
|
@@ -4168,6 +4302,21 @@ var SheetDiff = {
|
|
|
4168
4302
|
* example `columnWidths` decoded from `{"1":140,"0":120}`) as a map, not a
|
|
4169
4303
|
* list, and so as different from the same widths in order. A JS object has
|
|
4170
4304
|
* no such order to compare.
|
|
4305
|
+
*
|
|
4306
|
+
* Throws a `TypeError` on a value JSON cannot hold, as PHP 2.3.2 throws
|
|
4307
|
+
* `JsonException`, rather than calling two such values the same:
|
|
4308
|
+
*
|
|
4309
|
+
* - a non-finite number (PHP: NAN, INF);
|
|
4310
|
+
* - a lone UTF-16 surrogate in a string or a key, which is this runtime's
|
|
4311
|
+
* invalid UTF-8 (a JS string cannot hold invalid UTF-8, and PHP's own
|
|
4312
|
+
* `json_decode` refuses a lone surrogate);
|
|
4313
|
+
* - a function or a symbol, which `JSON.stringify` would silently drop
|
|
4314
|
+
* (a bigint already throws there);
|
|
4315
|
+
* - nesting deeper than PHP's `json_encode` depth of 4096, where every object
|
|
4316
|
+
* and array is a level, an empty one included. The walk is iterative, so
|
|
4317
|
+
* this is PHP's limit and not the call stack's.
|
|
4318
|
+
*
|
|
4319
|
+
* `undefined` stays what JSON makes it: an absent key, and `null` in a list.
|
|
4171
4320
|
*/
|
|
4172
4321
|
same(a, b) {
|
|
4173
4322
|
return canon(a) === canon(b);
|
|
@@ -4419,19 +4568,19 @@ function lines(sheet, axis) {
|
|
|
4419
4568
|
}
|
|
4420
4569
|
const [col, row] = parsed;
|
|
4421
4570
|
const line = axis === "row" ? row : col + 1;
|
|
4422
|
-
const
|
|
4571
|
+
const position2 = axis === "row" ? col : row;
|
|
4423
4572
|
let group = grouped.get(line);
|
|
4424
4573
|
if (group === void 0) {
|
|
4425
4574
|
group = /* @__PURE__ */ new Map();
|
|
4426
4575
|
grouped.set(line, group);
|
|
4427
4576
|
}
|
|
4428
|
-
group.set(
|
|
4577
|
+
group.set(position2, cell);
|
|
4429
4578
|
max = Math.max(max, line);
|
|
4430
4579
|
}
|
|
4431
4580
|
const out = [];
|
|
4432
4581
|
for (let i = 1; i <= max; i++) {
|
|
4433
4582
|
const positions = [...(grouped.get(i) ?? /* @__PURE__ */ new Map()).entries()].sort((x, y) => x[0] - y[0]);
|
|
4434
|
-
out.push(canonEntries(positions.map(([
|
|
4583
|
+
out.push(canonEntries(positions.map(([position2, cell]) => [String(position2), cell])));
|
|
4435
4584
|
}
|
|
4436
4585
|
return out;
|
|
4437
4586
|
}
|
|
@@ -4458,108 +4607,72 @@ function rowMajor(addresses) {
|
|
|
4458
4607
|
return parsed.map(([, , address]) => address);
|
|
4459
4608
|
}
|
|
4460
4609
|
function canon(value) {
|
|
4461
|
-
|
|
4462
|
-
|
|
4610
|
+
return isArr(value) ? canonEntries(entriesFor(value)) : scalar(value);
|
|
4611
|
+
}
|
|
4612
|
+
var JSON_DEPTH = 4096;
|
|
4613
|
+
function canonEntries(root) {
|
|
4614
|
+
const stack = [];
|
|
4615
|
+
const open = (key, entries) => {
|
|
4616
|
+
if (stack.length === JSON_DEPTH) {
|
|
4617
|
+
throw unencodable("Maximum stack depth exceeded");
|
|
4618
|
+
}
|
|
4619
|
+
const list = entries.every(([k], i) => k === String(i));
|
|
4620
|
+
if (!list) {
|
|
4621
|
+
entries = entries.slice().sort((x, y) => x[0] < y[0] ? -1 : x[0] > y[0] ? 1 : 0);
|
|
4622
|
+
}
|
|
4623
|
+
stack.push({ key, entries, list, next: 0, parts: [] });
|
|
4624
|
+
};
|
|
4625
|
+
open("", root);
|
|
4626
|
+
for (; ; ) {
|
|
4627
|
+
const top = stack[stack.length - 1];
|
|
4628
|
+
if (top.next < top.entries.length) {
|
|
4629
|
+
const [key, item] = top.entries[top.next++];
|
|
4630
|
+
if (isArr(item)) {
|
|
4631
|
+
open(key, entriesFor(item));
|
|
4632
|
+
} else {
|
|
4633
|
+
top.parts.push(member(top, key, scalar(item)));
|
|
4634
|
+
}
|
|
4635
|
+
continue;
|
|
4636
|
+
}
|
|
4637
|
+
stack.pop();
|
|
4638
|
+
const text = top.list ? "[" + top.parts.join(",") + "]" : "{" + top.parts.join(",") + "}";
|
|
4639
|
+
const parent = stack[stack.length - 1];
|
|
4640
|
+
if (parent === void 0) {
|
|
4641
|
+
return text;
|
|
4642
|
+
}
|
|
4643
|
+
parent.parts.push(member(parent, top.key, text));
|
|
4463
4644
|
}
|
|
4464
|
-
|
|
4465
|
-
|
|
4645
|
+
}
|
|
4646
|
+
function entriesFor(value) {
|
|
4647
|
+
return Array.isArray(value) ? Array.from(value, (item, i) => [String(i), item]) : entriesOf(value);
|
|
4648
|
+
}
|
|
4649
|
+
function member(frame, key, text) {
|
|
4650
|
+
return frame.list ? text : quote(key) + ":" + text;
|
|
4651
|
+
}
|
|
4652
|
+
function scalar(value) {
|
|
4653
|
+
switch (typeof value) {
|
|
4654
|
+
case "number":
|
|
4655
|
+
if (!Number.isFinite(value)) {
|
|
4656
|
+
throw unencodable("Inf and NaN cannot be JSON encoded");
|
|
4657
|
+
}
|
|
4658
|
+
break;
|
|
4659
|
+
case "string":
|
|
4660
|
+
return quote(value);
|
|
4661
|
+
case "function":
|
|
4662
|
+
case "symbol":
|
|
4663
|
+
throw unencodable(`a ${typeof value} cannot be JSON encoded`);
|
|
4466
4664
|
}
|
|
4467
4665
|
return JSON.stringify(value) ?? "null";
|
|
4468
4666
|
}
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4667
|
+
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
4668
|
+
function quote(text) {
|
|
4669
|
+
if (LONE_SURROGATE.test(text)) {
|
|
4670
|
+
throw unencodable("Single unpaired UTF-16 surrogate");
|
|
4472
4671
|
}
|
|
4473
|
-
|
|
4474
|
-
return "{" + sorted.map(([key, item]) => JSON.stringify(key) + ":" + canon(item)).join(",") + "}";
|
|
4672
|
+
return JSON.stringify(text);
|
|
4475
4673
|
}
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
var SheetOpSchema = {
|
|
4479
|
-
/** Every op type, in the order the variants are listed. */
|
|
4480
|
-
TYPES: [
|
|
4481
|
-
"set_cell",
|
|
4482
|
-
"set_range",
|
|
4483
|
-
"set_workbook",
|
|
4484
|
-
"clear_cell",
|
|
4485
|
-
"insert_rows",
|
|
4486
|
-
"delete_rows",
|
|
4487
|
-
"insert_columns",
|
|
4488
|
-
"delete_columns",
|
|
4489
|
-
"add_sheet",
|
|
4490
|
-
"remove_sheet",
|
|
4491
|
-
"rename_sheet",
|
|
4492
|
-
"move_sheet",
|
|
4493
|
-
"replace_sheet",
|
|
4494
|
-
"set_merged_regions",
|
|
4495
|
-
"set_column_widths",
|
|
4496
|
-
"set_frozen",
|
|
4497
|
-
"set_meta"
|
|
4498
|
-
],
|
|
4499
|
-
/** A fresh object on every call, so a caller editing it changes nothing here. */
|
|
4500
|
-
jsonSchema() {
|
|
4501
|
-
const address = () => ({ type: "string", pattern: "^[A-Za-z]+[0-9]+$" });
|
|
4502
|
-
const sheet = () => ({ type: "string", minLength: 1 });
|
|
4503
|
-
const count = () => ({ type: "integer", minimum: 1 });
|
|
4504
|
-
const position = () => ({ type: "integer", minimum: 1 });
|
|
4505
|
-
const object = () => ({ type: "object" });
|
|
4506
|
-
const nullableObject = () => ({ type: ["object", "null"] });
|
|
4507
|
-
const value = () => ({ type: ["string", "number", "boolean", "null"] });
|
|
4508
|
-
const variants = [
|
|
4509
|
-
variant(
|
|
4510
|
-
"set_cell",
|
|
4511
|
-
{ sheet: sheet(), address: address(), value: value(), formula: { type: "string" }, computedValue: value(), format: nullableObject(), comment: nullableObject() },
|
|
4512
|
-
["sheet", "address"],
|
|
4513
|
-
"Write one cell. Omitting formula or computedValue clears it; omitting format or comment keeps it; null clears it."
|
|
4514
|
-
),
|
|
4515
|
-
variant(
|
|
4516
|
-
"set_range",
|
|
4517
|
-
{ sheet: sheet(), start: address(), end: address(), values: { type: "array", items: { type: "array", items: value() } } },
|
|
4518
|
-
["sheet", "start", "values"],
|
|
4519
|
-
"Write a block of values row-major from start, each as a set_cell with no formula."
|
|
4520
|
-
),
|
|
4521
|
-
variant("set_workbook", { data: object() }, ["data"], "Replace the whole workbook schema."),
|
|
4522
|
-
variant("clear_cell", { sheet: sheet(), address: address() }, ["sheet", "address"], "Remove one cell."),
|
|
4523
|
-
variant("insert_rows", { sheet: sheet(), at: position(), count: count() }, ["sheet", "at", "count"], "Insert rows before 1-based row `at`; cells, merges below move down."),
|
|
4524
|
-
variant("delete_rows", { sheet: sheet(), at: position(), count: count() }, ["sheet", "at", "count"], "Delete rows starting at 1-based row `at`; cells, merges below move up."),
|
|
4525
|
-
variant("insert_columns", { sheet: sheet(), at: position(), count: count() }, ["sheet", "at", "count"], "Insert columns before 1-based column `at` (A = 1); cells, merges and widths move right."),
|
|
4526
|
-
variant("delete_columns", { sheet: sheet(), at: position(), count: count() }, ["sheet", "at", "count"], "Delete columns starting at 1-based column `at`; cells, merges and widths move left."),
|
|
4527
|
-
variant("add_sheet", { index: { type: "integer", minimum: 0 }, sheet: object() }, ["index", "sheet"], "Insert a sheet at a 0-based position."),
|
|
4528
|
-
variant("remove_sheet", { sheet: sheet() }, ["sheet"], "Remove a sheet by name."),
|
|
4529
|
-
variant("rename_sheet", { sheet: sheet(), name: sheet() }, ["sheet", "name"], "Rename a sheet."),
|
|
4530
|
-
variant("move_sheet", { sheet: sheet(), toIndex: { type: "integer", minimum: 0 } }, ["sheet", "toIndex"], "Move a sheet to a 0-based position."),
|
|
4531
|
-
variant("replace_sheet", { sheet: sheet(), data: object() }, ["sheet", "data"], "Replace one sheet whole."),
|
|
4532
|
-
variant(
|
|
4533
|
-
"set_merged_regions",
|
|
4534
|
-
{ sheet: sheet(), mergedRegions: { type: "array", items: { type: "object", required: ["start", "end"], properties: { start: address(), end: address() } } } },
|
|
4535
|
-
["sheet", "mergedRegions"],
|
|
4536
|
-
"Set every merged region of a sheet."
|
|
4537
|
-
),
|
|
4538
|
-
variant("set_column_widths", { sheet: sheet(), columnWidths: object() }, ["sheet", "columnWidths"], "Set every column width of a sheet (0-based column index to pixels)."),
|
|
4539
|
-
variant(
|
|
4540
|
-
"set_frozen",
|
|
4541
|
-
{ sheet: sheet(), rows: { type: "integer", minimum: 0 }, cols: { type: "integer", minimum: 0 } },
|
|
4542
|
-
["sheet", "rows", "cols"],
|
|
4543
|
-
"Set frozen rows and columns."
|
|
4544
|
-
),
|
|
4545
|
-
variant("set_meta", { meta: nullableObject() }, ["meta"], "Replace the workbook meta, or remove it with null.")
|
|
4546
|
-
];
|
|
4547
|
-
return {
|
|
4548
|
-
$schema: "http://json-schema.org/draft-07/schema#",
|
|
4549
|
-
title: "Holy Sheet op",
|
|
4550
|
-
description: "One op from Agent::diff, applied by Agent::reduce.",
|
|
4551
|
-
oneOf: variants
|
|
4552
|
-
};
|
|
4553
|
-
}
|
|
4554
|
-
};
|
|
4555
|
-
function variant(type, properties, required, description) {
|
|
4556
|
-
return {
|
|
4557
|
-
type: "object",
|
|
4558
|
-
description,
|
|
4559
|
-
required: ["type", ...required],
|
|
4560
|
-
additionalProperties: false,
|
|
4561
|
-
properties: { type: { const: type }, ...properties }
|
|
4562
|
-
};
|
|
4674
|
+
function unencodable(reason) {
|
|
4675
|
+
return new TypeError(`[holy-sheet] cannot compare a value JSON cannot hold: ${reason}`);
|
|
4563
4676
|
}
|
|
4564
4677
|
|
|
4565
4678
|
// src/holy-sheet.schema.json
|
|
@@ -4926,7 +5039,7 @@ var holy_sheet_schema_default = {
|
|
|
4926
5039
|
};
|
|
4927
5040
|
|
|
4928
5041
|
// src/agent.ts
|
|
4929
|
-
var VERSION = "2.4.
|
|
5042
|
+
var VERSION = "2.4.2";
|
|
4930
5043
|
function toU8(input) {
|
|
4931
5044
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
4932
5045
|
}
|