@cj-tech-master/excelts 2.0.0 → 2.0.1-canary.20251228093548.379d895

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/browser/excelts.esm.js +217 -126
  2. package/dist/browser/excelts.esm.js.map +1 -1
  3. package/dist/browser/excelts.esm.min.js +37 -34
  4. package/dist/browser/excelts.iife.js +217 -126
  5. package/dist/browser/excelts.iife.js.map +1 -1
  6. package/dist/browser/excelts.iife.min.js +37 -34
  7. package/dist/cjs/doc/pivot-table.js +37 -3
  8. package/dist/cjs/doc/worksheet.js +0 -1
  9. package/dist/cjs/stream/xlsx/worksheet-writer.js +0 -1
  10. package/dist/cjs/utils/datetime.js +7 -156
  11. package/dist/cjs/xlsx/xform/book/sheet-xform.js +3 -2
  12. package/dist/cjs/xlsx/xform/book/workbook-properties-xform.js +1 -1
  13. package/dist/cjs/xlsx/xform/core/content-types-xform.js +12 -6
  14. package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +11 -4
  15. package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +4 -11
  16. package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +124 -17
  17. package/dist/cjs/xlsx/xform/sheet/page-setup-xform.js +4 -3
  18. package/dist/cjs/xlsx/xform/sheet/row-xform.js +1 -1
  19. package/dist/cjs/xlsx/xform/sheet/sheet-format-properties-xform.js +6 -3
  20. package/dist/cjs/xlsx/xform/sheet/sheet-view-xform.js +8 -4
  21. package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +2 -2
  22. package/dist/cjs/xlsx/xform/style/font-xform.js +36 -23
  23. package/dist/cjs/xlsx/xform/table/auto-filter-xform.js +3 -1
  24. package/dist/cjs/xlsx/xform/table/table-column-xform.js +2 -1
  25. package/dist/cjs/xlsx/xform/table/table-xform.js +5 -9
  26. package/dist/esm/doc/pivot-table.js +37 -3
  27. package/dist/esm/doc/worksheet.js +0 -1
  28. package/dist/esm/stream/xlsx/worksheet-writer.js +0 -1
  29. package/dist/esm/utils/datetime.js +7 -153
  30. package/dist/esm/xlsx/xform/book/sheet-xform.js +3 -2
  31. package/dist/esm/xlsx/xform/book/workbook-properties-xform.js +1 -1
  32. package/dist/esm/xlsx/xform/core/content-types-xform.js +12 -6
  33. package/dist/esm/xlsx/xform/pivot-table/cache-field.js +11 -4
  34. package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +4 -11
  35. package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +124 -17
  36. package/dist/esm/xlsx/xform/sheet/page-setup-xform.js +4 -3
  37. package/dist/esm/xlsx/xform/sheet/row-xform.js +1 -1
  38. package/dist/esm/xlsx/xform/sheet/sheet-format-properties-xform.js +6 -3
  39. package/dist/esm/xlsx/xform/sheet/sheet-view-xform.js +8 -4
  40. package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +2 -2
  41. package/dist/esm/xlsx/xform/style/font-xform.js +36 -23
  42. package/dist/esm/xlsx/xform/table/auto-filter-xform.js +3 -1
  43. package/dist/esm/xlsx/xform/table/table-column-xform.js +2 -1
  44. package/dist/esm/xlsx/xform/table/table-xform.js +5 -9
  45. package/dist/types/csv/csv-core.d.ts +0 -6
  46. package/dist/types/doc/pivot-table.d.ts +5 -1
  47. package/dist/types/stream/xlsx/workbook-reader.d.ts +1 -1
  48. package/dist/types/stream/xlsx/worksheet-reader.d.ts +1 -1
  49. package/dist/types/stream/xlsx/worksheet-writer.d.ts +1 -1
  50. package/dist/types/types.d.ts +1 -1
  51. package/dist/types/utils/datetime.d.ts +0 -29
  52. package/dist/types/xlsx/xform/pivot-table/cache-field.d.ts +5 -1
  53. package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +0 -5
  54. package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +0 -3
  55. package/dist/types/xlsx/xform/sheet/sheet-format-properties-xform.d.ts +1 -1
  56. package/dist/types/xlsx/xform/sheet/worksheet-xform.d.ts +1 -1
  57. package/dist/types/xlsx/xform/style/font-xform.d.ts +1 -0
  58. package/dist/types/xlsx/xform/table/table-xform.d.ts +0 -4
  59. package/package.json +1 -1
@@ -10,10 +10,13 @@ class SheetFormatPropertiesXform extends base_xform_1.BaseXform {
10
10
  if (model) {
11
11
  const attributes = {
12
12
  defaultRowHeight: model.defaultRowHeight,
13
- outlineLevelRow: model.outlineLevelRow,
14
- outlineLevelCol: model.outlineLevelCol,
15
- "x14ac:dyDescent": model.dyDescent
13
+ // Only output outlineLevelRow/Col when non-zero (matches Excel behavior)
14
+ outlineLevelRow: model.outlineLevelRow || undefined,
15
+ outlineLevelCol: model.outlineLevelCol || undefined,
16
+ // Only output dyDescent if explicitly set (MS extension, not ECMA-376 standard)
17
+ "x14ac:dyDescent": model.dyDescent || undefined
16
18
  };
19
+ // Only output defaultColWidth if explicitly set
17
20
  if (model.defaultColWidth) {
18
21
  attributes.defaultColWidth = model.defaultColWidth;
19
22
  }
@@ -23,16 +23,20 @@ class SheetViewXform extends base_xform_1.BaseXform {
23
23
  }
24
24
  }
25
25
  render(xmlStream, model) {
26
- xmlStream.openNode("sheetView", {
27
- workbookViewId: model.workbookViewId || 0
28
- });
26
+ // Build initial attributes with correct order to match Excel output
27
+ const initialAttrs = {};
28
+ if (model.tabSelected) {
29
+ initialAttrs.tabSelected = "1";
30
+ }
31
+ initialAttrs.workbookViewId = model.workbookViewId || 0;
32
+ xmlStream.openNode("sheetView", initialAttrs);
29
33
  const add = function (name, value, included) {
30
34
  if (included) {
31
35
  xmlStream.addAttribute(name, value);
32
36
  }
33
37
  };
34
38
  add("rightToLeft", "1", model.rightToLeft === true);
35
- add("tabSelected", "1", model.tabSelected);
39
+ // tabSelected is now in initialAttrs
36
40
  add("showRuler", "0", model.showRuler === false);
37
41
  add("showRowColHeaders", "0", model.showRowColHeaders === false);
38
42
  add("showGridLines", "0", model.showGridLines === false);
@@ -515,6 +515,6 @@ WorkSheetXform.WORKSHEET_ATTRIBUTES = {
515
515
  xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
516
516
  "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
517
517
  "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
518
- "mc:Ignorable": "x14ac",
519
- "xmlns:x14ac": "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"
518
+ "xmlns:x14ac": "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac",
519
+ "mc:Ignorable": "x14ac"
520
520
  };
@@ -12,35 +12,48 @@ class FontXform extends base_xform_1.BaseXform {
12
12
  constructor(options) {
13
13
  super();
14
14
  this.options = options || FontXform.OPTIONS;
15
- this.map = {
16
- b: { prop: "bold", xform: new boolean_xform_1.BooleanXform({ tag: "b", attr: "val" }) },
17
- i: { prop: "italic", xform: new boolean_xform_1.BooleanXform({ tag: "i", attr: "val" }) },
18
- u: { prop: "underline", xform: new underline_xform_1.UnderlineXform() },
19
- charset: { prop: "charset", xform: new integer_xform_1.IntegerXform({ tag: "charset", attr: "val" }) },
20
- color: { prop: "color", xform: new color_xform_1.ColorXform() },
21
- condense: { prop: "condense", xform: new boolean_xform_1.BooleanXform({ tag: "condense", attr: "val" }) },
22
- extend: { prop: "extend", xform: new boolean_xform_1.BooleanXform({ tag: "extend", attr: "val" }) },
23
- family: { prop: "family", xform: new integer_xform_1.IntegerXform({ tag: "family", attr: "val" }) },
24
- outline: { prop: "outline", xform: new boolean_xform_1.BooleanXform({ tag: "outline", attr: "val" }) },
25
- vertAlign: { prop: "vertAlign", xform: new string_xform_1.StringXform({ tag: "vertAlign", attr: "val" }) },
26
- scheme: { prop: "scheme", xform: new string_xform_1.StringXform({ tag: "scheme", attr: "val" }) },
27
- shadow: { prop: "shadow", xform: new boolean_xform_1.BooleanXform({ tag: "shadow", attr: "val" }) },
28
- strike: { prop: "strike", xform: new boolean_xform_1.BooleanXform({ tag: "strike", attr: "val" }) },
29
- sz: { prop: "size", xform: new integer_xform_1.IntegerXform({ tag: "sz", attr: "val" }) }
30
- };
31
- this.map[this.options.fontNameTag] = {
32
- prop: "name",
33
- xform: new string_xform_1.StringXform({ tag: this.options.fontNameTag, attr: "val" })
34
- };
15
+ // Define properties in render order (Excel's expected order)
16
+ const fontProperties = [
17
+ { tag: "b", prop: "bold", xform: new boolean_xform_1.BooleanXform({ tag: "b", attr: "val" }) },
18
+ { tag: "i", prop: "italic", xform: new boolean_xform_1.BooleanXform({ tag: "i", attr: "val" }) },
19
+ { tag: "u", prop: "underline", xform: new underline_xform_1.UnderlineXform() },
20
+ { tag: "strike", prop: "strike", xform: new boolean_xform_1.BooleanXform({ tag: "strike", attr: "val" }) },
21
+ {
22
+ tag: "condense",
23
+ prop: "condense",
24
+ xform: new boolean_xform_1.BooleanXform({ tag: "condense", attr: "val" })
25
+ },
26
+ { tag: "extend", prop: "extend", xform: new boolean_xform_1.BooleanXform({ tag: "extend", attr: "val" }) },
27
+ { tag: "outline", prop: "outline", xform: new boolean_xform_1.BooleanXform({ tag: "outline", attr: "val" }) },
28
+ { tag: "shadow", prop: "shadow", xform: new boolean_xform_1.BooleanXform({ tag: "shadow", attr: "val" }) },
29
+ { tag: "sz", prop: "size", xform: new integer_xform_1.IntegerXform({ tag: "sz", attr: "val" }) },
30
+ { tag: "color", prop: "color", xform: new color_xform_1.ColorXform() },
31
+ {
32
+ tag: this.options.fontNameTag,
33
+ prop: "name",
34
+ xform: new string_xform_1.StringXform({ tag: this.options.fontNameTag, attr: "val" })
35
+ },
36
+ { tag: "family", prop: "family", xform: new integer_xform_1.IntegerXform({ tag: "family", attr: "val" }) },
37
+ { tag: "scheme", prop: "scheme", xform: new string_xform_1.StringXform({ tag: "scheme", attr: "val" }) },
38
+ { tag: "charset", prop: "charset", xform: new integer_xform_1.IntegerXform({ tag: "charset", attr: "val" }) },
39
+ {
40
+ tag: "vertAlign",
41
+ prop: "vertAlign",
42
+ xform: new string_xform_1.StringXform({ tag: "vertAlign", attr: "val" })
43
+ }
44
+ ];
45
+ // Build map and renderOrder from single source of truth
46
+ this.map = Object.fromEntries(fontProperties.map(p => [p.tag, { prop: p.prop, xform: p.xform }]));
47
+ this.renderOrder = fontProperties.map(p => p.tag);
35
48
  }
36
49
  get tag() {
37
50
  return this.options.tagName;
38
51
  }
39
52
  render(xmlStream, model) {
40
- const { map } = this;
53
+ const { map, renderOrder } = this;
41
54
  xmlStream.openNode(this.options.tagName);
42
- Object.entries(this.map).forEach(([tag, defn]) => {
43
- map[tag].xform.render(xmlStream, model[defn.prop]);
55
+ renderOrder.forEach(tag => {
56
+ map[tag].xform.render(xmlStream, model[map[tag].prop]);
44
57
  });
45
58
  xmlStream.closeNode();
46
59
  }
@@ -20,7 +20,9 @@ class AutoFilterXform extends base_xform_1.BaseXform {
20
20
  });
21
21
  }
22
22
  render(xmlStream, model) {
23
- xmlStream.openNode(this.tag, { ref: model.autoFilterRef });
23
+ xmlStream.openNode(this.tag, {
24
+ ref: model.autoFilterRef
25
+ });
24
26
  model.columns.forEach((column) => {
25
27
  this.map.filterColumn.render(xmlStream, column);
26
28
  });
@@ -18,7 +18,8 @@ class TableColumnXform extends base_xform_1.BaseXform {
18
18
  id: model.id.toString(),
19
19
  name: model.name,
20
20
  totalsRowLabel: model.totalsRowLabel,
21
- totalsRowFunction: model.totalsRowFunction,
21
+ // Excel doesn't output totalsRowFunction when value is 'none'
22
+ totalsRowFunction: model.totalsRowFunction === "none" ? undefined : model.totalsRowFunction,
22
23
  dxfId: model.dxfId
23
24
  });
24
25
  }
@@ -43,8 +43,8 @@ class TableXform extends base_xform_1.BaseXform {
43
43
  displayName: model.displayName || model.name,
44
44
  ref: model.tableRef,
45
45
  totalsRowCount: model.totalsRow ? "1" : undefined,
46
- totalsRowShown: model.totalsRow ? undefined : "1",
47
- headerRowCount: model.headerRow ? "1" : "0"
46
+ // Excel doesn't output headerRowCount when it's 1 (default) or when there's a header row
47
+ headerRowCount: model.headerRow ? undefined : "0"
48
48
  });
49
49
  this.map.autoFilter.render(xmlStream, model);
50
50
  this.map.tableColumns.render(xmlStream, model.columns);
@@ -65,7 +65,8 @@ class TableXform extends base_xform_1.BaseXform {
65
65
  displayName: attributes.displayName || attributes.name,
66
66
  tableRef: attributes.ref,
67
67
  totalsRow: attributes.totalsRowCount === "1",
68
- headerRow: attributes.headerRowCount === "1"
68
+ // ECMA-376: headerRowCount defaults to 1, so missing attribute means has header
69
+ headerRow: attributes.headerRowCount !== "0"
69
70
  };
70
71
  break;
71
72
  default:
@@ -124,10 +125,5 @@ class TableXform extends base_xform_1.BaseXform {
124
125
  }
125
126
  exports.TableXform = TableXform;
126
127
  TableXform.TABLE_ATTRIBUTES = {
127
- xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
128
- "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
129
- "mc:Ignorable": "xr xr3",
130
- "xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision",
131
- "xmlns:xr3": "http://schemas.microsoft.com/office/spreadsheetml/2016/revision3"
132
- // 'xr:uid': '{00000000-000C-0000-FFFF-FFFF00000000}',
128
+ xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
133
129
  };
@@ -34,8 +34,11 @@ function createTableSourceAdapter(table) {
34
34
  const endRow = startRow + tableModel.rows.length; // header row + data rows
35
35
  const endCol = startCol + columnNames.length - 1;
36
36
  const shortRange = colCache.encode(startRow, startCol, endRow, endCol);
37
+ // Use the worksheet name (not table name) for pivotCacheDefinition's worksheetSource
38
+ // The sheet attribute in worksheetSource must reference the actual worksheet name
39
+ const worksheetName = table.worksheet.name;
37
40
  return {
38
- name: tableModel.name,
41
+ name: worksheetName,
39
42
  getRow(rowNumber) {
40
43
  if (rowNumber === 1) {
41
44
  return { values: headerRow };
@@ -184,12 +187,43 @@ function makeCacheFields(source, fieldNamesWithSharedItems) {
184
187
  }
185
188
  return toSortedArray(uniqueValues);
186
189
  };
190
+ // Calculate min/max for numeric fields
191
+ const getMinMax = (columnIndex) => {
192
+ const columnValues = source.getColumn(columnIndex).values;
193
+ let min = Infinity;
194
+ let max = -Infinity;
195
+ let hasNumeric = false;
196
+ for (let i = 2; i < columnValues.length; i++) {
197
+ const v = columnValues[i];
198
+ if (typeof v === "number" && !isNaN(v)) {
199
+ hasNumeric = true;
200
+ if (v < min) {
201
+ min = v;
202
+ }
203
+ if (v > max) {
204
+ max = v;
205
+ }
206
+ }
207
+ }
208
+ return hasNumeric ? { minValue: min, maxValue: max } : null;
209
+ };
187
210
  // Build result array
188
211
  const result = [];
189
212
  for (const columnIndex of range(1, names.length)) {
190
213
  const name = names[columnIndex];
191
- const sharedItems = sharedItemsFields.has(name) ? aggregate(columnIndex) : null;
192
- result.push({ name, sharedItems });
214
+ if (sharedItemsFields.has(name)) {
215
+ result.push({ name, sharedItems: aggregate(columnIndex) });
216
+ }
217
+ else {
218
+ // Numeric field - calculate min/max
219
+ const minMax = getMinMax(columnIndex);
220
+ result.push({
221
+ name,
222
+ sharedItems: null,
223
+ minValue: minMax?.minValue,
224
+ maxValue: minMax?.maxValue
225
+ });
226
+ }
193
227
  }
194
228
  return result;
195
229
  }
@@ -40,7 +40,6 @@ class Worksheet {
40
40
  // for tabColor, default row height, outline levels, etc
41
41
  this.properties = Object.assign({}, {
42
42
  defaultRowHeight: 15,
43
- dyDescent: 55,
44
43
  outlineLevelCol: 0,
45
44
  outlineLevelRow: 0
46
45
  }, options.properties);
@@ -95,7 +95,6 @@ class WorksheetWriter {
95
95
  // for default row height, outline levels, etc
96
96
  this.properties = Object.assign({}, {
97
97
  defaultRowHeight: 15,
98
- dyDescent: 55,
99
98
  outlineLevelCol: 0,
100
99
  outlineLevelRow: 0
101
100
  }, options.properties);
@@ -11,7 +11,6 @@
11
11
  const PAD2 = Array.from({ length: 60 }, (_, i) => (i < 10 ? `0${i}` : `${i}`));
12
12
  // Character codes for fast comparison
13
13
  const C_0 = 48;
14
- const C_9 = 57;
15
14
  const C_DASH = 45;
16
15
  const C_SLASH = 47;
17
16
  const C_COLON = 58;
@@ -187,158 +186,6 @@ const AUTO_DETECT = [
187
186
  [29, [parseISOMsOffset]]
188
187
  ];
189
188
  // ============================================================================
190
- // Public API
191
- // ============================================================================
192
- /**
193
- * Quick pre-filter to reject non-date strings
194
- */
195
- export function mightBeDate(s) {
196
- const len = s.length;
197
- if (len < 10 || len > 30) {
198
- return false;
199
- }
200
- const c0 = s.charCodeAt(0);
201
- const c1 = s.charCodeAt(1);
202
- // Must start with digits
203
- if (c0 < C_0 || c0 > C_9 || c1 < C_0 || c1 > C_9) {
204
- return false;
205
- }
206
- const c2 = s.charCodeAt(2);
207
- // Either YYYY format (4 digits) or MM/DD format (separator at pos 2)
208
- return (c2 >= C_0 && c2 <= C_9) || c2 === C_DASH || c2 === C_SLASH;
209
- }
210
- /**
211
- * Parse a date string
212
- *
213
- * @param value - String to parse
214
- * @param formats - Specific formats to try (optional, auto-detects ISO if omitted)
215
- * @returns Date object or null
216
- *
217
- * @example
218
- * parseDate("2024-12-26") // auto-detect ISO
219
- * parseDate("12-26-2024", ["MM-DD-YYYY"]) // explicit US format
220
- */
221
- export function parseDate(value, formats) {
222
- if (!value || typeof value !== "string") {
223
- return null;
224
- }
225
- const s = value.trim();
226
- if (!s) {
227
- return null;
228
- }
229
- // Explicit formats
230
- if (formats?.length) {
231
- for (const fmt of formats) {
232
- const result = PARSERS[fmt]?.(s);
233
- if (result) {
234
- return result;
235
- }
236
- }
237
- return null;
238
- }
239
- // Auto-detect by length
240
- const len = s.length;
241
- for (const [l, parsers] of AUTO_DETECT) {
242
- if (len === l) {
243
- for (const p of parsers) {
244
- const result = p(s);
245
- if (result) {
246
- return result;
247
- }
248
- }
249
- }
250
- }
251
- return null;
252
- }
253
- /**
254
- * Format a Date to string
255
- *
256
- * @param date - Date to format
257
- * @param format - Format template (optional, defaults to ISO)
258
- * @param utc - Use UTC time (default: false)
259
- * @returns Formatted string or empty string if invalid
260
- *
261
- * @example
262
- * formatDate(date) // "2024-12-26T10:30:00.000+08:00"
263
- * formatDate(date, "YYYY-MM-DD", true) // "2024-12-26"
264
- */
265
- export function formatDate(date, format, utc = false) {
266
- if (!(date instanceof Date)) {
267
- return "";
268
- }
269
- const t = date.getTime();
270
- if (t !== t) {
271
- return "";
272
- } // NaN check (faster than isNaN)
273
- // Default ISO format - direct string building (faster than toISOString)
274
- if (!format) {
275
- if (utc) {
276
- const y = date.getUTCFullYear();
277
- const M = date.getUTCMonth() + 1;
278
- const D = date.getUTCDate();
279
- const H = date.getUTCHours();
280
- const m = date.getUTCMinutes();
281
- const s = date.getUTCSeconds();
282
- const ms = date.getUTCMilliseconds();
283
- return `${y}-${PAD2[M]}-${PAD2[D]}T${PAD2[H]}:${PAD2[m]}:${PAD2[s]}.${ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms}Z`;
284
- }
285
- const y = date.getFullYear();
286
- const M = date.getMonth() + 1;
287
- const D = date.getDate();
288
- const H = date.getHours();
289
- const m = date.getMinutes();
290
- const s = date.getSeconds();
291
- const ms = date.getMilliseconds();
292
- return `${y}-${PAD2[M]}-${PAD2[D]}T${PAD2[H]}:${PAD2[m]}:${PAD2[s]}.${ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms}${tzOffset(date)}`;
293
- }
294
- // Fast paths for common formats
295
- if (format === "YYYY-MM-DD") {
296
- return utc
297
- ? `${date.getUTCFullYear()}-${PAD2[date.getUTCMonth() + 1]}-${PAD2[date.getUTCDate()]}`
298
- : `${date.getFullYear()}-${PAD2[date.getMonth() + 1]}-${PAD2[date.getDate()]}`;
299
- }
300
- return renderFormat(date, format, utc);
301
- }
302
- function tzOffset(d) {
303
- const off = -d.getTimezoneOffset();
304
- const sign = off >= 0 ? "+" : "-";
305
- const h = (Math.abs(off) / 60) | 0; // Bitwise OR faster than Math.floor
306
- const m = Math.abs(off) % 60;
307
- return `${sign}${PAD2[h]}:${PAD2[m]}`;
308
- }
309
- function renderFormat(d, fmt, utc) {
310
- // Handle escaped sections [...]
311
- const esc = [];
312
- let out = fmt.replace(/\[([^\]]*)\]/g, (_, c) => {
313
- esc.push(c);
314
- return `\x00${esc.length - 1}\x00`;
315
- });
316
- // Get components once
317
- const y = utc ? d.getUTCFullYear() : d.getFullYear();
318
- const M = utc ? d.getUTCMonth() + 1 : d.getMonth() + 1;
319
- const D = utc ? d.getUTCDate() : d.getDate();
320
- const H = utc ? d.getUTCHours() : d.getHours();
321
- const m = utc ? d.getUTCMinutes() : d.getMinutes();
322
- const s = utc ? d.getUTCSeconds() : d.getSeconds();
323
- const ms = utc ? d.getUTCMilliseconds() : d.getMilliseconds();
324
- // Replace tokens
325
- out = out
326
- .replace(/YYYY/g, String(y))
327
- .replace(/SSS/g, ms < 10 ? `00${ms}` : ms < 100 ? `0${ms}` : String(ms))
328
- .replace(/MM/g, PAD2[M])
329
- .replace(/DD/g, PAD2[D])
330
- .replace(/HH/g, PAD2[H])
331
- .replace(/mm/g, PAD2[m])
332
- .replace(/ss/g, PAD2[s])
333
- .replace(/Z/g, utc ? "Z" : tzOffset(d));
334
- // Restore escaped
335
- if (esc.length) {
336
- // oxlint-disable-next-line no-control-regex
337
- out = out.replace(/\x00(\d+)\x00/g, (_, i) => esc[+i]);
338
- }
339
- return out;
340
- }
341
- // ============================================================================
342
189
  // High-performance batch processors (class-based for state encapsulation)
343
190
  // ============================================================================
344
191
  /**
@@ -411,6 +258,13 @@ export class DateParser {
411
258
  return out;
412
259
  }
413
260
  }
261
+ function tzOffset(d) {
262
+ const off = -d.getTimezoneOffset();
263
+ const sign = off >= 0 ? "+" : "-";
264
+ const h = (Math.abs(off) / 60) | 0; // Bitwise OR faster than Math.floor
265
+ const m = Math.abs(off) % 60;
266
+ return `${sign}${PAD2[h]}:${PAD2[m]}`;
267
+ }
414
268
  /**
415
269
  * Optimized date formatter for batch processing
416
270
  *
@@ -3,9 +3,10 @@ import { BaseXform } from "../base-xform.js";
3
3
  class WorksheetXform extends BaseXform {
4
4
  render(xmlStream, model) {
5
5
  xmlStream.leafNode("sheet", {
6
- sheetId: model.id,
7
6
  name: model.name,
8
- state: model.state,
7
+ sheetId: model.id,
8
+ // Excel doesn't output state when it's 'visible' (default)
9
+ state: model.state === "visible" ? undefined : model.state,
9
10
  "r:id": model.rId
10
11
  });
11
12
  }
@@ -3,7 +3,7 @@ class WorkbookPropertiesXform extends BaseXform {
3
3
  render(xmlStream, model) {
4
4
  xmlStream.leafNode("workbookPr", {
5
5
  date1904: model.date1904 ? 1 : undefined,
6
- defaultThemeVersion: 164011,
6
+ // Excel doesn't output defaultThemeVersion
7
7
  filterPrivacy: 1
8
8
  });
9
9
  }
@@ -38,7 +38,7 @@ class ContentTypesXform extends BaseXform {
38
38
  });
39
39
  });
40
40
  if ((model.pivotTables || []).length) {
41
- // Add content types for each pivot table
41
+ // Add content types for pivot cache (definition and records)
42
42
  (model.pivotTables || []).forEach((pivotTable) => {
43
43
  const n = pivotTable.tableNumber;
44
44
  xmlStream.leafNode("Override", {
@@ -49,10 +49,6 @@ class ContentTypesXform extends BaseXform {
49
49
  PartName: `/xl/pivotCache/pivotCacheRecords${n}.xml`,
50
50
  ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"
51
51
  });
52
- xmlStream.leafNode("Override", {
53
- PartName: `/xl/pivotTables/pivotTable${n}.xml`,
54
- ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
55
- });
56
52
  });
57
53
  }
58
54
  xmlStream.leafNode("Override", {
@@ -78,6 +74,16 @@ class ContentTypesXform extends BaseXform {
78
74
  });
79
75
  });
80
76
  }
77
+ // Add pivot table overrides after tables (matches Excel order)
78
+ if ((model.pivotTables || []).length) {
79
+ (model.pivotTables || []).forEach((pivotTable) => {
80
+ const n = pivotTable.tableNumber;
81
+ xmlStream.leafNode("Override", {
82
+ PartName: `/xl/pivotTables/pivotTable${n}.xml`,
83
+ ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
84
+ });
85
+ });
86
+ }
81
87
  if (model.drawings) {
82
88
  model.drawings.forEach((drawing) => {
83
89
  xmlStream.leafNode("Override", {
@@ -86,7 +92,7 @@ class ContentTypesXform extends BaseXform {
86
92
  });
87
93
  });
88
94
  }
89
- if (model.commentRefs) {
95
+ if (model.commentRefs && model.commentRefs.length) {
90
96
  xmlStream.leafNode("Default", {
91
97
  Extension: "vml",
92
98
  ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
@@ -1,6 +1,6 @@
1
1
  import { xmlEncode } from "../../../utils/utils.js";
2
2
  class CacheField {
3
- constructor({ name, sharedItems }) {
3
+ constructor({ name, sharedItems, minValue, maxValue }) {
4
4
  // string type
5
5
  //
6
6
  // {
@@ -14,10 +14,14 @@ class CacheField {
14
14
  //
15
15
  // {
16
16
  // 'name': 'D',
17
- // 'sharedItems': null
17
+ // 'sharedItems': null,
18
+ // 'minValue': 5,
19
+ // 'maxValue': 45
18
20
  // }
19
21
  this.name = name;
20
22
  this.sharedItems = sharedItems;
23
+ this.minValue = minValue;
24
+ this.maxValue = maxValue;
21
25
  }
22
26
  render() {
23
27
  // PivotCache Field: http://www.datypic.com/sc/ooxml/e-ssml_cacheField-1.html
@@ -26,9 +30,12 @@ class CacheField {
26
30
  const escapedName = xmlEncode(this.name);
27
31
  // integer types
28
32
  if (this.sharedItems === null) {
29
- // TK(2023-07-18): left out attributes... minValue="5" maxValue="45"
33
+ // Build minValue/maxValue attributes if available
34
+ const minMaxAttrs = this.minValue !== undefined && this.maxValue !== undefined
35
+ ? ` minValue="${this.minValue}" maxValue="${this.maxValue}"`
36
+ : "";
30
37
  return `<cacheField name="${escapedName}" numFmtId="0">
31
- <sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1" />
38
+ <sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1"${minMaxAttrs} />
32
39
  </cacheField>`;
33
40
  }
34
41
  // string types - escape XML special characters in each shared item value
@@ -43,17 +43,17 @@ class PivotCacheDefinitionXform extends BaseXform {
43
43
  */
44
44
  renderNew(xmlStream, model) {
45
45
  const { source, cacheFields } = model;
46
+ // Record count = number of data rows (excluding header row)
47
+ const recordCount = source.getSheetValues().slice(2).length;
46
48
  xmlStream.openXml(XmlStream.StdDocAttributes);
47
49
  xmlStream.openNode(this.tag, {
48
50
  ...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
49
51
  "r:id": "rId1",
50
52
  refreshOnLoad: "1", // important for our implementation to work
51
- refreshedBy: "Author",
52
- refreshedDate: "45125.026046874998",
53
53
  createdVersion: "8",
54
54
  refreshedVersion: "8",
55
55
  minRefreshableVersion: "3",
56
- recordCount: cacheFields.length + 1
56
+ recordCount
57
57
  });
58
58
  xmlStream.openNode("cacheSource", { type: "worksheet" });
59
59
  xmlStream.leafNode("worksheetSource", {
@@ -77,8 +77,6 @@ class PivotCacheDefinitionXform extends BaseXform {
77
77
  ...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
78
78
  "r:id": model.rId || "rId1",
79
79
  refreshOnLoad: model.refreshOnLoad || "1",
80
- refreshedBy: model.refreshedBy || "Author",
81
- refreshedDate: model.refreshedDate || "45125.026046874998",
82
80
  createdVersion: model.createdVersion || "8",
83
81
  refreshedVersion: model.refreshedVersion || "8",
84
82
  minRefreshableVersion: model.minRefreshableVersion || "3",
@@ -112,8 +110,6 @@ class PivotCacheDefinitionXform extends BaseXform {
112
110
  cacheFields: [],
113
111
  rId: attributes["r:id"],
114
112
  refreshOnLoad: attributes.refreshOnLoad,
115
- refreshedBy: attributes.refreshedBy,
116
- refreshedDate: attributes.refreshedDate,
117
113
  createdVersion: attributes.createdVersion,
118
114
  refreshedVersion: attributes.refreshedVersion,
119
115
  minRefreshableVersion: attributes.minRefreshableVersion,
@@ -178,9 +174,6 @@ class PivotCacheDefinitionXform extends BaseXform {
178
174
  }
179
175
  PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES = {
180
176
  xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
181
- "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
182
- "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
183
- "mc:Ignorable": "xr",
184
- "xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
177
+ "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
185
178
  };
186
179
  export { PivotCacheDefinitionXform };