@shbernal/ts-xlsx 3.0.0 → 3.2.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 +34 -24
- package/dist/bytes.d.ts +10 -2
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +96 -2
- package/dist/core/address.js +87 -33
- package/dist/core/array-formula-ranges.d.ts +33 -0
- package/dist/core/array-formula-ranges.js +62 -0
- package/dist/core/autofilter.d.ts +4 -3
- package/dist/core/autofilter.js +31 -30
- package/dist/core/axis-handle.d.ts +2 -0
- package/dist/core/axis-handle.js +5 -2
- package/dist/core/cell.d.ts +4 -3
- package/dist/core/cell.js +4 -6
- package/dist/core/clone.d.ts +29 -0
- package/dist/core/clone.js +24 -0
- package/dist/core/color-resolution.js +4 -6
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +6 -2
- package/dist/core/comment-thread.d.ts +7 -3
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +11 -1
- package/dist/core/conditional-formatting-overlay.js +30 -3
- package/dist/core/conditional-formatting.d.ts +107 -11
- package/dist/core/conditional-formatting.js +76 -19
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +7 -1
- package/dist/core/data-validation-overlay.js +19 -4
- package/dist/core/data-validation.d.ts +22 -0
- package/dist/core/data-validation.js +30 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +170 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +19 -3
- package/dist/core/formula-references.d.ts +26 -0
- package/dist/core/formula-references.js +173 -0
- package/dist/core/formula-scan.d.ts +2 -0
- package/dist/core/formula-scan.js +58 -0
- package/dist/core/formula.d.ts +76 -17
- package/dist/core/formula.js +87 -80
- package/dist/core/function-values.d.ts +4 -0
- package/dist/core/function-values.js +58 -0
- package/dist/core/future-functions.d.ts +3 -0
- package/dist/core/{modern-functions.js → future-functions.js} +119 -88
- package/dist/core/grid-edits.d.ts +30 -3
- package/dist/core/grid-edits.js +248 -81
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/hyperlink.d.ts +39 -0
- package/dist/core/hyperlink.js +88 -0
- package/dist/core/image.d.ts +49 -14
- package/dist/core/image.js +16 -5
- package/dist/core/internal.d.ts +10 -2
- package/dist/core/limits.d.ts +25 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +18 -4
- package/dist/core/merge.js +33 -20
- package/dist/core/page-setup.d.ts +26 -11
- package/dist/core/pivot-table.d.ts +49 -10
- package/dist/core/pivot-table.js +112 -60
- package/dist/core/protection.d.ts +66 -4
- package/dist/core/protection.js +6 -11
- package/dist/core/range.js +34 -30
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +1 -0
- package/dist/core/row.js +6 -2
- package/dist/core/structured-reference.d.ts +7 -0
- package/dist/core/structured-reference.js +7 -0
- package/dist/core/style.d.ts +85 -70
- package/dist/core/style.js +92 -28
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +64 -16
- package/dist/core/table.js +87 -40
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +9 -5
- package/dist/core/value.d.ts +60 -28
- package/dist/core/value.js +48 -43
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +83 -1
- package/dist/core/workbook-styles.js +4 -0
- package/dist/core/workbook-theme.js +5 -3
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +41 -3
- package/dist/core/workbook.js +46 -41
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +49 -0
- package/dist/core/worksheet-merges.js +65 -0
- package/dist/core/worksheet-model.d.ts +10 -2
- package/dist/core/worksheet-model.js +28 -9
- package/dist/core/worksheet-pictures.d.ts +3 -3
- package/dist/core/worksheet-pictures.js +7 -6
- package/dist/core/worksheet.d.ts +165 -27
- package/dist/core/worksheet.js +206 -89
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +21 -14
- package/dist/entries/core.js +14 -7
- package/dist/entries/errors.d.ts +1 -0
- package/dist/entries/errors.js +1 -0
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +22 -0
- package/dist/hex.d.ts +8 -0
- package/dist/hex.js +3 -0
- package/dist/io/cell-metadata/metadata.d.ts +42 -0
- package/dist/io/cell-metadata/metadata.js +35 -0
- package/dist/io/cell-metadata/rich-values.d.ts +11 -0
- package/dist/io/cell-metadata/rich-values.js +80 -0
- package/dist/io/csv/delimiter.d.ts +21 -0
- package/dist/io/csv/delimiter.js +18 -0
- package/dist/io/csv/errors.d.ts +14 -0
- package/dist/io/csv/errors.js +5 -0
- package/dist/io/csv/read.d.ts +6 -1
- package/dist/io/csv/read.js +51 -22
- package/dist/io/csv/write.d.ts +19 -5
- package/dist/io/csv/write.js +24 -44
- package/dist/io/opc/inflate.js +14 -0
- package/dist/io/opc/namespaces.d.ts +16 -0
- package/dist/io/opc/namespaces.js +4 -0
- package/dist/io/opc/part-paths.d.ts +9 -0
- package/dist/io/opc/part-paths.js +5 -5
- package/dist/io/opc/read-opc.d.ts +58 -14
- package/dist/io/opc/read-opc.js +49 -21
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/sniff-format.d.ts +3 -3
- package/dist/io/opc/sniff-format.js +2 -2
- package/dist/io/opc/strict-relationships.d.ts +2 -0
- package/dist/io/opc/strict-relationships.js +12 -0
- package/dist/io/read-policy/column-budget.d.ts +26 -0
- package/dist/io/read-policy/column-budget.js +18 -0
- package/dist/io/read-policy/read-repair.d.ts +74 -0
- package/dist/io/read-policy/read-repair.js +119 -0
- package/dist/io/style/cell-style-resolution.d.ts +25 -0
- package/dist/io/style/cell-style-resolution.js +30 -0
- package/dist/io/style/xf-style.d.ts +23 -1
- package/dist/io/style/xf-style.js +8 -0
- package/dist/io/xlsb/formula.d.ts +2 -2
- package/dist/io/xlsb/formula.js +52 -35
- package/dist/io/xlsb/primitives.js +8 -14
- package/dist/io/xlsb/ptg-functions.d.ts +19 -10
- package/dist/io/xlsb/ptg-functions.js +495 -596
- package/dist/io/xlsb/read-metadata.d.ts +7 -0
- package/dist/io/xlsb/read-metadata.js +69 -0
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +26 -1
- package/dist/io/xlsb/read-worksheet.js +148 -106
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +70 -31
- package/dist/io/xlsb/record-stream.d.ts +30 -0
- package/dist/io/xlsb/record-stream.js +20 -0
- package/dist/io/xlsb/record-types.d.ts +14 -5
- package/dist/io/xlsb/record-types.js +14 -5
- package/dist/io/xlsb/sheet-protection.d.ts +20 -0
- package/dist/io/xlsb/sheet-protection.js +60 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +24 -9
- package/dist/io/xlsx/cell-accumulator.js +74 -21
- package/dist/io/xlsx/cell-metadata.d.ts +39 -0
- package/dist/io/xlsx/cell-metadata.js +162 -0
- package/dist/io/xlsx/cell-value.d.ts +19 -5
- package/dist/io/xlsx/cell-value.js +45 -30
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-span.d.ts +11 -0
- package/dist/io/xlsx/column-span.js +9 -0
- package/dist/io/xlsx/comments.d.ts +9 -30
- package/dist/io/xlsx/comments.js +25 -83
- package/dist/io/xlsx/conditional-formatting.d.ts +43 -23
- package/dist/io/xlsx/conditional-formatting.js +437 -138
- package/dist/io/xlsx/data-validation.d.ts +12 -7
- package/dist/io/xlsx/data-validation.js +67 -53
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +52 -52
- package/dist/io/xlsx/font-xml.d.ts +7 -0
- package/dist/io/xlsx/font-xml.js +104 -0
- package/dist/io/xlsx/hyperlinks.d.ts +19 -21
- package/dist/io/xlsx/hyperlinks.js +12 -32
- package/dist/io/xlsx/images.d.ts +49 -16
- package/dist/io/xlsx/images.js +149 -50
- package/dist/io/xlsx/namespaces.d.ts +24 -0
- package/dist/io/xlsx/namespaces.js +11 -0
- package/dist/io/xlsx/package-plan.d.ts +60 -15
- package/dist/io/xlsx/package-plan.js +83 -18
- package/dist/io/xlsx/part-names.d.ts +6 -8
- package/dist/io/xlsx/part-names.js +3 -7
- package/dist/io/xlsx/pivot.d.ts +5 -5
- package/dist/io/xlsx/pivot.js +26 -24
- package/dist/io/xlsx/preserved-splices.d.ts +6 -0
- package/dist/io/xlsx/preserved-splices.js +74 -0
- package/dist/io/xlsx/read-comments.d.ts +29 -0
- package/dist/io/xlsx/read-comments.js +75 -0
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +259 -0
- package/dist/io/xlsx/read-pivot.js +11 -4
- package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +8 -5
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +24 -4
- package/dist/io/xlsx/read-rows.d.ts +35 -11
- package/dist/io/xlsx/read-rows.js +73 -49
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +0 -30
- package/dist/io/xlsx/read-styles.js +45 -105
- package/dist/io/xlsx/read-workbook-xml.d.ts +42 -0
- package/dist/io/xlsx/read-workbook-xml.js +175 -0
- package/dist/io/xlsx/read-worksheet.d.ts +9 -3
- package/dist/io/xlsx/read-worksheet.js +70 -177
- package/dist/io/xlsx/read.d.ts +3 -14
- package/dist/io/xlsx/read.js +74 -411
- package/dist/io/xlsx/relationships.d.ts +3 -0
- package/dist/io/xlsx/relationships.js +4 -0
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +20 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +139 -0
- package/dist/io/xlsx/row-xml.js +222 -0
- package/dist/io/xlsx/shared-formulas.js +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +10 -1
- package/dist/io/xlsx/sheet-properties.js +182 -20
- package/dist/io/xlsx/strict-parts.d.ts +7 -0
- package/dist/io/xlsx/strict-parts.js +171 -0
- package/dist/io/xlsx/style-elements.d.ts +41 -0
- package/dist/io/xlsx/style-elements.js +136 -0
- package/dist/io/xlsx/styles.d.ts +17 -3
- package/dist/io/xlsx/styles.js +13 -209
- package/dist/io/xlsx/tables.d.ts +10 -2
- package/dist/io/xlsx/tables.js +55 -52
- package/dist/io/xlsx/theme-xml.d.ts +32 -4
- package/dist/io/xlsx/theme-xml.js +106 -71
- package/dist/io/xlsx/threaded-comments.d.ts +14 -11
- package/dist/io/xlsx/threaded-comments.js +16 -8
- package/dist/io/xlsx/workbook-xml.d.ts +45 -6
- package/dist/io/xlsx/workbook-xml.js +62 -54
- package/dist/io/xlsx/worksheet-xml.d.ts +26 -70
- package/dist/io/xlsx/worksheet-xml.js +69 -282
- package/dist/io/xlsx/write-stream.d.ts +90 -19
- package/dist/io/xlsx/write-stream.js +80 -25
- package/dist/io/xlsx/write.d.ts +12 -6
- package/dist/io/xlsx/write.js +226 -123
- package/dist/rel-type.d.ts +25 -0
- package/dist/rel-type.js +9 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +16 -5
- package/dist/vba/bytes.js +27 -6
- package/dist/vba/cfb-format.d.ts +55 -0
- package/dist/vba/cfb-format.js +35 -0
- package/dist/vba/cfb-writer.d.ts +8 -0
- package/dist/vba/cfb-writer.js +40 -40
- package/dist/vba/cfb.d.ts +21 -4
- package/dist/vba/cfb.js +124 -90
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/dir-records.d.ts +6 -0
- package/dist/vba/dir-records.js +10 -2
- package/dist/vba/errors.d.ts +11 -4
- package/dist/vba/index.d.ts +0 -2
- package/dist/vba/index.js +0 -2
- package/dist/vba/ms-ovba.d.ts +6 -2
- package/dist/vba/ms-ovba.js +66 -22
- package/dist/vba/project-editor.js +129 -119
- package/dist/vba/project.d.ts +15 -1
- package/dist/vba/project.js +38 -13
- package/dist/vba/vba-encoding.d.ts +4 -10
- package/dist/vba/vba-encoding.js +3 -17
- package/dist/xml/xml-attrs.d.ts +37 -0
- package/dist/xml/xml-attrs.js +30 -0
- package/dist/xml/xml-chars.d.ts +40 -0
- package/dist/xml/xml-chars.js +12 -0
- package/dist/xml/xml-namespaces.d.ts +31 -0
- package/dist/xml/xml-namespaces.js +50 -0
- package/dist/xml/xml-read.d.ts +101 -11
- package/dist/xml/xml-read.js +150 -18
- package/dist/xml/xml-scan.d.ts +25 -34
- package/dist/xml/xml-scan.js +69 -46
- package/dist/xml/xml.d.ts +59 -26
- package/dist/xml/xml.js +45 -23
- package/package.json +15 -9
- package/skills/ts-xlsx-upstream/SKILL.md +21 -13
- package/dist/core/modern-functions.d.ts +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { tokenSet } from '../token-set.js';
|
|
2
|
+
import { cloneWith } from './clone.js';
|
|
3
|
+
import { cloneBorder, cloneFill, cloneFont } from './style.js';
|
|
2
4
|
export const isCfValueObjectType = tokenSet({
|
|
3
5
|
num: true,
|
|
4
6
|
percent: true,
|
|
@@ -6,6 +8,8 @@ export const isCfValueObjectType = tokenSet({
|
|
|
6
8
|
min: true,
|
|
7
9
|
percentile: true,
|
|
8
10
|
formula: true,
|
|
11
|
+
autoMin: true,
|
|
12
|
+
autoMax: true,
|
|
9
13
|
});
|
|
10
14
|
export const isConditionalFormattingType = tokenSet({
|
|
11
15
|
expression: true,
|
|
@@ -53,6 +57,16 @@ export const isCfTimePeriod = tokenSet({
|
|
|
53
57
|
lastWeek: true,
|
|
54
58
|
nextWeek: true,
|
|
55
59
|
});
|
|
60
|
+
export const isDataBarDirection = tokenSet({
|
|
61
|
+
context: true,
|
|
62
|
+
leftToRight: true,
|
|
63
|
+
rightToLeft: true,
|
|
64
|
+
});
|
|
65
|
+
export const isDataBarAxisPosition = tokenSet({
|
|
66
|
+
automatic: true,
|
|
67
|
+
middle: true,
|
|
68
|
+
none: true,
|
|
69
|
+
});
|
|
56
70
|
export const isIconSetType = tokenSet({
|
|
57
71
|
'3Arrows': true,
|
|
58
72
|
'3ArrowsGray': true,
|
|
@@ -71,29 +85,72 @@ export const isIconSetType = tokenSet({
|
|
|
71
85
|
'5ArrowsGray': true,
|
|
72
86
|
'5Rating': true,
|
|
73
87
|
'5Quarters': true,
|
|
88
|
+
'3Stars': true,
|
|
89
|
+
'3Triangles': true,
|
|
90
|
+
'5Boxes': true,
|
|
74
91
|
});
|
|
92
|
+
export function isExtensionIconSet(iconSet) {
|
|
93
|
+
return iconSet === '3Stars' || iconSet === '3Triangles' || iconSet === '5Boxes';
|
|
94
|
+
}
|
|
95
|
+
export function isCfIconSetType(value) {
|
|
96
|
+
return value === 'NoIcons' || isIconSetType(value);
|
|
97
|
+
}
|
|
75
98
|
export function cloneConditionalFormatting(cf) {
|
|
76
|
-
|
|
99
|
+
const copy = { ref: cf.ref, rules: cf.rules.map(cloneRule) };
|
|
100
|
+
if (cf.extended !== undefined)
|
|
101
|
+
copy.extended = cf.extended;
|
|
102
|
+
return copy;
|
|
103
|
+
}
|
|
104
|
+
export function ruleNeedsExtension(rule) {
|
|
105
|
+
return (rule.type === 'iconSet' &&
|
|
106
|
+
((rule.iconSet !== undefined && isExtensionIconSet(rule.iconSet)) ||
|
|
107
|
+
(rule.icons !== undefined && rule.icons.length > 0)));
|
|
77
108
|
}
|
|
109
|
+
const RULE_CLONE = {
|
|
110
|
+
type: 'value',
|
|
111
|
+
priority: 'value',
|
|
112
|
+
stopIfTrue: 'value',
|
|
113
|
+
operator: 'value',
|
|
114
|
+
formulae: 'values',
|
|
115
|
+
text: 'value',
|
|
116
|
+
style: cloneStyle,
|
|
117
|
+
dxfId: 'value',
|
|
118
|
+
cfvo: 'records',
|
|
119
|
+
color: 'record',
|
|
120
|
+
colors: 'records',
|
|
121
|
+
minLength: 'value',
|
|
122
|
+
maxLength: 'value',
|
|
123
|
+
gradient: 'value',
|
|
124
|
+
border: 'value',
|
|
125
|
+
borderColor: 'record',
|
|
126
|
+
direction: 'value',
|
|
127
|
+
negativeFillColor: 'record',
|
|
128
|
+
negativeBorderColor: 'record',
|
|
129
|
+
negativeBarColorSameAsPositive: 'value',
|
|
130
|
+
negativeBarBorderColorSameAsPositive: 'value',
|
|
131
|
+
axisPosition: 'value',
|
|
132
|
+
axisColor: 'record',
|
|
133
|
+
iconSet: 'value',
|
|
134
|
+
icons: 'records',
|
|
135
|
+
reverse: 'value',
|
|
136
|
+
showValue: 'value',
|
|
137
|
+
rank: 'value',
|
|
138
|
+
percent: 'value',
|
|
139
|
+
bottom: 'value',
|
|
140
|
+
aboveAverage: 'value',
|
|
141
|
+
equalAverage: 'value',
|
|
142
|
+
stdDev: 'value',
|
|
143
|
+
timePeriod: 'value',
|
|
144
|
+
};
|
|
78
145
|
function cloneRule(rule) {
|
|
79
|
-
return
|
|
80
|
-
...rule,
|
|
81
|
-
...(rule.formulae !== undefined ? { formulae: [...rule.formulae] } : {}),
|
|
82
|
-
...(rule.cfvo !== undefined ? { cfvo: rule.cfvo.map((v) => ({ ...v })) } : {}),
|
|
83
|
-
...(rule.color !== undefined ? { color: { ...rule.color } } : {}),
|
|
84
|
-
...(rule.negativeFillColor !== undefined
|
|
85
|
-
? { negativeFillColor: { ...rule.negativeFillColor } }
|
|
86
|
-
: {}),
|
|
87
|
-
...(rule.axisColor !== undefined ? { axisColor: { ...rule.axisColor } } : {}),
|
|
88
|
-
...(rule.colors !== undefined ? { colors: rule.colors.map((c) => ({ ...c })) } : {}),
|
|
89
|
-
...(rule.style !== undefined ? { style: cloneStyle(rule.style) } : {}),
|
|
90
|
-
};
|
|
146
|
+
return cloneWith(rule, RULE_CLONE);
|
|
91
147
|
}
|
|
148
|
+
const STYLE_CLONE = {
|
|
149
|
+
font: cloneFont,
|
|
150
|
+
fill: cloneFill,
|
|
151
|
+
border: cloneBorder,
|
|
152
|
+
numFmt: 'value',
|
|
153
|
+
};
|
|
92
154
|
function cloneStyle(style) {
|
|
93
|
-
return
|
|
94
|
-
...style,
|
|
95
|
-
...(style.font !== undefined ? { font: { ...style.font } } : {}),
|
|
96
|
-
...(style.fill !== undefined ? { fill: { ...style.fill } } : {}),
|
|
97
|
-
...(style.border !== undefined ? { border: { ...style.border } } : {}),
|
|
98
|
-
};
|
|
155
|
+
return cloneWith(style, STYLE_CLONE);
|
|
99
156
|
}
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
export declare function overwrite<T extends object>(target: T, source: T): void;
|
|
2
2
|
export declare function replaceContents<T>(array: T[], next: readonly T[]): void;
|
|
3
|
+
/**
|
|
4
|
+
* Copy one key from `source` onto `target`, leaving `target` untouched when `source` omits it.
|
|
5
|
+
*
|
|
6
|
+
* One key at a time is the whole point, and the reason is a limit of the checker rather than a
|
|
7
|
+
* preference: over a union key the compiler cannot correlate `source[key]`'s type with `target[key]`'s,
|
|
8
|
+
* so a version taking the union needs a cast, and that cast is the one place a facet could be written
|
|
9
|
+
* into the wrong slot with nothing to notice. Bound to a single member here, the assignment is checked.
|
|
10
|
+
*
|
|
11
|
+
* Generic over the object as well as the key because these two lines had been written out once for a
|
|
12
|
+
* cell's formatting and once for a cell's content, each under its own paragraph making this point.
|
|
13
|
+
*/
|
|
14
|
+
export declare function copyKeyIfPresent<T extends object, K extends keyof T>(target: T, source: Readonly<T>, key: K): void;
|
package/dist/core/containers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DataValidation, type DataValidationEntry } from './data-validation.ts';
|
|
2
|
+
import type { AxisSplice } from './grid-shift.ts';
|
|
2
3
|
export declare class DataValidationOverlay {
|
|
3
4
|
#private;
|
|
4
5
|
/**
|
|
@@ -33,7 +34,12 @@ export declare class DataValidationOverlay {
|
|
|
33
34
|
* attached to rather than on whatever moved into their place. A rule whose every target area fell
|
|
34
35
|
* inside a deleted span is dropped with them.
|
|
35
36
|
*/
|
|
36
|
-
shift(
|
|
37
|
+
shift(splice: AxisSplice): void;
|
|
38
|
+
/**
|
|
39
|
+
* Rewrite every rule's formula operands through `rewrite`, keeping a rule whose operands did not
|
|
40
|
+
* change as the same object. A numeric operand is not a formula and passes through.
|
|
41
|
+
*/
|
|
42
|
+
mapFormulas(rewrite: (formula: string) => string): void;
|
|
37
43
|
/** Drop every validation, leaving the overlay empty. */
|
|
38
44
|
clear(): void;
|
|
39
45
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthoringError } from '../errors.js';
|
|
1
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
2
2
|
import { replaceContents } from './containers.js';
|
|
3
3
|
import { cloneDataValidation, } from './data-validation.js';
|
|
4
4
|
import { decodeSqrefRects, shiftSqref } from './merge.js';
|
|
@@ -8,7 +8,7 @@ export class DataValidationOverlay {
|
|
|
8
8
|
add(sqref, rule, options = {}) {
|
|
9
9
|
const rects = decodeSqrefRects(sqref);
|
|
10
10
|
if (rects.length === 0) {
|
|
11
|
-
throw new AuthoringError(`data validation range
|
|
11
|
+
throw new AuthoringError(`data validation range ${quoted(sqref)} names no cells`);
|
|
12
12
|
}
|
|
13
13
|
const stored = cloneDataValidation(rule);
|
|
14
14
|
const entry = { sqref, rule: stored };
|
|
@@ -30,11 +30,11 @@ export class DataValidationOverlay {
|
|
|
30
30
|
}
|
|
31
31
|
return undefined;
|
|
32
32
|
}
|
|
33
|
-
shift(
|
|
33
|
+
shift(splice) {
|
|
34
34
|
const entries = [];
|
|
35
35
|
const rects = [];
|
|
36
36
|
for (const entry of this.#entries) {
|
|
37
|
-
const sqref = shiftSqref(entry.sqref,
|
|
37
|
+
const sqref = shiftSqref(entry.sqref, splice);
|
|
38
38
|
if (sqref === undefined)
|
|
39
39
|
continue;
|
|
40
40
|
entries.push({ ...entry, sqref });
|
|
@@ -43,6 +43,21 @@ export class DataValidationOverlay {
|
|
|
43
43
|
replaceContents(this.#entries, entries);
|
|
44
44
|
replaceContents(this.#rects, rects);
|
|
45
45
|
}
|
|
46
|
+
mapFormulas(rewrite) {
|
|
47
|
+
for (const [index, entry] of this.#entries.entries()) {
|
|
48
|
+
const formulae = entry.rule.formulae;
|
|
49
|
+
if (formulae === undefined)
|
|
50
|
+
continue;
|
|
51
|
+
const next = formulae.map((operand) => typeof operand === 'string' ? rewrite(operand) : operand);
|
|
52
|
+
if (next.every((operand, i) => operand === formulae[i]))
|
|
53
|
+
continue;
|
|
54
|
+
const rule = { ...entry.rule, formulae: next };
|
|
55
|
+
this.#entries[index] = { ...entry, rule };
|
|
56
|
+
const indexed = this.#rects[index];
|
|
57
|
+
if (indexed !== undefined)
|
|
58
|
+
this.#rects[index] = { rects: indexed.rects, rule };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
46
61
|
clear() {
|
|
47
62
|
this.#entries.length = 0;
|
|
48
63
|
this.#rects.length = 0;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ClonePlan } from './clone.ts';
|
|
2
|
+
import type { AssertNever } from './internal.ts';
|
|
1
3
|
/** The kind of constraint a validation enforces. `list` is a dropdown; `custom` is an arbitrary
|
|
2
4
|
* boolean formula; `none` constrains nothing and exists only to carry the rule's messages; the rest
|
|
3
5
|
* bound a typed value (`whole`/`decimal`/`date`/`time`/`textLength`). */
|
|
@@ -13,6 +15,14 @@ export declare const isDataValidationOperator: (value: string) => value is DataV
|
|
|
13
15
|
export type DataValidationErrorStyle = 'stop' | 'warning' | 'information';
|
|
14
16
|
/** Narrow a raw `<dataValidation errorStyle>` token to a known {@link DataValidationErrorStyle}. */
|
|
15
17
|
export declare const isDataValidationErrorStyle: (value: string) => value is DataValidationErrorStyle;
|
|
18
|
+
/**
|
|
19
|
+
* How the input method editor behaves while a covered cell is edited. Only an East Asian input method
|
|
20
|
+
* acts on it: `hiragana`, the katakana, alpha and hangul widths switch its mode, `on` and `off` turn it
|
|
21
|
+
* on and off, `disabled` turns it off and keeps it off, and `noControl` leaves it as the user had it.
|
|
22
|
+
*/
|
|
23
|
+
export type DataValidationImeMode = 'noControl' | 'off' | 'on' | 'disabled' | 'hiragana' | 'fullKatakana' | 'halfKatakana' | 'fullAlpha' | 'halfAlpha' | 'fullHangul' | 'halfHangul';
|
|
24
|
+
/** Narrow a raw `<dataValidation imeMode>` token to a known {@link DataValidationImeMode}. */
|
|
25
|
+
export declare const isDataValidationImeMode: (value: string) => value is DataValidationImeMode;
|
|
16
26
|
/** One validation rule. `formulae` holds the operand(s), `formula1` then optional `formula2`: a
|
|
17
27
|
* numeric literal is stored as a number, while a cell reference, defined name, or list source keeps
|
|
18
28
|
* its verbatim string. */
|
|
@@ -21,9 +31,17 @@ export interface DataValidation {
|
|
|
21
31
|
operator?: DataValidationOperator;
|
|
22
32
|
formulae?: (string | number)[];
|
|
23
33
|
allowBlank?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Hide the in-cell dropdown arrow of a `list` rule while still enforcing the list, the usual way to
|
|
36
|
+
* validate against a list without offering a picker. Stored as `showDropDown="1"`, an attribute
|
|
37
|
+
* whose name says the opposite of what it does.
|
|
38
|
+
*/
|
|
39
|
+
suppressDropDown?: boolean;
|
|
24
40
|
showInputMessage?: boolean;
|
|
25
41
|
showErrorMessage?: boolean;
|
|
26
42
|
errorStyle?: DataValidationErrorStyle;
|
|
43
|
+
/** How the input method editor behaves in a covered cell; absent leaves it as the user had it. */
|
|
44
|
+
imeMode?: DataValidationImeMode;
|
|
27
45
|
error?: string;
|
|
28
46
|
errorTitle?: string;
|
|
29
47
|
prompt?: string;
|
|
@@ -42,4 +60,8 @@ export interface DataValidationEntry {
|
|
|
42
60
|
}
|
|
43
61
|
/** A defensive copy of a rule, so a stored validation never aliases the caller's object (nor its
|
|
44
62
|
* `formulae` array). */
|
|
63
|
+
declare const RULE_CLONE: ClonePlan<DataValidation>;
|
|
64
|
+
/** The proof that {@link RULE_CLONE} names every field of the rule. */
|
|
65
|
+
export type EveryDataValidationFieldIsCloned = AssertNever<Exclude<keyof Required<DataValidation>, keyof typeof RULE_CLONE>>;
|
|
45
66
|
export declare function cloneDataValidation(rule: DataValidation): DataValidation;
|
|
67
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tokenSet } from '../token-set.js';
|
|
2
|
+
import { cloneWith } from './clone.js';
|
|
2
3
|
export const isDataValidationType = tokenSet({
|
|
3
4
|
none: true,
|
|
4
5
|
list: true,
|
|
@@ -24,9 +25,34 @@ export const isDataValidationErrorStyle = tokenSet({
|
|
|
24
25
|
warning: true,
|
|
25
26
|
information: true,
|
|
26
27
|
});
|
|
28
|
+
export const isDataValidationImeMode = tokenSet({
|
|
29
|
+
noControl: true,
|
|
30
|
+
off: true,
|
|
31
|
+
on: true,
|
|
32
|
+
disabled: true,
|
|
33
|
+
hiragana: true,
|
|
34
|
+
fullKatakana: true,
|
|
35
|
+
halfKatakana: true,
|
|
36
|
+
fullAlpha: true,
|
|
37
|
+
halfAlpha: true,
|
|
38
|
+
fullHangul: true,
|
|
39
|
+
halfHangul: true,
|
|
40
|
+
});
|
|
41
|
+
const RULE_CLONE = {
|
|
42
|
+
type: 'value',
|
|
43
|
+
operator: 'value',
|
|
44
|
+
formulae: 'values',
|
|
45
|
+
allowBlank: 'value',
|
|
46
|
+
suppressDropDown: 'value',
|
|
47
|
+
showInputMessage: 'value',
|
|
48
|
+
showErrorMessage: 'value',
|
|
49
|
+
errorStyle: 'value',
|
|
50
|
+
imeMode: 'value',
|
|
51
|
+
error: 'value',
|
|
52
|
+
errorTitle: 'value',
|
|
53
|
+
prompt: 'value',
|
|
54
|
+
promptTitle: 'value',
|
|
55
|
+
};
|
|
27
56
|
export function cloneDataValidation(rule) {
|
|
28
|
-
return
|
|
29
|
-
...rule,
|
|
30
|
-
...(rule.formulae !== undefined ? { formulae: [...rule.formulae] } : {}),
|
|
31
|
-
};
|
|
57
|
+
return cloneWith(rule, RULE_CLONE);
|
|
32
58
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a `Date` through an Excel number-format code: `yyyy-mm-dd`, `d mmm yy`, `hh:mm:ss`.
|
|
3
|
+
*
|
|
4
|
+
* The one vocabulary this library speaks for dates. `Cell.numFmt` holds a code, `DEFAULT_DATE_NUMFMT`
|
|
5
|
+
* is one, `isDateFormat` classifies one, and the CSV writer's `dateFormat` used to be a moment.js
|
|
6
|
+
* token set instead -- case-sensitive, with `mm` meaning minutes and the month spelled `MM` -- so
|
|
7
|
+
* `writeCsv(wb, {dateFormat: cell.numFmt})`, the obvious thing to write, rendered every date cell as
|
|
8
|
+
* `2024-45-dd` with no throw and no warning. A library with two public date-format properties
|
|
9
|
+
* speaking different languages has no defensible answer to "which one is this".
|
|
10
|
+
*
|
|
11
|
+
* Codes are case-insensitive, as Excel's are. What is *not* covered, deliberately: the elapsed-time
|
|
12
|
+
* forms (`[h]`, `[mm]`), which measure a duration rather than name a moment, and the locale directive
|
|
13
|
+
* (`[$-409]`), which is dropped rather than honoured -- month and weekday names render in English.
|
|
14
|
+
* Both are recorded in ADR 0041.
|
|
15
|
+
*
|
|
16
|
+
* @param utc read the date's fields in UTC rather than in the runner's local time. A serial carries
|
|
17
|
+
* no timezone, so a `Date` that came from one is UTC wall-clock and this should be `true`; a `Date`
|
|
18
|
+
* a caller assigned may mean either.
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatSerialDate(date: Date, code: string, utc: boolean): string;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const MONTH_NAMES = [
|
|
2
|
+
'January',
|
|
3
|
+
'February',
|
|
4
|
+
'March',
|
|
5
|
+
'April',
|
|
6
|
+
'May',
|
|
7
|
+
'June',
|
|
8
|
+
'July',
|
|
9
|
+
'August',
|
|
10
|
+
'September',
|
|
11
|
+
'October',
|
|
12
|
+
'November',
|
|
13
|
+
'December',
|
|
14
|
+
];
|
|
15
|
+
const WEEKDAY_NAMES = [
|
|
16
|
+
'Sunday',
|
|
17
|
+
'Monday',
|
|
18
|
+
'Tuesday',
|
|
19
|
+
'Wednesday',
|
|
20
|
+
'Thursday',
|
|
21
|
+
'Friday',
|
|
22
|
+
'Saturday',
|
|
23
|
+
];
|
|
24
|
+
function formatRuns(code) {
|
|
25
|
+
const runs = [];
|
|
26
|
+
const literal = (text) => {
|
|
27
|
+
if (text !== '')
|
|
28
|
+
runs.push({ kind: 'literal', text, count: text.length });
|
|
29
|
+
};
|
|
30
|
+
for (let i = 0; i < code.length; i++) {
|
|
31
|
+
const ch = code[i];
|
|
32
|
+
if (ch === ';')
|
|
33
|
+
break;
|
|
34
|
+
if (ch === '[') {
|
|
35
|
+
const end = code.indexOf(']', i);
|
|
36
|
+
i = end === -1 ? code.length : end;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (ch === '"') {
|
|
40
|
+
const end = code.indexOf('"', i + 1);
|
|
41
|
+
literal(code.slice(i + 1, end === -1 ? code.length : end));
|
|
42
|
+
i = end === -1 ? code.length : end;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ch === '\\') {
|
|
46
|
+
literal(code[i + 1] ?? '');
|
|
47
|
+
i += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const fraction = /^\.0+/.exec(code.slice(i));
|
|
51
|
+
const previous = runs.at(-1);
|
|
52
|
+
if (fraction !== null && previous?.kind === 'placeholder' && previous.text === 's') {
|
|
53
|
+
runs.push({ kind: 'placeholder', text: '.0', count: fraction[0].length - 1 });
|
|
54
|
+
i += fraction[0].length - 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const lower = ch.toLowerCase();
|
|
58
|
+
if (lower === 'y' || lower === 'm' || lower === 'd' || lower === 'h' || lower === 's') {
|
|
59
|
+
let count = 1;
|
|
60
|
+
while (code[i + count]?.toLowerCase() === lower)
|
|
61
|
+
count += 1;
|
|
62
|
+
runs.push({ kind: 'placeholder', text: lower, count });
|
|
63
|
+
i += count - 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const meridiem = /^(AM\/PM|A\/P)/i.exec(code.slice(i));
|
|
67
|
+
if (meridiem !== null) {
|
|
68
|
+
const token = meridiem[0];
|
|
69
|
+
if (token.length === 3)
|
|
70
|
+
runs.push({ kind: 'placeholder', text: 'a/p', count: 3, written: token });
|
|
71
|
+
else
|
|
72
|
+
runs.push({ kind: 'placeholder', text: 'am/pm', count: token.length });
|
|
73
|
+
i += token.length - 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
literal(ch);
|
|
77
|
+
}
|
|
78
|
+
return runs;
|
|
79
|
+
}
|
|
80
|
+
function isMinuteRun(runs, index) {
|
|
81
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
82
|
+
const run = runs[i];
|
|
83
|
+
if (run.kind === 'literal')
|
|
84
|
+
continue;
|
|
85
|
+
if (run.text === 'h')
|
|
86
|
+
return true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
for (let i = index + 1; i < runs.length; i++) {
|
|
90
|
+
const run = runs[i];
|
|
91
|
+
if (run.kind === 'literal')
|
|
92
|
+
continue;
|
|
93
|
+
return run.text === 's';
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
export function formatSerialDate(date, code, utc) {
|
|
98
|
+
if (Number.isNaN(date.getTime()))
|
|
99
|
+
return '';
|
|
100
|
+
const runs = formatRuns(code);
|
|
101
|
+
const year = utc ? date.getUTCFullYear() : date.getFullYear();
|
|
102
|
+
const month = (utc ? date.getUTCMonth() : date.getMonth()) + 1;
|
|
103
|
+
const day = utc ? date.getUTCDate() : date.getDate();
|
|
104
|
+
const weekday = utc ? date.getUTCDay() : date.getDay();
|
|
105
|
+
const hour24 = utc ? date.getUTCHours() : date.getHours();
|
|
106
|
+
const minute = utc ? date.getUTCMinutes() : date.getMinutes();
|
|
107
|
+
const second = utc ? date.getUTCSeconds() : date.getSeconds();
|
|
108
|
+
const twelveHour = runs.some((run) => run.kind === 'placeholder' && (run.text === 'am/pm' || run.text === 'a/p'));
|
|
109
|
+
const hour = twelveHour ? hour24 % 12 || 12 : hour24;
|
|
110
|
+
const pad = (value, width) => String(value).padStart(width, '0');
|
|
111
|
+
let out = '';
|
|
112
|
+
for (const [index, run] of runs.entries()) {
|
|
113
|
+
if (run.kind === 'literal') {
|
|
114
|
+
out += run.text;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
switch (run.text) {
|
|
118
|
+
case 'y':
|
|
119
|
+
out += run.count <= 2 ? pad(year % 100, 2) : String(year);
|
|
120
|
+
break;
|
|
121
|
+
case 'm':
|
|
122
|
+
if (isMinuteRun(runs, index)) {
|
|
123
|
+
out += run.count >= 2 ? pad(minute, 2) : String(minute);
|
|
124
|
+
}
|
|
125
|
+
else if (run.count >= 5) {
|
|
126
|
+
out += MONTH_NAMES[month - 1][0];
|
|
127
|
+
}
|
|
128
|
+
else if (run.count === 4) {
|
|
129
|
+
out += MONTH_NAMES[month - 1];
|
|
130
|
+
}
|
|
131
|
+
else if (run.count === 3) {
|
|
132
|
+
out += MONTH_NAMES[month - 1].slice(0, 3);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
out += run.count === 2 ? pad(month, 2) : String(month);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case 'd':
|
|
139
|
+
if (run.count >= 4)
|
|
140
|
+
out += WEEKDAY_NAMES[weekday];
|
|
141
|
+
else if (run.count === 3)
|
|
142
|
+
out += WEEKDAY_NAMES[weekday].slice(0, 3);
|
|
143
|
+
else
|
|
144
|
+
out += run.count === 2 ? pad(day, 2) : String(day);
|
|
145
|
+
break;
|
|
146
|
+
case 'h':
|
|
147
|
+
out += run.count >= 2 ? pad(hour, 2) : String(hour);
|
|
148
|
+
break;
|
|
149
|
+
case 's':
|
|
150
|
+
out += run.count >= 2 ? pad(second, 2) : String(second);
|
|
151
|
+
break;
|
|
152
|
+
case '.0': {
|
|
153
|
+
const digits = pad(utc ? date.getUTCMilliseconds() : date.getMilliseconds(), 3);
|
|
154
|
+
out += `.${(digits + '000').slice(0, run.count)}`;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'am/pm':
|
|
158
|
+
out += hour24 < 12 ? 'AM' : 'PM';
|
|
159
|
+
break;
|
|
160
|
+
case 'a/p': {
|
|
161
|
+
const written = run.written ?? 'A/P';
|
|
162
|
+
out += hour24 < 12 ? written.charAt(0) : written.charAt(2);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
package/dist/core/date.d.ts
CHANGED
|
@@ -1,20 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which date system a workbook counts its serials in: the `date1904` flag of `<workbookPr>`.
|
|
3
|
+
*
|
|
4
|
+
* Carried as the epoch year rather than as a boolean because it is what every conversion here
|
|
5
|
+
* actually needs, and a boolean would mean a `? 1904 : 1900` at each of them: five chances to write
|
|
6
|
+
* the ternary backwards, on a value whose wrongness is silent by construction (a date read under the
|
|
7
|
+
* wrong system is still a perfectly good date, four years off).
|
|
8
|
+
*/
|
|
9
|
+
export type DateEpoch = 1900 | 1904;
|
|
1
10
|
/**
|
|
2
11
|
* The number format applied to a `Date` cell that carries no explicit format of its own,
|
|
3
12
|
* so the value renders, and reads back, as a date rather than a bare serial number.
|
|
4
13
|
*/
|
|
5
14
|
export declare const DEFAULT_DATE_NUMFMT = "yyyy-mm-dd";
|
|
6
15
|
/**
|
|
7
|
-
* Convert a JS `Date` to its
|
|
8
|
-
* quirk so the value renders on the calendar date Excel would show.
|
|
9
|
-
* carry the time of day. The caller must reject a non-finite (invalid) date
|
|
16
|
+
* Convert a JS `Date` to its Excel serial under the workbook's date system, reproducing the
|
|
17
|
+
* phantom-leap-day quirk (1900 only) so the value renders on the calendar date Excel would show.
|
|
18
|
+
* Fractional serials carry the time of day. The caller must reject a non-finite (invalid) date
|
|
19
|
+
* before here.
|
|
20
|
+
*/
|
|
21
|
+
export declare function dateToSerial(date: Date, epoch: DateEpoch): number;
|
|
22
|
+
/**
|
|
23
|
+
* Convert an Excel serial back to a UTC `Date` under the workbook's date system, accounting in the
|
|
24
|
+
* 1900 system for the phantom 1900-02-29 so serial 1 reads as 1900-01-01 (not 1899-12-31) and
|
|
25
|
+
* consecutive serials map to consecutive days.
|
|
10
26
|
*/
|
|
11
|
-
export declare function
|
|
27
|
+
export declare function serialToDate(serial: number, epoch: DateEpoch): Date;
|
|
12
28
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
29
|
+
* Parse a date-bearing text field (a Strict-mode `t="d"` cell value, a core-property timestamp) to a
|
|
30
|
+
* `Date`, or `null` when it carries nothing parseable.
|
|
31
|
+
*
|
|
32
|
+
* The `null` is the point. `new Date('not-a-date')` is a `Date` whose time is `NaN`: it satisfies
|
|
33
|
+
* `instanceof Date` and every guard downstream, survives into the model, and reaches serialisation,
|
|
34
|
+
* where it writes back as the literal string `Invalid Date` and loses the value for good. Dropping it
|
|
35
|
+
* at the boundary is the only reading that cannot lie.
|
|
16
36
|
*/
|
|
17
|
-
export declare function
|
|
37
|
+
export declare function parseDateText(text: string): Date | null;
|
|
18
38
|
/**
|
|
19
39
|
* Whether a number-format code renders its value as a date or time. A format is a date
|
|
20
40
|
* format when, once its non-formatting sections are removed (bracketed color/locale/
|
|
@@ -23,3 +43,18 @@ export declare function serialToDate(serial: number): Date;
|
|
|
23
43
|
* while `yyyy-mm-dd`, `dd/mm/yyyy`, and `[$-409]mmmm d, yyyy` are.
|
|
24
44
|
*/
|
|
25
45
|
export declare function isDateFormat(code: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Surface a number stored under a date format as a `Date`, and leave everything else alone.
|
|
48
|
+
*
|
|
49
|
+
* OOXML has no date type: a date is a number plus a number format that renders it as one, so this
|
|
50
|
+
* one test is what separates `45000` from `2023-03-15` across every reader. Only a plain number
|
|
51
|
+
* qualifies; a string, a boolean or a formula result of another kind under a date format keeps its
|
|
52
|
+
* own kind.
|
|
53
|
+
*
|
|
54
|
+
* One function rather than three copies of the test, because the corpus asserts the rule is identical
|
|
55
|
+
* across serialisations: the `.xlsb` reader, the `.xlsx` cell decoder and the cached formula-result
|
|
56
|
+
* decoder must all answer the same, and three spellings of one rule are three chances to disagree.
|
|
57
|
+
* The workbook's {@link DateEpoch} rides along for exactly that reason: it is the one input to this
|
|
58
|
+
* test that is not the cell's, and the three used to agree on the wrong answer for a 1904 file.
|
|
59
|
+
*/
|
|
60
|
+
export declare function coerceDateSerial<T>(value: T, numFmt: string | undefined, epoch: DateEpoch): T | Date;
|
package/dist/core/date.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
const MS_PER_DAY = 86_400_000;
|
|
2
2
|
const EPOCH_1900_UTC = Date.UTC(1899, 11, 30);
|
|
3
3
|
const PHANTOM_SERIAL = 60;
|
|
4
|
+
const EPOCH_1904_UTC = Date.UTC(1904, 0, 1);
|
|
4
5
|
export const DEFAULT_DATE_NUMFMT = 'yyyy-mm-dd';
|
|
5
|
-
export function dateToSerial(date) {
|
|
6
|
+
export function dateToSerial(date, epoch) {
|
|
7
|
+
if (epoch === 1904)
|
|
8
|
+
return (date.getTime() - EPOCH_1904_UTC) / MS_PER_DAY;
|
|
6
9
|
const days = (date.getTime() - EPOCH_1900_UTC) / MS_PER_DAY;
|
|
7
|
-
return days <= PHANTOM_SERIAL ? days - 1 : days;
|
|
10
|
+
return Math.floor(days) <= PHANTOM_SERIAL ? days - 1 : days;
|
|
8
11
|
}
|
|
9
|
-
export function serialToDate(serial) {
|
|
12
|
+
export function serialToDate(serial, epoch) {
|
|
13
|
+
if (epoch === 1904)
|
|
14
|
+
return new Date(EPOCH_1904_UTC + serial * MS_PER_DAY);
|
|
10
15
|
const dayOffset = serial < PHANTOM_SERIAL ? serial + 1 : serial;
|
|
11
16
|
return new Date(EPOCH_1900_UTC + dayOffset * MS_PER_DAY);
|
|
12
17
|
}
|
|
18
|
+
export function parseDateText(text) {
|
|
19
|
+
if (text === '')
|
|
20
|
+
return null;
|
|
21
|
+
const date = new Date(text);
|
|
22
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
23
|
+
}
|
|
13
24
|
export function isDateFormat(code) {
|
|
14
25
|
const stripped = code
|
|
15
26
|
.replace(/\[[^\]]*\]/g, '')
|
|
@@ -17,3 +28,8 @@ export function isDateFormat(code) {
|
|
|
17
28
|
.replace(/\\./g, '');
|
|
18
29
|
return /[ymdhs]/i.test(stripped);
|
|
19
30
|
}
|
|
31
|
+
export function coerceDateSerial(value, numFmt, epoch) {
|
|
32
|
+
return typeof value === 'number' && numFmt !== undefined && isDateFormat(numFmt)
|
|
33
|
+
? serialToDate(value, epoch)
|
|
34
|
+
: value;
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AxisSplice } from './grid-shift.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Shift every relative reference in a formula by `colDelta` columns and `rowDelta` rows, leaving
|
|
4
|
+
* absolute (`$`-anchored) axes fixed. This is how a shared-formula clone recovers its own formula from
|
|
5
|
+
* the master's: a master `A1*2` shared one row down reads back as `A2*2`, `$A$1*B1` shared one row and
|
|
6
|
+
* one column across as `$A$1*C2`, and `COUNTIF(A:A,A1)` shared one column right as `COUNTIF(B:B,B1)`.
|
|
7
|
+
* Whole-column and whole-row ranges shift at each relative end, as cells do. String literals,
|
|
8
|
+
* single-quoted sheet names, and bracketed structured references are copied verbatim, and a
|
|
9
|
+
* sheet-qualified reference shifts while its sheet name is untouched. Function names and defined names
|
|
10
|
+
* are not reference-shaped, so they pass through.
|
|
11
|
+
*/
|
|
12
|
+
export declare function translateFormula(formula: string, colDelta: number, rowDelta: number): string;
|
|
13
|
+
/** A splice as a formula sees it: which sheet's lines moved, and how. */
|
|
14
|
+
export interface SheetSplice {
|
|
15
|
+
/** The spliced sheet's name, matched in any case. */
|
|
16
|
+
readonly sheet: string;
|
|
17
|
+
readonly splice: AxisSplice;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Rewrite a formula's references for a splice of `edit.sheet`. `home` is the sheet the formula belongs
|
|
21
|
+
* to, whose name an unqualified reference means; `undefined` for a formula that has none, such as a
|
|
22
|
+
* workbook-scoped defined name, whose references to a sheet are always qualified.
|
|
23
|
+
*
|
|
24
|
+
* Returns the formula unchanged, as the same string, when nothing in it moved.
|
|
25
|
+
*/
|
|
26
|
+
export declare function spliceFormula(formula: string, home: string | undefined, edit: SheetSplice): string;
|