@rowsncolumns/utils 9.2.39 → 9.2.41

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.
Files changed (39) hide show
  1. package/dist/cjs/cell-data-helpers.d.ts +13 -0
  2. package/dist/cjs/cell-data-helpers.d.ts.map +1 -1
  3. package/dist/cjs/cell-data-helpers.js +19 -1
  4. package/dist/cjs/excel-number-format.d.ts +128 -0
  5. package/dist/cjs/excel-number-format.d.ts.map +1 -0
  6. package/dist/cjs/excel-number-format.js +659 -0
  7. package/dist/cjs/formatter.d.ts +20 -0
  8. package/dist/cjs/formatter.d.ts.map +1 -1
  9. package/dist/cjs/formatter.js +300 -2
  10. package/dist/cjs/index.d.ts +2 -0
  11. package/dist/cjs/index.d.ts.map +1 -1
  12. package/dist/cjs/index.js +2 -0
  13. package/dist/cjs/locale.d.ts +15 -0
  14. package/dist/cjs/locale.d.ts.map +1 -1
  15. package/dist/cjs/locale.js +58 -6
  16. package/dist/cjs/sheet-span.d.ts +13 -0
  17. package/dist/cjs/sheet-span.d.ts.map +1 -0
  18. package/dist/cjs/sheet-span.js +24 -0
  19. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  20. package/dist/esm/cell-data-helpers.d.ts +13 -0
  21. package/dist/esm/cell-data-helpers.d.ts.map +1 -1
  22. package/dist/esm/cell-data-helpers.js +14 -0
  23. package/dist/esm/excel-number-format.d.ts +128 -0
  24. package/dist/esm/excel-number-format.d.ts.map +1 -0
  25. package/dist/esm/excel-number-format.js +640 -0
  26. package/dist/esm/formatter.d.ts +20 -0
  27. package/dist/esm/formatter.d.ts.map +1 -1
  28. package/dist/esm/formatter.js +277 -1
  29. package/dist/esm/index.d.ts +2 -0
  30. package/dist/esm/index.d.ts.map +1 -1
  31. package/dist/esm/index.js +2 -0
  32. package/dist/esm/locale.d.ts +15 -0
  33. package/dist/esm/locale.d.ts.map +1 -1
  34. package/dist/esm/locale.js +42 -5
  35. package/dist/esm/sheet-span.d.ts +13 -0
  36. package/dist/esm/sheet-span.d.ts.map +1 -0
  37. package/dist/esm/sheet-span.js +20 -0
  38. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  39. 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;
@@ -23,6 +23,26 @@ 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;
34
+ /**
35
+ * Excel's `[$symbol-LCID]` token selects the currency symbol and the
36
+ * language of day/month names and AM/PM; digit grouping and the decimal
37
+ * separator follow the workbook locale (`[$€-407] #,##0.00` is
38
+ * `€ 1,234.50` on a US host). numfmt applies the token's locale to the whole
39
+ * section, separators included, so the locale part is dropped from every
40
+ * section that is not a date (number, scientific, fraction, text, …); a
41
+ * date section keeps the token for its names, re-pointed at a companion
42
+ * locale that carries the workbook's separators for fractional seconds.
43
+ * Returns `undefined` when nothing had to change.
44
+ */
45
+ export declare const withWorkbookSeparators: (pattern: string, locale?: string) => string | undefined;
26
46
  export declare const ssfFormat: (pattern: string, value: any, locale?: string) => string;
27
47
  export declare const ssfFormatColor: (pattern: string, value: any) => string | undefined;
28
48
  //# sourceMappingURL=formatter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../formatter.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;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;AA8EF,eAAO,MAAM,SAAS,GACpB,SAAS,MAAM,EACf,OAAO,GAAG,EACV,SAAS,MAAM,KACd,MAeF,CAAC;AAmLF,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,OAAO,GAAG,KACT,MAAM,GAAG,SAoBX,CAAC"}
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../formatter.ts"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;;;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;AAiIF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GACjC,SAAS,MAAM,EACf,SAAS,MAAM,KACd,MAAM,GAAG,SAiBX,CAAC;AAiBF,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"}