@rowsncolumns/utils 9.2.38 → 9.2.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/excel-number-format.d.ts +128 -0
- package/dist/cjs/excel-number-format.d.ts.map +1 -0
- package/dist/cjs/excel-number-format.js +659 -0
- package/dist/cjs/formatter.d.ts +8 -0
- package/dist/cjs/formatter.d.ts.map +1 -1
- package/dist/cjs/formatter.js +151 -2
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/keyboard.d.ts +28 -0
- package/dist/cjs/keyboard.d.ts.map +1 -0
- package/dist/cjs/keyboard.js +67 -0
- package/dist/cjs/sheet-span.d.ts +13 -0
- package/dist/cjs/sheet-span.d.ts.map +1 -0
- package/dist/cjs/sheet-span.js +24 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/excel-number-format.d.ts +128 -0
- package/dist/esm/excel-number-format.d.ts.map +1 -0
- package/dist/esm/excel-number-format.js +640 -0
- package/dist/esm/formatter.d.ts +8 -0
- package/dist/esm/formatter.d.ts.map +1 -1
- package/dist/esm/formatter.js +135 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/keyboard.d.ts +28 -0
- package/dist/esm/keyboard.d.ts.map +1 -0
- package/dist/esm/keyboard.js +62 -0
- package/dist/esm/sheet-span.d.ts +13 -0
- package/dist/esm/sheet-span.d.ts.map +1 -0
- package/dist/esm/sheet-span.js +20 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Excel-parity pre-pass for number formatting.
|
|
4
|
+
*
|
|
5
|
+
* `numfmt` renders the bulk of Excel's format language, but it decides three
|
|
6
|
+
* things from the binary double where Excel decides them from the number's
|
|
7
|
+
* 15-significant-digit decimal representation:
|
|
8
|
+
*
|
|
9
|
+
* - rounding to a fixed number of decimals (`0.00` of `1.005` is `1.01`, not
|
|
10
|
+
* the `1.00` the double `1.00499999999999989…` rounds to);
|
|
11
|
+
* - `General`, which switches to scientific notation below 1E-4 and above
|
|
12
|
+
* 11 digits and rounds (never truncates) the 11th digit;
|
|
13
|
+
* - magnitudes ≥ 1E15, where Excel pads the 15 significant digits with
|
|
14
|
+
* zeros (`0.00` of `1E21` is `1000000000000000000000.00`).
|
|
15
|
+
*
|
|
16
|
+
* It also renders a date/time format for a serial outside Excel's calendar
|
|
17
|
+
* (negative, or ≥ 10000-01-01) as `#######`, as Excel does in the cell.
|
|
18
|
+
*
|
|
19
|
+
* Everything here works on the pattern's SECTION the value selects, so
|
|
20
|
+
* multi-section formats (`0.00;(0.00);"-"`) keep their Excel section rules.
|
|
21
|
+
* A format with `[>100]`-style conditions is resolved here too — which
|
|
22
|
+
* section matches, and whether the value's sign is shown — so the matched
|
|
23
|
+
* section gets the same decimal rules; the library then renders that one
|
|
24
|
+
* section on its own.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.excelNumberPrePass = exports.groupDigits = exports.hugeIntegerDigits = exports.excelGeneral = exports.roundDecimal15 = exports.stripConditionTokens = exports.resolveConditionalSection = exports.pickNumberSection = exports.analyzeFormatSection = exports.splitFormatSections = exports.DATE_OVERFLOW_DISPLAY = void 0;
|
|
28
|
+
var tslib_1 = require("tslib");
|
|
29
|
+
/** Excel's largest date serial: 9999-12-31 is 2958465 (1900 date system). */
|
|
30
|
+
var MAX_DATE_SERIAL_EXCLUSIVE = 2958466;
|
|
31
|
+
/**
|
|
32
|
+
* What Excel shows when a value cannot be rendered by its format: a
|
|
33
|
+
* date/time outside the calendar, or a number no conditional section takes.
|
|
34
|
+
*/
|
|
35
|
+
exports.DATE_OVERFLOW_DISPLAY = "#######";
|
|
36
|
+
/**
|
|
37
|
+
* Split a format code into its `;` sections, honouring quotes, bracket
|
|
38
|
+
* tokens and backslash escapes.
|
|
39
|
+
*/
|
|
40
|
+
var splitFormatSections = function (code) {
|
|
41
|
+
var sections = [];
|
|
42
|
+
var current = "";
|
|
43
|
+
var inQuotes = false;
|
|
44
|
+
var inBrackets = false;
|
|
45
|
+
for (var i = 0; i < code.length; i++) {
|
|
46
|
+
var c = code[i];
|
|
47
|
+
if (inQuotes) {
|
|
48
|
+
current += c;
|
|
49
|
+
if (c === '"')
|
|
50
|
+
inQuotes = false;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (inBrackets) {
|
|
54
|
+
current += c;
|
|
55
|
+
if (c === "]")
|
|
56
|
+
inBrackets = false;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (c === "\\" && i + 1 < code.length) {
|
|
60
|
+
current += c + code[i + 1];
|
|
61
|
+
i++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (c === '"') {
|
|
65
|
+
inQuotes = true;
|
|
66
|
+
current += c;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (c === "[") {
|
|
70
|
+
inBrackets = true;
|
|
71
|
+
current += c;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (c === ";") {
|
|
75
|
+
sections.push(current);
|
|
76
|
+
current = "";
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
current += c;
|
|
80
|
+
}
|
|
81
|
+
sections.push(current);
|
|
82
|
+
return sections;
|
|
83
|
+
};
|
|
84
|
+
exports.splitFormatSections = splitFormatSections;
|
|
85
|
+
var isDigitPlaceholder = function (c) { return c === "0" || c === "#" || c === "?"; };
|
|
86
|
+
var isDateLetter = function (c) { return /[ymdhsYMDHS]/.test(c); };
|
|
87
|
+
var CONDITION_TOKEN = /^(<>|<=|>=|=|<|>)\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*$/;
|
|
88
|
+
var parseCondition = function (token) {
|
|
89
|
+
var match = CONDITION_TOKEN.exec(token);
|
|
90
|
+
if (!match)
|
|
91
|
+
return undefined;
|
|
92
|
+
return {
|
|
93
|
+
op: match[1],
|
|
94
|
+
operand: Number(match[2]),
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
/** Classify one section of a format code. */
|
|
98
|
+
var analyzeFormatSection = function (section) {
|
|
99
|
+
var info = {
|
|
100
|
+
kind: "other",
|
|
101
|
+
body: "",
|
|
102
|
+
hasCondition: false,
|
|
103
|
+
isElapsed: false,
|
|
104
|
+
generalPrefix: "",
|
|
105
|
+
decimals: 0,
|
|
106
|
+
percent: 0,
|
|
107
|
+
commaScale: 0,
|
|
108
|
+
grouping: false,
|
|
109
|
+
isPlainNumber: true,
|
|
110
|
+
volatileSign: false,
|
|
111
|
+
};
|
|
112
|
+
// Pass 1: strip bracket tokens, classify them.
|
|
113
|
+
var body = "";
|
|
114
|
+
var inQuotes = false;
|
|
115
|
+
var hasToken = false;
|
|
116
|
+
for (var i = 0; i < section.length; i++) {
|
|
117
|
+
var c = section[i];
|
|
118
|
+
if (inQuotes) {
|
|
119
|
+
body += c;
|
|
120
|
+
if (c === '"')
|
|
121
|
+
inQuotes = false;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (c === "\\" && i + 1 < section.length) {
|
|
125
|
+
body += c + section[i + 1];
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (c === '"') {
|
|
130
|
+
inQuotes = true;
|
|
131
|
+
body += c;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (c === "[") {
|
|
135
|
+
var end = section.indexOf("]", i);
|
|
136
|
+
if (end === -1) {
|
|
137
|
+
info.kind = "other";
|
|
138
|
+
return info;
|
|
139
|
+
}
|
|
140
|
+
var token = section.slice(i + 1, end);
|
|
141
|
+
i = end;
|
|
142
|
+
hasToken = true;
|
|
143
|
+
if (/^[<>=]/.test(token)) {
|
|
144
|
+
var condition = parseCondition(token);
|
|
145
|
+
if (!condition) {
|
|
146
|
+
info.kind = "other";
|
|
147
|
+
return info;
|
|
148
|
+
}
|
|
149
|
+
info.hasCondition = true;
|
|
150
|
+
info.condition = condition;
|
|
151
|
+
}
|
|
152
|
+
else if (/^[hmsHMS]+$/.test(token)) {
|
|
153
|
+
info.isElapsed = true;
|
|
154
|
+
body += "[".concat(token, "]");
|
|
155
|
+
}
|
|
156
|
+
else if (/^\$/.test(token) || /^color\s*\d+$/i.test(token)) {
|
|
157
|
+
// currency/locale pin or color: no effect on the digits
|
|
158
|
+
}
|
|
159
|
+
else if (/^(black|blue|cyan|green|magenta|red|white|yellow)$/i.test(token)) {
|
|
160
|
+
// color
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
info.kind = "other";
|
|
164
|
+
return info;
|
|
165
|
+
}
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
body += c;
|
|
169
|
+
}
|
|
170
|
+
info.body = body;
|
|
171
|
+
if (body.trim() === "") {
|
|
172
|
+
// `0;;;` hides the value; a section holding only `[Red]` / `[$-409]` /
|
|
173
|
+
// `[<0]` tokens is Excel's `General` (`[Blue];[Red]` colors by sign).
|
|
174
|
+
info.kind = hasToken ? "general" : "empty";
|
|
175
|
+
return info;
|
|
176
|
+
}
|
|
177
|
+
// General (optionally with a literal prefix such as `-General`).
|
|
178
|
+
var generalMatch = /^([^"A-Za-z0#?@]*)general$/i.exec(body.trim());
|
|
179
|
+
if (generalMatch) {
|
|
180
|
+
info.kind = "general";
|
|
181
|
+
info.generalPrefix = generalMatch[1];
|
|
182
|
+
return info;
|
|
183
|
+
}
|
|
184
|
+
// Pass 2: scan the body outside quotes / escapes / pad markers.
|
|
185
|
+
var placeholders = 0;
|
|
186
|
+
var afterDot = false;
|
|
187
|
+
var sawDot = false;
|
|
188
|
+
var hasDate = info.isElapsed;
|
|
189
|
+
var hasText = false;
|
|
190
|
+
var hasScientific = false;
|
|
191
|
+
var hasFraction = false;
|
|
192
|
+
var hasOtherLetter = false;
|
|
193
|
+
var hasLiteral = false;
|
|
194
|
+
var chars = [];
|
|
195
|
+
inQuotes = false;
|
|
196
|
+
for (var i = 0; i < body.length; i++) {
|
|
197
|
+
var c = body[i];
|
|
198
|
+
if (inQuotes) {
|
|
199
|
+
if (c === '"')
|
|
200
|
+
inQuotes = false;
|
|
201
|
+
else
|
|
202
|
+
hasLiteral = true;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (c === '"') {
|
|
206
|
+
inQuotes = true;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (c === "\\" || c === "_" || c === "*") {
|
|
210
|
+
hasLiteral = true;
|
|
211
|
+
i++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (c === "[") {
|
|
215
|
+
// elapsed token kept from pass 1
|
|
216
|
+
var end = body.indexOf("]", i);
|
|
217
|
+
i = end === -1 ? body.length : end;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
chars.push(c);
|
|
221
|
+
if (isDigitPlaceholder(c)) {
|
|
222
|
+
placeholders++;
|
|
223
|
+
if (afterDot)
|
|
224
|
+
info.decimals++;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (c === ".") {
|
|
228
|
+
if (!sawDot) {
|
|
229
|
+
sawDot = true;
|
|
230
|
+
afterDot = true;
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (c === ",") {
|
|
235
|
+
// A comma directly followed by a digit placeholder groups; any
|
|
236
|
+
// other comma placed after a digit placeholder scales by 1000.
|
|
237
|
+
var j = i + 1;
|
|
238
|
+
while (j < body.length && (body[j] === "_" || body[j] === "*"))
|
|
239
|
+
j += 2;
|
|
240
|
+
if (j < body.length && isDigitPlaceholder(body[j])) {
|
|
241
|
+
info.grouping = true;
|
|
242
|
+
}
|
|
243
|
+
else if (placeholders > 0) {
|
|
244
|
+
info.commaScale++;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
hasLiteral = true;
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (c === "%") {
|
|
252
|
+
info.percent++;
|
|
253
|
+
afterDot = false;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if ((c === "E" || c === "e") &&
|
|
257
|
+
(body[i + 1] === "+" || body[i + 1] === "-")) {
|
|
258
|
+
hasScientific = true;
|
|
259
|
+
i++;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (c === "/") {
|
|
263
|
+
if (placeholders > 0)
|
|
264
|
+
hasFraction = true;
|
|
265
|
+
else
|
|
266
|
+
hasLiteral = true;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (c === "@") {
|
|
270
|
+
hasText = true;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (isDateLetter(c) || c === "a" || c === "A" || c === "p" || c === "P") {
|
|
274
|
+
if (c === "a" || c === "A" || c === "p" || c === "P") {
|
|
275
|
+
// AM/PM or A/P markers
|
|
276
|
+
var rest = body.slice(i).toUpperCase();
|
|
277
|
+
if (rest.startsWith("AM/PM") || rest.startsWith("A/P")) {
|
|
278
|
+
hasDate = true;
|
|
279
|
+
i += rest.startsWith("AM/PM") ? 4 : 2;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
hasOtherLetter = true;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
hasDate = true;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (/[A-Za-z]/.test(c)) {
|
|
289
|
+
hasOtherLetter = true;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
// $ - + ( ) : space and other literal symbols
|
|
293
|
+
hasLiteral = true;
|
|
294
|
+
}
|
|
295
|
+
if (hasOtherLetter) {
|
|
296
|
+
info.kind = "other";
|
|
297
|
+
}
|
|
298
|
+
else if (hasText) {
|
|
299
|
+
info.kind = "text";
|
|
300
|
+
}
|
|
301
|
+
else if (hasDate) {
|
|
302
|
+
info.kind = "date";
|
|
303
|
+
}
|
|
304
|
+
else if (hasScientific || hasFraction) {
|
|
305
|
+
info.kind = "other";
|
|
306
|
+
}
|
|
307
|
+
else if (placeholders === 0) {
|
|
308
|
+
info.kind = "other";
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
info.kind = "number";
|
|
312
|
+
}
|
|
313
|
+
info.isPlainNumber =
|
|
314
|
+
info.kind === "number" && !hasLiteral && !info.hasCondition;
|
|
315
|
+
info.volatileSign = placeholders > 0 && !hasFraction;
|
|
316
|
+
return info;
|
|
317
|
+
};
|
|
318
|
+
exports.analyzeFormatSection = analyzeFormatSection;
|
|
319
|
+
/**
|
|
320
|
+
* Which section Excel applies to a numeric value: one section covers every
|
|
321
|
+
* number; two sections split positive-or-zero / negative; three add a zero
|
|
322
|
+
* section. Returns the index and whether the section's sign is implicit.
|
|
323
|
+
*/
|
|
324
|
+
var pickNumberSection = function (sectionCount, value) {
|
|
325
|
+
var numeric = Math.min(sectionCount, 3);
|
|
326
|
+
if (numeric <= 1)
|
|
327
|
+
return { index: 0, implicitSign: true };
|
|
328
|
+
if (value < 0)
|
|
329
|
+
return { index: 1, implicitSign: false };
|
|
330
|
+
if (value === 0 && numeric >= 3)
|
|
331
|
+
return { index: 2, implicitSign: false };
|
|
332
|
+
return { index: 0, implicitSign: false };
|
|
333
|
+
};
|
|
334
|
+
exports.pickNumberSection = pickNumberSection;
|
|
335
|
+
var conditionHolds = function (_a, value) {
|
|
336
|
+
var op = _a.op, operand = _a.operand;
|
|
337
|
+
switch (op) {
|
|
338
|
+
case "=":
|
|
339
|
+
return value === operand;
|
|
340
|
+
case ">":
|
|
341
|
+
return value > operand;
|
|
342
|
+
case "<":
|
|
343
|
+
return value < operand;
|
|
344
|
+
case ">=":
|
|
345
|
+
return value >= operand;
|
|
346
|
+
case "<=":
|
|
347
|
+
return value <= operand;
|
|
348
|
+
default:
|
|
349
|
+
return value !== operand;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* A condition only negative numbers satisfy. Excel renders such a section
|
|
354
|
+
* without the value's `-` (`[<-25]0` of -50 is `50`); every other conditional
|
|
355
|
+
* section shows it (`[<0]"neg"` of -3 is `-neg`).
|
|
356
|
+
*/
|
|
357
|
+
var matchesOnlyNegatives = function (cond) {
|
|
358
|
+
return cond !== undefined &&
|
|
359
|
+
cond.operand < 0 &&
|
|
360
|
+
(cond.op === "<" || cond.op === "<=" || cond.op === "=");
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* Excel's section selection for a format with `[>100]`-style conditions:
|
|
364
|
+
* the first of the (up to three) numeric sections whose condition holds —
|
|
365
|
+
* an unconditional section always holds — or `undefined` when none does.
|
|
366
|
+
* `conditions[i]` is section i's condition (or `undefined`).
|
|
367
|
+
*/
|
|
368
|
+
var resolveConditionalSection = function (conditions, value) {
|
|
369
|
+
var parts = conditions.length === 1 ? [conditions[0], undefined] : conditions.slice(0, 4);
|
|
370
|
+
var first = parts[0];
|
|
371
|
+
var showsSign = function (index) {
|
|
372
|
+
var cond = parts[index];
|
|
373
|
+
if (parts.length <= 2 && index === 1 && cond === undefined) {
|
|
374
|
+
// The unconditional second section of a two-section conditional
|
|
375
|
+
// format shows the sign only when the first condition leaves the
|
|
376
|
+
// negatives to it.
|
|
377
|
+
return (first !== undefined &&
|
|
378
|
+
(first.op === "=" ||
|
|
379
|
+
(first.operand >= 0 && (first.op === ">" || first.op === ">="))));
|
|
380
|
+
}
|
|
381
|
+
return !matchesOnlyNegatives(cond);
|
|
382
|
+
};
|
|
383
|
+
for (var index = 0; index < Math.min(parts.length, 3); index++) {
|
|
384
|
+
var cond = parts[index];
|
|
385
|
+
if (cond === undefined || conditionHolds(cond, value)) {
|
|
386
|
+
return {
|
|
387
|
+
index: index,
|
|
388
|
+
generated: index >= conditions.length,
|
|
389
|
+
minus: showsSign(index),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return undefined;
|
|
394
|
+
};
|
|
395
|
+
exports.resolveConditionalSection = resolveConditionalSection;
|
|
396
|
+
/** Remove the `[>100]`-style condition tokens from one section. */
|
|
397
|
+
var stripConditionTokens = function (section) {
|
|
398
|
+
var out = "";
|
|
399
|
+
var inQuotes = false;
|
|
400
|
+
for (var i = 0; i < section.length; i++) {
|
|
401
|
+
var c = section[i];
|
|
402
|
+
if (inQuotes) {
|
|
403
|
+
out += c;
|
|
404
|
+
if (c === '"')
|
|
405
|
+
inQuotes = false;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (c === "\\" && i + 1 < section.length) {
|
|
409
|
+
out += c + section[i + 1];
|
|
410
|
+
i++;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (c === '"') {
|
|
414
|
+
inQuotes = true;
|
|
415
|
+
out += c;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (c === "[") {
|
|
419
|
+
var end = section.indexOf("]", i);
|
|
420
|
+
if (end !== -1 && /^[<>=]/.test(section.slice(i + 1, end))) {
|
|
421
|
+
i = end;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
out += c;
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
};
|
|
429
|
+
exports.stripConditionTokens = stripConditionTokens;
|
|
430
|
+
/**
|
|
431
|
+
* Decompose |x| into 15 significant decimal digits: x = digits × 10^exp10
|
|
432
|
+
* where `digits` is a 15-character string and exp10 = exponent − 14.
|
|
433
|
+
*/
|
|
434
|
+
var decimal15 = function (x) {
|
|
435
|
+
var _a = tslib_1.__read(Math.abs(x).toExponential(14).split("e"), 2), mantissa = _a[0], exponent = _a[1];
|
|
436
|
+
return { digits: mantissa.replace(".", ""), exp10: Number(exponent) - 14 };
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* Round |x|·10^shift half away from zero to `decimals` places using its
|
|
440
|
+
* 15-significant-digit decimal representation, and return the rounded value
|
|
441
|
+
* scaled back (÷10^shift). Returns `x` unchanged when no digit is dropped.
|
|
442
|
+
*/
|
|
443
|
+
var roundDecimal15 = function (x, decimals, shift) {
|
|
444
|
+
if (shift === void 0) { shift = 0; }
|
|
445
|
+
if (!Number.isFinite(x) || x === 0)
|
|
446
|
+
return x;
|
|
447
|
+
var _a = decimal15(x), digits = _a.digits, exp10 = _a.exp10;
|
|
448
|
+
var exp = exp10 + shift;
|
|
449
|
+
if (exp >= -decimals)
|
|
450
|
+
return x;
|
|
451
|
+
var drop = -decimals - exp;
|
|
452
|
+
var sign = x < 0 ? -1 : 1;
|
|
453
|
+
if (drop > 15)
|
|
454
|
+
return sign * 0;
|
|
455
|
+
var mantissa = Number(digits); // ≤ 15 digits, exact
|
|
456
|
+
var p = Math.pow(10, drop);
|
|
457
|
+
var remainder = mantissa % p;
|
|
458
|
+
var quotient = (mantissa - remainder) / p;
|
|
459
|
+
if (remainder >= p / 2)
|
|
460
|
+
quotient += 1;
|
|
461
|
+
var scale = decimals + shift;
|
|
462
|
+
var rounded = scale >= 0 ? quotient / Math.pow(10, scale) : quotient * Math.pow(10, -scale);
|
|
463
|
+
return sign * rounded;
|
|
464
|
+
};
|
|
465
|
+
exports.roundDecimal15 = roundDecimal15;
|
|
466
|
+
/**
|
|
467
|
+
* |v| rounded half away from zero to `precision` significant digits on its
|
|
468
|
+
* 15-digit decimal representation, as (`digits`, decimal exponent of the
|
|
469
|
+
* first digit). `toPrecision` would round the binary double instead.
|
|
470
|
+
*/
|
|
471
|
+
var significantDigits = function (v, precision) {
|
|
472
|
+
var _a = decimal15(v), digits = _a.digits, exp10 = _a.exp10;
|
|
473
|
+
var exponent = exp10 + 14;
|
|
474
|
+
if (precision >= 15)
|
|
475
|
+
return { digits: digits, exponent: exponent };
|
|
476
|
+
var drop = 15 - precision;
|
|
477
|
+
var mantissa = Number(digits);
|
|
478
|
+
var p = Math.pow(10, drop);
|
|
479
|
+
var remainder = mantissa % p;
|
|
480
|
+
var quotient = (mantissa - remainder) / p;
|
|
481
|
+
if (remainder >= p / 2)
|
|
482
|
+
quotient += 1;
|
|
483
|
+
if (quotient >= Math.pow(10, precision)) {
|
|
484
|
+
quotient /= 10;
|
|
485
|
+
exponent += 1;
|
|
486
|
+
}
|
|
487
|
+
return { digits: String(quotient).padStart(precision, "0"), exponent: exponent };
|
|
488
|
+
};
|
|
489
|
+
/** `precision` significant digits of |v| in fixed notation. */
|
|
490
|
+
var toPrecisionFixed = function (v, precision) {
|
|
491
|
+
var _a = significantDigits(v, precision), digits = _a.digits, exponent = _a.exponent;
|
|
492
|
+
if (exponent >= 0) {
|
|
493
|
+
var intLength = exponent + 1;
|
|
494
|
+
if (digits.length <= intLength)
|
|
495
|
+
return digits.padEnd(intLength, "0");
|
|
496
|
+
return "".concat(digits.slice(0, intLength), ".").concat(digits.slice(intLength));
|
|
497
|
+
}
|
|
498
|
+
return "0.".concat("0".repeat(-exponent - 1)).concat(digits);
|
|
499
|
+
};
|
|
500
|
+
/** Excel `General` for a finite number, using `.`/`E` (caller localizes). */
|
|
501
|
+
var excelGeneral = function (value) {
|
|
502
|
+
if (value === 0 || !Number.isFinite(value))
|
|
503
|
+
return "0";
|
|
504
|
+
var negative = value < 0;
|
|
505
|
+
var v = Math.abs(value);
|
|
506
|
+
var exponent = decimal15(v).exp10 + 14;
|
|
507
|
+
var out;
|
|
508
|
+
if (exponent >= -4 && exponent <= -1) {
|
|
509
|
+
out = stripTrailingZeros(toPrecisionFixed(v, 10 + exponent));
|
|
510
|
+
}
|
|
511
|
+
else if (exponent >= 0 && exponent <= 9) {
|
|
512
|
+
out = stripTrailingZeros(toPrecisionFixed(v, 10));
|
|
513
|
+
}
|
|
514
|
+
else if (exponent === 10) {
|
|
515
|
+
out = toPrecisionFixed(v, 11);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
out = generalScientific(v);
|
|
519
|
+
}
|
|
520
|
+
if (out.length > 11)
|
|
521
|
+
out = generalScientific(v);
|
|
522
|
+
return negative ? "-".concat(out) : out;
|
|
523
|
+
};
|
|
524
|
+
exports.excelGeneral = excelGeneral;
|
|
525
|
+
var generalScientific = function (v) {
|
|
526
|
+
var _a = significantDigits(v, 6), digits = _a.digits, exponent = _a.exponent;
|
|
527
|
+
var m = stripTrailingZeros("".concat(digits[0], ".").concat(digits.slice(1)));
|
|
528
|
+
var sign = exponent < 0 ? "-" : "+";
|
|
529
|
+
return "".concat(m, "E").concat(sign).concat(String(Math.abs(exponent)).padStart(2, "0"));
|
|
530
|
+
};
|
|
531
|
+
var stripTrailingZeros = function (s) {
|
|
532
|
+
if (!s.includes("."))
|
|
533
|
+
return s;
|
|
534
|
+
return s.replace(/\.?0+$/, "");
|
|
535
|
+
};
|
|
536
|
+
/**
|
|
537
|
+
* Digits of |x| for |x| ≥ 1E15 as Excel shows them: the 15 significant
|
|
538
|
+
* digits followed by zeros (Excel stores nothing beyond 15 digits).
|
|
539
|
+
*/
|
|
540
|
+
var hugeIntegerDigits = function (x) {
|
|
541
|
+
var _a = decimal15(x), digits = _a.digits, exp10 = _a.exp10;
|
|
542
|
+
if (exp10 >= 0)
|
|
543
|
+
return digits + "0".repeat(exp10);
|
|
544
|
+
// |x| ≥ 1E15 always has exp10 ≥ 0; guard anyway.
|
|
545
|
+
return digits.slice(0, digits.length + exp10);
|
|
546
|
+
};
|
|
547
|
+
exports.hugeIntegerDigits = hugeIntegerDigits;
|
|
548
|
+
var groupDigits = function (digits, separator) {
|
|
549
|
+
var out = "";
|
|
550
|
+
for (var i = 0; i < digits.length; i++) {
|
|
551
|
+
var fromEnd = digits.length - i;
|
|
552
|
+
if (i > 0 && fromEnd % 3 === 0)
|
|
553
|
+
out += separator;
|
|
554
|
+
out += digits[i];
|
|
555
|
+
}
|
|
556
|
+
return out;
|
|
557
|
+
};
|
|
558
|
+
exports.groupDigits = groupDigits;
|
|
559
|
+
var HUGE = 1e15;
|
|
560
|
+
var GENERAL_SECTION = (0, exports.analyzeFormatSection)("General");
|
|
561
|
+
/**
|
|
562
|
+
* Excel's decimal rules for one non-date section. `signPrefix` is prepended
|
|
563
|
+
* to a rendering produced here; `value` keeps its sign for the library.
|
|
564
|
+
*/
|
|
565
|
+
var prePassSection = function (info, value, signPrefix, l10n) {
|
|
566
|
+
var magnitude = Math.abs(value);
|
|
567
|
+
switch (info.kind) {
|
|
568
|
+
case "empty":
|
|
569
|
+
return { text: "", value: value };
|
|
570
|
+
case "general": {
|
|
571
|
+
var rendered = (0, exports.excelGeneral)(magnitude).replace(".", l10n.decimal);
|
|
572
|
+
return { text: "".concat(signPrefix).concat(info.generalPrefix).concat(rendered), value: value };
|
|
573
|
+
}
|
|
574
|
+
case "number": {
|
|
575
|
+
var shift = 2 * info.percent - 3 * info.commaScale;
|
|
576
|
+
if (magnitude >= HUGE && info.isPlainNumber && shift === 0) {
|
|
577
|
+
var digits = (0, exports.hugeIntegerDigits)(value);
|
|
578
|
+
var intPart = info.grouping
|
|
579
|
+
? (0, exports.groupDigits)(digits, l10n.group)
|
|
580
|
+
: digits;
|
|
581
|
+
var frac = info.decimals > 0
|
|
582
|
+
? "".concat(l10n.decimal).concat("0".repeat(info.decimals))
|
|
583
|
+
: "";
|
|
584
|
+
return { text: "".concat(signPrefix).concat(intPart).concat(frac), value: value };
|
|
585
|
+
}
|
|
586
|
+
if (magnitude * Math.pow(10, shift) >= HUGE)
|
|
587
|
+
return { value: value };
|
|
588
|
+
return { value: (0, exports.roundDecimal15)(value, info.decimals, shift) };
|
|
589
|
+
}
|
|
590
|
+
default:
|
|
591
|
+
return { value: value };
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
/**
|
|
595
|
+
* A format with conditions: pick the section Excel applies, render that
|
|
596
|
+
* section alone for the magnitude (so it gets the decimal rules), and show
|
|
597
|
+
* the sign by Excel's conditional-section rule.
|
|
598
|
+
*/
|
|
599
|
+
var renderConditional = function (sections, infos, value, l10n, render) {
|
|
600
|
+
var _a;
|
|
601
|
+
var resolved = (0, exports.resolveConditionalSection)(infos.map(function (info) { return info.condition; }), value);
|
|
602
|
+
if (!resolved)
|
|
603
|
+
return { text: exports.DATE_OVERFLOW_DISPLAY, value: value };
|
|
604
|
+
var info = resolved.generated ? GENERAL_SECTION : infos[resolved.index];
|
|
605
|
+
var code = resolved.generated
|
|
606
|
+
? "General"
|
|
607
|
+
: (0, exports.stripConditionTokens)(sections[resolved.index]);
|
|
608
|
+
if (info.kind === "date" && !info.isElapsed) {
|
|
609
|
+
// A date never carries a sign; the calendar rule applies to the serial.
|
|
610
|
+
if (value < 0 || value >= MAX_DATE_SERIAL_EXCLUSIVE) {
|
|
611
|
+
return { text: exports.DATE_OVERFLOW_DISPLAY, value: value };
|
|
612
|
+
}
|
|
613
|
+
return { text: render(code, value), value: value };
|
|
614
|
+
}
|
|
615
|
+
var pre = prePassSection(info, Math.abs(value), "", l10n);
|
|
616
|
+
var rendered = (_a = pre.text) !== null && _a !== void 0 ? _a : render(code, pre.value);
|
|
617
|
+
// Elapsed durations keep their `-36:00` policy; the volatile sign is
|
|
618
|
+
// dropped when every rendered digit is zero.
|
|
619
|
+
var showSign = value < 0 &&
|
|
620
|
+
(info.isElapsed ||
|
|
621
|
+
(resolved.minus && (!info.volatileSign || /[1-9]/.test(rendered))));
|
|
622
|
+
return { text: showSign ? "-".concat(rendered) : rendered, value: value };
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* Apply Excel's decimal rules before handing a numeric value to `numfmt`.
|
|
626
|
+
* `render` formats one section of the pattern with the library; it is used
|
|
627
|
+
* when the pre-pass has to resolve a conditional format itself.
|
|
628
|
+
*/
|
|
629
|
+
var excelNumberPrePass = function (pattern, value, l10n, render) {
|
|
630
|
+
if (!Number.isFinite(value) || pattern.trim() === "")
|
|
631
|
+
return { value: value };
|
|
632
|
+
var sections = (0, exports.splitFormatSections)(pattern);
|
|
633
|
+
var infos = sections.map(exports.analyzeFormatSection);
|
|
634
|
+
if (infos.some(function (s) { return s.hasCondition; })) {
|
|
635
|
+
return renderConditional(sections, infos, value, l10n, render);
|
|
636
|
+
}
|
|
637
|
+
var _a = (0, exports.pickNumberSection)(infos.length, value), index = _a.index, implicitSign = _a.implicitSign;
|
|
638
|
+
var info = infos[index];
|
|
639
|
+
if (!info)
|
|
640
|
+
return { value: value };
|
|
641
|
+
if (info.kind === "date") {
|
|
642
|
+
if (info.isElapsed && sections.length === 1)
|
|
643
|
+
return { value: value };
|
|
644
|
+
if (value < 0 || value >= MAX_DATE_SERIAL_EXCLUSIVE) {
|
|
645
|
+
return { text: exports.DATE_OVERFLOW_DISPLAY, value: value };
|
|
646
|
+
}
|
|
647
|
+
return { value: value };
|
|
648
|
+
}
|
|
649
|
+
var pre = prePassSection(info, value, implicitSign && value < 0 ? "-" : "", l10n);
|
|
650
|
+
// Excel picks the section from the value's sign, then rounds: a tiny
|
|
651
|
+
// negative under an explicit negative section still renders that section
|
|
652
|
+
// (`0.00;(0.00)` of -0.001 is `(0.00)`). The library would see the rounded
|
|
653
|
+
// -0 as positive, so render the negative section for the zero magnitude.
|
|
654
|
+
if (index === 1 && value < 0 && pre.text === undefined && pre.value === 0) {
|
|
655
|
+
return { text: render(sections[index], 0), value: value };
|
|
656
|
+
}
|
|
657
|
+
return pre;
|
|
658
|
+
};
|
|
659
|
+
exports.excelNumberPrePass = excelNumberPrePass;
|
package/dist/cjs/formatter.d.ts
CHANGED
|
@@ -23,6 +23,14 @@ export declare const isTextPattern: (pattern: string | undefined | null) => bool
|
|
|
23
23
|
* characters are skipped; `ddd`/`dddd` are weekday names, not a day.
|
|
24
24
|
*/
|
|
25
25
|
export declare const isStandaloneMonthPattern: (pattern: string) => boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Does the pattern carry Excel's short `A/P` marker outside quoted literals,
|
|
28
|
+
* bracket directives and escapes? Returns the pattern with the marker
|
|
29
|
+
* upper-cased (Excel accepts `a/p` and renders it `A`/`P`; numfmt only
|
|
30
|
+
* tokenizes `A/P`) and its `[$-LCID]` tokens re-pointed at the companion
|
|
31
|
+
* `A`/`P` locale, or `undefined` when there is none.
|
|
32
|
+
*/
|
|
33
|
+
export declare const withShortAmPmMarker: (pattern: string) => string | undefined;
|
|
26
34
|
export declare const ssfFormat: (pattern: string, value: any, locale?: string) => string;
|
|
27
35
|
export declare const ssfFormatColor: (pattern: string, value: any) => string | undefined;
|
|
28
36
|
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../formatter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../formatter.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OASlE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KACjC,OAWF,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAWlE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAOlE,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,KAAG,OA4B1D,CAAC;AAuHF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,SA4C9D,CAAC;AAgBF,eAAO,MAAM,SAAS,GACpB,SAAS,MAAM,EACf,OAAO,GAAG,EACV,SAAS,MAAM,KACd,MAyCF,CAAC;AAmLF,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,OAAO,GAAG,KACT,MAAM,GAAG,SAoBX,CAAC"}
|