@a3s-lab/office 0.36.0 → 0.37.1
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/README.md +18 -6
- package/dist/0~3557.js +2 -1
- package/dist/0~3635.js +4 -1
- package/dist/0~7048.js +93 -1
- package/dist/0~9073.js +7 -2
- package/dist/0~presentation-editor.js +45 -9
- package/dist/0~spreadsheet-editor.js +20321 -16966
- package/dist/4121.js +1942 -1939
- package/dist/{3266.js → 5416.js} +1504 -20
- package/dist/9333.js +1 -1
- package/dist/core.d.ts +1 -0
- package/dist/core.js +141 -28
- package/dist/index.js +1 -1
- package/dist/internal/features/work/editors/office-shortcuts.d.ts +1 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter-command.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter-condition-dialog-model.d.ts +27 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter-condition-dialog.d.ts +17 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter-menu.d.ts +10 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter.d.ts +16 -4
- package/dist/internal/features/work/editors/spreadsheet-cell-range.d.ts +1 -0
- package/dist/internal/features/work/editors/spreadsheet-command-catalog.d.ts +9 -0
- package/dist/internal/features/work/editors/spreadsheet-command-controller.d.ts +13 -1
- package/dist/internal/features/work/editors/spreadsheet-current-region.d.ts +12 -0
- package/dist/internal/features/work/editors/spreadsheet-editor.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-appearance.d.ts +45 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-collation.d.ts +8 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-command.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-custom-list-editor.d.ts +8 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-custom-list-store.d.ts +13 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-custom-list.d.ts +26 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-dialog.d.ts +9 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-matrix.d.ts +4 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-options-dialog.d.ts +7 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-order-controls.d.ts +16 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-range-dialog.d.ts +7 -0
- package/dist/internal/features/work/editors/spreadsheet-sort.d.ts +138 -0
- package/dist/internal/features/work/editors/spreadsheet-table-totals.d.ts +39 -0
- package/dist/internal/features/work/editors/spreadsheet-table.d.ts +11 -1
- package/dist/internal/features/work/editors/use-spreadsheet-auto-filter.d.ts +12 -3
- package/dist/internal/features/work/editors/use-spreadsheet-sort.d.ts +26 -0
- package/dist/internal/features/work/editors/work-office-chrome.d.ts +3 -0
- package/dist/internal/features/work/work-spreadsheet-auto-filter.d.ts +14 -0
- package/dist/internal/features/work/work-spreadsheet-dynamic-filter.d.ts +7 -0
- package/dist/internal/features/work/work-spreadsheet-filter-contract.d.ts +3 -0
- package/dist/internal/features/work/work-spreadsheet-wildcard.d.ts +7 -0
- package/dist/internal/features/work/work-types.d.ts +45 -7
- package/dist/internal/features/work/work-xlsx-interop.d.ts +2 -1
- package/dist/internal/features/work/work-xlsx-table-filters.d.ts +3 -1
- package/dist/internal/kernel/office-kernel-spreadsheet-fallback-formula.d.ts +11 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/office-kernel.worker.js +93 -1
- package/dist/styles.css +789 -145
- package/dist/vue.d.ts +3 -1
- package/dist/vue.js +2 -0
- package/dist/web-component.d.ts +3 -1
- package/dist/web-component.js +9 -0
- package/dist/work-spreadsheet-package-scan.worker.js +2 -1
- package/docs/latest/en/browser-editor-architecture.md +136 -2
- package/package.json +24 -1
package/dist/{3266.js → 5416.js}
RENAMED
|
@@ -173,6 +173,340 @@ function nearestSpreadsheetTableCell(sheet, table, row, column) {
|
|
|
173
173
|
}
|
|
174
174
|
return null;
|
|
175
175
|
}
|
|
176
|
+
const MAX_SPREADSHEET_TABLE_TOTALS_FORMULA_LENGTH = 8192;
|
|
177
|
+
const MAX_SPREADSHEET_TABLE_TOTALS_LABEL_LENGTH = 255;
|
|
178
|
+
const DEFAULT_SPREADSHEET_TABLE_TOTALS_LABEL = 'Total';
|
|
179
|
+
const SPREADSHEET_TABLE_TOTALS_FUNCTIONS = Object.freeze([
|
|
180
|
+
'sum',
|
|
181
|
+
'average',
|
|
182
|
+
'count',
|
|
183
|
+
'countNums',
|
|
184
|
+
'max',
|
|
185
|
+
'min',
|
|
186
|
+
'stdDev',
|
|
187
|
+
'stdDevP',
|
|
188
|
+
'var',
|
|
189
|
+
'varP',
|
|
190
|
+
'custom'
|
|
191
|
+
]);
|
|
192
|
+
const UNSAFE_TOTALS_FUNCTIONS = new Set([
|
|
193
|
+
'CALL',
|
|
194
|
+
'DDE',
|
|
195
|
+
'DDE.REQUEST',
|
|
196
|
+
'DDE.POKE',
|
|
197
|
+
'EXEC',
|
|
198
|
+
'HYPERLINK',
|
|
199
|
+
'INDIRECT',
|
|
200
|
+
'OFFSET',
|
|
201
|
+
'REGISTER.ID',
|
|
202
|
+
'RTD',
|
|
203
|
+
'WEBSERVICE'
|
|
204
|
+
]);
|
|
205
|
+
const SUBTOTAL_CODES = {
|
|
206
|
+
average: 101,
|
|
207
|
+
count: 103,
|
|
208
|
+
countNums: 102,
|
|
209
|
+
max: 104,
|
|
210
|
+
min: 105,
|
|
211
|
+
stdDev: 107,
|
|
212
|
+
stdDevP: 108,
|
|
213
|
+
sum: 109,
|
|
214
|
+
var: 110,
|
|
215
|
+
varP: 111
|
|
216
|
+
};
|
|
217
|
+
const OOXML_TOTALS_FUNCTIONS = {
|
|
218
|
+
average: 'average',
|
|
219
|
+
count: 'count',
|
|
220
|
+
countNums: 'countNums',
|
|
221
|
+
max: 'max',
|
|
222
|
+
min: 'min',
|
|
223
|
+
stdDev: 'stdDev',
|
|
224
|
+
stdDevP: 'stdDevp',
|
|
225
|
+
sum: 'sum',
|
|
226
|
+
var: 'var',
|
|
227
|
+
varP: 'varp'
|
|
228
|
+
};
|
|
229
|
+
const TOTALS_FUNCTION_LABELS = {
|
|
230
|
+
sum: '求和',
|
|
231
|
+
average: '平均值',
|
|
232
|
+
count: '计数',
|
|
233
|
+
countNums: '数值计数',
|
|
234
|
+
max: '最大值',
|
|
235
|
+
min: '最小值',
|
|
236
|
+
stdDev: '标准差',
|
|
237
|
+
stdDevP: '总体标准差',
|
|
238
|
+
var: '方差',
|
|
239
|
+
varP: '总体方差',
|
|
240
|
+
custom: '自定义'
|
|
241
|
+
};
|
|
242
|
+
function spreadsheetTableTotalsFunctionLabel(value) {
|
|
243
|
+
return value ? TOTALS_FUNCTION_LABELS[value] : '不汇总';
|
|
244
|
+
}
|
|
245
|
+
function normalizeSpreadsheetTableTotalsFunction(value) {
|
|
246
|
+
if ('string' != typeof value) return;
|
|
247
|
+
if ('none' === value || '' === value) return;
|
|
248
|
+
return SPREADSHEET_TABLE_TOTALS_FUNCTIONS.includes(value) ? value : void 0;
|
|
249
|
+
}
|
|
250
|
+
function normalizeSpreadsheetTableTotalsFormula(value) {
|
|
251
|
+
if ('string' != typeof value) return;
|
|
252
|
+
const trimmed = value.trim();
|
|
253
|
+
if (!trimmed) return;
|
|
254
|
+
const formula = trimmed.startsWith('=') ? trimmed : `=${trimmed}`;
|
|
255
|
+
if (formula.startsWith('==') || formula.length > MAX_SPREADSHEET_TABLE_TOTALS_FORMULA_LENGTH || /[\u0000-\u001f\u007f]/u.test(formula) || formulaHasExternalReference(formula) || spreadsheetFormulaFunctions(formula).some((name)=>UNSAFE_TOTALS_FUNCTIONS.has(name))) return;
|
|
256
|
+
const editable = editableSpreadsheetFormula(formula).trim();
|
|
257
|
+
return editable || void 0;
|
|
258
|
+
}
|
|
259
|
+
function normalizeSpreadsheetTableTotalsLabel(value) {
|
|
260
|
+
if ('string' != typeof value) return;
|
|
261
|
+
const label = value.trim();
|
|
262
|
+
if (!label || Array.from(label).length > MAX_SPREADSHEET_TABLE_TOTALS_LABEL_LENGTH || /[\u0000-\u001f\u007f]/u.test(label)) return;
|
|
263
|
+
return label;
|
|
264
|
+
}
|
|
265
|
+
function spreadsheetTableTotalsFunctionFromOoxml(value) {
|
|
266
|
+
if ('string' != typeof value) return;
|
|
267
|
+
const normalized = value.trim().toLowerCase();
|
|
268
|
+
if ('custom' === normalized) return 'custom';
|
|
269
|
+
const entry = Object.entries(OOXML_TOTALS_FUNCTIONS).find(([, token])=>token.toLowerCase() === normalized);
|
|
270
|
+
return entry?.[0];
|
|
271
|
+
}
|
|
272
|
+
function spreadsheetTableTotalsFunctionToOoxml(value) {
|
|
273
|
+
if (!value || 'custom' === value) return 'custom' === value ? 'custom' : void 0;
|
|
274
|
+
return OOXML_TOTALS_FUNCTIONS[value];
|
|
275
|
+
}
|
|
276
|
+
function spreadsheetTableTotalsFormula(table, columnOffset) {
|
|
277
|
+
const column = table.columns[columnOffset];
|
|
278
|
+
if (!column) return;
|
|
279
|
+
const custom = normalizeSpreadsheetTableTotalsFormula(column.totalsFormula);
|
|
280
|
+
if (custom) return custom;
|
|
281
|
+
const functionName = normalizeSpreadsheetTableTotalsFunction(column.totalsFunction);
|
|
282
|
+
if (!functionName || 'custom' === functionName) return;
|
|
283
|
+
const code = SUBTOTAL_CODES[functionName];
|
|
284
|
+
if (!code) return;
|
|
285
|
+
return `=SUBTOTAL(${code},${table.name}[${escapeStructuredColumnName(column.name)}])`;
|
|
286
|
+
}
|
|
287
|
+
function spreadsheetTableTotalsColumnPatch(columns, patches) {
|
|
288
|
+
if (void 0 === patches) return columns.map((column)=>({
|
|
289
|
+
...column
|
|
290
|
+
}));
|
|
291
|
+
const entries = Array.isArray(patches) ? patches.flatMap((patch, offset)=>patch ? [
|
|
292
|
+
[
|
|
293
|
+
String(offset),
|
|
294
|
+
patch
|
|
295
|
+
]
|
|
296
|
+
] : []) : Object.entries(patches);
|
|
297
|
+
const next = columns.map((column)=>({
|
|
298
|
+
...column
|
|
299
|
+
}));
|
|
300
|
+
for (const [rawOffset, patch] of entries){
|
|
301
|
+
const offset = Number(rawOffset);
|
|
302
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset >= next.length || !patch || 'object' != typeof patch || Array.isArray(patch)) return null;
|
|
303
|
+
const current = next[offset];
|
|
304
|
+
if (!current) return null;
|
|
305
|
+
const candidate = {
|
|
306
|
+
...current
|
|
307
|
+
};
|
|
308
|
+
if (Object.hasOwn(patch, 'totalsFunction')) if (null === patch.totalsFunction || '' === patch.totalsFunction) delete candidate.totalsFunction;
|
|
309
|
+
else {
|
|
310
|
+
const functionName = normalizeSpreadsheetTableTotalsFunction(patch.totalsFunction);
|
|
311
|
+
if (!functionName) return null;
|
|
312
|
+
candidate.totalsFunction = functionName;
|
|
313
|
+
}
|
|
314
|
+
if (Object.hasOwn(patch, 'totalsLabel')) if (null === patch.totalsLabel || '' === patch.totalsLabel) delete candidate.totalsLabel;
|
|
315
|
+
else {
|
|
316
|
+
const label = normalizeSpreadsheetTableTotalsLabel(patch.totalsLabel);
|
|
317
|
+
if (!label) return null;
|
|
318
|
+
candidate.totalsLabel = label;
|
|
319
|
+
}
|
|
320
|
+
if (Object.hasOwn(patch, 'totalsFormula')) if (null === patch.totalsFormula || '' === patch.totalsFormula) delete candidate.totalsFormula;
|
|
321
|
+
else {
|
|
322
|
+
const formula = normalizeSpreadsheetTableTotalsFormula(patch.totalsFormula);
|
|
323
|
+
if (!formula) return null;
|
|
324
|
+
candidate.totalsFormula = formula;
|
|
325
|
+
}
|
|
326
|
+
if (candidate.totalsFormula) {
|
|
327
|
+
if (void 0 !== candidate.totalsFunction && 'custom' !== candidate.totalsFunction) return null;
|
|
328
|
+
candidate.totalsFunction = 'custom';
|
|
329
|
+
} else if ('custom' === candidate.totalsFunction) return null;
|
|
330
|
+
if (void 0 !== candidate.totalsFunction && 'custom' !== candidate.totalsFunction) delete candidate.totalsFormula;
|
|
331
|
+
if (void 0 !== candidate.totalsFunction && candidate.totalsLabel) delete candidate.totalsLabel;
|
|
332
|
+
next[offset] = candidate;
|
|
333
|
+
}
|
|
334
|
+
return next;
|
|
335
|
+
}
|
|
336
|
+
function withDefaultSpreadsheetTableTotalsLabel(columns, totalsRow) {
|
|
337
|
+
const next = columns.map((column)=>({
|
|
338
|
+
...column
|
|
339
|
+
}));
|
|
340
|
+
if (!totalsRow || !next.length) return next;
|
|
341
|
+
const hasExplicitLabel = next.some((column)=>column.totalsLabel);
|
|
342
|
+
if (hasExplicitLabel) return next;
|
|
343
|
+
const first = next[0];
|
|
344
|
+
if (first && !first.totalsFunction && !first.totalsFormula) first.totalsLabel = DEFAULT_SPREADSHEET_TABLE_TOTALS_LABEL;
|
|
345
|
+
return next;
|
|
346
|
+
}
|
|
347
|
+
function spreadsheetTableTotalsRowHasContent(sheet, table, row = table.totalsRow ? table.range.row[1] : table.range.row[1] + 1) {
|
|
348
|
+
for(let column = table.range.column[0]; column <= table.range.column[1]; column += 1)if (!spreadsheetTableTotalsCellIsEmpty(spreadsheetTableTotalsCellAt(sheet, row, column))) return true;
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
function synchronizeSpreadsheetTableTotalsRow(sheet, previousTable, nextTable, options = {}) {
|
|
352
|
+
if (!nextTable.totalsRow) return sheet;
|
|
353
|
+
const force = true === options.force;
|
|
354
|
+
const previousRow = previousTable?.range.row[1];
|
|
355
|
+
const nextRow = nextTable.range.row[1];
|
|
356
|
+
const width = nextTable.range.column[1] - nextTable.range.column[0] + 1;
|
|
357
|
+
const updates = new Map();
|
|
358
|
+
for(let offset = 0; offset < width; offset += 1){
|
|
359
|
+
const column = nextTable.range.column[0] + offset;
|
|
360
|
+
const nextColumn = nextTable.columns[offset];
|
|
361
|
+
if (!nextColumn) continue;
|
|
362
|
+
const previousColumn = previousTable?.columns[offset];
|
|
363
|
+
const oldFormula = previousTable ? spreadsheetTableTotalsFormula(previousTable, offset) : void 0;
|
|
364
|
+
const newFormula = spreadsheetTableTotalsFormula(nextTable, offset);
|
|
365
|
+
const oldLabel = normalizeSpreadsheetTableTotalsLabel(previousColumn?.totalsLabel);
|
|
366
|
+
const newLabel = normalizeSpreadsheetTableTotalsLabel(nextColumn.totalsLabel);
|
|
367
|
+
const changed = force || void 0 === previousTable || previousTable.name !== nextTable.name || previousTable.range.row[1] !== nextTable.range.row[1] || previousColumn?.totalsFunction !== nextColumn.totalsFunction || previousColumn?.totalsFormula !== nextColumn.totalsFormula || previousColumn?.totalsLabel !== nextColumn.totalsLabel || previousColumn?.name !== nextColumn.name;
|
|
368
|
+
const current = spreadsheetTableTotalsCellAt(sheet, nextRow, column);
|
|
369
|
+
const previousCell = void 0 === previousRow ? null : spreadsheetTableTotalsCellAt(sheet, previousRow, column);
|
|
370
|
+
const styleSource = current ?? previousCell;
|
|
371
|
+
const empty = spreadsheetTableTotalsCellIsEmpty(current);
|
|
372
|
+
const currentFormula = normalizeSpreadsheetTableTotalsFormula(current?.f);
|
|
373
|
+
const generatedFormula = Boolean(oldFormula && currentFormula && formulasEqual(currentFormula, oldFormula));
|
|
374
|
+
const generatedLabel = Boolean(oldLabel && !current?.f && spreadsheetTableCellText(current) === oldLabel);
|
|
375
|
+
const replaceable = force || empty || changed && (generatedFormula || generatedLabel);
|
|
376
|
+
if (newFormula && replaceable) updates.set(column, spreadsheetCellWithTotalsFormula(styleSource, newFormula));
|
|
377
|
+
else if (newLabel && replaceable) updates.set(column, spreadsheetCellWithTotalsLabel(styleSource, newLabel));
|
|
378
|
+
else if (!newFormula && !newLabel && changed && (generatedFormula || generatedLabel)) updates.set(column, clearSpreadsheetTableTotalsCell(current));
|
|
379
|
+
}
|
|
380
|
+
return applySpreadsheetTableTotalsCellUpdates(sheet, nextRow, updates);
|
|
381
|
+
}
|
|
382
|
+
function reconcileSpreadsheetTableTotalsColumns(sheet, table, editedOffsets) {
|
|
383
|
+
return table.columns.map((column, offset)=>{
|
|
384
|
+
const candidate = {
|
|
385
|
+
...column
|
|
386
|
+
};
|
|
387
|
+
const declaredFunction = normalizeSpreadsheetTableTotalsFunction(candidate.totalsFunction);
|
|
388
|
+
const declaredFormula = normalizeSpreadsheetTableTotalsFormula(candidate.totalsFormula);
|
|
389
|
+
if (declaredFunction) candidate.totalsFunction = declaredFunction;
|
|
390
|
+
else delete candidate.totalsFunction;
|
|
391
|
+
if (declaredFormula) candidate.totalsFormula = declaredFormula;
|
|
392
|
+
else delete candidate.totalsFormula;
|
|
393
|
+
const label = normalizeSpreadsheetTableTotalsLabel(candidate.totalsLabel);
|
|
394
|
+
if (label) candidate.totalsLabel = label;
|
|
395
|
+
else delete candidate.totalsLabel;
|
|
396
|
+
if (!table.totalsRow) return candidate;
|
|
397
|
+
const cell = spreadsheetTableTotalsCellAt(sheet, table.range.row[1], table.range.column[0] + offset);
|
|
398
|
+
if (editedOffsets && !editedOffsets.has(offset)) return candidate;
|
|
399
|
+
const cellFormula = normalizeSpreadsheetTableTotalsFormula(cell?.f);
|
|
400
|
+
const expected = spreadsheetTableTotalsFormula({
|
|
401
|
+
...table,
|
|
402
|
+
columns: [
|
|
403
|
+
{
|
|
404
|
+
...candidate
|
|
405
|
+
}
|
|
406
|
+
]
|
|
407
|
+
}, 0);
|
|
408
|
+
if (cellFormula) {
|
|
409
|
+
if (expected && formulasEqual(cellFormula, expected)) return candidate;
|
|
410
|
+
delete candidate.totalsFunction;
|
|
411
|
+
delete candidate.totalsFormula;
|
|
412
|
+
candidate.totalsFunction = 'custom';
|
|
413
|
+
candidate.totalsFormula = cellFormula;
|
|
414
|
+
return candidate;
|
|
415
|
+
}
|
|
416
|
+
delete candidate.totalsFunction;
|
|
417
|
+
delete candidate.totalsFormula;
|
|
418
|
+
const cellLabel = normalizeSpreadsheetTableTotalsLabel(spreadsheetTableCellText(cell));
|
|
419
|
+
if (cellLabel) if (candidate.totalsLabel && candidate.totalsLabel !== cellLabel) delete candidate.totalsLabel;
|
|
420
|
+
else candidate.totalsLabel = cellLabel;
|
|
421
|
+
else delete candidate.totalsLabel;
|
|
422
|
+
return candidate;
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
function escapeStructuredColumnName(value) {
|
|
426
|
+
return value.replaceAll(']', ']]');
|
|
427
|
+
}
|
|
428
|
+
function formulasEqual(left, right) {
|
|
429
|
+
return left.trim().replace(/\s+/g, '').toLocaleLowerCase() === right.trim().replace(/\s+/g, '').toLocaleLowerCase();
|
|
430
|
+
}
|
|
431
|
+
function spreadsheetTableTotalsCellAt(sheet, row, column) {
|
|
432
|
+
return sheet.data?.[row]?.[column] ?? sheet.celldata?.find((entry)=>entry.r === row && entry.c === column)?.v ?? null;
|
|
433
|
+
}
|
|
434
|
+
function spreadsheetTableTotalsCellIsEmpty(cell) {
|
|
435
|
+
return !cell?.f && (cell?.v === void 0 || null === cell.v || '' === cell.v) && (cell?.m === void 0 || null === cell.m || '' === cell.m);
|
|
436
|
+
}
|
|
437
|
+
function spreadsheetTableCellText(cell) {
|
|
438
|
+
const value = cell?.m ?? cell?.v;
|
|
439
|
+
return null == value ? '' : String(value);
|
|
440
|
+
}
|
|
441
|
+
function spreadsheetCellWithTotalsFormula(source, formula) {
|
|
442
|
+
const { f: _formula, m: _display, v: _value, ...presentation } = source ?? {};
|
|
443
|
+
return {
|
|
444
|
+
...presentation,
|
|
445
|
+
f: formula
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function spreadsheetCellWithTotalsLabel(source, label) {
|
|
449
|
+
const { f: _formula, ...withoutFormula } = source ?? {};
|
|
450
|
+
return {
|
|
451
|
+
...withoutFormula,
|
|
452
|
+
m: label,
|
|
453
|
+
v: label
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function clearSpreadsheetTableTotalsCell(source) {
|
|
457
|
+
if (!source) return null;
|
|
458
|
+
const { f: _formula, m: _display, v: _value, ...presentation } = source;
|
|
459
|
+
return Object.keys(presentation).length ? presentation : null;
|
|
460
|
+
}
|
|
461
|
+
function applySpreadsheetTableTotalsCellUpdates(sheet, rowIndex, updates) {
|
|
462
|
+
if (!updates.size) return sheet;
|
|
463
|
+
if (void 0 !== sheet.data) {
|
|
464
|
+
const data = sheet.data.slice();
|
|
465
|
+
const source = data[rowIndex];
|
|
466
|
+
const row = source ? source.slice() : [];
|
|
467
|
+
for (const [column, cell] of updates)row[column] = cell;
|
|
468
|
+
data[rowIndex] = row;
|
|
469
|
+
return {
|
|
470
|
+
...sheet,
|
|
471
|
+
data
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const entries = [
|
|
475
|
+
...sheet.celldata ?? []
|
|
476
|
+
];
|
|
477
|
+
const indexes = new Map(entries.map((entry, index)=>[
|
|
478
|
+
`${entry.r}:${entry.c}`,
|
|
479
|
+
index
|
|
480
|
+
]));
|
|
481
|
+
for (const [column, cell] of updates){
|
|
482
|
+
const key = `${rowIndex}:${column}`;
|
|
483
|
+
const index = indexes.get(key);
|
|
484
|
+
if (null === cell) {
|
|
485
|
+
if (void 0 !== index) entries.splice(index, 1);
|
|
486
|
+
if (void 0 !== index) {
|
|
487
|
+
indexes.clear();
|
|
488
|
+
entries.forEach((entry, nextIndex)=>{
|
|
489
|
+
indexes.set(`${entry.r}:${entry.c}`, nextIndex);
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const entry = {
|
|
495
|
+
r: rowIndex,
|
|
496
|
+
c: column,
|
|
497
|
+
v: cell
|
|
498
|
+
};
|
|
499
|
+
if (void 0 === index) {
|
|
500
|
+
indexes.set(key, entries.length);
|
|
501
|
+
entries.push(entry);
|
|
502
|
+
} else entries[index] = entry;
|
|
503
|
+
}
|
|
504
|
+
entries.sort((left, right)=>left.r - right.r || left.c - right.c);
|
|
505
|
+
return {
|
|
506
|
+
...sheet,
|
|
507
|
+
celldata: entries
|
|
508
|
+
};
|
|
509
|
+
}
|
|
176
510
|
function requiredCoordinate(value, maximum, label, sheetId) {
|
|
177
511
|
if (!Number.isSafeInteger(value) || value < 0 || value >= maximum) invalidWorkOfficeSpreadsheetInput(`a valid ${label} coordinate in sheet '${sheetId}'`);
|
|
178
512
|
return value;
|
|
@@ -203,7 +537,6 @@ function invalidWorkOfficeSpreadsheetInput(expected) {
|
|
|
203
537
|
const MAX_POPULATED_CELLS = 1000000;
|
|
204
538
|
const MAX_DENSE_MATRIX_CELLS = 1000000;
|
|
205
539
|
const MAX_FILTER_VALUES_PER_COLUMN = 10000;
|
|
206
|
-
const MAX_FILTER_VALUE_CHARACTERS = 32767;
|
|
207
540
|
const MAX_FILTER_TEXT_BYTES = 1048576;
|
|
208
541
|
const FILTER_TEXT_ENCODER = new TextEncoder();
|
|
209
542
|
const SPREADSHEET_DYNAMIC_FILTERS = new Set([
|
|
@@ -428,20 +761,46 @@ function requiredSpreadsheetTableColumns(value, width, label) {
|
|
|
428
761
|
const record = requiredInputRecord(candidate, `${label} column`);
|
|
429
762
|
assertOptionalRecordKeys(record, [
|
|
430
763
|
'name',
|
|
431
|
-
'calculatedFormula'
|
|
764
|
+
'calculatedFormula',
|
|
765
|
+
'totalsFunction',
|
|
766
|
+
'totalsLabel',
|
|
767
|
+
'totalsFormula'
|
|
432
768
|
], `column for ${label}`);
|
|
433
769
|
const name = requiredTableColumnName(record.name, label);
|
|
434
770
|
const normalized = name.toLocaleLowerCase();
|
|
435
771
|
if (names.has(normalized)) invalidWorkOfficeSpreadsheetInput(`unique column names for ${label}`);
|
|
436
772
|
names.add(normalized);
|
|
437
|
-
|
|
773
|
+
let calculatedFormula;
|
|
774
|
+
if (void 0 !== record.calculatedFormula) {
|
|
775
|
+
calculatedFormula = normalizeSpreadsheetTableCalculatedFormula(record.calculatedFormula);
|
|
776
|
+
if (!calculatedFormula || calculatedFormula !== record.calculatedFormula) invalidWorkOfficeSpreadsheetInput(`a bounded structured calculated-column formula for ${label}`);
|
|
777
|
+
}
|
|
778
|
+
const totalsFunction = void 0 === record.totalsFunction ? void 0 : normalizeSpreadsheetTableTotalsFunction(record.totalsFunction);
|
|
779
|
+
if (void 0 !== record.totalsFunction && (!totalsFunction || totalsFunction !== record.totalsFunction)) invalidWorkOfficeSpreadsheetInput(`a supported totals-row function for ${label}`);
|
|
780
|
+
const totalsLabel = void 0 === record.totalsLabel ? void 0 : normalizeSpreadsheetTableTotalsLabel(record.totalsLabel);
|
|
781
|
+
if (void 0 !== record.totalsLabel && (!totalsLabel || totalsLabel !== record.totalsLabel)) invalidWorkOfficeSpreadsheetInput(`a bounded totals-row label for ${label}`);
|
|
782
|
+
const totalsFormula = void 0 === record.totalsFormula ? void 0 : normalizeSpreadsheetTableTotalsFormula(record.totalsFormula);
|
|
783
|
+
if (void 0 !== record.totalsFormula && (!totalsFormula || totalsFormula !== record.totalsFormula)) invalidWorkOfficeSpreadsheetInput(`a bounded totals-row formula for ${label}`);
|
|
784
|
+
if (totalsFormula && 'custom' !== totalsFunction) invalidWorkOfficeSpreadsheetInput(`custom totals-row formulas to declare the custom function for ${label}`);
|
|
785
|
+
if ('custom' === totalsFunction && !totalsFormula) invalidWorkOfficeSpreadsheetInput(`custom totals-row functions to include a formula for ${label}`);
|
|
786
|
+
if ((totalsFunction || totalsFormula) && totalsLabel) invalidWorkOfficeSpreadsheetInput(`totals-row labels not to share a cell with a formula for ${label}`);
|
|
787
|
+
if (!calculatedFormula && !totalsFunction && !totalsLabel && !totalsFormula) return {
|
|
438
788
|
name
|
|
439
789
|
};
|
|
440
|
-
const calculatedFormula = normalizeSpreadsheetTableCalculatedFormula(record.calculatedFormula);
|
|
441
|
-
if (!calculatedFormula || calculatedFormula !== record.calculatedFormula) invalidWorkOfficeSpreadsheetInput(`a bounded structured calculated-column formula for ${label}`);
|
|
442
790
|
return {
|
|
443
791
|
name,
|
|
444
|
-
calculatedFormula
|
|
792
|
+
...calculatedFormula ? {
|
|
793
|
+
calculatedFormula
|
|
794
|
+
} : {},
|
|
795
|
+
...totalsFunction ? {
|
|
796
|
+
totalsFunction
|
|
797
|
+
} : {},
|
|
798
|
+
...totalsLabel ? {
|
|
799
|
+
totalsLabel
|
|
800
|
+
} : {},
|
|
801
|
+
...totalsFormula ? {
|
|
802
|
+
totalsFormula
|
|
803
|
+
} : {}
|
|
445
804
|
};
|
|
446
805
|
});
|
|
447
806
|
}
|
|
@@ -504,19 +863,16 @@ function requiredSpreadsheetTableFilterCriteria(value, label, textBudget) {
|
|
|
504
863
|
case 'contains':
|
|
505
864
|
case 'does-not-contain':
|
|
506
865
|
case 'begins-with':
|
|
866
|
+
case 'does-not-begin-with':
|
|
507
867
|
case 'ends-with':
|
|
868
|
+
case 'does-not-end-with':
|
|
869
|
+
case 'matches-wildcard':
|
|
870
|
+
case 'does-not-match-wildcard':
|
|
508
871
|
case 'greater-than':
|
|
509
872
|
case 'greater-than-or-equal':
|
|
510
873
|
case 'less-than':
|
|
511
874
|
case 'less-than-or-equal':
|
|
512
|
-
|
|
513
|
-
'type',
|
|
514
|
-
'value'
|
|
515
|
-
], `filter criteria for ${label}`);
|
|
516
|
-
return {
|
|
517
|
-
type,
|
|
518
|
-
value: requiredSpreadsheetFilterText(record.value, label, textBudget)
|
|
519
|
-
};
|
|
875
|
+
return requiredSpreadsheetCustomFilterCondition(record, label, textBudget);
|
|
520
876
|
case 'between':
|
|
521
877
|
case 'not-between':
|
|
522
878
|
assertExactRecordKeys(record, [
|
|
@@ -537,6 +893,22 @@ function requiredSpreadsheetTableFilterCriteria(value, label, textBudget) {
|
|
|
537
893
|
return {
|
|
538
894
|
type
|
|
539
895
|
};
|
|
896
|
+
case 'compound':
|
|
897
|
+
assertExactRecordKeys(record, [
|
|
898
|
+
'type',
|
|
899
|
+
'conjunction',
|
|
900
|
+
'conditions'
|
|
901
|
+
], `filter criteria for ${label}`);
|
|
902
|
+
if ('and' !== record.conjunction && 'or' !== record.conjunction) invalidWorkOfficeSpreadsheetInput(`an 'and' or 'or' conjunction for compound filter criteria for ${label}`);
|
|
903
|
+
if (!Array.isArray(record.conditions) || 2 !== record.conditions.length) invalidWorkOfficeSpreadsheetInput(`exactly two custom filter conditions for ${label}`);
|
|
904
|
+
return {
|
|
905
|
+
type,
|
|
906
|
+
conjunction: record.conjunction,
|
|
907
|
+
conditions: [
|
|
908
|
+
requiredSpreadsheetCustomFilterCondition(record.conditions[0], label, textBudget),
|
|
909
|
+
requiredSpreadsheetCustomFilterCondition(record.conditions[1], label, textBudget)
|
|
910
|
+
]
|
|
911
|
+
};
|
|
540
912
|
case 'top':
|
|
541
913
|
case 'bottom':
|
|
542
914
|
assertExactRecordKeys(record, [
|
|
@@ -571,15 +943,44 @@ function requiredSpreadsheetTableFilterCriteria(value, label, textBudget) {
|
|
|
571
943
|
invalidWorkOfficeSpreadsheetInput(`supported filter criteria for ${label}`);
|
|
572
944
|
}
|
|
573
945
|
}
|
|
946
|
+
function requiredSpreadsheetCustomFilterCondition(value, label, textBudget) {
|
|
947
|
+
const record = validateJsonRecord(requiredInputRecord(value, `custom filter condition for ${label}`), `custom filter condition for ${label}`);
|
|
948
|
+
switch(record.type){
|
|
949
|
+
case 'equals':
|
|
950
|
+
case 'not-equals':
|
|
951
|
+
case 'contains':
|
|
952
|
+
case 'does-not-contain':
|
|
953
|
+
case 'begins-with':
|
|
954
|
+
case 'does-not-begin-with':
|
|
955
|
+
case 'ends-with':
|
|
956
|
+
case 'does-not-end-with':
|
|
957
|
+
case 'matches-wildcard':
|
|
958
|
+
case 'does-not-match-wildcard':
|
|
959
|
+
case 'greater-than':
|
|
960
|
+
case 'greater-than-or-equal':
|
|
961
|
+
case 'less-than':
|
|
962
|
+
case 'less-than-or-equal':
|
|
963
|
+
assertExactRecordKeys(record, [
|
|
964
|
+
'type',
|
|
965
|
+
'value'
|
|
966
|
+
], `custom filter condition for ${label}`);
|
|
967
|
+
return {
|
|
968
|
+
type: record.type,
|
|
969
|
+
value: requiredSpreadsheetFilterText(record.value, label, textBudget)
|
|
970
|
+
};
|
|
971
|
+
default:
|
|
972
|
+
invalidWorkOfficeSpreadsheetInput(`supported custom filter condition for ${label}`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
574
975
|
function requiredSpreadsheetFilterText(value, label, textBudget) {
|
|
575
|
-
if ('string' != typeof value) invalidWorkOfficeSpreadsheetInput(`filter text containing 1 to ${
|
|
976
|
+
if ('string' != typeof value) invalidWorkOfficeSpreadsheetInput(`filter text containing 1 to ${32767..toLocaleString()} characters for ${label}`);
|
|
576
977
|
let characters = 0;
|
|
577
978
|
for (const character of value){
|
|
578
979
|
characters += 1;
|
|
579
980
|
const codePoint = character.codePointAt(0);
|
|
580
981
|
if (void 0 === codePoint || !(0x9 === codePoint || 0xa === codePoint || 0xd === codePoint || codePoint >= 0x20 && codePoint <= 0xd7ff || codePoint >= 0xe000 && codePoint <= 0xfffd || codePoint >= 0x10000 && codePoint <= 0x10ffff)) invalidWorkOfficeSpreadsheetInput(`XML-compatible filter text for ${label}`);
|
|
581
982
|
}
|
|
582
|
-
if (characters < 1 || characters >
|
|
983
|
+
if (characters < 1 || characters > 32767) invalidWorkOfficeSpreadsheetInput(`filter text containing 1 to ${32767..toLocaleString()} characters for ${label}`);
|
|
583
984
|
textBudget.bytes += FILTER_TEXT_ENCODER.encode(value).byteLength;
|
|
584
985
|
if (textBudget.bytes > MAX_FILTER_TEXT_BYTES) invalidWorkOfficeSpreadsheetInput(`filter text totaling at most ${MAX_FILTER_TEXT_BYTES.toLocaleString()} bytes for ${label}`);
|
|
585
986
|
return value;
|
|
@@ -1800,7 +2201,867 @@ function assertInitializedSpreadsheetSession(session) {
|
|
|
1800
2201
|
if (metadata?.initialized) return;
|
|
1801
2202
|
throw new WorkOfficeCollaborationError('office.collaboration.not_initialized', 'The Spreadsheet collaboration session has not been initialized.');
|
|
1802
2203
|
}
|
|
2204
|
+
const WORK_SPREADSHEET_FILTER_TEXT_MAX_CHARACTERS = 32767;
|
|
2205
|
+
function workSpreadsheetFilterTextCharacters(value) {
|
|
2206
|
+
let characters = 0;
|
|
2207
|
+
for (const _character of value)characters += 1;
|
|
2208
|
+
return characters;
|
|
2209
|
+
}
|
|
2210
|
+
function workSpreadsheetFilterTextIsBounded(value) {
|
|
2211
|
+
const characters = workSpreadsheetFilterTextCharacters(value);
|
|
2212
|
+
return characters >= 1 && characters <= WORK_SPREADSHEET_FILTER_TEXT_MAX_CHARACTERS;
|
|
2213
|
+
}
|
|
1803
2214
|
const MILLISECONDS_PER_DAY = 86400000;
|
|
2215
|
+
const EXCEL_1900_EPOCH_UTC = Date.UTC(1899, 11, 30);
|
|
2216
|
+
const EXCEL_1900_CIVIL_EPOCH_UTC = Date.UTC(1899, 11, 31);
|
|
2217
|
+
const EXCEL_1900_LOCAL_BASE = new Date(1899, 11, 30);
|
|
2218
|
+
const SERIAL_INTEGER_EPSILON = 1e-7;
|
|
2219
|
+
const CALENDAR_MONTH_FILTERS = [
|
|
2220
|
+
'month-1',
|
|
2221
|
+
'month-2',
|
|
2222
|
+
'month-3',
|
|
2223
|
+
'month-4',
|
|
2224
|
+
'month-5',
|
|
2225
|
+
'month-6',
|
|
2226
|
+
'month-7',
|
|
2227
|
+
'month-8',
|
|
2228
|
+
'month-9',
|
|
2229
|
+
'month-10',
|
|
2230
|
+
'month-11',
|
|
2231
|
+
'month-12'
|
|
2232
|
+
];
|
|
2233
|
+
const CALENDAR_QUARTER_FILTERS = [
|
|
2234
|
+
'quarter-1',
|
|
2235
|
+
'quarter-2',
|
|
2236
|
+
'quarter-3',
|
|
2237
|
+
'quarter-4'
|
|
2238
|
+
];
|
|
2239
|
+
function workSpreadsheetDynamicFilterMatcher(kind, cells, context) {
|
|
2240
|
+
if ('above-average' === kind || 'below-average' === kind) return averageMatcher(kind, cells);
|
|
2241
|
+
const month = CALENDAR_MONTH_FILTERS.indexOf(kind);
|
|
2242
|
+
if (month >= 0) return (cell)=>spreadsheetDateParts(cell)?.month === month;
|
|
2243
|
+
const quarter = CALENDAR_QUARTER_FILTERS.indexOf(kind);
|
|
2244
|
+
if (quarter >= 0) return (cell)=>spreadsheetDateParts(cell)?.quarter === quarter;
|
|
2245
|
+
const bounds = relativeDateBounds(kind, context.now);
|
|
2246
|
+
if (!bounds) return null;
|
|
2247
|
+
return (cell)=>{
|
|
2248
|
+
const day = spreadsheetDateSerialDay(cell);
|
|
2249
|
+
return null !== day && day >= bounds.start && day < bounds.end;
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
function workSpreadsheetCellIsDate(cell) {
|
|
2253
|
+
return null !== spreadsheetDateSerialDay(cell);
|
|
2254
|
+
}
|
|
2255
|
+
function averageMatcher(kind, cells) {
|
|
2256
|
+
const average = finiteArithmeticMean(cells);
|
|
2257
|
+
if (null === average) return ()=>false;
|
|
2258
|
+
return 'above-average' === kind ? (cell)=>{
|
|
2259
|
+
const value = spreadsheetNumericCellValue(cell);
|
|
2260
|
+
return null !== value && value > average;
|
|
2261
|
+
} : (cell)=>{
|
|
2262
|
+
const value = spreadsheetNumericCellValue(cell);
|
|
2263
|
+
return null !== value && value < average;
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
function finiteArithmeticMean(cells) {
|
|
2267
|
+
let count = 0;
|
|
2268
|
+
let scale = 0;
|
|
2269
|
+
let scaledTotal = 0;
|
|
2270
|
+
for (const cell of cells){
|
|
2271
|
+
const value = spreadsheetNumericCellValue(cell);
|
|
2272
|
+
if (null === value) continue;
|
|
2273
|
+
count += 1;
|
|
2274
|
+
const magnitude = Math.abs(value);
|
|
2275
|
+
if (magnitude > scale) {
|
|
2276
|
+
scaledTotal = scale / magnitude * scaledTotal + value / magnitude;
|
|
2277
|
+
scale = magnitude;
|
|
2278
|
+
} else if (scale > 0) scaledTotal += value / scale;
|
|
2279
|
+
}
|
|
2280
|
+
return count ? scaledTotal / count * scale : null;
|
|
2281
|
+
}
|
|
2282
|
+
function relativeDateBounds(kind, now) {
|
|
2283
|
+
const timestamp = now.getTime();
|
|
2284
|
+
const year = now.getFullYear();
|
|
2285
|
+
if (!Number.isFinite(timestamp) || !Number.isSafeInteger(year) || year < 1900 || year > 9999) return null;
|
|
2286
|
+
const month = now.getMonth();
|
|
2287
|
+
const today = excelSerialFromCivilDate(year, month, now.getDate());
|
|
2288
|
+
if ('yesterday' === kind) return dayBounds(today - 1);
|
|
2289
|
+
if ('today' === kind) return dayBounds(today);
|
|
2290
|
+
if ('tomorrow' === kind) return dayBounds(today + 1);
|
|
2291
|
+
const weekStart = today - now.getDay();
|
|
2292
|
+
if ('last-week' === kind) return rangeBounds(weekStart - 7, weekStart);
|
|
2293
|
+
if ('this-week' === kind) return rangeBounds(weekStart, weekStart + 7);
|
|
2294
|
+
if ('next-week' === kind) return rangeBounds(weekStart + 7, weekStart + 14);
|
|
2295
|
+
const monthStart = excelSerialFromCivilDate(year, month, 1);
|
|
2296
|
+
if ('last-month' === kind) return rangeBounds(excelSerialFromCivilDate(year, month - 1, 1), monthStart);
|
|
2297
|
+
if ('this-month' === kind) return rangeBounds(monthStart, excelSerialFromCivilDate(year, month + 1, 1));
|
|
2298
|
+
if ('next-month' === kind) return rangeBounds(excelSerialFromCivilDate(year, month + 1, 1), excelSerialFromCivilDate(year, month + 2, 1));
|
|
2299
|
+
const quarterStartMonth = 3 * Math.floor(month / 3);
|
|
2300
|
+
const quarterStart = excelSerialFromCivilDate(year, quarterStartMonth, 1);
|
|
2301
|
+
if ('last-quarter' === kind) return rangeBounds(excelSerialFromCivilDate(year, quarterStartMonth - 3, 1), quarterStart);
|
|
2302
|
+
if ('this-quarter' === kind) return rangeBounds(quarterStart, excelSerialFromCivilDate(year, quarterStartMonth + 3, 1));
|
|
2303
|
+
if ('next-quarter' === kind) return rangeBounds(excelSerialFromCivilDate(year, quarterStartMonth + 3, 1), excelSerialFromCivilDate(year, quarterStartMonth + 6, 1));
|
|
2304
|
+
const yearStart = excelSerialFromCivilDate(year, 0, 1);
|
|
2305
|
+
if ('last-year' === kind) return rangeBounds(excelSerialFromCivilDate(year - 1, 0, 1), yearStart);
|
|
2306
|
+
if ('this-year' === kind) return rangeBounds(yearStart, excelSerialFromCivilDate(year + 1, 0, 1));
|
|
2307
|
+
if ('next-year' === kind) return rangeBounds(excelSerialFromCivilDate(year + 1, 0, 1), excelSerialFromCivilDate(year + 2, 0, 1));
|
|
2308
|
+
if ('year-to-date' === kind) return rangeBounds(yearStart, today + 1);
|
|
2309
|
+
return null;
|
|
2310
|
+
}
|
|
2311
|
+
function spreadsheetDateParts(cell) {
|
|
2312
|
+
const day = spreadsheetDateSerialDay(cell);
|
|
2313
|
+
if (null === day) return null;
|
|
2314
|
+
const adjustedDay = day > 60 ? day - 1 : day;
|
|
2315
|
+
const date = new Date(EXCEL_1900_CIVIL_EPOCH_UTC + adjustedDay * MILLISECONDS_PER_DAY);
|
|
2316
|
+
const timestamp = date.getTime();
|
|
2317
|
+
const year = date.getUTCFullYear();
|
|
2318
|
+
if (!Number.isFinite(timestamp) || year < 1900 || year > 9999) return null;
|
|
2319
|
+
const month = date.getUTCMonth();
|
|
2320
|
+
return {
|
|
2321
|
+
month,
|
|
2322
|
+
quarter: Math.floor(month / 3)
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
function spreadsheetDateSerialDay(cell) {
|
|
2326
|
+
const serial = spreadsheetDateCellSerial(cell);
|
|
2327
|
+
if (null === serial) return null;
|
|
2328
|
+
const rounded = Math.round(serial);
|
|
2329
|
+
const normalized = Math.abs(serial - rounded) <= SERIAL_INTEGER_EPSILON ? rounded : serial;
|
|
2330
|
+
const day = Math.floor(normalized);
|
|
2331
|
+
return day >= 1 && 60 !== day ? day : null;
|
|
2332
|
+
}
|
|
2333
|
+
function spreadsheetDateCellSerial(cell) {
|
|
2334
|
+
const value = spreadsheetCellRawValue(cell);
|
|
2335
|
+
if (value instanceof Date) {
|
|
2336
|
+
if (!Number.isFinite(value.getTime())) return null;
|
|
2337
|
+
const threshold = EXCEL_1900_LOCAL_BASE.getTime() + (value.getTimezoneOffset() - EXCEL_1900_LOCAL_BASE.getTimezoneOffset()) * 60000;
|
|
2338
|
+
const serial = (value.getTime() - threshold) / MILLISECONDS_PER_DAY;
|
|
2339
|
+
return Number.isFinite(serial) ? serial : null;
|
|
2340
|
+
}
|
|
2341
|
+
return 'number' == typeof value && Number.isFinite(value) && cell?.ct?.t === 'd' ? value : null;
|
|
2342
|
+
}
|
|
2343
|
+
function spreadsheetNumericCellValue(cell) {
|
|
2344
|
+
if (null !== spreadsheetDateCellSerial(cell)) return null;
|
|
2345
|
+
const value = spreadsheetCellRawValue(cell);
|
|
2346
|
+
return 'number' == typeof value && Number.isFinite(value) ? value : null;
|
|
2347
|
+
}
|
|
2348
|
+
function spreadsheetCellRawValue(cell) {
|
|
2349
|
+
const candidate = cell;
|
|
2350
|
+
return candidate?.v ?? candidate?.m;
|
|
2351
|
+
}
|
|
2352
|
+
function excelSerialFromCivilDate(year, month, day) {
|
|
2353
|
+
return (Date.UTC(year, month, day) - EXCEL_1900_EPOCH_UTC) / MILLISECONDS_PER_DAY;
|
|
2354
|
+
}
|
|
2355
|
+
function dayBounds(day) {
|
|
2356
|
+
return {
|
|
2357
|
+
start: day,
|
|
2358
|
+
end: day + 1
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
function rangeBounds(start, end) {
|
|
2362
|
+
return {
|
|
2363
|
+
start,
|
|
2364
|
+
end
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
const WILDCARD_ESCAPES = new Set([
|
|
2368
|
+
'*',
|
|
2369
|
+
'?',
|
|
2370
|
+
'~'
|
|
2371
|
+
]);
|
|
2372
|
+
const BITSET_MINIMUM_SEARCH_WORK = 1000000;
|
|
2373
|
+
const BITSET_MINIMUM_TOKENS = 256;
|
|
2374
|
+
const BITSET_MAXIMUM_LITERAL_VALUES = 256;
|
|
2375
|
+
function workSpreadsheetWildcardMatcher(source) {
|
|
2376
|
+
const tokens = wildcardTokens(source);
|
|
2377
|
+
const literalMatcher = literalWildcardMatcher(tokens);
|
|
2378
|
+
if (literalMatcher) return (value)=>literalMatcher(comparableWildcardText(value));
|
|
2379
|
+
const fixedWidthMatcher = fixedWidthWildcardMatcher(tokens);
|
|
2380
|
+
return (value)=>fixedWidthMatcher(comparableWildcardText(value));
|
|
2381
|
+
}
|
|
2382
|
+
function workSpreadsheetHasUnescapedWildcard(source) {
|
|
2383
|
+
const characters = [
|
|
2384
|
+
...source
|
|
2385
|
+
];
|
|
2386
|
+
for(let index = 0; index < characters.length; index += 1){
|
|
2387
|
+
const character = characters[index];
|
|
2388
|
+
if ('~' === character && WILDCARD_ESCAPES.has(characters[index + 1] ?? '')) {
|
|
2389
|
+
index += 1;
|
|
2390
|
+
continue;
|
|
2391
|
+
}
|
|
2392
|
+
if ('*' === character || '?' === character) return true;
|
|
2393
|
+
}
|
|
2394
|
+
return false;
|
|
2395
|
+
}
|
|
2396
|
+
function wildcardTokens(source) {
|
|
2397
|
+
const characters = [
|
|
2398
|
+
...source
|
|
2399
|
+
];
|
|
2400
|
+
const tokens = [];
|
|
2401
|
+
let literal = '';
|
|
2402
|
+
const flushLiteral = ()=>{
|
|
2403
|
+
if (!literal) return;
|
|
2404
|
+
for (const value of comparableWildcardText(literal))tokens.push({
|
|
2405
|
+
kind: 'literal',
|
|
2406
|
+
value
|
|
2407
|
+
});
|
|
2408
|
+
literal = '';
|
|
2409
|
+
};
|
|
2410
|
+
for(let index = 0; index < characters.length; index += 1){
|
|
2411
|
+
const character = characters[index] ?? '';
|
|
2412
|
+
const escaped = characters[index + 1] ?? '';
|
|
2413
|
+
if ('~' === character && WILDCARD_ESCAPES.has(escaped)) {
|
|
2414
|
+
literal += escaped;
|
|
2415
|
+
index += 1;
|
|
2416
|
+
continue;
|
|
2417
|
+
}
|
|
2418
|
+
if ('*' === character) {
|
|
2419
|
+
flushLiteral();
|
|
2420
|
+
if (tokens.at(-1)?.kind !== 'many') tokens.push({
|
|
2421
|
+
kind: 'many'
|
|
2422
|
+
});
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
if ('?' === character) {
|
|
2426
|
+
flushLiteral();
|
|
2427
|
+
tokens.push({
|
|
2428
|
+
kind: 'single'
|
|
2429
|
+
});
|
|
2430
|
+
continue;
|
|
2431
|
+
}
|
|
2432
|
+
literal += character;
|
|
2433
|
+
}
|
|
2434
|
+
flushLiteral();
|
|
2435
|
+
return tokens;
|
|
2436
|
+
}
|
|
2437
|
+
function literalWildcardMatcher(tokens) {
|
|
2438
|
+
if (tokens.some((token)=>'single' === token.kind)) return null;
|
|
2439
|
+
const startsWithMany = tokens[0]?.kind === 'many';
|
|
2440
|
+
const endsWithMany = tokens.at(-1)?.kind === 'many';
|
|
2441
|
+
const segments = [];
|
|
2442
|
+
let segment = '';
|
|
2443
|
+
for (const token of tokens){
|
|
2444
|
+
if ('many' === token.kind) {
|
|
2445
|
+
if (segment) segments.push(segment);
|
|
2446
|
+
segment = '';
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
if ('literal' !== token.kind) return null;
|
|
2450
|
+
segment += token.value;
|
|
2451
|
+
}
|
|
2452
|
+
if (segment) segments.push(segment);
|
|
2453
|
+
if (!segments.length) return tokens.length ? ()=>true : (value)=>'' === value;
|
|
2454
|
+
if (!startsWithMany && !endsWithMany && 1 === segments.length) return (value)=>value === segments[0];
|
|
2455
|
+
return (value)=>{
|
|
2456
|
+
let minimumIndex = 0;
|
|
2457
|
+
let segmentIndex = 0;
|
|
2458
|
+
if (!startsWithMany) {
|
|
2459
|
+
const first = segments[0] ?? '';
|
|
2460
|
+
if (!value.startsWith(first)) return false;
|
|
2461
|
+
minimumIndex = first.length;
|
|
2462
|
+
segmentIndex = 1;
|
|
2463
|
+
}
|
|
2464
|
+
const searchableSegments = endsWithMany ? segments.length : segments.length - 1;
|
|
2465
|
+
for(; segmentIndex < searchableSegments; segmentIndex += 1){
|
|
2466
|
+
const current = segments[segmentIndex] ?? '';
|
|
2467
|
+
const found = value.indexOf(current, minimumIndex);
|
|
2468
|
+
if (found < 0) return false;
|
|
2469
|
+
minimumIndex = found + current.length;
|
|
2470
|
+
}
|
|
2471
|
+
if (endsWithMany) return true;
|
|
2472
|
+
const last = segments.at(-1) ?? '';
|
|
2473
|
+
return value.endsWith(last) && value.length - last.length >= minimumIndex;
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
function fixedWidthWildcardMatcher(tokens) {
|
|
2477
|
+
const startsWithMany = tokens[0]?.kind === 'many';
|
|
2478
|
+
const endsWithMany = tokens.at(-1)?.kind === 'many';
|
|
2479
|
+
const segments = [];
|
|
2480
|
+
let current = [];
|
|
2481
|
+
for (const token of tokens){
|
|
2482
|
+
if ('many' === token.kind) {
|
|
2483
|
+
if (current.length) segments.push(fixedWidthWildcardSegment(current));
|
|
2484
|
+
current = [];
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
current.push(token);
|
|
2488
|
+
}
|
|
2489
|
+
if (current.length) segments.push(fixedWidthWildcardSegment(current));
|
|
2490
|
+
if (!segments.length) return ()=>true;
|
|
2491
|
+
if (!startsWithMany && !endsWithMany && 1 === segments.length) {
|
|
2492
|
+
const only = segments[0];
|
|
2493
|
+
return (value)=>{
|
|
2494
|
+
const characters = [
|
|
2495
|
+
...value
|
|
2496
|
+
];
|
|
2497
|
+
return characters.length === only.tokens.length && fixedWidthWildcardSegmentMatches(only, characters, 0);
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
return (value)=>{
|
|
2501
|
+
const characters = [
|
|
2502
|
+
...value
|
|
2503
|
+
];
|
|
2504
|
+
let minimumIndex = 0;
|
|
2505
|
+
let segmentIndex = 0;
|
|
2506
|
+
let maximumEnd = characters.length;
|
|
2507
|
+
let finalSegment = segments.length;
|
|
2508
|
+
if (!startsWithMany) {
|
|
2509
|
+
const first = segments[0];
|
|
2510
|
+
if (!fixedWidthWildcardSegmentMatches(first, characters, 0)) return false;
|
|
2511
|
+
minimumIndex = first.tokens.length;
|
|
2512
|
+
segmentIndex = 1;
|
|
2513
|
+
}
|
|
2514
|
+
if (!endsWithMany) {
|
|
2515
|
+
finalSegment -= 1;
|
|
2516
|
+
const last = segments[finalSegment];
|
|
2517
|
+
const start = characters.length - last.tokens.length;
|
|
2518
|
+
if (start < minimumIndex || !fixedWidthWildcardSegmentMatches(last, characters, start)) return false;
|
|
2519
|
+
maximumEnd = start;
|
|
2520
|
+
}
|
|
2521
|
+
if (segmentIndex >= finalSegment) return true;
|
|
2522
|
+
const offsets = wildcardCodePointOffsets(characters);
|
|
2523
|
+
for(; segmentIndex < finalSegment; segmentIndex += 1){
|
|
2524
|
+
const segment = segments[segmentIndex];
|
|
2525
|
+
const found = findFixedWidthWildcardSegment(segment, value, characters, offsets, minimumIndex, maximumEnd);
|
|
2526
|
+
if (found < 0) return false;
|
|
2527
|
+
minimumIndex = found + segment.tokens.length;
|
|
2528
|
+
}
|
|
2529
|
+
return true;
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
function fixedWidthWildcardSegment(tokens) {
|
|
2533
|
+
let anchor = null;
|
|
2534
|
+
let literalStart = -1;
|
|
2535
|
+
const considerLiteralRun = (end)=>{
|
|
2536
|
+
if (literalStart < 0) return;
|
|
2537
|
+
const text = tokens.slice(literalStart, end).map((token)=>'literal' === token.kind ? token.value : '').join('');
|
|
2538
|
+
if (!anchor || end - literalStart >= [
|
|
2539
|
+
...anchor.text
|
|
2540
|
+
].length) anchor = {
|
|
2541
|
+
offset: literalStart,
|
|
2542
|
+
text
|
|
2543
|
+
};
|
|
2544
|
+
literalStart = -1;
|
|
2545
|
+
};
|
|
2546
|
+
for(let index = 0; index <= tokens.length; index += 1)if (tokens[index]?.kind === 'literal') {
|
|
2547
|
+
if (literalStart < 0) literalStart = index;
|
|
2548
|
+
} else considerLiteralRun(index);
|
|
2549
|
+
return {
|
|
2550
|
+
anchor,
|
|
2551
|
+
bitset: fixedWidthWildcardBitset(tokens),
|
|
2552
|
+
tokens
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
function findFixedWidthWildcardSegment(segment, value, characters, offsets, minimumIndex, maximumEnd) {
|
|
2556
|
+
if (minimumIndex + segment.tokens.length > maximumEnd) return -1;
|
|
2557
|
+
if (!segment.anchor) return minimumIndex;
|
|
2558
|
+
if (segment.bitset && segment.tokens.length * (maximumEnd - minimumIndex) >= BITSET_MINIMUM_SEARCH_WORK) return findFixedWidthWildcardSegmentWithBitset(segment, characters, minimumIndex, maximumEnd);
|
|
2559
|
+
const minimumAnchorIndex = minimumIndex + segment.anchor.offset;
|
|
2560
|
+
let anchorOffset = value.indexOf(segment.anchor.text, offsets[minimumAnchorIndex] ?? value.length);
|
|
2561
|
+
while(anchorOffset >= 0){
|
|
2562
|
+
const anchorIndex = codePointIndexAtOffset(offsets, anchorOffset);
|
|
2563
|
+
if (anchorIndex >= 0) {
|
|
2564
|
+
const start = anchorIndex - segment.anchor.offset;
|
|
2565
|
+
if (start + segment.tokens.length > maximumEnd) return -1;
|
|
2566
|
+
if (start >= minimumIndex && fixedWidthWildcardSegmentMatches(segment, characters, start)) return start;
|
|
2567
|
+
}
|
|
2568
|
+
anchorOffset = value.indexOf(segment.anchor.text, anchorOffset + 1);
|
|
2569
|
+
}
|
|
2570
|
+
return -1;
|
|
2571
|
+
}
|
|
2572
|
+
function fixedWidthWildcardBitset(tokens) {
|
|
2573
|
+
if (tokens.length < BITSET_MINIMUM_TOKENS) return null;
|
|
2574
|
+
const literalValues = new Set(tokens.flatMap((token)=>'literal' === token.kind ? [
|
|
2575
|
+
token.value
|
|
2576
|
+
] : []));
|
|
2577
|
+
if (literalValues.size > BITSET_MAXIMUM_LITERAL_VALUES) return null;
|
|
2578
|
+
const literalMasks = new Map();
|
|
2579
|
+
let singleMask = 0n;
|
|
2580
|
+
for(let index = 0; index < tokens.length; index += 1){
|
|
2581
|
+
const token = tokens[index];
|
|
2582
|
+
const bit = 1n << BigInt(index);
|
|
2583
|
+
if ('single' === token.kind) {
|
|
2584
|
+
singleMask |= bit;
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
literalMasks.set(token.value, (literalMasks.get(token.value) ?? 0n) | bit);
|
|
2588
|
+
}
|
|
2589
|
+
return {
|
|
2590
|
+
literalMasks,
|
|
2591
|
+
matchBit: 1n << BigInt(tokens.length - 1),
|
|
2592
|
+
singleMask
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
function findFixedWidthWildcardSegmentWithBitset(segment, characters, minimumIndex, maximumEnd) {
|
|
2596
|
+
const bitset = segment.bitset;
|
|
2597
|
+
if (!bitset) return -1;
|
|
2598
|
+
let state = 0n;
|
|
2599
|
+
for(let index = minimumIndex; index < maximumEnd; index += 1){
|
|
2600
|
+
const mask = bitset.singleMask | (bitset.literalMasks.get(characters[index]) ?? 0n);
|
|
2601
|
+
state = (state << 1n | 1n) & mask;
|
|
2602
|
+
if ((state & bitset.matchBit) !== 0n) return index - segment.tokens.length + 1;
|
|
2603
|
+
}
|
|
2604
|
+
return -1;
|
|
2605
|
+
}
|
|
2606
|
+
function fixedWidthWildcardSegmentMatches(segment, characters, start) {
|
|
2607
|
+
if (start < 0 || start + segment.tokens.length > characters.length) return false;
|
|
2608
|
+
return segment.tokens.every((token, index)=>'single' === token.kind || token.value === characters[start + index]);
|
|
2609
|
+
}
|
|
2610
|
+
function wildcardCodePointOffsets(characters) {
|
|
2611
|
+
const offsets = [
|
|
2612
|
+
0
|
|
2613
|
+
];
|
|
2614
|
+
for (const character of characters)offsets.push((offsets.at(-1) ?? 0) + character.length);
|
|
2615
|
+
return offsets;
|
|
2616
|
+
}
|
|
2617
|
+
function codePointIndexAtOffset(offsets, target) {
|
|
2618
|
+
let low = 0;
|
|
2619
|
+
let high = offsets.length - 1;
|
|
2620
|
+
while(low <= high){
|
|
2621
|
+
const middle = Math.floor((low + high) / 2);
|
|
2622
|
+
const value = offsets[middle] ?? -1;
|
|
2623
|
+
if (value === target) return middle;
|
|
2624
|
+
if (value < target) low = middle + 1;
|
|
2625
|
+
else high = middle - 1;
|
|
2626
|
+
}
|
|
2627
|
+
return -1;
|
|
2628
|
+
}
|
|
2629
|
+
function comparableWildcardText(value) {
|
|
2630
|
+
return value.normalize('NFKC').toLocaleLowerCase('zh-CN');
|
|
2631
|
+
}
|
|
2632
|
+
const AUTO_FILTER_CRITERIA_KIND = 'a3s-office-auto-filter-criteria';
|
|
2633
|
+
const AUTO_FILTER_CRITERIA_VERSION = 1;
|
|
2634
|
+
const SINGLE_VALUE_FILTER_TYPES = new Set([
|
|
2635
|
+
'equals',
|
|
2636
|
+
'not-equals',
|
|
2637
|
+
'contains',
|
|
2638
|
+
'does-not-contain',
|
|
2639
|
+
'begins-with',
|
|
2640
|
+
'does-not-begin-with',
|
|
2641
|
+
'ends-with',
|
|
2642
|
+
'does-not-end-with',
|
|
2643
|
+
'matches-wildcard',
|
|
2644
|
+
'does-not-match-wildcard',
|
|
2645
|
+
'greater-than',
|
|
2646
|
+
'greater-than-or-equal',
|
|
2647
|
+
'less-than',
|
|
2648
|
+
'less-than-or-equal'
|
|
2649
|
+
]);
|
|
2650
|
+
const DYNAMIC_FILTER_KINDS = new Set([
|
|
2651
|
+
'above-average',
|
|
2652
|
+
'below-average',
|
|
2653
|
+
'tomorrow',
|
|
2654
|
+
'today',
|
|
2655
|
+
'yesterday',
|
|
2656
|
+
'next-week',
|
|
2657
|
+
'this-week',
|
|
2658
|
+
'last-week',
|
|
2659
|
+
'next-month',
|
|
2660
|
+
'this-month',
|
|
2661
|
+
'last-month',
|
|
2662
|
+
'next-quarter',
|
|
2663
|
+
'this-quarter',
|
|
2664
|
+
'last-quarter',
|
|
2665
|
+
'next-year',
|
|
2666
|
+
'this-year',
|
|
2667
|
+
'last-year',
|
|
2668
|
+
'year-to-date',
|
|
2669
|
+
'quarter-1',
|
|
2670
|
+
'quarter-2',
|
|
2671
|
+
'quarter-3',
|
|
2672
|
+
'quarter-4',
|
|
2673
|
+
'month-1',
|
|
2674
|
+
'month-2',
|
|
2675
|
+
'month-3',
|
|
2676
|
+
'month-4',
|
|
2677
|
+
'month-5',
|
|
2678
|
+
'month-6',
|
|
2679
|
+
'month-7',
|
|
2680
|
+
'month-8',
|
|
2681
|
+
'month-9',
|
|
2682
|
+
'month-10',
|
|
2683
|
+
'month-11',
|
|
2684
|
+
'month-12'
|
|
2685
|
+
]);
|
|
2686
|
+
function normalizedWorkSpreadsheetAutoFilterRange(selection) {
|
|
2687
|
+
const rowStart = finiteIndex(selection?.row?.[0]);
|
|
2688
|
+
const rowEnd = finiteIndex(selection?.row?.[1]);
|
|
2689
|
+
const columnStart = finiteIndex(selection?.column?.[0]);
|
|
2690
|
+
const columnEnd = finiteIndex(selection?.column?.[1]);
|
|
2691
|
+
if (null === rowStart || null === rowEnd || null === columnStart || null === columnEnd) return null;
|
|
2692
|
+
return {
|
|
2693
|
+
row: [
|
|
2694
|
+
Math.min(rowStart, rowEnd),
|
|
2695
|
+
Math.max(rowStart, rowEnd)
|
|
2696
|
+
],
|
|
2697
|
+
column: [
|
|
2698
|
+
Math.min(columnStart, columnEnd),
|
|
2699
|
+
Math.max(columnStart, columnEnd)
|
|
2700
|
+
]
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
function workSpreadsheetAutoFilterCriteria(sheet, column) {
|
|
2704
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet?.filter_select);
|
|
2705
|
+
if (!sheet || !range || !columnInRange(range, column)) return null;
|
|
2706
|
+
return criteriaFromEntry(sheet.filter?.[String(column - range.column[0])]);
|
|
2707
|
+
}
|
|
2708
|
+
function workSpreadsheetAutoFilterCriteriaEntries(sheet) {
|
|
2709
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
2710
|
+
if (!range) return [];
|
|
2711
|
+
const entries = [];
|
|
2712
|
+
for (const [key, value] of Object.entries(sheet.filter ?? {})){
|
|
2713
|
+
const column = Number(key);
|
|
2714
|
+
const criteria = criteriaFromEntry(value);
|
|
2715
|
+
if (criteria && Number.isSafeInteger(column) && !(column < 0) && !(column > range.column[1] - range.column[0])) entries.push({
|
|
2716
|
+
column,
|
|
2717
|
+
criteria
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
return entries.sort((left, right)=>left.column - right.column);
|
|
2721
|
+
}
|
|
2722
|
+
function workSpreadsheetSheetWithAutoFilterCriteria(sheet, column, criteria, context = {
|
|
2723
|
+
now: new Date()
|
|
2724
|
+
}) {
|
|
2725
|
+
const normalizedCriteria = normalizeWorkSpreadsheetFilterCriteria(criteria);
|
|
2726
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
2727
|
+
if (!normalizedCriteria || !range || !columnInRange(range, column)) return null;
|
|
2728
|
+
const rowhidden = hiddenRowsForCriteria(sheet, range, column, normalizedCriteria, context);
|
|
2729
|
+
if (!rowhidden) return null;
|
|
2730
|
+
const manuallyHiddenRows = workSpreadsheetAutoFilterManuallyHiddenRows(sheet);
|
|
2731
|
+
const filter = {
|
|
2732
|
+
...sheet.filter ?? {},
|
|
2733
|
+
[String(column - range.column[0])]: filterColumnState(range, column, normalizedCriteria, rowhidden)
|
|
2734
|
+
};
|
|
2735
|
+
return sheetWithFilterState(sheet, filter, manuallyHiddenRows);
|
|
2736
|
+
}
|
|
2737
|
+
function workSpreadsheetSheetWithoutAutoFilterCriteria(sheet, column) {
|
|
2738
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
2739
|
+
if (!range || !columnInRange(range, column)) return null;
|
|
2740
|
+
const key = String(column - range.column[0]);
|
|
2741
|
+
if (!Object.hasOwn(sheet.filter ?? {}, key)) return null;
|
|
2742
|
+
const filter = {
|
|
2743
|
+
...sheet.filter ?? {}
|
|
2744
|
+
};
|
|
2745
|
+
delete filter[key];
|
|
2746
|
+
return sheetWithFilterState(sheet, filter, workSpreadsheetAutoFilterManuallyHiddenRows(sheet));
|
|
2747
|
+
}
|
|
2748
|
+
function workSpreadsheetAutoFilterManuallyHiddenRows(sheet) {
|
|
2749
|
+
const owned = filterHiddenRows(sheet.filter);
|
|
2750
|
+
const manuallyHidden = new Set(Object.keys(sheet.config?.rowhidden ?? {}).filter((row)=>!owned.has(row)));
|
|
2751
|
+
for (const entry of Object.values(sheet.filter ?? {})){
|
|
2752
|
+
const overlap = criteriaPayloadFromEntry(entry)?.manualHiddenOverlap;
|
|
2753
|
+
if (Array.isArray(overlap)) {
|
|
2754
|
+
for (const row of overlap)if (validHiddenRowKey(row)) manuallyHidden.add(row);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
return manuallyHidden;
|
|
2758
|
+
}
|
|
2759
|
+
function workSpreadsheetSheetWithImportedAutoFilterCriteria(sheet, entries, context = {
|
|
2760
|
+
now: new Date()
|
|
2761
|
+
}) {
|
|
2762
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
2763
|
+
if (!range || !entries.length) return sheet;
|
|
2764
|
+
const width = range.column[1] - range.column[0] + 1;
|
|
2765
|
+
const filter = {};
|
|
2766
|
+
const owned = new Set();
|
|
2767
|
+
for (const entry of entries){
|
|
2768
|
+
const criteria = normalizeWorkSpreadsheetFilterCriteria(entry.criteria);
|
|
2769
|
+
if (!criteria || !Number.isSafeInteger(entry.column) || entry.column < 0 || entry.column >= width) continue;
|
|
2770
|
+
const column = range.column[0] + entry.column;
|
|
2771
|
+
const rowhidden = hiddenRowsForCriteria(sheet, range, column, criteria, context) ?? {};
|
|
2772
|
+
for (const row of Object.keys(rowhidden))owned.add(row);
|
|
2773
|
+
filter[String(entry.column)] = filterColumnState(range, column, criteria, rowhidden);
|
|
2774
|
+
}
|
|
2775
|
+
if (!Object.keys(filter).length) return sheet;
|
|
2776
|
+
const manuallyHiddenRows = new Set(Object.keys(sheet.config?.rowhidden ?? {}).filter((row)=>!owned.has(row)));
|
|
2777
|
+
return sheetWithFilterState(sheet, filter, manuallyHiddenRows);
|
|
2778
|
+
}
|
|
2779
|
+
function normalizeWorkSpreadsheetFilterCriteria(criteria) {
|
|
2780
|
+
if (!criteria || 'object' != typeof criteria || Array.isArray(criteria)) return null;
|
|
2781
|
+
const value = criteria;
|
|
2782
|
+
const type = value.type;
|
|
2783
|
+
if ('blanks' === type || 'non-blanks' === type) return {
|
|
2784
|
+
type
|
|
2785
|
+
};
|
|
2786
|
+
if ('values' === type) {
|
|
2787
|
+
if (!Array.isArray(value.values) || 'boolean' != typeof value.includeBlanks) return null;
|
|
2788
|
+
const values = value.values.filter((item)=>'string' == typeof item);
|
|
2789
|
+
if (values.length !== value.values.length || !values.length && !value.includeBlanks) return null;
|
|
2790
|
+
return {
|
|
2791
|
+
type,
|
|
2792
|
+
values: [
|
|
2793
|
+
...new Set(values)
|
|
2794
|
+
],
|
|
2795
|
+
includeBlanks: value.includeBlanks
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
if ('between' === type || 'not-between' === type) return 'string' == typeof value.lower && 'string' == typeof value.upper ? {
|
|
2799
|
+
type,
|
|
2800
|
+
lower: value.lower,
|
|
2801
|
+
upper: value.upper
|
|
2802
|
+
} : null;
|
|
2803
|
+
if ('compound' === type) {
|
|
2804
|
+
if ('and' !== value.conjunction && 'or' !== value.conjunction || !Array.isArray(value.conditions) || 2 !== value.conditions.length) return null;
|
|
2805
|
+
const first = normalizeWorkSpreadsheetCustomFilterCondition(value.conditions[0]);
|
|
2806
|
+
const second = normalizeWorkSpreadsheetCustomFilterCondition(value.conditions[1]);
|
|
2807
|
+
return first && second ? {
|
|
2808
|
+
type,
|
|
2809
|
+
conjunction: value.conjunction,
|
|
2810
|
+
conditions: [
|
|
2811
|
+
first,
|
|
2812
|
+
second
|
|
2813
|
+
]
|
|
2814
|
+
} : null;
|
|
2815
|
+
}
|
|
2816
|
+
if ('top' === type || 'bottom' === type) return Number.isSafeInteger(value.count) && Number(value.count) >= 1 && Number(value.count) <= 500 ? {
|
|
2817
|
+
type,
|
|
2818
|
+
count: Number(value.count)
|
|
2819
|
+
} : null;
|
|
2820
|
+
if ('top-percent' === type || 'bottom-percent' === type) return Number.isSafeInteger(value.percent) && Number(value.percent) >= 1 && Number(value.percent) <= 100 ? {
|
|
2821
|
+
type,
|
|
2822
|
+
percent: Number(value.percent)
|
|
2823
|
+
} : null;
|
|
2824
|
+
if ('dynamic' === type) return 'string' == typeof value.kind && DYNAMIC_FILTER_KINDS.has(value.kind) ? {
|
|
2825
|
+
type,
|
|
2826
|
+
kind: value.kind
|
|
2827
|
+
} : null;
|
|
2828
|
+
return normalizeWorkSpreadsheetCustomFilterCondition(value);
|
|
2829
|
+
}
|
|
2830
|
+
function normalizeWorkSpreadsheetCustomFilterCondition(condition) {
|
|
2831
|
+
if (!condition || 'object' != typeof condition || Array.isArray(condition)) return null;
|
|
2832
|
+
const value = condition;
|
|
2833
|
+
if ('string' != typeof value.type || !SINGLE_VALUE_FILTER_TYPES.has(value.type) || 'string' != typeof value.value) return null;
|
|
2834
|
+
if (('matches-wildcard' === value.type || 'does-not-match-wildcard' === value.type) && !workSpreadsheetFilterTextIsBounded(value.value)) return null;
|
|
2835
|
+
return {
|
|
2836
|
+
type: value.type,
|
|
2837
|
+
value: value.value
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
function sheetWithFilterState(sheet, filter, manuallyHiddenRows) {
|
|
2841
|
+
const owned = filterHiddenRows(filter);
|
|
2842
|
+
const overlap = [
|
|
2843
|
+
...manuallyHiddenRows
|
|
2844
|
+
].filter((row)=>owned.has(row)).sort(compareHiddenRowKeys);
|
|
2845
|
+
const rowhidden = {};
|
|
2846
|
+
for (const row of manuallyHiddenRows)rowhidden[row] = 0;
|
|
2847
|
+
for (const row of owned)rowhidden[row] = 0;
|
|
2848
|
+
return {
|
|
2849
|
+
...sheet,
|
|
2850
|
+
filter: filterWithManualHiddenOverlap(filter, overlap),
|
|
2851
|
+
config: {
|
|
2852
|
+
...sheet.config ?? {},
|
|
2853
|
+
rowhidden
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
function filterWithManualHiddenOverlap(filter, manualHiddenOverlap) {
|
|
2858
|
+
return Object.fromEntries(Object.entries(filter).map(([column, entry])=>{
|
|
2859
|
+
const payload = criteriaPayloadFromEntry(entry);
|
|
2860
|
+
if (!payload || !entry || 'object' != typeof entry) return [
|
|
2861
|
+
column,
|
|
2862
|
+
entry
|
|
2863
|
+
];
|
|
2864
|
+
return [
|
|
2865
|
+
column,
|
|
2866
|
+
{
|
|
2867
|
+
...entry,
|
|
2868
|
+
caljs: {
|
|
2869
|
+
...payload,
|
|
2870
|
+
manualHiddenOverlap: [
|
|
2871
|
+
...manualHiddenOverlap
|
|
2872
|
+
]
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
];
|
|
2876
|
+
}));
|
|
2877
|
+
}
|
|
2878
|
+
function filterColumnState(range, column, criteria, rowhidden) {
|
|
2879
|
+
return {
|
|
2880
|
+
caljs: {
|
|
2881
|
+
criteria,
|
|
2882
|
+
kind: AUTO_FILTER_CRITERIA_KIND,
|
|
2883
|
+
version: AUTO_FILTER_CRITERIA_VERSION
|
|
2884
|
+
},
|
|
2885
|
+
cindex: column,
|
|
2886
|
+
edc: range.column[1],
|
|
2887
|
+
edr: range.row[1],
|
|
2888
|
+
optionstate: true,
|
|
2889
|
+
rowhidden,
|
|
2890
|
+
stc: range.column[0],
|
|
2891
|
+
str: range.row[0]
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
function hiddenRowsForCriteria(sheet, range, column, criteria, context) {
|
|
2895
|
+
if ('top' === criteria.type || 'top-percent' === criteria.type || 'bottom' === criteria.type || 'bottom-percent' === criteria.type) return hiddenRowsForRankCriteria(sheet, range, column, criteria);
|
|
2896
|
+
if ('dynamic' === criteria.type) return hiddenRowsForDynamicCriteria(sheet, range, column, criteria.kind, context);
|
|
2897
|
+
const matches = filterMatcher(criteria);
|
|
2898
|
+
if (!matches) return null;
|
|
2899
|
+
const cellAt = sheetCellReader(sheet);
|
|
2900
|
+
const hidden = {};
|
|
2901
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1)if (!matches(cellAt(row, column))) hidden[String(row)] = 0;
|
|
2902
|
+
return hidden;
|
|
2903
|
+
}
|
|
2904
|
+
function hiddenRowsForDynamicCriteria(sheet, range, column, kind, context) {
|
|
2905
|
+
const cellAt = sheetCellReader(sheet);
|
|
2906
|
+
const cells = {
|
|
2907
|
+
*[Symbol.iterator] () {
|
|
2908
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1)yield cellAt(row, column);
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
const matches = workSpreadsheetDynamicFilterMatcher(kind, cells, context);
|
|
2912
|
+
if (!matches) return null;
|
|
2913
|
+
const hidden = {};
|
|
2914
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1)if (!matches(cellAt(row, column))) hidden[String(row)] = 0;
|
|
2915
|
+
return hidden;
|
|
2916
|
+
}
|
|
2917
|
+
function hiddenRowsForRankCriteria(sheet, range, column, criteria) {
|
|
2918
|
+
const cellAt = sheetCellReader(sheet);
|
|
2919
|
+
const values = [];
|
|
2920
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1){
|
|
2921
|
+
const value = numericCellValue(cellAt(row, column));
|
|
2922
|
+
if (null !== value) values.push(value);
|
|
2923
|
+
}
|
|
2924
|
+
const top = 'top' === criteria.type || 'top-percent' === criteria.type;
|
|
2925
|
+
values.sort((left, right)=>top ? right - left : left - right);
|
|
2926
|
+
const requested = 'top' === criteria.type || 'bottom' === criteria.type ? criteria.count : Math.ceil(values.length * criteria.percent / 100);
|
|
2927
|
+
const boundary = values[Math.min(requested, values.length) - 1];
|
|
2928
|
+
const hidden = {};
|
|
2929
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1){
|
|
2930
|
+
const value = numericCellValue(cellAt(row, column));
|
|
2931
|
+
if (null === value || void 0 === boundary || (top ? value < boundary : value > boundary)) hidden[String(row)] = 0;
|
|
2932
|
+
}
|
|
2933
|
+
return hidden;
|
|
2934
|
+
}
|
|
2935
|
+
function filterMatcher(criteria) {
|
|
2936
|
+
if ('blanks' === criteria.type) return cellIsBlank;
|
|
2937
|
+
if ('non-blanks' === criteria.type) return (cell)=>!cellIsBlank(cell);
|
|
2938
|
+
if ('values' === criteria.type) {
|
|
2939
|
+
const values = new Set(criteria.values.map(comparableText));
|
|
2940
|
+
return (cell)=>{
|
|
2941
|
+
const value = cellValue(cell);
|
|
2942
|
+
if (null === value) return criteria.includeBlanks;
|
|
2943
|
+
return values.has(comparableText(String(value)));
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
if ('between' === criteria.type || 'not-between' === criteria.type) return (cell)=>{
|
|
2947
|
+
const lower = compareCell(cell, criteria.lower);
|
|
2948
|
+
const upper = compareCell(cell, criteria.upper);
|
|
2949
|
+
const between = null !== lower && null !== upper && lower >= 0 && upper <= 0;
|
|
2950
|
+
return 'between' === criteria.type ? between : !between;
|
|
2951
|
+
};
|
|
2952
|
+
if ('compound' === criteria.type) {
|
|
2953
|
+
const first = customFilterMatcher(criteria.conditions[0]);
|
|
2954
|
+
const second = customFilterMatcher(criteria.conditions[1]);
|
|
2955
|
+
return 'and' === criteria.conjunction ? (cell)=>first(cell) && second(cell) : (cell)=>first(cell) || second(cell);
|
|
2956
|
+
}
|
|
2957
|
+
if ('top' === criteria.type || 'top-percent' === criteria.type || 'bottom' === criteria.type || 'bottom-percent' === criteria.type || 'dynamic' === criteria.type) return null;
|
|
2958
|
+
return customFilterMatcher(criteria);
|
|
2959
|
+
}
|
|
2960
|
+
function customFilterMatcher(criteria) {
|
|
2961
|
+
if ('matches-wildcard' === criteria.type || 'does-not-match-wildcard' === criteria.type) {
|
|
2962
|
+
const matchesWildcard = workSpreadsheetWildcardMatcher(criteria.value);
|
|
2963
|
+
const matches = (cell)=>{
|
|
2964
|
+
const value = cellValue(cell);
|
|
2965
|
+
return null !== value && matchesWildcard(String(value));
|
|
2966
|
+
};
|
|
2967
|
+
return 'matches-wildcard' === criteria.type ? matches : (cell)=>!matches(cell);
|
|
2968
|
+
}
|
|
2969
|
+
const expected = criteria.value;
|
|
2970
|
+
if ('contains' === criteria.type) return (cell)=>cellText(cell).includes(comparableText(expected));
|
|
2971
|
+
if ('does-not-contain' === criteria.type) return (cell)=>!cellText(cell).includes(comparableText(expected));
|
|
2972
|
+
if ('begins-with' === criteria.type) return (cell)=>cellText(cell).startsWith(comparableText(expected));
|
|
2973
|
+
if ('does-not-begin-with' === criteria.type) return (cell)=>!cellText(cell).startsWith(comparableText(expected));
|
|
2974
|
+
if ('ends-with' === criteria.type) return (cell)=>cellText(cell).endsWith(comparableText(expected));
|
|
2975
|
+
if ('does-not-end-with' === criteria.type) return (cell)=>!cellText(cell).endsWith(comparableText(expected));
|
|
2976
|
+
return (cell)=>{
|
|
2977
|
+
const order = compareCell(cell, expected);
|
|
2978
|
+
if (null === order) return false;
|
|
2979
|
+
if ('equals' === criteria.type) return 0 === order;
|
|
2980
|
+
if ('not-equals' === criteria.type) return 0 !== order;
|
|
2981
|
+
if ('greater-than' === criteria.type) return order > 0;
|
|
2982
|
+
if ('greater-than-or-equal' === criteria.type) return order >= 0;
|
|
2983
|
+
if ('less-than' === criteria.type) return order < 0;
|
|
2984
|
+
return order <= 0;
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
function compareCell(cell, expected) {
|
|
2988
|
+
const value = cellValue(cell);
|
|
2989
|
+
if (null === value) return null;
|
|
2990
|
+
const numericExpected = finiteNumber(expected);
|
|
2991
|
+
if ('number' == typeof value && null !== numericExpected) return value - numericExpected;
|
|
2992
|
+
return comparableText(String(value)).localeCompare(comparableText(expected), 'zh-CN', {
|
|
2993
|
+
numeric: true,
|
|
2994
|
+
sensitivity: 'base'
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
function cellIsBlank(cell) {
|
|
2998
|
+
return null === cellValue(cell);
|
|
2999
|
+
}
|
|
3000
|
+
function cellText(cell) {
|
|
3001
|
+
const value = cellValue(cell);
|
|
3002
|
+
return null === value ? '' : comparableText(String(value));
|
|
3003
|
+
}
|
|
3004
|
+
function cellValue(cell) {
|
|
3005
|
+
const value = cell?.v ?? cell?.m;
|
|
3006
|
+
if ('number' == typeof value && Number.isFinite(value)) return value;
|
|
3007
|
+
if ('string' == typeof value) return '' === value ? null : value;
|
|
3008
|
+
if ('boolean' == typeof value) return value;
|
|
3009
|
+
return null;
|
|
3010
|
+
}
|
|
3011
|
+
function numericCellValue(cell) {
|
|
3012
|
+
const value = cellValue(cell);
|
|
3013
|
+
return 'number' == typeof value ? value : null;
|
|
3014
|
+
}
|
|
3015
|
+
function comparableText(value) {
|
|
3016
|
+
return value.normalize('NFKC').toLocaleLowerCase('zh-CN');
|
|
3017
|
+
}
|
|
3018
|
+
function finiteNumber(value) {
|
|
3019
|
+
if (!value.trim()) return null;
|
|
3020
|
+
const numeric = Number(value);
|
|
3021
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
3022
|
+
}
|
|
3023
|
+
function criteriaFromEntry(entry) {
|
|
3024
|
+
const payload = criteriaPayloadFromEntry(entry);
|
|
3025
|
+
return payload ? normalizeWorkSpreadsheetFilterCriteria(payload.criteria) : null;
|
|
3026
|
+
}
|
|
3027
|
+
function criteriaPayloadFromEntry(entry) {
|
|
3028
|
+
if (!entry || 'object' != typeof entry) return null;
|
|
3029
|
+
const caljs = entry.caljs;
|
|
3030
|
+
if (!caljs || 'object' != typeof caljs) return null;
|
|
3031
|
+
const payload = caljs;
|
|
3032
|
+
if (payload.kind !== AUTO_FILTER_CRITERIA_KIND || payload.version !== AUTO_FILTER_CRITERIA_VERSION) return null;
|
|
3033
|
+
return normalizeWorkSpreadsheetFilterCriteria(payload.criteria) ? payload : null;
|
|
3034
|
+
}
|
|
3035
|
+
function filterHiddenRows(filter) {
|
|
3036
|
+
const rows = new Set();
|
|
3037
|
+
for (const value of Object.values(filter ?? {})){
|
|
3038
|
+
if (!value || 'object' != typeof value) continue;
|
|
3039
|
+
const rowhidden = value.rowhidden;
|
|
3040
|
+
if (rowhidden && 'object' == typeof rowhidden) for (const row of Object.keys(rowhidden))rows.add(row);
|
|
3041
|
+
}
|
|
3042
|
+
return rows;
|
|
3043
|
+
}
|
|
3044
|
+
function sheetCellReader(sheet) {
|
|
3045
|
+
if (sheet.data) return (row, column)=>sheet.data?.[row]?.[column] ?? null;
|
|
3046
|
+
const cells = new Map((sheet.celldata ?? []).map((entry)=>[
|
|
3047
|
+
`${entry.r}:${entry.c}`,
|
|
3048
|
+
entry.v ?? null
|
|
3049
|
+
]));
|
|
3050
|
+
return (row, column)=>cells.get(`${row}:${column}`) ?? null;
|
|
3051
|
+
}
|
|
3052
|
+
function columnInRange(range, column) {
|
|
3053
|
+
return Number.isSafeInteger(column) && column >= range.column[0] && column <= range.column[1];
|
|
3054
|
+
}
|
|
3055
|
+
function finiteIndex(value) {
|
|
3056
|
+
return 'number' == typeof value && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
3057
|
+
}
|
|
3058
|
+
function validHiddenRowKey(value) {
|
|
3059
|
+
return 'string' == typeof value && /^(?:0|[1-9]\d*)$/.test(value) && Number.isSafeInteger(Number(value));
|
|
3060
|
+
}
|
|
3061
|
+
function compareHiddenRowKeys(left, right) {
|
|
3062
|
+
return Number(left) - Number(right);
|
|
3063
|
+
}
|
|
3064
|
+
const work_spreadsheet_data_validation_MILLISECONDS_PER_DAY = 86400000;
|
|
1804
3065
|
const SPREADSHEET_DATA_VALIDATION_ERROR_STYLES = [
|
|
1805
3066
|
'stop',
|
|
1806
3067
|
'warning',
|
|
@@ -1824,7 +3085,7 @@ function normalizeSpreadsheetDateValidationBoundary(value, uses1904DateSystem =
|
|
|
1824
3085
|
if (!uses1904DateSystem && (0 === serial || 60 === serial)) return null;
|
|
1825
3086
|
const adjustedSerial = uses1904DateSystem ? serial : serial > 60 ? serial - 1 : serial;
|
|
1826
3087
|
const epoch = uses1904DateSystem ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 31);
|
|
1827
|
-
return isoDateFromTimestamp(epoch + adjustedSerial *
|
|
3088
|
+
return isoDateFromTimestamp(epoch + adjustedSerial * work_spreadsheet_data_validation_MILLISECONDS_PER_DAY);
|
|
1828
3089
|
}
|
|
1829
3090
|
function spreadsheetDateValidationFormula(value) {
|
|
1830
3091
|
const normalized = normalizeIsoDate(value.trim());
|
|
@@ -2478,7 +3739,7 @@ function readRichTextRun(element, value, colors) {
|
|
|
2478
3739
|
if (work_xlsx_rich_text_xlsxToggleEnabled(directChild(properties, 'b'))) run.bl = 1;
|
|
2479
3740
|
if (work_xlsx_rich_text_xlsxToggleEnabled(directChild(properties, 'i'))) run.it = 1;
|
|
2480
3741
|
if (work_xlsx_rich_text_xlsxToggleEnabled(directChild(properties, 'strike'))) run.cl = 1;
|
|
2481
|
-
const size =
|
|
3742
|
+
const size = work_xlsx_rich_text_finiteNumber(attribute(directChild(properties, 'sz') ?? properties, 'val'));
|
|
2482
3743
|
if (null !== size && size >= 1 && size <= 409) run.fs = size;
|
|
2483
3744
|
const underline = directChild(properties, 'u');
|
|
2484
3745
|
if (underline) {
|
|
@@ -2550,7 +3811,7 @@ function work_xlsx_rich_text_xlsxToggleEnabled(element) {
|
|
|
2550
3811
|
const value = attribute(element, 'val')?.trim().toLowerCase();
|
|
2551
3812
|
return '0' !== value && 'false' !== value && 'off' !== value;
|
|
2552
3813
|
}
|
|
2553
|
-
function
|
|
3814
|
+
function work_xlsx_rich_text_finiteNumber(value) {
|
|
2554
3815
|
if (null === value || !value.trim()) return null;
|
|
2555
3816
|
const parsed = Number(value);
|
|
2556
3817
|
return Number.isFinite(parsed) ? parsed : null;
|
|
@@ -2563,4 +3824,227 @@ function work_xlsx_rich_text_nonNegativeInteger(value) {
|
|
|
2563
3824
|
function work_xlsx_rich_text_isRecord(value) {
|
|
2564
3825
|
return 'object' == typeof value && null !== value && !Array.isArray(value);
|
|
2565
3826
|
}
|
|
2566
|
-
|
|
3827
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES = 256;
|
|
3828
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS = 128;
|
|
3829
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS = 4096;
|
|
3830
|
+
const MAX_SPREADSHEET_SORT_USER_CUSTOM_LISTS = 32;
|
|
3831
|
+
function parseSpreadsheetSortCustomList(text) {
|
|
3832
|
+
return validateSpreadsheetSortCustomList(text.split(/\r?\n|,|,/u).map((entry)=>entry.trim()).filter(Boolean));
|
|
3833
|
+
}
|
|
3834
|
+
function validateSpreadsheetSortCustomList(input) {
|
|
3835
|
+
if (!Array.isArray(input)) return customListError('invalid-entry');
|
|
3836
|
+
if (input.length < 2) return customListError('not-enough-entries');
|
|
3837
|
+
if (input.length > MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES) return customListError('too-many-entries');
|
|
3838
|
+
const entries = [];
|
|
3839
|
+
const seen = new Set();
|
|
3840
|
+
let totalCodePoints = 0;
|
|
3841
|
+
for (const candidate of input){
|
|
3842
|
+
if ('string' != typeof candidate) return customListError('invalid-entry');
|
|
3843
|
+
const entry = candidate.trim();
|
|
3844
|
+
if (!entry) return customListError('invalid-entry');
|
|
3845
|
+
const codePoints = Array.from(entry).length;
|
|
3846
|
+
if (codePoints > MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS) return customListError('entry-too-long');
|
|
3847
|
+
totalCodePoints += codePoints;
|
|
3848
|
+
if (totalCodePoints > MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS) return customListError('list-too-long');
|
|
3849
|
+
const matchKey = spreadsheetSortCustomListMatchKey(entry);
|
|
3850
|
+
if (seen.has(matchKey)) return customListError('duplicate-entry');
|
|
3851
|
+
seen.add(matchKey);
|
|
3852
|
+
entries.push(entry);
|
|
3853
|
+
}
|
|
3854
|
+
return {
|
|
3855
|
+
ok: true,
|
|
3856
|
+
entries
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
function createSpreadsheetSortCustomList(entries, source = 'session') {
|
|
3860
|
+
const validation = validateSpreadsheetSortCustomList(entries);
|
|
3861
|
+
if (!validation.ok) return null;
|
|
3862
|
+
const normalized = Object.freeze([
|
|
3863
|
+
...validation.entries
|
|
3864
|
+
]);
|
|
3865
|
+
return Object.freeze({
|
|
3866
|
+
source,
|
|
3867
|
+
entries: normalized,
|
|
3868
|
+
label: spreadsheetSortCustomListLabel(normalized)
|
|
3869
|
+
});
|
|
3870
|
+
}
|
|
3871
|
+
function mergeSpreadsheetSortCustomLists(lists) {
|
|
3872
|
+
const merged = [
|
|
3873
|
+
...SPREADSHEET_SORT_BUILT_IN_CUSTOM_LISTS
|
|
3874
|
+
];
|
|
3875
|
+
let userListCount = 0;
|
|
3876
|
+
for (const candidate of lists){
|
|
3877
|
+
if ('stored' !== candidate.source && 'session' !== candidate.source) continue;
|
|
3878
|
+
const list = createSpreadsheetSortCustomList(candidate.entries, candidate.source);
|
|
3879
|
+
if (!(!list || merged.some((item)=>spreadsheetSortCustomListsEqual(item.entries, list.entries)))) {
|
|
3880
|
+
if (userListCount >= MAX_SPREADSHEET_SORT_USER_CUSTOM_LISTS) break;
|
|
3881
|
+
merged.push(list);
|
|
3882
|
+
userListCount += 1;
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
return Object.freeze(merged);
|
|
3886
|
+
}
|
|
3887
|
+
function spreadsheetSortCustomListsEqual(left, right) {
|
|
3888
|
+
return left.length === right.length && left.every((entry, index)=>spreadsheetSortCustomListMatchKey(entry) === spreadsheetSortCustomListMatchKey(right[index] ?? ''));
|
|
3889
|
+
}
|
|
3890
|
+
function spreadsheetSortCustomListMatchKey(value) {
|
|
3891
|
+
return String(value).normalize('NFKC').trim().toLocaleLowerCase('zh-CN');
|
|
3892
|
+
}
|
|
3893
|
+
function spreadsheetSortCustomListLabel(entries) {
|
|
3894
|
+
const visible = entries.slice(0, 3).map((entry)=>truncateSpreadsheetSortCustomListLabelEntry(entry));
|
|
3895
|
+
return `${visible.join(' → ')}${entries.length > 3 ? ' → …' : ''}`;
|
|
3896
|
+
}
|
|
3897
|
+
function truncateSpreadsheetSortCustomListLabelEntry(entry) {
|
|
3898
|
+
const characters = Array.from(entry);
|
|
3899
|
+
return characters.length > 18 ? `${characters.slice(0, 17).join('')}…` : entry;
|
|
3900
|
+
}
|
|
3901
|
+
function customListError(code) {
|
|
3902
|
+
const messages = {
|
|
3903
|
+
'duplicate-entry': '自定义序列不能包含重复项(忽略大小写和全半角差异)。',
|
|
3904
|
+
'entry-too-long': `每个序列项最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS} 个字符。`,
|
|
3905
|
+
'invalid-entry': '自定义序列只能包含非空文本项。',
|
|
3906
|
+
'list-too-long': `一个自定义序列最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS.toLocaleString('en-US')} 个字符。`,
|
|
3907
|
+
'not-enough-entries': '自定义序列至少需要两个项目。',
|
|
3908
|
+
'too-many-entries': `一个自定义序列最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES} 个项目。`
|
|
3909
|
+
};
|
|
3910
|
+
return {
|
|
3911
|
+
ok: false,
|
|
3912
|
+
code,
|
|
3913
|
+
message: messages[code]
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
function builtInCustomList(label, entries) {
|
|
3917
|
+
return Object.freeze({
|
|
3918
|
+
source: 'built-in',
|
|
3919
|
+
entries: Object.freeze([
|
|
3920
|
+
...entries
|
|
3921
|
+
]),
|
|
3922
|
+
label
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
const SPREADSHEET_SORT_BUILT_IN_CUSTOM_LISTS = Object.freeze([
|
|
3926
|
+
builtInCustomList('月份(一月 → 十二月)', [
|
|
3927
|
+
'一月',
|
|
3928
|
+
'二月',
|
|
3929
|
+
'三月',
|
|
3930
|
+
'四月',
|
|
3931
|
+
'五月',
|
|
3932
|
+
'六月',
|
|
3933
|
+
'七月',
|
|
3934
|
+
'八月',
|
|
3935
|
+
'九月',
|
|
3936
|
+
'十月',
|
|
3937
|
+
'十一月',
|
|
3938
|
+
'十二月'
|
|
3939
|
+
]),
|
|
3940
|
+
builtInCustomList('星期(星期日 → 星期六)', [
|
|
3941
|
+
'星期日',
|
|
3942
|
+
'星期一',
|
|
3943
|
+
'星期二',
|
|
3944
|
+
'星期三',
|
|
3945
|
+
'星期四',
|
|
3946
|
+
'星期五',
|
|
3947
|
+
'星期六'
|
|
3948
|
+
]),
|
|
3949
|
+
builtInCustomList('周(周日 → 周六)', [
|
|
3950
|
+
'周日',
|
|
3951
|
+
'周一',
|
|
3952
|
+
'周二',
|
|
3953
|
+
'周三',
|
|
3954
|
+
'周四',
|
|
3955
|
+
'周五',
|
|
3956
|
+
'周六'
|
|
3957
|
+
]),
|
|
3958
|
+
builtInCustomList('月份(January → December)', [
|
|
3959
|
+
'January',
|
|
3960
|
+
'February',
|
|
3961
|
+
'March',
|
|
3962
|
+
'April',
|
|
3963
|
+
'May',
|
|
3964
|
+
'June',
|
|
3965
|
+
'July',
|
|
3966
|
+
'August',
|
|
3967
|
+
'September',
|
|
3968
|
+
'October',
|
|
3969
|
+
'November',
|
|
3970
|
+
'December'
|
|
3971
|
+
]),
|
|
3972
|
+
builtInCustomList('月份(Jan → Dec)', [
|
|
3973
|
+
'Jan',
|
|
3974
|
+
'Feb',
|
|
3975
|
+
'Mar',
|
|
3976
|
+
'Apr',
|
|
3977
|
+
'May',
|
|
3978
|
+
'Jun',
|
|
3979
|
+
'Jul',
|
|
3980
|
+
'Aug',
|
|
3981
|
+
'Sep',
|
|
3982
|
+
'Oct',
|
|
3983
|
+
'Nov',
|
|
3984
|
+
'Dec'
|
|
3985
|
+
]),
|
|
3986
|
+
builtInCustomList('星期(Sunday → Saturday)', [
|
|
3987
|
+
'Sunday',
|
|
3988
|
+
'Monday',
|
|
3989
|
+
'Tuesday',
|
|
3990
|
+
'Wednesday',
|
|
3991
|
+
'Thursday',
|
|
3992
|
+
'Friday',
|
|
3993
|
+
'Saturday'
|
|
3994
|
+
]),
|
|
3995
|
+
builtInCustomList('星期(Sun → Sat)', [
|
|
3996
|
+
'Sun',
|
|
3997
|
+
'Mon',
|
|
3998
|
+
'Tue',
|
|
3999
|
+
'Wed',
|
|
4000
|
+
'Thu',
|
|
4001
|
+
'Fri',
|
|
4002
|
+
'Sat'
|
|
4003
|
+
])
|
|
4004
|
+
]);
|
|
4005
|
+
const SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_VERSION = 1;
|
|
4006
|
+
const DEFAULT_SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_KEY = 'a3s-office.spreadsheet-sort-custom-lists.v1';
|
|
4007
|
+
class LocalStorageSpreadsheetSortCustomListStore {
|
|
4008
|
+
storage;
|
|
4009
|
+
key;
|
|
4010
|
+
constructor(storage, key = DEFAULT_SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_KEY){
|
|
4011
|
+
this.storage = storage;
|
|
4012
|
+
this.key = key;
|
|
4013
|
+
}
|
|
4014
|
+
load() {
|
|
4015
|
+
try {
|
|
4016
|
+
const source = this.storage.getItem(this.key);
|
|
4017
|
+
if (!source) return Object.freeze([]);
|
|
4018
|
+
const payload = JSON.parse(source);
|
|
4019
|
+
if (!spreadsheet_sort_custom_list_store_isRecord(payload) || payload.version !== SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_VERSION) return Object.freeze([]);
|
|
4020
|
+
return normalizeStoredSpreadsheetSortCustomLists(payload.lists);
|
|
4021
|
+
} catch {
|
|
4022
|
+
return Object.freeze([]);
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
save(lists) {
|
|
4026
|
+
const normalized = normalizeStoredSpreadsheetSortCustomLists(lists);
|
|
4027
|
+
this.storage.setItem(this.key, JSON.stringify({
|
|
4028
|
+
version: SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_VERSION,
|
|
4029
|
+
lists: normalized
|
|
4030
|
+
}));
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
function normalizeStoredSpreadsheetSortCustomLists(input) {
|
|
4034
|
+
if (!Array.isArray(input)) return Object.freeze([]);
|
|
4035
|
+
const lists = [];
|
|
4036
|
+
for (const candidate of input){
|
|
4037
|
+
const validation = validateSpreadsheetSortCustomList(candidate);
|
|
4038
|
+
if (!(!validation.ok || lists.some((entries)=>spreadsheetSortCustomListsEqual(entries, validation.entries)))) {
|
|
4039
|
+
lists.push(Object.freeze([
|
|
4040
|
+
...validation.entries
|
|
4041
|
+
]));
|
|
4042
|
+
if (lists.length >= 32) break;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
return Object.freeze(lists);
|
|
4046
|
+
}
|
|
4047
|
+
function spreadsheet_sort_custom_list_store_isRecord(value) {
|
|
4048
|
+
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
4049
|
+
}
|
|
4050
|
+
export { DEFAULT_SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_KEY, LocalStorageSpreadsheetSortCustomListStore, SPREADSHEET_TABLE_TOTALS_FUNCTIONS, activeXlsxNativeFill, activeXlsxSemanticColorOrigin, applyImportedXlsxRichText, boundedSpreadsheetDataValidationText, coalesceXlsxRichTextRuns, createSpreadsheetSortCustomList, createWorkOfficeSpreadsheetCollaborationBinding as createOfficeSpreadsheetCollaborationBinding, createXlsxRichTextReadContext, deleteXlsxNativeFills, directXlsxAlignment, directXlsxFontStyle, fillSpreadsheetTableCalculatedColumns, hasXlsxDirectFontStyle, initializeWorkOfficeSpreadsheetCollaboration as initializeOfficeSpreadsheetCollaboration, isHighSurrogate, isLowSurrogate, isSpreadsheetTextOrientationId, isSpreadsheetUnderlineStyle, mergeSpreadsheetSortCustomLists, normalizeSpreadsheetDataValidationErrorStyle, normalizeSpreadsheetDateValidationBoundary, normalizeSpreadsheetTableCalculatedFormula, normalizeSpreadsheetTableTotalsFormula, normalizeSpreadsheetTableTotalsLabel, normalizeStoredSpreadsheetSortCustomLists, normalizeWorkSpreadsheetFilterCriteria, normalizeXlsxRichTextCell, normalizeXlsxRichTextColor, normalizeXlsxRichTextEditSource, normalizeXlsxRichTextRun, normalizedWorkSpreadsheetAutoFilterRange, parseSpreadsheetSortCustomList, patchSpreadsheetRichTextFontRuns, readWorkOfficeSpreadsheetCollaboration as readOfficeSpreadsheetCollaboration, readXlsxRichTextCells, reconcileSpreadsheetTableCalculatedColumns, reconcileSpreadsheetTableTotalsColumns, replaceWorkOfficeSpreadsheetCollaboration as replaceOfficeSpreadsheetCollaboration, sameXlsxRichTextRunStyle, sheetHasXlsxRichTextCells, spreadsheetCellValueWithDiagonalBorder, spreadsheetDateValidationFormula, spreadsheetDiagonalBorderFromCellValue, spreadsheetExplicitTextOrientationFromCell, spreadsheetSortCustomListMatchKey, spreadsheetSortCustomListsEqual, spreadsheetTableTotalsColumnPatch, spreadsheetTableTotalsFunctionFromOoxml, spreadsheetTableTotalsFunctionLabel, spreadsheetTableTotalsFunctionToOoxml, spreadsheetTableTotalsRowHasContent, spreadsheetTextOrientationCellStyle, spreadsheetTextOrientationChoiceFromCell, spreadsheetTextOrientationFromAngle, spreadsheetTextOrientationFromCell, spreadsheetTextOrientationFromChoice, spreadsheetTextOrientationFromXlsx, spreadsheetUnderlineCellValue, spreadsheetUnderlineCellValueFromSheetJs, spreadsheetUnderlineCellValueFromXlsx, spreadsheetUnderlineStyle, spreadsheetVisibleTextRotationFromCell, synchronizeSpreadsheetTableTotalsRow, validXlsxRichText, validateSpreadsheetSortCustomList, withDefaultSpreadsheetTableTotalsLabel, workSpreadsheetAutoFilterCriteria, workSpreadsheetAutoFilterCriteriaEntries, workSpreadsheetAutoFilterManuallyHiddenRows, workSpreadsheetCellIsDate, workSpreadsheetFilterTextCharacters, workSpreadsheetHasUnescapedWildcard, workSpreadsheetSheetWithAutoFilterCriteria, workSpreadsheetSheetWithImportedAutoFilterCriteria, workSpreadsheetSheetWithoutAutoFilterCriteria, writeXlsxRichTextCells, xlsxAlignmentMatches, xlsxBooleanAttribute, xlsxBorderLineMatches, xlsxColorMatches, xlsxNativeFillCellKeys, xlsxNativeFillKey, xlsxNativeFillSemanticColors, xlsxRichTextCellText, xlsxRichTextStyleOrigins, xlsxStyleCollectionIndex, xlsxToggleEnabled, xlsxUnderlineStyle };
|