xls-codec 1.0.2 → 2.0.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.
Files changed (124) hide show
  1. package/README.md +128 -23
  2. package/dist/biff/bof-writer.cjs +18 -0
  3. package/dist/biff/bof-writer.d.cts +5 -0
  4. package/dist/biff/bof-writer.d.ts +5 -0
  5. package/dist/biff/bof-writer.js +17 -0
  6. package/dist/biff/builder.cjs +48 -0
  7. package/dist/biff/builder.d.cts +17 -0
  8. package/dist/biff/builder.d.ts +17 -0
  9. package/dist/biff/builder.js +47 -0
  10. package/dist/biff/cursor.cjs +4 -0
  11. package/dist/biff/cursor.d.cts +1 -27
  12. package/dist/biff/cursor.d.ts +1 -27
  13. package/dist/biff/cursor.js +4 -0
  14. package/dist/biff/errors.cjs +7 -0
  15. package/dist/biff/errors.d.cts +3 -1
  16. package/dist/biff/errors.d.ts +3 -1
  17. package/dist/biff/errors.js +7 -1
  18. package/dist/biff/print-setup.cjs +108 -0
  19. package/dist/biff/print-setup.d.cts +2 -0
  20. package/dist/biff/print-setup.d.ts +2 -0
  21. package/dist/biff/print-setup.js +103 -0
  22. package/dist/biff/ptg-functions.cjs +383 -0
  23. package/dist/biff/ptg-functions.d.cts +7 -0
  24. package/dist/biff/ptg-functions.d.ts +7 -0
  25. package/dist/biff/ptg-functions.js +381 -0
  26. package/dist/biff/ptg.cjs +319 -0
  27. package/dist/biff/ptg.d.cts +2 -0
  28. package/dist/biff/ptg.d.ts +2 -0
  29. package/dist/biff/ptg.js +318 -0
  30. package/dist/biff/record-types.cjs +60 -0
  31. package/dist/biff/record-types.d.cts +41 -1
  32. package/dist/biff/record-types.d.ts +41 -1
  33. package/dist/biff/record-types.js +41 -1
  34. package/dist/biff/record-writer.cjs +29 -0
  35. package/dist/biff/record-writer.d.cts +7 -0
  36. package/dist/biff/record-writer.d.ts +7 -0
  37. package/dist/biff/record-writer.js +27 -0
  38. package/dist/biff/string-writer.cjs +58 -0
  39. package/dist/biff/string-writer.d.cts +13 -0
  40. package/dist/biff/string-writer.d.ts +13 -0
  41. package/dist/biff/string-writer.js +55 -0
  42. package/dist/biff/strings.d.cts +1 -1
  43. package/dist/biff/strings.d.ts +1 -1
  44. package/dist/biff/substreams.d.cts +1 -20
  45. package/dist/biff/substreams.d.ts +1 -20
  46. package/dist/biff/write-errors.cjs +10 -0
  47. package/dist/biff/write-errors.d.cts +6 -0
  48. package/dist/biff/write-errors.d.ts +6 -0
  49. package/dist/biff/write-errors.js +9 -0
  50. package/dist/biff/xf-colors.cjs +374 -0
  51. package/dist/biff/xf-colors.d.cts +2 -0
  52. package/dist/biff/xf-colors.d.ts +2 -0
  53. package/dist/biff/xf-colors.js +339 -0
  54. package/dist/biff/xf-writer.cjs +96 -0
  55. package/dist/biff/xf-writer.d.cts +29 -0
  56. package/dist/biff/xf-writer.d.ts +29 -0
  57. package/dist/biff/xf-writer.js +90 -0
  58. package/dist/container.cjs +13 -4
  59. package/dist/container.d.cts +10 -3
  60. package/dist/container.d.ts +10 -3
  61. package/dist/container.js +12 -4
  62. package/dist/content.cjs +123 -18
  63. package/dist/content.js +123 -18
  64. package/dist/cursor-VMtw9uVP.d.cts +30 -0
  65. package/dist/cursor-VMtw9uVP.d.ts +30 -0
  66. package/dist/index.cjs +71 -5
  67. package/dist/index.d.cts +20 -10
  68. package/dist/index.d.ts +20 -10
  69. package/dist/index.js +17 -7
  70. package/dist/metadata.cjs +14 -0
  71. package/dist/metadata.d.cts +6 -0
  72. package/dist/metadata.d.ts +6 -0
  73. package/dist/metadata.js +13 -0
  74. package/dist/print-names-D-njuzVw.d.cts +41 -0
  75. package/dist/print-names-DUlpVE00.d.ts +41 -0
  76. package/dist/print-setup-B_ihDvm5.d.cts +57 -0
  77. package/dist/print-setup-B_ihDvm5.d.ts +57 -0
  78. package/dist/ptg-B2K8t3js.d.cts +21 -0
  79. package/dist/ptg-B2K8t3js.d.ts +21 -0
  80. package/dist/serial.cjs +41 -0
  81. package/dist/serial.d.cts +7 -1
  82. package/dist/serial.d.ts +7 -1
  83. package/dist/serial.js +39 -1
  84. package/dist/substreams-D7dQiJbp.d.ts +21 -0
  85. package/dist/substreams-Ddtvn_Vr.d.cts +21 -0
  86. package/dist/units.cjs +37 -0
  87. package/dist/units.d.cts +15 -1
  88. package/dist/units.d.ts +15 -1
  89. package/dist/units.js +33 -1
  90. package/dist/workbook/globals-writer.cjs +122 -0
  91. package/dist/workbook/globals-writer.d.cts +39 -0
  92. package/dist/workbook/globals-writer.d.ts +39 -0
  93. package/dist/workbook/globals-writer.js +120 -0
  94. package/dist/workbook/globals.cjs +72 -8
  95. package/dist/workbook/globals.d.cts +26 -2
  96. package/dist/workbook/globals.d.ts +26 -2
  97. package/dist/workbook/globals.js +71 -7
  98. package/dist/workbook/print-names.cjs +241 -0
  99. package/dist/workbook/print-names.d.cts +2 -0
  100. package/dist/workbook/print-names.d.ts +2 -0
  101. package/dist/workbook/print-names.js +238 -0
  102. package/dist/workbook/sheet-writer.cjs +277 -0
  103. package/dist/workbook/sheet-writer.d.cts +12 -0
  104. package/dist/workbook/sheet-writer.d.ts +12 -0
  105. package/dist/workbook/sheet-writer.js +276 -0
  106. package/dist/workbook/sheet.cjs +125 -17
  107. package/dist/workbook/sheet.d.cts +38 -4
  108. package/dist/workbook/sheet.d.ts +38 -4
  109. package/dist/workbook/sheet.js +125 -17
  110. package/dist/write.cjs +311 -0
  111. package/dist/write.d.cts +13 -0
  112. package/dist/write.d.ts +13 -0
  113. package/dist/write.js +309 -0
  114. package/dist/written-cells.cjs +26 -0
  115. package/dist/written-cells.d.cts +16 -0
  116. package/dist/written-cells.d.ts +16 -0
  117. package/dist/written-cells.js +24 -0
  118. package/dist/xf-colors-CehHZtBy.d.cts +91 -0
  119. package/dist/xf-colors-CpykR3B9.d.ts +91 -0
  120. package/package.json +4 -3
  121. package/dist/number-format.cjs +0 -298
  122. package/dist/number-format.d.cts +0 -32
  123. package/dist/number-format.d.ts +0 -32
  124. package/dist/number-format.js +0 -296
@@ -1,32 +0,0 @@
1
- //#region src/number-format.d.ts
2
- /** What a format code says the value is. `elapsedTime` is kept distinct from `time` because a duration may exceed 24 hours and so has no wall-clock spelling in the schema. */
3
- type NumberFormatClass = {
4
- kind: "number";
5
- } | {
6
- kind: "text";
7
- } | {
8
- kind: "percentage";
9
- } | {
10
- kind: "currency";
11
- code?: string;
12
- } | {
13
- kind: "date";
14
- } | {
15
- kind: "time";
16
- } | {
17
- kind: "dateTime";
18
- } | {
19
- kind: "elapsedTime";
20
- };
21
- /** Classifies a format code, reading the FIRST section only. Sections two through four are the negative/zero/text renderings of the same underlying value: they differ in colour, parentheses, and literal text, never in what kind of thing the cell holds, and a cell whose value happens to be negative must not classify differently from the identical cell holding a positive one. */
22
- declare function classifyNumberFormat(formatCode: string): NumberFormatClass;
23
- /**
24
- * The built-in format codes, which a file never writes into its own Format records and every reader is expected to know.
25
- *
26
- * [MS-XLS] 2.4.126 constrains a Format record's own ifmt to 5-8, 23-26, 41-44, 63-66, and 164-382, so an XF pointing at any other identifier resolves through this table instead. The codes are ECMA-376 Part 1 SS18.8.30's table, which BIFF8 and xlsx share.
27
- *
28
- * Ids 23-36 are deliberately absent: that table leaves them reserved, and inventing codes for them would fabricate a mapping no specification defines -- an XF pointing at one resolves to no code at all, which the caller reports as absent rather than silently substituting General. These strings are fed through the SAME classifyNumberFormat as a producer-declared code, never a second table of pre-decided kinds, so the two feeds cannot drift apart.
29
- */
30
- declare const BUILTIN_NUMBER_FORMATS: ReadonlyMap<number, string>;
31
- //#endregion
32
- export { BUILTIN_NUMBER_FORMATS, NumberFormatClass, classifyNumberFormat };
@@ -1,296 +0,0 @@
1
- //#region src/number-format.ts
2
- /** Excel honours at most four sections (positive; negative; zero; text); a fifth is malformed and is dropped rather than guessed at. */
3
- const MAX_SECTIONS = 4;
4
- /** Mirrors String.prototype.charAt's past-the-end contract, but over a CODE POINT array, so a rare astral currency symbol stays one token instead of splitting into two lone surrogates. */
5
- function at(chars, index) {
6
- return chars[index] ?? "";
7
- }
8
- function tokenize(formatCode) {
9
- const chars = [...formatCode];
10
- const tokens = [];
11
- let index = 0;
12
- while (index < chars.length) {
13
- const char = at(chars, index);
14
- if (char === "\"") {
15
- let text = "";
16
- index += 1;
17
- while (index < chars.length && at(chars, index) !== "\"") {
18
- text += at(chars, index);
19
- index += 1;
20
- }
21
- index += 1;
22
- tokens.push({
23
- kind: "literal",
24
- text
25
- });
26
- continue;
27
- }
28
- if (char === "\\" || char === "_" || char === "*") {
29
- tokens.push({
30
- kind: "literal",
31
- text: at(chars, index + 1)
32
- });
33
- index += 2;
34
- continue;
35
- }
36
- if (char === "[") {
37
- let body = "";
38
- index += 1;
39
- while (index < chars.length && at(chars, index) !== "]") {
40
- body += at(chars, index);
41
- index += 1;
42
- }
43
- index += 1;
44
- tokens.push({
45
- kind: "bracket",
46
- body
47
- });
48
- continue;
49
- }
50
- if (char === ";") {
51
- tokens.push({ kind: "separator" });
52
- index += 1;
53
- continue;
54
- }
55
- tokens.push({
56
- kind: "code",
57
- char
58
- });
59
- index += 1;
60
- }
61
- return tokens;
62
- }
63
- /** Splits on separator tokens only: a ';' inside a quote or bracket was already consumed as part of that token, so it can never split a section here. */
64
- function splitSections(tokens) {
65
- const sections = [];
66
- let current = [];
67
- for (const token of tokens) {
68
- if (token.kind === "separator") {
69
- sections.push(current);
70
- current = [];
71
- continue;
72
- }
73
- current.push(token);
74
- }
75
- sections.push(current);
76
- return sections.slice(0, MAX_SECTIONS);
77
- }
78
- /** The Unicode Currency_Symbol category IS the definition of "this character means money", so it is tested directly rather than against a hand-listed subset that would omit whichever symbol a real file happens to use. */
79
- const CURRENCY_SYMBOL = /\p{Sc}/u;
80
- /** `[$GBP-809]` carries an ISO 4217 code; `[$£-809]` carries a display symbol instead. Only the three-ASCII-letter shape counts as a code, because ContentCellValue's `currency` field is documented as the ISO code and there is no faithful symbol-to-code mapping ('$' alone is USD, CAD, AUD and a dozen others). */
81
- function isIsoCurrencyCodeShape(marker) {
82
- if (marker.length !== 3) return false;
83
- for (const char of marker) {
84
- const upper = char.toUpperCase();
85
- if (upper < "A" || upper > "Z") return false;
86
- }
87
- return true;
88
- }
89
- /** An elapsed-time bucket is a bracket holding one repeated h/m/s and nothing else -- the marker that the value is a DURATION, which may legitimately exceed 24 hours, rather than a time of day. */
90
- function isElapsedBracketBody(body) {
91
- let letter;
92
- for (const char of body) {
93
- const lower = char.toLowerCase();
94
- if (letter === void 0) {
95
- if (lower !== "h" && lower !== "m" && lower !== "s") return false;
96
- letter = lower;
97
- } else if (lower !== letter) return false;
98
- }
99
- return letter !== void 0;
100
- }
101
- function classifyBracket(body) {
102
- if (body.startsWith("$")) {
103
- const rest = body.slice(1);
104
- const dashIndex = rest.indexOf("-");
105
- const marker = dashIndex === -1 ? rest : rest.slice(0, dashIndex);
106
- if (marker === "") return { kind: "none" };
107
- return isIsoCurrencyCodeShape(marker) ? {
108
- kind: "currency",
109
- code: marker.toUpperCase()
110
- } : { kind: "currency" };
111
- }
112
- return isElapsedBracketBody(body) ? { kind: "elapsed" } : { kind: "none" };
113
- }
114
- const AMPM_MARKERS = ["am/pm", "a/p"];
115
- const AMPM_LETTER = "ampm";
116
- function matchesAt(chars, index, marker) {
117
- return [...marker].every((char, offset) => at(chars, index + offset).toLowerCase() === char);
118
- }
119
- function codeRunsOf(section) {
120
- const chars = [];
121
- for (const token of section) if (token.kind === "code") chars.push(token.char);
122
- const runs = [];
123
- let index = 0;
124
- while (index < chars.length) {
125
- const marker = AMPM_MARKERS.find((candidate) => matchesAt(chars, index, candidate));
126
- if (marker !== void 0) {
127
- runs.push({
128
- letter: AMPM_LETTER,
129
- length: marker.length
130
- });
131
- index += marker.length;
132
- continue;
133
- }
134
- const char = at(chars, index).toLowerCase();
135
- let length = 0;
136
- while (index + length < chars.length && at(chars, index + length).toLowerCase() === char) length += 1;
137
- runs.push({
138
- letter: char,
139
- length
140
- });
141
- index += length;
142
- }
143
- return runs;
144
- }
145
- /** The letters an ambiguous 'm' looks past its neighbours for. 'm' itself is excluded: an unresolved 'm' carries no information for resolving another, so `hh:mm:mm` resolves both against the 'hh'. */
146
- const RESOLVING_LETTERS = [
147
- "y",
148
- "d",
149
- "h",
150
- "s"
151
- ];
152
- function nearestResolvingLetter(runs, from, step) {
153
- for (let index = from + step; index >= 0 && index < runs.length; index += step) {
154
- const run = runs[index];
155
- if (run !== void 0 && RESOLVING_LETTERS.includes(run.letter)) return run.letter;
156
- }
157
- }
158
- /** Excel's minutes-vs-months rule: 'm'/'mm' is minutes when the nearest preceding date/time code is an hour or the nearest following one is a second, and a month otherwise. 'mmm' and longer are always month names. This is what makes `yyyy-mm-dd hh:mm:ss` resolve its two identical 'mm' runs oppositely. */
159
- function monthRunIsMinutes(runs, index) {
160
- return nearestResolvingLetter(runs, index, -1) === "h" || nearestResolvingLetter(runs, index, 1) === "s";
161
- }
162
- const PLAIN_NUMBER = { kind: "number" };
163
- /** Digit placeholders ('0' required, '#' suppressed, '?' space-padded), the decimal and thousands separators. Scientific notation's 'e' is handled at its own run, since a bare 'e' also occurs inside the literal word "General". */
164
- const NUMERIC_CODES = [
165
- "0",
166
- "#",
167
- "?",
168
- ".",
169
- ","
170
- ];
171
- function collectSignals(section) {
172
- const signals = {
173
- hasDate: false,
174
- hasTime: false,
175
- hasElapsed: false,
176
- hasPercent: false,
177
- hasNumeric: false,
178
- hasText: false,
179
- hasCurrency: false
180
- };
181
- for (const token of section) {
182
- if (token.kind === "literal" && CURRENCY_SYMBOL.test(token.text)) signals.hasCurrency = true;
183
- if (token.kind === "bracket") {
184
- const meaning = classifyBracket(token.body);
185
- if (meaning.kind === "elapsed") signals.hasElapsed = true;
186
- if (meaning.kind === "currency") {
187
- signals.hasCurrency = true;
188
- if (signals.currencyCode === void 0 && meaning.code !== void 0) signals.currencyCode = meaning.code;
189
- }
190
- }
191
- }
192
- const runs = codeRunsOf(section);
193
- runs.forEach((run, index) => {
194
- if (run.letter === "y" || run.letter === "d") {
195
- signals.hasDate = true;
196
- return;
197
- }
198
- if (run.letter === "h" || run.letter === "s" || run.letter === AMPM_LETTER) {
199
- signals.hasTime = true;
200
- return;
201
- }
202
- if (run.letter === "m") {
203
- if (run.length <= 2 && monthRunIsMinutes(runs, index)) signals.hasTime = true;
204
- else signals.hasDate = true;
205
- return;
206
- }
207
- if (run.letter === "e") {
208
- const next = runs[index + 1];
209
- signals.hasNumeric = signals.hasNumeric || next?.letter === "+" || next?.letter === "-";
210
- return;
211
- }
212
- if (run.letter === "%") {
213
- signals.hasPercent = true;
214
- return;
215
- }
216
- if (run.letter === "@") {
217
- signals.hasText = true;
218
- return;
219
- }
220
- if (NUMERIC_CODES.includes(run.letter)) {
221
- signals.hasNumeric = true;
222
- return;
223
- }
224
- if (CURRENCY_SYMBOL.test(run.letter)) signals.hasCurrency = true;
225
- });
226
- return signals;
227
- }
228
- /** Precedence when a format carries several signals at once, most specific first: an elapsed-time bracket beats everything (the only marker separating a duration from a time of day); any date code beats any time code (a format with both is a genuine combined date-and-time); a percent sign beats a currency marker (`[$GBP-809]0.00%` is still a percentage); and a text placeholder only wins when the section has no numeric placeholder to be a number with. */
229
- function classifySection(section) {
230
- const signals = collectSignals(section);
231
- if (signals.hasElapsed) return { kind: "elapsedTime" };
232
- if (signals.hasDate) return signals.hasTime ? { kind: "dateTime" } : { kind: "date" };
233
- if (signals.hasTime) return { kind: "time" };
234
- if (signals.hasPercent) return { kind: "percentage" };
235
- if (signals.hasCurrency) {
236
- const code = signals.currencyCode;
237
- return code === void 0 ? { kind: "currency" } : {
238
- kind: "currency",
239
- code
240
- };
241
- }
242
- if (signals.hasText && !signals.hasNumeric) return { kind: "text" };
243
- return PLAIN_NUMBER;
244
- }
245
- /** Classifies a format code, reading the FIRST section only. Sections two through four are the negative/zero/text renderings of the same underlying value: they differ in colour, parentheses, and literal text, never in what kind of thing the cell holds, and a cell whose value happens to be negative must not classify differently from the identical cell holding a positive one. */
246
- function classifyNumberFormat(formatCode) {
247
- const first = splitSections(tokenize(formatCode))[0];
248
- return first === void 0 ? PLAIN_NUMBER : classifySection(first);
249
- }
250
- /**
251
- * The built-in format codes, which a file never writes into its own Format records and every reader is expected to know.
252
- *
253
- * [MS-XLS] 2.4.126 constrains a Format record's own ifmt to 5-8, 23-26, 41-44, 63-66, and 164-382, so an XF pointing at any other identifier resolves through this table instead. The codes are ECMA-376 Part 1 SS18.8.30's table, which BIFF8 and xlsx share.
254
- *
255
- * Ids 23-36 are deliberately absent: that table leaves them reserved, and inventing codes for them would fabricate a mapping no specification defines -- an XF pointing at one resolves to no code at all, which the caller reports as absent rather than silently substituting General. These strings are fed through the SAME classifyNumberFormat as a producer-declared code, never a second table of pre-decided kinds, so the two feeds cannot drift apart.
256
- */
257
- const BUILTIN_NUMBER_FORMATS = /* @__PURE__ */ new Map([
258
- [0, "General"],
259
- [1, "0"],
260
- [2, "0.00"],
261
- [3, "#,##0"],
262
- [4, "#,##0.00"],
263
- [5, "$#,##0_);($#,##0)"],
264
- [6, "$#,##0_);[Red]($#,##0)"],
265
- [7, "$#,##0.00_);($#,##0.00)"],
266
- [8, "$#,##0.00_);[Red]($#,##0.00)"],
267
- [9, "0%"],
268
- [10, "0.00%"],
269
- [11, "0.00E+00"],
270
- [12, "# ?/?"],
271
- [13, "# ??/??"],
272
- [14, "mm-dd-yy"],
273
- [15, "d-mmm-yy"],
274
- [16, "d-mmm"],
275
- [17, "mmm-yy"],
276
- [18, "h:mm AM/PM"],
277
- [19, "h:mm:ss AM/PM"],
278
- [20, "h:mm"],
279
- [21, "h:mm:ss"],
280
- [22, "m/d/yy h:mm"],
281
- [37, "#,##0 ;(#,##0)"],
282
- [38, "#,##0 ;[Red](#,##0)"],
283
- [39, "#,##0.00;(#,##0.00)"],
284
- [40, "#,##0.00;[Red](#,##0.00)"],
285
- [41, "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)"],
286
- [42, "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)"],
287
- [43, "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)"],
288
- [44, "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)"],
289
- [45, "mm:ss"],
290
- [46, "[h]:mm:ss"],
291
- [47, "mmss.0"],
292
- [48, "##0.0E+0"],
293
- [49, "@"]
294
- ]);
295
- //#endregion
296
- export { BUILTIN_NUMBER_FORMATS, classifyNumberFormat };