@niicojs/excel 0.3.0 → 0.3.2
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/index.cjs +460 -103
- package/dist/index.d.cts +23 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +460 -103
- package/package.json +3 -3
- package/src/cell.ts +14 -0
- package/src/types.ts +323 -311
- package/src/utils/format.ts +356 -0
- package/src/workbook.ts +16 -0
- package/src/worksheet.ts +943 -932
package/dist/index.cjs
CHANGED
|
@@ -106,6 +106,334 @@ var fflate = require('fflate');
|
|
|
106
106
|
};
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
+
const DEFAULT_LOCALE = 'fr-FR';
|
|
110
|
+
const formatPartsCache = new Map();
|
|
111
|
+
const getLocaleNumberInfo = (locale)=>{
|
|
112
|
+
const cacheKey = `num-info:${locale}`;
|
|
113
|
+
const cached = formatPartsCache.get(cacheKey);
|
|
114
|
+
if (cached) {
|
|
115
|
+
const decimal = cached.find((p)=>p.type === 'decimal')?.value ?? ',';
|
|
116
|
+
const group = cached.find((p)=>p.type === 'group')?.value ?? ' ';
|
|
117
|
+
return {
|
|
118
|
+
decimal,
|
|
119
|
+
group
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
|
|
123
|
+
formatPartsCache.set(cacheKey, parts);
|
|
124
|
+
const decimal = parts.find((p)=>p.type === 'decimal')?.value ?? ',';
|
|
125
|
+
const group = parts.find((p)=>p.type === 'group')?.value ?? ' ';
|
|
126
|
+
return {
|
|
127
|
+
decimal,
|
|
128
|
+
group
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
const normalizeSpaces = (value)=>{
|
|
132
|
+
return value.replace(/[\u00a0\u202f]/g, ' ');
|
|
133
|
+
};
|
|
134
|
+
const splitFormatSections = (format)=>{
|
|
135
|
+
const sections = [];
|
|
136
|
+
let current = '';
|
|
137
|
+
let inQuote = false;
|
|
138
|
+
for(let i = 0; i < format.length; i++){
|
|
139
|
+
const ch = format[i];
|
|
140
|
+
if (ch === '"') {
|
|
141
|
+
inQuote = !inQuote;
|
|
142
|
+
current += ch;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (ch === ';' && !inQuote) {
|
|
146
|
+
sections.push(current);
|
|
147
|
+
current = '';
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
current += ch;
|
|
151
|
+
}
|
|
152
|
+
sections.push(current);
|
|
153
|
+
return sections;
|
|
154
|
+
};
|
|
155
|
+
const extractFormatLiterals = (format)=>{
|
|
156
|
+
let prefix = '';
|
|
157
|
+
let suffix = '';
|
|
158
|
+
let cleaned = '';
|
|
159
|
+
let inQuote = false;
|
|
160
|
+
let sawPlaceholder = false;
|
|
161
|
+
for(let i = 0; i < format.length; i++){
|
|
162
|
+
const ch = format[i];
|
|
163
|
+
if (ch === '"') {
|
|
164
|
+
inQuote = !inQuote;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (inQuote) {
|
|
168
|
+
if (!sawPlaceholder) {
|
|
169
|
+
prefix += ch;
|
|
170
|
+
} else {
|
|
171
|
+
suffix += ch;
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (ch === '\\' && i + 1 < format.length) {
|
|
176
|
+
const escaped = format[i + 1];
|
|
177
|
+
if (!sawPlaceholder) {
|
|
178
|
+
prefix += escaped;
|
|
179
|
+
} else {
|
|
180
|
+
suffix += escaped;
|
|
181
|
+
}
|
|
182
|
+
i++;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (ch === '_' || ch === '*') {
|
|
186
|
+
if (i + 1 < format.length) {
|
|
187
|
+
i++;
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (ch === '[') {
|
|
192
|
+
const end = format.indexOf(']', i + 1);
|
|
193
|
+
if (end !== -1) {
|
|
194
|
+
const content = format.slice(i + 1, end);
|
|
195
|
+
const currencyMatch = content.match(/[$€]/);
|
|
196
|
+
if (currencyMatch) {
|
|
197
|
+
if (!sawPlaceholder) {
|
|
198
|
+
prefix += currencyMatch[0];
|
|
199
|
+
} else {
|
|
200
|
+
suffix += currencyMatch[0];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
i = end;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (ch === '%') {
|
|
208
|
+
if (!sawPlaceholder) {
|
|
209
|
+
prefix += ch;
|
|
210
|
+
} else {
|
|
211
|
+
suffix += ch;
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (ch === '0' || ch === '#' || ch === '?' || ch === '.' || ch === ',') {
|
|
216
|
+
sawPlaceholder = true;
|
|
217
|
+
cleaned += ch;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (!sawPlaceholder) {
|
|
221
|
+
prefix += ch;
|
|
222
|
+
} else {
|
|
223
|
+
suffix += ch;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
cleaned,
|
|
228
|
+
prefix,
|
|
229
|
+
suffix
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
const parseNumberFormat = (format)=>{
|
|
233
|
+
const trimmed = format.trim();
|
|
234
|
+
if (!trimmed) return null;
|
|
235
|
+
const { cleaned, prefix, suffix } = extractFormatLiterals(trimmed);
|
|
236
|
+
const lower = cleaned.toLowerCase();
|
|
237
|
+
if (!/[0#?]/.test(lower)) return null;
|
|
238
|
+
const percent = /%/.test(trimmed);
|
|
239
|
+
const section = lower;
|
|
240
|
+
const lastDot = section.lastIndexOf('.');
|
|
241
|
+
const lastComma = section.lastIndexOf(',');
|
|
242
|
+
let decimalSeparator = null;
|
|
243
|
+
if (lastDot >= 0 && lastComma >= 0) {
|
|
244
|
+
decimalSeparator = lastDot > lastComma ? '.' : ',';
|
|
245
|
+
} else if (lastDot >= 0 || lastComma >= 0) {
|
|
246
|
+
const candidate = lastDot >= 0 ? '.' : ',';
|
|
247
|
+
const index = lastDot >= 0 ? lastDot : lastComma;
|
|
248
|
+
const fractionSection = section.slice(index + 1);
|
|
249
|
+
const fractionDigitsCandidate = fractionSection.replace(/[^0#?]/g, '').length;
|
|
250
|
+
if (fractionDigitsCandidate > 0) {
|
|
251
|
+
if (fractionDigitsCandidate === 3) {
|
|
252
|
+
decimalSeparator = null;
|
|
253
|
+
} else {
|
|
254
|
+
decimalSeparator = candidate;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let decimalIndex = decimalSeparator === '.' ? lastDot : decimalSeparator === ',' ? lastComma : -1;
|
|
259
|
+
if (decimalIndex >= 0) {
|
|
260
|
+
const fractionSection = section.slice(decimalIndex + 1);
|
|
261
|
+
if (!/[0#?]/.test(fractionSection)) {
|
|
262
|
+
decimalIndex = -1;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const decimalSection = decimalIndex >= 0 ? section.slice(decimalIndex + 1) : '';
|
|
266
|
+
const fractionDigits = decimalSection.replace(/[^0#?]/g, '').length;
|
|
267
|
+
const integerSection = decimalIndex >= 0 ? section.slice(0, decimalIndex) : section;
|
|
268
|
+
const useGrouping = /[,.\s\u00a0\u202f]/.test(integerSection) && integerSection.length > 0;
|
|
269
|
+
return {
|
|
270
|
+
fractionDigits,
|
|
271
|
+
percent,
|
|
272
|
+
useGrouping,
|
|
273
|
+
literalPrefix: prefix || undefined,
|
|
274
|
+
literalSuffix: suffix || undefined
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
const formatNumber = (value, info, locale)=>{
|
|
278
|
+
const adjusted = info.percent ? value * 100 : value;
|
|
279
|
+
const { decimal, group } = getLocaleNumberInfo(locale);
|
|
280
|
+
const absValue = Math.abs(adjusted);
|
|
281
|
+
const fixed = absValue.toFixed(info.fractionDigits);
|
|
282
|
+
const [integerPart, fractionPart] = fixed.split('.');
|
|
283
|
+
const grouped = info.useGrouping ? integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, group) : integerPart;
|
|
284
|
+
const fraction = info.fractionDigits > 0 ? `${decimal}${fractionPart}` : '';
|
|
285
|
+
const signed = adjusted < 0 ? '-' : '';
|
|
286
|
+
let result = `${signed}${grouped}${fraction}`;
|
|
287
|
+
if (info.literalPrefix) {
|
|
288
|
+
result = `${info.literalPrefix}${result}`;
|
|
289
|
+
}
|
|
290
|
+
if (info.literalSuffix) {
|
|
291
|
+
result = `${result}${info.literalSuffix}`;
|
|
292
|
+
}
|
|
293
|
+
return normalizeSpaces(result);
|
|
294
|
+
};
|
|
295
|
+
const padNumber = (value, length)=>{
|
|
296
|
+
const str = String(value);
|
|
297
|
+
return str.length >= length ? str : `${'0'.repeat(length - str.length)}${str}`;
|
|
298
|
+
};
|
|
299
|
+
const formatDatePart = (value, token, locale)=>{
|
|
300
|
+
switch(token){
|
|
301
|
+
case 'yyyy':
|
|
302
|
+
return String(value.getFullYear());
|
|
303
|
+
case 'yy':
|
|
304
|
+
return padNumber(value.getFullYear() % 100, 2);
|
|
305
|
+
case 'mmmm':
|
|
306
|
+
return value.toLocaleString(locale, {
|
|
307
|
+
month: 'long'
|
|
308
|
+
});
|
|
309
|
+
case 'mmm':
|
|
310
|
+
return value.toLocaleString(locale, {
|
|
311
|
+
month: 'short'
|
|
312
|
+
});
|
|
313
|
+
case 'mm':
|
|
314
|
+
return padNumber(value.getMonth() + 1, 2);
|
|
315
|
+
case 'm':
|
|
316
|
+
return String(value.getMonth() + 1);
|
|
317
|
+
case 'dd':
|
|
318
|
+
return padNumber(value.getDate(), 2);
|
|
319
|
+
case 'd':
|
|
320
|
+
return String(value.getDate());
|
|
321
|
+
case 'hh':
|
|
322
|
+
{
|
|
323
|
+
const hours = value.getHours();
|
|
324
|
+
return padNumber(hours, 2);
|
|
325
|
+
}
|
|
326
|
+
case 'h':
|
|
327
|
+
return String(value.getHours());
|
|
328
|
+
case 'min2':
|
|
329
|
+
return padNumber(value.getMinutes(), 2);
|
|
330
|
+
case 'min1':
|
|
331
|
+
return String(value.getMinutes());
|
|
332
|
+
case 'ss':
|
|
333
|
+
return padNumber(value.getSeconds(), 2);
|
|
334
|
+
case 's':
|
|
335
|
+
return String(value.getSeconds());
|
|
336
|
+
default:
|
|
337
|
+
return token;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
const tokenizeDateFormat = (format)=>{
|
|
341
|
+
const tokens = [];
|
|
342
|
+
let i = 0;
|
|
343
|
+
while(i < format.length){
|
|
344
|
+
const ch = format[i];
|
|
345
|
+
if (ch === '"') {
|
|
346
|
+
let literal = '';
|
|
347
|
+
i++;
|
|
348
|
+
while(i < format.length && format[i] !== '"'){
|
|
349
|
+
literal += format[i];
|
|
350
|
+
i++;
|
|
351
|
+
}
|
|
352
|
+
i++;
|
|
353
|
+
if (literal) tokens.push(literal);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (ch === '\\' && i + 1 < format.length) {
|
|
357
|
+
tokens.push(format[i + 1]);
|
|
358
|
+
i += 2;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (ch === '[') {
|
|
362
|
+
const end = format.indexOf(']', i + 1);
|
|
363
|
+
if (end !== -1) {
|
|
364
|
+
i = end + 1;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const lower = format.slice(i).toLowerCase();
|
|
369
|
+
const match = [
|
|
370
|
+
'yyyy',
|
|
371
|
+
'yy',
|
|
372
|
+
'mmmm',
|
|
373
|
+
'mmm',
|
|
374
|
+
'mm',
|
|
375
|
+
'm',
|
|
376
|
+
'dd',
|
|
377
|
+
'd',
|
|
378
|
+
'hh',
|
|
379
|
+
'h',
|
|
380
|
+
'ss',
|
|
381
|
+
's'
|
|
382
|
+
].find((t)=>lower.startsWith(t));
|
|
383
|
+
if (match) {
|
|
384
|
+
if (match === 'm' || match === 'mm') {
|
|
385
|
+
let j = i - 1;
|
|
386
|
+
let previousChar = '';
|
|
387
|
+
while(j >= 0 && previousChar === ''){
|
|
388
|
+
const candidate = format[j];
|
|
389
|
+
if (candidate && candidate !== ' ') {
|
|
390
|
+
previousChar = candidate;
|
|
391
|
+
}
|
|
392
|
+
j--;
|
|
393
|
+
}
|
|
394
|
+
const isMinute = previousChar === 'h' || previousChar === 'H' || previousChar === ':';
|
|
395
|
+
if (isMinute) {
|
|
396
|
+
tokens.push(match === 'mm' ? 'min2' : 'min1');
|
|
397
|
+
i += match.length;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
tokens.push(match);
|
|
402
|
+
i += match.length;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
tokens.push(ch);
|
|
406
|
+
i++;
|
|
407
|
+
}
|
|
408
|
+
return tokens;
|
|
409
|
+
};
|
|
410
|
+
const isDateFormat = (format)=>{
|
|
411
|
+
const lowered = format.toLowerCase();
|
|
412
|
+
return /[ymdhss]/.test(lowered);
|
|
413
|
+
};
|
|
414
|
+
const formatDate = (value, format, locale)=>{
|
|
415
|
+
const tokens = tokenizeDateFormat(format);
|
|
416
|
+
return tokens.map((token)=>formatDatePart(value, token, locale)).join('');
|
|
417
|
+
};
|
|
418
|
+
const formatCellValue = (value, style, locale)=>{
|
|
419
|
+
const numberFormat = style?.numberFormat;
|
|
420
|
+
if (!numberFormat) return null;
|
|
421
|
+
const normalizedLocale = locale || DEFAULT_LOCALE;
|
|
422
|
+
const sections = splitFormatSections(numberFormat);
|
|
423
|
+
const hasNegativeSection = sections.length > 1;
|
|
424
|
+
const section = value instanceof Date ? sections[0] : value < 0 ? sections[1] ?? sections[0] : sections[0];
|
|
425
|
+
if (value instanceof Date && isDateFormat(section)) {
|
|
426
|
+
return formatDate(value, section, normalizedLocale);
|
|
427
|
+
}
|
|
428
|
+
if (typeof value === 'number') {
|
|
429
|
+
const info = parseNumberFormat(section);
|
|
430
|
+
if (!info) return null;
|
|
431
|
+
const numericValue = value < 0 && hasNegativeSection ? Math.abs(value) : value;
|
|
432
|
+
return formatNumber(numericValue, info, normalizedLocale);
|
|
433
|
+
}
|
|
434
|
+
return null;
|
|
435
|
+
};
|
|
436
|
+
|
|
109
437
|
// Excel epoch: December 30, 1899 (accounting for the 1900 leap year bug)
|
|
110
438
|
const EXCEL_EPOCH = new Date(Date.UTC(1899, 11, 30));
|
|
111
439
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
@@ -306,12 +634,21 @@ const ERROR_TYPES = new Set([
|
|
|
306
634
|
/**
|
|
307
635
|
* Get the formatted text (as displayed in Excel)
|
|
308
636
|
*/ get text() {
|
|
637
|
+
return this.textWithLocale();
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Get the formatted text using a specific locale
|
|
641
|
+
*/ textWithLocale(locale) {
|
|
309
642
|
if (this._data.w) {
|
|
310
643
|
return this._data.w;
|
|
311
644
|
}
|
|
312
645
|
const val = this.value;
|
|
313
646
|
if (val === null) return '';
|
|
314
647
|
if (typeof val === 'object' && 'error' in val) return val.error;
|
|
648
|
+
if (val instanceof Date || typeof val === 'number') {
|
|
649
|
+
const formatted = formatCellValue(val, this.style, locale ?? this._worksheet.workbook.locale);
|
|
650
|
+
if (formatted !== null) return formatted;
|
|
651
|
+
}
|
|
315
652
|
if (val instanceof Date) return val.toISOString().split('T')[0];
|
|
316
653
|
return String(val);
|
|
317
654
|
}
|
|
@@ -933,8 +1270,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
933
1270
|
}
|
|
934
1271
|
}
|
|
935
1272
|
|
|
936
|
-
/**
|
|
937
|
-
* Represents a worksheet in a workbook
|
|
1273
|
+
/**
|
|
1274
|
+
* Represents a worksheet in a workbook
|
|
938
1275
|
*/ class Worksheet {
|
|
939
1276
|
constructor(workbook, name){
|
|
940
1277
|
this._cells = new Map();
|
|
@@ -956,24 +1293,24 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
956
1293
|
this._workbook = workbook;
|
|
957
1294
|
this._name = name;
|
|
958
1295
|
}
|
|
959
|
-
/**
|
|
960
|
-
* Get the workbook this sheet belongs to
|
|
1296
|
+
/**
|
|
1297
|
+
* Get the workbook this sheet belongs to
|
|
961
1298
|
*/ get workbook() {
|
|
962
1299
|
return this._workbook;
|
|
963
1300
|
}
|
|
964
|
-
/**
|
|
965
|
-
* Get the sheet name
|
|
1301
|
+
/**
|
|
1302
|
+
* Get the sheet name
|
|
966
1303
|
*/ get name() {
|
|
967
1304
|
return this._name;
|
|
968
1305
|
}
|
|
969
|
-
/**
|
|
970
|
-
* Set the sheet name
|
|
1306
|
+
/**
|
|
1307
|
+
* Set the sheet name
|
|
971
1308
|
*/ set name(value) {
|
|
972
1309
|
this._name = value;
|
|
973
1310
|
this._dirty = true;
|
|
974
1311
|
}
|
|
975
|
-
/**
|
|
976
|
-
* Parse worksheet XML content
|
|
1312
|
+
/**
|
|
1313
|
+
* Parse worksheet XML content
|
|
977
1314
|
*/ parse(xml) {
|
|
978
1315
|
this._xmlNodes = parseXml(xml);
|
|
979
1316
|
this._preserveXml = true;
|
|
@@ -1037,8 +1374,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1037
1374
|
}
|
|
1038
1375
|
}
|
|
1039
1376
|
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Parse the sheetData element to extract cells
|
|
1377
|
+
/**
|
|
1378
|
+
* Parse the sheetData element to extract cells
|
|
1042
1379
|
*/ _parseSheetData(rows) {
|
|
1043
1380
|
for (const rowNode of rows){
|
|
1044
1381
|
if (!('row' in rowNode)) continue;
|
|
@@ -1060,8 +1397,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1060
1397
|
}
|
|
1061
1398
|
this._boundsDirty = true;
|
|
1062
1399
|
}
|
|
1063
|
-
/**
|
|
1064
|
-
* Parse a cell XML node to CellData
|
|
1400
|
+
/**
|
|
1401
|
+
* Parse a cell XML node to CellData
|
|
1065
1402
|
*/ _parseCellNode(node) {
|
|
1066
1403
|
const data = {};
|
|
1067
1404
|
// Type attribute
|
|
@@ -1136,8 +1473,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1136
1473
|
}
|
|
1137
1474
|
return data;
|
|
1138
1475
|
}
|
|
1139
|
-
/**
|
|
1140
|
-
* Get a cell by address or row/col
|
|
1476
|
+
/**
|
|
1477
|
+
* Get a cell by address or row/col
|
|
1141
1478
|
*/ cell(rowOrAddress, col) {
|
|
1142
1479
|
const { row, col: c } = parseCellRef(rowOrAddress, col);
|
|
1143
1480
|
const address = toAddress(row, c);
|
|
@@ -1149,8 +1486,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1149
1486
|
}
|
|
1150
1487
|
return cell;
|
|
1151
1488
|
}
|
|
1152
|
-
/**
|
|
1153
|
-
* Get an existing cell without creating it.
|
|
1489
|
+
/**
|
|
1490
|
+
* Get an existing cell without creating it.
|
|
1154
1491
|
*/ getCellIfExists(rowOrAddress, col) {
|
|
1155
1492
|
const { row, col: c } = parseCellRef(rowOrAddress, col);
|
|
1156
1493
|
const address = toAddress(row, c);
|
|
@@ -1177,8 +1514,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1177
1514
|
}
|
|
1178
1515
|
return new Range(this, rangeAddr);
|
|
1179
1516
|
}
|
|
1180
|
-
/**
|
|
1181
|
-
* Merge cells in the given range
|
|
1517
|
+
/**
|
|
1518
|
+
* Merge cells in the given range
|
|
1182
1519
|
*/ mergeCells(rangeOrStart, end) {
|
|
1183
1520
|
let rangeStr;
|
|
1184
1521
|
if (end) {
|
|
@@ -1189,19 +1526,19 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1189
1526
|
this._mergedCells.add(rangeStr);
|
|
1190
1527
|
this._dirty = true;
|
|
1191
1528
|
}
|
|
1192
|
-
/**
|
|
1193
|
-
* Unmerge cells in the given range
|
|
1529
|
+
/**
|
|
1530
|
+
* Unmerge cells in the given range
|
|
1194
1531
|
*/ unmergeCells(rangeStr) {
|
|
1195
1532
|
this._mergedCells.delete(rangeStr);
|
|
1196
1533
|
this._dirty = true;
|
|
1197
1534
|
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Get all merged cell ranges
|
|
1535
|
+
/**
|
|
1536
|
+
* Get all merged cell ranges
|
|
1200
1537
|
*/ get mergedCells() {
|
|
1201
1538
|
return Array.from(this._mergedCells);
|
|
1202
1539
|
}
|
|
1203
|
-
/**
|
|
1204
|
-
* Check if the worksheet has been modified
|
|
1540
|
+
/**
|
|
1541
|
+
* Check if the worksheet has been modified
|
|
1205
1542
|
*/ get dirty() {
|
|
1206
1543
|
if (this._dirty) return true;
|
|
1207
1544
|
for (const cell of this._cells.values()){
|
|
@@ -1209,13 +1546,13 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1209
1546
|
}
|
|
1210
1547
|
return false;
|
|
1211
1548
|
}
|
|
1212
|
-
/**
|
|
1213
|
-
* Get all cells in the worksheet
|
|
1549
|
+
/**
|
|
1550
|
+
* Get all cells in the worksheet
|
|
1214
1551
|
*/ get cells() {
|
|
1215
1552
|
return this._cells;
|
|
1216
1553
|
}
|
|
1217
|
-
/**
|
|
1218
|
-
* Set a column width (0-based index or column letter)
|
|
1554
|
+
/**
|
|
1555
|
+
* Set a column width (0-based index or column letter)
|
|
1219
1556
|
*/ setColumnWidth(col, width) {
|
|
1220
1557
|
if (!Number.isFinite(width) || width <= 0) {
|
|
1221
1558
|
throw new Error('Column width must be a positive number');
|
|
@@ -1228,14 +1565,14 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1228
1565
|
this._colsDirty = true;
|
|
1229
1566
|
this._dirty = true;
|
|
1230
1567
|
}
|
|
1231
|
-
/**
|
|
1232
|
-
* Get a column width if set
|
|
1568
|
+
/**
|
|
1569
|
+
* Get a column width if set
|
|
1233
1570
|
*/ getColumnWidth(col) {
|
|
1234
1571
|
const colIndex = typeof col === 'number' ? col : letterToCol(col);
|
|
1235
1572
|
return this._columnWidths.get(colIndex);
|
|
1236
1573
|
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Set a row height (0-based index)
|
|
1574
|
+
/**
|
|
1575
|
+
* Set a row height (0-based index)
|
|
1239
1576
|
*/ setRowHeight(row, height) {
|
|
1240
1577
|
if (!Number.isFinite(height) || height <= 0) {
|
|
1241
1578
|
throw new Error('Row height must be a positive number');
|
|
@@ -1247,13 +1584,13 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1247
1584
|
this._colsDirty = true;
|
|
1248
1585
|
this._dirty = true;
|
|
1249
1586
|
}
|
|
1250
|
-
/**
|
|
1251
|
-
* Get a row height if set
|
|
1587
|
+
/**
|
|
1588
|
+
* Get a row height if set
|
|
1252
1589
|
*/ getRowHeight(row) {
|
|
1253
1590
|
return this._rowHeights.get(row);
|
|
1254
1591
|
}
|
|
1255
|
-
/**
|
|
1256
|
-
* Freeze panes at a given row/column split (counts from top-left)
|
|
1592
|
+
/**
|
|
1593
|
+
* Freeze panes at a given row/column split (counts from top-left)
|
|
1257
1594
|
*/ freezePane(rowSplit, colSplit) {
|
|
1258
1595
|
if (rowSplit < 0 || colSplit < 0) {
|
|
1259
1596
|
throw new Error('Freeze pane splits must be >= 0');
|
|
@@ -1269,68 +1606,68 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1269
1606
|
this._sheetViewsDirty = true;
|
|
1270
1607
|
this._dirty = true;
|
|
1271
1608
|
}
|
|
1272
|
-
/**
|
|
1273
|
-
* Get current frozen pane configuration
|
|
1609
|
+
/**
|
|
1610
|
+
* Get current frozen pane configuration
|
|
1274
1611
|
*/ getFrozenPane() {
|
|
1275
1612
|
return this._frozenPane ? {
|
|
1276
1613
|
...this._frozenPane
|
|
1277
1614
|
} : null;
|
|
1278
1615
|
}
|
|
1279
|
-
/**
|
|
1280
|
-
* Get all tables in the worksheet
|
|
1616
|
+
/**
|
|
1617
|
+
* Get all tables in the worksheet
|
|
1281
1618
|
*/ get tables() {
|
|
1282
1619
|
return [
|
|
1283
1620
|
...this._tables
|
|
1284
1621
|
];
|
|
1285
1622
|
}
|
|
1286
|
-
/**
|
|
1287
|
-
* Get column width entries
|
|
1288
|
-
* @internal
|
|
1623
|
+
/**
|
|
1624
|
+
* Get column width entries
|
|
1625
|
+
* @internal
|
|
1289
1626
|
*/ getColumnWidths() {
|
|
1290
1627
|
return new Map(this._columnWidths);
|
|
1291
1628
|
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Get row height entries
|
|
1294
|
-
* @internal
|
|
1629
|
+
/**
|
|
1630
|
+
* Get row height entries
|
|
1631
|
+
* @internal
|
|
1295
1632
|
*/ getRowHeights() {
|
|
1296
1633
|
return new Map(this._rowHeights);
|
|
1297
1634
|
}
|
|
1298
|
-
/**
|
|
1299
|
-
* Set table relationship IDs for tableParts generation.
|
|
1300
|
-
* @internal
|
|
1635
|
+
/**
|
|
1636
|
+
* Set table relationship IDs for tableParts generation.
|
|
1637
|
+
* @internal
|
|
1301
1638
|
*/ setTableRelIds(ids) {
|
|
1302
1639
|
this._tableRelIds = ids ? [
|
|
1303
1640
|
...ids
|
|
1304
1641
|
] : null;
|
|
1305
1642
|
this._tablePartsDirty = true;
|
|
1306
1643
|
}
|
|
1307
|
-
/**
|
|
1308
|
-
* Create an Excel Table (ListObject) from a data range.
|
|
1309
|
-
*
|
|
1310
|
-
* Tables provide structured data features like auto-filter, banded styling,
|
|
1311
|
-
* and total row with aggregation functions.
|
|
1312
|
-
*
|
|
1313
|
-
* @param config - Table configuration
|
|
1314
|
-
* @returns Table instance for method chaining
|
|
1315
|
-
*
|
|
1316
|
-
* @example
|
|
1317
|
-
* ```typescript
|
|
1318
|
-
* // Create a table with default styling
|
|
1319
|
-
* const table = sheet.createTable({
|
|
1320
|
-
* name: 'SalesData',
|
|
1321
|
-
* range: 'A1:D10',
|
|
1322
|
-
* });
|
|
1323
|
-
*
|
|
1324
|
-
* // Create a table with total row
|
|
1325
|
-
* const table = sheet.createTable({
|
|
1326
|
-
* name: 'SalesData',
|
|
1327
|
-
* range: 'A1:D10',
|
|
1328
|
-
* totalRow: true,
|
|
1329
|
-
* style: { name: 'TableStyleMedium2' }
|
|
1330
|
-
* });
|
|
1331
|
-
*
|
|
1332
|
-
* table.setTotalFunction('Sales', 'sum');
|
|
1333
|
-
* ```
|
|
1644
|
+
/**
|
|
1645
|
+
* Create an Excel Table (ListObject) from a data range.
|
|
1646
|
+
*
|
|
1647
|
+
* Tables provide structured data features like auto-filter, banded styling,
|
|
1648
|
+
* and total row with aggregation functions.
|
|
1649
|
+
*
|
|
1650
|
+
* @param config - Table configuration
|
|
1651
|
+
* @returns Table instance for method chaining
|
|
1652
|
+
*
|
|
1653
|
+
* @example
|
|
1654
|
+
* ```typescript
|
|
1655
|
+
* // Create a table with default styling
|
|
1656
|
+
* const table = sheet.createTable({
|
|
1657
|
+
* name: 'SalesData',
|
|
1658
|
+
* range: 'A1:D10',
|
|
1659
|
+
* });
|
|
1660
|
+
*
|
|
1661
|
+
* // Create a table with total row
|
|
1662
|
+
* const table = sheet.createTable({
|
|
1663
|
+
* name: 'SalesData',
|
|
1664
|
+
* range: 'A1:D10',
|
|
1665
|
+
* totalRow: true,
|
|
1666
|
+
* style: { name: 'TableStyleMedium2' }
|
|
1667
|
+
* });
|
|
1668
|
+
*
|
|
1669
|
+
* table.setTotalFunction('Sales', 'sum');
|
|
1670
|
+
* ```
|
|
1334
1671
|
*/ createTable(config) {
|
|
1335
1672
|
// Validate table name is unique within the workbook
|
|
1336
1673
|
for (const sheet of this._workbook.sheetNames){
|
|
@@ -1353,25 +1690,25 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1353
1690
|
this._dirty = true;
|
|
1354
1691
|
return table;
|
|
1355
1692
|
}
|
|
1356
|
-
/**
|
|
1693
|
+
/**
|
|
1357
1694
|
* Convert sheet data to an array of JSON objects.
|
|
1358
|
-
*
|
|
1695
|
+
*
|
|
1359
1696
|
* @param config - Configuration options
|
|
1360
1697
|
* @returns Array of objects where keys are field names and values are cell values
|
|
1361
1698
|
*
|
|
1362
1699
|
* @example
|
|
1363
|
-
* ```typescript
|
|
1364
|
-
* // Using first row as headers
|
|
1365
|
-
* const data = sheet.toJson();
|
|
1366
|
-
*
|
|
1367
|
-
* // Using custom field names
|
|
1368
|
-
* const data = sheet.toJson({ fields: ['name', 'age', 'city'] });
|
|
1369
|
-
*
|
|
1370
|
-
* // Starting from a specific row/column
|
|
1371
|
-
* const data = sheet.toJson({ startRow: 2, startCol: 1 });
|
|
1372
|
-
* ```
|
|
1700
|
+
* ```typescript
|
|
1701
|
+
* // Using first row as headers
|
|
1702
|
+
* const data = sheet.toJson();
|
|
1703
|
+
*
|
|
1704
|
+
* // Using custom field names
|
|
1705
|
+
* const data = sheet.toJson({ fields: ['name', 'age', 'city'] });
|
|
1706
|
+
*
|
|
1707
|
+
* // Starting from a specific row/column
|
|
1708
|
+
* const data = sheet.toJson({ startRow: 2, startCol: 1 });
|
|
1709
|
+
* ```
|
|
1373
1710
|
*/ toJson(config = {}) {
|
|
1374
|
-
const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true, dateHandling = this._workbook.dateHandling } = config;
|
|
1711
|
+
const { fields, startRow = 0, startCol = 0, endRow, endCol, stopOnEmptyRow = true, dateHandling = this._workbook.dateHandling, asText = false, locale } = config;
|
|
1375
1712
|
// Get the bounds of data in the sheet
|
|
1376
1713
|
const bounds = this._getDataBounds();
|
|
1377
1714
|
if (!bounds) {
|
|
@@ -1404,12 +1741,21 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1404
1741
|
for(let colOffset = 0; colOffset < fieldNames.length; colOffset++){
|
|
1405
1742
|
const col = startCol + colOffset;
|
|
1406
1743
|
const cell = this._cells.get(toAddress(row, col));
|
|
1407
|
-
let value
|
|
1408
|
-
if (
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1744
|
+
let value;
|
|
1745
|
+
if (asText) {
|
|
1746
|
+
// Return formatted text instead of raw value
|
|
1747
|
+
value = cell?.textWithLocale(locale) ?? '';
|
|
1748
|
+
if (value !== '') {
|
|
1749
|
+
hasData = true;
|
|
1750
|
+
}
|
|
1751
|
+
} else {
|
|
1752
|
+
value = cell?.value ?? null;
|
|
1753
|
+
if (value instanceof Date) {
|
|
1754
|
+
value = this._serializeDate(value, dateHandling, cell);
|
|
1755
|
+
}
|
|
1756
|
+
if (value !== null) {
|
|
1757
|
+
hasData = true;
|
|
1758
|
+
}
|
|
1413
1759
|
}
|
|
1414
1760
|
const fieldName = fieldNames[colOffset];
|
|
1415
1761
|
if (fieldName) {
|
|
@@ -1433,8 +1779,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1433
1779
|
}
|
|
1434
1780
|
return value;
|
|
1435
1781
|
}
|
|
1436
|
-
/**
|
|
1437
|
-
* Get the bounds of data in the sheet (min/max row and column with data)
|
|
1782
|
+
/**
|
|
1783
|
+
* Get the bounds of data in the sheet (min/max row and column with data)
|
|
1438
1784
|
*/ _getDataBounds() {
|
|
1439
1785
|
if (!this._boundsDirty && this._dataBoundsCache) {
|
|
1440
1786
|
return this._dataBoundsCache;
|
|
@@ -1470,8 +1816,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1470
1816
|
this._boundsDirty = false;
|
|
1471
1817
|
return this._dataBoundsCache;
|
|
1472
1818
|
}
|
|
1473
|
-
/**
|
|
1474
|
-
* Generate XML for this worksheet
|
|
1819
|
+
/**
|
|
1820
|
+
* Generate XML for this worksheet
|
|
1475
1821
|
*/ toXml() {
|
|
1476
1822
|
const preserved = this._preserveXml && this._xmlNodes ? this._buildPreservedWorksheet() : null;
|
|
1477
1823
|
// Build sheetData from cells
|
|
@@ -1706,8 +2052,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
1706
2052
|
}
|
|
1707
2053
|
return worksheet;
|
|
1708
2054
|
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Build a cell XML node from a Cell object
|
|
2055
|
+
/**
|
|
2056
|
+
* Build a cell XML node from a Cell object
|
|
1711
2057
|
*/ _buildCellNode(cell) {
|
|
1712
2058
|
const data = cell.data;
|
|
1713
2059
|
const attrs = {
|
|
@@ -3586,6 +3932,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3586
3932
|
this._nextTableId = 1;
|
|
3587
3933
|
// Date serialization handling
|
|
3588
3934
|
this._dateHandling = 'jsDate';
|
|
3935
|
+
this._locale = 'fr-FR';
|
|
3589
3936
|
this._sharedStrings = new SharedStrings();
|
|
3590
3937
|
this._styles = Styles.createDefault();
|
|
3591
3938
|
}
|
|
@@ -3660,6 +4007,16 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
|
|
|
3660
4007
|
this._dateHandling = value;
|
|
3661
4008
|
}
|
|
3662
4009
|
/**
|
|
4010
|
+
* Get the workbook locale for formatting.
|
|
4011
|
+
*/ get locale() {
|
|
4012
|
+
return this._locale;
|
|
4013
|
+
}
|
|
4014
|
+
/**
|
|
4015
|
+
* Set the workbook locale for formatting.
|
|
4016
|
+
*/ set locale(value) {
|
|
4017
|
+
this._locale = value;
|
|
4018
|
+
}
|
|
4019
|
+
/**
|
|
3663
4020
|
* Get the next unique table ID for this workbook.
|
|
3664
4021
|
* Table IDs must be unique across all worksheets.
|
|
3665
4022
|
* @internal
|