@niicojs/excel 0.3.1 → 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 CHANGED
@@ -4,10 +4,10 @@ var promises = require('fs/promises');
4
4
  var fastXmlParser = require('fast-xml-parser');
5
5
  var fflate = require('fflate');
6
6
 
7
- /**
8
- * Converts a column index (0-based) to Excel column letters (A, B, ..., Z, AA, AB, ...)
9
- * @param col - 0-based column index
10
- * @returns Column letter(s)
7
+ /**
8
+ * Converts a column index (0-based) to Excel column letters (A, B, ..., Z, AA, AB, ...)
9
+ * @param col - 0-based column index
10
+ * @returns Column letter(s)
11
11
  */ const colToLetter = (col)=>{
12
12
  let result = '';
13
13
  let n = col;
@@ -17,10 +17,10 @@ var fflate = require('fflate');
17
17
  }
18
18
  return result;
19
19
  };
20
- /**
21
- * Converts Excel column letters to a 0-based column index
22
- * @param letters - Column letter(s) like 'A', 'B', 'AA'
23
- * @returns 0-based column index
20
+ /**
21
+ * Converts Excel column letters to a 0-based column index
22
+ * @param letters - Column letter(s) like 'A', 'B', 'AA'
23
+ * @returns 0-based column index
24
24
  */ const letterToCol = (letters)=>{
25
25
  const upper = letters.toUpperCase();
26
26
  let col = 0;
@@ -29,10 +29,10 @@ var fflate = require('fflate');
29
29
  }
30
30
  return col - 1;
31
31
  };
32
- /**
33
- * Parses an Excel cell address (e.g., 'A1', '$B$2') to row/col indices
34
- * @param address - Cell address string
35
- * @returns CellAddress with 0-based row and col
32
+ /**
33
+ * Parses an Excel cell address (e.g., 'A1', '$B$2') to row/col indices
34
+ * @param address - Cell address string
35
+ * @returns CellAddress with 0-based row and col
36
36
  */ const parseAddress = (address)=>{
37
37
  // Remove $ signs for absolute references
38
38
  const clean = address.replace(/\$/g, '');
@@ -49,18 +49,18 @@ var fflate = require('fflate');
49
49
  col
50
50
  };
51
51
  };
52
- /**
53
- * Converts row/col indices to an Excel cell address
54
- * @param row - 0-based row index
55
- * @param col - 0-based column index
56
- * @returns Cell address string like 'A1'
52
+ /**
53
+ * Converts row/col indices to an Excel cell address
54
+ * @param row - 0-based row index
55
+ * @param col - 0-based column index
56
+ * @returns Cell address string like 'A1'
57
57
  */ const toAddress = (row, col)=>{
58
58
  return `${colToLetter(col)}${row + 1}`;
59
59
  };
60
- /**
61
- * Parses an Excel range (e.g., 'A1:B10') to start/end addresses
62
- * @param range - Range string
63
- * @returns RangeAddress with start and end
60
+ /**
61
+ * Parses an Excel range (e.g., 'A1:B10') to start/end addresses
62
+ * @param range - Range string
63
+ * @returns RangeAddress with start and end
64
64
  */ const parseRange = (range)=>{
65
65
  const parts = range.split(':');
66
66
  if (parts.length === 1) {
@@ -79,10 +79,10 @@ var fflate = require('fflate');
79
79
  end: parseAddress(parts[1])
80
80
  };
81
81
  };
82
- /**
83
- * Converts a RangeAddress to a range string
84
- * @param range - RangeAddress object
85
- * @returns Range string like 'A1:B10'
82
+ /**
83
+ * Converts a RangeAddress to a range string
84
+ * @param range - RangeAddress object
85
+ * @returns Range string like 'A1:B10'
86
86
  */ const toRange = (range)=>{
87
87
  const start = toAddress(range.start.row, range.start.col);
88
88
  const end = toAddress(range.end.row, range.end.col);
@@ -91,8 +91,8 @@ var fflate = require('fflate');
91
91
  }
92
92
  return `${start}:${end}`;
93
93
  };
94
- /**
95
- * Normalizes a range so start is always top-left and end is bottom-right
94
+ /**
95
+ * Normalizes a range so start is always top-left and end is bottom-right
96
96
  */ const normalizeRange = (range)=>{
97
97
  return {
98
98
  start: {
@@ -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
  }
@@ -409,38 +746,38 @@ const ERROR_TYPES = new Set([
409
746
 
410
747
  var _computedKey;
411
748
  _computedKey = Symbol.iterator;
412
- /**
413
- * Represents a range of cells in a worksheet
749
+ /**
750
+ * Represents a range of cells in a worksheet
414
751
  */ class Range {
415
752
  constructor(worksheet, range){
416
753
  this._worksheet = worksheet;
417
754
  this._range = normalizeRange(range);
418
755
  }
419
- /**
420
- * Get the range address as a string
756
+ /**
757
+ * Get the range address as a string
421
758
  */ get address() {
422
759
  const start = toAddress(this._range.start.row, this._range.start.col);
423
760
  const end = toAddress(this._range.end.row, this._range.end.col);
424
761
  if (start === end) return start;
425
762
  return `${start}:${end}`;
426
763
  }
427
- /**
428
- * Get the number of rows in the range
764
+ /**
765
+ * Get the number of rows in the range
429
766
  */ get rowCount() {
430
767
  return this._range.end.row - this._range.start.row + 1;
431
768
  }
432
- /**
433
- * Get the number of columns in the range
769
+ /**
770
+ * Get the number of columns in the range
434
771
  */ get colCount() {
435
772
  return this._range.end.col - this._range.start.col + 1;
436
773
  }
437
- /**
438
- * Get all values in the range as a 2D array
774
+ /**
775
+ * Get all values in the range as a 2D array
439
776
  */ get values() {
440
777
  return this.getValues();
441
778
  }
442
- /**
443
- * Get all values in the range as a 2D array with options
779
+ /**
780
+ * Get all values in the range as a 2D array with options
444
781
  */ getValues(options = {}) {
445
782
  const { createMissing = true } = options;
446
783
  const result = [];
@@ -459,8 +796,8 @@ _computedKey = Symbol.iterator;
459
796
  }
460
797
  return result;
461
798
  }
462
- /**
463
- * Set values in the range from a 2D array
799
+ /**
800
+ * Set values in the range from a 2D array
464
801
  */ set values(data) {
465
802
  for(let r = 0; r < data.length && r < this.rowCount; r++){
466
803
  const row = data[r];
@@ -470,8 +807,8 @@ _computedKey = Symbol.iterator;
470
807
  }
471
808
  }
472
809
  }
473
- /**
474
- * Get all formulas in the range as a 2D array
810
+ /**
811
+ * Get all formulas in the range as a 2D array
475
812
  */ get formulas() {
476
813
  const result = [];
477
814
  for(let r = this._range.start.row; r <= this._range.end.row; r++){
@@ -484,8 +821,8 @@ _computedKey = Symbol.iterator;
484
821
  }
485
822
  return result;
486
823
  }
487
- /**
488
- * Set formulas in the range from a 2D array
824
+ /**
825
+ * Set formulas in the range from a 2D array
489
826
  */ set formulas(data) {
490
827
  for(let r = 0; r < data.length && r < this.rowCount; r++){
491
828
  const row = data[r];
@@ -495,13 +832,13 @@ _computedKey = Symbol.iterator;
495
832
  }
496
833
  }
497
834
  }
498
- /**
499
- * Get the style of the top-left cell
835
+ /**
836
+ * Get the style of the top-left cell
500
837
  */ get style() {
501
838
  return this._worksheet.cell(this._range.start.row, this._range.start.col).style;
502
839
  }
503
- /**
504
- * Set style for all cells in the range
840
+ /**
841
+ * Set style for all cells in the range
505
842
  */ set style(style) {
506
843
  for(let r = this._range.start.row; r <= this._range.end.row; r++){
507
844
  for(let c = this._range.start.col; c <= this._range.end.col; c++){
@@ -510,8 +847,8 @@ _computedKey = Symbol.iterator;
510
847
  }
511
848
  }
512
849
  }
513
- /**
514
- * Iterate over all cells in the range
850
+ /**
851
+ * Iterate over all cells in the range
515
852
  */ *[_computedKey]() {
516
853
  for(let r = this._range.start.row; r <= this._range.end.row; r++){
517
854
  for(let c = this._range.start.col; c <= this._range.end.col; c++){
@@ -519,8 +856,8 @@ _computedKey = Symbol.iterator;
519
856
  }
520
857
  }
521
858
  }
522
- /**
523
- * Iterate over cells row by row
859
+ /**
860
+ * Iterate over cells row by row
524
861
  */ *rows() {
525
862
  for(let r = this._range.start.row; r <= this._range.end.row; r++){
526
863
  const row = [];
@@ -558,19 +895,19 @@ const builderOptions = {
558
895
  };
559
896
  const parser = new fastXmlParser.XMLParser(parserOptions);
560
897
  const builder = new fastXmlParser.XMLBuilder(builderOptions);
561
- /**
562
- * Parses an XML string into a JavaScript object
563
- * Preserves element order and attributes for round-trip compatibility
898
+ /**
899
+ * Parses an XML string into a JavaScript object
900
+ * Preserves element order and attributes for round-trip compatibility
564
901
  */ const parseXml = (xml)=>{
565
902
  return parser.parse(xml);
566
903
  };
567
- /**
568
- * Converts a JavaScript object back to an XML string
904
+ /**
905
+ * Converts a JavaScript object back to an XML string
569
906
  */ const stringifyXml = (obj)=>{
570
907
  return builder.build(obj);
571
908
  };
572
- /**
573
- * Finds the first element with the given tag name in the XML tree
909
+ /**
910
+ * Finds the first element with the given tag name in the XML tree
574
911
  */ const findElement = (nodes, tagName)=>{
575
912
  for (const node of nodes){
576
913
  if (tagName in node) {
@@ -579,8 +916,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
579
916
  }
580
917
  return undefined;
581
918
  };
582
- /**
583
- * Gets the children of an element
919
+ /**
920
+ * Gets the children of an element
584
921
  */ const getChildren = (node, tagName)=>{
585
922
  const children = node[tagName];
586
923
  if (Array.isArray(children)) {
@@ -588,14 +925,14 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
588
925
  }
589
926
  return [];
590
927
  };
591
- /**
592
- * Gets an attribute value from a node
928
+ /**
929
+ * Gets an attribute value from a node
593
930
  */ const getAttr = (node, name)=>{
594
931
  const attrs = node[':@'];
595
932
  return attrs?.[`@_${name}`];
596
933
  };
597
- /**
598
- * Creates a new XML element
934
+ /**
935
+ * Creates a new XML element
599
936
  */ const createElement = (tagName, attrs, children)=>{
600
937
  const node = {
601
938
  [tagName]: children || []
@@ -609,17 +946,17 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
609
946
  }
610
947
  return node;
611
948
  };
612
- /**
613
- * Creates a text node
949
+ /**
950
+ * Creates a text node
614
951
  */ const createText = (text)=>{
615
952
  return {
616
953
  '#text': text
617
954
  };
618
955
  };
619
956
 
620
- /**
621
- * Maps table total function names to SUBTOTAL function numbers
622
- * SUBTOTAL uses 101-111 for functions that ignore hidden values
957
+ /**
958
+ * Maps table total function names to SUBTOTAL function numbers
959
+ * SUBTOTAL uses 101-111 for functions that ignore hidden values
623
960
  */ const TOTAL_FUNCTION_NUMBERS = {
624
961
  average: 101,
625
962
  count: 102,
@@ -631,8 +968,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
631
968
  var: 110,
632
969
  none: 0
633
970
  };
634
- /**
635
- * Maps table total function names to XML attribute values
971
+ /**
972
+ * Maps table total function names to XML attribute values
636
973
  */ const TOTAL_FUNCTION_NAMES = {
637
974
  average: 'average',
638
975
  count: 'count',
@@ -644,8 +981,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
644
981
  var: 'var',
645
982
  none: 'none'
646
983
  };
647
- /**
648
- * Represents an Excel Table (ListObject) with auto-filter, banded styling, and total row.
984
+ /**
985
+ * Represents an Excel Table (ListObject) with auto-filter, banded styling, and total row.
649
986
  */ class Table {
650
987
  constructor(worksheet, config, tableId){
651
988
  this._columns = [];
@@ -681,80 +1018,80 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
681
1018
  // Extract column names from worksheet headers
682
1019
  this._extractColumns();
683
1020
  }
684
- /**
685
- * Get the table name
1021
+ /**
1022
+ * Get the table name
686
1023
  */ get name() {
687
1024
  return this._name;
688
1025
  }
689
- /**
690
- * Get the table display name
1026
+ /**
1027
+ * Get the table display name
691
1028
  */ get displayName() {
692
1029
  return this._displayName;
693
1030
  }
694
- /**
695
- * Get the table ID
1031
+ /**
1032
+ * Get the table ID
696
1033
  */ get id() {
697
1034
  return this._id;
698
1035
  }
699
- /**
700
- * Get the worksheet this table belongs to
1036
+ /**
1037
+ * Get the worksheet this table belongs to
701
1038
  */ get worksheet() {
702
1039
  return this._worksheet;
703
1040
  }
704
- /**
705
- * Get the table range address string
1041
+ /**
1042
+ * Get the table range address string
706
1043
  */ get range() {
707
1044
  return toRange(this._range);
708
1045
  }
709
- /**
710
- * Get the base range excluding total row
1046
+ /**
1047
+ * Get the base range excluding total row
711
1048
  */ get baseRange() {
712
1049
  return toRange(this._baseRange);
713
1050
  }
714
- /**
715
- * Get the table range as RangeAddress
1051
+ /**
1052
+ * Get the table range as RangeAddress
716
1053
  */ get rangeAddress() {
717
1054
  return {
718
1055
  ...this._range
719
1056
  };
720
1057
  }
721
- /**
722
- * Get column names
1058
+ /**
1059
+ * Get column names
723
1060
  */ get columns() {
724
1061
  return this._columns.map((c)=>c.name);
725
1062
  }
726
- /**
727
- * Check if table has a total row
1063
+ /**
1064
+ * Check if table has a total row
728
1065
  */ get hasTotalRow() {
729
1066
  return this._totalRow;
730
1067
  }
731
- /**
732
- * Check if table has a header row
1068
+ /**
1069
+ * Check if table has a header row
733
1070
  */ get hasHeaderRow() {
734
1071
  return this._headerRow;
735
1072
  }
736
- /**
737
- * Check if table has auto-filter enabled
1073
+ /**
1074
+ * Check if table has auto-filter enabled
738
1075
  */ get hasAutoFilter() {
739
1076
  return this._autoFilter;
740
1077
  }
741
- /**
742
- * Get the current style configuration
1078
+ /**
1079
+ * Get the current style configuration
743
1080
  */ get style() {
744
1081
  return {
745
1082
  ...this._style
746
1083
  };
747
1084
  }
748
- /**
749
- * Check if the table has been modified
1085
+ /**
1086
+ * Check if the table has been modified
750
1087
  */ get dirty() {
751
1088
  return this._dirty;
752
1089
  }
753
- /**
754
- * Set a total function for a column
755
- * @param columnName - Name of the column (header text)
756
- * @param fn - Aggregation function to use
757
- * @returns this for method chaining
1090
+ /**
1091
+ * Set a total function for a column
1092
+ * @param columnName - Name of the column (header text)
1093
+ * @param fn - Aggregation function to use
1094
+ * @returns this for method chaining
758
1095
  */ setTotalFunction(columnName, fn) {
759
1096
  if (!this._totalRow) {
760
1097
  throw new Error('Cannot set total function: table does not have a total row enabled');
@@ -769,25 +1106,25 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
769
1106
  this._writeTotalRowFormula(column);
770
1107
  return this;
771
1108
  }
772
- /**
773
- * Get total function for a column if set
1109
+ /**
1110
+ * Get total function for a column if set
774
1111
  */ getTotalFunction(columnName) {
775
1112
  const column = this._columns.find((c)=>c.name === columnName);
776
1113
  return column?.totalFunction;
777
1114
  }
778
- /**
779
- * Enable or disable auto-filter
780
- * @param enabled - Whether auto-filter should be enabled
781
- * @returns this for method chaining
1115
+ /**
1116
+ * Enable or disable auto-filter
1117
+ * @param enabled - Whether auto-filter should be enabled
1118
+ * @returns this for method chaining
782
1119
  */ setAutoFilter(enabled) {
783
1120
  this._autoFilter = enabled;
784
1121
  this._dirty = true;
785
1122
  return this;
786
1123
  }
787
- /**
788
- * Update table style configuration
789
- * @param style - Style options to apply
790
- * @returns this for method chaining
1124
+ /**
1125
+ * Update table style configuration
1126
+ * @param style - Style options to apply
1127
+ * @returns this for method chaining
791
1128
  */ setStyle(style) {
792
1129
  if (style.name !== undefined) this._style.name = style.name;
793
1130
  if (style.showRowStripes !== undefined) this._style.showRowStripes = style.showRowStripes;
@@ -797,10 +1134,10 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
797
1134
  this._dirty = true;
798
1135
  return this;
799
1136
  }
800
- /**
801
- * Enable or disable the total row
802
- * @param enabled - Whether total row should be shown
803
- * @returns this for method chaining
1137
+ /**
1138
+ * Enable or disable the total row
1139
+ * @param enabled - Whether total row should be shown
1140
+ * @returns this for method chaining
804
1141
  */ setTotalRow(enabled) {
805
1142
  if (this._totalRow === enabled) return this;
806
1143
  this._totalRow = enabled;
@@ -830,8 +1167,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
830
1167
  }
831
1168
  return this;
832
1169
  }
833
- /**
834
- * Extract column names from the header row of the worksheet
1170
+ /**
1171
+ * Extract column names from the header row of the worksheet
835
1172
  */ _extractColumns() {
836
1173
  const headerRow = this._range.start.row;
837
1174
  const startCol = this._range.start.col;
@@ -847,8 +1184,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
847
1184
  });
848
1185
  }
849
1186
  }
850
- /**
851
- * Write the SUBTOTAL formula to a total row cell
1187
+ /**
1188
+ * Write the SUBTOTAL formula to a total row cell
852
1189
  */ _writeTotalRowFormula(column) {
853
1190
  if (!this._totalRow || !column.totalFunction || column.totalFunction === 'none') {
854
1191
  return;
@@ -861,8 +1198,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
861
1198
  const formula = `SUBTOTAL(${funcNum},[${column.name}])`;
862
1199
  cell.formula = formula;
863
1200
  }
864
- /**
865
- * Get the auto-filter range (excludes total row if present)
1201
+ /**
1202
+ * Get the auto-filter range (excludes total row if present)
866
1203
  */ _getAutoFilterRange() {
867
1204
  const start = toAddress(this._range.start.row, this._range.start.col);
868
1205
  // Auto-filter excludes the total row
@@ -873,8 +1210,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
873
1210
  const end = toAddress(endRow, this._range.end.col);
874
1211
  return `${start}:${end}`;
875
1212
  }
876
- /**
877
- * Generate the table definition XML
1213
+ /**
1214
+ * Generate the table definition XML
878
1215
  */ toXml() {
879
1216
  const children = [];
880
1217
  // Auto-filter element
@@ -1354,12 +1691,12 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1354
1691
  return table;
1355
1692
  }
1356
1693
  /**
1357
- * Convert sheet data to an array of JSON objects.
1358
- *
1359
- * @param config - Configuration options
1360
- * @returns Array of objects where keys are field names and values are cell values
1694
+ * Convert sheet data to an array of JSON objects.
1361
1695
  *
1362
- * @example
1696
+ * @param config - Configuration options
1697
+ * @returns Array of objects where keys are field names and values are cell values
1698
+ *
1699
+ * @example
1363
1700
  * ```typescript
1364
1701
  * // Using first row as headers
1365
1702
  * const data = sheet.toJson();
@@ -1368,10 +1705,10 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1368
1705
  * const data = sheet.toJson({ fields: ['name', 'age', 'city'] });
1369
1706
  *
1370
1707
  * // Starting from a specific row/column
1371
- * const data = sheet.toJson({ startRow: 2, startCol: 1 });
1372
- * ```
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, asText = false } = 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) {
@@ -1407,7 +1744,7 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1407
1744
  let value;
1408
1745
  if (asText) {
1409
1746
  // Return formatted text instead of raw value
1410
- value = cell?.text ?? '';
1747
+ value = cell?.textWithLocale(locale) ?? '';
1411
1748
  if (value !== '') {
1412
1749
  hasData = true;
1413
1750
  }
@@ -1748,12 +2085,12 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1748
2085
  }
1749
2086
  }
1750
2087
 
1751
- /**
1752
- * Manages the shared strings table from xl/sharedStrings.xml
1753
- * Excel stores strings in a shared table to reduce file size
2088
+ /**
2089
+ * Manages the shared strings table from xl/sharedStrings.xml
2090
+ * Excel stores strings in a shared table to reduce file size
1754
2091
  */ class SharedStrings {
1755
- /**
1756
- * Parse shared strings from XML content
2092
+ /**
2093
+ * Parse shared strings from XML content
1757
2094
  */ static parse(xml) {
1758
2095
  const ss = new SharedStrings();
1759
2096
  const parsed = parseXml(xml);
@@ -1783,9 +2120,9 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1783
2120
  }
1784
2121
  return ss;
1785
2122
  }
1786
- /**
1787
- * Extract text from a string item (si element)
1788
- * Handles both simple <t> elements and rich text <r> elements
2123
+ /**
2124
+ * Extract text from a string item (si element)
2125
+ * Handles both simple <t> elements and rich text <r> elements
1789
2126
  */ extractText(nodes) {
1790
2127
  let text = '';
1791
2128
  for (const node of nodes){
@@ -1814,14 +2151,14 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1814
2151
  }
1815
2152
  return text;
1816
2153
  }
1817
- /**
1818
- * Get a string by index
2154
+ /**
2155
+ * Get a string by index
1819
2156
  */ getString(index) {
1820
2157
  return this.entries[index]?.text;
1821
2158
  }
1822
- /**
1823
- * Add a string and return its index
1824
- * If the string already exists, returns the existing index
2159
+ /**
2160
+ * Add a string and return its index
2161
+ * If the string already exists, returns the existing index
1825
2162
  */ addString(str) {
1826
2163
  const existing = this.stringToIndex.get(str);
1827
2164
  if (existing !== undefined) {
@@ -1847,23 +2184,23 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1847
2184
  this._dirty = true;
1848
2185
  return index;
1849
2186
  }
1850
- /**
1851
- * Check if the shared strings table has been modified
2187
+ /**
2188
+ * Check if the shared strings table has been modified
1852
2189
  */ get dirty() {
1853
2190
  return this._dirty;
1854
2191
  }
1855
- /**
1856
- * Get the count of strings
2192
+ /**
2193
+ * Get the count of strings
1857
2194
  */ get count() {
1858
2195
  return this.entries.length;
1859
2196
  }
1860
- /**
1861
- * Get total usage count of shared strings
2197
+ /**
2198
+ * Get total usage count of shared strings
1862
2199
  */ get totalCount() {
1863
2200
  return Math.max(this._totalCount, this.entries.length);
1864
2201
  }
1865
- /**
1866
- * Generate XML for the shared strings table
2202
+ /**
2203
+ * Generate XML for the shared strings table
1867
2204
  */ toXml() {
1868
2205
  const siElements = [];
1869
2206
  for (const entry of this.entries){
@@ -1900,9 +2237,9 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
1900
2237
  }
1901
2238
  }
1902
2239
 
1903
- /**
1904
- * Excel built-in number format IDs (0-163 are reserved).
1905
- * These formats don't need to be defined in the numFmts element.
2240
+ /**
2241
+ * Excel built-in number format IDs (0-163 are reserved).
2242
+ * These formats don't need to be defined in the numFmts element.
1906
2243
  */ const BUILTIN_NUM_FMTS = new Map([
1907
2244
  [
1908
2245
  'General',
@@ -2017,15 +2354,15 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2017
2354
  49
2018
2355
  ]
2019
2356
  ]);
2020
- /**
2021
- * Reverse lookup: built-in format ID -> format code
2357
+ /**
2358
+ * Reverse lookup: built-in format ID -> format code
2022
2359
  */ const BUILTIN_NUM_FMT_CODES = new Map(Array.from(BUILTIN_NUM_FMTS.entries()).map(([code, id])=>[
2023
2360
  id,
2024
2361
  code
2025
2362
  ]));
2026
- /**
2027
- * Normalize a color to ARGB format (8 hex chars).
2028
- * Accepts: "#RGB", "#RRGGBB", "RGB", "RRGGBB", "AARRGGBB", "#AARRGGBB"
2363
+ /**
2364
+ * Normalize a color to ARGB format (8 hex chars).
2365
+ * Accepts: "#RGB", "#RRGGBB", "RGB", "RRGGBB", "AARRGGBB", "#AARRGGBB"
2029
2366
  */ const normalizeColor = (color)=>{
2030
2367
  let c = color.replace(/^#/, '').toUpperCase();
2031
2368
  // Handle shorthand 3-char format (e.g., "FFF" -> "FFFFFF")
@@ -2038,14 +2375,14 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2038
2375
  }
2039
2376
  return c;
2040
2377
  };
2041
- /**
2042
- * Manages the styles (xl/styles.xml)
2378
+ /**
2379
+ * Manages the styles (xl/styles.xml)
2043
2380
  */ class Styles {
2044
- /**
2045
- * Generate a deterministic cache key for a style object.
2046
- * More efficient than JSON.stringify as it avoids the overhead of
2047
- * full JSON serialization and produces a consistent key regardless
2048
- * of property order.
2381
+ /**
2382
+ * Generate a deterministic cache key for a style object.
2383
+ * More efficient than JSON.stringify as it avoids the overhead of
2384
+ * full JSON serialization and produces a consistent key regardless
2385
+ * of property order.
2049
2386
  */ _getStyleKey(style) {
2050
2387
  // Use a delimiter that won't appear in values
2051
2388
  const SEP = '\x00';
@@ -2085,8 +2422,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2085
2422
  }
2086
2423
  return parts.join(SEP);
2087
2424
  }
2088
- /**
2089
- * Parse styles from XML content
2425
+ /**
2426
+ * Parse styles from XML content
2090
2427
  */ static parse(xml) {
2091
2428
  const styles = new Styles();
2092
2429
  styles._xmlNodes = parseXml(xml);
@@ -2142,8 +2479,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2142
2479
  }
2143
2480
  return styles;
2144
2481
  }
2145
- /**
2146
- * Create an empty styles object with defaults
2482
+ /**
2483
+ * Create an empty styles object with defaults
2147
2484
  */ static createDefault() {
2148
2485
  const styles = new Styles();
2149
2486
  // Default font (Calibri 11)
@@ -2282,8 +2619,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2282
2619
  textRotation: parseInt(getAttr(alignNode, 'textRotation') || '0', 10)
2283
2620
  };
2284
2621
  }
2285
- /**
2286
- * Get a style by index
2622
+ /**
2623
+ * Get a style by index
2287
2624
  */ getStyle(index) {
2288
2625
  const cached = this._styleObjectCache.get(index);
2289
2626
  if (cached) return {
@@ -2347,9 +2684,9 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2347
2684
  });
2348
2685
  return style;
2349
2686
  }
2350
- /**
2351
- * Create a style and return its index
2352
- * Uses caching to deduplicate identical styles
2687
+ /**
2688
+ * Create a style and return its index
2689
+ * Uses caching to deduplicate identical styles
2353
2690
  */ createStyle(style) {
2354
2691
  const key = this._getStyleKey(style);
2355
2692
  const cached = this._styleCache.get(key);
@@ -2388,8 +2725,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2388
2725
  });
2389
2726
  return index;
2390
2727
  }
2391
- /**
2392
- * Clone an existing style by index, optionally overriding fields.
2728
+ /**
2729
+ * Clone an existing style by index, optionally overriding fields.
2393
2730
  */ cloneStyle(index, overrides = {}) {
2394
2731
  const baseStyle = this.getStyle(index);
2395
2732
  return this.createStyle({
@@ -2473,21 +2810,21 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2473
2810
  this._numFmts.set(id, format);
2474
2811
  return id;
2475
2812
  }
2476
- /**
2477
- * Get or create a number format ID for the given format string.
2478
- * Returns built-in IDs (0-163) for standard formats, or creates custom IDs (164+).
2479
- * @param format - The number format string (e.g., '0.00', '#,##0', '$#,##0.00')
2813
+ /**
2814
+ * Get or create a number format ID for the given format string.
2815
+ * Returns built-in IDs (0-163) for standard formats, or creates custom IDs (164+).
2816
+ * @param format - The number format string (e.g., '0.00', '#,##0', '$#,##0.00')
2480
2817
  */ getOrCreateNumFmtId(format) {
2481
2818
  this._dirty = true;
2482
2819
  return this._findOrCreateNumFmt(format);
2483
2820
  }
2484
- /**
2485
- * Check if styles have been modified
2821
+ /**
2822
+ * Check if styles have been modified
2486
2823
  */ get dirty() {
2487
2824
  return this._dirty;
2488
2825
  }
2489
- /**
2490
- * Generate XML for styles
2826
+ /**
2827
+ * Generate XML for styles
2491
2828
  */ toXml() {
2492
2829
  const children = [];
2493
2830
  // Number formats
@@ -2687,8 +3024,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2687
3024
  }
2688
3025
  }
2689
3026
 
2690
- /**
2691
- * Represents an Excel pivot table with a fluent API for configuration.
3027
+ /**
3028
+ * Represents an Excel pivot table with a fluent API for configuration.
2692
3029
  */ class PivotTable {
2693
3030
  constructor(name, cache, targetSheet, targetCell, targetRow, targetCol, pivotTableIndex, cacheFileIndex){
2694
3031
  this._rowFields = [];
@@ -2706,47 +3043,47 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2706
3043
  this._pivotTableIndex = pivotTableIndex;
2707
3044
  this._cacheFileIndex = cacheFileIndex;
2708
3045
  }
2709
- /**
2710
- * Get the pivot table name
3046
+ /**
3047
+ * Get the pivot table name
2711
3048
  */ get name() {
2712
3049
  return this._name;
2713
3050
  }
2714
- /**
2715
- * Get the target sheet name
3051
+ /**
3052
+ * Get the target sheet name
2716
3053
  */ get targetSheet() {
2717
3054
  return this._targetSheet;
2718
3055
  }
2719
- /**
2720
- * Get the target cell address
3056
+ /**
3057
+ * Get the target cell address
2721
3058
  */ get targetCell() {
2722
3059
  return this._targetCell;
2723
3060
  }
2724
- /**
2725
- * Get the pivot cache
3061
+ /**
3062
+ * Get the pivot cache
2726
3063
  */ get cache() {
2727
3064
  return this._cache;
2728
3065
  }
2729
- /**
2730
- * Get the pivot table index (for file naming)
3066
+ /**
3067
+ * Get the pivot table index (for file naming)
2731
3068
  */ get index() {
2732
3069
  return this._pivotTableIndex;
2733
3070
  }
2734
- /**
2735
- * Get the pivot cache file index used for rels.
2736
- * @internal
3071
+ /**
3072
+ * Get the pivot cache file index used for rels.
3073
+ * @internal
2737
3074
  */ get cacheFileIndex() {
2738
3075
  return this._cacheFileIndex;
2739
3076
  }
2740
- /**
2741
- * Set the styles reference for number format resolution
2742
- * @internal
3077
+ /**
3078
+ * Set the styles reference for number format resolution
3079
+ * @internal
2743
3080
  */ setStyles(styles) {
2744
3081
  this._styles = styles;
2745
3082
  return this;
2746
3083
  }
2747
- /**
2748
- * Add a field to the row area
2749
- * @param fieldName - Name of the source field (column header)
3084
+ /**
3085
+ * Add a field to the row area
3086
+ * @param fieldName - Name of the source field (column header)
2750
3087
  */ addRowField(fieldName) {
2751
3088
  const fieldIndex = this._cache.getFieldIndex(fieldName);
2752
3089
  if (fieldIndex < 0) {
@@ -2761,9 +3098,9 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2761
3098
  this._fieldAssignments.set(fieldIndex, assignment);
2762
3099
  return this;
2763
3100
  }
2764
- /**
2765
- * Add a field to the column area
2766
- * @param fieldName - Name of the source field (column header)
3101
+ /**
3102
+ * Add a field to the column area
3103
+ * @param fieldName - Name of the source field (column header)
2767
3104
  */ addColumnField(fieldName) {
2768
3105
  const fieldIndex = this._cache.getFieldIndex(fieldName);
2769
3106
  if (fieldIndex < 0) {
@@ -2817,9 +3154,9 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2817
3154
  this._fieldAssignments.set(fieldIndex, assignment);
2818
3155
  return this;
2819
3156
  }
2820
- /**
2821
- * Add a field to the filter (page) area
2822
- * @param fieldName - Name of the source field (column header)
3157
+ /**
3158
+ * Add a field to the filter (page) area
3159
+ * @param fieldName - Name of the source field (column header)
2823
3160
  */ addFilterField(fieldName) {
2824
3161
  const fieldIndex = this._cache.getFieldIndex(fieldName);
2825
3162
  if (fieldIndex < 0) {
@@ -2834,10 +3171,10 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2834
3171
  this._fieldAssignments.set(fieldIndex, assignment);
2835
3172
  return this;
2836
3173
  }
2837
- /**
2838
- * Set a sort order for a row or column field
2839
- * @param fieldName - Name of the field to sort
2840
- * @param order - Sort order ('asc' or 'desc')
3174
+ /**
3175
+ * Set a sort order for a row or column field
3176
+ * @param fieldName - Name of the field to sort
3177
+ * @param order - Sort order ('asc' or 'desc')
2841
3178
  */ sortField(fieldName, order) {
2842
3179
  const fieldIndex = this._cache.getFieldIndex(fieldName);
2843
3180
  if (fieldIndex < 0) {
@@ -2853,10 +3190,10 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2853
3190
  assignment.sortOrder = order;
2854
3191
  return this;
2855
3192
  }
2856
- /**
2857
- * Filter items for a row, column, or filter field
2858
- * @param fieldName - Name of the field to filter
2859
- * @param filter - Filter configuration with include or exclude list
3193
+ /**
3194
+ * Filter items for a row, column, or filter field
3195
+ * @param fieldName - Name of the field to filter
3196
+ * @param filter - Filter configuration with include or exclude list
2860
3197
  */ filterField(fieldName, filter) {
2861
3198
  const fieldIndex = this._cache.getFieldIndex(fieldName);
2862
3199
  if (fieldIndex < 0) {
@@ -2872,8 +3209,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
2872
3209
  assignment.filter = filter;
2873
3210
  return this;
2874
3211
  }
2875
- /**
2876
- * Generate the pivotTableDefinition XML
3212
+ /**
3213
+ * Generate the pivotTableDefinition XML
2877
3214
  */ toXml() {
2878
3215
  const children = [];
2879
3216
  // Calculate location (estimate based on fields)
@@ -3033,8 +3370,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3033
3370
  pivotTableNode
3034
3371
  ])}`;
3035
3372
  }
3036
- /**
3037
- * Build a pivotField node for a given field index
3373
+ /**
3374
+ * Build a pivotField node for a given field index
3038
3375
  */ _buildPivotFieldNode(fieldIndex) {
3039
3376
  const attrs = {};
3040
3377
  const children = [];
@@ -3092,8 +3429,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3092
3429
  }
3093
3430
  return createElement('pivotField', attrs, children);
3094
3431
  }
3095
- /**
3096
- * Build item nodes for a pivot field, with optional filtering
3432
+ /**
3433
+ * Build item nodes for a pivot field, with optional filtering
3097
3434
  */ _buildItemNodes(sharedItems, filter) {
3098
3435
  const itemNodes = [];
3099
3436
  for(let i = 0; i < sharedItems.length; i++){
@@ -3121,8 +3458,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3121
3458
  }, []));
3122
3459
  return itemNodes;
3123
3460
  }
3124
- /**
3125
- * Build row items based on unique values in row fields
3461
+ /**
3462
+ * Build row items based on unique values in row fields
3126
3463
  */ _buildRowItems() {
3127
3464
  const items = [];
3128
3465
  if (this._rowFields.length === 0) return items;
@@ -3146,8 +3483,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3146
3483
  ]));
3147
3484
  return items;
3148
3485
  }
3149
- /**
3150
- * Build column items based on unique values in column fields
3486
+ /**
3487
+ * Build column items based on unique values in column fields
3151
3488
  */ _buildColItems() {
3152
3489
  const items = [];
3153
3490
  if (this._columnFields.length === 0) return items;
@@ -3204,8 +3541,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3204
3541
  }
3205
3542
  return items;
3206
3543
  }
3207
- /**
3208
- * Calculate the location reference for the pivot table output
3544
+ /**
3545
+ * Calculate the location reference for the pivot table output
3209
3546
  */ _calculateLocationRef() {
3210
3547
  // Estimate output size based on fields
3211
3548
  const numRows = this._estimateRowCount();
@@ -3216,8 +3553,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3216
3553
  const endCol = startCol + numCols - 1;
3217
3554
  return `${this._colToLetter(startCol)}${startRow}:${this._colToLetter(endCol)}${endRow}`;
3218
3555
  }
3219
- /**
3220
- * Estimate number of rows in pivot table output
3556
+ /**
3557
+ * Estimate number of rows in pivot table output
3221
3558
  */ _estimateRowCount() {
3222
3559
  let count = 1; // Header row
3223
3560
  // Add filter area rows
@@ -3232,8 +3569,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3232
3569
  }
3233
3570
  return Math.max(count, 3);
3234
3571
  }
3235
- /**
3236
- * Estimate number of columns in pivot table output
3572
+ /**
3573
+ * Estimate number of columns in pivot table output
3237
3574
  */ _estimateColCount() {
3238
3575
  let count = 0;
3239
3576
  // Row label columns
@@ -3249,8 +3586,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3249
3586
  }
3250
3587
  return Math.max(count, 2);
3251
3588
  }
3252
- /**
3253
- * Convert 0-based column index to letter (A, B, ..., Z, AA, etc.)
3589
+ /**
3590
+ * Convert 0-based column index to letter (A, B, ..., Z, AA, etc.)
3254
3591
  */ _colToLetter(col) {
3255
3592
  let result = '';
3256
3593
  let n = col;
@@ -3262,9 +3599,9 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3262
3599
  }
3263
3600
  }
3264
3601
 
3265
- /**
3266
- * Manages the pivot cache (definition and records) for a pivot table.
3267
- * The cache stores source data metadata and cached values.
3602
+ /**
3603
+ * Manages the pivot cache (definition and records) for a pivot table.
3604
+ * The cache stores source data metadata and cached values.
3268
3605
  */ class PivotCache {
3269
3606
  constructor(cacheId, sourceSheet, sourceRange, fileIndex){
3270
3607
  this._fields = [];
@@ -3278,55 +3615,55 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3278
3615
  this._sourceSheet = sourceSheet;
3279
3616
  this._sourceRange = sourceRange;
3280
3617
  }
3281
- /**
3282
- * Get the cache ID
3618
+ /**
3619
+ * Get the cache ID
3283
3620
  */ get cacheId() {
3284
3621
  return this._cacheId;
3285
3622
  }
3286
- /**
3287
- * Get the file index for this cache (used for file naming).
3623
+ /**
3624
+ * Get the file index for this cache (used for file naming).
3288
3625
  */ get fileIndex() {
3289
3626
  return this._fileIndex;
3290
3627
  }
3291
- /**
3292
- * Set refreshOnLoad option
3628
+ /**
3629
+ * Set refreshOnLoad option
3293
3630
  */ set refreshOnLoad(value) {
3294
3631
  this._refreshOnLoad = value;
3295
3632
  }
3296
- /**
3297
- * Get refreshOnLoad option
3633
+ /**
3634
+ * Get refreshOnLoad option
3298
3635
  */ get refreshOnLoad() {
3299
3636
  return this._refreshOnLoad;
3300
3637
  }
3301
- /**
3302
- * Get the source sheet name
3638
+ /**
3639
+ * Get the source sheet name
3303
3640
  */ get sourceSheet() {
3304
3641
  return this._sourceSheet;
3305
3642
  }
3306
- /**
3307
- * Get the source range
3643
+ /**
3644
+ * Get the source range
3308
3645
  */ get sourceRange() {
3309
3646
  return this._sourceRange;
3310
3647
  }
3311
- /**
3312
- * Get the full source reference (Sheet!Range)
3648
+ /**
3649
+ * Get the full source reference (Sheet!Range)
3313
3650
  */ get sourceRef() {
3314
3651
  return `${this._sourceSheet}!${this._sourceRange}`;
3315
3652
  }
3316
- /**
3317
- * Get the fields in this cache
3653
+ /**
3654
+ * Get the fields in this cache
3318
3655
  */ get fields() {
3319
3656
  return this._fields;
3320
3657
  }
3321
- /**
3322
- * Get the number of data records
3658
+ /**
3659
+ * Get the number of data records
3323
3660
  */ get recordCount() {
3324
3661
  return this._recordCount;
3325
3662
  }
3326
- /**
3327
- * Build the cache from source data.
3328
- * @param headers - Array of column header names
3329
- * @param data - 2D array of data rows (excluding headers)
3663
+ /**
3664
+ * Build the cache from source data.
3665
+ * @param headers - Array of column header names
3666
+ * @param data - 2D array of data rows (excluding headers)
3330
3667
  */ buildFromData(headers, data) {
3331
3668
  this._recordCount = data.length;
3332
3669
  // Initialize fields from headers
@@ -3387,19 +3724,19 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3387
3724
  // Store records
3388
3725
  this._records = data;
3389
3726
  }
3390
- /**
3391
- * Get field by name
3727
+ /**
3728
+ * Get field by name
3392
3729
  */ getField(name) {
3393
3730
  return this._fields.find((f)=>f.name === name);
3394
3731
  }
3395
- /**
3396
- * Get field index by name
3732
+ /**
3733
+ * Get field index by name
3397
3734
  */ getFieldIndex(name) {
3398
3735
  const field = this._fields.find((f)=>f.name === name);
3399
3736
  return field ? field.index : -1;
3400
3737
  }
3401
- /**
3402
- * Generate the pivotCacheDefinition XML
3738
+ /**
3739
+ * Generate the pivotCacheDefinition XML
3403
3740
  */ toDefinitionXml(recordsRelId) {
3404
3741
  const cacheFieldNodes = this._fields.map((field)=>{
3405
3742
  const sharedItemsAttrs = {};
@@ -3471,8 +3808,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3471
3808
  definitionNode
3472
3809
  ])}`;
3473
3810
  }
3474
- /**
3475
- * Generate the pivotCacheRecords XML
3811
+ /**
3812
+ * Generate the pivotCacheRecords XML
3476
3813
  */ toRecordsXml() {
3477
3814
  const recordNodes = [];
3478
3815
  for (const row of this._records){
@@ -3577,8 +3914,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3577
3914
  files.set(path, fflate.strToU8(content));
3578
3915
  };
3579
3916
 
3580
- /**
3581
- * Represents an Excel workbook (.xlsx file)
3917
+ /**
3918
+ * Represents an Excel workbook (.xlsx file)
3582
3919
  */ class Workbook {
3583
3920
  constructor(){
3584
3921
  this._files = new Map();
@@ -3595,17 +3932,18 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3595
3932
  this._nextTableId = 1;
3596
3933
  // Date serialization handling
3597
3934
  this._dateHandling = 'jsDate';
3935
+ this._locale = 'fr-FR';
3598
3936
  this._sharedStrings = new SharedStrings();
3599
3937
  this._styles = Styles.createDefault();
3600
3938
  }
3601
- /**
3602
- * Load a workbook from a file path
3939
+ /**
3940
+ * Load a workbook from a file path
3603
3941
  */ static async fromFile(path) {
3604
3942
  const data = await promises.readFile(path);
3605
3943
  return Workbook.fromBuffer(new Uint8Array(data));
3606
3944
  }
3607
- /**
3608
- * Load a workbook from a buffer
3945
+ /**
3946
+ * Load a workbook from a buffer
3609
3947
  */ static async fromBuffer(data) {
3610
3948
  const workbook = new Workbook();
3611
3949
  workbook._files = await readZip(data);
@@ -3631,52 +3969,62 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3631
3969
  }
3632
3970
  return workbook;
3633
3971
  }
3634
- /**
3635
- * Create a new empty workbook
3972
+ /**
3973
+ * Create a new empty workbook
3636
3974
  */ static create() {
3637
3975
  const workbook = new Workbook();
3638
3976
  workbook._dirty = true;
3639
3977
  return workbook;
3640
3978
  }
3641
- /**
3642
- * Get sheet names
3979
+ /**
3980
+ * Get sheet names
3643
3981
  */ get sheetNames() {
3644
3982
  return this._sheetDefs.map((s)=>s.name);
3645
3983
  }
3646
- /**
3647
- * Get number of sheets
3984
+ /**
3985
+ * Get number of sheets
3648
3986
  */ get sheetCount() {
3649
3987
  return this._sheetDefs.length;
3650
3988
  }
3651
- /**
3652
- * Get shared strings table
3989
+ /**
3990
+ * Get shared strings table
3653
3991
  */ get sharedStrings() {
3654
3992
  return this._sharedStrings;
3655
3993
  }
3656
- /**
3657
- * Get styles
3994
+ /**
3995
+ * Get styles
3658
3996
  */ get styles() {
3659
3997
  return this._styles;
3660
3998
  }
3661
- /**
3662
- * Get the workbook date handling strategy.
3999
+ /**
4000
+ * Get the workbook date handling strategy.
3663
4001
  */ get dateHandling() {
3664
4002
  return this._dateHandling;
3665
4003
  }
3666
- /**
3667
- * Set the workbook date handling strategy.
4004
+ /**
4005
+ * Set the workbook date handling strategy.
3668
4006
  */ set dateHandling(value) {
3669
4007
  this._dateHandling = value;
3670
4008
  }
3671
- /**
3672
- * Get the next unique table ID for this workbook.
3673
- * Table IDs must be unique across all worksheets.
3674
- * @internal
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
+ /**
4020
+ * Get the next unique table ID for this workbook.
4021
+ * Table IDs must be unique across all worksheets.
4022
+ * @internal
3675
4023
  */ getNextTableId() {
3676
4024
  return this._nextTableId++;
3677
4025
  }
3678
- /**
3679
- * Get a worksheet by name or index
4026
+ /**
4027
+ * Get a worksheet by name or index
3680
4028
  */ sheet(nameOrIndex) {
3681
4029
  let def;
3682
4030
  if (typeof nameOrIndex === 'number') {
@@ -3705,8 +4053,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3705
4053
  this._sheets.set(def.name, worksheet);
3706
4054
  return worksheet;
3707
4055
  }
3708
- /**
3709
- * Add a new worksheet
4056
+ /**
4057
+ * Add a new worksheet
3710
4058
  */ addSheet(name, index) {
3711
4059
  // Check for duplicate name
3712
4060
  if (this._sheetDefs.some((s)=>s.name === name)) {
@@ -3738,8 +4086,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3738
4086
  this._sheets.set(name, worksheet);
3739
4087
  return worksheet;
3740
4088
  }
3741
- /**
3742
- * Delete a worksheet by name or index
4089
+ /**
4090
+ * Delete a worksheet by name or index
3743
4091
  */ deleteSheet(nameOrIndex) {
3744
4092
  let index;
3745
4093
  if (typeof nameOrIndex === 'number') {
@@ -3768,8 +4116,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3768
4116
  this._relationships.splice(relIndex, 1);
3769
4117
  }
3770
4118
  }
3771
- /**
3772
- * Rename a worksheet
4119
+ /**
4120
+ * Rename a worksheet
3773
4121
  */ renameSheet(oldName, newName) {
3774
4122
  const def = this._sheetDefs.find((s)=>s.name === oldName);
3775
4123
  if (!def) {
@@ -3788,8 +4136,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3788
4136
  }
3789
4137
  def.name = newName;
3790
4138
  }
3791
- /**
3792
- * Copy a worksheet
4139
+ /**
4140
+ * Copy a worksheet
3793
4141
  */ copySheet(sourceName, newName) {
3794
4142
  const source = this.sheet(sourceName);
3795
4143
  const copy = this.addSheet(newName);
@@ -3874,49 +4222,49 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3874
4222
  }
3875
4223
  return false;
3876
4224
  }
3877
- /**
3878
- * Create a new worksheet from an array of objects.
3879
- *
3880
- * The first row contains headers (object keys or custom column headers),
3881
- * and subsequent rows contain the object values.
3882
- *
3883
- * @param config - Configuration for the sheet creation
3884
- * @returns The created Worksheet
3885
- *
3886
- * @example
3887
- * ```typescript
3888
- * const data = [
3889
- * { name: 'Alice', age: 30, city: 'Paris' },
3890
- * { name: 'Bob', age: 25, city: 'London' },
3891
- * { name: 'Charlie', age: 35, city: 'Berlin' },
3892
- * ];
3893
- *
3894
- * // Simple usage - all object keys become columns
3895
- * const sheet = wb.addSheetFromData({
3896
- * name: 'People',
3897
- * data: data,
3898
- * });
3899
- *
3900
- * // With custom column configuration
3901
- * const sheet2 = wb.addSheetFromData({
3902
- * name: 'People Custom',
3903
- * data: data,
3904
- * columns: [
3905
- * { key: 'name', header: 'Full Name' },
3906
- * { key: 'age', header: 'Age (years)' },
3907
- * ],
3908
- * });
3909
- *
3910
- * // With rich cell values (value, formula, style)
3911
- * const dataWithFormulas = [
3912
- * { product: 'Widget', price: 10, qty: 5, total: { formula: 'B2*C2', style: { bold: true } } },
3913
- * { product: 'Gadget', price: 20, qty: 3, total: { formula: 'B3*C3', style: { bold: true } } },
3914
- * ];
3915
- * const sheet3 = wb.addSheetFromData({
3916
- * name: 'With Formulas',
3917
- * data: dataWithFormulas,
3918
- * });
3919
- * ```
4225
+ /**
4226
+ * Create a new worksheet from an array of objects.
4227
+ *
4228
+ * The first row contains headers (object keys or custom column headers),
4229
+ * and subsequent rows contain the object values.
4230
+ *
4231
+ * @param config - Configuration for the sheet creation
4232
+ * @returns The created Worksheet
4233
+ *
4234
+ * @example
4235
+ * ```typescript
4236
+ * const data = [
4237
+ * { name: 'Alice', age: 30, city: 'Paris' },
4238
+ * { name: 'Bob', age: 25, city: 'London' },
4239
+ * { name: 'Charlie', age: 35, city: 'Berlin' },
4240
+ * ];
4241
+ *
4242
+ * // Simple usage - all object keys become columns
4243
+ * const sheet = wb.addSheetFromData({
4244
+ * name: 'People',
4245
+ * data: data,
4246
+ * });
4247
+ *
4248
+ * // With custom column configuration
4249
+ * const sheet2 = wb.addSheetFromData({
4250
+ * name: 'People Custom',
4251
+ * data: data,
4252
+ * columns: [
4253
+ * { key: 'name', header: 'Full Name' },
4254
+ * { key: 'age', header: 'Age (years)' },
4255
+ * ],
4256
+ * });
4257
+ *
4258
+ * // With rich cell values (value, formula, style)
4259
+ * const dataWithFormulas = [
4260
+ * { product: 'Widget', price: 10, qty: 5, total: { formula: 'B2*C2', style: { bold: true } } },
4261
+ * { product: 'Gadget', price: 20, qty: 3, total: { formula: 'B3*C3', style: { bold: true } } },
4262
+ * ];
4263
+ * const sheet3 = wb.addSheetFromData({
4264
+ * name: 'With Formulas',
4265
+ * data: dataWithFormulas,
4266
+ * });
4267
+ * ```
3920
4268
  */ addSheetFromData(config) {
3921
4269
  const { name, data, columns, headerStyle = true, startCell = 'A1' } = config;
3922
4270
  if (!data?.length) return this.addSheet(name);
@@ -3971,8 +4319,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3971
4319
  }
3972
4320
  return sheet;
3973
4321
  }
3974
- /**
3975
- * Check if a value is a rich cell value object with value, formula, or style fields
4322
+ /**
4323
+ * Check if a value is a rich cell value object with value, formula, or style fields
3976
4324
  */ _isRichCellValue(value) {
3977
4325
  if (value === null || value === undefined) {
3978
4326
  return false;
@@ -3984,15 +4332,15 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
3984
4332
  const obj = value;
3985
4333
  return 'value' in obj || 'formula' in obj || 'style' in obj;
3986
4334
  }
3987
- /**
3988
- * Infer column configuration from the first data object
4335
+ /**
4336
+ * Infer column configuration from the first data object
3989
4337
  */ _inferColumns(sample) {
3990
4338
  return Object.keys(sample).map((key)=>({
3991
4339
  key
3992
4340
  }));
3993
4341
  }
3994
- /**
3995
- * Convert an unknown value to a CellValue
4342
+ /**
4343
+ * Convert an unknown value to a CellValue
3996
4344
  */ _toCellValue(value) {
3997
4345
  if (value === null || value === undefined) {
3998
4346
  return null;
@@ -4009,25 +4357,25 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
4009
4357
  // Convert other types to string
4010
4358
  return String(value);
4011
4359
  }
4012
- /**
4013
- * Create a pivot table from source data.
4014
- *
4015
- * @param config - Pivot table configuration
4016
- * @returns PivotTable instance for fluent configuration
4017
- *
4018
- * @example
4019
- * ```typescript
4020
- * const pivot = wb.createPivotTable({
4021
- * name: 'SalesPivot',
4022
- * source: 'DataSheet!A1:D100',
4023
- * target: 'PivotSheet!A3',
4024
- * });
4025
- *
4026
- * pivot
4027
- * .addRowField('Region')
4028
- * .addColumnField('Product')
4029
- * .addValueField('Sales', 'sum', 'Total Sales');
4030
- * ```
4360
+ /**
4361
+ * Create a pivot table from source data.
4362
+ *
4363
+ * @param config - Pivot table configuration
4364
+ * @returns PivotTable instance for fluent configuration
4365
+ *
4366
+ * @example
4367
+ * ```typescript
4368
+ * const pivot = wb.createPivotTable({
4369
+ * name: 'SalesPivot',
4370
+ * source: 'DataSheet!A1:D100',
4371
+ * target: 'PivotSheet!A3',
4372
+ * });
4373
+ *
4374
+ * pivot
4375
+ * .addRowField('Region')
4376
+ * .addColumnField('Product')
4377
+ * .addValueField('Sales', 'sum', 'Total Sales');
4378
+ * ```
4031
4379
  */ createPivotTable(config) {
4032
4380
  this._dirty = true;
4033
4381
  // Parse source reference (Sheet!Range)
@@ -4061,8 +4409,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
4061
4409
  this._pivotTables.push(pivotTable);
4062
4410
  return pivotTable;
4063
4411
  }
4064
- /**
4065
- * Parse a sheet reference like "Sheet1!A1:D100" into sheet name and range
4412
+ /**
4413
+ * Parse a sheet reference like "Sheet1!A1:D100" into sheet name and range
4066
4414
  */ _parseSheetRef(ref) {
4067
4415
  const match = ref.match(/^(.+?)!(.+)$/);
4068
4416
  if (!match) {
@@ -4073,8 +4421,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
4073
4421
  range: match[2]
4074
4422
  };
4075
4423
  }
4076
- /**
4077
- * Extract headers and data from a source range
4424
+ /**
4425
+ * Extract headers and data from a source range
4078
4426
  */ _extractSourceData(sheet, rangeStr) {
4079
4427
  const range = parseRange(rangeStr);
4080
4428
  const headers = [];
@@ -4098,14 +4446,14 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
4098
4446
  data
4099
4447
  };
4100
4448
  }
4101
- /**
4102
- * Save the workbook to a file
4449
+ /**
4450
+ * Save the workbook to a file
4103
4451
  */ async toFile(path) {
4104
4452
  const buffer = await this.toBuffer();
4105
4453
  await promises.writeFile(path, buffer);
4106
4454
  }
4107
- /**
4108
- * Save the workbook to a buffer
4455
+ /**
4456
+ * Save the workbook to a buffer
4109
4457
  */ async toBuffer() {
4110
4458
  // Update files map with modified content
4111
4459
  this._updateFiles();
@@ -4419,8 +4767,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
4419
4767
  ])}`);
4420
4768
  }
4421
4769
  }
4422
- /**
4423
- * Generate all pivot table related files
4770
+ /**
4771
+ * Generate all pivot table related files
4424
4772
  */ _updatePivotTableFiles() {
4425
4773
  // Track which sheets have pivot tables for their .rels files
4426
4774
  const sheetPivotTables = new Map();
@@ -4538,8 +4886,8 @@ const builder = new fastXmlParser.XMLBuilder(builderOptions);
4538
4886
  ])}`);
4539
4887
  }
4540
4888
  }
4541
- /**
4542
- * Generate all table related files
4889
+ /**
4890
+ * Generate all table related files
4543
4891
  */ _updateTableFiles() {
4544
4892
  // Collect all tables with their global indices
4545
4893
  let globalTableIndex = 1;