@a3s-lab/office 0.42.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/dist/0~1403.js +1 -1
- package/dist/0~2330.js +1 -1
- package/dist/0~3539.js +2 -2
- package/dist/0~3557.js +1 -1
- package/dist/0~4980.js +1 -1
- package/dist/0~5024.js +1 -1
- package/dist/0~7828.js +1 -1
- package/dist/{0~7048.js → 0~7831.js} +2 -547
- package/dist/0~document-editor.js +4 -3
- package/dist/{0~3635.js → 0~office-color-picker.js} +1 -12
- package/dist/0~pdf-viewer.js +22 -8
- package/dist/0~presentation-editor.js +4 -3
- package/dist/0~spreadsheet-editor.js +314 -3617
- package/dist/0~work-docx-export.js +1 -1
- package/dist/0~work-docx-import.js +1 -1
- package/dist/0~work-office-diagnostics.js +1 -1
- package/dist/0~work-pptx-export.js +1 -1
- package/dist/0~work-pptx-import.js +1 -1
- package/dist/0~work-presentation-charts.js +1 -1
- package/dist/3818.js +1 -1
- package/dist/4166.js +1 -1
- package/dist/4174.js +3 -3
- package/dist/4560.js +2 -2
- package/dist/{5416.js → 610.js} +4335 -410
- package/dist/{4121.js → 6968.js} +875 -13
- package/dist/7060.js +1 -1
- package/dist/{4650.js → 816.js} +12 -1
- package/dist/8715.js +1 -1
- package/dist/9333.js +17 -17
- package/dist/core.js +7 -5
- package/dist/index.js +2 -2
- package/dist/internal/features/work/editors/spreadsheet-conditional-format-model.d.ts +6 -1
- package/dist/internal/features/work/editors/spreadsheet-data-validation-list.d.ts +44 -0
- package/dist/internal/features/work/editors/spreadsheet-data-validation.d.ts +2 -1
- package/dist/internal/features/work/editors/spreadsheet-editor-support.d.ts +1 -1
- package/dist/internal/features/work/work-spreadsheet-conditional-format.d.ts +3 -1
- package/dist/internal/features/work/work-spreadsheet-local-formula.d.ts +43 -0
- package/dist/internal/features/work/work-types.d.ts +4 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +13 -1
- package/package.json +4 -1
package/dist/{5416.js → 610.js}
RENAMED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Parser, SUPPORTED_FORMULAS } from "@fortune-sheet/formula-parser";
|
|
2
|
+
import { sparseMatrixColumnCount, formulaHasStructuredReference, sparseArrayIndexes, formulaHasExternalReference, spreadsheetFormulaFunctions, editableSpreadsheetFormula, formatSpreadsheetCellRanges, isValidSpreadsheetDefinedName, sparseArrayEntries as spreadsheet_sparse_sparseArrayEntries, cloneSparseMatrix, parseSpreadsheetCellRanges } from "./8715.js";
|
|
3
|
+
import { workOfficeCollaborationJsonEqual, isOfficeShortcutBlocked, canonicalWorkOfficeCollaborationJson, isWorkOfficeCollaborationRecord, cloneWorkOfficeCollaborationJson } from "./816.js";
|
|
3
4
|
import { initializeWorkOfficeCollaborationMetadata, assertWorkOfficeCollaborationEditable, OfficeCollaborationError as WorkOfficeCollaborationError, readOfficeCollaborationMetadata as readWorkOfficeCollaborationMetadata, registerWorkOfficeCollaborationInitializer, markWorkOfficeCollaborationInitialized, assertWorkOfficeCollaborationOrigin } from "./9787.js";
|
|
4
|
-
import {
|
|
5
|
+
import { directChildren, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, normalizeCssColor, officeFontFamilies, directChild, descendants, attribute, normalizeFormulaForFortuneParser } from "./6968.js";
|
|
5
6
|
import { patchWorkOfficeCollaborationFlatJsonMap, readWorkOfficeCollaborationFlatJsonMap } from "./7060.js";
|
|
6
|
-
import {
|
|
7
|
+
import { freezeImportedSpreadsheetCell, normalizeXlsxSemanticColorOrigin, spreadsheetMatrixProfile, withXlsxPatternFill, xlsxCellAddress, xlsxSemanticColorMatchesValue, normalizeSpreadsheetConditionalVisualOptions, xlsxColorElementMatchesOrigin, xlsxGradientFillSemanticColors, withXlsxGradientFill, deleteXlsxPatternFill, activeXlsxGradientFill, xlsxCellStyleOrigin, attachSpreadsheetShownCommentCells, normalizeXlsxPatternFill, decodeXlsxCellAddress, sameSpreadsheetHistoryValue, spreadsheetConditionalIconForValue, XLSX_GRADIENT_FILL_CELL_KEY, readXlsxSemanticColorOrigin, normalizeXlsxGradientFill, xlsxGradientFillFallbackColor, activeXlsxPatternFill, resolveXlsxColor, registerDerivedSpreadsheetMatrix, xlsxPatternFillKey, XLSX_PATTERN_FILL_CELL_KEY, normalizeSpreadsheetConditionalIconSetFormat, spreadsheetConditionalThresholdValue, defaultSpreadsheetDataBarOptions, createXlsxColorResolver, defaultSpreadsheetColorScaleThresholds, xlsxSemanticColorOriginSupported, xlsxRgbColor, xlsxPatternFillSemanticColors, xlsxGradientFillKey, applyXlsxSemanticColorOrigin, deleteXlsxGradientFill, isSpreadsheetConditionalIconSetName, spreadsheetConditionalIconAppearance, spreadsheetConditionalIconSetCount } from "./9333.js";
|
|
7
8
|
import * as __rspack_external_yjs from "yjs";
|
|
8
9
|
const MAX_SPREADSHEET_TABLE_CALCULATED_FORMULA_LENGTH = 8192;
|
|
9
10
|
const UNSAFE_SPREADSHEET_TABLE_CALCULATED_FUNCTIONS = new Set([
|
|
@@ -1371,7 +1372,7 @@ function patchSpreadsheetCells(record, previous, next) {
|
|
|
1371
1372
|
for (const entry of before){
|
|
1372
1373
|
if (afterByCoordinate.has(entry.coordinate)) continue;
|
|
1373
1374
|
if (!presence.has(entry.coordinate)) continue;
|
|
1374
|
-
const current =
|
|
1375
|
+
const current = office_spreadsheet_collaboration_cells_readCell(fields, entry.row, entry.column);
|
|
1375
1376
|
if (!workOfficeCollaborationJsonEqual(entry.cell, current)) throw staleSpreadsheetConflict(`Cell '${entry.coordinate}' in sheet '${next.id}' changed before it was deleted`);
|
|
1376
1377
|
presence.delete(entry.coordinate);
|
|
1377
1378
|
deleteCellFields(fields, entry.row, entry.column);
|
|
@@ -1406,7 +1407,7 @@ function readSpreadsheetCells(record, sheet) {
|
|
|
1406
1407
|
const entry = grouped.get(coordinate);
|
|
1407
1408
|
if (!entry || value instanceof __rspack_external_yjs.AbstractType) invalidWorkOfficeSpreadsheetShared(`cell field '${encoded}'`);
|
|
1408
1409
|
}
|
|
1409
|
-
for (const entry of grouped.values())entry.cell =
|
|
1410
|
+
for (const entry of grouped.values())entry.cell = office_spreadsheet_collaboration_cells_readCell(fields, entry.row, entry.column);
|
|
1410
1411
|
const entries = Array.from(grouped.values()).sort((left, right)=>left.row - right.row || left.column - right.column);
|
|
1411
1412
|
const mode = optionalCellMode(record.get(SPREADSHEET_RECORD_CELL_MODE));
|
|
1412
1413
|
if (void 0 === mode) {
|
|
@@ -1458,7 +1459,7 @@ function spreadsheetCellEntries(sheet) {
|
|
|
1458
1459
|
function patchCellFields(target, previous, next, row, column, sheetId) {
|
|
1459
1460
|
patchSpreadsheetFlatJsonMap(new SpreadsheetCellFieldView(target, row, column), previous, next, `cell '${row}:${column}' in sheet '${sheetId}'`);
|
|
1460
1461
|
}
|
|
1461
|
-
function
|
|
1462
|
+
function office_spreadsheet_collaboration_cells_readCell(target, row, column) {
|
|
1462
1463
|
return readSpreadsheetFlatJsonMap(new SpreadsheetCellFieldView(target, row, column), `cell '${row}:${column}'`);
|
|
1463
1464
|
}
|
|
1464
1465
|
function patchCellProjectionMetadata(record, rowLengths, previous, next) {
|
|
@@ -1832,18 +1833,18 @@ const SPREADSHEET_PRINT_AREAS_ROOT = 'spreadsheet.print-areas';
|
|
|
1832
1833
|
const SPREADSHEET_PRINT_TITLES_ROOT = 'spreadsheet.print-titles';
|
|
1833
1834
|
const SPREADSHEET_PAGE_BREAKS_ROOT = 'spreadsheet.page-breaks';
|
|
1834
1835
|
const SPREADSHEET_PAGE_SETUPS_ROOT = 'spreadsheet.page-setups';
|
|
1835
|
-
function workOfficeSpreadsheetRoots(
|
|
1836
|
+
function workOfficeSpreadsheetRoots(document1, rootName) {
|
|
1836
1837
|
return {
|
|
1837
|
-
options:
|
|
1838
|
-
sheets:
|
|
1839
|
-
sheetOrder:
|
|
1840
|
-
namedRanges:
|
|
1841
|
-
namedRangeOrder:
|
|
1842
|
-
recordClaims:
|
|
1843
|
-
printAreas:
|
|
1844
|
-
printTitles:
|
|
1845
|
-
pageBreaks:
|
|
1846
|
-
pageSetups:
|
|
1838
|
+
options: document1.getMap(rootName(SPREADSHEET_OPTIONS_ROOT)),
|
|
1839
|
+
sheets: document1.getMap(rootName(SPREADSHEET_SHEETS_ROOT)),
|
|
1840
|
+
sheetOrder: document1.getArray(rootName(SPREADSHEET_SHEET_ORDER_ROOT)),
|
|
1841
|
+
namedRanges: document1.getMap(rootName(SPREADSHEET_NAMED_RANGES_ROOT)),
|
|
1842
|
+
namedRangeOrder: document1.getArray(rootName(SPREADSHEET_NAMED_RANGE_ORDER_ROOT)),
|
|
1843
|
+
recordClaims: document1.getArray(rootName(SPREADSHEET_RECORD_CLAIMS_ROOT)),
|
|
1844
|
+
printAreas: document1.getMap(rootName(SPREADSHEET_PRINT_AREAS_ROOT)),
|
|
1845
|
+
printTitles: document1.getMap(rootName(SPREADSHEET_PRINT_TITLES_ROOT)),
|
|
1846
|
+
pageBreaks: document1.getMap(rootName(SPREADSHEET_PAGE_BREAKS_ROOT)),
|
|
1847
|
+
pageSetups: document1.getMap(rootName(SPREADSHEET_PAGE_SETUPS_ROOT))
|
|
1847
1848
|
};
|
|
1848
1849
|
}
|
|
1849
1850
|
function initializeWorkOfficeSpreadsheetRoots(roots, content) {
|
|
@@ -3145,11 +3146,4264 @@ function isoDateFromTimestamp(timestamp) {
|
|
|
3145
3146
|
if (!Number.isFinite(timestamp) || year < 1900 || year > 9999) return null;
|
|
3146
3147
|
return `${String(year).padStart(4, '0')}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;
|
|
3147
3148
|
}
|
|
3149
|
+
const MAX_XLSX_RICH_TEXT_CELL_CHARACTERS = 32767;
|
|
3150
|
+
const MAX_XLSX_RICH_TEXT_RUNS_PER_CELL = 512;
|
|
3151
|
+
const MAX_XLSX_RICH_TEXT_FONT_NAME_CHARACTERS = 128;
|
|
3152
|
+
const MAX_XLSX_RICH_TEXT_FONT_SIZE = 409;
|
|
3153
|
+
function normalizeXlsxRichTextEditSource(cell) {
|
|
3154
|
+
if (cell?.f) return null;
|
|
3155
|
+
if (cell) {
|
|
3156
|
+
const rich = normalizeXlsxRichTextCell(cell);
|
|
3157
|
+
if (rich) return rich;
|
|
3158
|
+
}
|
|
3159
|
+
const text = cell?.v;
|
|
3160
|
+
if (void 0 !== text && ('string' != typeof text || text.length > MAX_XLSX_RICH_TEXT_CELL_CHARACTERS || !validXlsxRichText(text))) return null;
|
|
3161
|
+
const value = text ?? '';
|
|
3162
|
+
return {
|
|
3163
|
+
runs: value ? [
|
|
3164
|
+
xlsxRichTextBaseRun(cell, value)
|
|
3165
|
+
] : [],
|
|
3166
|
+
text: value
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
function xlsxRichTextBaseRun(cell, text) {
|
|
3170
|
+
const run = {
|
|
3171
|
+
v: text
|
|
3172
|
+
};
|
|
3173
|
+
if (1 === Number(cell?.bl)) run.bl = 1;
|
|
3174
|
+
if (1 === Number(cell?.it)) run.it = 1;
|
|
3175
|
+
if (1 === Number(cell?.cl)) run.cl = 1;
|
|
3176
|
+
const underline = Number(cell?.un);
|
|
3177
|
+
if (Number.isSafeInteger(underline) && underline >= 1 && underline <= 4) run.un = underline;
|
|
3178
|
+
if ('string' == typeof cell?.ff && cell.ff.trim() && cell.ff.trim().length <= MAX_XLSX_RICH_TEXT_FONT_NAME_CHARACTERS) run.ff = cell.ff.trim();
|
|
3179
|
+
const size = Number(cell?.fs);
|
|
3180
|
+
if (Number.isFinite(size) && size >= 1 && size <= MAX_XLSX_RICH_TEXT_FONT_SIZE) run.fs = size;
|
|
3181
|
+
const color = normalizeXlsxRichTextColor(cell?.fc);
|
|
3182
|
+
if (color) {
|
|
3183
|
+
run.fc = color;
|
|
3184
|
+
const origin = normalizeXlsxSemanticColorOrigin(xlsxCellStyleOrigin(cell ?? {})?.fontColor);
|
|
3185
|
+
if (origin && xlsxSemanticColorMatchesValue(origin, color)) run.a3sXlsxColorOrigin = origin;
|
|
3186
|
+
}
|
|
3187
|
+
return run;
|
|
3188
|
+
}
|
|
3189
|
+
function normalizeXlsxRichTextCell(cell) {
|
|
3190
|
+
if (cell.f || cell.ct?.t !== 'inlineStr' || !Array.isArray(cell.ct.s)) return null;
|
|
3191
|
+
if (!cell.ct.s.length || cell.ct.s.length > MAX_XLSX_RICH_TEXT_RUNS_PER_CELL) return null;
|
|
3192
|
+
const runs = [];
|
|
3193
|
+
let characterCount = 0;
|
|
3194
|
+
for (const candidate of cell.ct.s){
|
|
3195
|
+
const run = normalizeXlsxRichTextRun(candidate);
|
|
3196
|
+
if (!run) return null;
|
|
3197
|
+
if (run.v) {
|
|
3198
|
+
characterCount += run.v.length;
|
|
3199
|
+
if (characterCount > MAX_XLSX_RICH_TEXT_CELL_CHARACTERS) return null;
|
|
3200
|
+
runs.push(run);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
const text = runs.map((run)=>run.v).join('');
|
|
3204
|
+
return runs.length && text ? {
|
|
3205
|
+
runs,
|
|
3206
|
+
text
|
|
3207
|
+
} : null;
|
|
3208
|
+
}
|
|
3209
|
+
function normalizeXlsxRichTextRun(value) {
|
|
3210
|
+
if (!isRecord(value) || 'string' != typeof value.v || !validXlsxRichText(value.v)) return null;
|
|
3211
|
+
const run = {
|
|
3212
|
+
v: value.v
|
|
3213
|
+
};
|
|
3214
|
+
copyToggle(value, run, 'bl');
|
|
3215
|
+
copyToggle(value, run, 'it');
|
|
3216
|
+
copyToggle(value, run, 'cl');
|
|
3217
|
+
if ('string' == typeof value.ff && value.ff.trim() && value.ff.trim().length <= MAX_XLSX_RICH_TEXT_FONT_NAME_CHARACTERS) run.ff = value.ff.trim();
|
|
3218
|
+
if ('number' == typeof value.fs && Number.isFinite(value.fs) && value.fs >= 1 && value.fs <= MAX_XLSX_RICH_TEXT_FONT_SIZE) run.fs = value.fs;
|
|
3219
|
+
const color = normalizeXlsxRichTextColor(value.fc);
|
|
3220
|
+
if (color) run.fc = color;
|
|
3221
|
+
const underline = Number(value.un);
|
|
3222
|
+
if (Number.isSafeInteger(underline) && underline >= 0 && underline <= 4 && void 0 !== value.un) run.un = underline;
|
|
3223
|
+
const colorOrigin = normalizeXlsxSemanticColorOrigin(value.a3sXlsxColorOrigin);
|
|
3224
|
+
if (colorOrigin) run.a3sXlsxColorOrigin = colorOrigin;
|
|
3225
|
+
return run;
|
|
3226
|
+
}
|
|
3227
|
+
function normalizeXlsxRichTextColor(value) {
|
|
3228
|
+
const rgb = xlsxRgbColor(value);
|
|
3229
|
+
return rgb ? `#${rgb.slice(-6).toLowerCase()}` : null;
|
|
3230
|
+
}
|
|
3231
|
+
function coalesceXlsxRichTextRuns(source) {
|
|
3232
|
+
const result = [];
|
|
3233
|
+
for (const run of source){
|
|
3234
|
+
if (!run.v) continue;
|
|
3235
|
+
const previous = result.at(-1);
|
|
3236
|
+
if (previous && sameXlsxRichTextRunStyle(previous, run)) previous.v += run.v;
|
|
3237
|
+
else result.push({
|
|
3238
|
+
...run
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
return result;
|
|
3242
|
+
}
|
|
3243
|
+
function sameXlsxRichTextRunStyle(left, right) {
|
|
3244
|
+
return left.bl === right.bl && left.cl === right.cl && left.fc === right.fc && left.ff === right.ff && left.fs === right.fs && left.it === right.it && left.un === right.un && JSON.stringify(left.a3sXlsxColorOrigin) === JSON.stringify(right.a3sXlsxColorOrigin);
|
|
3245
|
+
}
|
|
3246
|
+
function validXlsxRichText(value) {
|
|
3247
|
+
for(let index = 0; index < value.length; index += 1){
|
|
3248
|
+
const code = value.charCodeAt(index);
|
|
3249
|
+
if (0x09 !== code && 0x0a !== code && 0x0d !== code && (!(code >= 0x20) || !(code <= 0xd7ff)) && (!(code >= 0xe000) || !(code <= 0xfffd))) {
|
|
3250
|
+
if (isHighSurrogate(code) && index + 1 < value.length && isLowSurrogate(value.charCodeAt(index + 1))) {
|
|
3251
|
+
index += 1;
|
|
3252
|
+
continue;
|
|
3253
|
+
}
|
|
3254
|
+
return false;
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
return true;
|
|
3258
|
+
}
|
|
3259
|
+
function isHighSurrogate(value) {
|
|
3260
|
+
return value >= 0xd800 && value <= 0xdbff;
|
|
3261
|
+
}
|
|
3262
|
+
function isLowSurrogate(value) {
|
|
3263
|
+
return value >= 0xdc00 && value <= 0xdfff;
|
|
3264
|
+
}
|
|
3265
|
+
function copyToggle(source, target, key) {
|
|
3266
|
+
if (1 === Number(source[key])) target[key] = 1;
|
|
3267
|
+
else if (0 === Number(source[key]) && void 0 !== source[key]) target[key] = 0;
|
|
3268
|
+
}
|
|
3269
|
+
function isRecord(value) {
|
|
3270
|
+
return 'object' == typeof value && null !== value && !Array.isArray(value);
|
|
3271
|
+
}
|
|
3272
|
+
function reconcileSpreadsheetRichTextCellEdit(previous, current, formattedPaste) {
|
|
3273
|
+
if (current.f) return current;
|
|
3274
|
+
const source = formattedPaste ? normalizeXlsxRichTextEditSource(previous) : previous ? normalizeXlsxRichTextCell(previous) : null;
|
|
3275
|
+
if (!source) return current;
|
|
3276
|
+
const live = liveRichTextValue(current);
|
|
3277
|
+
if (!live || '' === live.text) return current;
|
|
3278
|
+
const replacement = richTextReplacement(source.text, live.text);
|
|
3279
|
+
const sourceRuns = indexRichTextRuns(source);
|
|
3280
|
+
const formattedPasteRuns = formattedPaste ? reconstructFormattedPasteRuns(source, sourceRuns, live.text, formattedPaste) : null;
|
|
3281
|
+
const runs = formattedPasteRuns ?? (live.rich && live.text === source.text ? restoreSemanticColorOrigins(live.rich.runs, sourceRuns, replacement) : reconstructPlainTextRuns(sourceRuns, replacement));
|
|
3282
|
+
const normalizedRuns = coalesceXlsxRichTextRuns(runs);
|
|
3283
|
+
if (!normalizedRuns.length || normalizedRuns.length > 512 || normalizedRuns.map((run)=>run.v).join('') !== live.text) return current;
|
|
3284
|
+
return spreadsheetRichTextCellWithRuns(previous, current, live.text, normalizedRuns);
|
|
3285
|
+
}
|
|
3286
|
+
function restoreSpreadsheetRichTextCellRuns(previous, current) {
|
|
3287
|
+
if (!previous || current.f) return current;
|
|
3288
|
+
const source = normalizeXlsxRichTextCell(previous);
|
|
3289
|
+
const live = liveRichTextValue(current);
|
|
3290
|
+
if (!source || !live || source.text !== live.text) return current;
|
|
3291
|
+
return spreadsheetRichTextCellWithRuns(previous, current, source.text, source.runs);
|
|
3292
|
+
}
|
|
3293
|
+
function spreadsheetRichTextCellWithRuns(previous, current, text, runs) {
|
|
3294
|
+
const next = {
|
|
3295
|
+
...current,
|
|
3296
|
+
ct: {
|
|
3297
|
+
...previous?.ct,
|
|
3298
|
+
...current.ct,
|
|
3299
|
+
s: runs.map((run)=>({
|
|
3300
|
+
...run
|
|
3301
|
+
})),
|
|
3302
|
+
t: 'inlineStr'
|
|
3303
|
+
},
|
|
3304
|
+
v: text
|
|
3305
|
+
};
|
|
3306
|
+
delete next.f;
|
|
3307
|
+
delete next.m;
|
|
3308
|
+
return next;
|
|
3309
|
+
}
|
|
3310
|
+
function sameSpreadsheetRichTextCellText(previous, current) {
|
|
3311
|
+
if (!previous || current.f) return false;
|
|
3312
|
+
const source = normalizeXlsxRichTextCell(previous);
|
|
3313
|
+
const live = liveRichTextValue(current);
|
|
3314
|
+
return Boolean(source && live && source.text === live.text);
|
|
3315
|
+
}
|
|
3316
|
+
function liveRichTextValue(cell) {
|
|
3317
|
+
const rich = normalizeXlsxRichTextCell(cell);
|
|
3318
|
+
if ('string' == typeof cell.v && cell.v !== rich?.text) return validBoundedText(cell.v) ? {
|
|
3319
|
+
rich: null,
|
|
3320
|
+
text: cell.v
|
|
3321
|
+
} : null;
|
|
3322
|
+
if (rich) return {
|
|
3323
|
+
rich,
|
|
3324
|
+
text: rich.text
|
|
3325
|
+
};
|
|
3326
|
+
return 'string' == typeof cell.v && validBoundedText(cell.v) ? {
|
|
3327
|
+
rich: null,
|
|
3328
|
+
text: cell.v
|
|
3329
|
+
} : null;
|
|
3330
|
+
}
|
|
3331
|
+
function validBoundedText(value) {
|
|
3332
|
+
return value.length <= 32767 && validXlsxRichText(value);
|
|
3333
|
+
}
|
|
3334
|
+
function richTextReplacement(previous, current) {
|
|
3335
|
+
let left = 0;
|
|
3336
|
+
let right = 0;
|
|
3337
|
+
while(left < previous.length && left < current.length && previous[left] === current[left])left += 1;
|
|
3338
|
+
if (left > 0 && isHighSurrogate(previous.charCodeAt(left - 1))) left -= 1;
|
|
3339
|
+
while(right + left < previous.length && right + left < current.length && previous[previous.length - right - 1] === current[current.length - right - 1])right += 1;
|
|
3340
|
+
if (right > 0 && isLowSurrogate(previous.charCodeAt(previous.length - right))) right -= 1;
|
|
3341
|
+
return {
|
|
3342
|
+
index: left,
|
|
3343
|
+
insert: current.slice(left, current.length - right),
|
|
3344
|
+
remove: previous.length - left - right
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
function indexRichTextRuns(source) {
|
|
3348
|
+
let offset = 0;
|
|
3349
|
+
return source.runs.map((run)=>{
|
|
3350
|
+
const indexed = {
|
|
3351
|
+
end: offset + run.v.length,
|
|
3352
|
+
run,
|
|
3353
|
+
start: offset
|
|
3354
|
+
};
|
|
3355
|
+
offset = indexed.end;
|
|
3356
|
+
return indexed;
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
function reconstructPlainTextRuns(source, replacement) {
|
|
3360
|
+
const runs = [];
|
|
3361
|
+
appendSourceRange(runs, source, 0, replacement.index);
|
|
3362
|
+
if (replacement.insert) {
|
|
3363
|
+
const inherited = insertionSourceRun(source, replacement);
|
|
3364
|
+
if (inherited) runs.push({
|
|
3365
|
+
...inherited.run,
|
|
3366
|
+
v: replacement.insert
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
const sourceLength = source.at(-1)?.end ?? 0;
|
|
3370
|
+
appendSourceRange(runs, source, replacement.index + replacement.remove, sourceLength);
|
|
3371
|
+
return runs;
|
|
3372
|
+
}
|
|
3373
|
+
function reconstructFormattedPasteRuns(source, sourceRuns, currentText, intent) {
|
|
3374
|
+
if (!validRichTextPasteSelection(source.text, intent.start, intent.end) || 'string' != typeof intent.text || !intent.text || intent.text.length > 32767 || !validXlsxRichText(intent.text) || !Array.isArray(intent.runs) || !intent.runs.length || intent.runs.length > 512) return null;
|
|
3375
|
+
const pastedRuns = [];
|
|
3376
|
+
for (const candidate of intent.runs){
|
|
3377
|
+
const run = normalizeXlsxRichTextRun(candidate);
|
|
3378
|
+
if (!run) return null;
|
|
3379
|
+
if (run.v) pastedRuns.push(run);
|
|
3380
|
+
}
|
|
3381
|
+
if (!pastedRuns.length || pastedRuns.map((run)=>run.v).join('') !== intent.text) return null;
|
|
3382
|
+
const expectedText = source.text.slice(0, intent.start) + intent.text + source.text.slice(intent.end);
|
|
3383
|
+
if (expectedText !== currentText || expectedText.length > 32767 || !validXlsxRichText(expectedText)) return null;
|
|
3384
|
+
const runs = [];
|
|
3385
|
+
appendSourceRange(runs, sourceRuns, 0, intent.start);
|
|
3386
|
+
runs.push(...pastedRuns);
|
|
3387
|
+
appendSourceRange(runs, sourceRuns, intent.end, source.text.length);
|
|
3388
|
+
const normalized = coalesceXlsxRichTextRuns(runs);
|
|
3389
|
+
return normalized.length > 0 && normalized.length <= 512 ? normalized : null;
|
|
3390
|
+
}
|
|
3391
|
+
function validRichTextPasteSelection(text, start, end) {
|
|
3392
|
+
return Boolean(Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end && end <= text.length && !splitsSurrogatePair(text, start) && !splitsSurrogatePair(text, end));
|
|
3393
|
+
}
|
|
3394
|
+
function splitsSurrogatePair(text, offset) {
|
|
3395
|
+
if (offset <= 0 || offset >= text.length) return false;
|
|
3396
|
+
return isHighSurrogate(text.charCodeAt(offset - 1)) && isLowSurrogate(text.charCodeAt(offset));
|
|
3397
|
+
}
|
|
3398
|
+
function appendSourceRange(target, source, start, end) {
|
|
3399
|
+
if (start >= end) return;
|
|
3400
|
+
for (const indexed of source){
|
|
3401
|
+
const overlapStart = Math.max(start, indexed.start);
|
|
3402
|
+
const overlapEnd = Math.min(end, indexed.end);
|
|
3403
|
+
if (!(overlapStart >= overlapEnd)) target.push({
|
|
3404
|
+
...indexed.run,
|
|
3405
|
+
v: indexed.run.v.slice(overlapStart - indexed.start, overlapEnd - indexed.start)
|
|
3406
|
+
});
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
function restoreSemanticColorOrigins(current, source, replacement) {
|
|
3410
|
+
const runs = [];
|
|
3411
|
+
let absoluteOffset = 0;
|
|
3412
|
+
for (const currentRun of current){
|
|
3413
|
+
let localOffset = 0;
|
|
3414
|
+
while(localOffset < currentRun.v.length){
|
|
3415
|
+
const width = codePointWidth(currentRun.v, localOffset);
|
|
3416
|
+
const origin = semanticOriginAt(source, replacement, absoluteOffset + localOffset);
|
|
3417
|
+
const next = {
|
|
3418
|
+
...currentRun,
|
|
3419
|
+
v: currentRun.v.slice(localOffset, localOffset + width)
|
|
3420
|
+
};
|
|
3421
|
+
delete next.a3sXlsxColorOrigin;
|
|
3422
|
+
if (origin && currentRun.fc && xlsxSemanticColorMatchesValue(origin, currentRun.fc)) next.a3sXlsxColorOrigin = origin;
|
|
3423
|
+
runs.push(next);
|
|
3424
|
+
localOffset += width;
|
|
3425
|
+
}
|
|
3426
|
+
absoluteOffset += currentRun.v.length;
|
|
3427
|
+
}
|
|
3428
|
+
return runs;
|
|
3429
|
+
}
|
|
3430
|
+
function semanticOriginAt(source, replacement, currentOffset) {
|
|
3431
|
+
let indexed;
|
|
3432
|
+
indexed = currentOffset < replacement.index ? sourceRunAt(source, currentOffset) : currentOffset >= replacement.index + replacement.insert.length ? sourceRunAt(source, currentOffset - replacement.insert.length + replacement.remove) : insertionSourceRun(source, replacement);
|
|
3433
|
+
return normalizeXlsxSemanticColorOrigin(indexed?.run.a3sXlsxColorOrigin);
|
|
3434
|
+
}
|
|
3435
|
+
function insertionSourceRun(source, replacement) {
|
|
3436
|
+
const sourceLength = source.at(-1)?.end ?? 0;
|
|
3437
|
+
if (!sourceLength) return;
|
|
3438
|
+
if (replacement.remove > 0 && replacement.index < sourceLength) return sourceRunAt(source, replacement.index);
|
|
3439
|
+
if (replacement.index > 0) return sourceRunAt(source, replacement.index - 1);
|
|
3440
|
+
return sourceRunAt(source, 0);
|
|
3441
|
+
}
|
|
3442
|
+
function sourceRunAt(source, offset) {
|
|
3443
|
+
let left = 0;
|
|
3444
|
+
let right = source.length - 1;
|
|
3445
|
+
while(left <= right){
|
|
3446
|
+
const middle = left + Math.floor((right - left) / 2);
|
|
3447
|
+
const indexed = source[middle];
|
|
3448
|
+
if (!indexed) break;
|
|
3449
|
+
if (offset < indexed.start) right = middle - 1;
|
|
3450
|
+
else {
|
|
3451
|
+
if (!(offset >= indexed.end)) return indexed;
|
|
3452
|
+
left = middle + 1;
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
function codePointWidth(value, offset) {
|
|
3457
|
+
return isHighSurrogate(value.charCodeAt(offset)) && isLowSurrogate(value.charCodeAt(offset + 1)) ? 2 : 1;
|
|
3458
|
+
}
|
|
3459
|
+
const spreadsheetFontSizes = [
|
|
3460
|
+
9,
|
|
3461
|
+
10,
|
|
3462
|
+
11,
|
|
3463
|
+
12,
|
|
3464
|
+
14,
|
|
3465
|
+
16,
|
|
3466
|
+
18,
|
|
3467
|
+
20,
|
|
3468
|
+
22,
|
|
3469
|
+
24,
|
|
3470
|
+
28,
|
|
3471
|
+
36,
|
|
3472
|
+
48,
|
|
3473
|
+
72
|
|
3474
|
+
];
|
|
3475
|
+
function nextSpreadsheetFontSize(current, direction) {
|
|
3476
|
+
if (!Number.isFinite(current) || current < 1 || current > 409) return null;
|
|
3477
|
+
return 'grow' === direction ? spreadsheetFontSizes.find((size)=>size > current) ?? null : [
|
|
3478
|
+
...spreadsheetFontSizes
|
|
3479
|
+
].reverse().find((size)=>size < current) ?? null;
|
|
3480
|
+
}
|
|
3481
|
+
function captureSpreadsheetRichTextDomSelection({ expandedOnly = false, root, target }) {
|
|
3482
|
+
const selection = window.getSelection();
|
|
3483
|
+
if (!root || !selection || 1 !== selection.rangeCount) return null;
|
|
3484
|
+
const range = selection.getRangeAt(0);
|
|
3485
|
+
if (expandedOnly && range.collapsed) return null;
|
|
3486
|
+
const targetNode = target instanceof Node ? target : null;
|
|
3487
|
+
const editor = spreadsheetRichTextEditor(targetNode ?? range.commonAncestorContainer);
|
|
3488
|
+
if (!editor || !root.contains(editor) || !editorContainsRange(editor, range)) return null;
|
|
3489
|
+
const start = spreadsheetTextOffsetAtPoint(editor, range.startContainer, range.startOffset);
|
|
3490
|
+
const end = spreadsheetTextOffsetAtPoint(editor, range.endContainer, range.endOffset);
|
|
3491
|
+
return null === start || null === end || start > end ? null : {
|
|
3492
|
+
editor,
|
|
3493
|
+
end,
|
|
3494
|
+
start
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
function spreadsheetRichTextEditor(node) {
|
|
3498
|
+
const element = node instanceof Element ? node : node.parentElement;
|
|
3499
|
+
const editor = element?.closest('.luckysheet-cell-input, .fortune-fx-input');
|
|
3500
|
+
return editor?.isContentEditable || editor?.getAttribute('contenteditable') === 'true' ? editor : null;
|
|
3501
|
+
}
|
|
3502
|
+
function editorContainsRange(editor, range) {
|
|
3503
|
+
return (range.startContainer === editor || editor.contains(range.startContainer)) && (range.endContainer === editor || editor.contains(range.endContainer));
|
|
3504
|
+
}
|
|
3505
|
+
function spreadsheetTextOffsetAtPoint(editor, node, offset) {
|
|
3506
|
+
try {
|
|
3507
|
+
const prefix = document.createRange();
|
|
3508
|
+
prefix.selectNodeContents(editor);
|
|
3509
|
+
prefix.setEnd(node, offset);
|
|
3510
|
+
return prefix.toString().length;
|
|
3511
|
+
} catch {
|
|
3512
|
+
return null;
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
const MAX_SPREADSHEET_RICH_TEXT_CLIPBOARD_HTML_CHARACTERS = 256000;
|
|
3516
|
+
const pendingPastes = new WeakMap();
|
|
3517
|
+
const ignoredClipboardElements = new Set([
|
|
3518
|
+
'HEAD',
|
|
3519
|
+
'META',
|
|
3520
|
+
'NOSCRIPT',
|
|
3521
|
+
'SCRIPT',
|
|
3522
|
+
'STYLE',
|
|
3523
|
+
'TITLE'
|
|
3524
|
+
]);
|
|
3525
|
+
const blockClipboardElements = new Set([
|
|
3526
|
+
'ADDRESS',
|
|
3527
|
+
'BLOCKQUOTE',
|
|
3528
|
+
'DIV',
|
|
3529
|
+
'H1',
|
|
3530
|
+
'H2',
|
|
3531
|
+
'H3',
|
|
3532
|
+
'H4',
|
|
3533
|
+
'H5',
|
|
3534
|
+
'H6',
|
|
3535
|
+
'LI',
|
|
3536
|
+
'P',
|
|
3537
|
+
'PRE'
|
|
3538
|
+
]);
|
|
3539
|
+
function parseSpreadsheetRichTextClipboard(html, plainText) {
|
|
3540
|
+
const text = normalizeClipboardText(plainText);
|
|
3541
|
+
if (!html || html.length > MAX_SPREADSHEET_RICH_TEXT_CLIPBOARD_HTML_CHARACTERS || !text || text.length > 32767 || !validXlsxRichText(text)) return null;
|
|
3542
|
+
const template = document.createElement('template');
|
|
3543
|
+
template.innerHTML = html;
|
|
3544
|
+
const runs = [];
|
|
3545
|
+
const state = {
|
|
3546
|
+
characterCount: 0,
|
|
3547
|
+
invalid: false
|
|
3548
|
+
};
|
|
3549
|
+
for (const child of template.content.childNodes){
|
|
3550
|
+
collectClipboardRuns(child, {}, runs, state);
|
|
3551
|
+
if (state.invalid) return null;
|
|
3552
|
+
}
|
|
3553
|
+
trimTrailingClipboardNewline(runs, text);
|
|
3554
|
+
const normalized = coalesceXlsxRichTextRuns(runs);
|
|
3555
|
+
if (!normalized.length || normalized.length > 512 || normalized.map((run)=>run.v).join('') !== text || !normalized.some(spreadsheetRichTextRunHasFormatting)) return null;
|
|
3556
|
+
return {
|
|
3557
|
+
runs: normalized,
|
|
3558
|
+
text
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
function captureSpreadsheetRichTextPaste({ clipboardData, content, root, selection, sheetId, target }) {
|
|
3562
|
+
const domSelection = captureSpreadsheetRichTextDomSelection({
|
|
3563
|
+
root,
|
|
3564
|
+
target
|
|
3565
|
+
});
|
|
3566
|
+
const sheet = content.sheets.find((candidate)=>candidate.id === sheetId);
|
|
3567
|
+
if (!domSelection || !sheet || !selection) return false;
|
|
3568
|
+
const row = focusedAxisIndex(selection.row_focus, selection.row);
|
|
3569
|
+
const column = focusedAxisIndex(selection.column_focus, selection.column);
|
|
3570
|
+
const cell = spreadsheetCellAt(sheet, row, column);
|
|
3571
|
+
const source = normalizeXlsxRichTextEditSource(cell);
|
|
3572
|
+
const key = spreadsheetRichTextPasteKey(row, column);
|
|
3573
|
+
if (!source || domSelection.editor.textContent !== source.text || domSelection.end > source.text.length) {
|
|
3574
|
+
clearPendingPaste(sheet, key);
|
|
3575
|
+
return false;
|
|
3576
|
+
}
|
|
3577
|
+
const parsed = parseSpreadsheetRichTextClipboard(clipboardData.getData('text/html'), clipboardData.getData('text/plain'));
|
|
3578
|
+
if (!parsed) {
|
|
3579
|
+
clearPendingPaste(sheet, key);
|
|
3580
|
+
return false;
|
|
3581
|
+
}
|
|
3582
|
+
stageSpreadsheetRichTextPaste(sheet, row, column, source.text, {
|
|
3583
|
+
end: domSelection.end,
|
|
3584
|
+
runs: parsed.runs,
|
|
3585
|
+
start: domSelection.start,
|
|
3586
|
+
text: parsed.text
|
|
3587
|
+
});
|
|
3588
|
+
return true;
|
|
3589
|
+
}
|
|
3590
|
+
function stageSpreadsheetRichTextPaste(sheet, row, column, sourceText, intent) {
|
|
3591
|
+
if (!validCoordinate(row) || !validCoordinate(column)) return;
|
|
3592
|
+
const key = spreadsheetRichTextPasteKey(row, column);
|
|
3593
|
+
const entries = pendingPastes.get(sheet) ?? new Map();
|
|
3594
|
+
pendingPastes.set(sheet, entries);
|
|
3595
|
+
entries.set(key, {
|
|
3596
|
+
intent: {
|
|
3597
|
+
...intent,
|
|
3598
|
+
runs: intent.runs.map((run)=>({
|
|
3599
|
+
...run
|
|
3600
|
+
}))
|
|
3601
|
+
},
|
|
3602
|
+
sourceText
|
|
3603
|
+
});
|
|
3604
|
+
}
|
|
3605
|
+
function takeSpreadsheetRichTextPaste(sheet, row, column, current) {
|
|
3606
|
+
const intent = peekSpreadsheetRichTextPaste(sheet, row, column, current);
|
|
3607
|
+
if (intent) consumeSpreadsheetRichTextPaste(sheet, row, column);
|
|
3608
|
+
return intent;
|
|
3609
|
+
}
|
|
3610
|
+
function peekSpreadsheetRichTextPaste(sheet, row, column, current) {
|
|
3611
|
+
const entries = pendingPastes.get(sheet);
|
|
3612
|
+
const key = spreadsheetRichTextPasteKey(row, column);
|
|
3613
|
+
const pending = entries?.get(key);
|
|
3614
|
+
if (!entries || !pending) return;
|
|
3615
|
+
const currentText = spreadsheetRichTextCellText(current);
|
|
3616
|
+
if (currentText === pending.sourceText) return;
|
|
3617
|
+
return null === currentText ? void 0 : pending.intent;
|
|
3618
|
+
}
|
|
3619
|
+
function consumeSpreadsheetRichTextPaste(sheet, row, column) {
|
|
3620
|
+
clearPendingPaste(sheet, spreadsheetRichTextPasteKey(row, column));
|
|
3621
|
+
}
|
|
3622
|
+
function collectClipboardRuns(node, inherited, runs, state) {
|
|
3623
|
+
if (state.invalid) return;
|
|
3624
|
+
if (node.nodeType === Node.TEXT_NODE) return void appendClipboardText(runs, inherited, node.textContent ?? '', state);
|
|
3625
|
+
if (!(node instanceof HTMLElement) || ignoredClipboardElements.has(node.tagName)) return;
|
|
3626
|
+
const style = spreadsheetClipboardElementStyle(node, inherited);
|
|
3627
|
+
if ('BR' === node.tagName) return void appendClipboardText(runs, style, '\n', state);
|
|
3628
|
+
for (const child of node.childNodes){
|
|
3629
|
+
collectClipboardRuns(child, style, runs, state);
|
|
3630
|
+
if (state.invalid) return;
|
|
3631
|
+
}
|
|
3632
|
+
if (blockClipboardElements.has(node.tagName) && hasFollowingClipboardContent(node)) appendClipboardText(runs, style, '\n', state);
|
|
3633
|
+
}
|
|
3634
|
+
function appendClipboardText(runs, style, source, state) {
|
|
3635
|
+
const value = normalizeClipboardText(source);
|
|
3636
|
+
if (!value) return;
|
|
3637
|
+
state.characterCount += value.length;
|
|
3638
|
+
if (state.characterCount > 32767 || !validXlsxRichText(value)) {
|
|
3639
|
+
state.invalid = true;
|
|
3640
|
+
return;
|
|
3641
|
+
}
|
|
3642
|
+
const run = {
|
|
3643
|
+
...style,
|
|
3644
|
+
v: value
|
|
3645
|
+
};
|
|
3646
|
+
const previous = runs.at(-1);
|
|
3647
|
+
if (previous && sameXlsxRichTextRunStyle(previous, run)) previous.v += value;
|
|
3648
|
+
else runs.push(run);
|
|
3649
|
+
if (runs.length > 512) state.invalid = true;
|
|
3650
|
+
}
|
|
3651
|
+
function spreadsheetClipboardElementStyle(element, inherited) {
|
|
3652
|
+
const style = {
|
|
3653
|
+
...inherited
|
|
3654
|
+
};
|
|
3655
|
+
if (element.matches('b, strong')) style.bl = 1;
|
|
3656
|
+
if (element.matches('i, em')) style.it = 1;
|
|
3657
|
+
if (element.matches('s, strike, del')) style.cl = 1;
|
|
3658
|
+
if (element.matches('u')) style.un = 1;
|
|
3659
|
+
const declarations = cssDeclarations(element.getAttribute('style'));
|
|
3660
|
+
applyFontWeight(style, declarations.get('font-weight'));
|
|
3661
|
+
applyToggle(style, 'it', declarations.get('font-style'), 'italic');
|
|
3662
|
+
applyTextDecoration(style, declarations);
|
|
3663
|
+
applyFontFamily(style, declarations.get('font-family'));
|
|
3664
|
+
applyFontSize(style, declarations.get('font-size'));
|
|
3665
|
+
applyFontColor(style, declarations.get('color'));
|
|
3666
|
+
applyFortuneToggle(style, 'cl', declarations.get('lucky-strike'));
|
|
3667
|
+
applyFortuneUnderline(style, declarations.get('lucky-underline'));
|
|
3668
|
+
if (declarations.get('border-bottom')?.trim().toLowerCase() !== 'none') {
|
|
3669
|
+
if (declarations.has('border-bottom')) style.un = 1;
|
|
3670
|
+
}
|
|
3671
|
+
if ('FONT' === element.tagName) {
|
|
3672
|
+
applyFontFamily(style, element.getAttribute('face') ?? void 0);
|
|
3673
|
+
applyFontColor(style, element.getAttribute('color') ?? void 0);
|
|
3674
|
+
applyHtmlFontSize(style, element.getAttribute('size'));
|
|
3675
|
+
}
|
|
3676
|
+
return style;
|
|
3677
|
+
}
|
|
3678
|
+
function cssDeclarations(value) {
|
|
3679
|
+
const declarations = new Map();
|
|
3680
|
+
for (const candidate of value?.split(';') ?? []){
|
|
3681
|
+
const separator = candidate.indexOf(':');
|
|
3682
|
+
if (separator <= 0) continue;
|
|
3683
|
+
const key = candidate.slice(0, separator).trim().toLowerCase();
|
|
3684
|
+
const declaration = candidate.slice(separator + 1).trim();
|
|
3685
|
+
if (key && declaration) declarations.set(key, declaration);
|
|
3686
|
+
}
|
|
3687
|
+
return declarations;
|
|
3688
|
+
}
|
|
3689
|
+
function applyFontWeight(style, value) {
|
|
3690
|
+
const normalized = value?.trim().toLowerCase();
|
|
3691
|
+
if (!normalized) return;
|
|
3692
|
+
if ('bold' === normalized || 'bolder' === normalized) style.bl = 1;
|
|
3693
|
+
else if ('normal' === normalized || 'lighter' === normalized) style.bl = 0;
|
|
3694
|
+
else if (/^\d+$/.test(normalized)) style.bl = Number(normalized) >= 600 ? 1 : 0;
|
|
3695
|
+
}
|
|
3696
|
+
function applyToggle(style, key, value, enabled) {
|
|
3697
|
+
const normalized = value?.trim().toLowerCase();
|
|
3698
|
+
if (!normalized) return;
|
|
3699
|
+
style[key] = normalized === enabled || 'oblique' === normalized ? 1 : 0;
|
|
3700
|
+
}
|
|
3701
|
+
function applyTextDecoration(style, declarations) {
|
|
3702
|
+
const value = declarations.get('text-decoration-line') ?? declarations.get('text-decoration');
|
|
3703
|
+
const normalized = value?.toLowerCase();
|
|
3704
|
+
if (!normalized) return;
|
|
3705
|
+
if (normalized.includes('none')) {
|
|
3706
|
+
style.cl = 0;
|
|
3707
|
+
style.un = 0;
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
if (normalized.includes('line-through')) style.cl = 1;
|
|
3711
|
+
if (normalized.includes('underline')) style.un = 'double' === declarations.get('text-decoration-style') ? 2 : 1;
|
|
3712
|
+
}
|
|
3713
|
+
function applyFontFamily(style, value) {
|
|
3714
|
+
const family = value?.split(',')[0]?.trim().replace(/^(['"])(.*)\1$/, '$2');
|
|
3715
|
+
if (family && family.length <= 128) style.ff = family;
|
|
3716
|
+
}
|
|
3717
|
+
function applyFontSize(style, value) {
|
|
3718
|
+
const match = /^([\d.]+)\s*(pt|px)$/i.exec(value?.trim() ?? '');
|
|
3719
|
+
if (!match) return;
|
|
3720
|
+
const number = Number(match[1]);
|
|
3721
|
+
const size = match[2]?.toLowerCase() === 'px' ? 0.75 * number : number;
|
|
3722
|
+
if (Number.isFinite(size) && size >= 1 && size <= 409) style.fs = Math.round(100 * size) / 100;
|
|
3723
|
+
}
|
|
3724
|
+
function applyFontColor(style, value) {
|
|
3725
|
+
const color = normalizeCssColor(value);
|
|
3726
|
+
if (color && 'transparent' !== color) style.fc = color;
|
|
3727
|
+
}
|
|
3728
|
+
function applyFortuneToggle(style, key, value) {
|
|
3729
|
+
const toggle = Number(value);
|
|
3730
|
+
if (Number.isSafeInteger(toggle) && (0 === toggle || 1 === toggle)) style[key] = toggle;
|
|
3731
|
+
}
|
|
3732
|
+
function applyFortuneUnderline(style, value) {
|
|
3733
|
+
const underline = Number(value);
|
|
3734
|
+
if (Number.isSafeInteger(underline) && underline >= 0 && underline <= 4) style.un = underline;
|
|
3735
|
+
}
|
|
3736
|
+
function applyHtmlFontSize(style, value) {
|
|
3737
|
+
const index = Number(value);
|
|
3738
|
+
const size = [
|
|
3739
|
+
0,
|
|
3740
|
+
8,
|
|
3741
|
+
10,
|
|
3742
|
+
12,
|
|
3743
|
+
14,
|
|
3744
|
+
18,
|
|
3745
|
+
24,
|
|
3746
|
+
36
|
|
3747
|
+
][index];
|
|
3748
|
+
if (size) style.fs = size;
|
|
3749
|
+
}
|
|
3750
|
+
function spreadsheetRichTextRunHasFormatting(run) {
|
|
3751
|
+
return [
|
|
3752
|
+
'bl',
|
|
3753
|
+
'cl',
|
|
3754
|
+
'fc',
|
|
3755
|
+
'ff',
|
|
3756
|
+
'fs',
|
|
3757
|
+
'it',
|
|
3758
|
+
'un'
|
|
3759
|
+
].some((key)=>void 0 !== run[key]);
|
|
3760
|
+
}
|
|
3761
|
+
function trimTrailingClipboardNewline(runs, expectedText) {
|
|
3762
|
+
const actual = runs.map((run)=>run.v).join('');
|
|
3763
|
+
if (!actual.endsWith('\n') || expectedText.endsWith('\n')) return;
|
|
3764
|
+
const last = runs.at(-1);
|
|
3765
|
+
if (!last) return;
|
|
3766
|
+
last.v = last.v.slice(0, -1);
|
|
3767
|
+
if (!last.v) runs.pop();
|
|
3768
|
+
}
|
|
3769
|
+
function hasFollowingClipboardContent(element) {
|
|
3770
|
+
let sibling = element.nextSibling;
|
|
3771
|
+
while(sibling){
|
|
3772
|
+
if (sibling.nodeType === Node.TEXT_NODE) {
|
|
3773
|
+
if (sibling.textContent) return true;
|
|
3774
|
+
} else if (sibling instanceof HTMLElement && !ignoredClipboardElements.has(sibling.tagName)) return true;
|
|
3775
|
+
sibling = sibling.nextSibling;
|
|
3776
|
+
}
|
|
3777
|
+
return false;
|
|
3778
|
+
}
|
|
3779
|
+
function spreadsheetRichTextCellText(cell) {
|
|
3780
|
+
const rich = normalizeXlsxRichTextCell(cell);
|
|
3781
|
+
if ('string' == typeof cell.v && cell.v !== rich?.text) return cell.v;
|
|
3782
|
+
return rich?.text ?? ('string' == typeof cell.v ? cell.v : null);
|
|
3783
|
+
}
|
|
3784
|
+
function spreadsheetCellAt(sheet, row, column) {
|
|
3785
|
+
return sheet.data?.[row]?.[column] ?? sheet.celldata?.find((candidate)=>candidate.r === row && candidate.c === column)?.v ?? null;
|
|
3786
|
+
}
|
|
3787
|
+
function focusedAxisIndex(value, axis) {
|
|
3788
|
+
const minimum = spreadsheet_rich_text_paste_finiteIndex(axis[0], 0);
|
|
3789
|
+
const maximum = spreadsheet_rich_text_paste_finiteIndex(axis[1], minimum);
|
|
3790
|
+
const focus = spreadsheet_rich_text_paste_finiteIndex(value, minimum);
|
|
3791
|
+
return Math.min(Math.max(minimum, maximum), Math.max(Math.min(minimum, maximum), focus));
|
|
3792
|
+
}
|
|
3793
|
+
function spreadsheet_rich_text_paste_finiteIndex(value, fallback) {
|
|
3794
|
+
return 'number' == typeof value && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : fallback;
|
|
3795
|
+
}
|
|
3796
|
+
function normalizeClipboardText(value) {
|
|
3797
|
+
return value.replace(/\r\n?/g, '\n').replace(/\u00a0/g, ' ');
|
|
3798
|
+
}
|
|
3799
|
+
function clearPendingPaste(sheet, key) {
|
|
3800
|
+
const entries = pendingPastes.get(sheet);
|
|
3801
|
+
entries?.delete(key);
|
|
3802
|
+
if (entries && !entries.size) pendingPastes.delete(sheet);
|
|
3803
|
+
}
|
|
3804
|
+
function spreadsheetRichTextPasteKey(row, column) {
|
|
3805
|
+
return `${row}:${column}`;
|
|
3806
|
+
}
|
|
3807
|
+
function validCoordinate(value) {
|
|
3808
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
3809
|
+
}
|
|
3810
|
+
const MAXIMUM_INCREMENTAL_SPREADSHEET_OPERATIONS = 10000;
|
|
3811
|
+
function spreadsheetCellOperationKey(row, column) {
|
|
3812
|
+
return `${row}:${column}`;
|
|
3813
|
+
}
|
|
3814
|
+
function spreadsheetCellOperationCoordinates(operations) {
|
|
3815
|
+
if (operations.length > MAXIMUM_INCREMENTAL_SPREADSHEET_OPERATIONS) return null;
|
|
3816
|
+
const coordinatesBySheet = new Map();
|
|
3817
|
+
for (const operation of operations){
|
|
3818
|
+
if ('insertRowCol' === operation.op || 'deleteRowCol' === operation.op || 'addSheet' === operation.op || 'deleteSheet' === operation.op || 0 === operation.path.length || !operation.id) return null;
|
|
3819
|
+
const root = operation.path[0];
|
|
3820
|
+
if ('id' === root || 'celldata' === root) return null;
|
|
3821
|
+
if ('data' !== root) continue;
|
|
3822
|
+
const row = operation.path[1];
|
|
3823
|
+
const column = operation.path[2];
|
|
3824
|
+
if (operation.path.length < 3 || !isSpreadsheetCoordinate(row) || !isSpreadsheetCoordinate(column)) return null;
|
|
3825
|
+
const coordinates = coordinatesBySheet.get(operation.id) ?? new Map();
|
|
3826
|
+
coordinatesBySheet.set(operation.id, coordinates);
|
|
3827
|
+
coordinates.set(spreadsheetCellOperationKey(row, column), {
|
|
3828
|
+
column,
|
|
3829
|
+
row
|
|
3830
|
+
});
|
|
3831
|
+
}
|
|
3832
|
+
return coordinatesBySheet;
|
|
3833
|
+
}
|
|
3834
|
+
function projectSpreadsheetSheetsFromFortuneOperations(sheets, sourceSheets, operations) {
|
|
3835
|
+
if (0 === operations.length || sheets.length !== sourceSheets.length) return null;
|
|
3836
|
+
const changedSheets = spreadsheetSheetsById(sheets);
|
|
3837
|
+
const controlledSheets = spreadsheetSheetsById(sourceSheets);
|
|
3838
|
+
if (!changedSheets || !controlledSheets) return null;
|
|
3839
|
+
if (changedSheets.size !== sheets.length || controlledSheets.size !== sourceSheets.length) return null;
|
|
3840
|
+
const coordinatesBySheet = spreadsheetCellOperationCoordinates(operations);
|
|
3841
|
+
if (!coordinatesBySheet) return null;
|
|
3842
|
+
for (const operation of operations)if (!operation.id || !changedSheets.has(operation.id) || !controlledSheets.has(operation.id)) return null;
|
|
3843
|
+
let affectedCellCount = 0;
|
|
3844
|
+
const projectedSheets = [];
|
|
3845
|
+
const pendingRichTextPastes = [];
|
|
3846
|
+
for (const sheet of sheets){
|
|
3847
|
+
const id = sheet.id;
|
|
3848
|
+
if (!id) return null;
|
|
3849
|
+
const source = controlledSheets.get(id);
|
|
3850
|
+
if (!source) return null;
|
|
3851
|
+
const coordinates = coordinatesBySheet.get(id);
|
|
3852
|
+
const { celldata: _cellData, data: _data, ...metadata } = sheet;
|
|
3853
|
+
if (!coordinates?.size) {
|
|
3854
|
+
projectedSheets.push(void 0 !== source.data ? {
|
|
3855
|
+
...metadata,
|
|
3856
|
+
data: source.data
|
|
3857
|
+
} : {
|
|
3858
|
+
...metadata,
|
|
3859
|
+
celldata: source.celldata ?? []
|
|
3860
|
+
});
|
|
3861
|
+
continue;
|
|
3862
|
+
}
|
|
3863
|
+
if (void 0 === source.data || void 0 === sheet.data || !spreadsheetMatrixProfile(source.data)?.fortuneReady) return null;
|
|
3864
|
+
const projection = projectSpreadsheetMatrixCells(sheet, source, coordinates.values(), pendingRichTextPastes);
|
|
3865
|
+
if (!projection) return null;
|
|
3866
|
+
affectedCellCount += projection.affectedCellCount;
|
|
3867
|
+
projectedSheets.push({
|
|
3868
|
+
...metadata,
|
|
3869
|
+
data: projection.data
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3872
|
+
for (const pending of pendingRichTextPastes)consumeSpreadsheetRichTextPaste(pending.sheet, pending.row, pending.column);
|
|
3873
|
+
return {
|
|
3874
|
+
affectedCellCount,
|
|
3875
|
+
sheets: projectedSheets
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
function projectSpreadsheetMatrixCells(changed, source, coordinates, pendingRichTextPastes) {
|
|
3879
|
+
if (!changed.data || !source.data) return null;
|
|
3880
|
+
const data = source.data.slice();
|
|
3881
|
+
data.length = Math.max(data.length, changed.data.length, positiveSpreadsheetDimension(changed.row));
|
|
3882
|
+
const mutableRows = new Map();
|
|
3883
|
+
const profileChanges = [];
|
|
3884
|
+
for (const { column, row } of coordinates){
|
|
3885
|
+
const changedRow = changed.data[row];
|
|
3886
|
+
const sourceRow = source.data[row];
|
|
3887
|
+
const currentCell = changedRow?.[column];
|
|
3888
|
+
const formattedPaste = currentCell ? peekSpreadsheetRichTextPaste(source, row, column, currentCell) : void 0;
|
|
3889
|
+
const nextCell = null == currentCell ? currentCell : cloneControlledSpreadsheetCell(currentCell, sourceRow?.[column], formattedPaste);
|
|
3890
|
+
if (formattedPaste) pendingRichTextPastes.push({
|
|
3891
|
+
column,
|
|
3892
|
+
row,
|
|
3893
|
+
sheet: source
|
|
3894
|
+
});
|
|
3895
|
+
let nextRow = mutableRows.get(row);
|
|
3896
|
+
if (!nextRow) {
|
|
3897
|
+
nextRow = sourceRow?.slice() ?? [];
|
|
3898
|
+
nextRow.length = Math.max(nextRow.length, changedRow?.length ?? 0, positiveSpreadsheetDimension(changed.column));
|
|
3899
|
+
mutableRows.set(row, nextRow);
|
|
3900
|
+
data[row] = nextRow;
|
|
3901
|
+
}
|
|
3902
|
+
if (null == nextCell) delete nextRow[column];
|
|
3903
|
+
else nextRow[column] = nextCell;
|
|
3904
|
+
profileChanges.push({
|
|
3905
|
+
column,
|
|
3906
|
+
current: nextCell,
|
|
3907
|
+
previous: sourceRow?.[column],
|
|
3908
|
+
row
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
if (!registerDerivedSpreadsheetMatrix(data, source.data, profileChanges)) return null;
|
|
3912
|
+
return {
|
|
3913
|
+
affectedCellCount: profileChanges.length,
|
|
3914
|
+
data
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
function cloneControlledSpreadsheetCell(cell, previous, formattedPaste) {
|
|
3918
|
+
return freezeImportedSpreadsheetCell(structuredClone(reconcileSpreadsheetRichTextCellEdit(previous, cell, formattedPaste)));
|
|
3919
|
+
}
|
|
3920
|
+
function spreadsheetSheetsById(sheets) {
|
|
3921
|
+
const byId = new Map();
|
|
3922
|
+
for (const sheet of sheets){
|
|
3923
|
+
if (!sheet.id || byId.has(sheet.id)) return null;
|
|
3924
|
+
byId.set(sheet.id, sheet);
|
|
3925
|
+
}
|
|
3926
|
+
return byId;
|
|
3927
|
+
}
|
|
3928
|
+
function isSpreadsheetCoordinate(value) {
|
|
3929
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
3930
|
+
}
|
|
3931
|
+
function positiveSpreadsheetDimension(value) {
|
|
3932
|
+
return 'number' == typeof value && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
3933
|
+
}
|
|
3934
|
+
function normalizeSpreadsheetCellRange(value) {
|
|
3935
|
+
const row = normalizeSpreadsheetCellRangeAxis(value.row);
|
|
3936
|
+
const column = normalizeSpreadsheetCellRangeAxis(value.column);
|
|
3937
|
+
return row && column ? {
|
|
3938
|
+
row,
|
|
3939
|
+
column
|
|
3940
|
+
} : null;
|
|
3941
|
+
}
|
|
3942
|
+
function parseSpreadsheetCellRange(value) {
|
|
3943
|
+
if (!spreadsheet_cell_range_isRecord(value)) return null;
|
|
3944
|
+
const row = normalizeSpreadsheetCellRangeAxis(value.row);
|
|
3945
|
+
const column = normalizeSpreadsheetCellRangeAxis(value.column);
|
|
3946
|
+
return row && column ? {
|
|
3947
|
+
row,
|
|
3948
|
+
column
|
|
3949
|
+
} : null;
|
|
3950
|
+
}
|
|
3951
|
+
function subtractSpreadsheetCellRange(source, removed) {
|
|
3952
|
+
const top = Math.max(source.row[0], removed.row[0]);
|
|
3953
|
+
const bottom = Math.min(source.row[1], removed.row[1]);
|
|
3954
|
+
const left = Math.max(source.column[0], removed.column[0]);
|
|
3955
|
+
const right = Math.min(source.column[1], removed.column[1]);
|
|
3956
|
+
if (top > bottom || left > right) return [
|
|
3957
|
+
source
|
|
3958
|
+
];
|
|
3959
|
+
const remaining = [];
|
|
3960
|
+
if (source.row[0] < top) remaining.push({
|
|
3961
|
+
row: [
|
|
3962
|
+
source.row[0],
|
|
3963
|
+
top - 1
|
|
3964
|
+
],
|
|
3965
|
+
column: [
|
|
3966
|
+
...source.column
|
|
3967
|
+
]
|
|
3968
|
+
});
|
|
3969
|
+
if (bottom < source.row[1]) remaining.push({
|
|
3970
|
+
row: [
|
|
3971
|
+
bottom + 1,
|
|
3972
|
+
source.row[1]
|
|
3973
|
+
],
|
|
3974
|
+
column: [
|
|
3975
|
+
...source.column
|
|
3976
|
+
]
|
|
3977
|
+
});
|
|
3978
|
+
if (source.column[0] < left) remaining.push({
|
|
3979
|
+
row: [
|
|
3980
|
+
top,
|
|
3981
|
+
bottom
|
|
3982
|
+
],
|
|
3983
|
+
column: [
|
|
3984
|
+
source.column[0],
|
|
3985
|
+
left - 1
|
|
3986
|
+
]
|
|
3987
|
+
});
|
|
3988
|
+
if (right < source.column[1]) remaining.push({
|
|
3989
|
+
row: [
|
|
3990
|
+
top,
|
|
3991
|
+
bottom
|
|
3992
|
+
],
|
|
3993
|
+
column: [
|
|
3994
|
+
right + 1,
|
|
3995
|
+
source.column[1]
|
|
3996
|
+
]
|
|
3997
|
+
});
|
|
3998
|
+
return remaining;
|
|
3999
|
+
}
|
|
4000
|
+
function spreadsheetCellRangesIntersect(left, right) {
|
|
4001
|
+
return !(left.row[1] < right.row[0] || right.row[1] < left.row[0] || left.column[1] < right.column[0] || right.column[1] < left.column[0]);
|
|
4002
|
+
}
|
|
4003
|
+
function spreadsheetCellRangesEqual(left, right) {
|
|
4004
|
+
const normalizedLeft = normalizeSpreadsheetCellRange(left);
|
|
4005
|
+
const normalizedRight = normalizeSpreadsheetCellRange(right);
|
|
4006
|
+
return Boolean(normalizedLeft && normalizedRight && normalizedLeft.row[0] === normalizedRight.row[0] && normalizedLeft.row[1] === normalizedRight.row[1] && normalizedLeft.column[0] === normalizedRight.column[0] && normalizedLeft.column[1] === normalizedRight.column[1]);
|
|
4007
|
+
}
|
|
4008
|
+
function spreadsheetCellRangeContains(range, row, column) {
|
|
4009
|
+
return row >= range.row[0] && row <= range.row[1] && column >= range.column[0] && column <= range.column[1];
|
|
4010
|
+
}
|
|
4011
|
+
function spreadsheetCellRangeArea(range) {
|
|
4012
|
+
return (range.row[1] - range.row[0] + 1) * (range.column[1] - range.column[0] + 1);
|
|
4013
|
+
}
|
|
4014
|
+
function normalizeSpreadsheetCellRangeAxis(value) {
|
|
4015
|
+
if (!Array.isArray(value)) return null;
|
|
4016
|
+
const first = finiteSpreadsheetCellIndex(value[0]);
|
|
4017
|
+
const second = finiteSpreadsheetCellIndex(value[1]);
|
|
4018
|
+
if (null === first || null === second) return null;
|
|
4019
|
+
return [
|
|
4020
|
+
Math.min(first, second),
|
|
4021
|
+
Math.max(first, second)
|
|
4022
|
+
];
|
|
4023
|
+
}
|
|
4024
|
+
function finiteSpreadsheetCellIndex(value) {
|
|
4025
|
+
return 'number' == typeof value && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
|
|
4026
|
+
}
|
|
4027
|
+
function spreadsheet_cell_range_isRecord(value) {
|
|
4028
|
+
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
4029
|
+
}
|
|
4030
|
+
function spreadsheetCurrentRegion(sheet, seed) {
|
|
4031
|
+
const range = normalizeSpreadsheetCellRange(seed);
|
|
4032
|
+
if (!range) return null;
|
|
4033
|
+
const cells = spreadsheetSheetCellReader(sheet);
|
|
4034
|
+
const bounds = spreadsheetCurrentRegionUsedBounds(sheet);
|
|
4035
|
+
let startRow = range.row[0];
|
|
4036
|
+
let endRow = range.row[1];
|
|
4037
|
+
let startColumn = range.column[0];
|
|
4038
|
+
let endColumn = range.column[1];
|
|
4039
|
+
if (startRow > bounds.lastRow || startColumn > bounds.lastColumn || !spreadsheetCellHasContent(cells(startRow, startColumn))) return null;
|
|
4040
|
+
let changed = true;
|
|
4041
|
+
while(changed){
|
|
4042
|
+
changed = false;
|
|
4043
|
+
while(startColumn > 0 && spreadsheetCurrentRegionColumnHasContent(cells, startColumn - 1, startRow, endRow)){
|
|
4044
|
+
startColumn -= 1;
|
|
4045
|
+
changed = true;
|
|
4046
|
+
}
|
|
4047
|
+
while(endColumn < bounds.lastColumn && spreadsheetCurrentRegionColumnHasContent(cells, endColumn + 1, startRow, endRow)){
|
|
4048
|
+
endColumn += 1;
|
|
4049
|
+
changed = true;
|
|
4050
|
+
}
|
|
4051
|
+
while(startRow > 0 && spreadsheetRowHasContent(cells, startRow - 1, startColumn, endColumn)){
|
|
4052
|
+
startRow -= 1;
|
|
4053
|
+
changed = true;
|
|
4054
|
+
}
|
|
4055
|
+
while(endRow < bounds.lastRow && spreadsheetRowHasContent(cells, endRow + 1, startColumn, endColumn)){
|
|
4056
|
+
endRow += 1;
|
|
4057
|
+
changed = true;
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
return {
|
|
4061
|
+
row: [
|
|
4062
|
+
startRow,
|
|
4063
|
+
endRow
|
|
4064
|
+
],
|
|
4065
|
+
column: [
|
|
4066
|
+
startColumn,
|
|
4067
|
+
endColumn
|
|
4068
|
+
]
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
function spreadsheetRowHasContent(cellAt, row, startColumn, endColumn) {
|
|
4072
|
+
for(let column = startColumn; column <= endColumn; column += 1)if (spreadsheetCellHasContent(cellAt(row, column))) return true;
|
|
4073
|
+
return false;
|
|
4074
|
+
}
|
|
4075
|
+
function spreadsheetCurrentRegionColumnHasContent(cellAt, column, startRow, endRow) {
|
|
4076
|
+
for(let row = startRow; row <= endRow; row += 1)if (spreadsheetCellHasContent(cellAt(row, column))) return true;
|
|
4077
|
+
return false;
|
|
4078
|
+
}
|
|
4079
|
+
function spreadsheetCellHasContent(cell) {
|
|
4080
|
+
if (!cell) return false;
|
|
4081
|
+
return [
|
|
4082
|
+
cell.v,
|
|
4083
|
+
cell.m,
|
|
4084
|
+
cell.f
|
|
4085
|
+
].some((value)=>null != value && '' !== value);
|
|
4086
|
+
}
|
|
4087
|
+
function spreadsheetSheetCellReader(sheet) {
|
|
4088
|
+
if (sheet.data) return (row, column)=>sheet.data?.[row]?.[column] ?? null;
|
|
4089
|
+
const cells = new Map((sheet.celldata ?? []).map((cell)=>[
|
|
4090
|
+
`${cell.r}:${cell.c}`,
|
|
4091
|
+
cell.v
|
|
4092
|
+
]));
|
|
4093
|
+
return (row, column)=>cells.get(`${row}:${column}`) ?? null;
|
|
4094
|
+
}
|
|
4095
|
+
function spreadsheetCurrentRegionUsedBounds(sheet) {
|
|
4096
|
+
let lastRow = Math.max((sheet.data?.length ?? 1) - 1, 0);
|
|
4097
|
+
let lastColumn = Math.max(sparseMatrixColumnCount(sheet.data) - 1, 0);
|
|
4098
|
+
for (const cell of sheet.celldata ?? []){
|
|
4099
|
+
lastRow = Math.max(lastRow, cell.r);
|
|
4100
|
+
lastColumn = Math.max(lastColumn, cell.c);
|
|
4101
|
+
}
|
|
4102
|
+
return {
|
|
4103
|
+
lastColumn,
|
|
4104
|
+
lastRow
|
|
4105
|
+
};
|
|
4106
|
+
}
|
|
4107
|
+
function parseSpreadsheetConditionalColor(value) {
|
|
4108
|
+
const hexadecimal = /^#?([0-9a-f]{6})$/i.exec(value.trim());
|
|
4109
|
+
if (hexadecimal) return {
|
|
4110
|
+
red: Number.parseInt(hexadecimal[1].slice(0, 2), 16),
|
|
4111
|
+
green: Number.parseInt(hexadecimal[1].slice(2, 4), 16),
|
|
4112
|
+
blue: Number.parseInt(hexadecimal[1].slice(4, 6), 16)
|
|
4113
|
+
};
|
|
4114
|
+
const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(value);
|
|
4115
|
+
if (!rgb) return null;
|
|
4116
|
+
return {
|
|
4117
|
+
red: boundedColorChannel(rgb[1]),
|
|
4118
|
+
green: boundedColorChannel(rgb[2]),
|
|
4119
|
+
blue: boundedColorChannel(rgb[3])
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
function interpolateSpreadsheetConditionalColor(start, end, amount) {
|
|
4123
|
+
return {
|
|
4124
|
+
red: Math.round(start.red + (end.red - start.red) * amount),
|
|
4125
|
+
green: Math.round(start.green + (end.green - start.green) * amount),
|
|
4126
|
+
blue: Math.round(start.blue + (end.blue - start.blue) * amount)
|
|
4127
|
+
};
|
|
4128
|
+
}
|
|
4129
|
+
function spreadsheetConditionalCssColor(color) {
|
|
4130
|
+
return `rgb(${color.red}, ${color.green}, ${color.blue})`;
|
|
4131
|
+
}
|
|
4132
|
+
function spreadsheetConditionalRatio(value, minimum, maximum) {
|
|
4133
|
+
if (maximum === minimum) return 0;
|
|
4134
|
+
return Math.max(0, Math.min(1, (value - minimum) / (maximum - minimum)));
|
|
4135
|
+
}
|
|
4136
|
+
function boundedColorChannel(value) {
|
|
4137
|
+
return Math.max(0, Math.min(255, Number(value)));
|
|
4138
|
+
}
|
|
4139
|
+
function spreadsheetConditionalDataBar(color, value, minimum, maximum, minLength, maxLength, showValue) {
|
|
4140
|
+
if (minimum < 0 && maximum > 0) {
|
|
4141
|
+
const axis = -minimum / (maximum - minimum) * 100;
|
|
4142
|
+
const ratio = value < 0 ? Math.abs(value / minimum) : value > 0 ? value / maximum : 0;
|
|
4143
|
+
const width = 0 === value ? 0 : displayLength(ratio, minLength, maxLength) * (value < 0 ? axis / 100 : (100 - axis) / 100);
|
|
4144
|
+
return {
|
|
4145
|
+
color,
|
|
4146
|
+
startPercent: value < 0 ? axis - width : axis,
|
|
4147
|
+
widthPercent: width,
|
|
4148
|
+
axisPercent: axis,
|
|
4149
|
+
showValue
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4152
|
+
if (maximum <= 0) {
|
|
4153
|
+
const ratio = minimum === maximum ? 0 === value ? 0 : 1 : Math.max(0, Math.min(1, (maximum - value) / (maximum - minimum)));
|
|
4154
|
+
const width = 0 === value ? 0 : displayLength(ratio, minLength, maxLength);
|
|
4155
|
+
return {
|
|
4156
|
+
color,
|
|
4157
|
+
startPercent: 100 - width,
|
|
4158
|
+
widthPercent: width,
|
|
4159
|
+
axisPercent: 100,
|
|
4160
|
+
showValue
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
const ratio = minimum === maximum ? 0 === value ? 0 : 1 : Math.max(0, Math.min(1, (value - minimum) / (maximum - minimum)));
|
|
4164
|
+
const width = 0 === value ? 0 : displayLength(ratio, minLength, maxLength);
|
|
4165
|
+
return {
|
|
4166
|
+
color,
|
|
4167
|
+
startPercent: 0,
|
|
4168
|
+
widthPercent: width,
|
|
4169
|
+
axisPercent: 0,
|
|
4170
|
+
showValue
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
function displayLength(valueRatio, minLength, maxLength) {
|
|
4174
|
+
const normalized = Math.max(0, Math.min(1, valueRatio));
|
|
4175
|
+
return minLength + (maxLength - minLength) * normalized;
|
|
4176
|
+
}
|
|
4177
|
+
const MAX_SPREADSHEET_LOCAL_FORMULA_LENGTH = 255;
|
|
4178
|
+
const MAX_SPREADSHEET_LOCAL_FORMULA_REFERENCED_CELLS = 1024;
|
|
4179
|
+
const MAX_SHEET_ROW = 1048576;
|
|
4180
|
+
const MAX_SHEET_COLUMN = 16384;
|
|
4181
|
+
const LOCAL_FORMULA_FUNCTIONS = new Set(SUPPORTED_FORMULAS.map((name)=>name.toUpperCase()));
|
|
4182
|
+
function evaluateSpreadsheetLocalFormula(formula, options) {
|
|
4183
|
+
const source = formula.trim().replace(/^=/, '').trim();
|
|
4184
|
+
if (!source) return unsupportedLocalFormula('公式为空。');
|
|
4185
|
+
if (Array.from(source).length > MAX_SPREADSHEET_LOCAL_FORMULA_LENGTH) return unsupportedLocalFormula('公式超过 255 个字符。');
|
|
4186
|
+
const currentSheet = options.sheets.find((candidate)=>candidate.id === options.sheetId);
|
|
4187
|
+
if (!currentSheet) return unsupportedLocalFormula('找不到公式所在的工作表。');
|
|
4188
|
+
const evaluator = new SpreadsheetLocalFormulaEvaluator(options, currentSheet);
|
|
4189
|
+
return evaluator.evaluate(source);
|
|
4190
|
+
}
|
|
4191
|
+
function spreadsheetLocalFormulaBoolean(value) {
|
|
4192
|
+
if ('boolean' == typeof value) return value;
|
|
4193
|
+
if ('number' == typeof value) return Number.isFinite(value) && 0 !== value;
|
|
4194
|
+
return 'string' == typeof value && 'TRUE' === value.trim().toUpperCase();
|
|
4195
|
+
}
|
|
4196
|
+
class SpreadsheetLocalFormulaEvaluator {
|
|
4197
|
+
options;
|
|
4198
|
+
currentSheet;
|
|
4199
|
+
sheetsByName = new Map();
|
|
4200
|
+
anchor;
|
|
4201
|
+
referencedCells = 0;
|
|
4202
|
+
unsupportedMessage;
|
|
4203
|
+
celldataBySheet = new Map();
|
|
4204
|
+
constructor(options, currentSheet){
|
|
4205
|
+
this.options = options;
|
|
4206
|
+
this.currentSheet = currentSheet;
|
|
4207
|
+
this.anchor = {
|
|
4208
|
+
row: options.anchorRow ?? options.row,
|
|
4209
|
+
column: options.anchorColumn ?? options.column
|
|
4210
|
+
};
|
|
4211
|
+
for (const sheet of options.sheets)this.sheetsByName.set(sheet.name.trim().toLocaleLowerCase(), sheet);
|
|
4212
|
+
}
|
|
4213
|
+
evaluate(formula) {
|
|
4214
|
+
const parser = new Parser();
|
|
4215
|
+
parser.setFunction('ROW', (parameters)=>parameters.length ? null : this.options.row + 1).setFunction('COLUMN', (parameters)=>parameters.length ? null : this.options.column + 1);
|
|
4216
|
+
let unsupportedFunction;
|
|
4217
|
+
parser.on('callFunction', (name)=>{
|
|
4218
|
+
const normalized = name.toUpperCase();
|
|
4219
|
+
if (!LOCAL_FORMULA_FUNCTIONS.has(normalized)) unsupportedFunction ??= normalized;
|
|
4220
|
+
});
|
|
4221
|
+
parser.on('callCellValue', (coordinate, _parserOptions, done)=>{
|
|
4222
|
+
const resolved = this.resolveCoordinate(this.currentSheet, coordinate);
|
|
4223
|
+
if (!resolved) return void done(null);
|
|
4224
|
+
done(this.readCell(resolved));
|
|
4225
|
+
});
|
|
4226
|
+
parser.on('callRangeValue', (start, end, _parserOptions, done)=>{
|
|
4227
|
+
done(this.readRange(start, end));
|
|
4228
|
+
});
|
|
4229
|
+
const parsed = parser.parse(normalizeFormulaForFortuneParser(formula), {
|
|
4230
|
+
sheetId: this.currentSheet.id
|
|
4231
|
+
});
|
|
4232
|
+
if (unsupportedFunction) return unsupportedLocalFormula(`公式函数“${unsupportedFunction}”无法在浏览器本地求值。`);
|
|
4233
|
+
if (this.unsupportedMessage) return unsupportedLocalFormula(this.unsupportedMessage);
|
|
4234
|
+
if (parsed.error || void 0 === parsed.result) return unsupportedLocalFormula('公式无法求值,请检查函数、引用和括号。');
|
|
4235
|
+
return {
|
|
4236
|
+
supported: true,
|
|
4237
|
+
value: parsed.result
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
resolveCoordinate(fallbackSheet, coordinate) {
|
|
4241
|
+
const sheet = coordinate.sheetName ? this.sheetsByName.get(coordinate.sheetName.trim().toLocaleLowerCase()) : fallbackSheet;
|
|
4242
|
+
if (!sheet) {
|
|
4243
|
+
this.unsupportedMessage = '公式引用了不存在的工作表。';
|
|
4244
|
+
return null;
|
|
4245
|
+
}
|
|
4246
|
+
if (coordinate.row.index < 0 || coordinate.column.index < 0) {
|
|
4247
|
+
this.unsupportedMessage = '公式暂不支持整行或整列引用,请改用有限区域。';
|
|
4248
|
+
return null;
|
|
4249
|
+
}
|
|
4250
|
+
const row = coordinate.row.isAbsolute ? coordinate.row.index : this.options.row + (coordinate.row.index - this.anchor.row);
|
|
4251
|
+
const column = coordinate.column.isAbsolute ? coordinate.column.index : this.options.column + (coordinate.column.index - this.anchor.column);
|
|
4252
|
+
if (row < 0 || column < 0 || row >= MAX_SHEET_ROW || column >= MAX_SHEET_COLUMN) {
|
|
4253
|
+
this.unsupportedMessage = '公式引用超出了工作表边界。';
|
|
4254
|
+
return null;
|
|
4255
|
+
}
|
|
4256
|
+
return {
|
|
4257
|
+
sheet,
|
|
4258
|
+
row,
|
|
4259
|
+
column
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
readRange(start, end) {
|
|
4263
|
+
const startCoordinate = this.resolveCoordinate(this.currentSheet, start);
|
|
4264
|
+
const rangeSheet = start.sheetName ? this.sheetsByName.get(start.sheetName.trim().toLocaleLowerCase()) : this.currentSheet;
|
|
4265
|
+
const endCoordinate = rangeSheet ? this.resolveCoordinate(rangeSheet, end) : null;
|
|
4266
|
+
if (!startCoordinate || !endCoordinate || startCoordinate.sheet.id !== endCoordinate.sheet.id) {
|
|
4267
|
+
this.unsupportedMessage ??= '公式区域引用必须位于同一工作表。';
|
|
4268
|
+
return [];
|
|
4269
|
+
}
|
|
4270
|
+
const rowStart = Math.min(startCoordinate.row, endCoordinate.row);
|
|
4271
|
+
const rowEnd = Math.max(startCoordinate.row, endCoordinate.row);
|
|
4272
|
+
const columnStart = Math.min(startCoordinate.column, endCoordinate.column);
|
|
4273
|
+
const columnEnd = Math.max(startCoordinate.column, endCoordinate.column);
|
|
4274
|
+
const area = (rowEnd - rowStart + 1) * (columnEnd - columnStart + 1);
|
|
4275
|
+
if (area <= 0 || area > MAX_SPREADSHEET_LOCAL_FORMULA_REFERENCED_CELLS - this.referencedCells) {
|
|
4276
|
+
this.unsupportedMessage = '公式一次最多读取 1,024 个单元格。';
|
|
4277
|
+
return [];
|
|
4278
|
+
}
|
|
4279
|
+
const values = [];
|
|
4280
|
+
for(let row = rowStart; row <= rowEnd; row += 1){
|
|
4281
|
+
const valuesRow = [];
|
|
4282
|
+
for(let column = columnStart; column <= columnEnd; column += 1)valuesRow.push(this.readCell({
|
|
4283
|
+
sheet: startCoordinate.sheet,
|
|
4284
|
+
row,
|
|
4285
|
+
column
|
|
4286
|
+
}));
|
|
4287
|
+
values.push(valuesRow);
|
|
4288
|
+
}
|
|
4289
|
+
return values;
|
|
4290
|
+
}
|
|
4291
|
+
readCell(coordinate) {
|
|
4292
|
+
this.referencedCells += 1;
|
|
4293
|
+
if (this.referencedCells > MAX_SPREADSHEET_LOCAL_FORMULA_REFERENCED_CELLS) {
|
|
4294
|
+
this.unsupportedMessage = '公式一次最多读取 1,024 个单元格。';
|
|
4295
|
+
return null;
|
|
4296
|
+
}
|
|
4297
|
+
if (this.options.useProposedValue && coordinate.sheet.id === this.currentSheet.id && coordinate.row === this.options.row && coordinate.column === this.options.column) return this.options.proposedValue;
|
|
4298
|
+
const cell = this.cellAt(coordinate.sheet, coordinate.row, coordinate.column);
|
|
4299
|
+
if (!cell) return null;
|
|
4300
|
+
if ('string' == typeof cell.f && cell.f.length > 0 && void 0 === cell.v) {
|
|
4301
|
+
this.unsupportedMessage ??= '公式引用了尚未计算的公式单元格。';
|
|
4302
|
+
return null;
|
|
4303
|
+
}
|
|
4304
|
+
return cell.v ?? null;
|
|
4305
|
+
}
|
|
4306
|
+
cellAt(sheet, row, column) {
|
|
4307
|
+
if (void 0 !== sheet.data) return sheet.data[row]?.[column] ?? null;
|
|
4308
|
+
let cells = this.celldataBySheet.get(sheet.id);
|
|
4309
|
+
if (!cells) {
|
|
4310
|
+
cells = new Map();
|
|
4311
|
+
for (const entry of sheet.celldata ?? []){
|
|
4312
|
+
const value = entry.v && 'object' == typeof entry.v && 'v' in entry.v ? entry.v : {
|
|
4313
|
+
v: entry.v
|
|
4314
|
+
};
|
|
4315
|
+
cells.set(`${entry.r}_${entry.c}`, value);
|
|
4316
|
+
}
|
|
4317
|
+
this.celldataBySheet.set(sheet.id, cells);
|
|
4318
|
+
}
|
|
4319
|
+
return cells.get(`${row}_${column}`) ?? null;
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
function unsupportedLocalFormula(message) {
|
|
4323
|
+
return {
|
|
4324
|
+
supported: false,
|
|
4325
|
+
value: false,
|
|
4326
|
+
message
|
|
4327
|
+
};
|
|
4328
|
+
}
|
|
4329
|
+
const MAX_SPREADSHEET_CONDITIONAL_FORMULA_SCAN_CELLS = 10000;
|
|
4330
|
+
function spreadsheetConditionalFormatStyles(sheet, formulaSheets) {
|
|
4331
|
+
const styles = new Map();
|
|
4332
|
+
const data = sheet.data ?? [];
|
|
4333
|
+
const rules = conditionalRules(sheet);
|
|
4334
|
+
if (!data.length && !rules.some(isFormulaConditionalRule)) return styles;
|
|
4335
|
+
const blocked = new Set();
|
|
4336
|
+
for (const rule of rules){
|
|
4337
|
+
const matched = new Set();
|
|
4338
|
+
if ('colorGradation' === rule.type) applyColorScale(styles, data, rule, blocked, matched);
|
|
4339
|
+
else if ('dataBar' === rule.type) applyDataBars(styles, data, rule, blocked, matched);
|
|
4340
|
+
else if ('icons' === rule.type) applyIconSet(styles, data, rule, blocked, matched);
|
|
4341
|
+
else applyDefaultRule(styles, data, rule, blocked, matched, formulaSheets, sheet);
|
|
4342
|
+
if (rule.stopIfTrue) for (const key of matched)blocked.add(key);
|
|
4343
|
+
}
|
|
4344
|
+
return styles;
|
|
4345
|
+
}
|
|
4346
|
+
function isFormulaConditionalRule(rule) {
|
|
4347
|
+
return 'default' === rule.type && 'formula' === rule.conditionName;
|
|
4348
|
+
}
|
|
4349
|
+
function applyDefaultRule(styles, data, rule, blocked, matched, formulaSheets, currentSheet) {
|
|
4350
|
+
if ('default' !== rule.type || !isConditionalFormatStyle(rule.format) || !rule.conditionName) return;
|
|
4351
|
+
const values = (rule.conditionValue ?? []).map(String);
|
|
4352
|
+
const style = rule.format;
|
|
4353
|
+
if ('formula' === rule.conditionName && values[0]) {
|
|
4354
|
+
for (const range of rule.cellrange)forEachFormulaCell(data, [
|
|
4355
|
+
range
|
|
4356
|
+
], ({ row, column })=>{
|
|
4357
|
+
if (evaluateFormula(values[0], row, column, range, currentSheet, formulaSheets)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
4358
|
+
});
|
|
4359
|
+
return;
|
|
4360
|
+
}
|
|
4361
|
+
if ('duplicateValue' === rule.conditionName) {
|
|
4362
|
+
for (const range of rule.cellrange){
|
|
4363
|
+
const positions = cellPositions(data, [
|
|
4364
|
+
range
|
|
4365
|
+
]).filter(({ row, column })=>null != work_spreadsheet_conditional_format_cellValue(data, row, column));
|
|
4366
|
+
const occurrences = new Map();
|
|
4367
|
+
for (const { row, column } of positions){
|
|
4368
|
+
const key = comparableKey(work_spreadsheet_conditional_format_cellValue(data, row, column));
|
|
4369
|
+
occurrences.set(key, (occurrences.get(key) ?? 0) + 1);
|
|
4370
|
+
}
|
|
4371
|
+
const unique = '1' === values[0];
|
|
4372
|
+
for (const { row, column } of positions){
|
|
4373
|
+
const count = occurrences.get(comparableKey(work_spreadsheet_conditional_format_cellValue(data, row, column))) ?? 0;
|
|
4374
|
+
if (unique && 1 === count || !unique && count > 1) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
return;
|
|
4378
|
+
}
|
|
4379
|
+
if ([
|
|
4380
|
+
'top10',
|
|
4381
|
+
'top10_percent',
|
|
4382
|
+
'last10',
|
|
4383
|
+
'last10_percent'
|
|
4384
|
+
].includes(rule.conditionName)) {
|
|
4385
|
+
for (const range of rule.cellrange)applyRankRule(styles, data, range, rule.conditionName, values[0], style, blocked, matched);
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
if ('aboveAverage' === rule.conditionName || 'belowAverage' === rule.conditionName) {
|
|
4389
|
+
for (const range of rule.cellrange)applyAverageRule(styles, data, range, rule.conditionName, style, blocked, matched);
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
forEachCell(data, rule.cellrange, ({ row, column })=>{
|
|
4393
|
+
const current = work_spreadsheet_conditional_format_cellValue(data, row, column);
|
|
4394
|
+
if (matchesCondition(current, rule.conditionName, values)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
function applyRankRule(styles, data, range, conditionName, sourceRank, style, blocked, matched) {
|
|
4398
|
+
const positions = cellPositions(data, [
|
|
4399
|
+
range
|
|
4400
|
+
]);
|
|
4401
|
+
const numbers = positions.flatMap(({ row, column })=>{
|
|
4402
|
+
const value = work_spreadsheet_conditional_format_numericCellValue(data, row, column);
|
|
4403
|
+
return null === value ? [] : [
|
|
4404
|
+
value
|
|
4405
|
+
];
|
|
4406
|
+
});
|
|
4407
|
+
if (!numbers.length) return;
|
|
4408
|
+
numbers.sort((left, right)=>right - left);
|
|
4409
|
+
const requested = Math.max(1, Math.trunc(Number(sourceRank) || 10));
|
|
4410
|
+
const count = conditionName.endsWith('_percent') ? Math.max(1, Math.ceil(Math.min(100, requested) / 100 * numbers.length)) : Math.min(numbers.length, requested);
|
|
4411
|
+
const bottom = conditionName.startsWith('last');
|
|
4412
|
+
const threshold = bottom ? numbers[numbers.length - count] : numbers[count - 1];
|
|
4413
|
+
for (const { row, column } of positions){
|
|
4414
|
+
const value = work_spreadsheet_conditional_format_numericCellValue(data, row, column);
|
|
4415
|
+
if (null !== value && (bottom ? value <= threshold : value >= threshold)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
function applyAverageRule(styles, data, range, conditionName, style, blocked, matched) {
|
|
4419
|
+
const positions = cellPositions(data, [
|
|
4420
|
+
range
|
|
4421
|
+
]);
|
|
4422
|
+
const values = positions.flatMap(({ row, column })=>{
|
|
4423
|
+
const value = work_spreadsheet_conditional_format_numericCellValue(data, row, column);
|
|
4424
|
+
return null === value ? [] : [
|
|
4425
|
+
value
|
|
4426
|
+
];
|
|
4427
|
+
});
|
|
4428
|
+
if (!values.length) return;
|
|
4429
|
+
const average = values.reduce((sum, value)=>sum + value, 0) / values.length;
|
|
4430
|
+
for (const { row, column } of positions){
|
|
4431
|
+
const value = work_spreadsheet_conditional_format_numericCellValue(data, row, column);
|
|
4432
|
+
if (null !== value && ('aboveAverage' === conditionName ? value > average : value < average)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
function applyColorScale(styles, data, rule, blocked, matched) {
|
|
4436
|
+
if (!Array.isArray(rule.format) || rule.format.length < 2) return;
|
|
4437
|
+
const positions = cellPositions(data, rule.cellrange);
|
|
4438
|
+
const numericPositions = positions.flatMap((position)=>{
|
|
4439
|
+
const value = work_spreadsheet_conditional_format_numericCellValue(data, position.row, position.column);
|
|
4440
|
+
return null === value ? [] : [
|
|
4441
|
+
{
|
|
4442
|
+
...position,
|
|
4443
|
+
value
|
|
4444
|
+
}
|
|
4445
|
+
];
|
|
4446
|
+
});
|
|
4447
|
+
if (!numericPositions.length) return;
|
|
4448
|
+
const colors = rule.format.slice(0, 3).map(parseSpreadsheetConditionalColor);
|
|
4449
|
+
if (colors.some((color)=>!color)) return;
|
|
4450
|
+
const values = numericPositions.map(({ value })=>value);
|
|
4451
|
+
const options = rule.visualOptions ? normalizeSpreadsheetConditionalVisualOptions(rule.visualOptions, colors.length, 'colorScale') : {
|
|
4452
|
+
thresholds: defaultSpreadsheetColorScaleThresholds(colors.length)
|
|
4453
|
+
};
|
|
4454
|
+
if (!options) return;
|
|
4455
|
+
const points = options.thresholds.map((threshold)=>spreadsheetConditionalThresholdValue(threshold, values));
|
|
4456
|
+
if (points.some((point)=>null === point)) return;
|
|
4457
|
+
const minimum = points[0];
|
|
4458
|
+
const midpoint = 3 === points.length ? points[1] : null;
|
|
4459
|
+
const maximum = points.at(-1);
|
|
4460
|
+
const maximumColor = colors[0];
|
|
4461
|
+
const midpointColor = 3 === colors.length ? colors[1] : null;
|
|
4462
|
+
const minimumColor = colors.at(-1);
|
|
4463
|
+
for (const { row, column, value } of numericPositions){
|
|
4464
|
+
let color;
|
|
4465
|
+
color = maximum === minimum ? midpointColor ?? minimumColor : midpointColor && null !== midpoint && value <= midpoint ? interpolateSpreadsheetConditionalColor(minimumColor, midpointColor, spreadsheetConditionalRatio(value, minimum, midpoint)) : midpointColor && null !== midpoint ? interpolateSpreadsheetConditionalColor(midpointColor, maximumColor, spreadsheetConditionalRatio(value, midpoint, maximum)) : interpolateSpreadsheetConditionalColor(minimumColor, maximumColor, spreadsheetConditionalRatio(value, minimum, maximum));
|
|
4466
|
+
applyConditionalStyle(styles, blocked, matched, row, column, {
|
|
4467
|
+
cellColor: spreadsheetConditionalCssColor(color)
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
function applyDataBars(styles, data, rule, blocked, matched) {
|
|
4472
|
+
if (!isConditionalFormatStyle(rule.format)) return;
|
|
4473
|
+
const color = rule.format.cellColor;
|
|
4474
|
+
if (!color) return;
|
|
4475
|
+
const positions = cellPositions(data, rule.cellrange);
|
|
4476
|
+
const numericPositions = positions.flatMap((position)=>{
|
|
4477
|
+
const value = work_spreadsheet_conditional_format_numericCellValue(data, position.row, position.column);
|
|
4478
|
+
return null === value ? [] : [
|
|
4479
|
+
{
|
|
4480
|
+
...position,
|
|
4481
|
+
value
|
|
4482
|
+
}
|
|
4483
|
+
];
|
|
4484
|
+
});
|
|
4485
|
+
if (!numericPositions.length) return;
|
|
4486
|
+
const values = numericPositions.map(({ value })=>value);
|
|
4487
|
+
const options = rule.visualOptions ? normalizeSpreadsheetConditionalVisualOptions(rule.visualOptions, 2, 'dataBar') : defaultSpreadsheetDataBarOptions();
|
|
4488
|
+
if (!options) return;
|
|
4489
|
+
const points = options.thresholds.map((threshold)=>spreadsheetConditionalThresholdValue(threshold, values));
|
|
4490
|
+
if (points.some((point)=>null === point)) return;
|
|
4491
|
+
const minimum = points[0];
|
|
4492
|
+
const maximum = points[1];
|
|
4493
|
+
for (const { row, column, value } of numericPositions)applyConditionalStyle(styles, blocked, matched, row, column, {
|
|
4494
|
+
dataBar: spreadsheetConditionalDataBar(color, value, minimum, maximum, options.minLength ?? 10, options.maxLength ?? 90, false !== options.showValue)
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
function applyIconSet(styles, data, rule, blocked, matched) {
|
|
4498
|
+
if ('icons' !== rule.type) return;
|
|
4499
|
+
const format = normalizeSpreadsheetConditionalIconSetFormat(rule.format);
|
|
4500
|
+
if (!format) return;
|
|
4501
|
+
const numericPositions = cellPositions(data, rule.cellrange).flatMap((position)=>{
|
|
4502
|
+
const value = work_spreadsheet_conditional_format_numericCellValue(data, position.row, position.column);
|
|
4503
|
+
return null === value ? [] : [
|
|
4504
|
+
{
|
|
4505
|
+
...position,
|
|
4506
|
+
value
|
|
4507
|
+
}
|
|
4508
|
+
];
|
|
4509
|
+
});
|
|
4510
|
+
const values = numericPositions.map(({ value })=>value);
|
|
4511
|
+
for (const { row, column, value } of numericPositions){
|
|
4512
|
+
const icon = spreadsheetConditionalIconForValue(format, value, values);
|
|
4513
|
+
if (icon) applyConditionalStyle(styles, blocked, matched, row, column, {
|
|
4514
|
+
icon
|
|
4515
|
+
});
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
function matchesCondition(value, name, conditions) {
|
|
4519
|
+
if (null == value) return false;
|
|
4520
|
+
if ('textContains' === name) return String(value).toLowerCase().includes((conditions[0] ?? '').toLowerCase());
|
|
4521
|
+
if ('equal' === name) return 0 === compareValues(value, conditions[0]);
|
|
4522
|
+
if ('notEqual' === name) return 0 !== compareValues(value, conditions[0]);
|
|
4523
|
+
if ('greaterThan' === name) return compareValues(value, conditions[0]) > 0;
|
|
4524
|
+
if ('greaterThanOrEqual' === name) return compareValues(value, conditions[0]) >= 0;
|
|
4525
|
+
if ('lessThan' === name) return compareValues(value, conditions[0]) < 0;
|
|
4526
|
+
if ('lessThanOrEqual' === name) return compareValues(value, conditions[0]) <= 0;
|
|
4527
|
+
if ('between' === name || 'notBetween' === name) {
|
|
4528
|
+
const lower = Math.min(Number(conditions[0]), Number(conditions[1]));
|
|
4529
|
+
const upper = Math.max(Number(conditions[0]), Number(conditions[1]));
|
|
4530
|
+
if (!Number.isFinite(lower) || !Number.isFinite(upper)) return false;
|
|
4531
|
+
const between = Number(value) >= lower && Number(value) <= upper;
|
|
4532
|
+
return 'between' === name ? between : !between;
|
|
4533
|
+
}
|
|
4534
|
+
return false;
|
|
4535
|
+
}
|
|
4536
|
+
function evaluateFormula(source, row, column, baseRange, currentSheet, formulaSheets) {
|
|
4537
|
+
const sheets = [
|
|
4538
|
+
toFormulaSheet(currentSheet),
|
|
4539
|
+
...(formulaSheets ?? []).filter((sheet)=>sheet.id && sheet.id !== currentSheet.id).map(toFormulaSheet)
|
|
4540
|
+
];
|
|
4541
|
+
const result = evaluateSpreadsheetLocalFormula(source, {
|
|
4542
|
+
sheets,
|
|
4543
|
+
sheetId: currentSheet.id ?? '',
|
|
4544
|
+
row,
|
|
4545
|
+
column,
|
|
4546
|
+
anchorRow: baseRange.row[0],
|
|
4547
|
+
anchorColumn: baseRange.column[0]
|
|
4548
|
+
});
|
|
4549
|
+
return result.supported && spreadsheetLocalFormulaBoolean(result.value);
|
|
4550
|
+
}
|
|
4551
|
+
function applyDifferentialStyle(styles, blocked, matched, row, column, style) {
|
|
4552
|
+
const patch = {};
|
|
4553
|
+
if (style.textColor) patch.textColor = style.textColor;
|
|
4554
|
+
if (style.cellColor) patch.cellColor = style.cellColor;
|
|
4555
|
+
applyConditionalStyle(styles, blocked, matched, row, column, patch);
|
|
4556
|
+
}
|
|
4557
|
+
function applyConditionalStyle(styles, blocked, matched, row, column, patch) {
|
|
4558
|
+
const key = cellKey(row, column);
|
|
4559
|
+
if (blocked.has(key)) return;
|
|
4560
|
+
matched.add(key);
|
|
4561
|
+
if (Object.keys(patch).length) patchCellStyle(styles, row, column, patch);
|
|
4562
|
+
}
|
|
4563
|
+
function patchCellStyle(styles, row, column, patch) {
|
|
4564
|
+
const key = cellKey(row, column);
|
|
4565
|
+
styles.set(key, {
|
|
4566
|
+
...patch,
|
|
4567
|
+
...styles.get(key)
|
|
4568
|
+
});
|
|
4569
|
+
}
|
|
4570
|
+
function conditionalRules(sheet) {
|
|
4571
|
+
if (!Array.isArray(sheet.luckysheet_conditionformat_save)) return [];
|
|
4572
|
+
return sheet.luckysheet_conditionformat_save.filter(isConditionalRule);
|
|
4573
|
+
}
|
|
4574
|
+
function isConditionalRule(value) {
|
|
4575
|
+
if (!value || 'object' != typeof value) return false;
|
|
4576
|
+
const rule = value;
|
|
4577
|
+
return [
|
|
4578
|
+
'default',
|
|
4579
|
+
'colorGradation',
|
|
4580
|
+
'dataBar',
|
|
4581
|
+
'icons'
|
|
4582
|
+
].includes(String(rule.type)) && Array.isArray(rule.cellrange);
|
|
4583
|
+
}
|
|
4584
|
+
function isConditionalFormatStyle(value) {
|
|
4585
|
+
return Boolean(value) && !Array.isArray(value) && 'object' == typeof value && !('iconSet' in value) && 'textColor' in value && 'cellColor' in value;
|
|
4586
|
+
}
|
|
4587
|
+
function cellPositions(data, ranges) {
|
|
4588
|
+
const positions = [];
|
|
4589
|
+
const seen = new Set();
|
|
4590
|
+
forEachCell(data, ranges, (position)=>{
|
|
4591
|
+
const key = cellKey(position.row, position.column);
|
|
4592
|
+
if (!seen.has(key)) {
|
|
4593
|
+
seen.add(key);
|
|
4594
|
+
positions.push(position);
|
|
4595
|
+
}
|
|
4596
|
+
});
|
|
4597
|
+
return positions;
|
|
4598
|
+
}
|
|
4599
|
+
function cellKey(row, column) {
|
|
4600
|
+
return `${row}_${column}`;
|
|
4601
|
+
}
|
|
4602
|
+
function forEachCell(data, ranges, visit) {
|
|
4603
|
+
for (const [row, cells] of spreadsheet_sparse_sparseArrayEntries(data))for (const [column] of spreadsheet_sparse_sparseArrayEntries(cells))if (ranges.some((range)=>row >= range.row[0] && row <= range.row[1] && column >= range.column[0] && column <= range.column[1])) visit({
|
|
4604
|
+
row,
|
|
4605
|
+
column
|
|
4606
|
+
});
|
|
4607
|
+
}
|
|
4608
|
+
function forEachFormulaCell(data, ranges, visit) {
|
|
4609
|
+
for (const range of ranges){
|
|
4610
|
+
const area = (range.row[1] - range.row[0] + 1) * (range.column[1] - range.column[0] + 1);
|
|
4611
|
+
if (area > 0 && area <= MAX_SPREADSHEET_CONDITIONAL_FORMULA_SCAN_CELLS) {
|
|
4612
|
+
for(let row = range.row[0]; row <= range.row[1]; row += 1)for(let column = range.column[0]; column <= range.column[1]; column += 1)visit({
|
|
4613
|
+
row,
|
|
4614
|
+
column
|
|
4615
|
+
});
|
|
4616
|
+
continue;
|
|
4617
|
+
}
|
|
4618
|
+
forEachCell(data, [
|
|
4619
|
+
range
|
|
4620
|
+
], visit);
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
function work_spreadsheet_conditional_format_cellValue(data, row, column) {
|
|
4624
|
+
return data[row]?.[column]?.v ?? null;
|
|
4625
|
+
}
|
|
4626
|
+
function work_spreadsheet_conditional_format_numericCellValue(data, row, column) {
|
|
4627
|
+
const value = work_spreadsheet_conditional_format_cellValue(data, row, column);
|
|
4628
|
+
return 'number' == typeof value && Number.isFinite(value) ? value : null;
|
|
4629
|
+
}
|
|
4630
|
+
function comparableKey(value) {
|
|
4631
|
+
return `${typeof value}:${String(value)}`;
|
|
4632
|
+
}
|
|
4633
|
+
function compareValues(left, right) {
|
|
4634
|
+
const leftNumber = Number(left);
|
|
4635
|
+
const rightNumber = Number(right);
|
|
4636
|
+
if (String(left).trim() && String(right).trim() && Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) return leftNumber === rightNumber ? 0 : leftNumber > rightNumber ? 1 : -1;
|
|
4637
|
+
return String(left).localeCompare(String(right));
|
|
4638
|
+
}
|
|
4639
|
+
function toFormulaSheet(sheet) {
|
|
4640
|
+
return {
|
|
4641
|
+
id: sheet.id ?? '',
|
|
4642
|
+
name: sheet.name,
|
|
4643
|
+
data: sheet.data,
|
|
4644
|
+
celldata: sheet.celldata
|
|
4645
|
+
};
|
|
4646
|
+
}
|
|
4647
|
+
const xlsxNativeFillCellKeys = [
|
|
4648
|
+
XLSX_PATTERN_FILL_CELL_KEY,
|
|
4649
|
+
XLSX_GRADIENT_FILL_CELL_KEY
|
|
4650
|
+
];
|
|
4651
|
+
function activeXlsxNativeFill(cell) {
|
|
4652
|
+
const gradient = activeXlsxGradientFill(cell);
|
|
4653
|
+
const pattern = activeXlsxPatternFill(cell);
|
|
4654
|
+
if (Boolean(gradient) === Boolean(pattern)) return;
|
|
4655
|
+
return gradient ? {
|
|
4656
|
+
kind: 'gradient',
|
|
4657
|
+
value: gradient
|
|
4658
|
+
} : {
|
|
4659
|
+
kind: 'pattern',
|
|
4660
|
+
value: pattern
|
|
4661
|
+
};
|
|
4662
|
+
}
|
|
4663
|
+
function deleteXlsxNativeFills(cell) {
|
|
4664
|
+
deleteXlsxGradientFill(cell);
|
|
4665
|
+
deleteXlsxPatternFill(cell);
|
|
4666
|
+
}
|
|
4667
|
+
function xlsxNativeFillSemanticColors(fill) {
|
|
4668
|
+
if (!fill) return [];
|
|
4669
|
+
return 'gradient' === fill.kind ? xlsxGradientFillSemanticColors(fill.value) : xlsxPatternFillSemanticColors(fill.value);
|
|
4670
|
+
}
|
|
4671
|
+
function xlsxNativeFillKey(fill) {
|
|
4672
|
+
if (!fill) return '';
|
|
4673
|
+
return 'gradient' === fill.kind ? `gradient:${xlsxGradientFillKey(fill.value)}` : `pattern:${xlsxPatternFillKey(fill.value)}`;
|
|
4674
|
+
}
|
|
4675
|
+
function spreadsheetCellFillFormat(cell) {
|
|
4676
|
+
const nativeFill = activeXlsxNativeFill(cell);
|
|
4677
|
+
if (nativeFill) return nativeFill;
|
|
4678
|
+
const color = normalizeSpreadsheetFillColor(cell?.bg);
|
|
4679
|
+
return color ? {
|
|
4680
|
+
color,
|
|
4681
|
+
kind: 'solid'
|
|
4682
|
+
} : {
|
|
4683
|
+
kind: 'none'
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4686
|
+
function normalizeSpreadsheetCellFillFormat(value) {
|
|
4687
|
+
if (!spreadsheet_cell_fill_format_isRecord(value)) return null;
|
|
4688
|
+
if ('none' === value.kind) return {
|
|
4689
|
+
kind: 'none'
|
|
4690
|
+
};
|
|
4691
|
+
if ('solid' === value.kind) {
|
|
4692
|
+
const color = normalizeSpreadsheetFillColor(value.color);
|
|
4693
|
+
return color ? {
|
|
4694
|
+
color,
|
|
4695
|
+
kind: 'solid'
|
|
4696
|
+
} : null;
|
|
4697
|
+
}
|
|
4698
|
+
if ('pattern' === value.kind) {
|
|
4699
|
+
const fill = normalizeXlsxPatternFill(value.value);
|
|
4700
|
+
return fill ? {
|
|
4701
|
+
kind: 'pattern',
|
|
4702
|
+
value: fill
|
|
4703
|
+
} : null;
|
|
4704
|
+
}
|
|
4705
|
+
if ('gradient' === value.kind) {
|
|
4706
|
+
const fill = normalizeXlsxGradientFill(value.value);
|
|
4707
|
+
return fill ? {
|
|
4708
|
+
kind: 'gradient',
|
|
4709
|
+
value: fill
|
|
4710
|
+
} : null;
|
|
4711
|
+
}
|
|
4712
|
+
return null;
|
|
4713
|
+
}
|
|
4714
|
+
function withSpreadsheetCellFillFormat(cell, fill) {
|
|
4715
|
+
const normalized = normalizeSpreadsheetCellFillFormat(fill);
|
|
4716
|
+
if (!normalized) return null;
|
|
4717
|
+
const next = {
|
|
4718
|
+
...cell
|
|
4719
|
+
};
|
|
4720
|
+
deleteXlsxNativeFills(next);
|
|
4721
|
+
if ('none' === normalized.kind) {
|
|
4722
|
+
delete next.bg;
|
|
4723
|
+
return next;
|
|
4724
|
+
}
|
|
4725
|
+
if ('solid' === normalized.kind) {
|
|
4726
|
+
next.bg = normalized.color;
|
|
4727
|
+
return next;
|
|
4728
|
+
}
|
|
4729
|
+
if ('pattern' === normalized.kind) {
|
|
4730
|
+
next.bg = normalized.value.backgroundColor;
|
|
4731
|
+
return withXlsxPatternFill(next, normalized.value);
|
|
4732
|
+
}
|
|
4733
|
+
next.bg = xlsxGradientFillFallbackColor(normalized.value);
|
|
4734
|
+
return withXlsxGradientFill(next, normalized.value);
|
|
4735
|
+
}
|
|
4736
|
+
function normalizeSpreadsheetFillColor(value) {
|
|
4737
|
+
if ('string' != typeof value) return null;
|
|
4738
|
+
const color = value.trim().toLowerCase();
|
|
4739
|
+
if (/^#[0-9a-f]{6}$/.test(color)) return color;
|
|
4740
|
+
if (!/^#[0-9a-f]{3}$/.test(color)) return null;
|
|
4741
|
+
return `#${[
|
|
4742
|
+
...color.slice(1)
|
|
4743
|
+
].map((character)=>character.repeat(2)).join('')}`;
|
|
4744
|
+
}
|
|
4745
|
+
function spreadsheet_cell_fill_format_isRecord(value) {
|
|
4746
|
+
return 'object' == typeof value && null !== value && !Array.isArray(value);
|
|
4747
|
+
}
|
|
4748
|
+
function createSpreadsheetSortAppearanceRows(sheet, range, rows) {
|
|
4749
|
+
const data = liveSpreadsheetSortMatrix(sheet, range, rows);
|
|
4750
|
+
const conditional = spreadsheetConditionalFormatStyles({
|
|
4751
|
+
...sheet,
|
|
4752
|
+
data
|
|
4753
|
+
});
|
|
4754
|
+
return rows.map((row, rowOffset)=>row.map((cell, columnOffset)=>{
|
|
4755
|
+
const direct = spreadsheetSortDirectCellAppearance(cell);
|
|
4756
|
+
const style = conditional.get(`${range.row[0] + rowOffset}_${range.column[0] + columnOffset}`);
|
|
4757
|
+
const conditionalCellColor = normalizeSpreadsheetSortColor(style?.cellColor);
|
|
4758
|
+
const conditionalFontColor = normalizeSpreadsheetSortColor(style?.textColor);
|
|
4759
|
+
return {
|
|
4760
|
+
cellColor: void 0 !== conditionalCellColor ? conditionalCellColor : direct.cellColor,
|
|
4761
|
+
fontColor: void 0 !== conditionalFontColor ? conditionalFontColor : direct.fontColor,
|
|
4762
|
+
icon: normalizeSpreadsheetSortIcon(style?.icon) ?? null
|
|
4763
|
+
};
|
|
4764
|
+
}));
|
|
4765
|
+
}
|
|
4766
|
+
function createSpreadsheetSortDirectAppearanceRows(rows) {
|
|
4767
|
+
return rows.map((row)=>row.map(spreadsheetSortDirectCellAppearance));
|
|
4768
|
+
}
|
|
4769
|
+
function spreadsheetSortAppearanceFields(rows, range, orientation, hasHeader) {
|
|
4770
|
+
const vertical = 'top-to-bottom' === orientation;
|
|
4771
|
+
const fieldCount = vertical ? range.column[1] - range.column[0] + 1 : range.row[1] - range.row[0] + 1;
|
|
4772
|
+
return Array.from({
|
|
4773
|
+
length: fieldCount
|
|
4774
|
+
}, (_, offset)=>{
|
|
4775
|
+
const cellColors = [];
|
|
4776
|
+
const fontColors = [];
|
|
4777
|
+
const icons = [];
|
|
4778
|
+
const seenCellColors = new Set();
|
|
4779
|
+
const seenFontColors = new Set();
|
|
4780
|
+
const seenIcons = new Set();
|
|
4781
|
+
const appearances = vertical ? rows.slice(hasHeader ? 1 : 0).map((row)=>row[offset]) : rows[offset] ?? [];
|
|
4782
|
+
for (const appearance of appearances)if (appearance) {
|
|
4783
|
+
if (void 0 !== appearance.cellColor) appendUniqueColor(cellColors, seenCellColors, appearance.cellColor);
|
|
4784
|
+
appendUniqueColor(fontColors, seenFontColors, appearance.fontColor);
|
|
4785
|
+
if (appearance.icon) {
|
|
4786
|
+
const key = spreadsheetSortIconTargetKey(appearance.icon);
|
|
4787
|
+
if (!seenIcons.has(key)) {
|
|
4788
|
+
seenIcons.add(key);
|
|
4789
|
+
icons.push({
|
|
4790
|
+
...appearance.icon
|
|
4791
|
+
});
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
return {
|
|
4796
|
+
index: (vertical ? range.column[0] : range.row[0]) + offset,
|
|
4797
|
+
cellColors,
|
|
4798
|
+
fontColors,
|
|
4799
|
+
icons
|
|
4800
|
+
};
|
|
4801
|
+
});
|
|
4802
|
+
}
|
|
4803
|
+
function normalizeSpreadsheetSortColor(value) {
|
|
4804
|
+
if (null === value) return null;
|
|
4805
|
+
if ('string' != typeof value) return;
|
|
4806
|
+
const source = value.trim();
|
|
4807
|
+
const shorthand = /^#([0-9a-f]{3})$/iu.exec(source);
|
|
4808
|
+
const parsed = parseSpreadsheetConditionalColor(shorthand ? `#${[
|
|
4809
|
+
...shorthand[1]
|
|
4810
|
+
].map((character)=>character.repeat(2)).join('')}` : source);
|
|
4811
|
+
return parsed ? spreadsheetSortHexColor(parsed) : void 0;
|
|
4812
|
+
}
|
|
4813
|
+
function normalizeSpreadsheetSortIcon(value) {
|
|
4814
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) return null;
|
|
4815
|
+
const source = value;
|
|
4816
|
+
if (!isSpreadsheetConditionalIconSetName(source.iconSet) || !Number.isSafeInteger(source.index) || source.index < 0 || source.index >= spreadsheetConditionalIconSetCount(source.iconSet)) return null;
|
|
4817
|
+
return {
|
|
4818
|
+
iconSet: source.iconSet,
|
|
4819
|
+
index: source.index
|
|
4820
|
+
};
|
|
4821
|
+
}
|
|
4822
|
+
function spreadsheetSortAppearanceTargetValue(target) {
|
|
4823
|
+
if ('icon' === target.kind) return `icon:${target.icon.iconSet}:${target.icon.index}`;
|
|
4824
|
+
const value = target.color ?? ('cell-color' === target.kind ? 'none' : 'automatic');
|
|
4825
|
+
return `${target.kind}:${value}`;
|
|
4826
|
+
}
|
|
4827
|
+
function parseSpreadsheetSortAppearanceTargetValue(value) {
|
|
4828
|
+
if (value.startsWith('cell-color:')) {
|
|
4829
|
+
const source = value.slice(11);
|
|
4830
|
+
if ('none' === source) return {
|
|
4831
|
+
kind: 'cell-color',
|
|
4832
|
+
color: null
|
|
4833
|
+
};
|
|
4834
|
+
const color = normalizeSpreadsheetSortColor(source);
|
|
4835
|
+
return 'string' == typeof color ? {
|
|
4836
|
+
kind: 'cell-color',
|
|
4837
|
+
color
|
|
4838
|
+
} : null;
|
|
4839
|
+
}
|
|
4840
|
+
if (value.startsWith('font-color:')) {
|
|
4841
|
+
const source = value.slice(11);
|
|
4842
|
+
if ('automatic' === source) return {
|
|
4843
|
+
kind: 'font-color',
|
|
4844
|
+
color: null
|
|
4845
|
+
};
|
|
4846
|
+
const color = normalizeSpreadsheetSortColor(source);
|
|
4847
|
+
return 'string' == typeof color ? {
|
|
4848
|
+
kind: 'font-color',
|
|
4849
|
+
color
|
|
4850
|
+
} : null;
|
|
4851
|
+
}
|
|
4852
|
+
const icon = /^icon:([^:]+):(\d+)$/u.exec(value);
|
|
4853
|
+
if (!icon) return null;
|
|
4854
|
+
const normalized = normalizeSpreadsheetSortIcon({
|
|
4855
|
+
iconSet: icon[1],
|
|
4856
|
+
index: Number(icon[2])
|
|
4857
|
+
});
|
|
4858
|
+
return normalized ? {
|
|
4859
|
+
kind: 'icon',
|
|
4860
|
+
icon: normalized
|
|
4861
|
+
} : null;
|
|
4862
|
+
}
|
|
4863
|
+
function spreadsheetSortAppearanceTargetLabel(target) {
|
|
4864
|
+
if ('cell-color' === target.kind) return target.color ? `单元格颜色 ${target.color.toUpperCase()}` : '无单元格颜色';
|
|
4865
|
+
if ('font-color' === target.kind) return target.color ? `字体颜色 ${target.color.toUpperCase()}` : '自动字体颜色';
|
|
4866
|
+
const count = spreadsheetConditionalIconSetCount(target.icon.iconSet);
|
|
4867
|
+
const appearance = spreadsheetConditionalIconAppearance({
|
|
4868
|
+
...target.icon,
|
|
4869
|
+
count,
|
|
4870
|
+
showValue: true
|
|
4871
|
+
});
|
|
4872
|
+
return `${appearance.glyph} ${appearance.label}`;
|
|
4873
|
+
}
|
|
4874
|
+
function spreadsheetSortAppearanceTargets(field, kind) {
|
|
4875
|
+
if (!field) return [];
|
|
4876
|
+
if ('cell-color' === kind) return field.cellColors.map((color)=>({
|
|
4877
|
+
kind,
|
|
4878
|
+
color
|
|
4879
|
+
}));
|
|
4880
|
+
if ('font-color' === kind) return field.fontColors.map((color)=>({
|
|
4881
|
+
kind,
|
|
4882
|
+
color
|
|
4883
|
+
}));
|
|
4884
|
+
return field.icons.map((icon)=>({
|
|
4885
|
+
kind,
|
|
4886
|
+
icon: {
|
|
4887
|
+
...icon
|
|
4888
|
+
}
|
|
4889
|
+
}));
|
|
4890
|
+
}
|
|
4891
|
+
function spreadsheetSortAppearanceTargetsEqual(left, right) {
|
|
4892
|
+
return spreadsheetSortAppearanceTargetValue(left) === spreadsheetSortAppearanceTargetValue(right);
|
|
4893
|
+
}
|
|
4894
|
+
function spreadsheetSortCellMatchesAppearance(appearance, target) {
|
|
4895
|
+
if (!appearance) return false;
|
|
4896
|
+
if ('cell-color' === target.kind) return appearance.cellColor === target.color;
|
|
4897
|
+
if ('font-color' === target.kind) return appearance.fontColor === target.color;
|
|
4898
|
+
return Boolean(appearance.icon && spreadsheetSortIconTargetKey(appearance.icon) === spreadsheetSortIconTargetKey(target.icon));
|
|
4899
|
+
}
|
|
4900
|
+
function spreadsheetSortDirectCellAppearance(cell) {
|
|
4901
|
+
const fill = spreadsheetCellFillFormat(cell);
|
|
4902
|
+
const fontColor = normalizeSpreadsheetSortColor(cell?.fc);
|
|
4903
|
+
return {
|
|
4904
|
+
cellColor: 'solid' === fill.kind ? fill.color : 'none' === fill.kind ? null : void 0,
|
|
4905
|
+
fontColor: 'string' == typeof fontColor ? fontColor : null,
|
|
4906
|
+
icon: null
|
|
4907
|
+
};
|
|
4908
|
+
}
|
|
4909
|
+
function liveSpreadsheetSortMatrix(sheet, range, rows) {
|
|
4910
|
+
const data = cloneSparseMatrix(sheet.data);
|
|
4911
|
+
for (const entry of sheet.celldata ?? []){
|
|
4912
|
+
const row = data[entry.r] ? [
|
|
4913
|
+
...data[entry.r]
|
|
4914
|
+
] : [];
|
|
4915
|
+
if (void 0 === row[entry.c]) row[entry.c] = entry.v;
|
|
4916
|
+
data[entry.r] = row;
|
|
4917
|
+
}
|
|
4918
|
+
for(let rowOffset = 0; rowOffset < rows.length; rowOffset += 1){
|
|
4919
|
+
const absoluteRow = range.row[0] + rowOffset;
|
|
4920
|
+
const target = data[absoluteRow] ? [
|
|
4921
|
+
...data[absoluteRow]
|
|
4922
|
+
] : [];
|
|
4923
|
+
const source = rows[rowOffset] ?? [];
|
|
4924
|
+
for(let columnOffset = 0; columnOffset < source.length; columnOffset += 1)target[range.column[0] + columnOffset] = source[columnOffset] ?? null;
|
|
4925
|
+
data[absoluteRow] = target;
|
|
4926
|
+
}
|
|
4927
|
+
return data;
|
|
4928
|
+
}
|
|
4929
|
+
function appendUniqueColor(target, seen, color) {
|
|
4930
|
+
const key = color ?? 'none';
|
|
4931
|
+
if (seen.has(key)) return;
|
|
4932
|
+
seen.add(key);
|
|
4933
|
+
target.push(color);
|
|
4934
|
+
}
|
|
4935
|
+
function spreadsheetSortIconTargetKey(target) {
|
|
4936
|
+
return `${target.iconSet}:${target.index}`;
|
|
4937
|
+
}
|
|
4938
|
+
function spreadsheetSortHexColor(color) {
|
|
4939
|
+
return `#${[
|
|
4940
|
+
color.red,
|
|
4941
|
+
color.green,
|
|
4942
|
+
color.blue
|
|
4943
|
+
].map((channel)=>channel.toString(16).padStart(2, '0')).join('')}`;
|
|
4944
|
+
}
|
|
4945
|
+
const DEFAULT_SPREADSHEET_SORT_TEXT_OPTIONS = Object.freeze({
|
|
4946
|
+
caseSensitive: false,
|
|
4947
|
+
textMethod: 'pinyin'
|
|
4948
|
+
});
|
|
4949
|
+
const SPREADSHEET_SORT_COLLATION_PROBES = Object.freeze({
|
|
4950
|
+
pinyin: Object.freeze([
|
|
4951
|
+
'阿',
|
|
4952
|
+
'丁',
|
|
4953
|
+
'王',
|
|
4954
|
+
'赵'
|
|
4955
|
+
]),
|
|
4956
|
+
stroke: Object.freeze([
|
|
4957
|
+
'丁',
|
|
4958
|
+
'王',
|
|
4959
|
+
'安',
|
|
4960
|
+
'阿',
|
|
4961
|
+
'赵'
|
|
4962
|
+
])
|
|
4963
|
+
});
|
|
4964
|
+
function isSpreadsheetSortTextMethod(value) {
|
|
4965
|
+
return 'pinyin' === value || 'stroke' === value;
|
|
4966
|
+
}
|
|
4967
|
+
function createSpreadsheetSortTextComparator(options) {
|
|
4968
|
+
try {
|
|
4969
|
+
const collator = new Intl.Collator(`zh-CN-u-co-${options.textMethod}`, {
|
|
4970
|
+
caseFirst: 'lower',
|
|
4971
|
+
numeric: false,
|
|
4972
|
+
sensitivity: options.caseSensitive ? 'case' : 'base',
|
|
4973
|
+
usage: 'sort'
|
|
4974
|
+
});
|
|
4975
|
+
if (!spreadsheetSortCollatorSupportsMethod(collator, options.textMethod)) return null;
|
|
4976
|
+
return collator.compare;
|
|
4977
|
+
} catch {
|
|
4978
|
+
return null;
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
function spreadsheetSortCollatorSupportsMethod(collator, textMethod) {
|
|
4982
|
+
const probe = SPREADSHEET_SORT_COLLATION_PROBES[textMethod];
|
|
4983
|
+
for(let index = 1; index < probe.length; index += 1){
|
|
4984
|
+
const previous = probe[index - 1];
|
|
4985
|
+
const current = probe[index];
|
|
4986
|
+
if (void 0 === previous || void 0 === current) return false;
|
|
4987
|
+
if (collator.compare(previous, current) >= 0) return false;
|
|
4988
|
+
}
|
|
4989
|
+
return true;
|
|
4990
|
+
}
|
|
4991
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES = 256;
|
|
4992
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS = 128;
|
|
4993
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS = 4096;
|
|
4994
|
+
const MAX_SPREADSHEET_SORT_USER_CUSTOM_LISTS = 32;
|
|
4995
|
+
function parseSpreadsheetSortCustomList(text) {
|
|
4996
|
+
return validateSpreadsheetSortCustomList(text.split(/\r?\n|,|,/u).map((entry)=>entry.trim()).filter(Boolean));
|
|
4997
|
+
}
|
|
4998
|
+
function validateSpreadsheetSortCustomList(input) {
|
|
4999
|
+
if (!Array.isArray(input)) return customListError('invalid-entry');
|
|
5000
|
+
if (input.length < 2) return customListError('not-enough-entries');
|
|
5001
|
+
if (input.length > MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES) return customListError('too-many-entries');
|
|
5002
|
+
const entries = [];
|
|
5003
|
+
const seen = new Set();
|
|
5004
|
+
let totalCodePoints = 0;
|
|
5005
|
+
for (const candidate of input){
|
|
5006
|
+
if ('string' != typeof candidate) return customListError('invalid-entry');
|
|
5007
|
+
const entry = candidate.trim();
|
|
5008
|
+
if (!entry) return customListError('invalid-entry');
|
|
5009
|
+
const codePoints = Array.from(entry).length;
|
|
5010
|
+
if (codePoints > MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS) return customListError('entry-too-long');
|
|
5011
|
+
totalCodePoints += codePoints;
|
|
5012
|
+
if (totalCodePoints > MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS) return customListError('list-too-long');
|
|
5013
|
+
const matchKey = spreadsheetSortCustomListMatchKey(entry);
|
|
5014
|
+
if (seen.has(matchKey)) return customListError('duplicate-entry');
|
|
5015
|
+
seen.add(matchKey);
|
|
5016
|
+
entries.push(entry);
|
|
5017
|
+
}
|
|
5018
|
+
return {
|
|
5019
|
+
ok: true,
|
|
5020
|
+
entries
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
function createSpreadsheetSortCustomList(entries, source = 'session') {
|
|
5024
|
+
const validation = validateSpreadsheetSortCustomList(entries);
|
|
5025
|
+
if (!validation.ok) return null;
|
|
5026
|
+
const normalized = Object.freeze([
|
|
5027
|
+
...validation.entries
|
|
5028
|
+
]);
|
|
5029
|
+
return Object.freeze({
|
|
5030
|
+
source,
|
|
5031
|
+
entries: normalized,
|
|
5032
|
+
label: spreadsheetSortCustomListLabel(normalized)
|
|
5033
|
+
});
|
|
5034
|
+
}
|
|
5035
|
+
function mergeSpreadsheetSortCustomLists(lists) {
|
|
5036
|
+
const merged = [
|
|
5037
|
+
...SPREADSHEET_SORT_BUILT_IN_CUSTOM_LISTS
|
|
5038
|
+
];
|
|
5039
|
+
let userListCount = 0;
|
|
5040
|
+
for (const candidate of lists){
|
|
5041
|
+
if ('stored' !== candidate.source && 'session' !== candidate.source) continue;
|
|
5042
|
+
const list = createSpreadsheetSortCustomList(candidate.entries, candidate.source);
|
|
5043
|
+
if (!(!list || merged.some((item)=>spreadsheetSortCustomListsEqual(item.entries, list.entries)))) {
|
|
5044
|
+
if (userListCount >= MAX_SPREADSHEET_SORT_USER_CUSTOM_LISTS) break;
|
|
5045
|
+
merged.push(list);
|
|
5046
|
+
userListCount += 1;
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
return Object.freeze(merged);
|
|
5050
|
+
}
|
|
5051
|
+
function spreadsheetSortCustomListsEqual(left, right) {
|
|
5052
|
+
return left.length === right.length && left.every((entry, index)=>spreadsheetSortCustomListMatchKey(entry) === spreadsheetSortCustomListMatchKey(right[index] ?? ''));
|
|
5053
|
+
}
|
|
5054
|
+
function spreadsheetSortCustomListMatchKey(value) {
|
|
5055
|
+
return String(value).normalize('NFKC').trim().toLocaleLowerCase('zh-CN');
|
|
5056
|
+
}
|
|
5057
|
+
function spreadsheetSortCustomListLabel(entries) {
|
|
5058
|
+
const visible = entries.slice(0, 3).map((entry)=>truncateSpreadsheetSortCustomListLabelEntry(entry));
|
|
5059
|
+
return `${visible.join(' → ')}${entries.length > 3 ? ' → …' : ''}`;
|
|
5060
|
+
}
|
|
5061
|
+
function truncateSpreadsheetSortCustomListLabelEntry(entry) {
|
|
5062
|
+
const characters = Array.from(entry);
|
|
5063
|
+
return characters.length > 18 ? `${characters.slice(0, 17).join('')}…` : entry;
|
|
5064
|
+
}
|
|
5065
|
+
function customListError(code) {
|
|
5066
|
+
const messages = {
|
|
5067
|
+
'duplicate-entry': '自定义序列不能包含重复项(忽略大小写和全半角差异)。',
|
|
5068
|
+
'entry-too-long': `每个序列项最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS} 个字符。`,
|
|
5069
|
+
'invalid-entry': '自定义序列只能包含非空文本项。',
|
|
5070
|
+
'list-too-long': `一个自定义序列最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS.toLocaleString('en-US')} 个字符。`,
|
|
5071
|
+
'not-enough-entries': '自定义序列至少需要两个项目。',
|
|
5072
|
+
'too-many-entries': `一个自定义序列最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES} 个项目。`
|
|
5073
|
+
};
|
|
5074
|
+
return {
|
|
5075
|
+
ok: false,
|
|
5076
|
+
code,
|
|
5077
|
+
message: messages[code]
|
|
5078
|
+
};
|
|
5079
|
+
}
|
|
5080
|
+
function builtInCustomList(label, entries) {
|
|
5081
|
+
return Object.freeze({
|
|
5082
|
+
source: 'built-in',
|
|
5083
|
+
entries: Object.freeze([
|
|
5084
|
+
...entries
|
|
5085
|
+
]),
|
|
5086
|
+
label
|
|
5087
|
+
});
|
|
5088
|
+
}
|
|
5089
|
+
const SPREADSHEET_SORT_BUILT_IN_CUSTOM_LISTS = Object.freeze([
|
|
5090
|
+
builtInCustomList('月份(一月 → 十二月)', [
|
|
5091
|
+
'一月',
|
|
5092
|
+
'二月',
|
|
5093
|
+
'三月',
|
|
5094
|
+
'四月',
|
|
5095
|
+
'五月',
|
|
5096
|
+
'六月',
|
|
5097
|
+
'七月',
|
|
5098
|
+
'八月',
|
|
5099
|
+
'九月',
|
|
5100
|
+
'十月',
|
|
5101
|
+
'十一月',
|
|
5102
|
+
'十二月'
|
|
5103
|
+
]),
|
|
5104
|
+
builtInCustomList('星期(星期日 → 星期六)', [
|
|
5105
|
+
'星期日',
|
|
5106
|
+
'星期一',
|
|
5107
|
+
'星期二',
|
|
5108
|
+
'星期三',
|
|
5109
|
+
'星期四',
|
|
5110
|
+
'星期五',
|
|
5111
|
+
'星期六'
|
|
5112
|
+
]),
|
|
5113
|
+
builtInCustomList('周(周日 → 周六)', [
|
|
5114
|
+
'周日',
|
|
5115
|
+
'周一',
|
|
5116
|
+
'周二',
|
|
5117
|
+
'周三',
|
|
5118
|
+
'周四',
|
|
5119
|
+
'周五',
|
|
5120
|
+
'周六'
|
|
5121
|
+
]),
|
|
5122
|
+
builtInCustomList('月份(January → December)', [
|
|
5123
|
+
'January',
|
|
5124
|
+
'February',
|
|
5125
|
+
'March',
|
|
5126
|
+
'April',
|
|
5127
|
+
'May',
|
|
5128
|
+
'June',
|
|
5129
|
+
'July',
|
|
5130
|
+
'August',
|
|
5131
|
+
'September',
|
|
5132
|
+
'October',
|
|
5133
|
+
'November',
|
|
5134
|
+
'December'
|
|
5135
|
+
]),
|
|
5136
|
+
builtInCustomList('月份(Jan → Dec)', [
|
|
5137
|
+
'Jan',
|
|
5138
|
+
'Feb',
|
|
5139
|
+
'Mar',
|
|
5140
|
+
'Apr',
|
|
5141
|
+
'May',
|
|
5142
|
+
'Jun',
|
|
5143
|
+
'Jul',
|
|
5144
|
+
'Aug',
|
|
5145
|
+
'Sep',
|
|
5146
|
+
'Oct',
|
|
5147
|
+
'Nov',
|
|
5148
|
+
'Dec'
|
|
5149
|
+
]),
|
|
5150
|
+
builtInCustomList('星期(Sunday → Saturday)', [
|
|
5151
|
+
'Sunday',
|
|
5152
|
+
'Monday',
|
|
5153
|
+
'Tuesday',
|
|
5154
|
+
'Wednesday',
|
|
5155
|
+
'Thursday',
|
|
5156
|
+
'Friday',
|
|
5157
|
+
'Saturday'
|
|
5158
|
+
]),
|
|
5159
|
+
builtInCustomList('星期(Sun → Sat)', [
|
|
5160
|
+
'Sun',
|
|
5161
|
+
'Mon',
|
|
5162
|
+
'Tue',
|
|
5163
|
+
'Wed',
|
|
5164
|
+
'Thu',
|
|
5165
|
+
'Fri',
|
|
5166
|
+
'Sat'
|
|
5167
|
+
])
|
|
5168
|
+
]);
|
|
5169
|
+
const MAX_SPREADSHEET_SORT_KEYS = 64;
|
|
5170
|
+
const MAX_SPREADSHEET_SORT_CELLS = 1000000;
|
|
5171
|
+
const MAX_SPREADSHEET_SORT_HEADER_LENGTH = 48;
|
|
5172
|
+
function createSpreadsheetSortRangePlan(sheet, selectedRange) {
|
|
5173
|
+
const selected = normalizeSpreadsheetCellRange(selectedRange);
|
|
5174
|
+
if (!selected) return null;
|
|
5175
|
+
const owned = spreadsheetSortOwnedRangeContaining(sheet, selected);
|
|
5176
|
+
if (owned) return {
|
|
5177
|
+
selectedRange: selected,
|
|
5178
|
+
expandedRange: spreadsheetCellRangesEqual(owned.range, selected) ? null : owned.range
|
|
5179
|
+
};
|
|
5180
|
+
const currentRegion = spreadsheetCurrentRegion(sheet, selected);
|
|
5181
|
+
return {
|
|
5182
|
+
selectedRange: selected,
|
|
5183
|
+
expandedRange: currentRegion && !spreadsheetCellRangesEqual(currentRegion, selected) ? currentRegion : null
|
|
5184
|
+
};
|
|
5185
|
+
}
|
|
5186
|
+
function createSpreadsheetSortRangeDialogSource(sheetName, request) {
|
|
5187
|
+
if (!request.expanded) return null;
|
|
5188
|
+
return {
|
|
5189
|
+
sheetName,
|
|
5190
|
+
selectedRangeReference: formatSpreadsheetCellRanges([
|
|
5191
|
+
request.selected.range
|
|
5192
|
+
]),
|
|
5193
|
+
expandedRangeReference: formatSpreadsheetCellRanges([
|
|
5194
|
+
request.expanded.range
|
|
5195
|
+
]),
|
|
5196
|
+
canSortSelection: request.selected.available,
|
|
5197
|
+
canSortExpandedRange: request.expanded.available,
|
|
5198
|
+
...request.expanded.scope ? {
|
|
5199
|
+
ownedScope: request.expanded.scope
|
|
5200
|
+
} : {}
|
|
5201
|
+
};
|
|
5202
|
+
}
|
|
5203
|
+
function createSpreadsheetSortDialogSource(sheetId, sheetName, target, rows, customLists = [], appearanceRows) {
|
|
5204
|
+
const range = normalizeSpreadsheetCellRange(target.range);
|
|
5205
|
+
if (!range || spreadsheetSortRangeError(range)) return null;
|
|
5206
|
+
const width = range.column[1] - range.column[0] + 1;
|
|
5207
|
+
const height = range.row[1] - range.row[0] + 1;
|
|
5208
|
+
if (!spreadsheetSortRowsMatchRange(rows, range)) return null;
|
|
5209
|
+
const hasHeader = height > 2 && spreadsheetSortRowsHaveHeader(rows);
|
|
5210
|
+
const resolvedHasHeader = target.scope?.hasHeader ?? hasHeader;
|
|
5211
|
+
const activeColumn = spreadsheetSortActiveIndex(target.activeColumn, range.column);
|
|
5212
|
+
const activeRow = spreadsheetSortActiveIndex(target.activeRow, range.row);
|
|
5213
|
+
const header = rows[0] ?? [];
|
|
5214
|
+
const resolvedAppearanceRows = appearanceRows && spreadsheetSortAppearanceRowsMatch(rows, appearanceRows) ? appearanceRows : createSpreadsheetSortDirectAppearanceRows(rows);
|
|
5215
|
+
return {
|
|
5216
|
+
sheetId,
|
|
5217
|
+
sheetName,
|
|
5218
|
+
range,
|
|
5219
|
+
rangeReference: formatSpreadsheetCellRanges([
|
|
5220
|
+
range
|
|
5221
|
+
]),
|
|
5222
|
+
activeRow,
|
|
5223
|
+
appearanceRows: resolvedAppearanceRows,
|
|
5224
|
+
customLists: mergeSpreadsheetSortCustomLists(customLists),
|
|
5225
|
+
...target.scope ? {
|
|
5226
|
+
scope: target.scope
|
|
5227
|
+
} : {},
|
|
5228
|
+
columns: Array.from({
|
|
5229
|
+
length: width
|
|
5230
|
+
}, (_, offset)=>{
|
|
5231
|
+
const index = range.column[0] + offset;
|
|
5232
|
+
const name = spreadsheetSortHeaderText(header[offset]);
|
|
5233
|
+
const coordinate = spreadsheetSortColumnLabel(index);
|
|
5234
|
+
return {
|
|
5235
|
+
index,
|
|
5236
|
+
label: name ? `${coordinate}(${name})` : coordinate
|
|
5237
|
+
};
|
|
5238
|
+
}),
|
|
5239
|
+
rows: Array.from({
|
|
5240
|
+
length: height
|
|
5241
|
+
}, (_, offset)=>{
|
|
5242
|
+
const index = range.row[0] + offset;
|
|
5243
|
+
return {
|
|
5244
|
+
index,
|
|
5245
|
+
label: `行 ${index + 1}`
|
|
5246
|
+
};
|
|
5247
|
+
}),
|
|
5248
|
+
value: {
|
|
5249
|
+
orientation: 'top-to-bottom',
|
|
5250
|
+
...DEFAULT_SPREADSHEET_SORT_TEXT_OPTIONS,
|
|
5251
|
+
hasHeader: resolvedHasHeader,
|
|
5252
|
+
keys: [
|
|
5253
|
+
{
|
|
5254
|
+
index: activeColumn,
|
|
5255
|
+
direction: 'ascending'
|
|
5256
|
+
}
|
|
5257
|
+
]
|
|
5258
|
+
}
|
|
5259
|
+
};
|
|
5260
|
+
}
|
|
5261
|
+
function spreadsheetSortRowsMatchRange(rows, range) {
|
|
5262
|
+
const normalized = normalizeSpreadsheetCellRange(range);
|
|
5263
|
+
if (!normalized) return false;
|
|
5264
|
+
const width = normalized.column[1] - normalized.column[0] + 1;
|
|
5265
|
+
const height = normalized.row[1] - normalized.row[0] + 1;
|
|
5266
|
+
return rows.length === height && rows.every((row)=>row.length === width);
|
|
5267
|
+
}
|
|
5268
|
+
function spreadsheetSortRowsFromSheet(sheet, range) {
|
|
5269
|
+
const normalized = normalizeSpreadsheetCellRange(range);
|
|
5270
|
+
if (!normalized || spreadsheetSortRangeError(normalized)) return null;
|
|
5271
|
+
const width = normalized.column[1] - normalized.column[0] + 1;
|
|
5272
|
+
const height = normalized.row[1] - normalized.row[0] + 1;
|
|
5273
|
+
const rows = Array.from({
|
|
5274
|
+
length: height
|
|
5275
|
+
}, ()=>Array(width).fill(null));
|
|
5276
|
+
for(let row = normalized.row[0]; row <= normalized.row[1]; row += 1){
|
|
5277
|
+
const source = sheet.data?.[row];
|
|
5278
|
+
if (!source) continue;
|
|
5279
|
+
const target = rows[row - normalized.row[0]];
|
|
5280
|
+
for(let column = normalized.column[0]; column <= normalized.column[1]; column += 1)target[column - normalized.column[0]] = source[column] ?? null;
|
|
5281
|
+
}
|
|
5282
|
+
for (const entry of sheet.celldata ?? []){
|
|
5283
|
+
if (entry.r < normalized.row[0] || entry.r > normalized.row[1] || entry.c < normalized.column[0] || entry.c > normalized.column[1]) continue;
|
|
5284
|
+
const row = rows[entry.r - normalized.row[0]];
|
|
5285
|
+
const column = entry.c - normalized.column[0];
|
|
5286
|
+
if (null === row[column]) row[column] = entry.v ?? null;
|
|
5287
|
+
}
|
|
5288
|
+
return rows;
|
|
5289
|
+
}
|
|
5290
|
+
function spreadsheetSortOwnedRangeForExactRange(sheet, range) {
|
|
5291
|
+
const normalized = normalizeSpreadsheetCellRange(range);
|
|
5292
|
+
if (!normalized) return null;
|
|
5293
|
+
const matches = spreadsheetSortOwnedRanges(sheet).filter((candidate)=>spreadsheetCellRangesEqual(candidate.range, normalized));
|
|
5294
|
+
return 1 === matches.length ? matches[0] : null;
|
|
5295
|
+
}
|
|
5296
|
+
function spreadsheetSortOwnedRangeContaining(sheet, range) {
|
|
5297
|
+
const matches = spreadsheetSortOwnedRanges(sheet).filter((candidate)=>spreadsheetSortRangeContains(candidate.range, range));
|
|
5298
|
+
return 1 === matches.length ? matches[0] : null;
|
|
5299
|
+
}
|
|
5300
|
+
function spreadsheetSortOwnedRanges(sheet) {
|
|
5301
|
+
const ranges = [];
|
|
5302
|
+
const autoFilter = normalizeSpreadsheetCellRange(sheet.filter_select ?? {
|
|
5303
|
+
row: [],
|
|
5304
|
+
column: []
|
|
5305
|
+
});
|
|
5306
|
+
if (autoFilter && autoFilter.row[1] > autoFilter.row[0]) ranges.push({
|
|
5307
|
+
range: autoFilter,
|
|
5308
|
+
scope: {
|
|
5309
|
+
kind: 'auto-filter',
|
|
5310
|
+
hasHeader: true
|
|
5311
|
+
}
|
|
5312
|
+
});
|
|
5313
|
+
for (const table of sheet.tables ?? []){
|
|
5314
|
+
const tableRange = normalizeSpreadsheetCellRange(table.range);
|
|
5315
|
+
if (!tableRange || !table.id || 0 === table.columns.length) continue;
|
|
5316
|
+
const width = tableRange.column[1] - tableRange.column[0] + 1;
|
|
5317
|
+
if (width !== table.columns.length) continue;
|
|
5318
|
+
const endRow = tableRange.row[1] - Number(table.totalsRow);
|
|
5319
|
+
if (!(endRow <= tableRange.row[0])) ranges.push({
|
|
5320
|
+
range: {
|
|
5321
|
+
row: [
|
|
5322
|
+
tableRange.row[0],
|
|
5323
|
+
endRow
|
|
5324
|
+
],
|
|
5325
|
+
column: [
|
|
5326
|
+
...tableRange.column
|
|
5327
|
+
]
|
|
5328
|
+
},
|
|
5329
|
+
scope: {
|
|
5330
|
+
kind: 'table',
|
|
5331
|
+
tableId: table.id,
|
|
5332
|
+
hasHeader: table.headerRow
|
|
5333
|
+
}
|
|
5334
|
+
});
|
|
5335
|
+
}
|
|
5336
|
+
return ranges;
|
|
5337
|
+
}
|
|
5338
|
+
function spreadsheetSortRangeContains(outer, inner) {
|
|
5339
|
+
return outer.row[0] <= inner.row[0] && outer.row[1] >= inner.row[1] && outer.column[0] <= inner.column[0] && outer.column[1] >= inner.column[1];
|
|
5340
|
+
}
|
|
5341
|
+
function validateSpreadsheetSortRequest(request) {
|
|
5342
|
+
const range = normalizeSpreadsheetCellRange(request.range);
|
|
5343
|
+
if (!range || 'string' != typeof request.sheetId || !request.sheetId.trim()) return spreadsheetSortError('invalid-range');
|
|
5344
|
+
const scope = normalizeSpreadsheetSortOwnedScope(request.scope);
|
|
5345
|
+
if (void 0 !== request.scope && !scope) return spreadsheetSortError('invalid-scope');
|
|
5346
|
+
if ('top-to-bottom' !== request.orientation && 'left-to-right' !== request.orientation) return spreadsheetSortError('invalid-orientation');
|
|
5347
|
+
const caseSensitive = void 0 === request.caseSensitive ? DEFAULT_SPREADSHEET_SORT_TEXT_OPTIONS.caseSensitive : request.caseSensitive;
|
|
5348
|
+
if ('boolean' != typeof caseSensitive) return spreadsheetSortError('invalid-case-sensitivity');
|
|
5349
|
+
const textMethod = void 0 === request.textMethod ? DEFAULT_SPREADSHEET_SORT_TEXT_OPTIONS.textMethod : request.textMethod;
|
|
5350
|
+
if (!isSpreadsheetSortTextMethod(textMethod)) return spreadsheetSortError('invalid-text-method');
|
|
5351
|
+
if ('left-to-right' === request.orientation && request.hasHeader) return spreadsheetSortError('invalid-header');
|
|
5352
|
+
const rangeError = spreadsheetSortRangeError(range);
|
|
5353
|
+
if (rangeError) return rangeError;
|
|
5354
|
+
if (!Array.isArray(request.keys) || !request.keys.length) return spreadsheetSortError('missing-key');
|
|
5355
|
+
if (request.keys.length > MAX_SPREADSHEET_SORT_KEYS) return spreadsheetSortError('too-many-keys');
|
|
5356
|
+
const keyRange = 'top-to-bottom' === request.orientation ? range.column : range.row;
|
|
5357
|
+
const outOfRangeCode = 'top-to-bottom' === request.orientation ? 'column-out-of-range' : 'row-out-of-range';
|
|
5358
|
+
const keys = [];
|
|
5359
|
+
for (const key of request.keys){
|
|
5360
|
+
if (!Number.isSafeInteger(key.index) || key.index < keyRange[0] || key.index > keyRange[1]) return spreadsheetSortError(outOfRangeCode);
|
|
5361
|
+
if (void 0 !== key.sortOn) {
|
|
5362
|
+
if ('first' !== key.position && 'last' !== key.position || void 0 !== key.customList || void 0 !== key.direction) return spreadsheetSortError('invalid-appearance');
|
|
5363
|
+
if ('cell-color' === key.sortOn || 'font-color' === key.sortOn) {
|
|
5364
|
+
const color = normalizeSpreadsheetSortColor(key.color);
|
|
5365
|
+
if (void 0 === color || void 0 !== key.icon) return spreadsheetSortError('invalid-appearance');
|
|
5366
|
+
const normalized = {
|
|
5367
|
+
index: key.index,
|
|
5368
|
+
sortOn: key.sortOn,
|
|
5369
|
+
color,
|
|
5370
|
+
position: key.position
|
|
5371
|
+
};
|
|
5372
|
+
if (!appendSpreadsheetSortKey(keys, normalized)) return spreadsheetSortError('duplicate-key');
|
|
5373
|
+
continue;
|
|
5374
|
+
}
|
|
5375
|
+
if ('icon' === key.sortOn && void 0 === key.color) {
|
|
5376
|
+
const icon = normalizeSpreadsheetSortIcon(key.icon);
|
|
5377
|
+
if (icon) {
|
|
5378
|
+
const normalized = {
|
|
5379
|
+
index: key.index,
|
|
5380
|
+
sortOn: 'icon',
|
|
5381
|
+
icon,
|
|
5382
|
+
position: key.position
|
|
5383
|
+
};
|
|
5384
|
+
if (!appendSpreadsheetSortKey(keys, normalized)) return spreadsheetSortError('duplicate-key');
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
return spreadsheetSortError('invalid-appearance');
|
|
5389
|
+
}
|
|
5390
|
+
if (void 0 !== key.customList) {
|
|
5391
|
+
if (void 0 !== key.direction) return spreadsheetSortError('invalid-custom-list');
|
|
5392
|
+
const customList = validateSpreadsheetSortCustomList(key.customList);
|
|
5393
|
+
if (!customList.ok) return spreadsheetSortError('invalid-custom-list');
|
|
5394
|
+
if (!appendSpreadsheetSortKey(keys, {
|
|
5395
|
+
index: key.index,
|
|
5396
|
+
customList: customList.entries
|
|
5397
|
+
})) return spreadsheetSortError('duplicate-key');
|
|
5398
|
+
continue;
|
|
5399
|
+
}
|
|
5400
|
+
if ('ascending' !== key.direction && 'descending' !== key.direction) return spreadsheetSortError('invalid-direction');
|
|
5401
|
+
if (!appendSpreadsheetSortKey(keys, {
|
|
5402
|
+
index: key.index,
|
|
5403
|
+
direction: key.direction
|
|
5404
|
+
})) return spreadsheetSortError('duplicate-key');
|
|
5405
|
+
}
|
|
5406
|
+
if ('top-to-bottom' === request.orientation) {
|
|
5407
|
+
const rowCount = range.row[1] - range.row[0] + 1;
|
|
5408
|
+
if (rowCount - (request.hasHeader ? 1 : 0) < 2) return spreadsheetSortError('not-enough-rows');
|
|
5409
|
+
} else if (range.column[1] - range.column[0] + 1 < 2) return spreadsheetSortError('not-enough-columns');
|
|
5410
|
+
return {
|
|
5411
|
+
ok: true,
|
|
5412
|
+
request: {
|
|
5413
|
+
sheetId: request.sheetId,
|
|
5414
|
+
range,
|
|
5415
|
+
orientation: request.orientation,
|
|
5416
|
+
caseSensitive,
|
|
5417
|
+
textMethod,
|
|
5418
|
+
hasHeader: Boolean(request.hasHeader),
|
|
5419
|
+
keys,
|
|
5420
|
+
...scope ? {
|
|
5421
|
+
scope
|
|
5422
|
+
} : {}
|
|
5423
|
+
}
|
|
5424
|
+
};
|
|
5425
|
+
}
|
|
5426
|
+
function spreadsheetSortAppearanceRowsMatch(rows, appearances) {
|
|
5427
|
+
return rows.length === appearances.length && rows.every((row, index)=>row.length === (appearances[index]?.length ?? -1));
|
|
5428
|
+
}
|
|
5429
|
+
function spreadsheetSortError(code) {
|
|
5430
|
+
const messages = {
|
|
5431
|
+
'column-out-of-range': '排序列必须位于当前选定区域内。',
|
|
5432
|
+
'duplicate-key': '同一行或列不能重复使用相同的值或外观排序条件。',
|
|
5433
|
+
'formula-reference-out-of-range': '排序会使相对公式引用超出工作表范围,因此未应用任何更改。',
|
|
5434
|
+
'invalid-appearance': '外观排序条件或当前颜色/图标快照无效,因此未应用任何更改。',
|
|
5435
|
+
'invalid-case-sensitivity': '请选择有效的大小写比较规则。',
|
|
5436
|
+
'invalid-custom-list': '自定义排序序列无效,请检查项目数量、长度和重复项。',
|
|
5437
|
+
'invalid-direction': '请选择有效的升序或降序次序。',
|
|
5438
|
+
'invalid-header': '按行排序会移动所有选定列,不能保留标题列。',
|
|
5439
|
+
'invalid-matrix': '排序只能应用到已完整读取的矩形区域。',
|
|
5440
|
+
'invalid-orientation': '请选择有效的按列或按行排序方向。',
|
|
5441
|
+
'invalid-range': '请选择一个有效的连续单元格区域。',
|
|
5442
|
+
'invalid-scope': '排序区域的表格或筛选所有权已变化,请重新打开排序。',
|
|
5443
|
+
'invalid-text-method': '请选择拼音排序或笔画排序。',
|
|
5444
|
+
'missing-key': '请至少添加一个排序条件。',
|
|
5445
|
+
'not-enough-columns': '当前区域没有足够的数据列可供按行排序。',
|
|
5446
|
+
'not-enough-rows': '当前区域没有足够的数据行可供排序。',
|
|
5447
|
+
'range-too-large': `一次最多可排序 ${MAX_SPREADSHEET_SORT_CELLS.toLocaleString('en-US')} 个单元格。`,
|
|
5448
|
+
'row-out-of-range': '排序行必须位于当前选定区域内。',
|
|
5449
|
+
'too-many-keys': `一次最多可设置 ${MAX_SPREADSHEET_SORT_KEYS} 个排序条件。`,
|
|
5450
|
+
'unsupported-text-method': '当前运行环境不支持所选中文排序方法,因此未应用任何更改。',
|
|
5451
|
+
'unsupported-linked-cell': '当前区域包含坐标关联的超链接,尚不能安全地随排序移动。'
|
|
5452
|
+
};
|
|
5453
|
+
return {
|
|
5454
|
+
ok: false,
|
|
5455
|
+
code,
|
|
5456
|
+
message: messages[code]
|
|
5457
|
+
};
|
|
5458
|
+
}
|
|
5459
|
+
function normalizeSpreadsheetSortOwnedScope(value) {
|
|
5460
|
+
if (void 0 === value) return null;
|
|
5461
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) return null;
|
|
5462
|
+
if ('auto-filter' === value.kind && true === value.hasHeader && Object.keys(value).every((key)=>'kind' === key || 'hasHeader' === key)) return {
|
|
5463
|
+
kind: 'auto-filter',
|
|
5464
|
+
hasHeader: true
|
|
5465
|
+
};
|
|
5466
|
+
if ('table' === value.kind && 'string' == typeof value.tableId && Boolean(value.tableId.trim()) && 'boolean' == typeof value.hasHeader && Object.keys(value).every((key)=>'kind' === key || 'tableId' === key || 'hasHeader' === key)) return {
|
|
5467
|
+
kind: 'table',
|
|
5468
|
+
tableId: value.tableId,
|
|
5469
|
+
hasHeader: value.hasHeader
|
|
5470
|
+
};
|
|
5471
|
+
return null;
|
|
5472
|
+
}
|
|
5473
|
+
function spreadsheetSortRangeError(range) {
|
|
5474
|
+
const area = spreadsheetCellRangeArea(range);
|
|
5475
|
+
if (!Number.isSafeInteger(area) || area > MAX_SPREADSHEET_SORT_CELLS) return spreadsheetSortError('range-too-large');
|
|
5476
|
+
return null;
|
|
5477
|
+
}
|
|
5478
|
+
function spreadsheetSortRowsHaveHeader(rows) {
|
|
5479
|
+
const first = rows[0];
|
|
5480
|
+
if (!first?.length) return false;
|
|
5481
|
+
const labels = first.map(spreadsheetSortHeaderText);
|
|
5482
|
+
return labels.every(Boolean) && new Set(labels.map((label)=>label.toLocaleLowerCase())).size === labels.length;
|
|
5483
|
+
}
|
|
5484
|
+
function appendSpreadsheetSortKey(keys, key) {
|
|
5485
|
+
const identity = spreadsheetSortKeyIdentity(key);
|
|
5486
|
+
if (keys.some((candidate)=>spreadsheetSortKeyIdentity(candidate) === identity)) return false;
|
|
5487
|
+
keys.push(key);
|
|
5488
|
+
return true;
|
|
5489
|
+
}
|
|
5490
|
+
function spreadsheetSortKeyIdentity(key) {
|
|
5491
|
+
if ('cell-color' === key.sortOn || 'font-color' === key.sortOn) return `${key.index}:${spreadsheetSortAppearanceTargetValue({
|
|
5492
|
+
kind: key.sortOn,
|
|
5493
|
+
color: key.color
|
|
5494
|
+
})}`;
|
|
5495
|
+
if ('icon' === key.sortOn) return `${key.index}:${spreadsheetSortAppearanceTargetValue({
|
|
5496
|
+
kind: 'icon',
|
|
5497
|
+
icon: key.icon
|
|
5498
|
+
})}`;
|
|
5499
|
+
return `${key.index}:values`;
|
|
5500
|
+
}
|
|
5501
|
+
function spreadsheetSortActiveIndex(active, range) {
|
|
5502
|
+
return active >= range[0] && active <= range[1] ? active : range[0];
|
|
5503
|
+
}
|
|
5504
|
+
function spreadsheetSortHeaderText(cell) {
|
|
5505
|
+
if (cell?.f || 'string' != typeof cell?.v) return '';
|
|
5506
|
+
const value = cell.v;
|
|
5507
|
+
return Array.from(value.trim()).slice(0, MAX_SPREADSHEET_SORT_HEADER_LENGTH).join('');
|
|
5508
|
+
}
|
|
5509
|
+
function spreadsheetSortColumnLabel(column) {
|
|
5510
|
+
return formatSpreadsheetCellRanges([
|
|
5511
|
+
{
|
|
5512
|
+
row: [
|
|
5513
|
+
0,
|
|
5514
|
+
0
|
|
5515
|
+
],
|
|
5516
|
+
column: [
|
|
5517
|
+
column,
|
|
5518
|
+
column
|
|
5519
|
+
]
|
|
5520
|
+
}
|
|
5521
|
+
]).replace(/\d+$/, '');
|
|
5522
|
+
}
|
|
5523
|
+
function reconcileSpreadsheetFiltersAfterFortune(sheets, sourceSheets, operations = [], context = {
|
|
5524
|
+
now: new Date()
|
|
5525
|
+
}) {
|
|
5526
|
+
const coordinates = spreadsheetCellOperationCoordinates(operations);
|
|
5527
|
+
return sheets.map((sheet, index)=>{
|
|
5528
|
+
const source = sourceSheet(sheet, sourceSheets, index);
|
|
5529
|
+
if (!source || !spreadsheetSheetHasCriteria(source) || !spreadsheetFilterReconciliationIsBounded(source) || !spreadsheetFilterRangesWereEdited(source, operations, coordinates)) return sheet;
|
|
5530
|
+
const previousTableOwnedRows = spreadsheetTableFilterOwnedRows(source, context);
|
|
5531
|
+
const manuallyHiddenRows = workSpreadsheetAutoFilterManuallyHiddenRows(source);
|
|
5532
|
+
for (const row of previousTableOwnedRows)manuallyHiddenRows.delete(row);
|
|
5533
|
+
let next = sheet;
|
|
5534
|
+
if (normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select) && workSpreadsheetAutoFilterCriteriaEntries(source).length) next = workSpreadsheetSheetWithReappliedAutoFilterCriteria(sheet, source, manuallyHiddenRows, context);
|
|
5535
|
+
const hiddenRows = new Set(manuallyHiddenRows);
|
|
5536
|
+
for (const row of workSpreadsheetAutoFilterOwnedRows(next))hiddenRows.add(row);
|
|
5537
|
+
for (const row of spreadsheetTableFilterOwnedRows(next, context))hiddenRows.add(row);
|
|
5538
|
+
const rowhidden = Object.fromEntries([
|
|
5539
|
+
...hiddenRows
|
|
5540
|
+
].sort(compareSpreadsheetRowKeys).map((row)=>[
|
|
5541
|
+
row,
|
|
5542
|
+
0
|
|
5543
|
+
]));
|
|
5544
|
+
return {
|
|
5545
|
+
...next,
|
|
5546
|
+
config: {
|
|
5547
|
+
...next.config ?? {},
|
|
5548
|
+
rowhidden
|
|
5549
|
+
}
|
|
5550
|
+
};
|
|
5551
|
+
});
|
|
5552
|
+
}
|
|
5553
|
+
function reconcileSpreadsheetFiltersAfterSort(sheet, source, range, sourceIndexes, context = {
|
|
5554
|
+
now: new Date()
|
|
5555
|
+
}) {
|
|
5556
|
+
if (!spreadsheetFilterReconciliationIsBounded(source) || !spreadsheetSortSourceIndexesAreValid(range, sourceIndexes)) return null;
|
|
5557
|
+
const reconciled = reconcileSpreadsheetFiltersAfterFortune([
|
|
5558
|
+
sheet
|
|
5559
|
+
], [
|
|
5560
|
+
source
|
|
5561
|
+
], [], context)[0];
|
|
5562
|
+
if (!reconciled) return null;
|
|
5563
|
+
let filter = reconciled.filter;
|
|
5564
|
+
const autoFilter = normalizedWorkSpreadsheetAutoFilterRange(source.filter_select);
|
|
5565
|
+
if (autoFilter && autoFilter.row[0] === range.row[0] && autoFilter.row[1] === range.row[1] && autoFilter.column[0] === range.column[0] && autoFilter.column[1] === range.column[1]) filter = spreadsheetFilterWithRemappedOpaqueEntries(reconciled, source, range, sourceIndexes);
|
|
5566
|
+
const manuallyHiddenRows = workSpreadsheetAutoFilterManuallyHiddenRows(source);
|
|
5567
|
+
for (const row of spreadsheetTableFilterOwnedRows(source, context))manuallyHiddenRows.delete(row);
|
|
5568
|
+
const withFilter = {
|
|
5569
|
+
...reconciled,
|
|
5570
|
+
filter
|
|
5571
|
+
};
|
|
5572
|
+
const hiddenRows = new Set(manuallyHiddenRows);
|
|
5573
|
+
for (const row of workSpreadsheetAutoFilterOwnedRows(withFilter))hiddenRows.add(row);
|
|
5574
|
+
for (const row of spreadsheetTableFilterOwnedRows(withFilter, context))hiddenRows.add(row);
|
|
5575
|
+
const rowhidden = Object.fromEntries([
|
|
5576
|
+
...hiddenRows
|
|
5577
|
+
].sort(compareSpreadsheetRowKeys).map((row)=>[
|
|
5578
|
+
row,
|
|
5579
|
+
0
|
|
5580
|
+
]));
|
|
5581
|
+
return {
|
|
5582
|
+
...sheet,
|
|
5583
|
+
filter,
|
|
5584
|
+
config: {
|
|
5585
|
+
...sheet.config ?? {},
|
|
5586
|
+
rowhidden
|
|
5587
|
+
}
|
|
5588
|
+
};
|
|
5589
|
+
}
|
|
5590
|
+
function spreadsheetFilterReconciliationIsBounded(sheet) {
|
|
5591
|
+
let cellVisits = 0;
|
|
5592
|
+
const autoFilter = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
5593
|
+
if (autoFilter) {
|
|
5594
|
+
const filterCount = Object.keys(sheet.filter ?? {}).length;
|
|
5595
|
+
cellVisits += (autoFilter.row[1] - autoFilter.row[0]) * Math.max(filterCount, workSpreadsheetAutoFilterCriteriaEntries(sheet).length);
|
|
5596
|
+
}
|
|
5597
|
+
for (const table of sheet.tables ?? []){
|
|
5598
|
+
const range = spreadsheetTableFilterRange(table);
|
|
5599
|
+
if (range) {
|
|
5600
|
+
cellVisits += (range.row[1] - range.row[0]) * table.filters.length;
|
|
5601
|
+
if (!Number.isSafeInteger(cellVisits) || cellVisits > MAX_SPREADSHEET_SORT_CELLS) return false;
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
return Number.isSafeInteger(cellVisits) && cellVisits <= MAX_SPREADSHEET_SORT_CELLS;
|
|
5605
|
+
}
|
|
5606
|
+
function spreadsheetFilterWithRemappedOpaqueEntries(reconciled, source, range, sourceIndexes) {
|
|
5607
|
+
const next = {
|
|
5608
|
+
...reconciled.filter ?? {}
|
|
5609
|
+
};
|
|
5610
|
+
const typedColumns = new Set(workSpreadsheetAutoFilterCriteriaEntries(source).map((entry)=>String(entry.column)));
|
|
5611
|
+
for (const [column, entry] of Object.entries(source.filter ?? {}))if (!typedColumns.has(column) && spreadsheet_filter_reconciliation_isRecord(entry)) next[column] = {
|
|
5612
|
+
...entry,
|
|
5613
|
+
rowhidden: spreadsheetRemappedFilterRows(entry.rowhidden, range, sourceIndexes)
|
|
5614
|
+
};
|
|
5615
|
+
return next;
|
|
5616
|
+
}
|
|
5617
|
+
function spreadsheetRemappedFilterRows(value, range, sourceIndexes) {
|
|
5618
|
+
const sourceRows = spreadsheet_filter_reconciliation_isRecord(value) ? value : {};
|
|
5619
|
+
const rowhidden = {};
|
|
5620
|
+
const dataStart = range.row[0] + 1;
|
|
5621
|
+
sourceIndexes.forEach((sourceIndex, targetIndex)=>{
|
|
5622
|
+
if (Object.hasOwn(sourceRows, String(dataStart + sourceIndex))) rowhidden[String(dataStart + targetIndex)] = 0;
|
|
5623
|
+
});
|
|
5624
|
+
return rowhidden;
|
|
5625
|
+
}
|
|
5626
|
+
function spreadsheetSortSourceIndexesAreValid(range, sourceIndexes) {
|
|
5627
|
+
const count = range.row[1] - range.row[0];
|
|
5628
|
+
return sourceIndexes.length === count && sourceIndexes.every((index)=>Number.isSafeInteger(index) && index >= 0 && index < count) && new Set(sourceIndexes).size === count;
|
|
5629
|
+
}
|
|
5630
|
+
function spreadsheetSheetHasCriteria(sheet) {
|
|
5631
|
+
return Boolean(workSpreadsheetAutoFilterCriteriaEntries(sheet).length || sheet.tables?.some((table)=>table.filters.length));
|
|
5632
|
+
}
|
|
5633
|
+
function spreadsheetFilterRangesWereEdited(sheet, operations, coordinates) {
|
|
5634
|
+
if (!operations.length) return true;
|
|
5635
|
+
if (!sheet.id) return false;
|
|
5636
|
+
const sheetCoordinates = coordinates?.get(sheet.id);
|
|
5637
|
+
if (sheetCoordinates) {
|
|
5638
|
+
const ranges = spreadsheetFilterRanges(sheet);
|
|
5639
|
+
return [
|
|
5640
|
+
...sheetCoordinates.values()
|
|
5641
|
+
].some(({ column, row })=>ranges.some((range)=>row >= range.row[0] && row <= range.row[1] && column >= range.column[0] && column <= range.column[1]));
|
|
5642
|
+
}
|
|
5643
|
+
return operations.some((operation)=>operation.id === sheet.id);
|
|
5644
|
+
}
|
|
5645
|
+
function spreadsheetFilterRanges(sheet) {
|
|
5646
|
+
const ranges = [];
|
|
5647
|
+
const autoFilter = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
5648
|
+
if (autoFilter && workSpreadsheetAutoFilterCriteriaEntries(sheet).length) ranges.push(autoFilter);
|
|
5649
|
+
for (const table of sheet.tables ?? []){
|
|
5650
|
+
if (!table.filters.length) continue;
|
|
5651
|
+
const range = spreadsheetTableFilterRange(table);
|
|
5652
|
+
if (range) ranges.push(range);
|
|
5653
|
+
}
|
|
5654
|
+
return ranges;
|
|
5655
|
+
}
|
|
5656
|
+
function spreadsheetTableFilterOwnedRows(sheet, context) {
|
|
5657
|
+
const hidden = new Set();
|
|
5658
|
+
for (const table of sheet.tables ?? []){
|
|
5659
|
+
const range = spreadsheetTableFilterRange(table);
|
|
5660
|
+
if (range) for (const filter of table.filters){
|
|
5661
|
+
const column = range.column[0] + filter.column;
|
|
5662
|
+
const rowhidden = workSpreadsheetFilterHiddenRows(sheet, range, column, filter.criteria, context);
|
|
5663
|
+
for (const row of Object.keys(rowhidden ?? {}))hidden.add(row);
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5666
|
+
return hidden;
|
|
5667
|
+
}
|
|
5668
|
+
function spreadsheetTableFilterRange(table) {
|
|
5669
|
+
if (!table.headerRow || !table.filters.length) return null;
|
|
5670
|
+
const rowStart = spreadsheet_filter_reconciliation_finiteIndex(table.range.row[0]);
|
|
5671
|
+
const rowEnd = spreadsheet_filter_reconciliation_finiteIndex(table.range.row[1]);
|
|
5672
|
+
const columnStart = spreadsheet_filter_reconciliation_finiteIndex(table.range.column[0]);
|
|
5673
|
+
const columnEnd = spreadsheet_filter_reconciliation_finiteIndex(table.range.column[1]);
|
|
5674
|
+
if (null === rowStart || null === rowEnd || null === columnStart || null === columnEnd) return null;
|
|
5675
|
+
const end = Math.max(rowStart, rowEnd) - Number(table.totalsRow);
|
|
5676
|
+
const start = Math.min(rowStart, rowEnd);
|
|
5677
|
+
if (end <= start) return null;
|
|
5678
|
+
return {
|
|
5679
|
+
row: [
|
|
5680
|
+
start,
|
|
5681
|
+
end
|
|
5682
|
+
],
|
|
5683
|
+
column: [
|
|
5684
|
+
Math.min(columnStart, columnEnd),
|
|
5685
|
+
Math.max(columnStart, columnEnd)
|
|
5686
|
+
]
|
|
5687
|
+
};
|
|
5688
|
+
}
|
|
5689
|
+
function sourceSheet(sheet, sources, index) {
|
|
5690
|
+
return sources.find((candidate)=>candidate.id === sheet.id) ?? sources[index];
|
|
5691
|
+
}
|
|
5692
|
+
function spreadsheet_filter_reconciliation_finiteIndex(value) {
|
|
5693
|
+
return 'number' == typeof value && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
5694
|
+
}
|
|
5695
|
+
function compareSpreadsheetRowKeys(left, right) {
|
|
5696
|
+
return Number(left) - Number(right);
|
|
5697
|
+
}
|
|
5698
|
+
function spreadsheet_filter_reconciliation_isRecord(value) {
|
|
5699
|
+
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
5700
|
+
}
|
|
5701
|
+
function canApplySpreadsheetTableStructureChange(sheet, change) {
|
|
5702
|
+
if (!sheet || !validSpreadsheetTableStructureChange(change)) return false;
|
|
5703
|
+
for (const table of sheet.tables ?? []){
|
|
5704
|
+
if ('delete' === change.kind && 'row' === change.axis && spreadsheetTableDeleteRemovesSemanticRow(table, change.start, change.end)) return false;
|
|
5705
|
+
if (!transformSpreadsheetTableStructure(table, change)) return false;
|
|
5706
|
+
}
|
|
5707
|
+
return true;
|
|
5708
|
+
}
|
|
5709
|
+
function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations = []) {
|
|
5710
|
+
return sheets.map((sheet, index)=>{
|
|
5711
|
+
const source = sourceSheetForReconciliation(sheet, sourceSheets, index);
|
|
5712
|
+
if (!source?.tables?.length) return sheet;
|
|
5713
|
+
const sheetOperations = operations.filter((operation)=>operation.id === source.id);
|
|
5714
|
+
let tables = source.tables;
|
|
5715
|
+
const reconcileColumns = new Set();
|
|
5716
|
+
const calculatedColumnRows = new Map();
|
|
5717
|
+
const editedTotalsColumns = new Map();
|
|
5718
|
+
if (!operations.length) for (const table of tables)reconcileColumns.add(table.id);
|
|
5719
|
+
for (const operation of sheetOperations){
|
|
5720
|
+
const structure = spreadsheetTableStructureChangeFromOperation(operation);
|
|
5721
|
+
if (structure) {
|
|
5722
|
+
transformCalculatedColumnRows(calculatedColumnRows, structure);
|
|
5723
|
+
const transformed = [];
|
|
5724
|
+
for (const table of tables){
|
|
5725
|
+
if ('row' === structure.axis && 'insert' === structure.kind) {
|
|
5726
|
+
const insertedRows = rowsInsertedIntoSpreadsheetTableBody(table, structure);
|
|
5727
|
+
if (insertedRows.length) {
|
|
5728
|
+
const rows = calculatedColumnRows.get(table.id) ?? new Set();
|
|
5729
|
+
for (const row of insertedRows)rows.add(row);
|
|
5730
|
+
calculatedColumnRows.set(table.id, rows);
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
const next = transformSpreadsheetTableStructure(table, structure);
|
|
5734
|
+
if (next) {
|
|
5735
|
+
transformed.push(next);
|
|
5736
|
+
if (next !== table) reconcileColumns.add(table.id);
|
|
5737
|
+
}
|
|
5738
|
+
}
|
|
5739
|
+
tables = transformed;
|
|
5740
|
+
continue;
|
|
5741
|
+
}
|
|
5742
|
+
const coordinate = spreadsheetCellCoordinateFromOperation(operation);
|
|
5743
|
+
if (!coordinate) continue;
|
|
5744
|
+
const table = tables.find((candidate)=>spreadsheetTableContainsCell(candidate, coordinate.row, coordinate.column));
|
|
5745
|
+
if (table) {
|
|
5746
|
+
reconcileColumns.add(table.id);
|
|
5747
|
+
if (table.totalsRow && coordinate.row === table.range.row[1]) {
|
|
5748
|
+
const offset = coordinate.column - table.range.column[0];
|
|
5749
|
+
if (offset >= 0 && offset < table.columns.length) {
|
|
5750
|
+
const offsets = editedTotalsColumns.get(table.id) ?? new Set();
|
|
5751
|
+
offsets.add(offset);
|
|
5752
|
+
editedTotalsColumns.set(table.id, offsets);
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
const updates = [];
|
|
5758
|
+
const readCell = spreadsheet_table_reconciliation_spreadsheetSheetCellReader(sheet);
|
|
5759
|
+
tables = tables.map((table)=>{
|
|
5760
|
+
if (!reconcileColumns.has(table.id)) return table;
|
|
5761
|
+
const columns = canonicalSpreadsheetTableColumns(table, readCell);
|
|
5762
|
+
const calculatedColumns = reconcileSpreadsheetTableCalculatedColumns(sheet, {
|
|
5763
|
+
...table,
|
|
5764
|
+
columns
|
|
5765
|
+
});
|
|
5766
|
+
const reconciledColumns = reconcileSpreadsheetTableTotalsColumns(sheet, {
|
|
5767
|
+
...table,
|
|
5768
|
+
columns: calculatedColumns
|
|
5769
|
+
}, editedTotalsColumns.get(table.id));
|
|
5770
|
+
if (table.headerRow) for (const [offset, column] of reconciledColumns.entries()){
|
|
5771
|
+
const cellColumn = table.range.column[0] + offset;
|
|
5772
|
+
if (spreadsheetCellText(readCell(table.range.row[0], cellColumn)) !== column.name) updates.push({
|
|
5773
|
+
column: cellColumn,
|
|
5774
|
+
name: column.name,
|
|
5775
|
+
row: table.range.row[0]
|
|
5776
|
+
});
|
|
5777
|
+
}
|
|
5778
|
+
return sameSpreadsheetTableColumns(table.columns, reconciledColumns) ? table : {
|
|
5779
|
+
...table,
|
|
5780
|
+
columns: reconciledColumns
|
|
5781
|
+
};
|
|
5782
|
+
});
|
|
5783
|
+
let withCalculatedColumns = sheet;
|
|
5784
|
+
for (const table of tables){
|
|
5785
|
+
const rows = calculatedColumnRows.get(table.id);
|
|
5786
|
+
if (rows?.size) withCalculatedColumns = fillSpreadsheetTableCalculatedColumns(withCalculatedColumns, table, [
|
|
5787
|
+
...rows
|
|
5788
|
+
]);
|
|
5789
|
+
}
|
|
5790
|
+
const withHeaders = stampSpreadsheetTableHeaderCells(withCalculatedColumns, updates);
|
|
5791
|
+
let withTotals = withHeaders;
|
|
5792
|
+
for (const table of tables){
|
|
5793
|
+
const previous = source.tables.find((candidate)=>candidate.id === table.id);
|
|
5794
|
+
withTotals = synchronizeSpreadsheetTableTotalsRow(withTotals, previous, table);
|
|
5795
|
+
}
|
|
5796
|
+
return {
|
|
5797
|
+
...withTotals,
|
|
5798
|
+
tables: tables.length ? tables : void 0
|
|
5799
|
+
};
|
|
5800
|
+
});
|
|
5801
|
+
}
|
|
5802
|
+
function transformSpreadsheetTableStructure(table, change) {
|
|
5803
|
+
if (!validSpreadsheetTableStructureChange(change)) return null;
|
|
5804
|
+
const axis = 'row' === change.axis ? table.range.row : table.range.column;
|
|
5805
|
+
const transformed = 'insert' === change.kind ? transformSpreadsheetTableAxisForInsertion(axis, change) : transformSpreadsheetTableAxisForDeletion(axis, change);
|
|
5806
|
+
if (!transformed) return null;
|
|
5807
|
+
const range = {
|
|
5808
|
+
row: 'row' === change.axis ? transformed.axis : [
|
|
5809
|
+
...table.range.row
|
|
5810
|
+
],
|
|
5811
|
+
column: 'column' === change.axis ? transformed.axis : [
|
|
5812
|
+
...table.range.column
|
|
5813
|
+
]
|
|
5814
|
+
};
|
|
5815
|
+
const height = range.row[1] - range.row[0] + 1;
|
|
5816
|
+
if (height <= Number(table.headerRow) + Number(table.totalsRow)) return null;
|
|
5817
|
+
if ('column' !== change.axis || !transformed.inside) {
|
|
5818
|
+
if (range.row[0] === table.range.row[0] && range.row[1] === table.range.row[1] && range.column[0] === table.range.column[0] && range.column[1] === table.range.column[1]) return table;
|
|
5819
|
+
return {
|
|
5820
|
+
...table,
|
|
5821
|
+
range
|
|
5822
|
+
};
|
|
5823
|
+
}
|
|
5824
|
+
if ('insert' === change.kind) {
|
|
5825
|
+
const columns = [
|
|
5826
|
+
...table.columns
|
|
5827
|
+
];
|
|
5828
|
+
columns.splice(transformed.offset, 0, ...Array.from({
|
|
5829
|
+
length: change.count
|
|
5830
|
+
}, ()=>({
|
|
5831
|
+
name: ''
|
|
5832
|
+
})));
|
|
5833
|
+
return {
|
|
5834
|
+
...table,
|
|
5835
|
+
range,
|
|
5836
|
+
columns,
|
|
5837
|
+
filters: table.filters.map((filter)=>filter.column >= transformed.offset ? {
|
|
5838
|
+
...filter,
|
|
5839
|
+
column: filter.column + change.count
|
|
5840
|
+
} : filter)
|
|
5841
|
+
};
|
|
5842
|
+
}
|
|
5843
|
+
if (!('removed' in transformed)) return null;
|
|
5844
|
+
const removed = transformed.removed;
|
|
5845
|
+
if ('number' != typeof removed) return null;
|
|
5846
|
+
const columns = [
|
|
5847
|
+
...table.columns
|
|
5848
|
+
];
|
|
5849
|
+
columns.splice(transformed.offset, removed);
|
|
5850
|
+
if (!columns.length) return null;
|
|
5851
|
+
return {
|
|
5852
|
+
...table,
|
|
5853
|
+
range,
|
|
5854
|
+
columns,
|
|
5855
|
+
filters: filtersAfterSpreadsheetTableColumnDeletion(table.filters, transformed.offset, removed)
|
|
5856
|
+
};
|
|
5857
|
+
}
|
|
5858
|
+
function spreadsheetTableContainsCell(table, row, column) {
|
|
5859
|
+
return row >= table.range.row[0] && row <= table.range.row[1] && column >= table.range.column[0] && column <= table.range.column[1];
|
|
5860
|
+
}
|
|
5861
|
+
function rowsInsertedIntoSpreadsheetTableBody(table, change) {
|
|
5862
|
+
if ('row' !== change.axis) return [];
|
|
5863
|
+
const transformed = transformSpreadsheetTableAxisForInsertion(table.range.row, change);
|
|
5864
|
+
if (!transformed.inside) return [];
|
|
5865
|
+
const insertion = change.index + ('rightbottom' === change.direction ? 1 : 0);
|
|
5866
|
+
const bodyStart = table.range.row[0] + Number(table.headerRow);
|
|
5867
|
+
if (insertion < bodyStart) return [];
|
|
5868
|
+
return Array.from({
|
|
5869
|
+
length: change.count
|
|
5870
|
+
}, (_, offset)=>insertion + offset);
|
|
5871
|
+
}
|
|
5872
|
+
function transformCalculatedColumnRows(rowsByTable, change) {
|
|
5873
|
+
if ('row' !== change.axis || !rowsByTable.size) return;
|
|
5874
|
+
if ('insert' === change.kind) {
|
|
5875
|
+
const insertion = change.index + ('rightbottom' === change.direction ? 1 : 0);
|
|
5876
|
+
for (const [tableId, rows] of rowsByTable){
|
|
5877
|
+
const next = new Set();
|
|
5878
|
+
for (const row of rows)next.add(row >= insertion ? row + change.count : row);
|
|
5879
|
+
rowsByTable.set(tableId, next);
|
|
5880
|
+
}
|
|
5881
|
+
return;
|
|
5882
|
+
}
|
|
5883
|
+
const count = change.end - change.start + 1;
|
|
5884
|
+
for (const [tableId, rows] of rowsByTable){
|
|
5885
|
+
const next = new Set();
|
|
5886
|
+
for (const row of rows)if (row < change.start) next.add(row);
|
|
5887
|
+
else if (row > change.end) next.add(row - count);
|
|
5888
|
+
rowsByTable.set(tableId, next);
|
|
5889
|
+
}
|
|
5890
|
+
}
|
|
5891
|
+
function transformSpreadsheetTableAxisForInsertion(axis, change) {
|
|
5892
|
+
const start = axis[0] ?? 0;
|
|
5893
|
+
const end = axis[1] ?? start;
|
|
5894
|
+
const insertion = change.index + ('rightbottom' === change.direction ? 1 : 0);
|
|
5895
|
+
if (insertion <= start) return {
|
|
5896
|
+
axis: [
|
|
5897
|
+
start + change.count,
|
|
5898
|
+
end + change.count
|
|
5899
|
+
],
|
|
5900
|
+
inside: false,
|
|
5901
|
+
offset: 0
|
|
5902
|
+
};
|
|
5903
|
+
if (insertion <= end) return {
|
|
5904
|
+
axis: [
|
|
5905
|
+
start,
|
|
5906
|
+
end + change.count
|
|
5907
|
+
],
|
|
5908
|
+
inside: true,
|
|
5909
|
+
offset: insertion - start
|
|
5910
|
+
};
|
|
5911
|
+
return {
|
|
5912
|
+
axis: [
|
|
5913
|
+
start,
|
|
5914
|
+
end
|
|
5915
|
+
],
|
|
5916
|
+
inside: false,
|
|
5917
|
+
offset: 0
|
|
5918
|
+
};
|
|
5919
|
+
}
|
|
5920
|
+
function transformSpreadsheetTableAxisForDeletion(axis, change) {
|
|
5921
|
+
const start = axis[0] ?? 0;
|
|
5922
|
+
const end = axis[1] ?? start;
|
|
5923
|
+
const count = change.end - change.start + 1;
|
|
5924
|
+
if (change.end < start) return {
|
|
5925
|
+
axis: [
|
|
5926
|
+
start - count,
|
|
5927
|
+
end - count
|
|
5928
|
+
],
|
|
5929
|
+
inside: false,
|
|
5930
|
+
offset: 0,
|
|
5931
|
+
removed: 0
|
|
5932
|
+
};
|
|
5933
|
+
if (change.start > end) return {
|
|
5934
|
+
axis: [
|
|
5935
|
+
start,
|
|
5936
|
+
end
|
|
5937
|
+
],
|
|
5938
|
+
inside: false,
|
|
5939
|
+
offset: 0,
|
|
5940
|
+
removed: 0
|
|
5941
|
+
};
|
|
5942
|
+
const overlapStart = Math.max(start, change.start);
|
|
5943
|
+
const overlapEnd = Math.min(end, change.end);
|
|
5944
|
+
const removed = overlapEnd - overlapStart + 1;
|
|
5945
|
+
const remaining = end - start + 1 - removed;
|
|
5946
|
+
if (remaining <= 0) return null;
|
|
5947
|
+
const deletedBeforeStart = Math.max(0, Math.min(change.end, start - 1) - change.start + 1);
|
|
5948
|
+
const nextStart = start - deletedBeforeStart;
|
|
5949
|
+
return {
|
|
5950
|
+
axis: [
|
|
5951
|
+
nextStart,
|
|
5952
|
+
nextStart + remaining - 1
|
|
5953
|
+
],
|
|
5954
|
+
inside: true,
|
|
5955
|
+
offset: overlapStart - start,
|
|
5956
|
+
removed
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
function filtersAfterSpreadsheetTableColumnDeletion(filters, offset, removed) {
|
|
5960
|
+
const end = offset + removed - 1;
|
|
5961
|
+
return filters.flatMap((filter)=>{
|
|
5962
|
+
if (filter.column >= offset && filter.column <= end) return [];
|
|
5963
|
+
return [
|
|
5964
|
+
filter.column > end ? {
|
|
5965
|
+
...filter,
|
|
5966
|
+
column: filter.column - removed
|
|
5967
|
+
} : filter
|
|
5968
|
+
];
|
|
5969
|
+
});
|
|
5970
|
+
}
|
|
5971
|
+
function canonicalSpreadsheetTableColumns(table, readCell) {
|
|
5972
|
+
const width = table.range.column[1] - table.range.column[0] + 1;
|
|
5973
|
+
const observed = new Set();
|
|
5974
|
+
return Array.from({
|
|
5975
|
+
length: width
|
|
5976
|
+
}, (_, offset)=>{
|
|
5977
|
+
const raw = table.headerRow ? spreadsheetCellText(readCell(table.range.row[0], table.range.column[0] + offset)).trim() : table.columns[offset]?.name.trim() ?? '';
|
|
5978
|
+
const base = validSpreadsheetTableColumnName(raw) ? raw : `Column${offset + 1}`;
|
|
5979
|
+
let candidate = base;
|
|
5980
|
+
let suffix = 2;
|
|
5981
|
+
while(observed.has(candidate.toLocaleLowerCase())){
|
|
5982
|
+
const suffixText = String(suffix);
|
|
5983
|
+
candidate = `${truncateSpreadsheetTableColumnName(base, 255 - suffixText.length)}${suffixText}`;
|
|
5984
|
+
suffix += 1;
|
|
5985
|
+
}
|
|
5986
|
+
observed.add(candidate.toLocaleLowerCase());
|
|
5987
|
+
return {
|
|
5988
|
+
...table.columns[offset] ?? {},
|
|
5989
|
+
name: candidate
|
|
5990
|
+
};
|
|
5991
|
+
});
|
|
5992
|
+
}
|
|
5993
|
+
function spreadsheetTableDeleteRemovesSemanticRow(table, start, end) {
|
|
5994
|
+
return Boolean(table.headerRow && start <= table.range.row[0] && end >= table.range.row[0] || table.totalsRow && start <= table.range.row[1] && end >= table.range.row[1]);
|
|
5995
|
+
}
|
|
5996
|
+
function spreadsheetTableStructureChangeFromOperation(operation) {
|
|
5997
|
+
if (!spreadsheet_table_reconciliation_isRecord(operation.value)) return null;
|
|
5998
|
+
if ('insertRowCol' === operation.op) {
|
|
5999
|
+
const { count, direction, index, type } = operation.value;
|
|
6000
|
+
const change = {
|
|
6001
|
+
axis: 'column' === type ? 'column' : 'row',
|
|
6002
|
+
count: Number(count),
|
|
6003
|
+
direction: 'rightbottom' === direction ? 'rightbottom' : 'lefttop',
|
|
6004
|
+
index: Number(index),
|
|
6005
|
+
kind: 'insert'
|
|
6006
|
+
};
|
|
6007
|
+
return ('column' === type || 'row' === type) && ('lefttop' === direction || 'rightbottom' === direction) && validSpreadsheetTableStructureChange(change) ? change : null;
|
|
6008
|
+
}
|
|
6009
|
+
if ('deleteRowCol' === operation.op) {
|
|
6010
|
+
const { end, start, type } = operation.value;
|
|
6011
|
+
const change = {
|
|
6012
|
+
axis: 'column' === type ? 'column' : 'row',
|
|
6013
|
+
end: Number(end),
|
|
6014
|
+
kind: 'delete',
|
|
6015
|
+
start: Number(start)
|
|
6016
|
+
};
|
|
6017
|
+
return ('column' === type || 'row' === type) && validSpreadsheetTableStructureChange(change) ? change : null;
|
|
6018
|
+
}
|
|
6019
|
+
return null;
|
|
6020
|
+
}
|
|
6021
|
+
function spreadsheetCellCoordinateFromOperation(operation) {
|
|
6022
|
+
if ('data' !== operation.path[0] || operation.path.length < 3) return null;
|
|
6023
|
+
const row = operation.path[1];
|
|
6024
|
+
const column = operation.path[2];
|
|
6025
|
+
return Number.isSafeInteger(row) && Number(row) >= 0 && Number.isSafeInteger(column) && Number(column) >= 0 ? {
|
|
6026
|
+
column: Number(column),
|
|
6027
|
+
row: Number(row)
|
|
6028
|
+
} : null;
|
|
6029
|
+
}
|
|
6030
|
+
function validSpreadsheetTableStructureChange(change) {
|
|
6031
|
+
if ('insert' === change.kind) return Number.isSafeInteger(change.index) && change.index >= 0 && Number.isSafeInteger(change.count) && change.count > 0;
|
|
6032
|
+
return Number.isSafeInteger(change.start) && change.start >= 0 && Number.isSafeInteger(change.end) && change.end >= change.start;
|
|
6033
|
+
}
|
|
6034
|
+
function sourceSheetForReconciliation(sheet, sourceSheets, index) {
|
|
6035
|
+
return (sheet.id ? sourceSheets.find((candidate)=>candidate.id === sheet.id) : void 0) ?? sourceSheets[index];
|
|
6036
|
+
}
|
|
6037
|
+
function spreadsheet_table_reconciliation_spreadsheetSheetCellReader(sheet) {
|
|
6038
|
+
if (void 0 !== sheet.data) return (row, column)=>sheet.data?.[row]?.[column] ?? null;
|
|
6039
|
+
const cells = new Map((sheet.celldata ?? []).map((entry)=>[
|
|
6040
|
+
`${entry.r}:${entry.c}`,
|
|
6041
|
+
entry.v
|
|
6042
|
+
]));
|
|
6043
|
+
return (row, column)=>cells.get(`${row}:${column}`) ?? null;
|
|
6044
|
+
}
|
|
6045
|
+
function stampSpreadsheetTableHeaderCells(sheet, updates) {
|
|
6046
|
+
if (!updates.length) return sheet;
|
|
6047
|
+
if (void 0 !== sheet.data) {
|
|
6048
|
+
const data = [];
|
|
6049
|
+
data.length = sheet.data.length;
|
|
6050
|
+
for (const [row, value] of spreadsheet_sparse_sparseArrayEntries(sheet.data))data[row] = value;
|
|
6051
|
+
const mutableRows = new Map();
|
|
6052
|
+
for (const update of updates){
|
|
6053
|
+
let row = mutableRows.get(update.row);
|
|
6054
|
+
if (!row) {
|
|
6055
|
+
const source = data[update.row];
|
|
6056
|
+
row = [];
|
|
6057
|
+
row.length = source?.length ?? 0;
|
|
6058
|
+
for (const column of sparseArrayIndexes(source))row[column] = source?.[column];
|
|
6059
|
+
mutableRows.set(update.row, row);
|
|
6060
|
+
data[update.row] = row;
|
|
6061
|
+
}
|
|
6062
|
+
row[update.column] = stampSpreadsheetTableHeaderCell(row[update.column], update.name);
|
|
6063
|
+
}
|
|
6064
|
+
return {
|
|
6065
|
+
...sheet,
|
|
6066
|
+
data
|
|
6067
|
+
};
|
|
6068
|
+
}
|
|
6069
|
+
const celldata = [
|
|
6070
|
+
...sheet.celldata ?? []
|
|
6071
|
+
];
|
|
6072
|
+
const indexes = new Map(celldata.map((entry, index)=>[
|
|
6073
|
+
`${entry.r}:${entry.c}`,
|
|
6074
|
+
index
|
|
6075
|
+
]));
|
|
6076
|
+
for (const update of updates){
|
|
6077
|
+
const key = `${update.row}:${update.column}`;
|
|
6078
|
+
const index = indexes.get(key);
|
|
6079
|
+
const source = void 0 === index ? void 0 : celldata[index];
|
|
6080
|
+
const entry = {
|
|
6081
|
+
r: update.row,
|
|
6082
|
+
c: update.column,
|
|
6083
|
+
v: stampSpreadsheetTableHeaderCell(source?.v, update.name)
|
|
6084
|
+
};
|
|
6085
|
+
if (void 0 === index) {
|
|
6086
|
+
indexes.set(key, celldata.length);
|
|
6087
|
+
celldata.push(entry);
|
|
6088
|
+
} else celldata[index] = entry;
|
|
6089
|
+
}
|
|
6090
|
+
return {
|
|
6091
|
+
...sheet,
|
|
6092
|
+
celldata
|
|
6093
|
+
};
|
|
6094
|
+
}
|
|
6095
|
+
function stampSpreadsheetTableHeaderCell(cell, name) {
|
|
6096
|
+
return {
|
|
6097
|
+
...cell ?? {},
|
|
6098
|
+
m: name,
|
|
6099
|
+
v: name
|
|
6100
|
+
};
|
|
6101
|
+
}
|
|
6102
|
+
function spreadsheetCellText(cell) {
|
|
6103
|
+
const value = cell?.m ?? cell?.v;
|
|
6104
|
+
return null == value ? '' : String(value);
|
|
6105
|
+
}
|
|
6106
|
+
function validSpreadsheetTableColumnName(name) {
|
|
6107
|
+
const characters = Array.from(name);
|
|
6108
|
+
return name.trim() === name && characters.length >= 1 && characters.length <= 255 && !characters.some((character)=>/\p{Cc}/u.test(character) || '\uFFFE' === character || '\uFFFF' === character);
|
|
6109
|
+
}
|
|
6110
|
+
function truncateSpreadsheetTableColumnName(value, maximum) {
|
|
6111
|
+
return Array.from(value).slice(0, maximum).join('');
|
|
6112
|
+
}
|
|
6113
|
+
function sameSpreadsheetTableColumns(left, right) {
|
|
6114
|
+
return left.length === right.length && left.every((column, index)=>column.name === right[index]?.name && column.calculatedFormula === right[index]?.calculatedFormula && column.totalsFunction === right[index]?.totalsFunction && column.totalsLabel === right[index]?.totalsLabel && column.totalsFormula === right[index]?.totalsFormula);
|
|
6115
|
+
}
|
|
6116
|
+
function spreadsheet_table_reconciliation_isRecord(value) {
|
|
6117
|
+
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
6118
|
+
}
|
|
6119
|
+
function spreadsheetSelectionReference(selection) {
|
|
6120
|
+
const rowStart = Math.min(selection.row[0] ?? 0, selection.row[1] ?? selection.row[0] ?? 0);
|
|
6121
|
+
const rowEnd = Math.max(selection.row[0] ?? 0, selection.row[1] ?? selection.row[0] ?? 0);
|
|
6122
|
+
const columnStart = Math.min(selection.column[0] ?? 0, selection.column[1] ?? selection.column[0] ?? 0);
|
|
6123
|
+
const columnEnd = Math.max(selection.column[0] ?? 0, selection.column[1] ?? selection.column[0] ?? 0);
|
|
6124
|
+
const start = `${spreadsheetColumnLabel(columnStart)}${rowStart + 1}`;
|
|
6125
|
+
const end = `${spreadsheetColumnLabel(columnEnd)}${rowEnd + 1}`;
|
|
6126
|
+
return start === end ? start : `${start}:${end}`;
|
|
6127
|
+
}
|
|
6128
|
+
function spreadsheetSingleRange(selection) {
|
|
6129
|
+
return {
|
|
6130
|
+
row: finiteSpreadsheetSelectionAxis(selection.row),
|
|
6131
|
+
column: finiteSpreadsheetSelectionAxis(selection.column)
|
|
6132
|
+
};
|
|
6133
|
+
}
|
|
6134
|
+
function spreadsheet_editor_support_spreadsheetCellAt(sheet, row, column) {
|
|
6135
|
+
if (!sheet) return null;
|
|
6136
|
+
const safeRow = finiteSpreadsheetIndex(row, 0);
|
|
6137
|
+
const safeColumn = finiteSpreadsheetIndex(column, 0);
|
|
6138
|
+
return sheet.data?.[safeRow]?.[safeColumn] ?? sheet.celldata?.find((entry)=>entry.r === safeRow && entry.c === safeColumn)?.v ?? null;
|
|
6139
|
+
}
|
|
6140
|
+
function spreadsheetSelectionSummary(sheet, selection) {
|
|
6141
|
+
const summary = {
|
|
6142
|
+
average: null,
|
|
6143
|
+
nonEmptyCount: 0,
|
|
6144
|
+
numericCount: 0,
|
|
6145
|
+
sum: null
|
|
6146
|
+
};
|
|
6147
|
+
if (!sheet) return summary;
|
|
6148
|
+
const range = spreadsheetSingleRange(selection);
|
|
6149
|
+
let sum = 0;
|
|
6150
|
+
const collect = (cell)=>{
|
|
6151
|
+
if (!cell || !spreadsheet_editor_support_spreadsheetCellHasContent(cell)) return;
|
|
6152
|
+
summary.nonEmptyCount += 1;
|
|
6153
|
+
if ('number' != typeof cell.v || !Number.isFinite(cell.v)) return;
|
|
6154
|
+
summary.numericCount += 1;
|
|
6155
|
+
sum += cell.v;
|
|
6156
|
+
};
|
|
6157
|
+
if (sheet.data?.length) {
|
|
6158
|
+
const rowStart = range.row[0] ?? 0;
|
|
6159
|
+
const rowEnd = Math.min(range.row[1] ?? rowStart, sheet.data.length - 1);
|
|
6160
|
+
const columnStart = range.column[0] ?? 0;
|
|
6161
|
+
const columnEnd = range.column[1] ?? columnStart;
|
|
6162
|
+
for (const rowIndex of sparseArrayIndexes(sheet.data)){
|
|
6163
|
+
if (rowIndex < rowStart || rowIndex > rowEnd) continue;
|
|
6164
|
+
const row = sheet.data[rowIndex];
|
|
6165
|
+
if (row) {
|
|
6166
|
+
for (const columnIndex of sparseArrayIndexes(row))if (!(columnIndex < columnStart) && !(columnIndex > columnEnd)) collect(row[columnIndex]);
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
} else {
|
|
6170
|
+
const rowStart = range.row[0] ?? 0;
|
|
6171
|
+
const rowEnd = range.row[1] ?? rowStart;
|
|
6172
|
+
const columnStart = range.column[0] ?? 0;
|
|
6173
|
+
const columnEnd = range.column[1] ?? columnStart;
|
|
6174
|
+
for (const entry of sheet.celldata ?? [])if (!(entry.r < rowStart) && !(entry.r > rowEnd) && !(entry.c < columnStart) && !(entry.c > columnEnd)) collect(entry.v);
|
|
6175
|
+
}
|
|
6176
|
+
if (summary.numericCount > 0) {
|
|
6177
|
+
summary.sum = sum;
|
|
6178
|
+
summary.average = sum / summary.numericCount;
|
|
6179
|
+
}
|
|
6180
|
+
return summary;
|
|
6181
|
+
}
|
|
6182
|
+
function spreadsheetFontSizeOptions(current) {
|
|
6183
|
+
const values = [
|
|
6184
|
+
...spreadsheetFontSizes
|
|
6185
|
+
];
|
|
6186
|
+
if (current && !values.includes(current)) values.push(current);
|
|
6187
|
+
return values.sort((left, right)=>left - right).map((value)=>({
|
|
6188
|
+
value: String(value),
|
|
6189
|
+
label: String(value)
|
|
6190
|
+
}));
|
|
6191
|
+
}
|
|
6192
|
+
function spreadsheetFontFamilyOptions(current) {
|
|
6193
|
+
const options = officeFontFamilies.map(({ cssFamily, group, label, name })=>({
|
|
6194
|
+
value: name,
|
|
6195
|
+
group,
|
|
6196
|
+
label,
|
|
6197
|
+
previewStyle: {
|
|
6198
|
+
fontFamily: cssFamily
|
|
6199
|
+
},
|
|
6200
|
+
searchText: `${name} ${label}`
|
|
6201
|
+
}));
|
|
6202
|
+
const normalizedCurrent = current?.trim();
|
|
6203
|
+
if (normalizedCurrent && !options.some(({ value })=>value === normalizedCurrent)) options.push({
|
|
6204
|
+
value: normalizedCurrent,
|
|
6205
|
+
group: '文档字体',
|
|
6206
|
+
label: normalizedCurrent,
|
|
6207
|
+
previewStyle: {
|
|
6208
|
+
fontFamily: normalizedCurrent
|
|
6209
|
+
},
|
|
6210
|
+
searchText: normalizedCurrent
|
|
6211
|
+
});
|
|
6212
|
+
return options;
|
|
6213
|
+
}
|
|
6214
|
+
function spreadsheetSheetsWithFiniteSelections(sheets) {
|
|
6215
|
+
return sheets.map((sheet)=>({
|
|
6216
|
+
...sheet,
|
|
6217
|
+
luckysheet_select_save: (sheet.luckysheet_select_save?.length ? sheet.luckysheet_select_save : [
|
|
6218
|
+
void 0
|
|
6219
|
+
]).map(finiteSpreadsheetSelection)
|
|
6220
|
+
}));
|
|
6221
|
+
}
|
|
6222
|
+
function spreadsheetSheetsForFortune(sheets, namedRanges = []) {
|
|
6223
|
+
const startedAt = spreadsheetProjectionNow();
|
|
6224
|
+
let populatedCellCount = 0;
|
|
6225
|
+
const projectedSheets = sheets.map((sheet)=>{
|
|
6226
|
+
const { celldata: sourceCellData, data, ...sourceMetadata } = sheet;
|
|
6227
|
+
const projected = structuredClone(sourceMetadata);
|
|
6228
|
+
const profile = spreadsheetMatrixProfile(data);
|
|
6229
|
+
if (data && profile?.fortuneReady) {
|
|
6230
|
+
populatedCellCount += profile.populatedCellCount;
|
|
6231
|
+
return {
|
|
6232
|
+
...projected,
|
|
6233
|
+
data
|
|
6234
|
+
};
|
|
6235
|
+
}
|
|
6236
|
+
const fortuneProjection = spreadsheetMatrixForFortune({
|
|
6237
|
+
...projected,
|
|
6238
|
+
...void 0 === data ? {} : {
|
|
6239
|
+
data
|
|
6240
|
+
},
|
|
6241
|
+
...void 0 === sourceCellData ? {} : {
|
|
6242
|
+
celldata: sourceCellData
|
|
6243
|
+
}
|
|
6244
|
+
}, projected.config?.merge);
|
|
6245
|
+
populatedCellCount += fortuneProjection.populatedCellCount;
|
|
6246
|
+
return {
|
|
6247
|
+
...projected,
|
|
6248
|
+
data: fortuneProjection.data
|
|
6249
|
+
};
|
|
6250
|
+
});
|
|
6251
|
+
recordSpreadsheetProjectionMeasure(startedAt, spreadsheetProjectionNow(), {
|
|
6252
|
+
populatedCellCount,
|
|
6253
|
+
sheetCount: sheets.length
|
|
6254
|
+
});
|
|
6255
|
+
return materializeSpreadsheetDependentListsForFortune(projectedSheets, namedRanges);
|
|
6256
|
+
}
|
|
6257
|
+
function spreadsheetMatrixForFortune(sheet, merges) {
|
|
6258
|
+
const declaredRows = spreadsheet_editor_support_positiveSpreadsheetDimension(sheet.row);
|
|
6259
|
+
const declaredColumns = spreadsheet_editor_support_positiveSpreadsheetDimension(sheet.column);
|
|
6260
|
+
const hasDeclaredGrid = declaredRows > 0 && declaredColumns > 0;
|
|
6261
|
+
let rowCount = hasDeclaredGrid ? declaredRows : 60;
|
|
6262
|
+
let columnCount = hasDeclaredGrid ? declaredColumns : 26;
|
|
6263
|
+
let populatedCellCount = 0;
|
|
6264
|
+
const shownCommentCells = [];
|
|
6265
|
+
const projected = [];
|
|
6266
|
+
const setSourceCell = (rowIndex, columnIndex, cell)=>{
|
|
6267
|
+
rowCount = Math.max(rowCount, rowIndex + 1);
|
|
6268
|
+
columnCount = Math.max(columnCount, columnIndex + 1);
|
|
6269
|
+
const row = projected[rowIndex] ?? [];
|
|
6270
|
+
projected[rowIndex] = row;
|
|
6271
|
+
row[columnIndex] = cloneSpreadsheetCellForFortune(cell);
|
|
6272
|
+
if (cell.ps?.isShow) shownCommentCells.push({
|
|
6273
|
+
c: columnIndex,
|
|
6274
|
+
r: rowIndex
|
|
6275
|
+
});
|
|
6276
|
+
populatedCellCount += 1;
|
|
6277
|
+
};
|
|
6278
|
+
if (void 0 !== sheet.data) {
|
|
6279
|
+
rowCount = Math.max(rowCount, sheet.data.length);
|
|
6280
|
+
for (const rowIndex of sparseArrayIndexes(sheet.data)){
|
|
6281
|
+
const sourceRow = sheet.data[rowIndex];
|
|
6282
|
+
if (sourceRow) {
|
|
6283
|
+
columnCount = Math.max(columnCount, sourceRow.length);
|
|
6284
|
+
for (const columnIndex of sparseArrayIndexes(sourceRow)){
|
|
6285
|
+
const cell = sourceRow[columnIndex];
|
|
6286
|
+
if (null != cell) setSourceCell(rowIndex, columnIndex, cell);
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
} else for (const entry of sheet.celldata ?? [])if (null != entry.v) setSourceCell(entry.r, entry.c, entry.v);
|
|
6291
|
+
for (const merge of Object.values(merges ?? {})){
|
|
6292
|
+
rowCount = Math.max(rowCount, merge.r + merge.rs);
|
|
6293
|
+
columnCount = Math.max(columnCount, merge.c + merge.cs);
|
|
6294
|
+
for(let rowIndex = merge.r; rowIndex < merge.r + merge.rs; rowIndex += 1){
|
|
6295
|
+
const row = projected[rowIndex] ?? [];
|
|
6296
|
+
projected[rowIndex] = row;
|
|
6297
|
+
for(let columnIndex = merge.c; columnIndex < merge.c + merge.cs; columnIndex += 1)row[columnIndex] = Object.freeze({
|
|
6298
|
+
...row[columnIndex] ?? {},
|
|
6299
|
+
mc: rowIndex === merge.r && columnIndex === merge.c ? {
|
|
6300
|
+
...merge
|
|
6301
|
+
} : {
|
|
6302
|
+
r: merge.r,
|
|
6303
|
+
c: merge.c
|
|
6304
|
+
}
|
|
6305
|
+
});
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
projected.length = rowCount;
|
|
6309
|
+
projected[0] ??= [];
|
|
6310
|
+
for (const rowIndex of sparseArrayIndexes(projected)){
|
|
6311
|
+
const row = projected[rowIndex];
|
|
6312
|
+
if (row) {
|
|
6313
|
+
row.length = Math.max(row.length, columnCount);
|
|
6314
|
+
Object.freeze(row);
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
attachSpreadsheetShownCommentCells(projected, Object.freeze(shownCommentCells.map((cell)=>Object.freeze(cell))));
|
|
6318
|
+
Object.freeze(projected);
|
|
6319
|
+
return {
|
|
6320
|
+
data: projected,
|
|
6321
|
+
populatedCellCount
|
|
6322
|
+
};
|
|
6323
|
+
}
|
|
6324
|
+
function cloneSpreadsheetCellForFortune(cell) {
|
|
6325
|
+
const clone = {
|
|
6326
|
+
...cell
|
|
6327
|
+
};
|
|
6328
|
+
if (cell.mc) clone.mc = {
|
|
6329
|
+
...cell.mc
|
|
6330
|
+
};
|
|
6331
|
+
if (cell.ct) clone.ct = {
|
|
6332
|
+
...cell.ct,
|
|
6333
|
+
...void 0 === cell.ct.s ? {} : {
|
|
6334
|
+
s: structuredClone(cell.ct.s)
|
|
6335
|
+
}
|
|
6336
|
+
};
|
|
6337
|
+
if (cell.ps) clone.ps = {
|
|
6338
|
+
...cell.ps
|
|
6339
|
+
};
|
|
6340
|
+
if (cell.hl) clone.hl = {
|
|
6341
|
+
...cell.hl
|
|
6342
|
+
};
|
|
6343
|
+
if (cell.spl && 'object' == typeof cell.spl) clone.spl = structuredClone(cell.spl);
|
|
6344
|
+
Object.freeze(clone);
|
|
6345
|
+
return clone;
|
|
6346
|
+
}
|
|
6347
|
+
function spreadsheetProjectionNow() {
|
|
6348
|
+
return globalThis.performance?.now?.() ?? Date.now();
|
|
6349
|
+
}
|
|
6350
|
+
function recordSpreadsheetProjectionMeasure(start, end, detail) {
|
|
6351
|
+
try {
|
|
6352
|
+
globalThis.performance?.measure('a3s-office.spreadsheet.fortune-projection', {
|
|
6353
|
+
detail,
|
|
6354
|
+
end,
|
|
6355
|
+
start
|
|
6356
|
+
});
|
|
6357
|
+
} catch {}
|
|
6358
|
+
}
|
|
6359
|
+
function spreadsheetSheetsFromFortune(sheets, sourceSheets, operations = [], dateSystem = '1900') {
|
|
6360
|
+
const startedAt = spreadsheetProjectionNow();
|
|
6361
|
+
const filterContext = {
|
|
6362
|
+
dateSystem,
|
|
6363
|
+
now: new Date()
|
|
6364
|
+
};
|
|
6365
|
+
const incremental = projectSpreadsheetSheetsFromFortuneOperations(sheets, sourceSheets, operations);
|
|
6366
|
+
if (incremental) {
|
|
6367
|
+
recordSpreadsheetControlledProjectionMeasure(startedAt, spreadsheetProjectionNow(), {
|
|
6368
|
+
affectedCellCount: incremental.affectedCellCount,
|
|
6369
|
+
incremental: true,
|
|
6370
|
+
operationCount: operations.length
|
|
6371
|
+
});
|
|
6372
|
+
return restoreSpreadsheetDependentListProjections(reconcileSpreadsheetFiltersAfterFortune(reconcileSpreadsheetTablesAfterFortune(incremental.sheets, sourceSheets, operations), sourceSheets, operations, filterContext));
|
|
6373
|
+
}
|
|
6374
|
+
const operationCoordinatesBySheet = spreadsheetCellOperationCoordinates(operations);
|
|
6375
|
+
const projected = sheets.map((sheet, index)=>{
|
|
6376
|
+
const source = (sheet.id ? sourceSheets.find((candidate)=>candidate.id === sheet.id) : void 0) ?? sourceSheets[index];
|
|
6377
|
+
const operationCoordinates = sheet.id ? operationCoordinatesBySheet?.get(sheet.id) : void 0;
|
|
6378
|
+
const cells = spreadsheetPopulatedCellData(sheet).map((entry)=>{
|
|
6379
|
+
if (!source || !entry.v) return entry;
|
|
6380
|
+
const previous = spreadsheet_editor_support_spreadsheetCellAt(source, entry.r, entry.c);
|
|
6381
|
+
const exactCellOperation = operationCoordinates?.has(spreadsheetCellOperationKey(entry.r, entry.c));
|
|
6382
|
+
const textStableNonStructuralCallback = null !== operationCoordinatesBySheet && sameSpreadsheetRichTextCellText(previous, entry.v);
|
|
6383
|
+
if (!exactCellOperation && !textStableNonStructuralCallback) return entry;
|
|
6384
|
+
const reconciled = exactCellOperation ? reconcileSpreadsheetRichTextCellEdit(previous, entry.v, takeSpreadsheetRichTextPaste(source, entry.r, entry.c, entry.v)) : restoreSpreadsheetRichTextCellRuns(previous, entry.v);
|
|
6385
|
+
return reconciled === entry.v ? entry : {
|
|
6386
|
+
...entry,
|
|
6387
|
+
v: reconciled
|
|
6388
|
+
};
|
|
6389
|
+
});
|
|
6390
|
+
const { celldata: _cellData, data: _data, ...metadata } = sheet;
|
|
6391
|
+
const useMatrix = source ? void 0 !== source.data : void 0 !== sheet.data;
|
|
6392
|
+
if (!useMatrix) return {
|
|
6393
|
+
...metadata,
|
|
6394
|
+
celldata: cells
|
|
6395
|
+
};
|
|
6396
|
+
const data = [];
|
|
6397
|
+
data.length = Math.max(sheet.data?.length ?? 0, source?.data?.length ?? 0, spreadsheet_editor_support_positiveSpreadsheetDimension(sheet.row));
|
|
6398
|
+
for (const { r: rowIndex, c: columnIndex, v: cell } of cells){
|
|
6399
|
+
const row = data[rowIndex] ?? [];
|
|
6400
|
+
data[rowIndex] = row;
|
|
6401
|
+
row.length = Math.max(row.length, sheet.data?.[rowIndex]?.length ?? 0, source?.data?.[rowIndex]?.length ?? 0, spreadsheet_editor_support_positiveSpreadsheetDimension(sheet.column));
|
|
6402
|
+
row[columnIndex] = cell;
|
|
6403
|
+
}
|
|
6404
|
+
return {
|
|
6405
|
+
...metadata,
|
|
6406
|
+
data
|
|
6407
|
+
};
|
|
6408
|
+
});
|
|
6409
|
+
recordSpreadsheetControlledProjectionMeasure(startedAt, spreadsheetProjectionNow(), {
|
|
6410
|
+
affectedCellCount: 0,
|
|
6411
|
+
incremental: false,
|
|
6412
|
+
operationCount: operations.length
|
|
6413
|
+
});
|
|
6414
|
+
return restoreSpreadsheetDependentListProjections(reconcileSpreadsheetFiltersAfterFortune(reconcileSpreadsheetTablesAfterFortune(projected, sourceSheets, operations), sourceSheets, operations, filterContext));
|
|
6415
|
+
}
|
|
6416
|
+
function recordSpreadsheetControlledProjectionMeasure(start, end, detail) {
|
|
6417
|
+
try {
|
|
6418
|
+
globalThis.performance?.measure('a3s-office.spreadsheet.controlled-projection', {
|
|
6419
|
+
detail,
|
|
6420
|
+
end,
|
|
6421
|
+
start
|
|
6422
|
+
});
|
|
6423
|
+
} catch {}
|
|
6424
|
+
}
|
|
6425
|
+
function finiteSpreadsheetSelection(selection) {
|
|
6426
|
+
const row = finiteSpreadsheetSelectionAxis(selection?.row);
|
|
6427
|
+
const column = finiteSpreadsheetSelectionAxis(selection?.column);
|
|
6428
|
+
return {
|
|
6429
|
+
...selection,
|
|
6430
|
+
row,
|
|
6431
|
+
column,
|
|
6432
|
+
row_focus: finiteSpreadsheetFocus(selection?.row_focus, row),
|
|
6433
|
+
column_focus: finiteSpreadsheetFocus(selection?.column_focus, column)
|
|
6434
|
+
};
|
|
6435
|
+
}
|
|
6436
|
+
function sameSpreadsheetWorkbookState(changed, rendered) {
|
|
6437
|
+
return changed.length === rendered.length && changed.every((sheet, index)=>sameSpreadsheetSheetState(sheet, rendered[index]));
|
|
6438
|
+
}
|
|
6439
|
+
function sameSpreadsheetWorkbookStateAfterOperations(changed, rendered, operations) {
|
|
6440
|
+
if (0 === operations.length || operations.length > 10000) return null;
|
|
6441
|
+
if (changed.length !== rendered.length || !changed.every((sheet, index)=>sameSpreadsheetHistoryValue(spreadsheetSheetMetadata(sheet), spreadsheetSheetMetadata(rendered[index])))) return false;
|
|
6442
|
+
const changedById = spreadsheet_editor_support_spreadsheetSheetsById(changed);
|
|
6443
|
+
const renderedById = spreadsheet_editor_support_spreadsheetSheetsById(rendered);
|
|
6444
|
+
if (!changedById || !renderedById) return null;
|
|
6445
|
+
const coordinates = new Map();
|
|
6446
|
+
for (const operation of operations){
|
|
6447
|
+
if ('insertRowCol' === operation.op || 'deleteRowCol' === operation.op || 'addSheet' === operation.op || 'deleteSheet' === operation.op || 0 === operation.path.length) return false;
|
|
6448
|
+
if (!operation.id || !changedById.has(operation.id) || !renderedById.has(operation.id)) return null;
|
|
6449
|
+
if ('data' !== operation.path[0]) continue;
|
|
6450
|
+
const row = operation.path[1];
|
|
6451
|
+
const column = operation.path[2];
|
|
6452
|
+
if (operation.path.length < 3 || !spreadsheet_editor_support_isSpreadsheetCoordinate(row) || !spreadsheet_editor_support_isSpreadsheetCoordinate(column)) return null;
|
|
6453
|
+
coordinates.set(`${operation.id}:${row}:${column}`, {
|
|
6454
|
+
column,
|
|
6455
|
+
row,
|
|
6456
|
+
sheetId: operation.id
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
for (const { column, row, sheetId } of coordinates.values())if (!sameSpreadsheetHistoryValue(spreadsheet_editor_support_spreadsheetCellAt(changedById.get(sheetId), row, column), spreadsheet_editor_support_spreadsheetCellAt(renderedById.get(sheetId), row, column))) return false;
|
|
6460
|
+
return true;
|
|
6461
|
+
}
|
|
6462
|
+
function sameSpreadsheetHistoryContent(left, right) {
|
|
6463
|
+
if (left === right) return true;
|
|
6464
|
+
const { sheets: leftSheets, ...leftMetadata } = left;
|
|
6465
|
+
const { sheets: rightSheets, ...rightMetadata } = right;
|
|
6466
|
+
return sameSpreadsheetHistoryValue(leftMetadata, rightMetadata) && leftSheets.length === rightSheets.length && leftSheets.every((sheet, index)=>sameSpreadsheetSheetState(sheet, rightSheets[index]));
|
|
6467
|
+
}
|
|
6468
|
+
function spreadsheetContentWithSelection(content, sheetId, selection) {
|
|
6469
|
+
return spreadsheetContentWithSelections(content, sheetId, selection ? [
|
|
6470
|
+
selection
|
|
6471
|
+
] : []);
|
|
6472
|
+
}
|
|
6473
|
+
function spreadsheetContentWithSelections(content, sheetId, selections) {
|
|
6474
|
+
if (!sheetId || !selections.length) return content;
|
|
6475
|
+
const nextSelections = selections.map(finiteSpreadsheetSelection);
|
|
6476
|
+
let changed = false;
|
|
6477
|
+
const sheets = content.sheets.map((sheet)=>{
|
|
6478
|
+
if (sheet.id !== sheetId) return sheet;
|
|
6479
|
+
changed = true;
|
|
6480
|
+
return {
|
|
6481
|
+
...sheet,
|
|
6482
|
+
luckysheet_select_save: nextSelections
|
|
6483
|
+
};
|
|
6484
|
+
});
|
|
6485
|
+
return changed ? {
|
|
6486
|
+
...content,
|
|
6487
|
+
sheets
|
|
6488
|
+
} : content;
|
|
6489
|
+
}
|
|
6490
|
+
function isSpreadsheetNativeTextUndoTarget(target) {
|
|
6491
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
6492
|
+
if (isSpreadsheetCellEditingTarget(target)) return true;
|
|
6493
|
+
if (target.closest('.fortune-container')) return false;
|
|
6494
|
+
return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target.isContentEditable || Boolean(target.closest('[contenteditable="true"]'));
|
|
6495
|
+
}
|
|
6496
|
+
function isSpreadsheetCellEditingTarget(target) {
|
|
6497
|
+
if (!isSpreadsheetCellEditorTarget(target)) return false;
|
|
6498
|
+
if (!(target instanceof Element)) return false;
|
|
6499
|
+
const formulaInput = target.closest('.fortune-fx-input');
|
|
6500
|
+
if (formulaInput) return true;
|
|
6501
|
+
const cellInput = target.closest('.luckysheet-cell-input');
|
|
6502
|
+
if (!cellInput) return false;
|
|
6503
|
+
const inputBox = cellInput.closest('.luckysheet-input-box');
|
|
6504
|
+
if (!inputBox) return true;
|
|
6505
|
+
const zIndex = Number.parseInt(inputBox.style.zIndex || getComputedStyle(inputBox).zIndex, 10);
|
|
6506
|
+
return !Number.isFinite(zIndex) || zIndex >= 0;
|
|
6507
|
+
}
|
|
6508
|
+
function isSpreadsheetCellEditorTarget(target) {
|
|
6509
|
+
return target instanceof Element && Boolean(target.closest('.fortune-fx-input, .luckysheet-cell-input'));
|
|
6510
|
+
}
|
|
6511
|
+
function spreadsheetFormulaBarSelectAllTarget(event) {
|
|
6512
|
+
if (event.defaultPrevented || event.repeat || event.altKey || event.shiftKey || !(event.metaKey || event.ctrlKey) || 'a' !== event.key.toLocaleLowerCase() || !(event.target instanceof Element)) return null;
|
|
6513
|
+
const formulaBar = event.target.closest('.fortune-fx-input');
|
|
6514
|
+
return formulaBar instanceof HTMLElement ? formulaBar : null;
|
|
6515
|
+
}
|
|
6516
|
+
function selectSpreadsheetFormulaBarContents(event) {
|
|
6517
|
+
const formulaBar = spreadsheetFormulaBarSelectAllTarget(event);
|
|
6518
|
+
const selection = window.getSelection();
|
|
6519
|
+
if (!formulaBar || !selection) return false;
|
|
6520
|
+
const range = document.createRange();
|
|
6521
|
+
range.selectNodeContents(formulaBar);
|
|
6522
|
+
selection.removeAllRanges();
|
|
6523
|
+
selection.addRange(range);
|
|
6524
|
+
return true;
|
|
6525
|
+
}
|
|
6526
|
+
function spreadsheetColumnLabel(column) {
|
|
6527
|
+
let value = Math.max(0, column) + 1;
|
|
6528
|
+
let label = '';
|
|
6529
|
+
while(value > 0){
|
|
6530
|
+
value -= 1;
|
|
6531
|
+
label = String.fromCharCode(65 + value % 26) + label;
|
|
6532
|
+
value = Math.floor(value / 26);
|
|
6533
|
+
}
|
|
6534
|
+
return label;
|
|
6535
|
+
}
|
|
6536
|
+
function spreadsheet_editor_support_spreadsheetCellHasContent(cell) {
|
|
6537
|
+
if ('string' == typeof cell.f && cell.f.trim()) return true;
|
|
6538
|
+
if (null === cell.v || void 0 === cell.v) return false;
|
|
6539
|
+
return 'string' != typeof cell.v || cell.v.trim().length > 0;
|
|
6540
|
+
}
|
|
6541
|
+
function finiteSpreadsheetSelectionAxis(axis) {
|
|
6542
|
+
const first = finiteSpreadsheetIndex(axis?.[0], 0);
|
|
6543
|
+
const second = finiteSpreadsheetIndex(axis?.[1], first);
|
|
6544
|
+
return [
|
|
6545
|
+
Math.min(first, second),
|
|
6546
|
+
Math.max(first, second)
|
|
6547
|
+
];
|
|
6548
|
+
}
|
|
6549
|
+
function finiteSpreadsheetFocus(value, axis) {
|
|
6550
|
+
const focus = finiteSpreadsheetIndex(value, axis[0] ?? 0);
|
|
6551
|
+
return Math.min(axis[1] ?? focus, Math.max(axis[0] ?? focus, focus));
|
|
6552
|
+
}
|
|
6553
|
+
function finiteSpreadsheetIndex(value, fallback) {
|
|
6554
|
+
return 'number' == typeof value && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : fallback;
|
|
6555
|
+
}
|
|
6556
|
+
function spreadsheet_editor_support_isSpreadsheetCoordinate(value) {
|
|
6557
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
6558
|
+
}
|
|
6559
|
+
function spreadsheet_editor_support_spreadsheetSheetsById(sheets) {
|
|
6560
|
+
const byId = new Map();
|
|
6561
|
+
for (const sheet of sheets){
|
|
6562
|
+
if (!sheet.id || byId.has(sheet.id)) return null;
|
|
6563
|
+
byId.set(sheet.id, sheet);
|
|
6564
|
+
}
|
|
6565
|
+
return byId;
|
|
6566
|
+
}
|
|
6567
|
+
function spreadsheetSheetMetadata(sheet) {
|
|
6568
|
+
if (!sheet) return null;
|
|
6569
|
+
const { celldata: _cellData, calcChain: _calculationChain, data: _data, dynamicArray_compute: _dynamicArrayComputation, luckysheet_select_save: _selection, luckysheet_selection_range: _range, zoomRatio, ...content } = sheet;
|
|
6570
|
+
return {
|
|
6571
|
+
...content,
|
|
6572
|
+
...void 0 === zoomRatio || 1 === zoomRatio ? {} : {
|
|
6573
|
+
zoomRatio
|
|
6574
|
+
}
|
|
6575
|
+
};
|
|
6576
|
+
}
|
|
6577
|
+
function sameSpreadsheetSheetState(left, right) {
|
|
6578
|
+
return Boolean(right && sameSpreadsheetHistoryValue(spreadsheetSheetMetadata(left), spreadsheetSheetMetadata(right)) && sameSpreadsheetCellStorage(left, right));
|
|
6579
|
+
}
|
|
6580
|
+
function sameSpreadsheetCellStorage(left, right) {
|
|
6581
|
+
if (void 0 !== left.data && void 0 !== right.data) {
|
|
6582
|
+
const leftProfile = spreadsheetMatrixProfile(left.data);
|
|
6583
|
+
const rightProfile = spreadsheetMatrixProfile(right.data);
|
|
6584
|
+
if (leftProfile && rightProfile && leftProfile.historyRoot === rightProfile.historyRoot) return leftProfile.historyState === rightProfile.historyState;
|
|
6585
|
+
return sameSpreadsheetDataMatrices(left.data, right.data);
|
|
6586
|
+
}
|
|
6587
|
+
if (void 0 === left.data && void 0 === right.data) {
|
|
6588
|
+
if (left.celldata === right.celldata) return true;
|
|
6589
|
+
}
|
|
6590
|
+
return sameSpreadsheetHistoryValue(spreadsheetPopulatedCellData(left), spreadsheetPopulatedCellData(right));
|
|
6591
|
+
}
|
|
6592
|
+
function sameSpreadsheetDataMatrices(left, right) {
|
|
6593
|
+
if (left === right) return true;
|
|
6594
|
+
for (const rowIndex of sparseArrayIndexes(left))if (!sameSpreadsheetDataRows(left[rowIndex], right[rowIndex])) return false;
|
|
6595
|
+
for (const rowIndex of sparseArrayIndexes(right))if (!Object.hasOwn(left, rowIndex) && !sameSpreadsheetDataRows(void 0, right[rowIndex])) return false;
|
|
6596
|
+
return true;
|
|
6597
|
+
}
|
|
6598
|
+
function sameSpreadsheetDataRows(left, right) {
|
|
6599
|
+
if (left === right) return true;
|
|
6600
|
+
for (const columnIndex of sparseArrayIndexes(left ?? [])){
|
|
6601
|
+
const leftCell = left?.[columnIndex];
|
|
6602
|
+
if (null == leftCell) continue;
|
|
6603
|
+
const rightCell = right?.[columnIndex];
|
|
6604
|
+
if (null == rightCell || !sameSpreadsheetHistoryValue(leftCell, rightCell)) return false;
|
|
6605
|
+
}
|
|
6606
|
+
for (const columnIndex of sparseArrayIndexes(right ?? []))if (right?.[columnIndex] != null && left?.[columnIndex] == null) return false;
|
|
6607
|
+
return true;
|
|
6608
|
+
}
|
|
6609
|
+
function spreadsheetPopulatedCellData(sheet) {
|
|
6610
|
+
const cells = [];
|
|
6611
|
+
if (void 0 !== sheet.data) for (const rowIndex of sparseArrayIndexes(sheet.data)){
|
|
6612
|
+
const row = sheet.data[rowIndex];
|
|
6613
|
+
if (row) for (const columnIndex of sparseArrayIndexes(row)){
|
|
6614
|
+
const cell = row[columnIndex];
|
|
6615
|
+
if (null != cell) cells.push({
|
|
6616
|
+
r: rowIndex,
|
|
6617
|
+
c: columnIndex,
|
|
6618
|
+
v: cell
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
6621
|
+
}
|
|
6622
|
+
else for (const cell of sheet.celldata ?? [])if (null != cell.v) cells.push(cell);
|
|
6623
|
+
return cells.sort((left, right)=>left.r - right.r || left.c - right.c);
|
|
6624
|
+
}
|
|
6625
|
+
function spreadsheet_editor_support_positiveSpreadsheetDimension(value) {
|
|
6626
|
+
return 'number' == typeof value && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
6627
|
+
}
|
|
6628
|
+
const spreadsheetKeyboardPageRows = 20;
|
|
6629
|
+
const spreadsheetDefaultRows = 60;
|
|
6630
|
+
const spreadsheetDefaultColumns = 26;
|
|
6631
|
+
function moveSpreadsheetKeyboardSelection(sheet, selection, move, extend) {
|
|
6632
|
+
const bounds = spreadsheetSheetBounds(sheet);
|
|
6633
|
+
const current = normalizeSpreadsheetKeyboardSelection(selection, bounds);
|
|
6634
|
+
let targetRow = current.rowFocus;
|
|
6635
|
+
let targetColumn = current.columnFocus;
|
|
6636
|
+
switch(move){
|
|
6637
|
+
case 'up':
|
|
6638
|
+
targetRow -= 1;
|
|
6639
|
+
break;
|
|
6640
|
+
case 'down':
|
|
6641
|
+
targetRow += 1;
|
|
6642
|
+
break;
|
|
6643
|
+
case 'left':
|
|
6644
|
+
targetColumn -= 1;
|
|
6645
|
+
break;
|
|
6646
|
+
case 'right':
|
|
6647
|
+
targetColumn += 1;
|
|
6648
|
+
break;
|
|
6649
|
+
case 'next-cell':
|
|
6650
|
+
if (targetColumn < bounds.lastColumn) targetColumn += 1;
|
|
6651
|
+
else if (targetRow < bounds.lastRow) {
|
|
6652
|
+
targetRow += 1;
|
|
6653
|
+
targetColumn = 0;
|
|
6654
|
+
}
|
|
6655
|
+
break;
|
|
6656
|
+
case 'previous-cell':
|
|
6657
|
+
if (targetColumn > 0) targetColumn -= 1;
|
|
6658
|
+
else if (targetRow > 0) {
|
|
6659
|
+
targetRow -= 1;
|
|
6660
|
+
targetColumn = bounds.lastColumn;
|
|
6661
|
+
}
|
|
6662
|
+
break;
|
|
6663
|
+
case 'page-up':
|
|
6664
|
+
targetRow -= spreadsheetKeyboardPageRows;
|
|
6665
|
+
break;
|
|
6666
|
+
case 'page-down':
|
|
6667
|
+
targetRow += spreadsheetKeyboardPageRows;
|
|
6668
|
+
break;
|
|
6669
|
+
case 'row-start':
|
|
6670
|
+
targetColumn = 0;
|
|
6671
|
+
break;
|
|
6672
|
+
case 'sheet-start':
|
|
6673
|
+
targetRow = 0;
|
|
6674
|
+
targetColumn = 0;
|
|
6675
|
+
break;
|
|
6676
|
+
case 'used-end':
|
|
6677
|
+
{
|
|
6678
|
+
const usedEnd = spreadsheetUsedRangeEnd(sheet);
|
|
6679
|
+
targetRow = usedEnd.row;
|
|
6680
|
+
targetColumn = usedEnd.column;
|
|
6681
|
+
break;
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
targetRow = clampSpreadsheetIndex(targetRow, bounds.lastRow);
|
|
6685
|
+
targetColumn = clampSpreadsheetIndex(targetColumn, bounds.lastColumn);
|
|
6686
|
+
if (!extend) return {
|
|
6687
|
+
row: [
|
|
6688
|
+
targetRow,
|
|
6689
|
+
targetRow
|
|
6690
|
+
],
|
|
6691
|
+
column: [
|
|
6692
|
+
targetColumn,
|
|
6693
|
+
targetColumn
|
|
6694
|
+
],
|
|
6695
|
+
row_focus: targetRow,
|
|
6696
|
+
column_focus: targetColumn
|
|
6697
|
+
};
|
|
6698
|
+
const rowAnchor = selectionAnchor(current.startRow, current.endRow, current.rowFocus);
|
|
6699
|
+
const columnAnchor = selectionAnchor(current.startColumn, current.endColumn, current.columnFocus);
|
|
6700
|
+
return {
|
|
6701
|
+
row: [
|
|
6702
|
+
Math.min(rowAnchor, targetRow),
|
|
6703
|
+
Math.max(rowAnchor, targetRow)
|
|
6704
|
+
],
|
|
6705
|
+
column: [
|
|
6706
|
+
Math.min(columnAnchor, targetColumn),
|
|
6707
|
+
Math.max(columnAnchor, targetColumn)
|
|
6708
|
+
],
|
|
6709
|
+
row_focus: targetRow,
|
|
6710
|
+
column_focus: targetColumn
|
|
6711
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
function scopeSpreadsheetKeyboardSelection(sheet, selection, scope) {
|
|
6714
|
+
const bounds = spreadsheetSheetBounds(sheet);
|
|
6715
|
+
const current = normalizeSpreadsheetKeyboardSelection(selection, bounds);
|
|
6716
|
+
if ('column' === scope) return {
|
|
6717
|
+
row: [
|
|
6718
|
+
0,
|
|
6719
|
+
bounds.lastRow
|
|
6720
|
+
],
|
|
6721
|
+
column: [
|
|
6722
|
+
current.columnFocus,
|
|
6723
|
+
current.columnFocus
|
|
6724
|
+
],
|
|
6725
|
+
row_focus: current.rowFocus,
|
|
6726
|
+
column_focus: current.columnFocus
|
|
6727
|
+
};
|
|
6728
|
+
if ('row' === scope) return {
|
|
6729
|
+
row: [
|
|
6730
|
+
current.rowFocus,
|
|
6731
|
+
current.rowFocus
|
|
6732
|
+
],
|
|
6733
|
+
column: [
|
|
6734
|
+
0,
|
|
6735
|
+
bounds.lastColumn
|
|
6736
|
+
],
|
|
6737
|
+
row_focus: current.rowFocus,
|
|
6738
|
+
column_focus: current.columnFocus
|
|
6739
|
+
};
|
|
6740
|
+
return {
|
|
6741
|
+
row: [
|
|
6742
|
+
0,
|
|
6743
|
+
bounds.lastRow
|
|
6744
|
+
],
|
|
6745
|
+
column: [
|
|
6746
|
+
0,
|
|
6747
|
+
bounds.lastColumn
|
|
6748
|
+
],
|
|
6749
|
+
row_focus: current.rowFocus,
|
|
6750
|
+
column_focus: current.columnFocus
|
|
6751
|
+
};
|
|
6752
|
+
}
|
|
6753
|
+
function isSpreadsheetGridKeyboardTarget(target) {
|
|
6754
|
+
if (null === target) return true;
|
|
6755
|
+
return target instanceof Element && Boolean(target.closest('.fortune-container'));
|
|
6756
|
+
}
|
|
6757
|
+
function runSpreadsheetSelectionMoveShortcut(event, canExecute, execute, move, extend) {
|
|
6758
|
+
if (event.isComposing || isOfficeShortcutBlocked(event.target) || isSpreadsheetNativeTextUndoTarget(event.target) || !isSpreadsheetGridKeyboardTarget(event.target) || !canExecute(move, extend)) return false;
|
|
6759
|
+
return execute(move, extend);
|
|
6760
|
+
}
|
|
6761
|
+
function runSpreadsheetSelectionScopeShortcut(event, canExecute, execute, scope) {
|
|
6762
|
+
if (event.repeat || event.isComposing || isOfficeShortcutBlocked(event.target) || isSpreadsheetNativeTextUndoTarget(event.target) || !isSpreadsheetGridKeyboardTarget(event.target) || !canExecute(scope)) return false;
|
|
6763
|
+
return execute(scope);
|
|
6764
|
+
}
|
|
6765
|
+
function spreadsheetSelectionContainsFocus(selection, focus) {
|
|
6766
|
+
const firstRow = selection.row[0] ?? 0;
|
|
6767
|
+
const secondRow = selection.row[1] ?? firstRow;
|
|
6768
|
+
const firstColumn = selection.column[0] ?? 0;
|
|
6769
|
+
const secondColumn = selection.column[1] ?? firstColumn;
|
|
6770
|
+
return focus.row >= Math.min(firstRow, secondRow) && focus.row <= Math.max(firstRow, secondRow) && focus.column >= Math.min(firstColumn, secondColumn) && focus.column <= Math.max(firstColumn, secondColumn);
|
|
6771
|
+
}
|
|
6772
|
+
function spreadsheetSheetBounds(sheet) {
|
|
6773
|
+
const dataRows = sheet.data?.length ?? 0;
|
|
6774
|
+
const dataColumns = sparseMatrixColumnCount(sheet.data);
|
|
6775
|
+
return {
|
|
6776
|
+
lastRow: spreadsheet_keyboard_navigation_positiveSpreadsheetDimension(sheet.row, dataRows, spreadsheetDefaultRows) - 1,
|
|
6777
|
+
lastColumn: spreadsheet_keyboard_navigation_positiveSpreadsheetDimension(sheet.column, dataColumns, spreadsheetDefaultColumns) - 1
|
|
6778
|
+
};
|
|
6779
|
+
}
|
|
6780
|
+
function spreadsheet_keyboard_navigation_positiveSpreadsheetDimension(configured, dataDimension, fallback) {
|
|
6781
|
+
const value = Number(configured);
|
|
6782
|
+
if (Number.isFinite(value) && value > 0) return Math.max(1, Math.trunc(value));
|
|
6783
|
+
if (dataDimension > 0) return dataDimension;
|
|
6784
|
+
return fallback;
|
|
6785
|
+
}
|
|
6786
|
+
function spreadsheetUsedRangeEnd(sheet) {
|
|
6787
|
+
const bounds = spreadsheetSheetBounds(sheet);
|
|
6788
|
+
let row = 0;
|
|
6789
|
+
let column = 0;
|
|
6790
|
+
sheet.data?.forEach((cells, rowIndex)=>{
|
|
6791
|
+
cells?.forEach((cell, columnIndex)=>{
|
|
6792
|
+
if (!spreadsheetCellHasValue(cell)) return;
|
|
6793
|
+
row = Math.max(row, rowIndex);
|
|
6794
|
+
column = Math.max(column, columnIndex);
|
|
6795
|
+
});
|
|
6796
|
+
});
|
|
6797
|
+
sheet.celldata?.forEach((cell)=>{
|
|
6798
|
+
if (!spreadsheetCellHasValue(cell.v)) return;
|
|
6799
|
+
row = Math.max(row, Number(cell.r) || 0);
|
|
6800
|
+
column = Math.max(column, Number(cell.c) || 0);
|
|
6801
|
+
});
|
|
6802
|
+
return {
|
|
6803
|
+
row: clampSpreadsheetIndex(row, bounds.lastRow),
|
|
6804
|
+
column: clampSpreadsheetIndex(column, bounds.lastColumn)
|
|
6805
|
+
};
|
|
6806
|
+
}
|
|
6807
|
+
function spreadsheetCellHasValue(cell) {
|
|
6808
|
+
if (!cell) return false;
|
|
6809
|
+
if ('string' == typeof cell.f && cell.f.trim()) return true;
|
|
6810
|
+
const value = cell.v;
|
|
6811
|
+
if (null != value) return 'string' != typeof value || value.trim().length > 0;
|
|
6812
|
+
return 'string' == typeof cell.m && cell.m.trim().length > 0;
|
|
6813
|
+
}
|
|
6814
|
+
function normalizeSpreadsheetKeyboardSelection(selection, bounds) {
|
|
6815
|
+
const firstRow = clampSpreadsheetIndex(selection.row[0], bounds.lastRow);
|
|
6816
|
+
const secondRow = clampSpreadsheetIndex(selection.row[1] ?? firstRow, bounds.lastRow);
|
|
6817
|
+
const firstColumn = clampSpreadsheetIndex(selection.column[0], bounds.lastColumn);
|
|
6818
|
+
const secondColumn = clampSpreadsheetIndex(selection.column[1] ?? firstColumn, bounds.lastColumn);
|
|
6819
|
+
const startRow = Math.min(firstRow, secondRow);
|
|
6820
|
+
const endRow = Math.max(firstRow, secondRow);
|
|
6821
|
+
const startColumn = Math.min(firstColumn, secondColumn);
|
|
6822
|
+
const endColumn = Math.max(firstColumn, secondColumn);
|
|
6823
|
+
return {
|
|
6824
|
+
startRow,
|
|
6825
|
+
endRow,
|
|
6826
|
+
startColumn,
|
|
6827
|
+
endColumn,
|
|
6828
|
+
rowFocus: clampSpreadsheetIndex(selection.row_focus ?? secondRow, bounds.lastRow),
|
|
6829
|
+
columnFocus: clampSpreadsheetIndex(selection.column_focus ?? secondColumn, bounds.lastColumn)
|
|
6830
|
+
};
|
|
6831
|
+
}
|
|
6832
|
+
function clampSpreadsheetIndex(value, maximum) {
|
|
6833
|
+
const numeric = Number(value);
|
|
6834
|
+
const finite = Number.isFinite(numeric) ? Math.trunc(numeric) : 0;
|
|
6835
|
+
return Math.min(maximum, Math.max(0, finite));
|
|
6836
|
+
}
|
|
6837
|
+
function selectionAnchor(start, end, focus) {
|
|
6838
|
+
return focus === start ? end : start;
|
|
6839
|
+
}
|
|
6840
|
+
const spreadsheetMaximumRows = 1048576;
|
|
6841
|
+
const spreadsheetMaximumColumns = 16384;
|
|
6842
|
+
const spreadsheetGoToMaximumInputLength = 512;
|
|
6843
|
+
function resolveSpreadsheetGoToTarget(content, activeSheetId, input) {
|
|
6844
|
+
const value = input.trim().replace(/^=/, '').trim();
|
|
6845
|
+
if (!value) return spreadsheetGoToError('empty');
|
|
6846
|
+
if (value.length > spreadsheetGoToMaximumInputLength) return spreadsheetGoToError('invalid-reference');
|
|
6847
|
+
const activeSheet = content.sheets.find((sheet)=>sheet.id === activeSheetId);
|
|
6848
|
+
if (!activeSheet) return spreadsheetGoToError('sheet-not-found');
|
|
6849
|
+
const qualified = splitSpreadsheetQualifiedReference(value);
|
|
6850
|
+
if (!qualified) return spreadsheetGoToError('invalid-reference');
|
|
6851
|
+
const requestedSheet = qualified.qualifier ? spreadsheetSheetByName(content, qualified.qualifier) : activeSheet;
|
|
6852
|
+
if (qualified.qualifier && !requestedSheet) return spreadsheetGoToError('sheet-not-found');
|
|
6853
|
+
if (requestedSheet?.hide === 1) return spreadsheetGoToError('hidden-sheet');
|
|
6854
|
+
const direct = resolveSpreadsheetDirectRange(content, requestedSheet ?? activeSheet, qualified);
|
|
6855
|
+
if ('error' === direct.kind) return spreadsheetGoToError(direct.code);
|
|
6856
|
+
if ('match' === direct.kind) return spreadsheetGoToSuccess(direct.value, 'reference');
|
|
6857
|
+
const named = findSpreadsheetGoToName(content, qualified.reference, requestedSheet ?? activeSheet, Boolean(qualified.qualifier));
|
|
6858
|
+
if ('error' === named.kind) return spreadsheetGoToError(named.code);
|
|
6859
|
+
if (!named.value) return spreadsheetGoToError('invalid-reference');
|
|
6860
|
+
const fallbackSheet = named.value.scopeSheetId ? content.sheets.find((sheet)=>sheet.id === named.value?.scopeSheetId) : requestedSheet ?? activeSheet;
|
|
6861
|
+
if (!fallbackSheet) return spreadsheetGoToError('sheet-not-found');
|
|
6862
|
+
if (1 === fallbackSheet.hide) return spreadsheetGoToError('hidden-sheet');
|
|
6863
|
+
const namedReference = splitSpreadsheetQualifiedReference(named.value.reference.trim().replace(/^=/, '').trim());
|
|
6864
|
+
if (!namedReference) return spreadsheetGoToError('unsupported-name');
|
|
6865
|
+
const resolved = resolveSpreadsheetDirectRange(content, fallbackSheet, namedReference);
|
|
6866
|
+
if ('error' === resolved.kind) return spreadsheetGoToError(resolved.code);
|
|
6867
|
+
if ('match' !== resolved.kind) return spreadsheetGoToError('unsupported-name');
|
|
6868
|
+
return spreadsheetGoToSuccess(resolved.value, 'named-range', named.value.name);
|
|
6869
|
+
}
|
|
6870
|
+
function spreadsheetGoToValidationMessage(content, activeSheetId, input) {
|
|
6871
|
+
const resolution = resolveSpreadsheetGoToTarget(content, activeSheetId, input);
|
|
6872
|
+
return resolution.ok ? null : resolution.message;
|
|
6873
|
+
}
|
|
6874
|
+
function resolveSpreadsheetDirectRange(content, fallbackSheet, qualified) {
|
|
6875
|
+
const sheet = qualified.qualifier ? spreadsheetSheetByName(content, qualified.qualifier) : fallbackSheet;
|
|
6876
|
+
if (!sheet) return {
|
|
6877
|
+
kind: 'error',
|
|
6878
|
+
code: 'sheet-not-found'
|
|
6879
|
+
};
|
|
6880
|
+
if (1 === sheet.hide) return {
|
|
6881
|
+
kind: 'error',
|
|
6882
|
+
code: 'hidden-sheet'
|
|
6883
|
+
};
|
|
6884
|
+
const ranges = parseSpreadsheetCellRanges(qualified.reference);
|
|
6885
|
+
if (!ranges) return {
|
|
6886
|
+
kind: 'miss'
|
|
6887
|
+
};
|
|
6888
|
+
if (1 !== ranges.length) return {
|
|
6889
|
+
kind: 'error',
|
|
6890
|
+
code: 'multiple-ranges'
|
|
6891
|
+
};
|
|
6892
|
+
const range = ranges[0];
|
|
6893
|
+
if (!range || !spreadsheetGoToRangeIsBounded(sheet, range)) return {
|
|
6894
|
+
kind: 'error',
|
|
6895
|
+
code: 'out-of-bounds'
|
|
6896
|
+
};
|
|
6897
|
+
return {
|
|
6898
|
+
kind: 'match',
|
|
6899
|
+
value: {
|
|
6900
|
+
range,
|
|
6901
|
+
sheet
|
|
6902
|
+
}
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
function findSpreadsheetGoToName(content, input, scopeSheet, qualified) {
|
|
6906
|
+
const normalized = input.trim().toLocaleLowerCase();
|
|
6907
|
+
const matching = (content.namedRanges ?? []).filter((range)=>range.name.trim().toLocaleLowerCase() === normalized);
|
|
6908
|
+
const scoped = matching.filter((range)=>range.scopeSheetId === scopeSheet.id);
|
|
6909
|
+
const workbook = matching.filter((range)=>!range.scopeSheetId);
|
|
6910
|
+
const preferred = scoped.length ? scoped : workbook;
|
|
6911
|
+
if (preferred.length > 1) return {
|
|
6912
|
+
kind: 'error',
|
|
6913
|
+
code: 'ambiguous-name'
|
|
6914
|
+
};
|
|
6915
|
+
if (!preferred.length && !qualified) return {
|
|
6916
|
+
kind: 'match',
|
|
6917
|
+
value: null
|
|
6918
|
+
};
|
|
6919
|
+
return {
|
|
6920
|
+
kind: 'match',
|
|
6921
|
+
value: preferred[0] ?? null
|
|
6922
|
+
};
|
|
6923
|
+
}
|
|
6924
|
+
function spreadsheetGoToRangeIsBounded(sheet, range) {
|
|
6925
|
+
const bounds = spreadsheetSheetBounds(sheet);
|
|
6926
|
+
return range.row[0] >= 0 && range.row[1] < spreadsheetMaximumRows && range.row[1] <= bounds.lastRow && range.column[0] >= 0 && range.column[1] < spreadsheetMaximumColumns && range.column[1] <= bounds.lastColumn;
|
|
6927
|
+
}
|
|
6928
|
+
function spreadsheetGoToSuccess(resolved, source, name) {
|
|
6929
|
+
const reference = formatSpreadsheetCellRanges([
|
|
6930
|
+
resolved.range
|
|
6931
|
+
]);
|
|
6932
|
+
return {
|
|
6933
|
+
ok: true,
|
|
6934
|
+
target: {
|
|
6935
|
+
displayReference: `${quoteSpreadsheetSheetName(resolved.sheet.name)}!${reference}`,
|
|
6936
|
+
name,
|
|
6937
|
+
range: resolved.range,
|
|
6938
|
+
selection: {
|
|
6939
|
+
row: [
|
|
6940
|
+
...resolved.range.row
|
|
6941
|
+
],
|
|
6942
|
+
column: [
|
|
6943
|
+
...resolved.range.column
|
|
6944
|
+
],
|
|
6945
|
+
row_focus: resolved.range.row[0],
|
|
6946
|
+
column_focus: resolved.range.column[0]
|
|
6947
|
+
},
|
|
6948
|
+
sheetId: resolved.sheet.id ?? '',
|
|
6949
|
+
source
|
|
6950
|
+
}
|
|
6951
|
+
};
|
|
6952
|
+
}
|
|
6953
|
+
function spreadsheetGoToError(code) {
|
|
6954
|
+
const messages = {
|
|
6955
|
+
'ambiguous-name': '同一作用域中存在多个同名区域,无法确定定位目标。',
|
|
6956
|
+
empty: '请输入单元格、连续区域或已定义名称。',
|
|
6957
|
+
'hidden-sheet': '不能定位到隐藏工作表。',
|
|
6958
|
+
'invalid-reference': '请输入有效的 A1 单元格、连续区域或已定义名称。',
|
|
6959
|
+
'multiple-ranges': '一次只能定位到一个连续区域。',
|
|
6960
|
+
'out-of-bounds': '引用超出了目标工作表的有效边界。',
|
|
6961
|
+
'sheet-not-found': '找不到引用的工作表。',
|
|
6962
|
+
'unsupported-name': '该名称不是可定位的连续单元格区域。'
|
|
6963
|
+
};
|
|
6964
|
+
return {
|
|
6965
|
+
ok: false,
|
|
6966
|
+
code,
|
|
6967
|
+
message: messages[code]
|
|
6968
|
+
};
|
|
6969
|
+
}
|
|
6970
|
+
function splitSpreadsheetQualifiedReference(value) {
|
|
6971
|
+
let quoted = false;
|
|
6972
|
+
let separator = -1;
|
|
6973
|
+
for(let index = 0; index < value.length; index += 1){
|
|
6974
|
+
const character = value[index];
|
|
6975
|
+
if ("'" === character) {
|
|
6976
|
+
if (quoted && "'" === value[index + 1]) {
|
|
6977
|
+
index += 1;
|
|
6978
|
+
continue;
|
|
6979
|
+
}
|
|
6980
|
+
quoted = !quoted;
|
|
6981
|
+
continue;
|
|
6982
|
+
}
|
|
6983
|
+
if ('!' === character && !quoted) {
|
|
6984
|
+
if (separator >= 0) return null;
|
|
6985
|
+
separator = index;
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6988
|
+
if (quoted) return null;
|
|
6989
|
+
if (separator < 0) return {
|
|
6990
|
+
qualifier: null,
|
|
6991
|
+
reference: value.trim()
|
|
6992
|
+
};
|
|
6993
|
+
const rawQualifier = value.slice(0, separator).trim();
|
|
6994
|
+
const reference = value.slice(separator + 1).trim();
|
|
6995
|
+
if (!rawQualifier || !reference) return null;
|
|
6996
|
+
const qualifier = unquoteSpreadsheetSheetName(rawQualifier);
|
|
6997
|
+
return qualifier ? {
|
|
6998
|
+
qualifier,
|
|
6999
|
+
reference
|
|
7000
|
+
} : null;
|
|
7001
|
+
}
|
|
7002
|
+
function unquoteSpreadsheetSheetName(value) {
|
|
7003
|
+
if (!value.startsWith("'")) return value.includes("'") ? null : value;
|
|
7004
|
+
if (!value.endsWith("'") || value.length < 2) return null;
|
|
7005
|
+
const inner = value.slice(1, -1);
|
|
7006
|
+
for(let index = 0; index < inner.length; index += 1)if ("'" === inner[index]) {
|
|
7007
|
+
if ("'" !== inner[index + 1]) return null;
|
|
7008
|
+
index += 1;
|
|
7009
|
+
}
|
|
7010
|
+
return inner.replaceAll("''", "'");
|
|
7011
|
+
}
|
|
7012
|
+
function spreadsheetSheetByName(content, name) {
|
|
7013
|
+
const normalized = name.trim().toLocaleLowerCase();
|
|
7014
|
+
return content.sheets.find((sheet)=>sheet.name.trim().toLocaleLowerCase() === normalized);
|
|
7015
|
+
}
|
|
7016
|
+
function quoteSpreadsheetSheetName(name) {
|
|
7017
|
+
return `'${name.replaceAll("'", "''")}'`;
|
|
7018
|
+
}
|
|
7019
|
+
const MAX_SPREADSHEET_DEPENDENT_LIST_FORMULA_LENGTH = 255;
|
|
7020
|
+
const MAX_SPREADSHEET_DEPENDENT_LIST_REFERENCE_CELLS = 1024;
|
|
7021
|
+
const MAX_SPREADSHEET_DEPENDENT_LIST_MATERIALIZED_CELLS = 10000;
|
|
7022
|
+
const DEPENDENT_LIST_PROJECTION_KEY = '__a3sDependentListProjection';
|
|
7023
|
+
function isSpreadsheetDependentListFormula(value) {
|
|
7024
|
+
return 'string' == typeof value && /^=?\s*INDIRECT\s*\(/iu.test(value.trim());
|
|
7025
|
+
}
|
|
7026
|
+
function normalizeSpreadsheetDependentListFormula(value) {
|
|
7027
|
+
const source = value.trim();
|
|
7028
|
+
if (!source) return {
|
|
7029
|
+
ok: false,
|
|
7030
|
+
message: '动态下拉来源不能为空。'
|
|
7031
|
+
};
|
|
7032
|
+
if (Array.from(source).length > MAX_SPREADSHEET_DEPENDENT_LIST_FORMULA_LENGTH) return {
|
|
7033
|
+
ok: false,
|
|
7034
|
+
message: '动态下拉公式超过 255 个字符。'
|
|
7035
|
+
};
|
|
7036
|
+
if (/[\u0000-\u001f\u007f]/u.test(source) || /[\[\]]/u.test(source)) return {
|
|
7037
|
+
ok: false,
|
|
7038
|
+
message: '动态下拉公式只能引用当前工作簿。'
|
|
7039
|
+
};
|
|
7040
|
+
const body = source.replace(/^=/, '').trim();
|
|
7041
|
+
const argument = indirectArgument(body);
|
|
7042
|
+
if (null === argument) return {
|
|
7043
|
+
ok: false,
|
|
7044
|
+
message: '动态下拉公式仅支持 =INDIRECT(单元格或文本拼接)。'
|
|
7045
|
+
};
|
|
7046
|
+
const terms = splitConcatenation(argument);
|
|
7047
|
+
if (!terms || 0 === terms.length || terms.length > 16) return {
|
|
7048
|
+
ok: false,
|
|
7049
|
+
message: '动态下拉公式的拼接项数量超出本地安全边界。'
|
|
7050
|
+
};
|
|
7051
|
+
if (!terms.every((term)=>isDependentListTerm(term))) return {
|
|
7052
|
+
ok: false,
|
|
7053
|
+
message: '动态下拉公式只能拼接文本和单元格引用。'
|
|
7054
|
+
};
|
|
7055
|
+
return {
|
|
7056
|
+
ok: true,
|
|
7057
|
+
formula: `=INDIRECT(${terms.map((term)=>term.trim()).join('&')})`
|
|
7058
|
+
};
|
|
7059
|
+
}
|
|
7060
|
+
function resolveSpreadsheetDependentListReference(content, sheetId, row, column, anchorRow, anchorColumn, formula) {
|
|
7061
|
+
const normalized = normalizeSpreadsheetDependentListFormula(formula);
|
|
7062
|
+
if (!normalized.ok || !normalized.formula) return {
|
|
7063
|
+
ok: false,
|
|
7064
|
+
message: normalized.message
|
|
7065
|
+
};
|
|
7066
|
+
const body = normalized.formula.slice(1);
|
|
7067
|
+
const argument = indirectArgument(body);
|
|
7068
|
+
if (null === argument) return {
|
|
7069
|
+
ok: false,
|
|
7070
|
+
message: '动态下拉公式括号不完整。'
|
|
7071
|
+
};
|
|
7072
|
+
const terms = splitConcatenation(argument);
|
|
7073
|
+
if (!terms) return {
|
|
7074
|
+
ok: false,
|
|
7075
|
+
message: '动态下拉公式拼接项无效。'
|
|
7076
|
+
};
|
|
7077
|
+
let referenceText = '';
|
|
7078
|
+
for (const term of terms){
|
|
7079
|
+
const parsed = parseDependentListTerm(term.trim());
|
|
7080
|
+
if (!parsed) return {
|
|
7081
|
+
ok: false,
|
|
7082
|
+
message: '动态下拉公式包含不支持的表达式。'
|
|
7083
|
+
};
|
|
7084
|
+
if ('text' === parsed.kind) {
|
|
7085
|
+
referenceText += parsed.value;
|
|
7086
|
+
continue;
|
|
7087
|
+
}
|
|
7088
|
+
const resolvedCell = resolveDependentListCell(content, sheetId, row, column, anchorRow, anchorColumn, parsed);
|
|
7089
|
+
if (!resolvedCell.ok) return resolvedCell;
|
|
7090
|
+
referenceText += resolvedCell.value;
|
|
7091
|
+
}
|
|
7092
|
+
const trimmedReference = referenceText.trim();
|
|
7093
|
+
if (!trimmedReference) return {
|
|
7094
|
+
ok: true,
|
|
7095
|
+
empty: true
|
|
7096
|
+
};
|
|
7097
|
+
const target = resolveSpreadsheetGoToTarget(content, sheetId, trimmedReference);
|
|
7098
|
+
if (!target.ok) return {
|
|
7099
|
+
ok: false,
|
|
7100
|
+
message: `动态下拉来源无法解析:${target.message}`
|
|
7101
|
+
};
|
|
7102
|
+
const area = spreadsheetCellRangeArea(target.target.range);
|
|
7103
|
+
if (target.target.range.row[0] !== target.target.range.row[1] && target.target.range.column[0] !== target.target.range.column[1]) return {
|
|
7104
|
+
ok: false,
|
|
7105
|
+
message: '动态下拉来源只能是一行或一列连续区域。'
|
|
7106
|
+
};
|
|
7107
|
+
if (area > MAX_SPREADSHEET_DEPENDENT_LIST_REFERENCE_CELLS) return {
|
|
7108
|
+
ok: false,
|
|
7109
|
+
message: `动态下拉来源最多读取 ${MAX_SPREADSHEET_DEPENDENT_LIST_REFERENCE_CELLS.toLocaleString('en-US')} 个单元格。`
|
|
7110
|
+
};
|
|
7111
|
+
return {
|
|
7112
|
+
ok: true,
|
|
7113
|
+
displayReference: target.target.displayReference
|
|
7114
|
+
};
|
|
7115
|
+
}
|
|
7116
|
+
function materializeSpreadsheetDependentListsForFortune(sheets, namedRanges = []) {
|
|
7117
|
+
const content = {
|
|
7118
|
+
type: 'spreadsheet',
|
|
7119
|
+
sheets: sheets.map((sheet)=>sheet),
|
|
7120
|
+
namedRanges
|
|
7121
|
+
};
|
|
7122
|
+
return sheets.map((sheet)=>{
|
|
7123
|
+
let dataVerification;
|
|
7124
|
+
let dataValidationRanges;
|
|
7125
|
+
let changed = false;
|
|
7126
|
+
let materializationBudget = MAX_SPREADSHEET_DEPENDENT_LIST_MATERIALIZED_CELLS;
|
|
7127
|
+
const direct = sheet.dataVerification;
|
|
7128
|
+
if (direct) for (const [key, value] of Object.entries(direct)){
|
|
7129
|
+
const coordinate = dependentListCoordinate(key);
|
|
7130
|
+
const item = dependentListItem(value);
|
|
7131
|
+
if (!coordinate || !item || !isSpreadsheetDependentListFormula(item.value1)) continue;
|
|
7132
|
+
const range = dependentListAnchorRange(item, coordinate.row, coordinate.column);
|
|
7133
|
+
const reference = resolveSpreadsheetDependentListReference(content, sheet.id ?? '', coordinate.row, coordinate.column, range.row[0], range.column[0], item.value1);
|
|
7134
|
+
const projected = projectedDependentListItem(item, reference, 'direct');
|
|
7135
|
+
dataVerification ??= {
|
|
7136
|
+
...direct
|
|
7137
|
+
};
|
|
7138
|
+
dataVerification[key] = projected;
|
|
7139
|
+
changed = true;
|
|
7140
|
+
materializationBudget = Math.max(0, materializationBudget - 1);
|
|
7141
|
+
}
|
|
7142
|
+
for (const [entryIndex, entry] of (sheet.dataValidationRanges ?? []).entries()){
|
|
7143
|
+
if (!isSpreadsheetDependentListFormula(entry.item.value1)) continue;
|
|
7144
|
+
const formula = entry.item.value1;
|
|
7145
|
+
const projectedItem = {
|
|
7146
|
+
...entry.item,
|
|
7147
|
+
value1: '',
|
|
7148
|
+
[DEPENDENT_LIST_PROJECTION_KEY]: {
|
|
7149
|
+
kind: 'compact',
|
|
7150
|
+
formula
|
|
7151
|
+
}
|
|
7152
|
+
};
|
|
7153
|
+
dataValidationRanges ??= [
|
|
7154
|
+
...sheet.dataValidationRanges ?? []
|
|
7155
|
+
];
|
|
7156
|
+
dataValidationRanges[entryIndex] = {
|
|
7157
|
+
...entry,
|
|
7158
|
+
item: projectedItem
|
|
7159
|
+
};
|
|
7160
|
+
changed = true;
|
|
7161
|
+
for (const range of entry.ranges){
|
|
7162
|
+
const area = spreadsheetCellRangeArea(range);
|
|
7163
|
+
if (!(area > materializationBudget)) {
|
|
7164
|
+
materializationBudget -= area;
|
|
7165
|
+
for(let row = range.row[0]; row <= range.row[1]; row += 1)for(let column = range.column[0]; column <= range.column[1]; column += 1){
|
|
7166
|
+
const key = `${row}_${column}`;
|
|
7167
|
+
if (direct && Object.hasOwn(direct, key)) continue;
|
|
7168
|
+
const reference = resolveSpreadsheetDependentListReference(content, sheet.id ?? '', row, column, range.row[0], range.column[0], entry.item.value1);
|
|
7169
|
+
dataVerification ??= {
|
|
7170
|
+
...direct ?? {}
|
|
7171
|
+
};
|
|
7172
|
+
dataVerification[key] = projectedDependentListItem(entry.item, reference, 'compact');
|
|
7173
|
+
changed = true;
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7178
|
+
return changed ? {
|
|
7179
|
+
...sheet,
|
|
7180
|
+
dataVerification: dataVerification,
|
|
7181
|
+
dataValidationRanges
|
|
7182
|
+
} : sheet;
|
|
7183
|
+
});
|
|
7184
|
+
}
|
|
7185
|
+
function restoreSpreadsheetDependentListProjections(sheets) {
|
|
7186
|
+
return sheets.map((sheet)=>{
|
|
7187
|
+
const direct = sheet.dataVerification;
|
|
7188
|
+
let nextDirect;
|
|
7189
|
+
if (direct) for (const [key, value] of Object.entries(direct)){
|
|
7190
|
+
const item = dependentListItem(value);
|
|
7191
|
+
const marker = item?.[DEPENDENT_LIST_PROJECTION_KEY];
|
|
7192
|
+
if (marker) {
|
|
7193
|
+
nextDirect ??= {
|
|
7194
|
+
...direct
|
|
7195
|
+
};
|
|
7196
|
+
if ('compact' === marker.kind) delete nextDirect[key];
|
|
7197
|
+
else {
|
|
7198
|
+
const { [DEPENDENT_LIST_PROJECTION_KEY]: _marker, ...rest } = item;
|
|
7199
|
+
nextDirect[key] = {
|
|
7200
|
+
...rest,
|
|
7201
|
+
value1: marker.formula
|
|
7202
|
+
};
|
|
7203
|
+
}
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
let nextRanges;
|
|
7207
|
+
for (const [entryIndex, entry] of (sheet.dataValidationRanges ?? []).entries()){
|
|
7208
|
+
const item = entry.item;
|
|
7209
|
+
const marker = item[DEPENDENT_LIST_PROJECTION_KEY];
|
|
7210
|
+
if (!marker) continue;
|
|
7211
|
+
const { [DEPENDENT_LIST_PROJECTION_KEY]: _marker, ...rest } = item;
|
|
7212
|
+
nextRanges ??= [
|
|
7213
|
+
...sheet.dataValidationRanges ?? []
|
|
7214
|
+
];
|
|
7215
|
+
nextRanges[entryIndex] = {
|
|
7216
|
+
...entry,
|
|
7217
|
+
item: {
|
|
7218
|
+
...rest,
|
|
7219
|
+
value1: marker.formula
|
|
7220
|
+
}
|
|
7221
|
+
};
|
|
7222
|
+
}
|
|
7223
|
+
if (!nextDirect && !nextRanges) return sheet;
|
|
7224
|
+
return {
|
|
7225
|
+
...sheet,
|
|
7226
|
+
dataVerification: nextDirect && Object.keys(nextDirect).length ? nextDirect : void 0,
|
|
7227
|
+
dataValidationRanges: nextRanges
|
|
7228
|
+
};
|
|
7229
|
+
});
|
|
7230
|
+
}
|
|
7231
|
+
function projectedDependentListItem(item, reference, kind) {
|
|
7232
|
+
const { [DEPENDENT_LIST_PROJECTION_KEY]: _previous, ...rest } = item;
|
|
7233
|
+
return {
|
|
7234
|
+
...rest,
|
|
7235
|
+
value1: reference.ok ? reference.displayReference ?? '' : '',
|
|
7236
|
+
[DEPENDENT_LIST_PROJECTION_KEY]: {
|
|
7237
|
+
kind,
|
|
7238
|
+
formula: item.value1
|
|
7239
|
+
}
|
|
7240
|
+
};
|
|
7241
|
+
}
|
|
7242
|
+
function dependentListItem(value) {
|
|
7243
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) return null;
|
|
7244
|
+
if ('string' != typeof value.type) return null;
|
|
7245
|
+
return value;
|
|
7246
|
+
}
|
|
7247
|
+
function dependentListCoordinate(value) {
|
|
7248
|
+
const match = /^(\d+)_(\d+)$/.exec(value);
|
|
7249
|
+
if (!match) return null;
|
|
7250
|
+
const row = Number(match[1]);
|
|
7251
|
+
const column = Number(match[2]);
|
|
7252
|
+
return Number.isSafeInteger(row) && Number.isSafeInteger(column) ? {
|
|
7253
|
+
row,
|
|
7254
|
+
column
|
|
7255
|
+
} : null;
|
|
7256
|
+
}
|
|
7257
|
+
function dependentListAnchorRange(item, row, column) {
|
|
7258
|
+
const ranges = parseSpreadsheetCellRanges(item.rangeTxt);
|
|
7259
|
+
const containing = ranges?.find((range)=>spreadsheetCellRangeContains(range, row, column));
|
|
7260
|
+
return containing ? {
|
|
7261
|
+
row: [
|
|
7262
|
+
containing.row[0],
|
|
7263
|
+
containing.row[0]
|
|
7264
|
+
],
|
|
7265
|
+
column: [
|
|
7266
|
+
containing.column[0],
|
|
7267
|
+
containing.column[0]
|
|
7268
|
+
]
|
|
7269
|
+
} : {
|
|
7270
|
+
row: [
|
|
7271
|
+
row,
|
|
7272
|
+
row
|
|
7273
|
+
],
|
|
7274
|
+
column: [
|
|
7275
|
+
column,
|
|
7276
|
+
column
|
|
7277
|
+
]
|
|
7278
|
+
};
|
|
7279
|
+
}
|
|
7280
|
+
function indirectArgument(value) {
|
|
7281
|
+
const match = /^INDIRECT\s*\(/iu.exec(value.trim());
|
|
7282
|
+
if (!match) return null;
|
|
7283
|
+
const start = match[0].length;
|
|
7284
|
+
let depth = 1;
|
|
7285
|
+
let quoted = false;
|
|
7286
|
+
for(let index = start; index < value.length; index += 1){
|
|
7287
|
+
const character = value[index];
|
|
7288
|
+
if ('"' === character) {
|
|
7289
|
+
if (quoted && '"' === value[index + 1]) {
|
|
7290
|
+
index += 1;
|
|
7291
|
+
continue;
|
|
7292
|
+
}
|
|
7293
|
+
quoted = !quoted;
|
|
7294
|
+
continue;
|
|
7295
|
+
}
|
|
7296
|
+
if (!quoted) {
|
|
7297
|
+
if ('(' === character) depth += 1;
|
|
7298
|
+
if (')' === character) {
|
|
7299
|
+
depth -= 1;
|
|
7300
|
+
if (0 === depth) return value.slice(start, index).trim() && !value.slice(index + 1).trim() ? value.slice(start, index).trim() : null;
|
|
7301
|
+
}
|
|
7302
|
+
}
|
|
7303
|
+
}
|
|
7304
|
+
return null;
|
|
7305
|
+
}
|
|
7306
|
+
function splitConcatenation(value) {
|
|
7307
|
+
const terms = [];
|
|
7308
|
+
let start = 0;
|
|
7309
|
+
let quoted = false;
|
|
7310
|
+
for(let index = 0; index < value.length; index += 1){
|
|
7311
|
+
const character = value[index];
|
|
7312
|
+
if ('"' === character) {
|
|
7313
|
+
if (quoted && '"' === value[index + 1]) {
|
|
7314
|
+
index += 1;
|
|
7315
|
+
continue;
|
|
7316
|
+
}
|
|
7317
|
+
quoted = !quoted;
|
|
7318
|
+
} else if (quoted || '&' !== character) {
|
|
7319
|
+
if (!quoted && ('(' === character || ')' === character)) return null;
|
|
7320
|
+
} else {
|
|
7321
|
+
terms.push(value.slice(start, index).trim());
|
|
7322
|
+
start = index + 1;
|
|
7323
|
+
}
|
|
7324
|
+
}
|
|
7325
|
+
if (quoted) return null;
|
|
7326
|
+
terms.push(value.slice(start).trim());
|
|
7327
|
+
return terms;
|
|
7328
|
+
}
|
|
7329
|
+
function isDependentListTerm(value) {
|
|
7330
|
+
return Boolean(parseDependentListTerm(value));
|
|
7331
|
+
}
|
|
7332
|
+
function parseDependentListTerm(value) {
|
|
7333
|
+
const text = value.trim();
|
|
7334
|
+
if (text.startsWith('"') && text.endsWith('"')) {
|
|
7335
|
+
const inner = text.slice(1, -1);
|
|
7336
|
+
for(let index = 0; index < inner.length; index += 1)if ('"' === inner[index]) {
|
|
7337
|
+
if ('"' === inner[index + 1]) {
|
|
7338
|
+
index += 1;
|
|
7339
|
+
continue;
|
|
7340
|
+
}
|
|
7341
|
+
return null;
|
|
7342
|
+
}
|
|
7343
|
+
return {
|
|
7344
|
+
kind: 'text',
|
|
7345
|
+
value: inner.replaceAll('""', '"')
|
|
7346
|
+
};
|
|
7347
|
+
}
|
|
7348
|
+
const match = /^(?:(?:'((?:[^']|'')+)'|([^'!\[\]\s]+))!)?(\$?)([A-Z]{1,3})(\$?)([1-9]\d*)$/iu.exec(text);
|
|
7349
|
+
if (!match) return null;
|
|
7350
|
+
const column = decodeColumn(match[4]);
|
|
7351
|
+
const row = Number(match[6]) - 1;
|
|
7352
|
+
if (column < 0 || row < 0 || column >= 16384 || row >= 1048576) return null;
|
|
7353
|
+
return {
|
|
7354
|
+
kind: 'cell',
|
|
7355
|
+
qualifier: match[1] ? match[1].replaceAll("''", "'") : match[2],
|
|
7356
|
+
column,
|
|
7357
|
+
row,
|
|
7358
|
+
absoluteColumn: Boolean(match[3]),
|
|
7359
|
+
absoluteRow: Boolean(match[5])
|
|
7360
|
+
};
|
|
7361
|
+
}
|
|
7362
|
+
function resolveDependentListCell(content, sheetId, row, column, anchorRow, anchorColumn, term) {
|
|
7363
|
+
const fallback = content.sheets.find((sheet)=>sheet.id === sheetId);
|
|
7364
|
+
const sheet = term.qualifier ? content.sheets.find((candidate)=>candidate.name.trim().toLocaleLowerCase() === term.qualifier?.trim().toLocaleLowerCase()) : fallback;
|
|
7365
|
+
if (!sheet) return {
|
|
7366
|
+
ok: false,
|
|
7367
|
+
message: '动态下拉公式引用了不存在的工作表。'
|
|
7368
|
+
};
|
|
7369
|
+
const resolvedRow = term.absoluteRow ? term.row : row + (term.row - anchorRow);
|
|
7370
|
+
const resolvedColumn = term.absoluteColumn ? term.column : column + (term.column - anchorColumn);
|
|
7371
|
+
if (resolvedRow < 0 || resolvedColumn < 0 || resolvedRow >= 1048576 || resolvedColumn >= 16384) return {
|
|
7372
|
+
ok: false,
|
|
7373
|
+
message: '动态下拉公式引用超出了工作表边界。'
|
|
7374
|
+
};
|
|
7375
|
+
const cell = spreadsheetDependentListCellAt(sheet, resolvedRow, resolvedColumn);
|
|
7376
|
+
if (!cell) return {
|
|
7377
|
+
ok: true,
|
|
7378
|
+
value: ''
|
|
7379
|
+
};
|
|
7380
|
+
if ('string' == typeof cell.f && cell.f && void 0 === cell.v) return {
|
|
7381
|
+
ok: false,
|
|
7382
|
+
message: '动态下拉公式引用了尚未计算的公式单元格。'
|
|
7383
|
+
};
|
|
7384
|
+
const displayed = cell.m ?? cell.v;
|
|
7385
|
+
return {
|
|
7386
|
+
ok: true,
|
|
7387
|
+
value: null == displayed ? '' : String(displayed)
|
|
7388
|
+
};
|
|
7389
|
+
}
|
|
7390
|
+
function spreadsheetDependentListCellAt(sheet, row, column) {
|
|
7391
|
+
const direct = sheet.data?.[row]?.[column];
|
|
7392
|
+
if (direct) return direct;
|
|
7393
|
+
const entry = sheet.celldata?.find((candidate)=>candidate.r === row && candidate.c === column);
|
|
7394
|
+
if (!entry || !entry.v || 'object' != typeof entry.v) return null;
|
|
7395
|
+
return entry.v;
|
|
7396
|
+
}
|
|
7397
|
+
function decodeColumn(value) {
|
|
7398
|
+
let result = 0;
|
|
7399
|
+
for (const character of value.toUpperCase())result = 26 * result + character.charCodeAt(0) - 64;
|
|
7400
|
+
return result - 1;
|
|
7401
|
+
}
|
|
3148
7402
|
const A3S_DIAGONAL_BORDER_KEY = 'a3sDiagonal';
|
|
3149
7403
|
function spreadsheetDiagonalBorderFromCellValue(value) {
|
|
3150
|
-
if (!
|
|
7404
|
+
if (!work_spreadsheet_diagonal_borders_isRecord(value)) return;
|
|
3151
7405
|
const metadata = value[A3S_DIAGONAL_BORDER_KEY];
|
|
3152
|
-
if (
|
|
7406
|
+
if (work_spreadsheet_diagonal_borders_isRecord(metadata)) {
|
|
3153
7407
|
const up = metadata.up;
|
|
3154
7408
|
const down = metadata.down;
|
|
3155
7409
|
const line = spreadsheetDiagonalBorderLine(metadata.line);
|
|
@@ -3189,13 +7443,13 @@ function spreadsheetCellValueWithDiagonalBorder(value, border) {
|
|
|
3189
7443
|
return next;
|
|
3190
7444
|
}
|
|
3191
7445
|
function spreadsheetDiagonalBorderLine(value) {
|
|
3192
|
-
if (!
|
|
7446
|
+
if (!work_spreadsheet_diagonal_borders_isRecord(value)) return null;
|
|
3193
7447
|
return 'string' == typeof value.color && 'string' == typeof value.style ? {
|
|
3194
7448
|
color: value.color,
|
|
3195
7449
|
style: value.style
|
|
3196
7450
|
} : null;
|
|
3197
7451
|
}
|
|
3198
|
-
function
|
|
7452
|
+
function work_spreadsheet_diagonal_borders_isRecord(value) {
|
|
3199
7453
|
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
3200
7454
|
}
|
|
3201
7455
|
const spreadsheetTextOrientationIds = [
|
|
@@ -3393,209 +7647,58 @@ function directXlsxFontStyle(cell) {
|
|
|
3393
7647
|
}
|
|
3394
7648
|
function directXlsxAlignment(cell) {
|
|
3395
7649
|
const alignment = {};
|
|
3396
|
-
if (void 0 !== cell.ht) alignment.horizontal = 0 === Number(cell.ht) ? 'center' : 2 === Number(cell.ht) ? 'right' : 'left';
|
|
3397
|
-
if (void 0 !== cell.vt) alignment.vertical = 0 === Number(cell.vt) ? 'center' : 1 === Number(cell.vt) ? 'top' : 'bottom';
|
|
3398
|
-
if (void 0 !== cell.tb) alignment.wrapText = '2' === cell.tb;
|
|
3399
|
-
const rotation = spreadsheetTextOrientationXlsxValueFromCell(cell);
|
|
3400
|
-
if (null !== rotation) alignment.textRotation = rotation;
|
|
3401
|
-
return Object.keys(alignment).length ? alignment : null;
|
|
3402
|
-
}
|
|
3403
|
-
function xlsxStyleCollectionIndex(collection, childName, value) {
|
|
3404
|
-
const index = nonNegativeInteger(value) ?? 0;
|
|
3405
|
-
return directChildren(collection, childName)[index] ? index : 0;
|
|
3406
|
-
}
|
|
3407
|
-
function xlsxColorMatches(element, value, colors, fallback) {
|
|
3408
|
-
const rgb = xlsxRgbColor(value);
|
|
3409
|
-
const resolved = resolveXlsxColor(element, colors) ?? fallback;
|
|
3410
|
-
return Boolean(rgb && resolved && `#${rgb.slice(-6).toLowerCase()}` === resolved.toLowerCase());
|
|
3411
|
-
}
|
|
3412
|
-
function xlsxToggleEnabled(element) {
|
|
3413
|
-
if (!element) return false;
|
|
3414
|
-
const value = attribute(element, 'val')?.trim().toLowerCase();
|
|
3415
|
-
return '0' !== value && 'false' !== value && 'off' !== value;
|
|
3416
|
-
}
|
|
3417
|
-
function xlsxUnderlineStyle(element) {
|
|
3418
|
-
return element ? spreadsheetUnderlineStyle(spreadsheetUnderlineCellValueFromXlsx(attribute(element, 'val'))) : 'none';
|
|
3419
|
-
}
|
|
3420
|
-
function xlsxBorderLineMatches(element, line, colors, semanticColor) {
|
|
3421
|
-
const style = element ? attribute(element, 'style') : null;
|
|
3422
|
-
if (!line) return !style;
|
|
3423
|
-
return style === line.style && (semanticColor ? xlsxColorElementMatchesOrigin(element ? directChild(element, 'color') : void 0, semanticColor) : xlsxColorMatches(element ? directChild(element, 'color') : void 0, line.color, colors, '#000000'));
|
|
3424
|
-
}
|
|
3425
|
-
function activeXlsxSemanticColorOrigin(origin, value, palette) {
|
|
3426
|
-
return origin && xlsxSemanticColorMatchesValue(origin, value) && xlsxSemanticColorOriginSupported(origin, palette) ? origin : void 0;
|
|
3427
|
-
}
|
|
3428
|
-
function xlsxBooleanAttribute(element, name) {
|
|
3429
|
-
if (!element) return false;
|
|
3430
|
-
const value = attribute(element, name)?.trim().toLowerCase();
|
|
3431
|
-
return '1' === value || 'true' === value || 'on' === value;
|
|
3432
|
-
}
|
|
3433
|
-
function xlsxAlignmentMatches(xf, style) {
|
|
3434
|
-
const alignment = directChild(xf, 'alignment');
|
|
3435
|
-
if (void 0 !== style.horizontal && attribute(alignment ?? xf, 'horizontal') !== style.horizontal) return false;
|
|
3436
|
-
if (void 0 !== style.vertical && attribute(alignment ?? xf, 'vertical') !== style.vertical) return false;
|
|
3437
|
-
if (void 0 !== style.wrapText && (!alignment || null === attribute(alignment, 'wrapText') || xlsxBooleanAttribute(alignment, 'wrapText') !== style.wrapText)) return false;
|
|
3438
|
-
if (void 0 !== style.textRotation) {
|
|
3439
|
-
const rotation = nonNegativeInteger(alignment ? attribute(alignment, 'textRotation') : null);
|
|
3440
|
-
if (rotation !== style.textRotation) return false;
|
|
3441
|
-
}
|
|
3442
|
-
return true;
|
|
3443
|
-
}
|
|
3444
|
-
function nonNegativeInteger(value) {
|
|
3445
|
-
if (null === value || !/^\d+$/.test(value)) return null;
|
|
3446
|
-
const parsed = Number(value);
|
|
3447
|
-
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
3448
|
-
}
|
|
3449
|
-
const xlsxNativeFillCellKeys = [
|
|
3450
|
-
XLSX_PATTERN_FILL_CELL_KEY,
|
|
3451
|
-
XLSX_GRADIENT_FILL_CELL_KEY
|
|
3452
|
-
];
|
|
3453
|
-
function activeXlsxNativeFill(cell) {
|
|
3454
|
-
const gradient = activeXlsxGradientFill(cell);
|
|
3455
|
-
const pattern = activeXlsxPatternFill(cell);
|
|
3456
|
-
if (Boolean(gradient) === Boolean(pattern)) return;
|
|
3457
|
-
return gradient ? {
|
|
3458
|
-
kind: 'gradient',
|
|
3459
|
-
value: gradient
|
|
3460
|
-
} : {
|
|
3461
|
-
kind: 'pattern',
|
|
3462
|
-
value: pattern
|
|
3463
|
-
};
|
|
3464
|
-
}
|
|
3465
|
-
function deleteXlsxNativeFills(cell) {
|
|
3466
|
-
deleteXlsxGradientFill(cell);
|
|
3467
|
-
deleteXlsxPatternFill(cell);
|
|
3468
|
-
}
|
|
3469
|
-
function xlsxNativeFillSemanticColors(fill) {
|
|
3470
|
-
if (!fill) return [];
|
|
3471
|
-
return 'gradient' === fill.kind ? xlsxGradientFillSemanticColors(fill.value) : xlsxPatternFillSemanticColors(fill.value);
|
|
3472
|
-
}
|
|
3473
|
-
function xlsxNativeFillKey(fill) {
|
|
3474
|
-
if (!fill) return '';
|
|
3475
|
-
return 'gradient' === fill.kind ? `gradient:${xlsxGradientFillKey(fill.value)}` : `pattern:${xlsxPatternFillKey(fill.value)}`;
|
|
3476
|
-
}
|
|
3477
|
-
const MAX_XLSX_RICH_TEXT_CELL_CHARACTERS = 32767;
|
|
3478
|
-
const MAX_XLSX_RICH_TEXT_RUNS_PER_CELL = 512;
|
|
3479
|
-
const MAX_XLSX_RICH_TEXT_FONT_NAME_CHARACTERS = 128;
|
|
3480
|
-
const MAX_XLSX_RICH_TEXT_FONT_SIZE = 409;
|
|
3481
|
-
function normalizeXlsxRichTextEditSource(cell) {
|
|
3482
|
-
if (cell?.f) return null;
|
|
3483
|
-
if (cell) {
|
|
3484
|
-
const rich = normalizeXlsxRichTextCell(cell);
|
|
3485
|
-
if (rich) return rich;
|
|
3486
|
-
}
|
|
3487
|
-
const text = cell?.v;
|
|
3488
|
-
if (void 0 !== text && ('string' != typeof text || text.length > MAX_XLSX_RICH_TEXT_CELL_CHARACTERS || !validXlsxRichText(text))) return null;
|
|
3489
|
-
const value = text ?? '';
|
|
3490
|
-
return {
|
|
3491
|
-
runs: value ? [
|
|
3492
|
-
xlsxRichTextBaseRun(cell, value)
|
|
3493
|
-
] : [],
|
|
3494
|
-
text: value
|
|
3495
|
-
};
|
|
3496
|
-
}
|
|
3497
|
-
function xlsxRichTextBaseRun(cell, text) {
|
|
3498
|
-
const run = {
|
|
3499
|
-
v: text
|
|
3500
|
-
};
|
|
3501
|
-
if (1 === Number(cell?.bl)) run.bl = 1;
|
|
3502
|
-
if (1 === Number(cell?.it)) run.it = 1;
|
|
3503
|
-
if (1 === Number(cell?.cl)) run.cl = 1;
|
|
3504
|
-
const underline = Number(cell?.un);
|
|
3505
|
-
if (Number.isSafeInteger(underline) && underline >= 1 && underline <= 4) run.un = underline;
|
|
3506
|
-
if ('string' == typeof cell?.ff && cell.ff.trim() && cell.ff.trim().length <= MAX_XLSX_RICH_TEXT_FONT_NAME_CHARACTERS) run.ff = cell.ff.trim();
|
|
3507
|
-
const size = Number(cell?.fs);
|
|
3508
|
-
if (Number.isFinite(size) && size >= 1 && size <= MAX_XLSX_RICH_TEXT_FONT_SIZE) run.fs = size;
|
|
3509
|
-
const color = normalizeXlsxRichTextColor(cell?.fc);
|
|
3510
|
-
if (color) {
|
|
3511
|
-
run.fc = color;
|
|
3512
|
-
const origin = normalizeXlsxSemanticColorOrigin(xlsxCellStyleOrigin(cell ?? {})?.fontColor);
|
|
3513
|
-
if (origin && xlsxSemanticColorMatchesValue(origin, color)) run.a3sXlsxColorOrigin = origin;
|
|
3514
|
-
}
|
|
3515
|
-
return run;
|
|
3516
|
-
}
|
|
3517
|
-
function normalizeXlsxRichTextCell(cell) {
|
|
3518
|
-
if (cell.f || cell.ct?.t !== 'inlineStr' || !Array.isArray(cell.ct.s)) return null;
|
|
3519
|
-
if (!cell.ct.s.length || cell.ct.s.length > MAX_XLSX_RICH_TEXT_RUNS_PER_CELL) return null;
|
|
3520
|
-
const runs = [];
|
|
3521
|
-
let characterCount = 0;
|
|
3522
|
-
for (const candidate of cell.ct.s){
|
|
3523
|
-
const run = normalizeXlsxRichTextRun(candidate);
|
|
3524
|
-
if (!run) return null;
|
|
3525
|
-
if (run.v) {
|
|
3526
|
-
characterCount += run.v.length;
|
|
3527
|
-
if (characterCount > MAX_XLSX_RICH_TEXT_CELL_CHARACTERS) return null;
|
|
3528
|
-
runs.push(run);
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
const text = runs.map((run)=>run.v).join('');
|
|
3532
|
-
return runs.length && text ? {
|
|
3533
|
-
runs,
|
|
3534
|
-
text
|
|
3535
|
-
} : null;
|
|
3536
|
-
}
|
|
3537
|
-
function normalizeXlsxRichTextRun(value) {
|
|
3538
|
-
if (!work_xlsx_rich_text_model_isRecord(value) || 'string' != typeof value.v || !validXlsxRichText(value.v)) return null;
|
|
3539
|
-
const run = {
|
|
3540
|
-
v: value.v
|
|
3541
|
-
};
|
|
3542
|
-
copyToggle(value, run, 'bl');
|
|
3543
|
-
copyToggle(value, run, 'it');
|
|
3544
|
-
copyToggle(value, run, 'cl');
|
|
3545
|
-
if ('string' == typeof value.ff && value.ff.trim() && value.ff.trim().length <= MAX_XLSX_RICH_TEXT_FONT_NAME_CHARACTERS) run.ff = value.ff.trim();
|
|
3546
|
-
if ('number' == typeof value.fs && Number.isFinite(value.fs) && value.fs >= 1 && value.fs <= MAX_XLSX_RICH_TEXT_FONT_SIZE) run.fs = value.fs;
|
|
3547
|
-
const color = normalizeXlsxRichTextColor(value.fc);
|
|
3548
|
-
if (color) run.fc = color;
|
|
3549
|
-
const underline = Number(value.un);
|
|
3550
|
-
if (Number.isSafeInteger(underline) && underline >= 0 && underline <= 4 && void 0 !== value.un) run.un = underline;
|
|
3551
|
-
const colorOrigin = normalizeXlsxSemanticColorOrigin(value.a3sXlsxColorOrigin);
|
|
3552
|
-
if (colorOrigin) run.a3sXlsxColorOrigin = colorOrigin;
|
|
3553
|
-
return run;
|
|
7650
|
+
if (void 0 !== cell.ht) alignment.horizontal = 0 === Number(cell.ht) ? 'center' : 2 === Number(cell.ht) ? 'right' : 'left';
|
|
7651
|
+
if (void 0 !== cell.vt) alignment.vertical = 0 === Number(cell.vt) ? 'center' : 1 === Number(cell.vt) ? 'top' : 'bottom';
|
|
7652
|
+
if (void 0 !== cell.tb) alignment.wrapText = '2' === cell.tb;
|
|
7653
|
+
const rotation = spreadsheetTextOrientationXlsxValueFromCell(cell);
|
|
7654
|
+
if (null !== rotation) alignment.textRotation = rotation;
|
|
7655
|
+
return Object.keys(alignment).length ? alignment : null;
|
|
3554
7656
|
}
|
|
3555
|
-
function
|
|
7657
|
+
function xlsxStyleCollectionIndex(collection, childName, value) {
|
|
7658
|
+
const index = nonNegativeInteger(value) ?? 0;
|
|
7659
|
+
return directChildren(collection, childName)[index] ? index : 0;
|
|
7660
|
+
}
|
|
7661
|
+
function xlsxColorMatches(element, value, colors, fallback) {
|
|
3556
7662
|
const rgb = xlsxRgbColor(value);
|
|
3557
|
-
|
|
7663
|
+
const resolved = resolveXlsxColor(element, colors) ?? fallback;
|
|
7664
|
+
return Boolean(rgb && resolved && `#${rgb.slice(-6).toLowerCase()}` === resolved.toLowerCase());
|
|
3558
7665
|
}
|
|
3559
|
-
function
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
const previous = result.at(-1);
|
|
3564
|
-
if (previous && sameXlsxRichTextRunStyle(previous, run)) previous.v += run.v;
|
|
3565
|
-
else result.push({
|
|
3566
|
-
...run
|
|
3567
|
-
});
|
|
3568
|
-
}
|
|
3569
|
-
return result;
|
|
7666
|
+
function xlsxToggleEnabled(element) {
|
|
7667
|
+
if (!element) return false;
|
|
7668
|
+
const value = attribute(element, 'val')?.trim().toLowerCase();
|
|
7669
|
+
return '0' !== value && 'false' !== value && 'off' !== value;
|
|
3570
7670
|
}
|
|
3571
|
-
function
|
|
3572
|
-
return
|
|
7671
|
+
function xlsxUnderlineStyle(element) {
|
|
7672
|
+
return element ? spreadsheetUnderlineStyle(spreadsheetUnderlineCellValueFromXlsx(attribute(element, 'val'))) : 'none';
|
|
3573
7673
|
}
|
|
3574
|
-
function
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
if (isHighSurrogate(code) && index + 1 < value.length && isLowSurrogate(value.charCodeAt(index + 1))) {
|
|
3579
|
-
index += 1;
|
|
3580
|
-
continue;
|
|
3581
|
-
}
|
|
3582
|
-
return false;
|
|
3583
|
-
}
|
|
3584
|
-
}
|
|
3585
|
-
return true;
|
|
7674
|
+
function xlsxBorderLineMatches(element, line, colors, semanticColor) {
|
|
7675
|
+
const style = element ? attribute(element, 'style') : null;
|
|
7676
|
+
if (!line) return !style;
|
|
7677
|
+
return style === line.style && (semanticColor ? xlsxColorElementMatchesOrigin(element ? directChild(element, 'color') : void 0, semanticColor) : xlsxColorMatches(element ? directChild(element, 'color') : void 0, line.color, colors, '#000000'));
|
|
3586
7678
|
}
|
|
3587
|
-
function
|
|
3588
|
-
return
|
|
7679
|
+
function activeXlsxSemanticColorOrigin(origin, value, palette) {
|
|
7680
|
+
return origin && xlsxSemanticColorMatchesValue(origin, value) && xlsxSemanticColorOriginSupported(origin, palette) ? origin : void 0;
|
|
3589
7681
|
}
|
|
3590
|
-
function
|
|
3591
|
-
|
|
7682
|
+
function xlsxBooleanAttribute(element, name) {
|
|
7683
|
+
if (!element) return false;
|
|
7684
|
+
const value = attribute(element, name)?.trim().toLowerCase();
|
|
7685
|
+
return '1' === value || 'true' === value || 'on' === value;
|
|
3592
7686
|
}
|
|
3593
|
-
function
|
|
3594
|
-
|
|
3595
|
-
|
|
7687
|
+
function xlsxAlignmentMatches(xf, style) {
|
|
7688
|
+
const alignment = directChild(xf, 'alignment');
|
|
7689
|
+
if (void 0 !== style.horizontal && attribute(alignment ?? xf, 'horizontal') !== style.horizontal) return false;
|
|
7690
|
+
if (void 0 !== style.vertical && attribute(alignment ?? xf, 'vertical') !== style.vertical) return false;
|
|
7691
|
+
if (void 0 !== style.wrapText && (!alignment || null === attribute(alignment, 'wrapText') || xlsxBooleanAttribute(alignment, 'wrapText') !== style.wrapText)) return false;
|
|
7692
|
+
if (void 0 !== style.textRotation) {
|
|
7693
|
+
const rotation = nonNegativeInteger(alignment ? attribute(alignment, 'textRotation') : null);
|
|
7694
|
+
if (rotation !== style.textRotation) return false;
|
|
7695
|
+
}
|
|
7696
|
+
return true;
|
|
3596
7697
|
}
|
|
3597
|
-
function
|
|
3598
|
-
|
|
7698
|
+
function nonNegativeInteger(value) {
|
|
7699
|
+
if (null === value || !/^\d+$/.test(value)) return null;
|
|
7700
|
+
const parsed = Number(value);
|
|
7701
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
3599
7702
|
}
|
|
3600
7703
|
const MAX_XLSX_SHARED_RICH_TEXT_ITEMS = 10000;
|
|
3601
7704
|
function createXlsxRichTextReadContext(options) {
|
|
@@ -3733,11 +7836,11 @@ function writeXlsxRichTextCells(worksheet, sheet, semanticPalette) {
|
|
|
3733
7836
|
}
|
|
3734
7837
|
}
|
|
3735
7838
|
}
|
|
3736
|
-
function readRichSharedStrings(
|
|
3737
|
-
if (!
|
|
7839
|
+
function readRichSharedStrings(document1, colors) {
|
|
7840
|
+
if (!document1) return new Map();
|
|
3738
7841
|
const result = new Map();
|
|
3739
7842
|
let parsedRuns = 0;
|
|
3740
|
-
for (const [index, item] of directChildren(
|
|
7843
|
+
for (const [index, item] of directChildren(document1.documentElement, 'si').entries()){
|
|
3741
7844
|
if (result.size >= MAX_XLSX_SHARED_RICH_TEXT_ITEMS || parsedRuns >= 100000) break;
|
|
3742
7845
|
const runs = readRichTextRuns(item, colors);
|
|
3743
7846
|
if (runs && !(parsedRuns + runs.length > 100000)) {
|
|
@@ -3795,14 +7898,14 @@ function readRichTextRun(element, value, colors) {
|
|
|
3795
7898
|
function writeRichTextCell(element, richText, semanticPalette) {
|
|
3796
7899
|
for (const child of directChildren(element))if ('v' === child.localName || 'is' === child.localName) child.remove();
|
|
3797
7900
|
element.setAttribute('t', 'inlineStr');
|
|
3798
|
-
const
|
|
3799
|
-
const namespace =
|
|
3800
|
-
const inlineString =
|
|
7901
|
+
const document1 = element.ownerDocument;
|
|
7902
|
+
const namespace = document1.documentElement.namespaceURI;
|
|
7903
|
+
const inlineString = document1.createElementNS(namespace, 'is');
|
|
3801
7904
|
for (const run of richText.runs){
|
|
3802
|
-
const runElement =
|
|
3803
|
-
const properties = writeRichTextRunProperties(
|
|
7905
|
+
const runElement = document1.createElementNS(namespace, 'r');
|
|
7906
|
+
const properties = writeRichTextRunProperties(document1, run, semanticPalette);
|
|
3804
7907
|
if (properties) runElement.append(properties);
|
|
3805
|
-
const text =
|
|
7908
|
+
const text = document1.createElementNS(namespace, 't');
|
|
3806
7909
|
if (/^\s|\s$/.test(run.v)) text.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
|
3807
7910
|
text.textContent = run.v;
|
|
3808
7911
|
runElement.append(text);
|
|
@@ -3810,17 +7913,17 @@ function writeRichTextCell(element, richText, semanticPalette) {
|
|
|
3810
7913
|
}
|
|
3811
7914
|
element.insertBefore(inlineString, directChildren(element).find((child)=>'extLst' === child.localName) ?? null);
|
|
3812
7915
|
}
|
|
3813
|
-
function writeRichTextRunProperties(
|
|
3814
|
-
const namespace =
|
|
3815
|
-
const properties =
|
|
7916
|
+
function writeRichTextRunProperties(document1, run, semanticPalette) {
|
|
7917
|
+
const namespace = document1.documentElement.namespaceURI;
|
|
7918
|
+
const properties = document1.createElementNS(namespace, 'rPr');
|
|
3816
7919
|
const appendValue = (name, value)=>{
|
|
3817
|
-
const element =
|
|
7920
|
+
const element = document1.createElementNS(namespace, name);
|
|
3818
7921
|
element.setAttribute('val', value);
|
|
3819
7922
|
properties.append(element);
|
|
3820
7923
|
};
|
|
3821
7924
|
const appendToggle = (name, enabled)=>{
|
|
3822
7925
|
if (!enabled) return;
|
|
3823
|
-
const element =
|
|
7926
|
+
const element = document1.createElementNS(namespace, name);
|
|
3824
7927
|
element.setAttribute('val', '1');
|
|
3825
7928
|
properties.append(element);
|
|
3826
7929
|
};
|
|
@@ -3829,7 +7932,7 @@ function writeRichTextRunProperties(document, run, semanticPalette) {
|
|
|
3829
7932
|
appendToggle('i', 1 === run.it);
|
|
3830
7933
|
appendToggle('strike', 1 === run.cl);
|
|
3831
7934
|
if (run.fc) {
|
|
3832
|
-
const color =
|
|
7935
|
+
const color = document1.createElementNS(namespace, 'color');
|
|
3833
7936
|
const semanticOrigin = activeXlsxSemanticColorOrigin(normalizeXlsxSemanticColorOrigin(run.a3sXlsxColorOrigin), run.fc, semanticPalette);
|
|
3834
7937
|
if (semanticOrigin) applyXlsxSemanticColorOrigin(color, semanticOrigin);
|
|
3835
7938
|
else {
|
|
@@ -3863,184 +7966,6 @@ function work_xlsx_rich_text_nonNegativeInteger(value) {
|
|
|
3863
7966
|
function work_xlsx_rich_text_isRecord(value) {
|
|
3864
7967
|
return 'object' == typeof value && null !== value && !Array.isArray(value);
|
|
3865
7968
|
}
|
|
3866
|
-
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES = 256;
|
|
3867
|
-
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS = 128;
|
|
3868
|
-
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS = 4096;
|
|
3869
|
-
const MAX_SPREADSHEET_SORT_USER_CUSTOM_LISTS = 32;
|
|
3870
|
-
function parseSpreadsheetSortCustomList(text) {
|
|
3871
|
-
return validateSpreadsheetSortCustomList(text.split(/\r?\n|,|,/u).map((entry)=>entry.trim()).filter(Boolean));
|
|
3872
|
-
}
|
|
3873
|
-
function validateSpreadsheetSortCustomList(input) {
|
|
3874
|
-
if (!Array.isArray(input)) return customListError('invalid-entry');
|
|
3875
|
-
if (input.length < 2) return customListError('not-enough-entries');
|
|
3876
|
-
if (input.length > MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES) return customListError('too-many-entries');
|
|
3877
|
-
const entries = [];
|
|
3878
|
-
const seen = new Set();
|
|
3879
|
-
let totalCodePoints = 0;
|
|
3880
|
-
for (const candidate of input){
|
|
3881
|
-
if ('string' != typeof candidate) return customListError('invalid-entry');
|
|
3882
|
-
const entry = candidate.trim();
|
|
3883
|
-
if (!entry) return customListError('invalid-entry');
|
|
3884
|
-
const codePoints = Array.from(entry).length;
|
|
3885
|
-
if (codePoints > MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS) return customListError('entry-too-long');
|
|
3886
|
-
totalCodePoints += codePoints;
|
|
3887
|
-
if (totalCodePoints > MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS) return customListError('list-too-long');
|
|
3888
|
-
const matchKey = spreadsheetSortCustomListMatchKey(entry);
|
|
3889
|
-
if (seen.has(matchKey)) return customListError('duplicate-entry');
|
|
3890
|
-
seen.add(matchKey);
|
|
3891
|
-
entries.push(entry);
|
|
3892
|
-
}
|
|
3893
|
-
return {
|
|
3894
|
-
ok: true,
|
|
3895
|
-
entries
|
|
3896
|
-
};
|
|
3897
|
-
}
|
|
3898
|
-
function createSpreadsheetSortCustomList(entries, source = 'session') {
|
|
3899
|
-
const validation = validateSpreadsheetSortCustomList(entries);
|
|
3900
|
-
if (!validation.ok) return null;
|
|
3901
|
-
const normalized = Object.freeze([
|
|
3902
|
-
...validation.entries
|
|
3903
|
-
]);
|
|
3904
|
-
return Object.freeze({
|
|
3905
|
-
source,
|
|
3906
|
-
entries: normalized,
|
|
3907
|
-
label: spreadsheetSortCustomListLabel(normalized)
|
|
3908
|
-
});
|
|
3909
|
-
}
|
|
3910
|
-
function mergeSpreadsheetSortCustomLists(lists) {
|
|
3911
|
-
const merged = [
|
|
3912
|
-
...SPREADSHEET_SORT_BUILT_IN_CUSTOM_LISTS
|
|
3913
|
-
];
|
|
3914
|
-
let userListCount = 0;
|
|
3915
|
-
for (const candidate of lists){
|
|
3916
|
-
if ('stored' !== candidate.source && 'session' !== candidate.source) continue;
|
|
3917
|
-
const list = createSpreadsheetSortCustomList(candidate.entries, candidate.source);
|
|
3918
|
-
if (!(!list || merged.some((item)=>spreadsheetSortCustomListsEqual(item.entries, list.entries)))) {
|
|
3919
|
-
if (userListCount >= MAX_SPREADSHEET_SORT_USER_CUSTOM_LISTS) break;
|
|
3920
|
-
merged.push(list);
|
|
3921
|
-
userListCount += 1;
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
return Object.freeze(merged);
|
|
3925
|
-
}
|
|
3926
|
-
function spreadsheetSortCustomListsEqual(left, right) {
|
|
3927
|
-
return left.length === right.length && left.every((entry, index)=>spreadsheetSortCustomListMatchKey(entry) === spreadsheetSortCustomListMatchKey(right[index] ?? ''));
|
|
3928
|
-
}
|
|
3929
|
-
function spreadsheetSortCustomListMatchKey(value) {
|
|
3930
|
-
return String(value).normalize('NFKC').trim().toLocaleLowerCase('zh-CN');
|
|
3931
|
-
}
|
|
3932
|
-
function spreadsheetSortCustomListLabel(entries) {
|
|
3933
|
-
const visible = entries.slice(0, 3).map((entry)=>truncateSpreadsheetSortCustomListLabelEntry(entry));
|
|
3934
|
-
return `${visible.join(' → ')}${entries.length > 3 ? ' → …' : ''}`;
|
|
3935
|
-
}
|
|
3936
|
-
function truncateSpreadsheetSortCustomListLabelEntry(entry) {
|
|
3937
|
-
const characters = Array.from(entry);
|
|
3938
|
-
return characters.length > 18 ? `${characters.slice(0, 17).join('')}…` : entry;
|
|
3939
|
-
}
|
|
3940
|
-
function customListError(code) {
|
|
3941
|
-
const messages = {
|
|
3942
|
-
'duplicate-entry': '自定义序列不能包含重复项(忽略大小写和全半角差异)。',
|
|
3943
|
-
'entry-too-long': `每个序列项最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS} 个字符。`,
|
|
3944
|
-
'invalid-entry': '自定义序列只能包含非空文本项。',
|
|
3945
|
-
'list-too-long': `一个自定义序列最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS.toLocaleString('en-US')} 个字符。`,
|
|
3946
|
-
'not-enough-entries': '自定义序列至少需要两个项目。',
|
|
3947
|
-
'too-many-entries': `一个自定义序列最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES} 个项目。`
|
|
3948
|
-
};
|
|
3949
|
-
return {
|
|
3950
|
-
ok: false,
|
|
3951
|
-
code,
|
|
3952
|
-
message: messages[code]
|
|
3953
|
-
};
|
|
3954
|
-
}
|
|
3955
|
-
function builtInCustomList(label, entries) {
|
|
3956
|
-
return Object.freeze({
|
|
3957
|
-
source: 'built-in',
|
|
3958
|
-
entries: Object.freeze([
|
|
3959
|
-
...entries
|
|
3960
|
-
]),
|
|
3961
|
-
label
|
|
3962
|
-
});
|
|
3963
|
-
}
|
|
3964
|
-
const SPREADSHEET_SORT_BUILT_IN_CUSTOM_LISTS = Object.freeze([
|
|
3965
|
-
builtInCustomList('月份(一月 → 十二月)', [
|
|
3966
|
-
'一月',
|
|
3967
|
-
'二月',
|
|
3968
|
-
'三月',
|
|
3969
|
-
'四月',
|
|
3970
|
-
'五月',
|
|
3971
|
-
'六月',
|
|
3972
|
-
'七月',
|
|
3973
|
-
'八月',
|
|
3974
|
-
'九月',
|
|
3975
|
-
'十月',
|
|
3976
|
-
'十一月',
|
|
3977
|
-
'十二月'
|
|
3978
|
-
]),
|
|
3979
|
-
builtInCustomList('星期(星期日 → 星期六)', [
|
|
3980
|
-
'星期日',
|
|
3981
|
-
'星期一',
|
|
3982
|
-
'星期二',
|
|
3983
|
-
'星期三',
|
|
3984
|
-
'星期四',
|
|
3985
|
-
'星期五',
|
|
3986
|
-
'星期六'
|
|
3987
|
-
]),
|
|
3988
|
-
builtInCustomList('周(周日 → 周六)', [
|
|
3989
|
-
'周日',
|
|
3990
|
-
'周一',
|
|
3991
|
-
'周二',
|
|
3992
|
-
'周三',
|
|
3993
|
-
'周四',
|
|
3994
|
-
'周五',
|
|
3995
|
-
'周六'
|
|
3996
|
-
]),
|
|
3997
|
-
builtInCustomList('月份(January → December)', [
|
|
3998
|
-
'January',
|
|
3999
|
-
'February',
|
|
4000
|
-
'March',
|
|
4001
|
-
'April',
|
|
4002
|
-
'May',
|
|
4003
|
-
'June',
|
|
4004
|
-
'July',
|
|
4005
|
-
'August',
|
|
4006
|
-
'September',
|
|
4007
|
-
'October',
|
|
4008
|
-
'November',
|
|
4009
|
-
'December'
|
|
4010
|
-
]),
|
|
4011
|
-
builtInCustomList('月份(Jan → Dec)', [
|
|
4012
|
-
'Jan',
|
|
4013
|
-
'Feb',
|
|
4014
|
-
'Mar',
|
|
4015
|
-
'Apr',
|
|
4016
|
-
'May',
|
|
4017
|
-
'Jun',
|
|
4018
|
-
'Jul',
|
|
4019
|
-
'Aug',
|
|
4020
|
-
'Sep',
|
|
4021
|
-
'Oct',
|
|
4022
|
-
'Nov',
|
|
4023
|
-
'Dec'
|
|
4024
|
-
]),
|
|
4025
|
-
builtInCustomList('星期(Sunday → Saturday)', [
|
|
4026
|
-
'Sunday',
|
|
4027
|
-
'Monday',
|
|
4028
|
-
'Tuesday',
|
|
4029
|
-
'Wednesday',
|
|
4030
|
-
'Thursday',
|
|
4031
|
-
'Friday',
|
|
4032
|
-
'Saturday'
|
|
4033
|
-
]),
|
|
4034
|
-
builtInCustomList('星期(Sun → Sat)', [
|
|
4035
|
-
'Sun',
|
|
4036
|
-
'Mon',
|
|
4037
|
-
'Tue',
|
|
4038
|
-
'Wed',
|
|
4039
|
-
'Thu',
|
|
4040
|
-
'Fri',
|
|
4041
|
-
'Sat'
|
|
4042
|
-
])
|
|
4043
|
-
]);
|
|
4044
7969
|
const SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_VERSION = 1;
|
|
4045
7970
|
const DEFAULT_SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_KEY = 'a3s-office.spreadsheet-sort-custom-lists.v1';
|
|
4046
7971
|
class LocalStorageSpreadsheetSortCustomListStore {
|
|
@@ -4086,4 +8011,4 @@ function normalizeStoredSpreadsheetSortCustomLists(input) {
|
|
|
4086
8011
|
function spreadsheet_sort_custom_list_store_isRecord(value) {
|
|
4087
8012
|
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
4088
8013
|
}
|
|
4089
|
-
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,
|
|
8014
|
+
export { DEFAULT_SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_KEY, LocalStorageSpreadsheetSortCustomListStore, MAX_SPREADSHEET_SORT_CELLS, SPREADSHEET_TABLE_TOTALS_FUNCTIONS, activeXlsxNativeFill, activeXlsxSemanticColorOrigin, applyImportedXlsxRichText, boundedSpreadsheetDataValidationText, canApplySpreadsheetTableStructureChange, captureSpreadsheetRichTextDomSelection, captureSpreadsheetRichTextPaste, coalesceXlsxRichTextRuns, createSpreadsheetSortAppearanceRows, createSpreadsheetSortCustomList, createSpreadsheetSortDialogSource, createSpreadsheetSortDirectAppearanceRows, createSpreadsheetSortRangeDialogSource, createSpreadsheetSortRangePlan, createSpreadsheetSortTextComparator, createWorkOfficeSpreadsheetCollaborationBinding as createOfficeSpreadsheetCollaborationBinding, createXlsxRichTextReadContext, deleteXlsxNativeFills, directXlsxAlignment, directXlsxFontStyle, evaluateSpreadsheetLocalFormula, finiteSpreadsheetSelection, hasXlsxDirectFontStyle, initializeWorkOfficeSpreadsheetCollaboration as initializeOfficeSpreadsheetCollaboration, isSpreadsheetCellEditingTarget, isSpreadsheetCellEditorTarget, isSpreadsheetDependentListFormula, isSpreadsheetGridKeyboardTarget, isSpreadsheetNativeTextUndoTarget, isSpreadsheetTextOrientationId, isSpreadsheetUnderlineStyle, mergeSpreadsheetSortCustomLists, moveSpreadsheetKeyboardSelection, nextSpreadsheetFontSize, normalizeSpreadsheetCellFillFormat, normalizeSpreadsheetCellRange, normalizeSpreadsheetDataValidationErrorStyle, normalizeSpreadsheetDateValidationBoundary, normalizeSpreadsheetDependentListFormula, normalizeSpreadsheetTableCalculatedFormula, normalizeSpreadsheetTableTotalsFormula, normalizeSpreadsheetTableTotalsLabel, normalizeStoredSpreadsheetSortCustomLists, normalizeWorkSpreadsheetFilterCriteria, normalizeXlsxRichTextColor, normalizeXlsxRichTextEditSource, normalizedWorkSpreadsheetAutoFilterRange, parseSpreadsheetCellRange, parseSpreadsheetSortAppearanceTargetValue, parseSpreadsheetSortCustomList, patchSpreadsheetRichTextFontRuns, readWorkOfficeSpreadsheetCollaboration as readOfficeSpreadsheetCollaboration, readXlsxRichTextCells, reconcileSpreadsheetFiltersAfterSort, reconcileSpreadsheetTableCalculatedColumns, replaceWorkOfficeSpreadsheetCollaboration as replaceOfficeSpreadsheetCollaboration, resolveSpreadsheetDependentListReference, resolveSpreadsheetGoToTarget, runSpreadsheetSelectionMoveShortcut, runSpreadsheetSelectionScopeShortcut, sameSpreadsheetHistoryContent, sameSpreadsheetWorkbookState, sameSpreadsheetWorkbookStateAfterOperations, scopeSpreadsheetKeyboardSelection, selectSpreadsheetFormulaBarContents, sheetHasXlsxRichTextCells, spreadsheetCellFillFormat, spreadsheetCellRangeArea, spreadsheetCellRangeContains, spreadsheetCellRangesEqual, spreadsheetCellRangesIntersect, spreadsheetCellValueWithDiagonalBorder, spreadsheetConditionalFormatStyles, spreadsheetContentWithSelection, spreadsheetContentWithSelections, spreadsheetCurrentRegion, spreadsheetDateValidationFormula, spreadsheetDiagonalBorderFromCellValue, spreadsheetExplicitTextOrientationFromCell, spreadsheetFilterReconciliationIsBounded, spreadsheetFontFamilyOptions, spreadsheetFontSizeOptions, spreadsheetGoToValidationMessage, spreadsheetLocalFormulaBoolean, spreadsheetRowHasContent, spreadsheetSelectionContainsFocus, spreadsheetSelectionReference, spreadsheetSelectionSummary, spreadsheetSheetBounds, spreadsheetSheetCellReader, spreadsheetSheetsForFortune, spreadsheetSheetsFromFortune, spreadsheetSheetsWithFiniteSelections, spreadsheetSingleRange, spreadsheetSortAppearanceFields, spreadsheetSortAppearanceRowsMatch, spreadsheetSortAppearanceTargetLabel, spreadsheetSortAppearanceTargetValue, spreadsheetSortAppearanceTargets, spreadsheetSortAppearanceTargetsEqual, spreadsheetSortCellMatchesAppearance, spreadsheetSortCustomListMatchKey, spreadsheetSortCustomListsEqual, spreadsheetSortError, spreadsheetSortOwnedRangeForExactRange, spreadsheetSortRowsFromSheet, spreadsheetSortRowsMatchRange, spreadsheetTableTotalsColumnPatch, spreadsheetTableTotalsFunctionFromOoxml, spreadsheetTableTotalsFunctionLabel, spreadsheetTableTotalsFunctionToOoxml, spreadsheetTableTotalsRowHasContent, spreadsheetTextOrientationCellStyle, spreadsheetTextOrientationChoiceFromCell, spreadsheetTextOrientationFromAngle, spreadsheetTextOrientationFromCell, spreadsheetTextOrientationFromChoice, spreadsheetTextOrientationFromXlsx, spreadsheetUnderlineCellValue, spreadsheetUnderlineCellValueFromSheetJs, spreadsheetUnderlineCellValueFromXlsx, spreadsheetUnderlineStyle, spreadsheetVisibleTextRotationFromCell, spreadsheet_editor_support_spreadsheetCellAt, subtractSpreadsheetCellRange, synchronizeSpreadsheetTableTotalsRow, validateSpreadsheetSortRequest, withDefaultSpreadsheetTableTotalsLabel, withSpreadsheetCellFillFormat, workSpreadsheetAutoFilterCriteria, workSpreadsheetAutoFilterCriteriaEntries, workSpreadsheetAutoFilterManuallyHiddenRows, workSpreadsheetCellIsDate, workSpreadsheetFilterTextCharacters, workSpreadsheetHasUnescapedWildcard, workSpreadsheetSheetWithAutoFilterCriteria, workSpreadsheetSheetWithImportedAutoFilterCriteria, workSpreadsheetSheetWithoutAutoFilterCriteria, writeXlsxRichTextCells, xlsxAlignmentMatches, xlsxBooleanAttribute, xlsxBorderLineMatches, xlsxColorMatches, xlsxNativeFillCellKeys, xlsxNativeFillKey, xlsxNativeFillSemanticColors, xlsxRichTextCellText, xlsxRichTextStyleOrigins, xlsxStyleCollectionIndex, xlsxToggleEnabled, xlsxUnderlineStyle };
|