@a3s-lab/office 0.37.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/0~3557.js +2 -1
- package/dist/0~3635.js +4 -1
- package/dist/0~9073.js +7 -2
- package/dist/0~presentation-editor.js +45 -9
- package/dist/0~spreadsheet-editor.js +13345 -10343
- package/dist/{3266.js → 5416.js} +1139 -15
- package/dist/9333.js +1 -1
- package/dist/core.d.ts +1 -0
- package/dist/core.js +112 -26
- package/dist/index.js +1 -1
- package/dist/internal/features/work/editors/office-shortcuts.d.ts +1 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter-command.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter-condition-dialog-model.d.ts +27 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter-condition-dialog.d.ts +17 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter-menu.d.ts +10 -0
- package/dist/internal/features/work/editors/spreadsheet-auto-filter.d.ts +16 -4
- package/dist/internal/features/work/editors/spreadsheet-cell-range.d.ts +1 -0
- package/dist/internal/features/work/editors/spreadsheet-command-catalog.d.ts +9 -0
- package/dist/internal/features/work/editors/spreadsheet-command-controller.d.ts +13 -1
- package/dist/internal/features/work/editors/spreadsheet-current-region.d.ts +12 -0
- package/dist/internal/features/work/editors/spreadsheet-editor.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-appearance.d.ts +45 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-collation.d.ts +8 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-command.d.ts +3 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-custom-list-editor.d.ts +8 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-custom-list-store.d.ts +13 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-custom-list.d.ts +26 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-dialog.d.ts +9 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-matrix.d.ts +4 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-options-dialog.d.ts +7 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-order-controls.d.ts +16 -0
- package/dist/internal/features/work/editors/spreadsheet-sort-range-dialog.d.ts +7 -0
- package/dist/internal/features/work/editors/spreadsheet-sort.d.ts +138 -0
- package/dist/internal/features/work/editors/use-spreadsheet-auto-filter.d.ts +12 -3
- package/dist/internal/features/work/editors/use-spreadsheet-sort.d.ts +26 -0
- package/dist/internal/features/work/editors/work-office-chrome.d.ts +3 -0
- package/dist/internal/features/work/work-spreadsheet-auto-filter.d.ts +14 -0
- package/dist/internal/features/work/work-spreadsheet-dynamic-filter.d.ts +7 -0
- package/dist/internal/features/work/work-spreadsheet-filter-contract.d.ts +3 -0
- package/dist/internal/features/work/work-spreadsheet-wildcard.d.ts +7 -0
- package/dist/internal/features/work/work-types.d.ts +31 -7
- package/dist/internal/features/work/work-xlsx-interop.d.ts +2 -1
- package/dist/internal/features/work/work-xlsx-table-filters.d.ts +3 -1
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +544 -20
- package/dist/vue.d.ts +3 -1
- package/dist/vue.js +2 -0
- package/dist/web-component.d.ts +3 -1
- package/dist/web-component.js +9 -0
- package/dist/work-spreadsheet-package-scan.worker.js +2 -1
- package/docs/latest/en/browser-editor-architecture.md +128 -0
- package/package.json +21 -1
package/dist/{3266.js → 5416.js}
RENAMED
|
@@ -537,7 +537,6 @@ function invalidWorkOfficeSpreadsheetInput(expected) {
|
|
|
537
537
|
const MAX_POPULATED_CELLS = 1000000;
|
|
538
538
|
const MAX_DENSE_MATRIX_CELLS = 1000000;
|
|
539
539
|
const MAX_FILTER_VALUES_PER_COLUMN = 10000;
|
|
540
|
-
const MAX_FILTER_VALUE_CHARACTERS = 32767;
|
|
541
540
|
const MAX_FILTER_TEXT_BYTES = 1048576;
|
|
542
541
|
const FILTER_TEXT_ENCODER = new TextEncoder();
|
|
543
542
|
const SPREADSHEET_DYNAMIC_FILTERS = new Set([
|
|
@@ -864,19 +863,16 @@ function requiredSpreadsheetTableFilterCriteria(value, label, textBudget) {
|
|
|
864
863
|
case 'contains':
|
|
865
864
|
case 'does-not-contain':
|
|
866
865
|
case 'begins-with':
|
|
866
|
+
case 'does-not-begin-with':
|
|
867
867
|
case 'ends-with':
|
|
868
|
+
case 'does-not-end-with':
|
|
869
|
+
case 'matches-wildcard':
|
|
870
|
+
case 'does-not-match-wildcard':
|
|
868
871
|
case 'greater-than':
|
|
869
872
|
case 'greater-than-or-equal':
|
|
870
873
|
case 'less-than':
|
|
871
874
|
case 'less-than-or-equal':
|
|
872
|
-
|
|
873
|
-
'type',
|
|
874
|
-
'value'
|
|
875
|
-
], `filter criteria for ${label}`);
|
|
876
|
-
return {
|
|
877
|
-
type,
|
|
878
|
-
value: requiredSpreadsheetFilterText(record.value, label, textBudget)
|
|
879
|
-
};
|
|
875
|
+
return requiredSpreadsheetCustomFilterCondition(record, label, textBudget);
|
|
880
876
|
case 'between':
|
|
881
877
|
case 'not-between':
|
|
882
878
|
assertExactRecordKeys(record, [
|
|
@@ -897,6 +893,22 @@ function requiredSpreadsheetTableFilterCriteria(value, label, textBudget) {
|
|
|
897
893
|
return {
|
|
898
894
|
type
|
|
899
895
|
};
|
|
896
|
+
case 'compound':
|
|
897
|
+
assertExactRecordKeys(record, [
|
|
898
|
+
'type',
|
|
899
|
+
'conjunction',
|
|
900
|
+
'conditions'
|
|
901
|
+
], `filter criteria for ${label}`);
|
|
902
|
+
if ('and' !== record.conjunction && 'or' !== record.conjunction) invalidWorkOfficeSpreadsheetInput(`an 'and' or 'or' conjunction for compound filter criteria for ${label}`);
|
|
903
|
+
if (!Array.isArray(record.conditions) || 2 !== record.conditions.length) invalidWorkOfficeSpreadsheetInput(`exactly two custom filter conditions for ${label}`);
|
|
904
|
+
return {
|
|
905
|
+
type,
|
|
906
|
+
conjunction: record.conjunction,
|
|
907
|
+
conditions: [
|
|
908
|
+
requiredSpreadsheetCustomFilterCondition(record.conditions[0], label, textBudget),
|
|
909
|
+
requiredSpreadsheetCustomFilterCondition(record.conditions[1], label, textBudget)
|
|
910
|
+
]
|
|
911
|
+
};
|
|
900
912
|
case 'top':
|
|
901
913
|
case 'bottom':
|
|
902
914
|
assertExactRecordKeys(record, [
|
|
@@ -931,15 +943,44 @@ function requiredSpreadsheetTableFilterCriteria(value, label, textBudget) {
|
|
|
931
943
|
invalidWorkOfficeSpreadsheetInput(`supported filter criteria for ${label}`);
|
|
932
944
|
}
|
|
933
945
|
}
|
|
946
|
+
function requiredSpreadsheetCustomFilterCondition(value, label, textBudget) {
|
|
947
|
+
const record = validateJsonRecord(requiredInputRecord(value, `custom filter condition for ${label}`), `custom filter condition for ${label}`);
|
|
948
|
+
switch(record.type){
|
|
949
|
+
case 'equals':
|
|
950
|
+
case 'not-equals':
|
|
951
|
+
case 'contains':
|
|
952
|
+
case 'does-not-contain':
|
|
953
|
+
case 'begins-with':
|
|
954
|
+
case 'does-not-begin-with':
|
|
955
|
+
case 'ends-with':
|
|
956
|
+
case 'does-not-end-with':
|
|
957
|
+
case 'matches-wildcard':
|
|
958
|
+
case 'does-not-match-wildcard':
|
|
959
|
+
case 'greater-than':
|
|
960
|
+
case 'greater-than-or-equal':
|
|
961
|
+
case 'less-than':
|
|
962
|
+
case 'less-than-or-equal':
|
|
963
|
+
assertExactRecordKeys(record, [
|
|
964
|
+
'type',
|
|
965
|
+
'value'
|
|
966
|
+
], `custom filter condition for ${label}`);
|
|
967
|
+
return {
|
|
968
|
+
type: record.type,
|
|
969
|
+
value: requiredSpreadsheetFilterText(record.value, label, textBudget)
|
|
970
|
+
};
|
|
971
|
+
default:
|
|
972
|
+
invalidWorkOfficeSpreadsheetInput(`supported custom filter condition for ${label}`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
934
975
|
function requiredSpreadsheetFilterText(value, label, textBudget) {
|
|
935
|
-
if ('string' != typeof value) invalidWorkOfficeSpreadsheetInput(`filter text containing 1 to ${
|
|
976
|
+
if ('string' != typeof value) invalidWorkOfficeSpreadsheetInput(`filter text containing 1 to ${32767..toLocaleString()} characters for ${label}`);
|
|
936
977
|
let characters = 0;
|
|
937
978
|
for (const character of value){
|
|
938
979
|
characters += 1;
|
|
939
980
|
const codePoint = character.codePointAt(0);
|
|
940
981
|
if (void 0 === codePoint || !(0x9 === codePoint || 0xa === codePoint || 0xd === codePoint || codePoint >= 0x20 && codePoint <= 0xd7ff || codePoint >= 0xe000 && codePoint <= 0xfffd || codePoint >= 0x10000 && codePoint <= 0x10ffff)) invalidWorkOfficeSpreadsheetInput(`XML-compatible filter text for ${label}`);
|
|
941
982
|
}
|
|
942
|
-
if (characters < 1 || characters >
|
|
983
|
+
if (characters < 1 || characters > 32767) invalidWorkOfficeSpreadsheetInput(`filter text containing 1 to ${32767..toLocaleString()} characters for ${label}`);
|
|
943
984
|
textBudget.bytes += FILTER_TEXT_ENCODER.encode(value).byteLength;
|
|
944
985
|
if (textBudget.bytes > MAX_FILTER_TEXT_BYTES) invalidWorkOfficeSpreadsheetInput(`filter text totaling at most ${MAX_FILTER_TEXT_BYTES.toLocaleString()} bytes for ${label}`);
|
|
945
986
|
return value;
|
|
@@ -2160,7 +2201,867 @@ function assertInitializedSpreadsheetSession(session) {
|
|
|
2160
2201
|
if (metadata?.initialized) return;
|
|
2161
2202
|
throw new WorkOfficeCollaborationError('office.collaboration.not_initialized', 'The Spreadsheet collaboration session has not been initialized.');
|
|
2162
2203
|
}
|
|
2204
|
+
const WORK_SPREADSHEET_FILTER_TEXT_MAX_CHARACTERS = 32767;
|
|
2205
|
+
function workSpreadsheetFilterTextCharacters(value) {
|
|
2206
|
+
let characters = 0;
|
|
2207
|
+
for (const _character of value)characters += 1;
|
|
2208
|
+
return characters;
|
|
2209
|
+
}
|
|
2210
|
+
function workSpreadsheetFilterTextIsBounded(value) {
|
|
2211
|
+
const characters = workSpreadsheetFilterTextCharacters(value);
|
|
2212
|
+
return characters >= 1 && characters <= WORK_SPREADSHEET_FILTER_TEXT_MAX_CHARACTERS;
|
|
2213
|
+
}
|
|
2163
2214
|
const MILLISECONDS_PER_DAY = 86400000;
|
|
2215
|
+
const EXCEL_1900_EPOCH_UTC = Date.UTC(1899, 11, 30);
|
|
2216
|
+
const EXCEL_1900_CIVIL_EPOCH_UTC = Date.UTC(1899, 11, 31);
|
|
2217
|
+
const EXCEL_1900_LOCAL_BASE = new Date(1899, 11, 30);
|
|
2218
|
+
const SERIAL_INTEGER_EPSILON = 1e-7;
|
|
2219
|
+
const CALENDAR_MONTH_FILTERS = [
|
|
2220
|
+
'month-1',
|
|
2221
|
+
'month-2',
|
|
2222
|
+
'month-3',
|
|
2223
|
+
'month-4',
|
|
2224
|
+
'month-5',
|
|
2225
|
+
'month-6',
|
|
2226
|
+
'month-7',
|
|
2227
|
+
'month-8',
|
|
2228
|
+
'month-9',
|
|
2229
|
+
'month-10',
|
|
2230
|
+
'month-11',
|
|
2231
|
+
'month-12'
|
|
2232
|
+
];
|
|
2233
|
+
const CALENDAR_QUARTER_FILTERS = [
|
|
2234
|
+
'quarter-1',
|
|
2235
|
+
'quarter-2',
|
|
2236
|
+
'quarter-3',
|
|
2237
|
+
'quarter-4'
|
|
2238
|
+
];
|
|
2239
|
+
function workSpreadsheetDynamicFilterMatcher(kind, cells, context) {
|
|
2240
|
+
if ('above-average' === kind || 'below-average' === kind) return averageMatcher(kind, cells);
|
|
2241
|
+
const month = CALENDAR_MONTH_FILTERS.indexOf(kind);
|
|
2242
|
+
if (month >= 0) return (cell)=>spreadsheetDateParts(cell)?.month === month;
|
|
2243
|
+
const quarter = CALENDAR_QUARTER_FILTERS.indexOf(kind);
|
|
2244
|
+
if (quarter >= 0) return (cell)=>spreadsheetDateParts(cell)?.quarter === quarter;
|
|
2245
|
+
const bounds = relativeDateBounds(kind, context.now);
|
|
2246
|
+
if (!bounds) return null;
|
|
2247
|
+
return (cell)=>{
|
|
2248
|
+
const day = spreadsheetDateSerialDay(cell);
|
|
2249
|
+
return null !== day && day >= bounds.start && day < bounds.end;
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
function workSpreadsheetCellIsDate(cell) {
|
|
2253
|
+
return null !== spreadsheetDateSerialDay(cell);
|
|
2254
|
+
}
|
|
2255
|
+
function averageMatcher(kind, cells) {
|
|
2256
|
+
const average = finiteArithmeticMean(cells);
|
|
2257
|
+
if (null === average) return ()=>false;
|
|
2258
|
+
return 'above-average' === kind ? (cell)=>{
|
|
2259
|
+
const value = spreadsheetNumericCellValue(cell);
|
|
2260
|
+
return null !== value && value > average;
|
|
2261
|
+
} : (cell)=>{
|
|
2262
|
+
const value = spreadsheetNumericCellValue(cell);
|
|
2263
|
+
return null !== value && value < average;
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
function finiteArithmeticMean(cells) {
|
|
2267
|
+
let count = 0;
|
|
2268
|
+
let scale = 0;
|
|
2269
|
+
let scaledTotal = 0;
|
|
2270
|
+
for (const cell of cells){
|
|
2271
|
+
const value = spreadsheetNumericCellValue(cell);
|
|
2272
|
+
if (null === value) continue;
|
|
2273
|
+
count += 1;
|
|
2274
|
+
const magnitude = Math.abs(value);
|
|
2275
|
+
if (magnitude > scale) {
|
|
2276
|
+
scaledTotal = scale / magnitude * scaledTotal + value / magnitude;
|
|
2277
|
+
scale = magnitude;
|
|
2278
|
+
} else if (scale > 0) scaledTotal += value / scale;
|
|
2279
|
+
}
|
|
2280
|
+
return count ? scaledTotal / count * scale : null;
|
|
2281
|
+
}
|
|
2282
|
+
function relativeDateBounds(kind, now) {
|
|
2283
|
+
const timestamp = now.getTime();
|
|
2284
|
+
const year = now.getFullYear();
|
|
2285
|
+
if (!Number.isFinite(timestamp) || !Number.isSafeInteger(year) || year < 1900 || year > 9999) return null;
|
|
2286
|
+
const month = now.getMonth();
|
|
2287
|
+
const today = excelSerialFromCivilDate(year, month, now.getDate());
|
|
2288
|
+
if ('yesterday' === kind) return dayBounds(today - 1);
|
|
2289
|
+
if ('today' === kind) return dayBounds(today);
|
|
2290
|
+
if ('tomorrow' === kind) return dayBounds(today + 1);
|
|
2291
|
+
const weekStart = today - now.getDay();
|
|
2292
|
+
if ('last-week' === kind) return rangeBounds(weekStart - 7, weekStart);
|
|
2293
|
+
if ('this-week' === kind) return rangeBounds(weekStart, weekStart + 7);
|
|
2294
|
+
if ('next-week' === kind) return rangeBounds(weekStart + 7, weekStart + 14);
|
|
2295
|
+
const monthStart = excelSerialFromCivilDate(year, month, 1);
|
|
2296
|
+
if ('last-month' === kind) return rangeBounds(excelSerialFromCivilDate(year, month - 1, 1), monthStart);
|
|
2297
|
+
if ('this-month' === kind) return rangeBounds(monthStart, excelSerialFromCivilDate(year, month + 1, 1));
|
|
2298
|
+
if ('next-month' === kind) return rangeBounds(excelSerialFromCivilDate(year, month + 1, 1), excelSerialFromCivilDate(year, month + 2, 1));
|
|
2299
|
+
const quarterStartMonth = 3 * Math.floor(month / 3);
|
|
2300
|
+
const quarterStart = excelSerialFromCivilDate(year, quarterStartMonth, 1);
|
|
2301
|
+
if ('last-quarter' === kind) return rangeBounds(excelSerialFromCivilDate(year, quarterStartMonth - 3, 1), quarterStart);
|
|
2302
|
+
if ('this-quarter' === kind) return rangeBounds(quarterStart, excelSerialFromCivilDate(year, quarterStartMonth + 3, 1));
|
|
2303
|
+
if ('next-quarter' === kind) return rangeBounds(excelSerialFromCivilDate(year, quarterStartMonth + 3, 1), excelSerialFromCivilDate(year, quarterStartMonth + 6, 1));
|
|
2304
|
+
const yearStart = excelSerialFromCivilDate(year, 0, 1);
|
|
2305
|
+
if ('last-year' === kind) return rangeBounds(excelSerialFromCivilDate(year - 1, 0, 1), yearStart);
|
|
2306
|
+
if ('this-year' === kind) return rangeBounds(yearStart, excelSerialFromCivilDate(year + 1, 0, 1));
|
|
2307
|
+
if ('next-year' === kind) return rangeBounds(excelSerialFromCivilDate(year + 1, 0, 1), excelSerialFromCivilDate(year + 2, 0, 1));
|
|
2308
|
+
if ('year-to-date' === kind) return rangeBounds(yearStart, today + 1);
|
|
2309
|
+
return null;
|
|
2310
|
+
}
|
|
2311
|
+
function spreadsheetDateParts(cell) {
|
|
2312
|
+
const day = spreadsheetDateSerialDay(cell);
|
|
2313
|
+
if (null === day) return null;
|
|
2314
|
+
const adjustedDay = day > 60 ? day - 1 : day;
|
|
2315
|
+
const date = new Date(EXCEL_1900_CIVIL_EPOCH_UTC + adjustedDay * MILLISECONDS_PER_DAY);
|
|
2316
|
+
const timestamp = date.getTime();
|
|
2317
|
+
const year = date.getUTCFullYear();
|
|
2318
|
+
if (!Number.isFinite(timestamp) || year < 1900 || year > 9999) return null;
|
|
2319
|
+
const month = date.getUTCMonth();
|
|
2320
|
+
return {
|
|
2321
|
+
month,
|
|
2322
|
+
quarter: Math.floor(month / 3)
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
function spreadsheetDateSerialDay(cell) {
|
|
2326
|
+
const serial = spreadsheetDateCellSerial(cell);
|
|
2327
|
+
if (null === serial) return null;
|
|
2328
|
+
const rounded = Math.round(serial);
|
|
2329
|
+
const normalized = Math.abs(serial - rounded) <= SERIAL_INTEGER_EPSILON ? rounded : serial;
|
|
2330
|
+
const day = Math.floor(normalized);
|
|
2331
|
+
return day >= 1 && 60 !== day ? day : null;
|
|
2332
|
+
}
|
|
2333
|
+
function spreadsheetDateCellSerial(cell) {
|
|
2334
|
+
const value = spreadsheetCellRawValue(cell);
|
|
2335
|
+
if (value instanceof Date) {
|
|
2336
|
+
if (!Number.isFinite(value.getTime())) return null;
|
|
2337
|
+
const threshold = EXCEL_1900_LOCAL_BASE.getTime() + (value.getTimezoneOffset() - EXCEL_1900_LOCAL_BASE.getTimezoneOffset()) * 60000;
|
|
2338
|
+
const serial = (value.getTime() - threshold) / MILLISECONDS_PER_DAY;
|
|
2339
|
+
return Number.isFinite(serial) ? serial : null;
|
|
2340
|
+
}
|
|
2341
|
+
return 'number' == typeof value && Number.isFinite(value) && cell?.ct?.t === 'd' ? value : null;
|
|
2342
|
+
}
|
|
2343
|
+
function spreadsheetNumericCellValue(cell) {
|
|
2344
|
+
if (null !== spreadsheetDateCellSerial(cell)) return null;
|
|
2345
|
+
const value = spreadsheetCellRawValue(cell);
|
|
2346
|
+
return 'number' == typeof value && Number.isFinite(value) ? value : null;
|
|
2347
|
+
}
|
|
2348
|
+
function spreadsheetCellRawValue(cell) {
|
|
2349
|
+
const candidate = cell;
|
|
2350
|
+
return candidate?.v ?? candidate?.m;
|
|
2351
|
+
}
|
|
2352
|
+
function excelSerialFromCivilDate(year, month, day) {
|
|
2353
|
+
return (Date.UTC(year, month, day) - EXCEL_1900_EPOCH_UTC) / MILLISECONDS_PER_DAY;
|
|
2354
|
+
}
|
|
2355
|
+
function dayBounds(day) {
|
|
2356
|
+
return {
|
|
2357
|
+
start: day,
|
|
2358
|
+
end: day + 1
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
function rangeBounds(start, end) {
|
|
2362
|
+
return {
|
|
2363
|
+
start,
|
|
2364
|
+
end
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
const WILDCARD_ESCAPES = new Set([
|
|
2368
|
+
'*',
|
|
2369
|
+
'?',
|
|
2370
|
+
'~'
|
|
2371
|
+
]);
|
|
2372
|
+
const BITSET_MINIMUM_SEARCH_WORK = 1000000;
|
|
2373
|
+
const BITSET_MINIMUM_TOKENS = 256;
|
|
2374
|
+
const BITSET_MAXIMUM_LITERAL_VALUES = 256;
|
|
2375
|
+
function workSpreadsheetWildcardMatcher(source) {
|
|
2376
|
+
const tokens = wildcardTokens(source);
|
|
2377
|
+
const literalMatcher = literalWildcardMatcher(tokens);
|
|
2378
|
+
if (literalMatcher) return (value)=>literalMatcher(comparableWildcardText(value));
|
|
2379
|
+
const fixedWidthMatcher = fixedWidthWildcardMatcher(tokens);
|
|
2380
|
+
return (value)=>fixedWidthMatcher(comparableWildcardText(value));
|
|
2381
|
+
}
|
|
2382
|
+
function workSpreadsheetHasUnescapedWildcard(source) {
|
|
2383
|
+
const characters = [
|
|
2384
|
+
...source
|
|
2385
|
+
];
|
|
2386
|
+
for(let index = 0; index < characters.length; index += 1){
|
|
2387
|
+
const character = characters[index];
|
|
2388
|
+
if ('~' === character && WILDCARD_ESCAPES.has(characters[index + 1] ?? '')) {
|
|
2389
|
+
index += 1;
|
|
2390
|
+
continue;
|
|
2391
|
+
}
|
|
2392
|
+
if ('*' === character || '?' === character) return true;
|
|
2393
|
+
}
|
|
2394
|
+
return false;
|
|
2395
|
+
}
|
|
2396
|
+
function wildcardTokens(source) {
|
|
2397
|
+
const characters = [
|
|
2398
|
+
...source
|
|
2399
|
+
];
|
|
2400
|
+
const tokens = [];
|
|
2401
|
+
let literal = '';
|
|
2402
|
+
const flushLiteral = ()=>{
|
|
2403
|
+
if (!literal) return;
|
|
2404
|
+
for (const value of comparableWildcardText(literal))tokens.push({
|
|
2405
|
+
kind: 'literal',
|
|
2406
|
+
value
|
|
2407
|
+
});
|
|
2408
|
+
literal = '';
|
|
2409
|
+
};
|
|
2410
|
+
for(let index = 0; index < characters.length; index += 1){
|
|
2411
|
+
const character = characters[index] ?? '';
|
|
2412
|
+
const escaped = characters[index + 1] ?? '';
|
|
2413
|
+
if ('~' === character && WILDCARD_ESCAPES.has(escaped)) {
|
|
2414
|
+
literal += escaped;
|
|
2415
|
+
index += 1;
|
|
2416
|
+
continue;
|
|
2417
|
+
}
|
|
2418
|
+
if ('*' === character) {
|
|
2419
|
+
flushLiteral();
|
|
2420
|
+
if (tokens.at(-1)?.kind !== 'many') tokens.push({
|
|
2421
|
+
kind: 'many'
|
|
2422
|
+
});
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
if ('?' === character) {
|
|
2426
|
+
flushLiteral();
|
|
2427
|
+
tokens.push({
|
|
2428
|
+
kind: 'single'
|
|
2429
|
+
});
|
|
2430
|
+
continue;
|
|
2431
|
+
}
|
|
2432
|
+
literal += character;
|
|
2433
|
+
}
|
|
2434
|
+
flushLiteral();
|
|
2435
|
+
return tokens;
|
|
2436
|
+
}
|
|
2437
|
+
function literalWildcardMatcher(tokens) {
|
|
2438
|
+
if (tokens.some((token)=>'single' === token.kind)) return null;
|
|
2439
|
+
const startsWithMany = tokens[0]?.kind === 'many';
|
|
2440
|
+
const endsWithMany = tokens.at(-1)?.kind === 'many';
|
|
2441
|
+
const segments = [];
|
|
2442
|
+
let segment = '';
|
|
2443
|
+
for (const token of tokens){
|
|
2444
|
+
if ('many' === token.kind) {
|
|
2445
|
+
if (segment) segments.push(segment);
|
|
2446
|
+
segment = '';
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
if ('literal' !== token.kind) return null;
|
|
2450
|
+
segment += token.value;
|
|
2451
|
+
}
|
|
2452
|
+
if (segment) segments.push(segment);
|
|
2453
|
+
if (!segments.length) return tokens.length ? ()=>true : (value)=>'' === value;
|
|
2454
|
+
if (!startsWithMany && !endsWithMany && 1 === segments.length) return (value)=>value === segments[0];
|
|
2455
|
+
return (value)=>{
|
|
2456
|
+
let minimumIndex = 0;
|
|
2457
|
+
let segmentIndex = 0;
|
|
2458
|
+
if (!startsWithMany) {
|
|
2459
|
+
const first = segments[0] ?? '';
|
|
2460
|
+
if (!value.startsWith(first)) return false;
|
|
2461
|
+
minimumIndex = first.length;
|
|
2462
|
+
segmentIndex = 1;
|
|
2463
|
+
}
|
|
2464
|
+
const searchableSegments = endsWithMany ? segments.length : segments.length - 1;
|
|
2465
|
+
for(; segmentIndex < searchableSegments; segmentIndex += 1){
|
|
2466
|
+
const current = segments[segmentIndex] ?? '';
|
|
2467
|
+
const found = value.indexOf(current, minimumIndex);
|
|
2468
|
+
if (found < 0) return false;
|
|
2469
|
+
minimumIndex = found + current.length;
|
|
2470
|
+
}
|
|
2471
|
+
if (endsWithMany) return true;
|
|
2472
|
+
const last = segments.at(-1) ?? '';
|
|
2473
|
+
return value.endsWith(last) && value.length - last.length >= minimumIndex;
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
function fixedWidthWildcardMatcher(tokens) {
|
|
2477
|
+
const startsWithMany = tokens[0]?.kind === 'many';
|
|
2478
|
+
const endsWithMany = tokens.at(-1)?.kind === 'many';
|
|
2479
|
+
const segments = [];
|
|
2480
|
+
let current = [];
|
|
2481
|
+
for (const token of tokens){
|
|
2482
|
+
if ('many' === token.kind) {
|
|
2483
|
+
if (current.length) segments.push(fixedWidthWildcardSegment(current));
|
|
2484
|
+
current = [];
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
current.push(token);
|
|
2488
|
+
}
|
|
2489
|
+
if (current.length) segments.push(fixedWidthWildcardSegment(current));
|
|
2490
|
+
if (!segments.length) return ()=>true;
|
|
2491
|
+
if (!startsWithMany && !endsWithMany && 1 === segments.length) {
|
|
2492
|
+
const only = segments[0];
|
|
2493
|
+
return (value)=>{
|
|
2494
|
+
const characters = [
|
|
2495
|
+
...value
|
|
2496
|
+
];
|
|
2497
|
+
return characters.length === only.tokens.length && fixedWidthWildcardSegmentMatches(only, characters, 0);
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
return (value)=>{
|
|
2501
|
+
const characters = [
|
|
2502
|
+
...value
|
|
2503
|
+
];
|
|
2504
|
+
let minimumIndex = 0;
|
|
2505
|
+
let segmentIndex = 0;
|
|
2506
|
+
let maximumEnd = characters.length;
|
|
2507
|
+
let finalSegment = segments.length;
|
|
2508
|
+
if (!startsWithMany) {
|
|
2509
|
+
const first = segments[0];
|
|
2510
|
+
if (!fixedWidthWildcardSegmentMatches(first, characters, 0)) return false;
|
|
2511
|
+
minimumIndex = first.tokens.length;
|
|
2512
|
+
segmentIndex = 1;
|
|
2513
|
+
}
|
|
2514
|
+
if (!endsWithMany) {
|
|
2515
|
+
finalSegment -= 1;
|
|
2516
|
+
const last = segments[finalSegment];
|
|
2517
|
+
const start = characters.length - last.tokens.length;
|
|
2518
|
+
if (start < minimumIndex || !fixedWidthWildcardSegmentMatches(last, characters, start)) return false;
|
|
2519
|
+
maximumEnd = start;
|
|
2520
|
+
}
|
|
2521
|
+
if (segmentIndex >= finalSegment) return true;
|
|
2522
|
+
const offsets = wildcardCodePointOffsets(characters);
|
|
2523
|
+
for(; segmentIndex < finalSegment; segmentIndex += 1){
|
|
2524
|
+
const segment = segments[segmentIndex];
|
|
2525
|
+
const found = findFixedWidthWildcardSegment(segment, value, characters, offsets, minimumIndex, maximumEnd);
|
|
2526
|
+
if (found < 0) return false;
|
|
2527
|
+
minimumIndex = found + segment.tokens.length;
|
|
2528
|
+
}
|
|
2529
|
+
return true;
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
function fixedWidthWildcardSegment(tokens) {
|
|
2533
|
+
let anchor = null;
|
|
2534
|
+
let literalStart = -1;
|
|
2535
|
+
const considerLiteralRun = (end)=>{
|
|
2536
|
+
if (literalStart < 0) return;
|
|
2537
|
+
const text = tokens.slice(literalStart, end).map((token)=>'literal' === token.kind ? token.value : '').join('');
|
|
2538
|
+
if (!anchor || end - literalStart >= [
|
|
2539
|
+
...anchor.text
|
|
2540
|
+
].length) anchor = {
|
|
2541
|
+
offset: literalStart,
|
|
2542
|
+
text
|
|
2543
|
+
};
|
|
2544
|
+
literalStart = -1;
|
|
2545
|
+
};
|
|
2546
|
+
for(let index = 0; index <= tokens.length; index += 1)if (tokens[index]?.kind === 'literal') {
|
|
2547
|
+
if (literalStart < 0) literalStart = index;
|
|
2548
|
+
} else considerLiteralRun(index);
|
|
2549
|
+
return {
|
|
2550
|
+
anchor,
|
|
2551
|
+
bitset: fixedWidthWildcardBitset(tokens),
|
|
2552
|
+
tokens
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
function findFixedWidthWildcardSegment(segment, value, characters, offsets, minimumIndex, maximumEnd) {
|
|
2556
|
+
if (minimumIndex + segment.tokens.length > maximumEnd) return -1;
|
|
2557
|
+
if (!segment.anchor) return minimumIndex;
|
|
2558
|
+
if (segment.bitset && segment.tokens.length * (maximumEnd - minimumIndex) >= BITSET_MINIMUM_SEARCH_WORK) return findFixedWidthWildcardSegmentWithBitset(segment, characters, minimumIndex, maximumEnd);
|
|
2559
|
+
const minimumAnchorIndex = minimumIndex + segment.anchor.offset;
|
|
2560
|
+
let anchorOffset = value.indexOf(segment.anchor.text, offsets[minimumAnchorIndex] ?? value.length);
|
|
2561
|
+
while(anchorOffset >= 0){
|
|
2562
|
+
const anchorIndex = codePointIndexAtOffset(offsets, anchorOffset);
|
|
2563
|
+
if (anchorIndex >= 0) {
|
|
2564
|
+
const start = anchorIndex - segment.anchor.offset;
|
|
2565
|
+
if (start + segment.tokens.length > maximumEnd) return -1;
|
|
2566
|
+
if (start >= minimumIndex && fixedWidthWildcardSegmentMatches(segment, characters, start)) return start;
|
|
2567
|
+
}
|
|
2568
|
+
anchorOffset = value.indexOf(segment.anchor.text, anchorOffset + 1);
|
|
2569
|
+
}
|
|
2570
|
+
return -1;
|
|
2571
|
+
}
|
|
2572
|
+
function fixedWidthWildcardBitset(tokens) {
|
|
2573
|
+
if (tokens.length < BITSET_MINIMUM_TOKENS) return null;
|
|
2574
|
+
const literalValues = new Set(tokens.flatMap((token)=>'literal' === token.kind ? [
|
|
2575
|
+
token.value
|
|
2576
|
+
] : []));
|
|
2577
|
+
if (literalValues.size > BITSET_MAXIMUM_LITERAL_VALUES) return null;
|
|
2578
|
+
const literalMasks = new Map();
|
|
2579
|
+
let singleMask = 0n;
|
|
2580
|
+
for(let index = 0; index < tokens.length; index += 1){
|
|
2581
|
+
const token = tokens[index];
|
|
2582
|
+
const bit = 1n << BigInt(index);
|
|
2583
|
+
if ('single' === token.kind) {
|
|
2584
|
+
singleMask |= bit;
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
literalMasks.set(token.value, (literalMasks.get(token.value) ?? 0n) | bit);
|
|
2588
|
+
}
|
|
2589
|
+
return {
|
|
2590
|
+
literalMasks,
|
|
2591
|
+
matchBit: 1n << BigInt(tokens.length - 1),
|
|
2592
|
+
singleMask
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
function findFixedWidthWildcardSegmentWithBitset(segment, characters, minimumIndex, maximumEnd) {
|
|
2596
|
+
const bitset = segment.bitset;
|
|
2597
|
+
if (!bitset) return -1;
|
|
2598
|
+
let state = 0n;
|
|
2599
|
+
for(let index = minimumIndex; index < maximumEnd; index += 1){
|
|
2600
|
+
const mask = bitset.singleMask | (bitset.literalMasks.get(characters[index]) ?? 0n);
|
|
2601
|
+
state = (state << 1n | 1n) & mask;
|
|
2602
|
+
if ((state & bitset.matchBit) !== 0n) return index - segment.tokens.length + 1;
|
|
2603
|
+
}
|
|
2604
|
+
return -1;
|
|
2605
|
+
}
|
|
2606
|
+
function fixedWidthWildcardSegmentMatches(segment, characters, start) {
|
|
2607
|
+
if (start < 0 || start + segment.tokens.length > characters.length) return false;
|
|
2608
|
+
return segment.tokens.every((token, index)=>'single' === token.kind || token.value === characters[start + index]);
|
|
2609
|
+
}
|
|
2610
|
+
function wildcardCodePointOffsets(characters) {
|
|
2611
|
+
const offsets = [
|
|
2612
|
+
0
|
|
2613
|
+
];
|
|
2614
|
+
for (const character of characters)offsets.push((offsets.at(-1) ?? 0) + character.length);
|
|
2615
|
+
return offsets;
|
|
2616
|
+
}
|
|
2617
|
+
function codePointIndexAtOffset(offsets, target) {
|
|
2618
|
+
let low = 0;
|
|
2619
|
+
let high = offsets.length - 1;
|
|
2620
|
+
while(low <= high){
|
|
2621
|
+
const middle = Math.floor((low + high) / 2);
|
|
2622
|
+
const value = offsets[middle] ?? -1;
|
|
2623
|
+
if (value === target) return middle;
|
|
2624
|
+
if (value < target) low = middle + 1;
|
|
2625
|
+
else high = middle - 1;
|
|
2626
|
+
}
|
|
2627
|
+
return -1;
|
|
2628
|
+
}
|
|
2629
|
+
function comparableWildcardText(value) {
|
|
2630
|
+
return value.normalize('NFKC').toLocaleLowerCase('zh-CN');
|
|
2631
|
+
}
|
|
2632
|
+
const AUTO_FILTER_CRITERIA_KIND = 'a3s-office-auto-filter-criteria';
|
|
2633
|
+
const AUTO_FILTER_CRITERIA_VERSION = 1;
|
|
2634
|
+
const SINGLE_VALUE_FILTER_TYPES = new Set([
|
|
2635
|
+
'equals',
|
|
2636
|
+
'not-equals',
|
|
2637
|
+
'contains',
|
|
2638
|
+
'does-not-contain',
|
|
2639
|
+
'begins-with',
|
|
2640
|
+
'does-not-begin-with',
|
|
2641
|
+
'ends-with',
|
|
2642
|
+
'does-not-end-with',
|
|
2643
|
+
'matches-wildcard',
|
|
2644
|
+
'does-not-match-wildcard',
|
|
2645
|
+
'greater-than',
|
|
2646
|
+
'greater-than-or-equal',
|
|
2647
|
+
'less-than',
|
|
2648
|
+
'less-than-or-equal'
|
|
2649
|
+
]);
|
|
2650
|
+
const DYNAMIC_FILTER_KINDS = new Set([
|
|
2651
|
+
'above-average',
|
|
2652
|
+
'below-average',
|
|
2653
|
+
'tomorrow',
|
|
2654
|
+
'today',
|
|
2655
|
+
'yesterday',
|
|
2656
|
+
'next-week',
|
|
2657
|
+
'this-week',
|
|
2658
|
+
'last-week',
|
|
2659
|
+
'next-month',
|
|
2660
|
+
'this-month',
|
|
2661
|
+
'last-month',
|
|
2662
|
+
'next-quarter',
|
|
2663
|
+
'this-quarter',
|
|
2664
|
+
'last-quarter',
|
|
2665
|
+
'next-year',
|
|
2666
|
+
'this-year',
|
|
2667
|
+
'last-year',
|
|
2668
|
+
'year-to-date',
|
|
2669
|
+
'quarter-1',
|
|
2670
|
+
'quarter-2',
|
|
2671
|
+
'quarter-3',
|
|
2672
|
+
'quarter-4',
|
|
2673
|
+
'month-1',
|
|
2674
|
+
'month-2',
|
|
2675
|
+
'month-3',
|
|
2676
|
+
'month-4',
|
|
2677
|
+
'month-5',
|
|
2678
|
+
'month-6',
|
|
2679
|
+
'month-7',
|
|
2680
|
+
'month-8',
|
|
2681
|
+
'month-9',
|
|
2682
|
+
'month-10',
|
|
2683
|
+
'month-11',
|
|
2684
|
+
'month-12'
|
|
2685
|
+
]);
|
|
2686
|
+
function normalizedWorkSpreadsheetAutoFilterRange(selection) {
|
|
2687
|
+
const rowStart = finiteIndex(selection?.row?.[0]);
|
|
2688
|
+
const rowEnd = finiteIndex(selection?.row?.[1]);
|
|
2689
|
+
const columnStart = finiteIndex(selection?.column?.[0]);
|
|
2690
|
+
const columnEnd = finiteIndex(selection?.column?.[1]);
|
|
2691
|
+
if (null === rowStart || null === rowEnd || null === columnStart || null === columnEnd) return null;
|
|
2692
|
+
return {
|
|
2693
|
+
row: [
|
|
2694
|
+
Math.min(rowStart, rowEnd),
|
|
2695
|
+
Math.max(rowStart, rowEnd)
|
|
2696
|
+
],
|
|
2697
|
+
column: [
|
|
2698
|
+
Math.min(columnStart, columnEnd),
|
|
2699
|
+
Math.max(columnStart, columnEnd)
|
|
2700
|
+
]
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
function workSpreadsheetAutoFilterCriteria(sheet, column) {
|
|
2704
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet?.filter_select);
|
|
2705
|
+
if (!sheet || !range || !columnInRange(range, column)) return null;
|
|
2706
|
+
return criteriaFromEntry(sheet.filter?.[String(column - range.column[0])]);
|
|
2707
|
+
}
|
|
2708
|
+
function workSpreadsheetAutoFilterCriteriaEntries(sheet) {
|
|
2709
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
2710
|
+
if (!range) return [];
|
|
2711
|
+
const entries = [];
|
|
2712
|
+
for (const [key, value] of Object.entries(sheet.filter ?? {})){
|
|
2713
|
+
const column = Number(key);
|
|
2714
|
+
const criteria = criteriaFromEntry(value);
|
|
2715
|
+
if (criteria && Number.isSafeInteger(column) && !(column < 0) && !(column > range.column[1] - range.column[0])) entries.push({
|
|
2716
|
+
column,
|
|
2717
|
+
criteria
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
return entries.sort((left, right)=>left.column - right.column);
|
|
2721
|
+
}
|
|
2722
|
+
function workSpreadsheetSheetWithAutoFilterCriteria(sheet, column, criteria, context = {
|
|
2723
|
+
now: new Date()
|
|
2724
|
+
}) {
|
|
2725
|
+
const normalizedCriteria = normalizeWorkSpreadsheetFilterCriteria(criteria);
|
|
2726
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
2727
|
+
if (!normalizedCriteria || !range || !columnInRange(range, column)) return null;
|
|
2728
|
+
const rowhidden = hiddenRowsForCriteria(sheet, range, column, normalizedCriteria, context);
|
|
2729
|
+
if (!rowhidden) return null;
|
|
2730
|
+
const manuallyHiddenRows = workSpreadsheetAutoFilterManuallyHiddenRows(sheet);
|
|
2731
|
+
const filter = {
|
|
2732
|
+
...sheet.filter ?? {},
|
|
2733
|
+
[String(column - range.column[0])]: filterColumnState(range, column, normalizedCriteria, rowhidden)
|
|
2734
|
+
};
|
|
2735
|
+
return sheetWithFilterState(sheet, filter, manuallyHiddenRows);
|
|
2736
|
+
}
|
|
2737
|
+
function workSpreadsheetSheetWithoutAutoFilterCriteria(sheet, column) {
|
|
2738
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
2739
|
+
if (!range || !columnInRange(range, column)) return null;
|
|
2740
|
+
const key = String(column - range.column[0]);
|
|
2741
|
+
if (!Object.hasOwn(sheet.filter ?? {}, key)) return null;
|
|
2742
|
+
const filter = {
|
|
2743
|
+
...sheet.filter ?? {}
|
|
2744
|
+
};
|
|
2745
|
+
delete filter[key];
|
|
2746
|
+
return sheetWithFilterState(sheet, filter, workSpreadsheetAutoFilterManuallyHiddenRows(sheet));
|
|
2747
|
+
}
|
|
2748
|
+
function workSpreadsheetAutoFilterManuallyHiddenRows(sheet) {
|
|
2749
|
+
const owned = filterHiddenRows(sheet.filter);
|
|
2750
|
+
const manuallyHidden = new Set(Object.keys(sheet.config?.rowhidden ?? {}).filter((row)=>!owned.has(row)));
|
|
2751
|
+
for (const entry of Object.values(sheet.filter ?? {})){
|
|
2752
|
+
const overlap = criteriaPayloadFromEntry(entry)?.manualHiddenOverlap;
|
|
2753
|
+
if (Array.isArray(overlap)) {
|
|
2754
|
+
for (const row of overlap)if (validHiddenRowKey(row)) manuallyHidden.add(row);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
return manuallyHidden;
|
|
2758
|
+
}
|
|
2759
|
+
function workSpreadsheetSheetWithImportedAutoFilterCriteria(sheet, entries, context = {
|
|
2760
|
+
now: new Date()
|
|
2761
|
+
}) {
|
|
2762
|
+
const range = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
2763
|
+
if (!range || !entries.length) return sheet;
|
|
2764
|
+
const width = range.column[1] - range.column[0] + 1;
|
|
2765
|
+
const filter = {};
|
|
2766
|
+
const owned = new Set();
|
|
2767
|
+
for (const entry of entries){
|
|
2768
|
+
const criteria = normalizeWorkSpreadsheetFilterCriteria(entry.criteria);
|
|
2769
|
+
if (!criteria || !Number.isSafeInteger(entry.column) || entry.column < 0 || entry.column >= width) continue;
|
|
2770
|
+
const column = range.column[0] + entry.column;
|
|
2771
|
+
const rowhidden = hiddenRowsForCriteria(sheet, range, column, criteria, context) ?? {};
|
|
2772
|
+
for (const row of Object.keys(rowhidden))owned.add(row);
|
|
2773
|
+
filter[String(entry.column)] = filterColumnState(range, column, criteria, rowhidden);
|
|
2774
|
+
}
|
|
2775
|
+
if (!Object.keys(filter).length) return sheet;
|
|
2776
|
+
const manuallyHiddenRows = new Set(Object.keys(sheet.config?.rowhidden ?? {}).filter((row)=>!owned.has(row)));
|
|
2777
|
+
return sheetWithFilterState(sheet, filter, manuallyHiddenRows);
|
|
2778
|
+
}
|
|
2779
|
+
function normalizeWorkSpreadsheetFilterCriteria(criteria) {
|
|
2780
|
+
if (!criteria || 'object' != typeof criteria || Array.isArray(criteria)) return null;
|
|
2781
|
+
const value = criteria;
|
|
2782
|
+
const type = value.type;
|
|
2783
|
+
if ('blanks' === type || 'non-blanks' === type) return {
|
|
2784
|
+
type
|
|
2785
|
+
};
|
|
2786
|
+
if ('values' === type) {
|
|
2787
|
+
if (!Array.isArray(value.values) || 'boolean' != typeof value.includeBlanks) return null;
|
|
2788
|
+
const values = value.values.filter((item)=>'string' == typeof item);
|
|
2789
|
+
if (values.length !== value.values.length || !values.length && !value.includeBlanks) return null;
|
|
2790
|
+
return {
|
|
2791
|
+
type,
|
|
2792
|
+
values: [
|
|
2793
|
+
...new Set(values)
|
|
2794
|
+
],
|
|
2795
|
+
includeBlanks: value.includeBlanks
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
if ('between' === type || 'not-between' === type) return 'string' == typeof value.lower && 'string' == typeof value.upper ? {
|
|
2799
|
+
type,
|
|
2800
|
+
lower: value.lower,
|
|
2801
|
+
upper: value.upper
|
|
2802
|
+
} : null;
|
|
2803
|
+
if ('compound' === type) {
|
|
2804
|
+
if ('and' !== value.conjunction && 'or' !== value.conjunction || !Array.isArray(value.conditions) || 2 !== value.conditions.length) return null;
|
|
2805
|
+
const first = normalizeWorkSpreadsheetCustomFilterCondition(value.conditions[0]);
|
|
2806
|
+
const second = normalizeWorkSpreadsheetCustomFilterCondition(value.conditions[1]);
|
|
2807
|
+
return first && second ? {
|
|
2808
|
+
type,
|
|
2809
|
+
conjunction: value.conjunction,
|
|
2810
|
+
conditions: [
|
|
2811
|
+
first,
|
|
2812
|
+
second
|
|
2813
|
+
]
|
|
2814
|
+
} : null;
|
|
2815
|
+
}
|
|
2816
|
+
if ('top' === type || 'bottom' === type) return Number.isSafeInteger(value.count) && Number(value.count) >= 1 && Number(value.count) <= 500 ? {
|
|
2817
|
+
type,
|
|
2818
|
+
count: Number(value.count)
|
|
2819
|
+
} : null;
|
|
2820
|
+
if ('top-percent' === type || 'bottom-percent' === type) return Number.isSafeInteger(value.percent) && Number(value.percent) >= 1 && Number(value.percent) <= 100 ? {
|
|
2821
|
+
type,
|
|
2822
|
+
percent: Number(value.percent)
|
|
2823
|
+
} : null;
|
|
2824
|
+
if ('dynamic' === type) return 'string' == typeof value.kind && DYNAMIC_FILTER_KINDS.has(value.kind) ? {
|
|
2825
|
+
type,
|
|
2826
|
+
kind: value.kind
|
|
2827
|
+
} : null;
|
|
2828
|
+
return normalizeWorkSpreadsheetCustomFilterCondition(value);
|
|
2829
|
+
}
|
|
2830
|
+
function normalizeWorkSpreadsheetCustomFilterCondition(condition) {
|
|
2831
|
+
if (!condition || 'object' != typeof condition || Array.isArray(condition)) return null;
|
|
2832
|
+
const value = condition;
|
|
2833
|
+
if ('string' != typeof value.type || !SINGLE_VALUE_FILTER_TYPES.has(value.type) || 'string' != typeof value.value) return null;
|
|
2834
|
+
if (('matches-wildcard' === value.type || 'does-not-match-wildcard' === value.type) && !workSpreadsheetFilterTextIsBounded(value.value)) return null;
|
|
2835
|
+
return {
|
|
2836
|
+
type: value.type,
|
|
2837
|
+
value: value.value
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
function sheetWithFilterState(sheet, filter, manuallyHiddenRows) {
|
|
2841
|
+
const owned = filterHiddenRows(filter);
|
|
2842
|
+
const overlap = [
|
|
2843
|
+
...manuallyHiddenRows
|
|
2844
|
+
].filter((row)=>owned.has(row)).sort(compareHiddenRowKeys);
|
|
2845
|
+
const rowhidden = {};
|
|
2846
|
+
for (const row of manuallyHiddenRows)rowhidden[row] = 0;
|
|
2847
|
+
for (const row of owned)rowhidden[row] = 0;
|
|
2848
|
+
return {
|
|
2849
|
+
...sheet,
|
|
2850
|
+
filter: filterWithManualHiddenOverlap(filter, overlap),
|
|
2851
|
+
config: {
|
|
2852
|
+
...sheet.config ?? {},
|
|
2853
|
+
rowhidden
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
function filterWithManualHiddenOverlap(filter, manualHiddenOverlap) {
|
|
2858
|
+
return Object.fromEntries(Object.entries(filter).map(([column, entry])=>{
|
|
2859
|
+
const payload = criteriaPayloadFromEntry(entry);
|
|
2860
|
+
if (!payload || !entry || 'object' != typeof entry) return [
|
|
2861
|
+
column,
|
|
2862
|
+
entry
|
|
2863
|
+
];
|
|
2864
|
+
return [
|
|
2865
|
+
column,
|
|
2866
|
+
{
|
|
2867
|
+
...entry,
|
|
2868
|
+
caljs: {
|
|
2869
|
+
...payload,
|
|
2870
|
+
manualHiddenOverlap: [
|
|
2871
|
+
...manualHiddenOverlap
|
|
2872
|
+
]
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
];
|
|
2876
|
+
}));
|
|
2877
|
+
}
|
|
2878
|
+
function filterColumnState(range, column, criteria, rowhidden) {
|
|
2879
|
+
return {
|
|
2880
|
+
caljs: {
|
|
2881
|
+
criteria,
|
|
2882
|
+
kind: AUTO_FILTER_CRITERIA_KIND,
|
|
2883
|
+
version: AUTO_FILTER_CRITERIA_VERSION
|
|
2884
|
+
},
|
|
2885
|
+
cindex: column,
|
|
2886
|
+
edc: range.column[1],
|
|
2887
|
+
edr: range.row[1],
|
|
2888
|
+
optionstate: true,
|
|
2889
|
+
rowhidden,
|
|
2890
|
+
stc: range.column[0],
|
|
2891
|
+
str: range.row[0]
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
function hiddenRowsForCriteria(sheet, range, column, criteria, context) {
|
|
2895
|
+
if ('top' === criteria.type || 'top-percent' === criteria.type || 'bottom' === criteria.type || 'bottom-percent' === criteria.type) return hiddenRowsForRankCriteria(sheet, range, column, criteria);
|
|
2896
|
+
if ('dynamic' === criteria.type) return hiddenRowsForDynamicCriteria(sheet, range, column, criteria.kind, context);
|
|
2897
|
+
const matches = filterMatcher(criteria);
|
|
2898
|
+
if (!matches) return null;
|
|
2899
|
+
const cellAt = sheetCellReader(sheet);
|
|
2900
|
+
const hidden = {};
|
|
2901
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1)if (!matches(cellAt(row, column))) hidden[String(row)] = 0;
|
|
2902
|
+
return hidden;
|
|
2903
|
+
}
|
|
2904
|
+
function hiddenRowsForDynamicCriteria(sheet, range, column, kind, context) {
|
|
2905
|
+
const cellAt = sheetCellReader(sheet);
|
|
2906
|
+
const cells = {
|
|
2907
|
+
*[Symbol.iterator] () {
|
|
2908
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1)yield cellAt(row, column);
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
const matches = workSpreadsheetDynamicFilterMatcher(kind, cells, context);
|
|
2912
|
+
if (!matches) return null;
|
|
2913
|
+
const hidden = {};
|
|
2914
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1)if (!matches(cellAt(row, column))) hidden[String(row)] = 0;
|
|
2915
|
+
return hidden;
|
|
2916
|
+
}
|
|
2917
|
+
function hiddenRowsForRankCriteria(sheet, range, column, criteria) {
|
|
2918
|
+
const cellAt = sheetCellReader(sheet);
|
|
2919
|
+
const values = [];
|
|
2920
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1){
|
|
2921
|
+
const value = numericCellValue(cellAt(row, column));
|
|
2922
|
+
if (null !== value) values.push(value);
|
|
2923
|
+
}
|
|
2924
|
+
const top = 'top' === criteria.type || 'top-percent' === criteria.type;
|
|
2925
|
+
values.sort((left, right)=>top ? right - left : left - right);
|
|
2926
|
+
const requested = 'top' === criteria.type || 'bottom' === criteria.type ? criteria.count : Math.ceil(values.length * criteria.percent / 100);
|
|
2927
|
+
const boundary = values[Math.min(requested, values.length) - 1];
|
|
2928
|
+
const hidden = {};
|
|
2929
|
+
for(let row = range.row[0] + 1; row <= range.row[1]; row += 1){
|
|
2930
|
+
const value = numericCellValue(cellAt(row, column));
|
|
2931
|
+
if (null === value || void 0 === boundary || (top ? value < boundary : value > boundary)) hidden[String(row)] = 0;
|
|
2932
|
+
}
|
|
2933
|
+
return hidden;
|
|
2934
|
+
}
|
|
2935
|
+
function filterMatcher(criteria) {
|
|
2936
|
+
if ('blanks' === criteria.type) return cellIsBlank;
|
|
2937
|
+
if ('non-blanks' === criteria.type) return (cell)=>!cellIsBlank(cell);
|
|
2938
|
+
if ('values' === criteria.type) {
|
|
2939
|
+
const values = new Set(criteria.values.map(comparableText));
|
|
2940
|
+
return (cell)=>{
|
|
2941
|
+
const value = cellValue(cell);
|
|
2942
|
+
if (null === value) return criteria.includeBlanks;
|
|
2943
|
+
return values.has(comparableText(String(value)));
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
if ('between' === criteria.type || 'not-between' === criteria.type) return (cell)=>{
|
|
2947
|
+
const lower = compareCell(cell, criteria.lower);
|
|
2948
|
+
const upper = compareCell(cell, criteria.upper);
|
|
2949
|
+
const between = null !== lower && null !== upper && lower >= 0 && upper <= 0;
|
|
2950
|
+
return 'between' === criteria.type ? between : !between;
|
|
2951
|
+
};
|
|
2952
|
+
if ('compound' === criteria.type) {
|
|
2953
|
+
const first = customFilterMatcher(criteria.conditions[0]);
|
|
2954
|
+
const second = customFilterMatcher(criteria.conditions[1]);
|
|
2955
|
+
return 'and' === criteria.conjunction ? (cell)=>first(cell) && second(cell) : (cell)=>first(cell) || second(cell);
|
|
2956
|
+
}
|
|
2957
|
+
if ('top' === criteria.type || 'top-percent' === criteria.type || 'bottom' === criteria.type || 'bottom-percent' === criteria.type || 'dynamic' === criteria.type) return null;
|
|
2958
|
+
return customFilterMatcher(criteria);
|
|
2959
|
+
}
|
|
2960
|
+
function customFilterMatcher(criteria) {
|
|
2961
|
+
if ('matches-wildcard' === criteria.type || 'does-not-match-wildcard' === criteria.type) {
|
|
2962
|
+
const matchesWildcard = workSpreadsheetWildcardMatcher(criteria.value);
|
|
2963
|
+
const matches = (cell)=>{
|
|
2964
|
+
const value = cellValue(cell);
|
|
2965
|
+
return null !== value && matchesWildcard(String(value));
|
|
2966
|
+
};
|
|
2967
|
+
return 'matches-wildcard' === criteria.type ? matches : (cell)=>!matches(cell);
|
|
2968
|
+
}
|
|
2969
|
+
const expected = criteria.value;
|
|
2970
|
+
if ('contains' === criteria.type) return (cell)=>cellText(cell).includes(comparableText(expected));
|
|
2971
|
+
if ('does-not-contain' === criteria.type) return (cell)=>!cellText(cell).includes(comparableText(expected));
|
|
2972
|
+
if ('begins-with' === criteria.type) return (cell)=>cellText(cell).startsWith(comparableText(expected));
|
|
2973
|
+
if ('does-not-begin-with' === criteria.type) return (cell)=>!cellText(cell).startsWith(comparableText(expected));
|
|
2974
|
+
if ('ends-with' === criteria.type) return (cell)=>cellText(cell).endsWith(comparableText(expected));
|
|
2975
|
+
if ('does-not-end-with' === criteria.type) return (cell)=>!cellText(cell).endsWith(comparableText(expected));
|
|
2976
|
+
return (cell)=>{
|
|
2977
|
+
const order = compareCell(cell, expected);
|
|
2978
|
+
if (null === order) return false;
|
|
2979
|
+
if ('equals' === criteria.type) return 0 === order;
|
|
2980
|
+
if ('not-equals' === criteria.type) return 0 !== order;
|
|
2981
|
+
if ('greater-than' === criteria.type) return order > 0;
|
|
2982
|
+
if ('greater-than-or-equal' === criteria.type) return order >= 0;
|
|
2983
|
+
if ('less-than' === criteria.type) return order < 0;
|
|
2984
|
+
return order <= 0;
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
function compareCell(cell, expected) {
|
|
2988
|
+
const value = cellValue(cell);
|
|
2989
|
+
if (null === value) return null;
|
|
2990
|
+
const numericExpected = finiteNumber(expected);
|
|
2991
|
+
if ('number' == typeof value && null !== numericExpected) return value - numericExpected;
|
|
2992
|
+
return comparableText(String(value)).localeCompare(comparableText(expected), 'zh-CN', {
|
|
2993
|
+
numeric: true,
|
|
2994
|
+
sensitivity: 'base'
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
function cellIsBlank(cell) {
|
|
2998
|
+
return null === cellValue(cell);
|
|
2999
|
+
}
|
|
3000
|
+
function cellText(cell) {
|
|
3001
|
+
const value = cellValue(cell);
|
|
3002
|
+
return null === value ? '' : comparableText(String(value));
|
|
3003
|
+
}
|
|
3004
|
+
function cellValue(cell) {
|
|
3005
|
+
const value = cell?.v ?? cell?.m;
|
|
3006
|
+
if ('number' == typeof value && Number.isFinite(value)) return value;
|
|
3007
|
+
if ('string' == typeof value) return '' === value ? null : value;
|
|
3008
|
+
if ('boolean' == typeof value) return value;
|
|
3009
|
+
return null;
|
|
3010
|
+
}
|
|
3011
|
+
function numericCellValue(cell) {
|
|
3012
|
+
const value = cellValue(cell);
|
|
3013
|
+
return 'number' == typeof value ? value : null;
|
|
3014
|
+
}
|
|
3015
|
+
function comparableText(value) {
|
|
3016
|
+
return value.normalize('NFKC').toLocaleLowerCase('zh-CN');
|
|
3017
|
+
}
|
|
3018
|
+
function finiteNumber(value) {
|
|
3019
|
+
if (!value.trim()) return null;
|
|
3020
|
+
const numeric = Number(value);
|
|
3021
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
3022
|
+
}
|
|
3023
|
+
function criteriaFromEntry(entry) {
|
|
3024
|
+
const payload = criteriaPayloadFromEntry(entry);
|
|
3025
|
+
return payload ? normalizeWorkSpreadsheetFilterCriteria(payload.criteria) : null;
|
|
3026
|
+
}
|
|
3027
|
+
function criteriaPayloadFromEntry(entry) {
|
|
3028
|
+
if (!entry || 'object' != typeof entry) return null;
|
|
3029
|
+
const caljs = entry.caljs;
|
|
3030
|
+
if (!caljs || 'object' != typeof caljs) return null;
|
|
3031
|
+
const payload = caljs;
|
|
3032
|
+
if (payload.kind !== AUTO_FILTER_CRITERIA_KIND || payload.version !== AUTO_FILTER_CRITERIA_VERSION) return null;
|
|
3033
|
+
return normalizeWorkSpreadsheetFilterCriteria(payload.criteria) ? payload : null;
|
|
3034
|
+
}
|
|
3035
|
+
function filterHiddenRows(filter) {
|
|
3036
|
+
const rows = new Set();
|
|
3037
|
+
for (const value of Object.values(filter ?? {})){
|
|
3038
|
+
if (!value || 'object' != typeof value) continue;
|
|
3039
|
+
const rowhidden = value.rowhidden;
|
|
3040
|
+
if (rowhidden && 'object' == typeof rowhidden) for (const row of Object.keys(rowhidden))rows.add(row);
|
|
3041
|
+
}
|
|
3042
|
+
return rows;
|
|
3043
|
+
}
|
|
3044
|
+
function sheetCellReader(sheet) {
|
|
3045
|
+
if (sheet.data) return (row, column)=>sheet.data?.[row]?.[column] ?? null;
|
|
3046
|
+
const cells = new Map((sheet.celldata ?? []).map((entry)=>[
|
|
3047
|
+
`${entry.r}:${entry.c}`,
|
|
3048
|
+
entry.v ?? null
|
|
3049
|
+
]));
|
|
3050
|
+
return (row, column)=>cells.get(`${row}:${column}`) ?? null;
|
|
3051
|
+
}
|
|
3052
|
+
function columnInRange(range, column) {
|
|
3053
|
+
return Number.isSafeInteger(column) && column >= range.column[0] && column <= range.column[1];
|
|
3054
|
+
}
|
|
3055
|
+
function finiteIndex(value) {
|
|
3056
|
+
return 'number' == typeof value && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
3057
|
+
}
|
|
3058
|
+
function validHiddenRowKey(value) {
|
|
3059
|
+
return 'string' == typeof value && /^(?:0|[1-9]\d*)$/.test(value) && Number.isSafeInteger(Number(value));
|
|
3060
|
+
}
|
|
3061
|
+
function compareHiddenRowKeys(left, right) {
|
|
3062
|
+
return Number(left) - Number(right);
|
|
3063
|
+
}
|
|
3064
|
+
const work_spreadsheet_data_validation_MILLISECONDS_PER_DAY = 86400000;
|
|
2164
3065
|
const SPREADSHEET_DATA_VALIDATION_ERROR_STYLES = [
|
|
2165
3066
|
'stop',
|
|
2166
3067
|
'warning',
|
|
@@ -2184,7 +3085,7 @@ function normalizeSpreadsheetDateValidationBoundary(value, uses1904DateSystem =
|
|
|
2184
3085
|
if (!uses1904DateSystem && (0 === serial || 60 === serial)) return null;
|
|
2185
3086
|
const adjustedSerial = uses1904DateSystem ? serial : serial > 60 ? serial - 1 : serial;
|
|
2186
3087
|
const epoch = uses1904DateSystem ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 31);
|
|
2187
|
-
return isoDateFromTimestamp(epoch + adjustedSerial *
|
|
3088
|
+
return isoDateFromTimestamp(epoch + adjustedSerial * work_spreadsheet_data_validation_MILLISECONDS_PER_DAY);
|
|
2188
3089
|
}
|
|
2189
3090
|
function spreadsheetDateValidationFormula(value) {
|
|
2190
3091
|
const normalized = normalizeIsoDate(value.trim());
|
|
@@ -2838,7 +3739,7 @@ function readRichTextRun(element, value, colors) {
|
|
|
2838
3739
|
if (work_xlsx_rich_text_xlsxToggleEnabled(directChild(properties, 'b'))) run.bl = 1;
|
|
2839
3740
|
if (work_xlsx_rich_text_xlsxToggleEnabled(directChild(properties, 'i'))) run.it = 1;
|
|
2840
3741
|
if (work_xlsx_rich_text_xlsxToggleEnabled(directChild(properties, 'strike'))) run.cl = 1;
|
|
2841
|
-
const size =
|
|
3742
|
+
const size = work_xlsx_rich_text_finiteNumber(attribute(directChild(properties, 'sz') ?? properties, 'val'));
|
|
2842
3743
|
if (null !== size && size >= 1 && size <= 409) run.fs = size;
|
|
2843
3744
|
const underline = directChild(properties, 'u');
|
|
2844
3745
|
if (underline) {
|
|
@@ -2910,7 +3811,7 @@ function work_xlsx_rich_text_xlsxToggleEnabled(element) {
|
|
|
2910
3811
|
const value = attribute(element, 'val')?.trim().toLowerCase();
|
|
2911
3812
|
return '0' !== value && 'false' !== value && 'off' !== value;
|
|
2912
3813
|
}
|
|
2913
|
-
function
|
|
3814
|
+
function work_xlsx_rich_text_finiteNumber(value) {
|
|
2914
3815
|
if (null === value || !value.trim()) return null;
|
|
2915
3816
|
const parsed = Number(value);
|
|
2916
3817
|
return Number.isFinite(parsed) ? parsed : null;
|
|
@@ -2923,4 +3824,227 @@ function work_xlsx_rich_text_nonNegativeInteger(value) {
|
|
|
2923
3824
|
function work_xlsx_rich_text_isRecord(value) {
|
|
2924
3825
|
return 'object' == typeof value && null !== value && !Array.isArray(value);
|
|
2925
3826
|
}
|
|
2926
|
-
|
|
3827
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES = 256;
|
|
3828
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS = 128;
|
|
3829
|
+
const MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS = 4096;
|
|
3830
|
+
const MAX_SPREADSHEET_SORT_USER_CUSTOM_LISTS = 32;
|
|
3831
|
+
function parseSpreadsheetSortCustomList(text) {
|
|
3832
|
+
return validateSpreadsheetSortCustomList(text.split(/\r?\n|,|,/u).map((entry)=>entry.trim()).filter(Boolean));
|
|
3833
|
+
}
|
|
3834
|
+
function validateSpreadsheetSortCustomList(input) {
|
|
3835
|
+
if (!Array.isArray(input)) return customListError('invalid-entry');
|
|
3836
|
+
if (input.length < 2) return customListError('not-enough-entries');
|
|
3837
|
+
if (input.length > MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES) return customListError('too-many-entries');
|
|
3838
|
+
const entries = [];
|
|
3839
|
+
const seen = new Set();
|
|
3840
|
+
let totalCodePoints = 0;
|
|
3841
|
+
for (const candidate of input){
|
|
3842
|
+
if ('string' != typeof candidate) return customListError('invalid-entry');
|
|
3843
|
+
const entry = candidate.trim();
|
|
3844
|
+
if (!entry) return customListError('invalid-entry');
|
|
3845
|
+
const codePoints = Array.from(entry).length;
|
|
3846
|
+
if (codePoints > MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS) return customListError('entry-too-long');
|
|
3847
|
+
totalCodePoints += codePoints;
|
|
3848
|
+
if (totalCodePoints > MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS) return customListError('list-too-long');
|
|
3849
|
+
const matchKey = spreadsheetSortCustomListMatchKey(entry);
|
|
3850
|
+
if (seen.has(matchKey)) return customListError('duplicate-entry');
|
|
3851
|
+
seen.add(matchKey);
|
|
3852
|
+
entries.push(entry);
|
|
3853
|
+
}
|
|
3854
|
+
return {
|
|
3855
|
+
ok: true,
|
|
3856
|
+
entries
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
function createSpreadsheetSortCustomList(entries, source = 'session') {
|
|
3860
|
+
const validation = validateSpreadsheetSortCustomList(entries);
|
|
3861
|
+
if (!validation.ok) return null;
|
|
3862
|
+
const normalized = Object.freeze([
|
|
3863
|
+
...validation.entries
|
|
3864
|
+
]);
|
|
3865
|
+
return Object.freeze({
|
|
3866
|
+
source,
|
|
3867
|
+
entries: normalized,
|
|
3868
|
+
label: spreadsheetSortCustomListLabel(normalized)
|
|
3869
|
+
});
|
|
3870
|
+
}
|
|
3871
|
+
function mergeSpreadsheetSortCustomLists(lists) {
|
|
3872
|
+
const merged = [
|
|
3873
|
+
...SPREADSHEET_SORT_BUILT_IN_CUSTOM_LISTS
|
|
3874
|
+
];
|
|
3875
|
+
let userListCount = 0;
|
|
3876
|
+
for (const candidate of lists){
|
|
3877
|
+
if ('stored' !== candidate.source && 'session' !== candidate.source) continue;
|
|
3878
|
+
const list = createSpreadsheetSortCustomList(candidate.entries, candidate.source);
|
|
3879
|
+
if (!(!list || merged.some((item)=>spreadsheetSortCustomListsEqual(item.entries, list.entries)))) {
|
|
3880
|
+
if (userListCount >= MAX_SPREADSHEET_SORT_USER_CUSTOM_LISTS) break;
|
|
3881
|
+
merged.push(list);
|
|
3882
|
+
userListCount += 1;
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
return Object.freeze(merged);
|
|
3886
|
+
}
|
|
3887
|
+
function spreadsheetSortCustomListsEqual(left, right) {
|
|
3888
|
+
return left.length === right.length && left.every((entry, index)=>spreadsheetSortCustomListMatchKey(entry) === spreadsheetSortCustomListMatchKey(right[index] ?? ''));
|
|
3889
|
+
}
|
|
3890
|
+
function spreadsheetSortCustomListMatchKey(value) {
|
|
3891
|
+
return String(value).normalize('NFKC').trim().toLocaleLowerCase('zh-CN');
|
|
3892
|
+
}
|
|
3893
|
+
function spreadsheetSortCustomListLabel(entries) {
|
|
3894
|
+
const visible = entries.slice(0, 3).map((entry)=>truncateSpreadsheetSortCustomListLabelEntry(entry));
|
|
3895
|
+
return `${visible.join(' → ')}${entries.length > 3 ? ' → …' : ''}`;
|
|
3896
|
+
}
|
|
3897
|
+
function truncateSpreadsheetSortCustomListLabelEntry(entry) {
|
|
3898
|
+
const characters = Array.from(entry);
|
|
3899
|
+
return characters.length > 18 ? `${characters.slice(0, 17).join('')}…` : entry;
|
|
3900
|
+
}
|
|
3901
|
+
function customListError(code) {
|
|
3902
|
+
const messages = {
|
|
3903
|
+
'duplicate-entry': '自定义序列不能包含重复项(忽略大小写和全半角差异)。',
|
|
3904
|
+
'entry-too-long': `每个序列项最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRY_CODE_POINTS} 个字符。`,
|
|
3905
|
+
'invalid-entry': '自定义序列只能包含非空文本项。',
|
|
3906
|
+
'list-too-long': `一个自定义序列最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_CODE_POINTS.toLocaleString('en-US')} 个字符。`,
|
|
3907
|
+
'not-enough-entries': '自定义序列至少需要两个项目。',
|
|
3908
|
+
'too-many-entries': `一个自定义序列最多包含 ${MAX_SPREADSHEET_SORT_CUSTOM_LIST_ENTRIES} 个项目。`
|
|
3909
|
+
};
|
|
3910
|
+
return {
|
|
3911
|
+
ok: false,
|
|
3912
|
+
code,
|
|
3913
|
+
message: messages[code]
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
function builtInCustomList(label, entries) {
|
|
3917
|
+
return Object.freeze({
|
|
3918
|
+
source: 'built-in',
|
|
3919
|
+
entries: Object.freeze([
|
|
3920
|
+
...entries
|
|
3921
|
+
]),
|
|
3922
|
+
label
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
const SPREADSHEET_SORT_BUILT_IN_CUSTOM_LISTS = Object.freeze([
|
|
3926
|
+
builtInCustomList('月份(一月 → 十二月)', [
|
|
3927
|
+
'一月',
|
|
3928
|
+
'二月',
|
|
3929
|
+
'三月',
|
|
3930
|
+
'四月',
|
|
3931
|
+
'五月',
|
|
3932
|
+
'六月',
|
|
3933
|
+
'七月',
|
|
3934
|
+
'八月',
|
|
3935
|
+
'九月',
|
|
3936
|
+
'十月',
|
|
3937
|
+
'十一月',
|
|
3938
|
+
'十二月'
|
|
3939
|
+
]),
|
|
3940
|
+
builtInCustomList('星期(星期日 → 星期六)', [
|
|
3941
|
+
'星期日',
|
|
3942
|
+
'星期一',
|
|
3943
|
+
'星期二',
|
|
3944
|
+
'星期三',
|
|
3945
|
+
'星期四',
|
|
3946
|
+
'星期五',
|
|
3947
|
+
'星期六'
|
|
3948
|
+
]),
|
|
3949
|
+
builtInCustomList('周(周日 → 周六)', [
|
|
3950
|
+
'周日',
|
|
3951
|
+
'周一',
|
|
3952
|
+
'周二',
|
|
3953
|
+
'周三',
|
|
3954
|
+
'周四',
|
|
3955
|
+
'周五',
|
|
3956
|
+
'周六'
|
|
3957
|
+
]),
|
|
3958
|
+
builtInCustomList('月份(January → December)', [
|
|
3959
|
+
'January',
|
|
3960
|
+
'February',
|
|
3961
|
+
'March',
|
|
3962
|
+
'April',
|
|
3963
|
+
'May',
|
|
3964
|
+
'June',
|
|
3965
|
+
'July',
|
|
3966
|
+
'August',
|
|
3967
|
+
'September',
|
|
3968
|
+
'October',
|
|
3969
|
+
'November',
|
|
3970
|
+
'December'
|
|
3971
|
+
]),
|
|
3972
|
+
builtInCustomList('月份(Jan → Dec)', [
|
|
3973
|
+
'Jan',
|
|
3974
|
+
'Feb',
|
|
3975
|
+
'Mar',
|
|
3976
|
+
'Apr',
|
|
3977
|
+
'May',
|
|
3978
|
+
'Jun',
|
|
3979
|
+
'Jul',
|
|
3980
|
+
'Aug',
|
|
3981
|
+
'Sep',
|
|
3982
|
+
'Oct',
|
|
3983
|
+
'Nov',
|
|
3984
|
+
'Dec'
|
|
3985
|
+
]),
|
|
3986
|
+
builtInCustomList('星期(Sunday → Saturday)', [
|
|
3987
|
+
'Sunday',
|
|
3988
|
+
'Monday',
|
|
3989
|
+
'Tuesday',
|
|
3990
|
+
'Wednesday',
|
|
3991
|
+
'Thursday',
|
|
3992
|
+
'Friday',
|
|
3993
|
+
'Saturday'
|
|
3994
|
+
]),
|
|
3995
|
+
builtInCustomList('星期(Sun → Sat)', [
|
|
3996
|
+
'Sun',
|
|
3997
|
+
'Mon',
|
|
3998
|
+
'Tue',
|
|
3999
|
+
'Wed',
|
|
4000
|
+
'Thu',
|
|
4001
|
+
'Fri',
|
|
4002
|
+
'Sat'
|
|
4003
|
+
])
|
|
4004
|
+
]);
|
|
4005
|
+
const SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_VERSION = 1;
|
|
4006
|
+
const DEFAULT_SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_KEY = 'a3s-office.spreadsheet-sort-custom-lists.v1';
|
|
4007
|
+
class LocalStorageSpreadsheetSortCustomListStore {
|
|
4008
|
+
storage;
|
|
4009
|
+
key;
|
|
4010
|
+
constructor(storage, key = DEFAULT_SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_KEY){
|
|
4011
|
+
this.storage = storage;
|
|
4012
|
+
this.key = key;
|
|
4013
|
+
}
|
|
4014
|
+
load() {
|
|
4015
|
+
try {
|
|
4016
|
+
const source = this.storage.getItem(this.key);
|
|
4017
|
+
if (!source) return Object.freeze([]);
|
|
4018
|
+
const payload = JSON.parse(source);
|
|
4019
|
+
if (!spreadsheet_sort_custom_list_store_isRecord(payload) || payload.version !== SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_VERSION) return Object.freeze([]);
|
|
4020
|
+
return normalizeStoredSpreadsheetSortCustomLists(payload.lists);
|
|
4021
|
+
} catch {
|
|
4022
|
+
return Object.freeze([]);
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
save(lists) {
|
|
4026
|
+
const normalized = normalizeStoredSpreadsheetSortCustomLists(lists);
|
|
4027
|
+
this.storage.setItem(this.key, JSON.stringify({
|
|
4028
|
+
version: SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_VERSION,
|
|
4029
|
+
lists: normalized
|
|
4030
|
+
}));
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
function normalizeStoredSpreadsheetSortCustomLists(input) {
|
|
4034
|
+
if (!Array.isArray(input)) return Object.freeze([]);
|
|
4035
|
+
const lists = [];
|
|
4036
|
+
for (const candidate of input){
|
|
4037
|
+
const validation = validateSpreadsheetSortCustomList(candidate);
|
|
4038
|
+
if (!(!validation.ok || lists.some((entries)=>spreadsheetSortCustomListsEqual(entries, validation.entries)))) {
|
|
4039
|
+
lists.push(Object.freeze([
|
|
4040
|
+
...validation.entries
|
|
4041
|
+
]));
|
|
4042
|
+
if (lists.length >= 32) break;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
return Object.freeze(lists);
|
|
4046
|
+
}
|
|
4047
|
+
function spreadsheet_sort_custom_list_store_isRecord(value) {
|
|
4048
|
+
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
4049
|
+
}
|
|
4050
|
+
export { DEFAULT_SPREADSHEET_SORT_CUSTOM_LIST_STORAGE_KEY, LocalStorageSpreadsheetSortCustomListStore, SPREADSHEET_TABLE_TOTALS_FUNCTIONS, activeXlsxNativeFill, activeXlsxSemanticColorOrigin, applyImportedXlsxRichText, boundedSpreadsheetDataValidationText, coalesceXlsxRichTextRuns, createSpreadsheetSortCustomList, createWorkOfficeSpreadsheetCollaborationBinding as createOfficeSpreadsheetCollaborationBinding, createXlsxRichTextReadContext, deleteXlsxNativeFills, directXlsxAlignment, directXlsxFontStyle, fillSpreadsheetTableCalculatedColumns, hasXlsxDirectFontStyle, initializeWorkOfficeSpreadsheetCollaboration as initializeOfficeSpreadsheetCollaboration, isHighSurrogate, isLowSurrogate, isSpreadsheetTextOrientationId, isSpreadsheetUnderlineStyle, mergeSpreadsheetSortCustomLists, normalizeSpreadsheetDataValidationErrorStyle, normalizeSpreadsheetDateValidationBoundary, normalizeSpreadsheetTableCalculatedFormula, normalizeSpreadsheetTableTotalsFormula, normalizeSpreadsheetTableTotalsLabel, normalizeStoredSpreadsheetSortCustomLists, normalizeWorkSpreadsheetFilterCriteria, normalizeXlsxRichTextCell, normalizeXlsxRichTextColor, normalizeXlsxRichTextEditSource, normalizeXlsxRichTextRun, normalizedWorkSpreadsheetAutoFilterRange, parseSpreadsheetSortCustomList, patchSpreadsheetRichTextFontRuns, readWorkOfficeSpreadsheetCollaboration as readOfficeSpreadsheetCollaboration, readXlsxRichTextCells, reconcileSpreadsheetTableCalculatedColumns, reconcileSpreadsheetTableTotalsColumns, replaceWorkOfficeSpreadsheetCollaboration as replaceOfficeSpreadsheetCollaboration, sameXlsxRichTextRunStyle, sheetHasXlsxRichTextCells, spreadsheetCellValueWithDiagonalBorder, spreadsheetDateValidationFormula, spreadsheetDiagonalBorderFromCellValue, spreadsheetExplicitTextOrientationFromCell, spreadsheetSortCustomListMatchKey, spreadsheetSortCustomListsEqual, spreadsheetTableTotalsColumnPatch, spreadsheetTableTotalsFunctionFromOoxml, spreadsheetTableTotalsFunctionLabel, spreadsheetTableTotalsFunctionToOoxml, spreadsheetTableTotalsRowHasContent, spreadsheetTextOrientationCellStyle, spreadsheetTextOrientationChoiceFromCell, spreadsheetTextOrientationFromAngle, spreadsheetTextOrientationFromCell, spreadsheetTextOrientationFromChoice, spreadsheetTextOrientationFromXlsx, spreadsheetUnderlineCellValue, spreadsheetUnderlineCellValueFromSheetJs, spreadsheetUnderlineCellValueFromXlsx, spreadsheetUnderlineStyle, spreadsheetVisibleTextRotationFromCell, synchronizeSpreadsheetTableTotalsRow, validXlsxRichText, validateSpreadsheetSortCustomList, withDefaultSpreadsheetTableTotalsLabel, workSpreadsheetAutoFilterCriteria, workSpreadsheetAutoFilterCriteriaEntries, workSpreadsheetAutoFilterManuallyHiddenRows, workSpreadsheetCellIsDate, workSpreadsheetFilterTextCharacters, workSpreadsheetHasUnescapedWildcard, workSpreadsheetSheetWithAutoFilterCriteria, workSpreadsheetSheetWithImportedAutoFilterCriteria, workSpreadsheetSheetWithoutAutoFilterCriteria, writeXlsxRichTextCells, xlsxAlignmentMatches, xlsxBooleanAttribute, xlsxBorderLineMatches, xlsxColorMatches, xlsxNativeFillCellKeys, xlsxNativeFillKey, xlsxNativeFillSemanticColors, xlsxRichTextCellText, xlsxRichTextStyleOrigins, xlsxStyleCollectionIndex, xlsxToggleEnabled, xlsxUnderlineStyle };
|